From 5957bc2d3ea0628b192f2d27aadaa06f7c5351b4 Mon Sep 17 00:00:00 2001 From: Souryo Date: Wed, 30 Aug 2017 18:31:27 -0400 Subject: [PATCH] Debugger: LUA scripting --- Core/APU.cpp | 12 + Core/APU.h | 2 + Core/ApuEnvelope.h | 12 + Core/ApuFrameCounter.h | 9 + Core/ApuLengthCounter.h | 11 +- Core/BaseApuChannel.h | 2 +- Core/BaseMapper.cpp | 21 + Core/BaseMapper.h | 2 + Core/CheatManager.cpp | 5 + Core/CheatManager.h | 8 +- Core/Console.cpp | 20 +- Core/Console.h | 2 +- Core/Core.vcxproj | 17 + Core/Core.vcxproj.filters | 60 + Core/DebugHud.cpp | 67 + Core/DebugHud.h | 26 + Core/Debugger.cpp | 93 +- Core/Debugger.h | 20 +- Core/DebuggerTypes.h | 14 + Core/DefaultVideoFilter.cpp | 14 +- Core/DeltaModulationChannel.cpp | 14 + Core/DeltaModulationChannel.h | 2 + Core/DrawCommand.h | 59 + Core/DrawLineCommand.h | 42 + Core/DrawPixelCommand.h | 24 + Core/DrawRectangleCommand.h | 40 + Core/DrawStringCommand.h | 163 + Core/ExpressionEvaluator.cpp | 2 +- Core/HdAudioDevice.cpp | 2 +- Core/LuaApi.cpp | 825 ++ Core/LuaApi.h | 72 + Core/LuaCallHelper.cpp | 117 + Core/LuaCallHelper.h | 31 + Core/LuaScriptingContext.cpp | 67 + Core/LuaScriptingContext.h | 20 + Core/MemoryDumper.cpp | 35 +- Core/MemoryDumper.h | 6 +- Core/MemoryManager.cpp | 26 +- Core/MemoryManager.h | 4 +- Core/NoiseChannel.h | 14 + Core/OggReader.cpp | 2 +- Core/PPU.h | 5 + Core/ScriptHost.cpp | 60 + Core/ScriptHost.h | 25 + Core/ScriptingContext.cpp | 87 + Core/ScriptingContext.h | 46 + Core/SquareChannel.h | 18 + Core/StandardController.cpp | 8 +- Core/StandardController.h | 2 +- Core/TriangleChannel.h | 13 +- Core/Types.h | 94 +- GUI.NET/Config/DebugInfo.cs | 23 + GUI.NET/Controls/ctrlSplitContainer.cs | 2 +- GUI.NET/Debugger/DebugWindowManager.cs | 4 +- .../FastColoredTextBox/AutocompleteItem.cs | 266 + .../FastColoredTextBox/AutocompleteMenu.cs | 750 ++ .../Debugger/FastColoredTextBox/Bookmarks.cs | 256 + GUI.NET/Debugger/FastColoredTextBox/Char.cs | 26 + .../FastColoredTextBox/CommandManager.cs | 239 + .../Debugger/FastColoredTextBox/Commands.cs | 809 ++ .../FastColoredTextBox/DocumentMap.cs | 250 + .../FastColoredTextBox/EncodingDetector.cs | 363 + .../FastColoredTextBox/ExportToHTML.cs | 222 + .../FastColoredTextBox/ExportToRTF.cs | 217 + .../FastColoredTextBox/FastColoredTextBox.cs | 8466 +++++++++++++++++ .../FastColoredTextBox.resx | 120 + .../FastColoredTextBox/FileTextSource.cs | 453 + .../Debugger/FastColoredTextBox/FindForm.cs | 129 + .../FastColoredTextBox/FindForm.designer.cs | 146 + .../Debugger/FastColoredTextBox/FindForm.resx | 120 + .../Debugger/FastColoredTextBox/GoToForm.cs | 53 + .../FastColoredTextBox/GoToForm.designer.cs | 110 + .../Debugger/FastColoredTextBox/GoToForm.resx | 120 + GUI.NET/Debugger/FastColoredTextBox/Hints.cs | 364 + .../Debugger/FastColoredTextBox/Hotkeys.cs | 251 + .../FastColoredTextBox/HotkeysEditorForm.cs | 179 + .../HotkeysEditorForm.designer.cs | 210 + .../FastColoredTextBox/HotkeysEditorForm.resx | 129 + .../FastColoredTextBox/LimitedStack.cs | 105 + GUI.NET/Debugger/FastColoredTextBox/Line.cs | 289 + .../FastColoredTextBox/LinesAccessor.cs | 98 + .../FastColoredTextBox/MacrosManager.cs | 183 + .../FastColoredTextBox/MonoUtility.cs | 21 + .../FastColoredTextBox/NativeMethods.cs | 62 + .../NativeMethodsWrapper.cs | 129 + GUI.NET/Debugger/FastColoredTextBox/Place.cs | 99 + .../FastColoredTextBox/PlatformType.cs | 60 + GUI.NET/Debugger/FastColoredTextBox/Range.cs | 1678 ++++ .../FastColoredTextBox/ReplaceForm.cs | 187 + .../ReplaceForm.designer.cs | 196 + .../FastColoredTextBox/ReplaceForm.resx | 120 + GUI.NET/Debugger/FastColoredTextBox/Ruler.cs | 138 + .../FastColoredTextBox/Ruler.designer.cs | 37 + GUI.NET/Debugger/FastColoredTextBox/Style.cs | 430 + .../FastColoredTextBox/SyntaxDescriptor.cs | 51 + .../FastColoredTextBox/SyntaxHighlighter.cs | 1383 +++ .../Debugger/FastColoredTextBox/TextSource.cs | 338 + .../FastColoredTextBox/TypeDescriptor.cs | 96 + .../FastColoredTextBox/UnfocusablePanel.cs | 41 + .../FastColoredTextBox/VisualMarker.cs | 106 + GUI.NET/Debugger/frmDebugger.Designer.cs | 56 +- GUI.NET/Debugger/frmDebugger.cs | 5 + GUI.NET/Debugger/frmScript.Designer.cs | 560 ++ GUI.NET/Debugger/frmScript.cs | 453 + GUI.NET/Debugger/frmScript.resx | 141 + GUI.NET/Forms/frmMain.Designer.cs | 50 +- GUI.NET/Forms/frmMain.Tools.cs | 5 + GUI.NET/Forms/frmMain.cs | 9 + GUI.NET/GUI.NET.csproj | 97 + GUI.NET/InteropEmu.cs | 5 + GUI.NET/Properties/Resources.Designer.cs | 30 + GUI.NET/Properties/Resources.resx | 9 + GUI.NET/Resources/Enum.png | Bin 0 -> 250 bytes GUI.NET/Resources/Function.png | Bin 0 -> 501 bytes GUI.NET/Resources/Script.png | Bin 0 -> 644 bytes InteropDLL/DebugWrapper.cpp | 4 + InteropDLL/stdafx.h | 1 + Lua/Lua.vcxproj | 367 + Lua/Lua.vcxproj.filters | 200 + Lua/lapi.c | 1298 +++ Lua/lapi.h | 24 + Lua/lauxlib.c | 1043 ++ Lua/lauxlib.h | 264 + Lua/lbaselib.c | 498 + Lua/lbitlib.c | 233 + Lua/lcode.c | 1203 +++ Lua/lcode.h | 88 + Lua/lcorolib.c | 168 + Lua/lctype.c | 55 + Lua/lctype.h | 95 + Lua/ldblib.c | 456 + Lua/ldebug.c | 698 ++ Lua/ldebug.h | 39 + Lua/ldo.c | 802 ++ Lua/ldo.h | 58 + Lua/ldump.c | 215 + Lua/lfunc.c | 151 + Lua/lfunc.h | 61 + Lua/lgc.c | 1178 +++ Lua/lgc.h | 147 + Lua/linit.c | 68 + Lua/liolib.c | 771 ++ Lua/llex.c | 565 ++ Lua/llex.h | 85 + Lua/llimits.h | 323 + Lua/lmathlib.c | 410 + Lua/lmem.c | 100 + Lua/lmem.h | 69 + Lua/loadlib.c | 790 ++ Lua/lobject.c | 521 + Lua/lobject.h | 549 ++ Lua/lopcodes.c | 124 + Lua/lopcodes.h | 297 + Lua/loslib.c | 407 + Lua/lparser.c | 1650 ++++ Lua/lparser.h | 133 + Lua/lprefix.h | 45 + Lua/lstate.c | 347 + Lua/lstate.h | 235 + Lua/lstring.c | 248 + Lua/lstring.h | 49 + Lua/lstrlib.c | 1584 +++ Lua/ltable.c | 669 ++ Lua/ltable.h | 66 + Lua/ltablib.c | 450 + Lua/ltm.c | 165 + Lua/ltm.h | 76 + Lua/lua.c | 612 ++ Lua/lua.h | 486 + Lua/lua.hpp | 9 + Lua/luac.c | 449 + Lua/luaconf.h | 783 ++ Lua/lualib.h | 61 + Lua/lundump.c | 279 + Lua/lundump.h | 32 + Lua/lutf8lib.c | 256 + Lua/lvm.c | 1322 +++ Lua/lvm.h | 113 + Lua/lzio.c | 68 + Lua/lzio.h | 66 + Mesen.sln | 28 +- 181 files changed, 48489 insertions(+), 103 deletions(-) create mode 100644 Core/DebugHud.cpp create mode 100644 Core/DebugHud.h create mode 100644 Core/DrawCommand.h create mode 100644 Core/DrawLineCommand.h create mode 100644 Core/DrawPixelCommand.h create mode 100644 Core/DrawRectangleCommand.h create mode 100644 Core/DrawStringCommand.h create mode 100644 Core/LuaApi.cpp create mode 100644 Core/LuaApi.h create mode 100644 Core/LuaCallHelper.cpp create mode 100644 Core/LuaCallHelper.h create mode 100644 Core/LuaScriptingContext.cpp create mode 100644 Core/LuaScriptingContext.h create mode 100644 Core/ScriptHost.cpp create mode 100644 Core/ScriptHost.h create mode 100644 Core/ScriptingContext.cpp create mode 100644 Core/ScriptingContext.h create mode 100644 GUI.NET/Debugger/FastColoredTextBox/AutocompleteItem.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/AutocompleteMenu.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/Bookmarks.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/Char.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/CommandManager.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/Commands.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/DocumentMap.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/EncodingDetector.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/ExportToHTML.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/ExportToRTF.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/FastColoredTextBox.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/FastColoredTextBox.resx create mode 100644 GUI.NET/Debugger/FastColoredTextBox/FileTextSource.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/FindForm.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/FindForm.designer.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/FindForm.resx create mode 100644 GUI.NET/Debugger/FastColoredTextBox/GoToForm.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/GoToForm.designer.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/GoToForm.resx create mode 100644 GUI.NET/Debugger/FastColoredTextBox/Hints.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/Hotkeys.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/HotkeysEditorForm.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/HotkeysEditorForm.designer.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/HotkeysEditorForm.resx create mode 100644 GUI.NET/Debugger/FastColoredTextBox/LimitedStack.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/Line.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/LinesAccessor.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/MacrosManager.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/MonoUtility.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/NativeMethods.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/NativeMethodsWrapper.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/Place.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/PlatformType.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/Range.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/ReplaceForm.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/ReplaceForm.designer.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/ReplaceForm.resx create mode 100644 GUI.NET/Debugger/FastColoredTextBox/Ruler.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/Ruler.designer.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/Style.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/SyntaxDescriptor.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/SyntaxHighlighter.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/TextSource.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/TypeDescriptor.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/UnfocusablePanel.cs create mode 100644 GUI.NET/Debugger/FastColoredTextBox/VisualMarker.cs create mode 100644 GUI.NET/Debugger/frmScript.Designer.cs create mode 100644 GUI.NET/Debugger/frmScript.cs create mode 100644 GUI.NET/Debugger/frmScript.resx create mode 100644 GUI.NET/Resources/Enum.png create mode 100644 GUI.NET/Resources/Function.png create mode 100644 GUI.NET/Resources/Script.png create mode 100644 Lua/Lua.vcxproj create mode 100644 Lua/Lua.vcxproj.filters create mode 100644 Lua/lapi.c create mode 100644 Lua/lapi.h create mode 100644 Lua/lauxlib.c create mode 100644 Lua/lauxlib.h create mode 100644 Lua/lbaselib.c create mode 100644 Lua/lbitlib.c create mode 100644 Lua/lcode.c create mode 100644 Lua/lcode.h create mode 100644 Lua/lcorolib.c create mode 100644 Lua/lctype.c create mode 100644 Lua/lctype.h create mode 100644 Lua/ldblib.c create mode 100644 Lua/ldebug.c create mode 100644 Lua/ldebug.h create mode 100644 Lua/ldo.c create mode 100644 Lua/ldo.h create mode 100644 Lua/ldump.c create mode 100644 Lua/lfunc.c create mode 100644 Lua/lfunc.h create mode 100644 Lua/lgc.c create mode 100644 Lua/lgc.h create mode 100644 Lua/linit.c create mode 100644 Lua/liolib.c create mode 100644 Lua/llex.c create mode 100644 Lua/llex.h create mode 100644 Lua/llimits.h create mode 100644 Lua/lmathlib.c create mode 100644 Lua/lmem.c create mode 100644 Lua/lmem.h create mode 100644 Lua/loadlib.c create mode 100644 Lua/lobject.c create mode 100644 Lua/lobject.h create mode 100644 Lua/lopcodes.c create mode 100644 Lua/lopcodes.h create mode 100644 Lua/loslib.c create mode 100644 Lua/lparser.c create mode 100644 Lua/lparser.h create mode 100644 Lua/lprefix.h create mode 100644 Lua/lstate.c create mode 100644 Lua/lstate.h create mode 100644 Lua/lstring.c create mode 100644 Lua/lstring.h create mode 100644 Lua/lstrlib.c create mode 100644 Lua/ltable.c create mode 100644 Lua/ltable.h create mode 100644 Lua/ltablib.c create mode 100644 Lua/ltm.c create mode 100644 Lua/ltm.h create mode 100644 Lua/lua.c create mode 100644 Lua/lua.h create mode 100644 Lua/lua.hpp create mode 100644 Lua/luac.c create mode 100644 Lua/luaconf.h create mode 100644 Lua/lualib.h create mode 100644 Lua/lundump.c create mode 100644 Lua/lundump.h create mode 100644 Lua/lutf8lib.c create mode 100644 Lua/lvm.c create mode 100644 Lua/lvm.h create mode 100644 Lua/lzio.c create mode 100644 Lua/lzio.h diff --git a/Core/APU.cpp b/Core/APU.cpp index 73ee64dc..743617e8 100644 --- a/Core/APU.cpp +++ b/Core/APU.cpp @@ -254,4 +254,16 @@ bool APU::IsApuEnabled() //load over the entire PPU frame, like what was done before. //This is most likely due to the timing of the Frame Counter & DMC IRQs. return _apuEnabled; +} + +ApuState APU::GetState() +{ + ApuState state; + state.Dmc = _deltaModulationChannel->GetState(); + state.FrameCounter = _frameCounter->GetState(); + state.Noise = _noiseChannel->GetState(); + state.Square1 = _squareChannel[0]->GetState(); + state.Square2 = _squareChannel[1]->GetState(); + state.Triangle = _triangleChannel->GetState(); + return state; } \ No newline at end of file diff --git a/Core/APU.h b/Core/APU.h index e8833200..91e26d52 100644 --- a/Core/APU.h +++ b/Core/APU.h @@ -62,6 +62,8 @@ class APU : public Snapshotable, public IMemoryHandler void WriteRAM(uint16_t addr, uint8_t value) override; void GetMemoryRanges(MemoryRanges &ranges) override; + ApuState GetState(); + void Exec(); __forceinline static void ExecStatic() diff --git a/Core/ApuEnvelope.h b/Core/ApuEnvelope.h index 1df00fd6..7734b879 100644 --- a/Core/ApuEnvelope.h +++ b/Core/ApuEnvelope.h @@ -81,4 +81,16 @@ public: _divider = _volume; } } + + ApuEnvelopeState GetState() + { + ApuEnvelopeState state; + state.ConstantVolume = _constantVolume; + state.Counter = _counter; + state.Divider = _divider; + state.Loop = _lengthCounterHalt; + state.StartFlag = _start; + state.Volume = _volume; + return state; + } }; diff --git a/Core/ApuFrameCounter.h b/Core/ApuFrameCounter.h index 56d9aa72..29f2b580 100644 --- a/Core/ApuFrameCounter.h +++ b/Core/ApuFrameCounter.h @@ -214,4 +214,13 @@ public: CPU::ClearIRQSource(IRQSource::FrameCounter); } } + + ApuFrameCounterState GetState() + { + ApuFrameCounterState state; + state.IrqEnabled = !_inhibitIRQ; + state.SequencePosition = _currentStep; + state.FiveStepMode = _stepMode == 1; + return state; + } }; \ No newline at end of file diff --git a/Core/ApuLengthCounter.h b/Core/ApuLengthCounter.h index 691c6568..df74b2dc 100644 --- a/Core/ApuLengthCounter.h +++ b/Core/ApuLengthCounter.h @@ -6,11 +6,11 @@ class ApuLengthCounter : public BaseApuChannel { private: uint8_t _lcLookupTable[32] = { 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30 }; - bool _enabled = false; bool _newHaltValue; static bool _needToRun; protected: + bool _enabled = false; bool _lengthCounterHalt; uint8_t _lengthCounter; uint8_t _lengthCounterReloadValue; @@ -112,4 +112,13 @@ public: } _enabled = enabled; } + + ApuLengthCounterState GetState() + { + ApuLengthCounterState state; + state.Counter = _lengthCounter; + state.Halt = _lengthCounterHalt; + state.ReloadValue = _lengthCounterReloadValue; + return state; + } }; diff --git a/Core/BaseApuChannel.h b/Core/BaseApuChannel.h index d8226446..603a0f2d 100644 --- a/Core/BaseApuChannel.h +++ b/Core/BaseApuChannel.h @@ -9,12 +9,12 @@ class BaseApuChannel : public IMemoryHandler, public Snapshotable { private: SoundMixer *_mixer; - int8_t _lastOutput; uint32_t _previousCycle; AudioChannel _channel; NesModel _nesModel; protected: + int8_t _lastOutput; uint16_t _timer = 0; uint16_t _period = 0; diff --git a/Core/BaseMapper.cpp b/Core/BaseMapper.cpp index 7b689f3c..9b5f9f1a 100644 --- a/Core/BaseMapper.cpp +++ b/Core/BaseMapper.cpp @@ -737,6 +737,16 @@ uint8_t BaseMapper::ReadRAM(uint16_t addr) return MemoryManager::GetOpenBus(); } +uint8_t BaseMapper::DebugReadRAM(uint16_t addr) +{ + if(_prgPageAccessType[addr >> 8] & MemoryAccessType::Read) { + return _prgPages[addr >> 8][(uint8_t)addr]; + } else { + //assert(false); + } + return MemoryManager::GetOpenBus(); +} + void BaseMapper::WriteRAM(uint16_t addr, uint8_t value) { if(_isWriteRegisterAddr[addr]) { @@ -749,6 +759,17 @@ void BaseMapper::WriteRAM(uint16_t addr, uint8_t value) } } +void BaseMapper::DebugWriteRAM(uint16_t addr, uint8_t value) +{ + if(_isWriteRegisterAddr[addr]) { + if(_hasBusConflicts) { + value &= _prgPages[addr >> 8][(uint8_t)addr]; + } + } else { + WritePrgRam(addr, value); + } +} + void BaseMapper::WritePrgRam(uint16_t addr, uint8_t value) { if(_prgPageAccessType[addr >> 8] & MemoryAccessType::Write) { diff --git a/Core/BaseMapper.h b/Core/BaseMapper.h index 9c8250bc..0377e1c1 100644 --- a/Core/BaseMapper.h +++ b/Core/BaseMapper.h @@ -168,7 +168,9 @@ public: RomFormat GetRomFormat(); __forceinline uint8_t ReadRAM(uint16_t addr) override; + uint8_t DebugReadRAM(uint16_t addr); virtual void WriteRAM(uint16_t addr, uint8_t value) override; + void DebugWriteRAM(uint16_t addr, uint8_t value); void WritePrgRam(uint16_t addr, uint8_t value); __forceinline uint8_t InternalReadVRAM(uint16_t addr); diff --git a/Core/CheatManager.cpp b/Core/CheatManager.cpp index ca31c71e..d6d56f00 100644 --- a/Core/CheatManager.cpp +++ b/Core/CheatManager.cpp @@ -13,6 +13,11 @@ CheatManager::CheatManager() } } +CheatManager * CheatManager::GetInstance() +{ + return Instance; +} + uint32_t CheatManager::DecodeValue(uint32_t code, uint32_t* bitIndexes, uint32_t bitCount) { uint32_t result = 0; diff --git a/Core/CheatManager.h b/Core/CheatManager.h index 2d4fcf15..f78abbad 100644 --- a/Core/CheatManager.h +++ b/Core/CheatManager.h @@ -41,14 +41,16 @@ private: CodeInfo GetPARCodeInfo(uint32_t parCode); void AddCode(CodeInfo &code); +public: + CheatManager(); + + static CheatManager* GetInstance(); + void AddGameGenieCode(string code); void AddProActionRockyCode(uint32_t code); void AddCustomCode(uint32_t address, uint8_t value, int32_t compareValue = -1, bool isRelativeAddress = true); void ClearCodes(); -public: - CheatManager(); - static vector GetCheats(); static void SetCheats(vector &cheats); static void SetCheats(CheatInfo cheats[], uint32_t length); diff --git a/Core/Console.cpp b/Core/Console.cpp index 67bca2a2..d49e1f97 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -287,17 +287,7 @@ void Console::ResetComponents(bool softReset) SoundMixer::StopAudio(true); - if(softReset) { - if(_debugger) { - auto lock = _debuggerLock.AcquireSafe(); - StopDebugger(); - GetDebugger(); - } - - MessageManager::SendNotification(ConsoleNotificationType::GameReset); - } else { - MessageManager::SendNotification(ConsoleNotificationType::GameLoaded); - } + MessageManager::SendNotification(softReset ? ConsoleNotificationType::GameReset : ConsoleNotificationType::GameLoaded); } void Console::Stop() @@ -380,6 +370,10 @@ void Console::Run() _lagCounter++; } + if(_debugger) { + _debugger->ProcessEvent(EventType::StartFrame); + } + _rewindManager->ProcessEndOfFrame(); EmulationSettings::DisableOverclocking(_disableOcNextFrame || NsfMapper::GetInstance()); _disableOcNextFrame = false; @@ -485,7 +479,7 @@ void Console::Run() bool Console::IsRunning() { - return !Instance->_stopLock.IsFree(); + return !Instance->_stopLock.IsFree() && !Instance->_runLock.IsFree(); } void Console::UpdateNesModel(bool sendNotification) @@ -597,7 +591,7 @@ std::shared_ptr Console::GetDebugger(bool autoStart) { auto lock = _debuggerLock.AcquireSafe(); if(!_debugger && autoStart) { - _debugger.reset(new Debugger(Console::Instance, _cpu, _ppu, _memoryManager, _mapper)); + _debugger.reset(new Debugger(Console::Instance, _cpu, _ppu, _apu, _memoryManager, _mapper)); } return _debugger; } diff --git a/Core/Console.h b/Core/Console.h index 2b2a0ea6..0d5c99e0 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -32,7 +32,7 @@ class Console shared_ptr _rewindManager; shared_ptr _cpu; shared_ptr _ppu; - unique_ptr _apu; + shared_ptr _apu; shared_ptr _debugger; SimpleLock _debuggerLock; shared_ptr _mapper; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 4ea4e753..aa73d0b7 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -412,12 +412,20 @@ + + + + + + + + @@ -461,6 +469,8 @@ + + @@ -780,11 +790,16 @@ + + + + + @@ -829,6 +844,8 @@ + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index f871238f..576ccf52 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -92,6 +92,15 @@ {a6994cb5-f9d2-416c-84ab-c1abe4975eb1} + + {ee232799-5562-4fea-946b-844786d6fd66} + + + {9e32ff0a-5fae-47bc-8007-404fd4d0204c} + + + {e1e8a5d2-aa9a-40e1-94eb-00adec1cdef3} + @@ -1201,6 +1210,36 @@ HdPacks + + Debugger\Scripting + + + Debugger\Scripting + + + Debugger\Scripting\DebugHud + + + Debugger\Scripting\DebugHud + + + Debugger\Scripting\DebugHud + + + Debugger\Scripting\DebugHud + + + Debugger\Scripting\DebugHud + + + Debugger\Scripting\Lua + + + Debugger\Scripting\Lua + + + Debugger\Scripting\Lua + @@ -1443,5 +1482,26 @@ Misc + + Debugger\Scripting\DebugHud + + + Debugger\Scripting\DebugHud + + + Debugger\Scripting + + + Debugger\Scripting\Lua + + + Debugger\Scripting\Lua + + + Debugger\Scripting\Lua + + + Debugger\Scripting + \ No newline at end of file diff --git a/Core/DebugHud.cpp b/Core/DebugHud.cpp new file mode 100644 index 00000000..439daef3 --- /dev/null +++ b/Core/DebugHud.cpp @@ -0,0 +1,67 @@ +#include "stdafx.h" +#include "DebugHud.h" +#include "DrawCommand.h" +#include "DrawLineCommand.h" +#include "DrawPixelCommand.h" +#include "DrawRectangleCommand.h" +#include "DrawStringCommand.h" + +DebugHud* DebugHud::_instance = nullptr; + +DebugHud::DebugHud() +{ + _instance = this; +} + +DebugHud::~DebugHud() +{ + _commandLock.Acquire(); + if(_instance == this) { + _instance = nullptr; + } + _commandLock.Release(); +} + +DebugHud* DebugHud::GetInstance() +{ + return _instance; +} + +void DebugHud::ClearScreen() +{ + auto lock = _commandLock.AcquireSafe(); + _commands.clear(); +} + +void DebugHud::Draw(uint32_t* argbBuffer, OverscanDimensions &overscan) +{ + auto lock = _commandLock.AcquireSafe(); + for(shared_ptr &command : _commands) { + command->Draw(argbBuffer, overscan); + } + _commands.erase(std::remove_if(_commands.begin(), _commands.end(), [](const shared_ptr& c) { return c->Expired(); }), _commands.end()); +} + +void DebugHud::DrawPixel(int x, int y, int color, int frameCount) +{ + auto lock = _commandLock.AcquireSafe(); + _commands.push_back(shared_ptr(new DrawPixelCommand(x, y, color, frameCount))); +} + +void DebugHud::DrawLine(int x, int y, int x2, int y2, int color, int frameCount) +{ + auto lock = _commandLock.AcquireSafe(); + _commands.push_back(shared_ptr(new DrawLineCommand(x, y, x2, y2, color, frameCount))); +} + +void DebugHud::DrawRectangle(int x, int y, int width, int height, int color, bool fill, int frameCount) +{ + auto lock = _commandLock.AcquireSafe(); + _commands.push_back(shared_ptr(new DrawRectangleCommand(x, y, width, height, color, fill, frameCount))); +} + +void DebugHud::DrawString(int x, int y, string text, int color, int backColor, int frameCount) +{ + auto lock = _commandLock.AcquireSafe(); + _commands.push_back(shared_ptr(new DrawStringCommand(x, y, text, color, backColor, frameCount))); +} diff --git a/Core/DebugHud.h b/Core/DebugHud.h new file mode 100644 index 00000000..12b48d59 --- /dev/null +++ b/Core/DebugHud.h @@ -0,0 +1,26 @@ +#pragma once +#include "stdafx.h" +#include "../Utilities/SimpleLock.h" +#include "EmulationSettings.h" +class DrawCommand; + +class DebugHud +{ +private: + static DebugHud* _instance; + vector> _commands; + SimpleLock _commandLock; + +public: + static DebugHud* GetInstance(); + DebugHud(); + ~DebugHud(); + + void Draw(uint32_t* argbBuffer, OverscanDimensions &overscan); + void ClearScreen(); + + void DrawPixel(int x, int y, int color, int frameCount); + void DrawLine(int x, int y, int x2, int y2, int color, int frameCount); + void DrawRectangle(int x, int y, int width, int height, int color, bool fill, int frameCount); + void DrawString(int x, int y, string text, int color, int backColor, int frameCount); +}; diff --git a/Core/Debugger.cpp b/Core/Debugger.cpp index c9e1cbdf..2ac48e7e 100644 --- a/Core/Debugger.cpp +++ b/Core/Debugger.cpp @@ -22,16 +22,19 @@ #include "MemoryManager.h" #include "RewindManager.h" #include "DebugBreakHelper.h" +#include "ScriptHost.h" +#include "DebugHud.h" Debugger* Debugger::Instance = nullptr; const int Debugger::BreakpointTypeCount; -Debugger::Debugger(shared_ptr console, shared_ptr cpu, shared_ptr ppu, shared_ptr memoryManager, shared_ptr mapper) +Debugger::Debugger(shared_ptr console, shared_ptr cpu, shared_ptr ppu, shared_ptr apu, shared_ptr memoryManager, shared_ptr mapper) { _romName = Console::GetRomName(); _console = console; _cpu = cpu; _ppu = ppu; + _apu = apu; _memoryManager = memoryManager; _mapper = mapper; @@ -43,6 +46,7 @@ Debugger::Debugger(shared_ptr console, shared_ptr cpu, shared_ptr< _memoryAccessCounter.reset(new MemoryAccessCounter(this)); _profiler.reset(new Profiler(this)); _traceLogger.reset(new TraceLogger(this, memoryManager, _labelManager)); + _debugHud.reset(new DebugHud()); _stepOut = false; _stepCount = -1; @@ -85,7 +89,10 @@ Debugger::Debugger(shared_ptr console, shared_ptr cpu, shared_ptr< if(!LoadCdlFile(FolderUtilities::CombinePath(FolderUtilities::GetDebuggerFolder(), FolderUtilities::GetFilename(_romName, false) + ".cdl"))) { _disassembler->Reset(); } - + + _hasScript = false; + _nextScriptId = 0; + Debugger::Instance = this; } @@ -356,6 +363,8 @@ void Debugger::PrivateProcessInterrupt(uint16_t cpuAddr, uint16_t destCpuAddr, b _callstackAbsolute.push_back(_mapper->ToAbsoluteAddress(destCpuAddr)); _profiler->StackFunction(-1, _mapper->ToAbsoluteAddress(destCpuAddr)); + + ProcessEvent(forNmi ? EventType::Nmi : EventType::Irq); } void Debugger::ProcessInterrupt(uint16_t cpuAddr, uint16_t destCpuAddr, bool forNmi) @@ -381,6 +390,9 @@ void Debugger::PrivateProcessPpuCycle() { if(PPU::GetCurrentCycle() == (uint32_t)_ppuViewerCycle && PPU::GetCurrentScanline() == _ppuViewerScanline) { MessageManager::SendNotification(ConsoleNotificationType::PpuViewerDisplayFrame); + } + if(PPU::GetCurrentCycle() == 0 && PPU::GetCurrentScanline() == 241) { + ProcessEvent(EventType::EndFrame); } OperationInfo operationInfo { 0, 0, MemoryOperationType::DummyRead }; @@ -538,6 +550,8 @@ bool Debugger::PrivateProcessRamOperation(MemoryOperationType type, uint16_t &ad _currentReadAddr = nullptr; _currentReadValue = nullptr; + ProcessCpuOperation(addr, value, type); + if(type == MemoryOperationType::Write) { if(_frozenAddresses[addr]) { return false; @@ -563,6 +577,7 @@ bool Debugger::SleepUntilResume() if(_sendNotification) { SoundMixer::StopAudio(); MessageManager::SendNotification(ConsoleNotificationType::CodeBreak); + ProcessEvent(EventType::CodeBreak); _stepOverAddr = -1; if(CheckFlag(DebuggerFlags::PpuPartialDraw)) { _ppu->DebugSendFrame(); @@ -594,6 +609,8 @@ void Debugger::PrivateProcessVramReadOperation(MemoryOperationType type, uint16_ SleepUntilResume(); } } + + ProcessPpuOperation(addr, value, MemoryOperationType::Read); } void Debugger::PrivateProcessVramWriteOperation(uint16_t addr, uint8_t value) @@ -606,14 +623,18 @@ void Debugger::PrivateProcessVramWriteOperation(uint16_t addr, uint8_t value) SleepUntilResume(); } } + + ProcessPpuOperation(addr, value, MemoryOperationType::Write); } void Debugger::GetState(DebugState *state, bool includeMapperInfo) { + state->Model = _console->GetModel(); state->CPU = _cpu->GetState(); state->PPU = _ppu->GetState(); if(includeMapperInfo) { state->Cartridge = _mapper->GetState(); + state->APU = _apu->GetState(); } } @@ -1013,3 +1034,71 @@ void Debugger::SetInputOverride(uint8_t port, uint32_t state) { _inputOverride[port] = state; } + +int Debugger::LoadScript(string content, int32_t scriptId) +{ + DebugBreakHelper helper(this); + + if(scriptId < 0) { + shared_ptr script(new ScriptHost(_nextScriptId++)); + script->LoadScript(content, this); + _scripts.push_back(script); + _hasScript = true; + return script->GetScriptId(); + } else { + auto result = std::find_if(_scripts.begin(), _scripts.end(), [=](shared_ptr &script) { + return script->GetScriptId() == scriptId; + }); + if(result != _scripts.end()) { + (*result)->LoadScript(content, this); + return scriptId; + } + } + + return -1; +} + +void Debugger::RemoveScript(int32_t scriptId) +{ + DebugBreakHelper helper(this); + _scripts.erase(std::remove_if(_scripts.begin(), _scripts.end(), [=](const shared_ptr& script) { return script->GetScriptId() == scriptId; }), _scripts.end()); + _hasScript = _scripts.size() > 0; +} + +const char* Debugger::GetScriptLog(int32_t scriptId) +{ + DebugBreakHelper helper(this); + for(shared_ptr &script : _scripts) { + if(script->GetScriptId() == scriptId) { + return script->GetLog(); + } + } + return ""; +} + +void Debugger::ProcessCpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type) +{ + if(_hasScript) { + for(shared_ptr &script : _scripts) { + script->ProcessCpuOperation(addr, value, type); + } + } +} + +void Debugger::ProcessPpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type) +{ + if(_hasScript) { + for(shared_ptr &script : _scripts) { + script->ProcessPpuOperation(addr, value, MemoryOperationType::Write); + } + } +} + +void Debugger::ProcessEvent(EventType type) +{ + if(_hasScript) { + for(shared_ptr &script : _scripts) { + script->ProcessEvent(type); + } + } +} diff --git a/Core/Debugger.h b/Core/Debugger.h index 2598f2c6..fb5189d4 100644 --- a/Core/Debugger.h +++ b/Core/Debugger.h @@ -15,6 +15,7 @@ using std::unordered_set; #include "DebuggerTypes.h" class CPU; +class APU; class PPU; class MemoryManager; class Console; @@ -26,6 +27,8 @@ class MemoryAccessCounter; class Profiler; class CodeRunner; class BaseMapper; +class ScriptHost; +class DebugHud; class Debugger { @@ -43,12 +46,18 @@ private: shared_ptr _traceLogger; shared_ptr _profiler; unique_ptr _codeRunner; + unique_ptr _debugHud; shared_ptr _console; shared_ptr _cpu; shared_ptr _ppu; + shared_ptr _apu; shared_ptr _memoryManager; shared_ptr _mapper; + + bool _hasScript; + int _nextScriptId; + vector> _scripts; bool _bpUpdateNeeded; SimpleLock _bpUpdateLock; @@ -125,7 +134,7 @@ private: void RemoveExcessCallstackEntries(); public: - Debugger(shared_ptr console, shared_ptr cpu, shared_ptr ppu, shared_ptr memoryManager, shared_ptr mapper); + Debugger(shared_ptr console, shared_ptr cpu, shared_ptr ppu, shared_ptr apu, shared_ptr memoryManager, shared_ptr mapper); ~Debugger(); void SetFlags(uint32_t flags); @@ -185,7 +194,7 @@ public: static void ProcessVramReadOperation(MemoryOperationType type, uint16_t addr, uint8_t value); static void ProcessVramWriteOperation(uint16_t addr, uint8_t value); static void ProcessPpuCycle(); - + static void SetLastFramePpuScroll(uint16_t x, uint16_t y); uint32_t GetPpuScroll(); @@ -210,4 +219,11 @@ public: static bool HasInputOverride(uint8_t port); static uint32_t GetInputOverride(uint8_t port); void SetInputOverride(uint8_t port, uint32_t state); + + int32_t LoadScript(string content, int32_t scriptId); + void RemoveScript(int32_t scriptId); + const char* GetScriptLog(int32_t scriptId); + void ProcessCpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type); + void ProcessPpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type); + void ProcessEvent(EventType type); }; \ No newline at end of file diff --git a/Core/DebuggerTypes.h b/Core/DebuggerTypes.h index ace17f63..05a3cd96 100644 --- a/Core/DebuggerTypes.h +++ b/Core/DebuggerTypes.h @@ -1,5 +1,6 @@ #pragma once #include "Types.h" +#include "EmulationSettings.h" enum class DebuggerFlags { @@ -66,6 +67,8 @@ struct DebugState State CPU; PPUDebugState PPU; CartridgeState Cartridge; + ApuState APU; + NesModel Model; }; struct OperationInfo @@ -73,4 +76,15 @@ struct OperationInfo uint16_t Address; int16_t Value; MemoryOperationType OperationType; +}; + +enum class EventType +{ + Power = 0, + Reset = 1, + Nmi = 2, + Irq = 3, + StartFrame = 4, + EndFrame = 5, + CodeBreak = 6 }; \ No newline at end of file diff --git a/Core/DefaultVideoFilter.cpp b/Core/DefaultVideoFilter.cpp index df0c6ebd..370f9dae 100644 --- a/Core/DefaultVideoFilter.cpp +++ b/Core/DefaultVideoFilter.cpp @@ -5,6 +5,7 @@ #include #include #include "PPU.h" +#include "DebugHud.h" DefaultVideoFilter::DefaultVideoFilter() { @@ -54,21 +55,26 @@ void DefaultVideoFilter::OnBeforeApplyFilter() void DefaultVideoFilter::DecodePpuBuffer(uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, bool displayScanlines) { + uint32_t* out = outputBuffer; OverscanDimensions overscan = GetOverscan(); double scanlineIntensity = 1.0 - EmulationSettings::GetPictureSettings().ScanlineIntensity; for(uint32_t i = overscan.Top, iMax = 240 - overscan.Bottom; i < iMax; i++) { if(displayScanlines && (i + overscan.Top) % 2 == 0) { for(uint32_t j = overscan.Left, jMax = 256 - overscan.Right; j < jMax; j++) { - *outputBuffer = ProcessIntensifyBits(ppuOutputBuffer[i * 256 + j], scanlineIntensity); - outputBuffer++; + *out = ProcessIntensifyBits(ppuOutputBuffer[i * 256 + j], scanlineIntensity); + out++; } } else { for(uint32_t j = overscan.Left, jMax = 256 - overscan.Right; j < jMax; j++) { - *outputBuffer = ProcessIntensifyBits(ppuOutputBuffer[i * 256 + j]); - outputBuffer++; + *out = ProcessIntensifyBits(ppuOutputBuffer[i * 256 + j]); + out++; } } } + + if(DebugHud::GetInstance()) { + DebugHud::GetInstance()->Draw(outputBuffer, overscan); + } } void DefaultVideoFilter::ApplyFilter(uint16_t *ppuOutputBuffer) diff --git a/Core/DeltaModulationChannel.cpp b/Core/DeltaModulationChannel.cpp index e256b14a..06094ba0 100644 --- a/Core/DeltaModulationChannel.cpp +++ b/Core/DeltaModulationChannel.cpp @@ -210,4 +210,18 @@ bool DeltaModulationChannel::NeedToRun() void DeltaModulationChannel::SetReadBuffer() { Instance->FillReadBuffer(); +} + +ApuDmcState DeltaModulationChannel::GetState() +{ + ApuDmcState state; + state.BytesRemaining = _bytesRemaining; + state.Frequency = (uint32_t)(CPU::GetClockRate(GetNesModel()) / 32.0 / (_period + 1)); + state.IrqEnabled = _irqEnabled; + state.Loop = _loopFlag; + state.OutputVolume = _lastOutput; + state.Period = _period; + state.SampleAddr = _sampleAddr; + state.SampleLength = _sampleLength; + return state; } \ No newline at end of file diff --git a/Core/DeltaModulationChannel.h b/Core/DeltaModulationChannel.h index a19f65ee..a996cb12 100644 --- a/Core/DeltaModulationChannel.h +++ b/Core/DeltaModulationChannel.h @@ -52,4 +52,6 @@ public: void SetEnabled(bool enabled); void StartDmcTransfer(); static void SetReadBuffer(); + + ApuDmcState GetState(); }; \ No newline at end of file diff --git a/Core/DrawCommand.h b/Core/DrawCommand.h new file mode 100644 index 00000000..06176b82 --- /dev/null +++ b/Core/DrawCommand.h @@ -0,0 +1,59 @@ +#pragma once +#include "stdafx.h" +#include "EmulationSettings.h" + +class DrawCommand +{ +private: + int _frameCount; + uint32_t* _argbBuffer; + OverscanDimensions _overscan; + +protected: + virtual void InternalDraw() = 0; + __forceinline void DrawPixel(uint32_t x, uint32_t y, int color) + { + if(x < _overscan.Left || x >= _overscan.Left + _overscan.GetScreenWidth() || + y < _overscan.Top || y >= _overscan.Top + _overscan.GetScreenHeight()) { + //In overscan (out of bounds), skip drawing + return; + } + + if((color & 0xFF000000) != 0xFF000000) { + BlendColors((uint8_t*)&_argbBuffer[(y - _overscan.Top)*_overscan.GetScreenWidth() + (x - _overscan.Left)], (uint8_t*)&color); + } else { + _argbBuffer[(y - _overscan.Top)*_overscan.GetScreenWidth() + (x - _overscan.Left)] = color; + } + } + + __forceinline void BlendColors(uint8_t output[4], uint8_t input[4]) + { + uint8_t alpha = input[3] + 1; + uint8_t invertedAlpha = 256 - input[3]; + output[0] = (uint8_t)((alpha * input[0] + invertedAlpha * output[0]) >> 8); + output[1] = (uint8_t)((alpha * input[1] + invertedAlpha * output[1]) >> 8); + output[2] = (uint8_t)((alpha * input[2] + invertedAlpha * output[2]) >> 8); + output[3] = 0xFF; + } + +public: + DrawCommand(int frameCount) + { + _frameCount = frameCount > 0 ? frameCount : -1; + } + + void Draw(uint32_t* argbBuffer, OverscanDimensions &overscan) + { + _argbBuffer = argbBuffer; + _overscan = overscan; + + InternalDraw(); + + _frameCount--; + } + + bool Expired() + { + return _frameCount == 0; + } +}; diff --git a/Core/DrawLineCommand.h b/Core/DrawLineCommand.h new file mode 100644 index 00000000..5f720224 --- /dev/null +++ b/Core/DrawLineCommand.h @@ -0,0 +1,42 @@ +#pragma once +#include "stdafx.h" +#include "DrawCommand.h" + +class DrawLineCommand : public DrawCommand +{ +private: + int _x, _y, _x2, _y2, _color; + +protected: + void InternalDraw() + { + int xDiff = _x2 - _x; + int yDiff = _y2 - _y; + if(xDiff == 0) { + for(int i = _y; i <= _y2; i++) { + DrawPixel(_x, i, _color); + } + } else { + double deltaErr = abs((double)yDiff / (double)xDiff); + double error = deltaErr - 0.5; + int y = _y; + for(int x = _x; x <= _x2; x++) { + DrawPixel(x, y, _color); + error += deltaErr; + if(error >= 0.5) { + y++; + error -= 1.0; + } + } + } + } + +public: + DrawLineCommand(int x, int y, int x2, int y2, int color, int frameCount) : + _x(x), _y(y), _x2(x2), _y2(y2), _color(color), DrawCommand(frameCount) + { + if(!(_color & 0xFF000000)) { + _color |= 0xFF000000; + } + } +}; diff --git a/Core/DrawPixelCommand.h b/Core/DrawPixelCommand.h new file mode 100644 index 00000000..2e1cb9fa --- /dev/null +++ b/Core/DrawPixelCommand.h @@ -0,0 +1,24 @@ +#pragma once +#include "stdafx.h" +#include "DrawCommand.h" + +class DrawPixelCommand : public DrawCommand +{ +private: + int _x, _y, _color; + +protected: + void InternalDraw() + { + DrawPixel(_x, _y, _color); + } + +public: + DrawPixelCommand(int x, int y, int color, int frameCount) : + _x(x), _y(y), _color(color), DrawCommand(frameCount) + { + if(!(_color & 0xFF000000)) { + _color |= 0xFF000000; + } + } +}; diff --git a/Core/DrawRectangleCommand.h b/Core/DrawRectangleCommand.h new file mode 100644 index 00000000..4de60ab1 --- /dev/null +++ b/Core/DrawRectangleCommand.h @@ -0,0 +1,40 @@ +#pragma once +#include "stdafx.h" +#include "DrawCommand.h" + +class DrawRectangleCommand : public DrawCommand +{ +private: + int _x, _y, _width, _height, _color; + bool _fill; + +protected: + void InternalDraw() + { + if(_fill) { + for(int j = 0; j < _height; j++) { + for(int i = 0; i < _width; i++) { + DrawPixel(_x + i, _y + j, _color); + } + } + } else { + for(int i = 0; i < _width; i++) { + DrawPixel(_x + i, _y, _color); + DrawPixel(_x + i, _y + _height - 1, _color); + } + for(int i = 0; i < _height; i++) { + DrawPixel(_x, _y + i, _color); + DrawPixel(_x + _width - 1, _y + i, _color); + } + } + } + +public: + DrawRectangleCommand(int x, int y, int width, int height, int color, bool fill, int frameCount) : + _x(x), _y(y), _width(width), _height(height), _color(color), _fill(fill), DrawCommand(frameCount) + { + if(!(_color & 0xFF000000)) { + _color |= 0xFF000000; + } + } +}; \ No newline at end of file diff --git a/Core/DrawStringCommand.h b/Core/DrawStringCommand.h new file mode 100644 index 00000000..21254035 --- /dev/null +++ b/Core/DrawStringCommand.h @@ -0,0 +1,163 @@ +#pragma once +#include "stdafx.h" +#include "DrawCommand.h" + +class DrawStringCommand : public DrawCommand +{ +private: + int _x, _y, _color, _backColor; + string _text; + + //Taken from FCEUX's LUA code + const int _tabSpace = 4; + const uint8_t _font[792] = { + 6, 0, 0, 0, 0, 0, 0, 0, // 0x20 - Spacebar + 3, 64, 64, 64, 64, 64, 0, 64, + 5, 80, 80, 80, 0, 0, 0, 0, + 6, 80, 80,248, 80,248, 80, 80, + 6, 32,120,160,112, 40,240, 32, + 6, 64,168, 80, 32, 80,168, 16, + 6, 96,144,160, 64,168,144,104, + 3, 64, 64, 0, 0, 0, 0, 0, + 4, 32, 64, 64, 64, 64, 64, 32, + 4, 64, 32, 32, 32, 32, 32, 64, + 6, 0, 80, 32,248, 32, 80, 0, + 6, 0, 32, 32,248, 32, 32, 0, + 3, 0, 0, 0, 0, 0, 64,128, + 5, 0, 0, 0,240, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 64, + 5, 16, 16, 32, 32, 32, 64, 64, + 6,112,136,136,136,136,136,112, // 0x30 - 0 + 6, 32, 96, 32, 32, 32, 32, 32, + 6,112,136, 8, 48, 64,128,248, + 6,112,136, 8, 48, 8,136,112, + 6, 16, 48, 80,144,248, 16, 16, + 6,248,128,128,240, 8, 8,240, + 6, 48, 64,128,240,136,136,112, + 6,248, 8, 16, 16, 32, 32, 32, + 6,112,136,136,112,136,136,112, + 6,112,136,136,120, 8, 16, 96, + 3, 0, 0, 64, 0, 0, 64, 0, + 3, 0, 0, 64, 0, 0, 64,128, + 4, 0, 32, 64,128, 64, 32, 0, + 5, 0, 0,240, 0,240, 0, 0, + 4, 0,128, 64, 32, 64,128, 0, + 6,112,136, 8, 16, 32, 0, 32, // 0x3F - ? + 6,112,136,136,184,176,128,112, // 0x40 - @ + 6,112,136,136,248,136,136,136, // 0x41 - A + 6,240,136,136,240,136,136,240, + 6,112,136,128,128,128,136,112, + 6,224,144,136,136,136,144,224, + 6,248,128,128,240,128,128,248, + 6,248,128,128,240,128,128,128, + 6,112,136,128,184,136,136,120, + 6,136,136,136,248,136,136,136, + 4,224, 64, 64, 64, 64, 64,224, + 6, 8, 8, 8, 8, 8,136,112, + 6,136,144,160,192,160,144,136, + 6,128,128,128,128,128,128,248, + 6,136,216,168,168,136,136,136, + 6,136,136,200,168,152,136,136, + 7, 48, 72,132,132,132, 72, 48, + 6,240,136,136,240,128,128,128, + 6,112,136,136,136,168,144,104, + 6,240,136,136,240,144,136,136, + 6,112,136,128,112, 8,136,112, + 6,248, 32, 32, 32, 32, 32, 32, + 6,136,136,136,136,136,136,112, + 6,136,136,136, 80, 80, 32, 32, + 6,136,136,136,136,168,168, 80, + 6,136,136, 80, 32, 80,136,136, + 6,136,136, 80, 32, 32, 32, 32, + 6,248, 8, 16, 32, 64,128,248, + 3,192,128,128,128,128,128,192, + 5, 64, 64, 32, 32, 32, 16, 16, + 3,192, 64, 64, 64, 64, 64,192, + 4, 64,160, 0, 0, 0, 0, 0, + 6, 0, 0, 0, 0, 0, 0,248, + 3,128, 64, 0, 0, 0, 0, 0, + 5, 0, 0, 96, 16,112,144,112, // 0x61 - a + 5,128,128,224,144,144,144,224, + 5, 0, 0,112,128,128,128,112, + 5, 16, 16,112,144,144,144,112, + 5, 0, 0, 96,144,240,128,112, + 5, 48, 64,224, 64, 64, 64, 64, + 5, 0,112,144,144,112, 16,224, + 5,128,128,224,144,144,144,144, + 2,128, 0,128,128,128,128,128, + 4, 32, 0, 32, 32, 32, 32,192, + 5,128,128,144,160,192,160,144, + 2,128,128,128,128,128,128,128, + 6, 0, 0,208,168,168,168,168, + 5, 0, 0,224,144,144,144,144, + 5, 0, 0, 96,144,144,144, 96, + 5, 0,224,144,144,224,128,128, + 5, 0,112,144,144,112, 16, 16, + 5, 0, 0,176,192,128,128,128, + 5, 0, 0,112,128, 96, 16,224, + 4, 64, 64,224, 64, 64, 64, 32, + 5, 0, 0,144,144,144,144,112, + 5, 0, 0,144,144,144,160,192, + 6, 0, 0,136,136,168,168, 80, + 5, 0, 0,144,144, 96,144,144, + 5, 0,144,144,144,112, 16, 96, + 5, 0, 0,240, 32, 64,128,240, + 4, 32, 64, 64,128, 64, 64, 32, + 3, 64, 64, 64, 64, 64, 64, 64, + 4,128, 64, 64, 32, 64, 64,128, + 6, 0,104,176, 0, 0, 0, 0 + }; + + int GetCharNumber(char ch) + { + ch -= 32; + return (ch < 0 || ch > 98) ? 0 : ch; + } + + int GetCharWidth(char ch) + { + return _font[GetCharNumber(ch) * 8]; + } + +protected: + void InternalDraw() + { + int x = _x; + int y = _y; + for(char c : _text) { + if(c == '\n') { + x = _x; + y += 9; + } else if(c == '\t') { + x += (_tabSpace - (((x - _x) / 8) % _tabSpace)) * 8; + } else { + int ch = GetCharNumber(c); + int width = GetCharWidth(c); + int rowOffset = (c == 'y' || c == 'g' || c == 'p' || c == 'q') ? 1 : 0; + for(int j = 0; j < 8; j++) { + uint8_t rowData = (j == 7 && rowOffset == 0 || j == 0 && rowOffset == 1) ? 0 : _font[ch * 8 + 1 + j - rowOffset]; + for(int i = 0; i < width; i++) { + int drawFg = (rowData >> (7 - i)) & 0x01; + DrawPixel(x + i, y + j, drawFg ? _color : _backColor); + } + } + for(int i = 0; i < width; i++) { + DrawPixel(x + i, y - 1, _backColor); + } + x += width; + } + } + } + +public: + DrawStringCommand(int x, int y, string text, int color, int backColor, int frameCount) : + _x(x), _y(y), _text(text), _color(color), _backColor(backColor), DrawCommand(frameCount) + { + if(!(_color & 0xFF000000)) { + _color |= 0xFF000000; + } + if(!(_backColor & 0xFF000000)) { + _backColor |= 0xFF000000; + } + } +}; diff --git a/Core/ExpressionEvaluator.cpp b/Core/ExpressionEvaluator.cpp index 75f6a461..755fa798 100644 --- a/Core/ExpressionEvaluator.cpp +++ b/Core/ExpressionEvaluator.cpp @@ -359,7 +359,7 @@ int32_t ExpressionEvaluator::Evaluate(vector &rpnList, DebugState &state, E case EvalOperators::BinaryNot: token = ~right; break; case EvalOperators::LogicalNot: token = !right; break; case EvalOperators::Bracket: token = _debugger->GetMemoryDumper()->GetMemoryValue(DebugMemoryType::CpuMemory, right); break; - case EvalOperators::Braces: token = _debugger->GetMemoryDumper()->GetMemoryValue(DebugMemoryType::CpuMemory, right) | (_debugger->GetMemoryDumper()->GetMemoryValue(DebugMemoryType::CpuMemory, right+1) << 8); break; + case EvalOperators::Braces: token = _debugger->GetMemoryDumper()->GetMemoryValueWord(DebugMemoryType::CpuMemory, right); break; default: throw std::runtime_error("Invalid operator"); } } diff --git a/Core/HdAudioDevice.cpp b/Core/HdAudioDevice.cpp index 9038fcb4..8603c8e3 100644 --- a/Core/HdAudioDevice.cpp +++ b/Core/HdAudioDevice.cpp @@ -67,7 +67,7 @@ void HdAudioDevice::ProcessControlFlags(uint8_t flags) void HdAudioDevice::GetMemoryRanges(MemoryRanges & ranges) { - bool useAlternateRegisters = (_hdData->OptionFlags & (int)HdPackOptions::AlternateRegisterRange); + bool useAlternateRegisters = (_hdData->OptionFlags & (int)HdPackOptions::AlternateRegisterRange) == (int)HdPackOptions::AlternateRegisterRange; ranges.SetAllowOverride(); if(useAlternateRegisters) { diff --git a/Core/LuaApi.cpp b/Core/LuaApi.cpp new file mode 100644 index 00000000..06692c87 --- /dev/null +++ b/Core/LuaApi.cpp @@ -0,0 +1,825 @@ +#include "stdafx.h" +#include "LuaApi.h" +#include "../Utilities/HexUtilities.h" +#include "../Lua/lua.hpp" +#include "LuaCallHelper.h" +#include "Debugger.h" +#include "MemoryDumper.h" +#include "MessageManager.h" +#include "ScriptingContext.h" +#include "DebugHud.h" +#include "VideoDecoder.h" +#include "RewindManager.h" +#include "SaveStateManager.h" +#include "Console.h" +#include "IKeyManager.h" +#include "ControlManager.h" +#include "StandardController.h" +#include "PPU.h" +#include "CheatManager.h" + +#define lua_pushintvalue(name, value) lua_pushliteral(lua, #name); lua_pushinteger(lua, (int)value); lua_settable(lua, -3); +#define lua_pushboolvalue(name, value) lua_pushliteral(lua, #name); lua_pushboolean(lua, (int)value); lua_settable(lua, -3); +#define lua_starttable(name) lua_pushliteral(lua, name); lua_newtable(lua); +#define lua_endtable() lua_settable(lua, -3); +#define lua_readint(name, dest) lua_getfield(lua, -1, #name); dest = l.ReadInteger(); +#define lua_readbool(name, dest) lua_getfield(lua, -1, #name); dest = l.ReadBool(); +#define error(text) luaL_error(lua, text); return 0; +#define errorCond(cond, text) if(cond) { luaL_error(lua, text); return 0; } +#define checkparams() if(!l.CheckParamCount()) { return 0; } +#define checkminparams(x) if(!l.CheckParamCount(x)) { return 0; } +#define checkstartframe() if(!_context->CheckInStartFrameEvent()) { error("This function cannot be called outside StartFrame event callback"); return 0; } + +enum class ExecuteCountType +{ + CpuCycles = 0, + PpuCycles = 1, + CpuInstructions = 2 +}; + +Debugger* LuaApi::_debugger = nullptr; +MemoryDumper* LuaApi::_memoryDumper = nullptr; +ScriptingContext* LuaApi::_context = nullptr; + +void LuaApi::SetContext(ScriptingContext * context) +{ + _context = context; +} + +void LuaApi::RegisterDebugger(Debugger * debugger) +{ + _debugger = debugger; + _memoryDumper = debugger->GetMemoryDumper().get(); +} + +int LuaApi::GetLibrary(lua_State *lua) +{ + static const luaL_Reg apilib[] = { + { "read", LuaApi::ReadMemory }, + { "write", LuaApi::WriteMemory }, + { "debugRead", LuaApi::DebugReadMemory }, + { "debugWrite", LuaApi::DebugWriteMemory }, + { "readWord", LuaApi::ReadMemoryWord }, + { "writeWord", LuaApi::WriteMemoryWord }, + { "debugReadWord", LuaApi::DebugReadMemoryWord }, + { "debugWriteWord", LuaApi::DebugWriteMemoryWord }, + { "revertPrgChrChanges", LuaApi::RevertPrgChrChanges }, + { "addMemoryCallback", LuaApi::RegisterMemoryCallback }, + { "removeMemoryCallback", LuaApi::UnregisterMemoryCallback }, + { "addEventCallback", LuaApi::RegisterEventCallback }, + { "removeEventCallback", LuaApi::UnregisterEventCallback }, + { "drawString", LuaApi::DrawString }, + { "drawPixel", LuaApi::DrawPixel }, + { "drawLine", LuaApi::DrawLine }, + { "drawRectangle", LuaApi::DrawRectangle }, + { "clearScreen", LuaApi::ClearScreen }, + { "getPixel", LuaApi::GetPixel }, + { "getMouseState", LuaApi::GetMouseState }, + { "log", LuaApi::Log }, + { "displayMessage", LuaApi::DisplayMessage }, + { "reset", LuaApi::Reset }, + { "breakExecution", LuaApi::Break }, + { "resume", LuaApi::Resume }, + { "execute", LuaApi::Execute }, + { "rewind", LuaApi::Rewind }, + { "takeScreenshot", LuaApi::TakeScreenshot }, + { "saveSavestate", LuaApi::SaveSavestate }, + { "loadSavestate", LuaApi::LoadSavestate }, + { "getInput", LuaApi::GetInput }, + { "setInput", LuaApi::SetInput }, + { "addCheat", LuaApi::AddCheat }, + { "clearCheats", LuaApi::ClearCheats }, + { "setState", LuaApi::SetState }, + { "getState", LuaApi::GetState }, + { NULL,NULL } + }; + + luaL_newlib(lua, apilib); + + //Expose DebugMemoryType enum as "memory.type" + lua_pushliteral(lua, "memType"); + lua_newtable(lua); + lua_pushintvalue(cpu, DebugMemoryType::CpuMemory); + lua_pushintvalue(ppu, DebugMemoryType::PpuMemory); + lua_pushintvalue(palette, DebugMemoryType::PaletteMemory); + lua_pushintvalue(oam, DebugMemoryType::SpriteMemory); + lua_pushintvalue(secondaryOam, DebugMemoryType::SecondarySpriteMemory); + lua_pushintvalue(prgRom, DebugMemoryType::PrgRom); + lua_pushintvalue(chrRom, DebugMemoryType::ChrRom); + lua_pushintvalue(chrRam, DebugMemoryType::ChrRam); + lua_pushintvalue(workRam, DebugMemoryType::WorkRam); + lua_pushintvalue(saveRam, DebugMemoryType::SaveRam); + lua_settable(lua, -3); + + lua_pushliteral(lua, "memCallbackType"); + lua_newtable(lua); + lua_pushintvalue(cpuRead, CallbackType::CpuRead); + lua_pushintvalue(cpuWrite, CallbackType::CpuWrite); + lua_pushintvalue(cpuExec, CallbackType::CpuExec); + lua_pushintvalue(ppuRead, CallbackType::PpuRead); + lua_pushintvalue(ppuWrite, CallbackType::PpuWrite); + lua_settable(lua, -3); + + lua_pushliteral(lua, "eventCallbackType"); + lua_newtable(lua); + lua_pushintvalue(power, EventType::Power); + lua_pushintvalue(reset, EventType::Reset); + lua_pushintvalue(nmi, EventType::Nmi); + lua_pushintvalue(irq, EventType::Irq); + lua_pushintvalue(startFrame, EventType::StartFrame); + lua_pushintvalue(endFrame, EventType::EndFrame); + lua_pushintvalue(codeBreak, EventType::CodeBreak); + lua_settable(lua, -3); + + return 1; +} + +int LuaApi::ReadMemory(lua_State *lua) +{ + LuaCallHelper l(lua); + DebugMemoryType type = (DebugMemoryType)l.ReadInteger(); + int address = l.ReadInteger(); + checkparams(); + errorCond(address < 0, "address must be >= 0"); + l.Return(_memoryDumper->GetMemoryValue(type, address, false)); + return l.ReturnCount(); +} + +int LuaApi::WriteMemory(lua_State *lua) +{ + LuaCallHelper l(lua); + DebugMemoryType type = (DebugMemoryType)l.ReadInteger(); + int value = l.ReadInteger(); + int address = l.ReadInteger(); + checkparams(); + errorCond(value > 255 || value < -128, "value out of range"); + errorCond(address < 0, "address must be >= 0"); + _memoryDumper->SetMemoryValue(type, address, value, false); + return l.ReturnCount(); +} + +int LuaApi::DebugReadMemory(lua_State *lua) +{ + LuaCallHelper l(lua); + DebugMemoryType type = (DebugMemoryType)l.ReadInteger(); + int address = l.ReadInteger(); + checkparams(); + errorCond(address < 0, "address must be >= 0"); + l.Return(_memoryDumper->GetMemoryValue(type, address)); + return l.ReturnCount(); +} + +int LuaApi::DebugWriteMemory(lua_State *lua) +{ + LuaCallHelper l(lua); + DebugMemoryType type = (DebugMemoryType)l.ReadInteger(); + int value = l.ReadInteger(); + int address = l.ReadInteger(); + checkparams(); + errorCond(value > 255 || value < -128, "value out of range"); + errorCond(address < 0, "address must be >= 0"); + _memoryDumper->SetMemoryValue(type, address, value); + return l.ReturnCount(); +} + +int LuaApi::ReadMemoryWord(lua_State *lua) +{ + LuaCallHelper l(lua); + DebugMemoryType type = (DebugMemoryType)l.ReadInteger(); + int address = l.ReadInteger(); + checkparams(); + errorCond(address < 0, "address must be >= 0"); + l.Return(_memoryDumper->GetMemoryValueWord(type, address, false)); + return l.ReturnCount(); +} + +int LuaApi::WriteMemoryWord(lua_State *lua) +{ + LuaCallHelper l(lua); + DebugMemoryType type = (DebugMemoryType)l.ReadInteger(); + int value = l.ReadInteger(); + int address = l.ReadInteger(); + checkparams(); + errorCond(value > 65535 || value < -32768, "value out of range"); + errorCond(address < 0, "address must be >= 0"); + _memoryDumper->SetMemoryValueWord(type, address, value, false); + return l.ReturnCount(); +} + +int LuaApi::DebugReadMemoryWord(lua_State *lua) +{ + LuaCallHelper l(lua); + DebugMemoryType type = (DebugMemoryType)l.ReadInteger(); + int address = l.ReadInteger(); + checkparams(); + errorCond(address < 0, "address must be >= 0"); + l.Return(_memoryDumper->GetMemoryValueWord(type, address)); + return l.ReturnCount(); +} + +int LuaApi::DebugWriteMemoryWord(lua_State *lua) +{ + LuaCallHelper l(lua); + DebugMemoryType type = (DebugMemoryType)l.ReadInteger(); + int value = l.ReadInteger(); + int address = l.ReadInteger(); + checkparams(); + errorCond(value > 65535 || value < -32768, "value out of range"); + errorCond(address < 0, "address must be >= 0"); + _memoryDumper->SetMemoryValueWord(type, address, value); + return l.ReturnCount(); +} + +int LuaApi::RevertPrgChrChanges(lua_State *lua) +{ + LuaCallHelper l(lua); + checkparams(); + _debugger->RevertPrgChrChanges(); + return l.ReturnCount(); +} + +int LuaApi::RegisterMemoryCallback(lua_State *lua) +{ + LuaCallHelper l(lua); + int endAddr = l.ReadInteger(); + int startAddr = l.ReadInteger(); + CallbackType type = (CallbackType)l.ReadInteger(); + int reference = l.GetReference(); + checkparams(); + errorCond(startAddr > endAddr, "start address must be <= end address"); + errorCond(type < CallbackType::CpuRead || type > CallbackType::PpuWrite, "the specified type is invalid"); + errorCond(reference == LUA_NOREF, "the specified function could not be found"); + _context->RegisterMemoryCallback(type, startAddr, endAddr, reference); + _context->Log("Registered memory callback from $" + HexUtilities::ToHex((uint32_t)startAddr) + " to $" + HexUtilities::ToHex((uint32_t)endAddr)); + l.Return(reference); + return l.ReturnCount(); +} + +int LuaApi::UnregisterMemoryCallback(lua_State *lua) +{ + LuaCallHelper l(lua); + int endAddr = l.ReadInteger(); + int startAddr = l.ReadInteger(); + CallbackType type = (CallbackType)l.ReadInteger(); + int reference = l.ReadInteger(); + checkparams(); + errorCond(startAddr > endAddr, "start address must be <= end address"); + errorCond(type < CallbackType::CpuRead || type > CallbackType::PpuWrite, "the specified type is invalid"); + errorCond(reference == LUA_NOREF, "function reference is invalid"); + _context->UnregisterMemoryCallback(type, startAddr, endAddr, reference); + return l.ReturnCount(); +} + +int LuaApi::RegisterEventCallback(lua_State *lua) +{ + LuaCallHelper l(lua); + EventType type = (EventType)l.ReadInteger(); + int reference = l.GetReference(); + checkparams(); + errorCond(type < EventType::Power || type > EventType::CodeBreak, "the specified type is invalid"); + errorCond(reference == LUA_NOREF, "the specified function could not be found"); + _context->RegisterEventCallback(type, reference); + return l.ReturnCount(); +} + +int LuaApi::UnregisterEventCallback(lua_State *lua) +{ + LuaCallHelper l(lua); + EventType type = (EventType)l.ReadInteger(); + int reference = l.ReadInteger(); + checkparams(); + errorCond(type < EventType::Power || type > EventType::CodeBreak, "the specified type is invalid"); + errorCond(reference == LUA_NOREF, "function reference is invalid"); + _context->UnregisterEventCallback(type, reference); + return l.ReturnCount(); +} + +int LuaApi::DrawString(lua_State *lua) +{ + LuaCallHelper l(lua); + l.ForceParamCount(6); + int frameCount = l.ReadInteger(1); + int backColor = l.ReadInteger(0); + int color = l.ReadInteger(0xFFFFFFFF); + string text = l.ReadString(); + int y = l.ReadInteger(); + int x = l.ReadInteger(); + checkminparams(3); + + DebugHud::GetInstance()->DrawString(x, y, text, color, backColor, frameCount); + + return l.ReturnCount(); +} + +int LuaApi::DrawLine(lua_State *lua) +{ + LuaCallHelper l(lua); + int frameCount = l.ReadInteger(1); + int color = l.ReadInteger(0xFFFFFFFF); + int y2 = l.ReadInteger(); + int x2 = l.ReadInteger(); + int y = l.ReadInteger(); + int x = l.ReadInteger(); + checkminparams(4); + + DebugHud::GetInstance()->DrawLine(x, y, x2, y2, color, frameCount); + + return l.ReturnCount(); +} + +int LuaApi::DrawPixel(lua_State *lua) +{ + LuaCallHelper l(lua); + int frameCount = l.ReadInteger(1); + int color = l.ReadInteger(); + int y = l.ReadInteger(); + int x = l.ReadInteger(); + checkminparams(3); + + DebugHud::GetInstance()->DrawPixel(x, y, color, frameCount); + + return l.ReturnCount(); +} + +int LuaApi::DrawRectangle(lua_State *lua) +{ + LuaCallHelper l(lua); + int frameCount = l.ReadInteger(1); + bool fill = l.ReadBool(false); + int color = l.ReadInteger(0xFFFFFFFF); + int height = l.ReadInteger(); + int width = l.ReadInteger(); + int y = l.ReadInteger(); + int x = l.ReadInteger(); + checkminparams(4); + errorCond(height <= 0, "height must be >= 1"); + errorCond(width <= 0, "width must be >= 1"); + + DebugHud::GetInstance()->DrawRectangle(x, y, width, height, color, fill, frameCount); + + return l.ReturnCount(); +} + +int LuaApi::ClearScreen(lua_State *lua) +{ + LuaCallHelper l(lua); + checkparams(); + DebugHud::GetInstance()->ClearScreen(); + return l.ReturnCount(); +} + +int LuaApi::GetPixel(lua_State *lua) +{ + LuaCallHelper l(lua); + int y = l.ReadInteger(); + int x = l.ReadInteger(); + checkparams(); + errorCond(x < 0 || x > 255 || y < 0 || y > 239, "invalid x,y coordinates (must be between 0-255, 0-239)"); + + //Ignores intensify & grayscale bits + l.Return(EmulationSettings::GetRgbPalette()[PPU::GetPixel(x, y) & 0x3F]); + return l.ReturnCount(); +} + +int LuaApi::GetMouseState(lua_State *lua) +{ + LuaCallHelper l(lua); + MousePosition pos = ControlManager::GetMousePosition(); + checkparams(); + lua_newtable(lua); + lua_pushintvalue(x, pos.X); + lua_pushintvalue(y, pos.Y); + lua_pushboolvalue(left, ControlManager::IsMouseButtonPressed(MouseButton::LeftButton)); + lua_pushboolvalue(middle, ControlManager::IsMouseButtonPressed(MouseButton::MiddleButton)); + lua_pushboolvalue(right, ControlManager::IsMouseButtonPressed(MouseButton::RightButton)); + return 1; +} + +int LuaApi::Log(lua_State *lua) +{ + LuaCallHelper l(lua); + string text = l.ReadString(); + checkparams(); + _context->Log(text); + return l.ReturnCount(); +} + +int LuaApi::DisplayMessage(lua_State *lua) +{ + LuaCallHelper l(lua); + string text = l.ReadString(); + string category = l.ReadString(); + checkparams(); + MessageManager::DisplayMessage(category, text); + return l.ReturnCount(); +} + +int LuaApi::Reset(lua_State *lua) +{ + LuaCallHelper l(lua); + checkparams(); + Console::RequestReset(); + return l.ReturnCount(); +} + +int LuaApi::Break(lua_State *lua) +{ + LuaCallHelper l(lua); + checkparams(); + _debugger->Step(1); + return l.ReturnCount(); +} + +int LuaApi::Resume(lua_State *lua) +{ + LuaCallHelper l(lua); + checkparams(); + _debugger->Run(); + return l.ReturnCount(); +} + +int LuaApi::Execute(lua_State *lua) +{ + LuaCallHelper l(lua); + ExecuteCountType type = (ExecuteCountType)l.ReadInteger(); + int count = l.ReadInteger(); + checkparams(); + errorCond(count <= 0, "count must be >= 1"); + errorCond(type < ExecuteCountType::CpuCycles || type > ExecuteCountType::CpuInstructions, "type is invalid"); + + switch(type) { + case ExecuteCountType::CpuCycles: _debugger->StepCycles(count); break; + case ExecuteCountType::PpuCycles: _debugger->PpuStep(count); break; + case ExecuteCountType::CpuInstructions: _debugger->Step(count); break; + } + return l.ReturnCount(); +} + +int LuaApi::Rewind(lua_State *lua) +{ + LuaCallHelper l(lua); + int seconds = l.ReadInteger(); + checkparams(); + checkstartframe(); + errorCond(seconds <= 0, "seconds must be >= 1"); + RewindManager::RewindSeconds(seconds); + return l.ReturnCount(); +} + +int LuaApi::TakeScreenshot(lua_State *lua) +{ + LuaCallHelper l(lua); + checkparams(); + stringstream ss; + VideoDecoder::GetInstance()->TakeScreenshot(ss); + l.Return(ss.str()); + return l.ReturnCount(); +} + +int LuaApi::SaveSavestate(lua_State *lua) +{ + LuaCallHelper l(lua); + checkstartframe(); + stringstream ss; + SaveStateManager::SaveState(ss); + l.Return(ss.str()); + return l.ReturnCount(); +} + +int LuaApi::LoadSavestate(lua_State *lua) +{ + LuaCallHelper l(lua); + string savestate = l.ReadString(); + checkparams(); + checkstartframe(); + stringstream ss; + ss << savestate; + l.Return(SaveStateManager::LoadState(ss)); + return l.ReturnCount(); +} + +int LuaApi::GetInput(lua_State *lua) +{ + LuaCallHelper l(lua); + int port = l.ReadInteger(); + checkparams(); + errorCond(port < 0 || port > 3, "Invalid port number - must be between 0 to 3"); + + shared_ptr controller = std::dynamic_pointer_cast(ControlManager::GetControlDevice(port)); + errorCond(controller == nullptr, "Input port must be connected to a standard controller"); + + ButtonState state = controller->GetButtonState(); + lua_newtable(lua); + lua_pushboolvalue(a, state.A); + lua_pushboolvalue(b, state.B); + lua_pushboolvalue(start, state.Start); + lua_pushboolvalue(select, state.Select); + lua_pushboolvalue(up, state.Up); + lua_pushboolvalue(down, state.Down); + lua_pushboolvalue(left, state.Left); + lua_pushboolvalue(right, state.Right); + return 1; +} + +int LuaApi::SetInput(lua_State *lua) +{ + lua_settop(lua, 2); + luaL_checktype(lua, 2, LUA_TTABLE); + lua_getfield(lua, 2, "a"); + lua_getfield(lua, 2, "b"); + lua_getfield(lua, 2, "start"); + lua_getfield(lua, 2, "select"); + lua_getfield(lua, 2, "up"); + lua_getfield(lua, 2, "down"); + lua_getfield(lua, 2, "left"); + lua_getfield(lua, 2, "right"); + + LuaCallHelper l(lua); + ButtonState buttonState; + buttonState.Right = l.ReadBool(); + buttonState.Left = l.ReadBool(); + buttonState.Down = l.ReadBool(); + buttonState.Up = l.ReadBool(); + buttonState.Select = l.ReadBool(); + buttonState.Start = l.ReadBool(); + buttonState.B = l.ReadBool(); + buttonState.A = l.ReadBool(); + lua_pop(lua, 1); + int port = l.ReadInteger(); + + _debugger->SetInputOverride(port, buttonState.ToByte()); + errorCond(port < 0 || port > 3, "Invalid port number - must be between 0 to 3"); + + return l.ReturnCount(); +} + +int LuaApi::AddCheat(lua_State *lua) +{ + LuaCallHelper l(lua); + string gamegenieCode = l.ReadString(); + checkparams(); + errorCond(gamegenieCode.length() != 6 && gamegenieCode.length() != 8, "Game genie code must be 6 or 8 characters long"); + errorCond(gamegenieCode.find_first_not_of("APZLGITYEOXUKSVN", 0) != string::npos, "Game genie code may only contain these characters: AEGIKLNOPSTUVXYZ"); + CheatManager::GetInstance()->AddGameGenieCode(gamegenieCode); + return l.ReturnCount(); +} + +int LuaApi::ClearCheats(lua_State *lua) +{ + LuaCallHelper l(lua); + checkparams(); + CheatManager::GetInstance()->ClearCodes(); + return l.ReturnCount(); +} + +int LuaApi::SetState(lua_State *lua) +{ + LuaCallHelper l(lua); + lua_settop(lua, 1); + luaL_checktype(lua, -1, LUA_TTABLE); + + DebugState state; + + lua_getfield(lua, -1, "cpu"); + luaL_checktype(lua, -1, LUA_TTABLE); + lua_readint(a, state.CPU.A); + lua_readint(cycleCount, state.CPU.CycleCount); + lua_readint(irqFlag, state.CPU.IRQFlag); + lua_readbool(nmiFlag, state.CPU.NMIFlag); + lua_readint(pc, state.CPU.PC); + lua_readint(ps, state.CPU.PS); + lua_readint(sp, state.CPU.SP); + lua_readint(x, state.CPU.X); + lua_readint(y, state.CPU.Y); + lua_pop(lua, 1); + + lua_getfield(lua, -1, "ppu"); + luaL_checktype(lua, -1, LUA_TTABLE); + lua_readint(cycle, state.PPU.Cycle); + lua_readint(frameCount, state.PPU.FrameCount); + lua_readint(scanline, state.PPU.Scanline); + + lua_getfield(lua, -1, "control"); + luaL_checktype(lua, -1, LUA_TTABLE); + lua_readbool(backgroundEnabled, state.PPU.ControlFlags.BackgroundEnabled); + lua_readbool(backgroundMask, state.PPU.ControlFlags.BackgroundMask); + lua_readint(backgroundPatternAddr, state.PPU.ControlFlags.BackgroundPatternAddr); + lua_readbool(grayscale, state.PPU.ControlFlags.Grayscale); + lua_readbool(intensifyBlue, state.PPU.ControlFlags.IntensifyBlue); + lua_readbool(intensifyGreen, state.PPU.ControlFlags.IntensifyGreen); + lua_readbool(intensifyRed, state.PPU.ControlFlags.IntensifyRed); + lua_readbool(largeSprites, state.PPU.ControlFlags.LargeSprites); + lua_readbool(spriteMask, state.PPU.ControlFlags.SpriteMask); + lua_readint(spritePatternAddr, state.PPU.ControlFlags.SpritePatternAddr); + lua_readbool(spritesEnabled, state.PPU.ControlFlags.SpritesEnabled); + lua_readbool(nmiOnVBlank, state.PPU.ControlFlags.VBlank); + lua_readbool(verticalWrite, state.PPU.ControlFlags.VerticalWrite); + lua_pop(lua, 1); + + lua_getfield(lua, -1, "status"); + luaL_checktype(lua, -1, LUA_TTABLE); + lua_readbool(sprite0Hit, state.PPU.StatusFlags.Sprite0Hit); + lua_readbool(spriteOverflow, state.PPU.StatusFlags.SpriteOverflow); + lua_readbool(verticalBlank, state.PPU.StatusFlags.VerticalBlank); + lua_pop(lua, 1); + + lua_getfield(lua, -1, "state"); + luaL_checktype(lua, -1, LUA_TTABLE); + lua_readint(control, state.PPU.State.Control); + lua_readint(highBitShift, state.PPU.State.HighBitShift); + lua_readint(lowBitShift, state.PPU.State.LowBitShift); + lua_readint(mask, state.PPU.State.Mask); + lua_readint(spriteRamAddr, state.PPU.State.SpriteRamAddr); + lua_readint(status, state.PPU.State.Status); + lua_readint(tmpVideoRamAddr, state.PPU.State.TmpVideoRamAddr); + lua_readint(videoRamAddr, state.PPU.State.VideoRamAddr); + lua_readbool(writeToggle, state.PPU.State.WriteToggle); + lua_readint(xScroll, state.PPU.State.XScroll); + lua_pop(lua, 1); + + lua_pop(lua, 1); + + _debugger->SetState(state); + + return 0; +} + +int LuaApi::GetState(lua_State *lua) +{ + LuaCallHelper l(lua); + checkparams(); + DebugState state; + _debugger->GetState(&state, true); + + lua_newtable(lua); + lua_pushintvalue(region, state.Model); + + lua_starttable("cpu"); + lua_pushintvalue(a, state.CPU.A); + lua_pushintvalue(cycleCount, state.CPU.CycleCount); + lua_pushintvalue(irqFlag, state.CPU.IRQFlag); + lua_pushboolvalue(nmiFlag, state.CPU.NMIFlag); + lua_pushintvalue(pc, state.CPU.PC); + lua_pushintvalue(status, state.CPU.PS); + lua_pushintvalue(sp, state.CPU.SP); + lua_pushintvalue(x, state.CPU.X); + lua_pushintvalue(y, state.CPU.Y); + lua_endtable(); + + lua_starttable("ppu"); + lua_pushintvalue(cycle, state.PPU.Cycle); + lua_pushintvalue(frameCount, state.PPU.FrameCount); + lua_pushintvalue(scanline, state.PPU.Scanline); + + lua_starttable("control"); + lua_pushboolvalue(backgroundEnabled, state.PPU.ControlFlags.BackgroundEnabled); + lua_pushboolvalue(backgroundMask, state.PPU.ControlFlags.BackgroundMask); + lua_pushintvalue(backgroundPatternAddr, state.PPU.ControlFlags.BackgroundPatternAddr); + lua_pushboolvalue(grayscale, state.PPU.ControlFlags.Grayscale); + lua_pushboolvalue(intensifyBlue, state.PPU.ControlFlags.IntensifyBlue); + lua_pushboolvalue(intensifyGreen, state.PPU.ControlFlags.IntensifyGreen); + lua_pushboolvalue(intensifyRed, state.PPU.ControlFlags.IntensifyRed); + lua_pushboolvalue(largeSprites, state.PPU.ControlFlags.LargeSprites); + lua_pushboolvalue(spriteMask, state.PPU.ControlFlags.SpriteMask); + lua_pushintvalue(spritePatternAddr, state.PPU.ControlFlags.SpritePatternAddr); + lua_pushboolvalue(spritesEnabled, state.PPU.ControlFlags.SpritesEnabled); + lua_pushboolvalue(nmiOnVBlank, state.PPU.ControlFlags.VBlank); + lua_pushboolvalue(verticalWrite, state.PPU.ControlFlags.VerticalWrite); + lua_endtable(); + + lua_starttable("status"); + lua_pushboolvalue(sprite0Hit, state.PPU.StatusFlags.Sprite0Hit); + lua_pushboolvalue(spriteOverflow, state.PPU.StatusFlags.SpriteOverflow); + lua_pushboolvalue(verticalBlank, state.PPU.StatusFlags.VerticalBlank); + lua_endtable(); + + lua_starttable("state"); + lua_pushintvalue(control, state.PPU.State.Control); + lua_pushintvalue(highBitShift, state.PPU.State.HighBitShift); + lua_pushintvalue(lowBitShift, state.PPU.State.LowBitShift); + lua_pushintvalue(mask, state.PPU.State.Mask); + lua_pushintvalue(spriteRamAddr, state.PPU.State.SpriteRamAddr); + lua_pushintvalue(status, state.PPU.State.Status); + lua_pushintvalue(tmpVideoRamAddr, state.PPU.State.TmpVideoRamAddr); + lua_pushintvalue(videoRamAddr, state.PPU.State.VideoRamAddr); + lua_pushboolvalue(writeToggle, state.PPU.State.WriteToggle); + lua_pushintvalue(xScroll, state.PPU.State.XScroll); + lua_endtable(); + + lua_endtable(); //end ppu + + lua_starttable("cart"); + lua_pushintvalue(chrPageCount, state.Cartridge.ChrPageCount); + lua_pushintvalue(chrPageSize, state.Cartridge.ChrPageSize); + lua_pushintvalue(chrRamSize, state.Cartridge.ChrRamSize); + lua_pushintvalue(chrRomSize, state.Cartridge.ChrRomSize); + lua_pushintvalue(prgPageCount, state.Cartridge.PrgPageCount); + lua_pushintvalue(prgPageSize, state.Cartridge.PrgPageSize); + lua_pushintvalue(prgRomSize, state.Cartridge.PrgRomSize); + + lua_starttable("selectedPrgPages"); + for(int i = 0, max = 0x8000 / state.Cartridge.PrgPageSize; i < max; i++) { + lua_pushinteger(lua, i + 1); + lua_pushinteger(lua, (int32_t)state.Cartridge.PrgSelectedPages[i]); + lua_settable(lua, -3); + } + lua_endtable(); + + lua_starttable("selectedChrPages"); + for(int i = 0, max = 0x2000 / state.Cartridge.ChrPageSize; i < max; i++) { + lua_pushinteger(lua, i + 1); + lua_pushinteger(lua, (int32_t)state.Cartridge.ChrSelectedPages[i]); + lua_settable(lua, -3); + } + lua_endtable(); + + lua_endtable(); + + lua_starttable("apu"); + + lua_starttable("square1"); + PushSquareState(lua, state.APU.Square1); + lua_endtable(); + + lua_starttable("square2"); + PushSquareState(lua, state.APU.Square2); + lua_endtable(); + + lua_starttable("triangle"); + lua_pushboolvalue(enabled, state.APU.Triangle.Enabled); + lua_pushintvalue(frequency, state.APU.Triangle.Frequency); + PushLengthCounterState(lua, state.APU.Triangle.LengthCounter); + lua_pushintvalue(outputVolume, state.APU.Triangle.OutputVolume); + lua_pushintvalue(period, state.APU.Triangle.Period); + lua_pushintvalue(sequencePosition, state.APU.Triangle.SequencePosition); + lua_endtable(); + + lua_starttable("noise"); + lua_pushboolvalue(enabled, state.APU.Noise.Enabled); + lua_pushintvalue(frequency, state.APU.Noise.Frequency); + PushEnvelopeState(lua, state.APU.Noise.Envelope); + PushLengthCounterState(lua, state.APU.Noise.LengthCounter); + lua_pushboolvalue(modeFlag, state.APU.Noise.ModeFlag); + lua_pushintvalue(outputVolume, state.APU.Noise.OutputVolume); + lua_pushintvalue(period, state.APU.Noise.Period); + lua_pushintvalue(shiftRegister, state.APU.Noise.ShiftRegister); + lua_endtable(); + + lua_starttable("dmc"); + lua_pushintvalue(bytesRemaining, state.APU.Dmc.BytesRemaining); + lua_pushintvalue(frequency, state.APU.Dmc.Frequency); + lua_pushboolvalue(irqEnabled, state.APU.Dmc.IrqEnabled); + lua_pushboolvalue(loop, state.APU.Dmc.Loop); + lua_pushintvalue(outputVolume, state.APU.Dmc.OutputVolume); + lua_pushintvalue(period, state.APU.Dmc.Period); + lua_pushintvalue(sampleAddr, state.APU.Dmc.SampleAddr); + lua_pushintvalue(sampleLength, state.APU.Dmc.SampleLength); + lua_endtable(); + + lua_starttable("frameCounter"); + lua_pushintvalue(fiveStepMode, state.APU.FrameCounter.FiveStepMode); + lua_pushboolvalue(irqEnabled, state.APU.FrameCounter.IrqEnabled); + lua_pushintvalue(sequencePosition, state.APU.FrameCounter.SequencePosition); + lua_endtable(); + + lua_endtable(); //end apu + + return 1; +} + +void LuaApi::PushSquareState(lua_State *lua, ApuSquareState & state) +{ + lua_pushintvalue(duty, state.Duty); + lua_pushintvalue(dutyPosition, state.DutyPosition); + lua_pushintvalue(frequency, state.Frequency); + lua_pushintvalue(period, state.Period); + lua_pushboolvalue(sweepEnabled, state.SweepEnabled); + lua_pushboolvalue(sweepNegate, state.SweepNegate); + lua_pushintvalue(sweepPeriod, state.SweepPeriod); + lua_pushintvalue(sweepShift, state.SweepShift); + lua_pushboolvalue(enabled, state.Enabled); + lua_pushintvalue(outputVolume, state.OutputVolume); + PushEnvelopeState(lua, state.Envelope); + PushLengthCounterState(lua, state.LengthCounter); +} + +void LuaApi::PushEnvelopeState(lua_State *lua, ApuEnvelopeState & state) +{ + lua_starttable("envelope"); + lua_pushboolvalue(startFlag, state.StartFlag); + lua_pushboolvalue(loop, state.Loop); + lua_pushboolvalue(constantVolume, state.ConstantVolume); + lua_pushintvalue(divider, state.Divider); + lua_pushintvalue(counter, state.Counter); + lua_pushintvalue(volume, state.Volume); + lua_endtable(); +} + +void LuaApi::PushLengthCounterState(lua_State *lua, ApuLengthCounterState & state) +{ + lua_starttable("lengthCounter"); + lua_pushboolvalue(halt, state.Halt); + lua_pushintvalue(counter, state.Counter); + lua_pushintvalue(reloadValue, state.ReloadValue); + lua_endtable(); +} diff --git a/Core/LuaApi.h b/Core/LuaApi.h new file mode 100644 index 00000000..73af8252 --- /dev/null +++ b/Core/LuaApi.h @@ -0,0 +1,72 @@ +#pragma once +#include "stdafx.h" + +struct lua_State; +class ScriptingContext; +class Debugger; +class MemoryDumper; +struct ApuSquareState; +struct ApuEnvelopeState; +struct ApuLengthCounterState; + +class LuaApi +{ +public: + static void SetContext(ScriptingContext *context); + static void RegisterDebugger(Debugger* debugger); + static int GetLibrary(lua_State *lua); + + static int ReadMemory(lua_State *lua); + static int WriteMemory(lua_State *lua); + static int DebugReadMemory(lua_State *lua); + static int DebugWriteMemory(lua_State *lua); + static int ReadMemoryWord(lua_State *lua); + static int WriteMemoryWord(lua_State *lua); + static int DebugReadMemoryWord(lua_State *lua); + static int DebugWriteMemoryWord(lua_State *lua); + static int RevertPrgChrChanges(lua_State *lua); + + static int RegisterMemoryCallback(lua_State *lua); + static int UnregisterMemoryCallback(lua_State *lua); + static int RegisterEventCallback(lua_State *lua); + static int UnregisterEventCallback(lua_State *lua); + + static int DrawString(lua_State *lua); + static int DrawLine(lua_State *lua); + static int DrawPixel(lua_State *lua); + static int DrawRectangle(lua_State *lua); + static int ClearScreen(lua_State *lua); + static int GetPixel(lua_State *lua); + static int GetMouseState(lua_State *lua); + + static int Log(lua_State *lua); + static int DisplayMessage(lua_State *lua); + + static int Reset(lua_State *lua); + static int Break(lua_State *lua); + static int Resume(lua_State *lua); + static int Execute(lua_State *lua); + static int Rewind(lua_State *lua); + + static int TakeScreenshot(lua_State *lua); + static int SaveSavestate(lua_State *lua); + static int LoadSavestate(lua_State *lua); + + static int GetInput(lua_State *lua); + static int SetInput(lua_State *lua); + + static int AddCheat(lua_State *lua); + static int ClearCheats(lua_State *lua); + + static int SetState(lua_State *lua); + static int GetState(lua_State *lua); + +private: + static Debugger* _debugger; + static MemoryDumper* _memoryDumper; + static ScriptingContext* _context; + + static void PushSquareState(lua_State* lua, ApuSquareState &state); + static void PushEnvelopeState(lua_State* lua, ApuEnvelopeState &state); + static void PushLengthCounterState(lua_State* lua, ApuLengthCounterState &state); +}; diff --git a/Core/LuaCallHelper.cpp b/Core/LuaCallHelper.cpp new file mode 100644 index 00000000..6fc4cfe3 --- /dev/null +++ b/Core/LuaCallHelper.cpp @@ -0,0 +1,117 @@ +#include "stdafx.h" +#include "LuaCallHelper.h" + +LuaCallHelper::LuaCallHelper(lua_State *lua) : _lua(lua) +{ + _stackSize = lua_gettop(lua); +} + +void LuaCallHelper::ForceParamCount(int paramCount) +{ + while(lua_gettop(_lua) < paramCount) { + lua_pushnil(_lua); + } +} + +bool LuaCallHelper::CheckParamCount(int minParamCount) +{ + if(minParamCount >= 0 && _stackSize < _paramCount && _stackSize >= minParamCount) { + return true; + } + if(_stackSize != _paramCount) { + string message = string("too ") + (_stackSize < _paramCount ? "little" : "many") + " parameters. expected " + std::to_string(_paramCount) + " got " + std::to_string(_stackSize); + luaL_error(_lua, message.c_str()); + return false; + } + return true; +} + +double LuaCallHelper::ReadDouble() +{ + _paramCount++; + double value = 0; + if(lua_isnumber(_lua, -1)) { + value = lua_tonumber(_lua, -1); + } + lua_pop(_lua, 1); + return value; +} + +bool LuaCallHelper::ReadBool(bool defaultValue) +{ + _paramCount++; + bool value = defaultValue; + if(lua_isboolean(_lua, -1)) { + value = lua_toboolean(_lua, -1) != 0; + } else if(lua_isnumber(_lua, -1)) { + value = lua_tonumber(_lua, -1) != 0; + } + lua_pop(_lua, 1); + return value; +} + +uint32_t LuaCallHelper::ReadInteger(uint32_t defaultValue) +{ + _paramCount++; + uint32_t value = defaultValue; + if(lua_isinteger(_lua, -1)) { + value = (uint32_t)lua_tointeger(_lua, -1); + } else if(lua_isnumber(_lua, -1)) { + value = (uint32_t)lua_tonumber(_lua, -1); + } + lua_pop(_lua, 1); + return value; +} + +string LuaCallHelper::ReadString() +{ + _paramCount++; + size_t len; + string str; + if(lua_isstring(_lua, -1)) { + const char* cstr = lua_tolstring(_lua, -1, &len); + str = string(cstr, len); + } + lua_pop(_lua, 1); + return str; +} + +int LuaCallHelper::GetReference() +{ + _paramCount++; + if(lua_isfunction(_lua, -1)) { + return luaL_ref(_lua, LUA_REGISTRYINDEX); + } else { + lua_pop(_lua, 1); + return LUA_NOREF; + } +} + +void LuaCallHelper::Return(bool value) +{ + lua_pushboolean(_lua, value); + _returnCount++; +} + +void LuaCallHelper::Return(int value) +{ + lua_pushinteger(_lua, value); + _returnCount++; +} + +void LuaCallHelper::Return(uint32_t value) +{ + lua_pushinteger(_lua, value); + _returnCount++; +} + +void LuaCallHelper::Return(string value) +{ + lua_pushlstring(_lua, value.c_str(), value.size()); + _returnCount++; +} + +int LuaCallHelper::ReturnCount() +{ + return _returnCount; +} diff --git a/Core/LuaCallHelper.h b/Core/LuaCallHelper.h new file mode 100644 index 00000000..28b562ca --- /dev/null +++ b/Core/LuaCallHelper.h @@ -0,0 +1,31 @@ +#pragma once +#include "stdafx.h" +#include "../Lua/lua.hpp" + +class LuaCallHelper +{ +private: + int _stackSize = 0; + int _paramCount = 0; + int _returnCount = 0; + lua_State* _lua; + +public: + LuaCallHelper(lua_State* lua); + + void ForceParamCount(int paramCount); + bool CheckParamCount(int minParamCount = -1); + + double ReadDouble(); + bool ReadBool(bool defaultValue = false); + uint32_t ReadInteger(uint32_t defaultValue = 0); + string ReadString(); + int GetReference(); + + void Return(bool value); + void Return(int value); + void Return(uint32_t value); + void Return(string value); + + int ReturnCount(); +}; \ No newline at end of file diff --git a/Core/LuaScriptingContext.cpp b/Core/LuaScriptingContext.cpp new file mode 100644 index 00000000..e0a468c3 --- /dev/null +++ b/Core/LuaScriptingContext.cpp @@ -0,0 +1,67 @@ +#include "stdafx.h" +#include "../Lua/lua.hpp" +#include "LuaScriptingContext.h" +#include "LuaApi.h" +#include "LuaCallHelper.h" +#include "DebuggerTypes.h" +#include "Debugger.h" + +LuaScriptingContext::LuaScriptingContext() { } + +LuaScriptingContext::~LuaScriptingContext() +{ + if(_lua) { + lua_close(_lua); + _lua = nullptr; + } +} + +bool LuaScriptingContext::LoadScript(string scriptContent, Debugger* debugger) +{ + int iErr = 0; + _lua = luaL_newstate(); + LuaApi::RegisterDebugger(debugger); + LuaApi::SetContext(this); + + luaL_openlibs(_lua); + luaL_requiref(_lua, "emu", LuaApi::GetLibrary, 1); + Log("Loading script..."); + if((iErr = luaL_loadstring(_lua, scriptContent.c_str())) == 0) { + if((iErr = lua_pcall(_lua, 0, LUA_MULTRET, 0)) == 0) { + //Script loaded properly + Log("Script loaded successfully."); + return true; + } + } + + if(lua_isstring(_lua, -1)) { + Log(lua_tostring(_lua, -1)); + } + return false; +} + +void LuaScriptingContext::CallMemoryCallback(int addr, int value, CallbackType type) +{ + LuaApi::SetContext(this); + for(int &ref : _callbacks[(int)type][addr]) { + lua_rawgeti(_lua, LUA_REGISTRYINDEX, ref); + lua_pushinteger(_lua, addr); + lua_pushinteger(_lua, value); + if(lua_pcall(_lua, 2, 0, 0) != 0) { + Log(lua_tostring(_lua, -1)); + } + } +} + +int LuaScriptingContext::InternalCallEventCallback(EventType type) +{ + LuaApi::SetContext(this); + LuaCallHelper l(_lua); + for(int &ref : _eventCallbacks[(int)type]) { + lua_rawgeti(_lua, LUA_REGISTRYINDEX, ref); + if(lua_pcall(_lua, 0, 0, 0) != 0) { + Log(lua_tostring(_lua, -1)); + } + } + return l.ReturnCount(); +} diff --git a/Core/LuaScriptingContext.h b/Core/LuaScriptingContext.h new file mode 100644 index 00000000..6dea6061 --- /dev/null +++ b/Core/LuaScriptingContext.h @@ -0,0 +1,20 @@ +#pragma once +#include "stdafx.h" +#include "ScriptingContext.h" + +struct lua_State; +class Debugger; + +class LuaScriptingContext : public ScriptingContext +{ +private: + lua_State* _lua = nullptr; + +public: + LuaScriptingContext(); + ~LuaScriptingContext(); + + bool LoadScript(string scriptContent, Debugger* debugger); + void CallMemoryCallback(int addr, int value, CallbackType type); + int InternalCallEventCallback(EventType type); +}; diff --git a/Core/MemoryDumper.cpp b/Core/MemoryDumper.cpp index 9e52ac2e..d5879a49 100644 --- a/Core/MemoryDumper.cpp +++ b/Core/MemoryDumper.cpp @@ -126,7 +126,7 @@ void MemoryDumper::SetMemoryValues(DebugMemoryType memoryType, uint32_t address, } } -void MemoryDumper::SetMemoryValue(DebugMemoryType memoryType, uint32_t address, uint8_t value, bool preventRebuildCache) +void MemoryDumper::SetMemoryValue(DebugMemoryType memoryType, uint32_t address, uint8_t value, bool preventRebuildCache, bool disableSideEffects) { switch(memoryType) { case DebugMemoryType::CpuMemory: @@ -134,15 +134,15 @@ void MemoryDumper::SetMemoryValue(DebugMemoryType memoryType, uint32_t address, _debugger->GetAbsoluteAddressAndType(address, &info); if(info.Address >= 0) { switch(info.Type) { - case AddressType::InternalRam: SetMemoryValue(DebugMemoryType::InternalRam, info.Address, value, preventRebuildCache); break; - case AddressType::PrgRom: SetMemoryValue(DebugMemoryType::PrgRom, info.Address, value, preventRebuildCache); break; - case AddressType::WorkRam: SetMemoryValue(DebugMemoryType::WorkRam, info.Address, value, preventRebuildCache); break; - case AddressType::SaveRam: SetMemoryValue(DebugMemoryType::SaveRam, info.Address, value, preventRebuildCache); break; + case AddressType::InternalRam: SetMemoryValue(DebugMemoryType::InternalRam, info.Address, value, preventRebuildCache, disableSideEffects); break; + case AddressType::PrgRom: SetMemoryValue(DebugMemoryType::PrgRom, info.Address, value, preventRebuildCache, disableSideEffects); break; + case AddressType::WorkRam: SetMemoryValue(DebugMemoryType::WorkRam, info.Address, value, preventRebuildCache, disableSideEffects); break; + case AddressType::SaveRam: SetMemoryValue(DebugMemoryType::SaveRam, info.Address, value, preventRebuildCache, disableSideEffects); break; } } break; - case DebugMemoryType::InternalRam: _memoryManager->DebugWrite(address, value); break; + case DebugMemoryType::InternalRam: _memoryManager->DebugWrite(address, value, disableSideEffects); break; case DebugMemoryType::PaletteMemory: _ppu->WritePaletteRAM(address, value); break; case DebugMemoryType::SpriteMemory: _ppu->GetSpriteRam()[address] = value; break; @@ -166,7 +166,18 @@ void MemoryDumper::SetMemoryValue(DebugMemoryType memoryType, uint32_t address, } } -uint8_t MemoryDumper::GetMemoryValue(DebugMemoryType memoryType, uint32_t address) +uint16_t MemoryDumper::GetMemoryValueWord(DebugMemoryType memoryType, uint32_t address, bool disableSideEffects) +{ + return GetMemoryValue(memoryType, address, disableSideEffects) | (GetMemoryValue(memoryType, address + 1, disableSideEffects) << 8); +} + +void MemoryDumper::SetMemoryValueWord(DebugMemoryType memoryType, uint32_t address, uint16_t value, bool preventRebuildCache, bool disableSideEffects) +{ + SetMemoryValue(memoryType, address, (uint8_t)value, preventRebuildCache, disableSideEffects); + SetMemoryValue(memoryType, address + 1, (uint8_t)(value >> 8), preventRebuildCache, disableSideEffects); +} + +uint8_t MemoryDumper::GetMemoryValue(DebugMemoryType memoryType, uint32_t address, bool disableSideEffects) { switch(memoryType) { case DebugMemoryType::CpuMemory: @@ -174,15 +185,15 @@ uint8_t MemoryDumper::GetMemoryValue(DebugMemoryType memoryType, uint32_t addres _debugger->GetAbsoluteAddressAndType(address, &info); if(info.Address >= 0) { switch(info.Type) { - case AddressType::InternalRam: return GetMemoryValue(DebugMemoryType::InternalRam, info.Address); - case AddressType::PrgRom: return GetMemoryValue(DebugMemoryType::PrgRom, info.Address); - case AddressType::WorkRam: return GetMemoryValue(DebugMemoryType::WorkRam, info.Address); - case AddressType::SaveRam: return GetMemoryValue(DebugMemoryType::SaveRam, info.Address); + case AddressType::InternalRam: return GetMemoryValue(DebugMemoryType::InternalRam, info.Address, disableSideEffects); + case AddressType::PrgRom: return GetMemoryValue(DebugMemoryType::PrgRom, info.Address, disableSideEffects); + case AddressType::WorkRam: return GetMemoryValue(DebugMemoryType::WorkRam, info.Address, disableSideEffects); + case AddressType::SaveRam: return GetMemoryValue(DebugMemoryType::SaveRam, info.Address, disableSideEffects); } } break; - case DebugMemoryType::InternalRam: return _memoryManager->DebugRead(address); + case DebugMemoryType::InternalRam: return _memoryManager->DebugRead(address, disableSideEffects); case DebugMemoryType::PaletteMemory: return _ppu->ReadPaletteRAM(address); case DebugMemoryType::SpriteMemory: return _ppu->GetSpriteRam()[address]; diff --git a/Core/MemoryDumper.h b/Core/MemoryDumper.h index e76de189..e16daea2 100644 --- a/Core/MemoryDumper.h +++ b/Core/MemoryDumper.h @@ -29,8 +29,10 @@ public: void GetSprites(uint32_t* frameBuffer); void GetPalette(uint32_t* frameBuffer); - uint8_t GetMemoryValue(DebugMemoryType memoryType, uint32_t address); + uint8_t GetMemoryValue(DebugMemoryType memoryType, uint32_t address, bool disableSideEffects = true); + uint16_t GetMemoryValueWord(DebugMemoryType memoryType, uint32_t address, bool disableSideEffects = true); + void SetMemoryValue(DebugMemoryType memoryType, uint32_t address, uint8_t value, bool preventRebuildCache = false, bool disableSideEffects = true); + void SetMemoryValueWord(DebugMemoryType memoryType, uint32_t address, uint16_t value, bool preventRebuildCache = false, bool disableSideEffects = true); void SetMemoryValues(DebugMemoryType memoryType, uint32_t address, uint8_t* data, int32_t length); - void SetMemoryValue(DebugMemoryType memoryType, uint32_t address, uint8_t value, bool preventRebuildCache = false); void SetMemoryState(DebugMemoryType type, uint8_t *buffer); }; \ No newline at end of file diff --git a/Core/MemoryManager.cpp b/Core/MemoryManager.cpp index 445e69a9..3e2c8f7f 100644 --- a/Core/MemoryManager.cpp +++ b/Core/MemoryManager.cpp @@ -101,13 +101,22 @@ uint8_t* MemoryManager::GetInternalRAM() return _internalRAM; } -uint8_t MemoryManager::DebugRead(uint16_t addr, bool disableRegisterReads) +uint8_t MemoryManager::DebugRead(uint16_t addr, bool disableSideEffects) { uint8_t value = 0x00; if(addr <= 0x1FFF) { value = _internalRAM[addr & 0x07FF]; - } else if(!disableRegisterReads || addr > 0x4017) { - value = ReadRegister(addr); + } else { + IMemoryHandler* handler = _ramReadHandlers[addr]; + if(handler) { + if(handler == _mapper.get() && disableSideEffects) { + value = ((BaseMapper*)handler)->DebugReadRAM(addr); + } else { + value = handler->ReadRAM(addr); + } + } else { + value = GetOpenBus(); + } } CheatManager::ApplyRamCodes(addr, value); @@ -154,12 +163,19 @@ void MemoryManager::Write(uint16_t addr, uint8_t value) } } -void MemoryManager::DebugWrite(uint16_t addr, uint8_t value) +void MemoryManager::DebugWrite(uint16_t addr, uint8_t value, bool disableSideEffects) { if(addr <= 0x1FFF) { _internalRAM[addr & 0x07FF] = value; } else { - WriteRegister(addr, value); + IMemoryHandler* handler = _ramReadHandlers[addr]; + if(handler) { + if(handler == _mapper.get() && disableSideEffects) { + ((BaseMapper*)handler)->DebugWriteRAM(addr, value); + } else { + handler->WriteRAM(addr, value); + } + } } } diff --git a/Core/MemoryManager.h b/Core/MemoryManager.h index 91c3b13b..cada4dc3 100644 --- a/Core/MemoryManager.h +++ b/Core/MemoryManager.h @@ -39,9 +39,9 @@ class MemoryManager: public Snapshotable void RegisterIODevice(IMemoryHandler *handler); void UnregisterIODevice(IMemoryHandler *handler); - uint8_t DebugRead(uint16_t addr, bool disableRegisterReads = true); + uint8_t DebugRead(uint16_t addr, bool disableSideEffects = true); uint16_t DebugReadWord(uint16_t addr); - void DebugWrite(uint16_t addr, uint8_t value); + void DebugWrite(uint16_t addr, uint8_t value, bool disableSideEffects = true); uint8_t* GetInternalRAM(); diff --git a/Core/NoiseChannel.h b/Core/NoiseChannel.h index aecbff06..05312115 100644 --- a/Core/NoiseChannel.h +++ b/Core/NoiseChannel.h @@ -85,4 +85,18 @@ public: break; } } + + ApuNoiseState GetState() + { + ApuNoiseState state; + state.Enabled = _enabled; + state.Envelope = ApuEnvelope::GetState(); + state.Frequency = (uint32_t)((CPU::GetClockRate(GetNesModel()) / (_period + 1)) / (_modeFlag ? 93 : 1)); + state.LengthCounter = ApuLengthCounter::GetState(); + state.ModeFlag = _modeFlag; + state.OutputVolume = _lastOutput; + state.Period = _period; + state.ShiftRegister = _shiftRegister; + return state; + } }; \ No newline at end of file diff --git a/Core/OggReader.cpp b/Core/OggReader.cpp index 128b263f..dcf5f908 100644 --- a/Core/OggReader.cpp +++ b/Core/OggReader.cpp @@ -90,7 +90,7 @@ bool OggReader::LoadSamples() void OggReader::ApplySamples(int16_t * buffer, size_t sampleCount, uint8_t volume) { - while(blip_samples_avail(_blipLeft) < sampleCount) { + while(blip_samples_avail(_blipLeft) < (int)sampleCount) { if(!LoadSamples()) { break; } diff --git a/Core/PPU.h b/Core/PPU.h index 4cb4ef16..c701b8cf 100644 --- a/Core/PPU.h +++ b/Core/PPU.h @@ -232,4 +232,9 @@ class PPU : public IMemoryHandler, public Snapshotable uint32_t argbColor = EmulationSettings::GetRgbPalette()[pixelData & 0x3F]; return (argbColor & 0xFF) + ((argbColor >> 8) & 0xFF) + ((argbColor >> 16) & 0xFF); } + + static uint16_t GetPixel(uint8_t x, uint8_t y) + { + return PPU::Instance->_currentOutputBuffer[y << 8 | x]; + } }; diff --git a/Core/ScriptHost.cpp b/Core/ScriptHost.cpp new file mode 100644 index 00000000..fd6a7b6d --- /dev/null +++ b/Core/ScriptHost.cpp @@ -0,0 +1,60 @@ +#include "stdafx.h" +#include "ScriptHost.h" +#include "ScriptingContext.h" +#include "LuaScriptingContext.h" + +ScriptHost::ScriptHost(int scriptId) +{ + _scriptId = scriptId; +} + +int ScriptHost::GetScriptId() +{ + return _scriptId; +} + +const char* ScriptHost::GetLog() +{ + return _context ? _context->GetLog() : ""; +} + +bool ScriptHost::LoadScript(string scriptContent, Debugger* debugger) +{ + _context.reset(); + if(scriptContent.size() > 0) { + _context.reset(new LuaScriptingContext()); + + if(!_context->LoadScript(scriptContent, debugger)) { + return false; + } + } + return true; +} + +void ScriptHost::ProcessCpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type) +{ + if(_context) { + switch(type) { + case MemoryOperationType::Read: _context->CallMemoryCallback(addr, value, CallbackType::CpuRead); break; + case MemoryOperationType::Write: _context->CallMemoryCallback(addr, value, CallbackType::CpuWrite); break; + case MemoryOperationType::ExecOpCode: _context->CallMemoryCallback(addr, value, CallbackType::CpuExec); break; + } + } +} + +void ScriptHost::ProcessPpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type) +{ + if(_context) { + switch(type) { + case MemoryOperationType::Read: _context->CallMemoryCallback(addr, value, CallbackType::PpuRead); break; + case MemoryOperationType::Write: _context->CallMemoryCallback(addr, value, CallbackType::PpuWrite); break; + } + } +} + +void ScriptHost::ProcessEvent(EventType eventType) +{ + if(_context) { + _context->CallEventCallback(eventType); + } +} diff --git a/Core/ScriptHost.h b/Core/ScriptHost.h new file mode 100644 index 00000000..5bf36ffd --- /dev/null +++ b/Core/ScriptHost.h @@ -0,0 +1,25 @@ +#pragma once +#include "stdafx.h" +#include "DebuggerTypes.h" + +class ScriptingContext; +class Debugger; + +class ScriptHost +{ +private: + shared_ptr _context; + int _scriptId; + +public: + ScriptHost(int scriptId); + + int GetScriptId(); + const char* GetLog(); + + bool LoadScript(string scriptContent, Debugger* debugger); + + void ProcessCpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type); + void ProcessPpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type); + void ProcessEvent(EventType eventType); +}; \ No newline at end of file diff --git a/Core/ScriptingContext.cpp b/Core/ScriptingContext.cpp new file mode 100644 index 00000000..4e029d4d --- /dev/null +++ b/Core/ScriptingContext.cpp @@ -0,0 +1,87 @@ +#include "stdafx.h" +#include "ScriptingContext.h" +#include "DebuggerTypes.h" + +void ScriptingContext::Log(string message) +{ + auto lock = _logLock.AcquireSafe(); + _logRows.push_back(message); + if(_logRows.size() > 500) { + _logRows.pop_front(); + } +} + +const char* ScriptingContext::GetLog() +{ + auto lock = _logLock.AcquireSafe(); + stringstream ss; + for(string &msg : _logRows) { + ss << msg << "\n"; + } + _log = ss.str(); + return _log.c_str(); +} + +int ScriptingContext::CallEventCallback(EventType type) +{ + _inStartFrameEvent = type == EventType::StartFrame; + int returnValue = InternalCallEventCallback(type); + _inStartFrameEvent = false; + + return returnValue; +} + +bool ScriptingContext::CheckInStartFrameEvent() +{ + return _inStartFrameEvent; +} + +void ScriptingContext::RegisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference) +{ + if(endAddr < startAddr) { + return; + } + + if(startAddr == 0 && endAddr == 0) { + if(type <= CallbackType::CpuExec) { + endAddr = 0xFFFF; + } else { + endAddr = 0x3FFF; + } + } + + for(int i = startAddr; i < endAddr; i++) { + _callbacks[(int)type][i].push_back(reference); + } +} + +void ScriptingContext::UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference) +{ + if(endAddr < startAddr) { + return; + } + + if(startAddr == 0 && endAddr == 0) { + if(type <= CallbackType::CpuExec) { + endAddr = 0xFFFF; + } else { + endAddr = 0x3FFF; + } + } + + for(int i = startAddr; startAddr < endAddr; i++) { + vector &refs = _callbacks[(int)type][i]; + refs.erase(std::remove(refs.begin(), refs.end(), reference), refs.end()); + } +} + +void ScriptingContext::RegisterEventCallback(EventType type, int reference) +{ + _eventCallbacks[(int)type].push_back(reference); +} + +void ScriptingContext::UnregisterEventCallback(EventType type, int reference) +{ + vector &callbacks = _eventCallbacks[(int)type]; + callbacks.erase(std::remove(callbacks.begin(), callbacks.end(), reference), callbacks.end()); +} diff --git a/Core/ScriptingContext.h b/Core/ScriptingContext.h new file mode 100644 index 00000000..3db38672 --- /dev/null +++ b/Core/ScriptingContext.h @@ -0,0 +1,46 @@ +#pragma once +#include "stdafx.h" +#include +#include "../Utilities/SimpleLock.h" +#include "DebuggerTypes.h" + +class Debugger; + +enum class CallbackType +{ + CpuRead = 0, + CpuWrite = 1, + CpuExec = 2, + PpuRead = 3, + PpuWrite = 4 +}; + +class ScriptingContext +{ +private: + std::deque _logRows; + string _log; + SimpleLock _logLock; + bool _inStartFrameEvent = false; + +protected: + vector _callbacks[5][0x10000]; + vector _eventCallbacks[7]; + +public: + virtual bool LoadScript(string scriptContent, Debugger* debugger) = 0; + + void Log(string message); + const char* GetLog(); + + virtual void CallMemoryCallback(int addr, int value, CallbackType type) = 0; + virtual int InternalCallEventCallback(EventType type) = 0; + + int CallEventCallback(EventType type); + bool CheckInStartFrameEvent(); + + void RegisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference); + void UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference); + void RegisterEventCallback(EventType type, int reference); + void UnregisterEventCallback(EventType type, int reference); +}; diff --git a/Core/SquareChannel.h b/Core/SquareChannel.h index 01105b79..05197eae 100644 --- a/Core/SquareChannel.h +++ b/Core/SquareChannel.h @@ -183,4 +183,22 @@ public: _reloadSweep = false; } } + + ApuSquareState GetState() + { + ApuSquareState state; + state.Duty = _duty; + state.DutyPosition = _dutyPos; + state.Enabled = _enabled; + state.Envelope = ApuEnvelope::GetState(); + state.Frequency = (uint32_t)(CPU::GetClockRate(GetNesModel()) / 16.0 / (_realPeriod + 1)); + state.LengthCounter = ApuLengthCounter::GetState(); + state.OutputVolume = _lastOutput; + state.Period = _realPeriod; + state.SweepEnabled = _sweepEnabled; + state.SweepNegate = _sweepNegate; + state.SweepPeriod = _sweepPeriod; + state.SweepShift = _sweepShift; + return state; + } }; \ No newline at end of file diff --git a/Core/StandardController.cpp b/Core/StandardController.cpp index 147cb9a3..92334f17 100644 --- a/Core/StandardController.cpp +++ b/Core/StandardController.cpp @@ -32,7 +32,7 @@ bool StandardController::IsMicrophoneActive() return false; } -uint8_t StandardController::GetButtonState() +ButtonState StandardController::GetButtonState() { ButtonState state; @@ -70,12 +70,12 @@ uint8_t StandardController::GetButtonState() } } - return state.ToByte(); + return state; } uint32_t StandardController::GetNetPlayState() { - return GetButtonState(); + return GetButtonState().ToByte(); } uint8_t StandardController::GetPortOutput() @@ -132,7 +132,7 @@ void StandardController::RefreshStateBuffer() uint8_t StandardController::RefreshState() { - return GetButtonState(); + return GetButtonState().ToByte(); } void StandardController::AddAdditionalController(shared_ptr controller) diff --git a/Core/StandardController.h b/Core/StandardController.h index a9741d46..2c289537 100644 --- a/Core/StandardController.h +++ b/Core/StandardController.h @@ -12,7 +12,6 @@ private: uint8_t _lastButtonState = 0; shared_ptr _additionalController; - uint8_t GetButtonState(); protected: uint8_t RefreshState() override; @@ -21,6 +20,7 @@ protected: public: StandardController(uint8_t port, bool emptyPort = false); + ButtonState GetButtonState(); uint32_t GetNetPlayState() override; uint8_t GetPortOutput() override; diff --git a/Core/TriangleChannel.h b/Core/TriangleChannel.h index c2da2a9a..0074c448 100644 --- a/Core/TriangleChannel.h +++ b/Core/TriangleChannel.h @@ -22,7 +22,6 @@ protected: //The sequencer is clocked by the timer as long as both the linear counter and the length counter are nonzero. if(_lengthCounter > 0 && _linearCounter > 0) { _sequencePosition = (_sequencePosition + 1) & 0x1F; - if(_period >= 2 || !EmulationSettings::CheckFlag(EmulationFlags::SilenceTriangleHighFreq)) { //Disabling the triangle channel when period is < 2 removes "pops" in the audio that are caused by the ultrasonic frequencies @@ -101,4 +100,16 @@ public: _linearReloadFlag = false; } } + + ApuTriangleState GetState() + { + ApuTriangleState state; + state.Enabled = _enabled; + state.Frequency = (uint32_t)(CPU::GetClockRate(GetNesModel()) / 32.0 / (_period + 1)); + state.LengthCounter = ApuLengthCounter::GetState(); + state.OutputVolume = _lastOutput; + state.Period = _period; + state.SequencePosition = _sequencePosition; + return state; + } }; \ No newline at end of file diff --git a/Core/Types.h b/Core/Types.h index a89c33de..fb205545 100644 --- a/Core/Types.h +++ b/Core/Types.h @@ -78,7 +78,6 @@ struct CartridgeState uint32_t Nametables[8]; }; - struct PPUControlFlags { bool VerticalWrite; @@ -137,4 +136,97 @@ struct SpriteInfo : TileInfo uint8_t SpriteX; bool VerticalMirror; //used by HD ppu +}; + +struct ApuLengthCounterState +{ + bool Halt; + uint8_t Counter; + uint8_t ReloadValue; +}; + +struct ApuEnvelopeState +{ + bool StartFlag; + bool Loop; + bool ConstantVolume; + uint8_t Divider; + uint8_t Counter; + uint8_t Volume; +}; + +struct ApuSquareState +{ + uint8_t Duty; + uint8_t DutyPosition; + uint16_t Period; + + bool SweepEnabled; + bool SweepNegate; + uint8_t SweepPeriod; + uint8_t SweepShift; + + bool Enabled; + uint8_t OutputVolume; + uint32_t Frequency; + + ApuLengthCounterState LengthCounter; + ApuEnvelopeState Envelope; +}; + +struct ApuTriangleState +{ + uint16_t Period; + uint8_t SequencePosition; + + bool Enabled; + uint32_t Frequency; + uint8_t OutputVolume; + + ApuLengthCounterState LengthCounter; +}; + +struct ApuNoiseState +{ + uint16_t Period; + uint16_t ShiftRegister; + bool ModeFlag; + + bool Enabled; + uint32_t Frequency; + uint8_t OutputVolume; + + ApuLengthCounterState LengthCounter; + ApuEnvelopeState Envelope; +}; + +struct ApuDmcState +{ + uint16_t SampleAddr; + uint16_t SampleLength; + + bool Loop; + bool IrqEnabled; + uint16_t Period; + uint16_t BytesRemaining; + + uint32_t Frequency; + uint8_t OutputVolume; +}; + +struct ApuFrameCounterState +{ + bool FiveStepMode; + uint8_t SequencePosition; + bool IrqEnabled; +}; + +struct ApuState +{ + ApuSquareState Square1; + ApuSquareState Square2; + ApuTriangleState Triangle; + ApuNoiseState Noise; + ApuDmcState Dmc; + ApuFrameCounterState FrameCounter; }; \ No newline at end of file diff --git a/GUI.NET/Config/DebugInfo.cs b/GUI.NET/Config/DebugInfo.cs index 1cbcc045..6a49dbb5 100644 --- a/GUI.NET/Config/DebugInfo.cs +++ b/GUI.NET/Config/DebugInfo.cs @@ -90,6 +90,8 @@ namespace Mesen.GUI.Config public class DebugInfo { + private const int MaxRecentScripts = 10; + public DebugViewInfo LeftView; public DebugViewInfo RightView; @@ -161,6 +163,13 @@ namespace Mesen.GUI.Config public bool TraceIndentCode = false; public Size TraceLoggerSize = new Size(0, 0); + public Size ScriptWindowSize = new Size(0, 0); + public int ScriptCodeWindowHeight = 0; + public List RecentScripts = new List(); + public bool SaveScriptBeforeRun = true; + public bool AutoReloadScript = false; + public int ScriptZoom = 100; + public DebugInfo() { LeftView = new DebugViewInfo(); @@ -178,5 +187,19 @@ namespace Mesen.GUI.Config StatusFormat = StatusFlagFormat.Hexadecimal }; } + + public void AddRecentScript(string scriptFile) + { + string existingItem = RecentScripts.Where((file) => file == scriptFile).FirstOrDefault(); + if(existingItem != null) { + RecentScripts.Remove(existingItem); + } + + RecentScripts.Insert(0, scriptFile); + if(RecentScripts.Count > DebugInfo.MaxRecentScripts) { + RecentScripts.RemoveAt(DebugInfo.MaxRecentScripts); + } + ConfigManager.ApplyChanges(); + } } } diff --git a/GUI.NET/Controls/ctrlSplitContainer.cs b/GUI.NET/Controls/ctrlSplitContainer.cs index e75598e5..1a02bb86 100644 --- a/GUI.NET/Controls/ctrlSplitContainer.cs +++ b/GUI.NET/Controls/ctrlSplitContainer.cs @@ -84,7 +84,7 @@ namespace Mesen.GUI.Controls } else if(this.FixedPanel == FixedPanel.Panel2) { _originalDistance = this.SplitterDistance; _originalMinSize = this.Panel2MinSize; - this.Panel2MinSize = 4; + this.Panel2MinSize = 2; this.SplitterDistance = this.Orientation == Orientation.Horizontal ? this.Height : this.Width; this.PanelCollapsed?.Invoke(this, EventArgs.Empty); diff --git a/GUI.NET/Debugger/DebugWindowManager.cs b/GUI.NET/Debugger/DebugWindowManager.cs index 06bc0f3c..a01291e7 100644 --- a/GUI.NET/Debugger/DebugWindowManager.cs +++ b/GUI.NET/Debugger/DebugWindowManager.cs @@ -25,6 +25,7 @@ namespace Mesen.GUI.Debugger case DebugWindow.MemoryViewer: frm = new frmMemoryViewer(); break; case DebugWindow.Assembler: frm = new frmAssembler(); break; case DebugWindow.Debugger: frm = new frmDebugger(); break; + case DebugWindow.ScriptWindow: frm = new frmScript(); break; } _openedWindows.Add(frm); frm.FormClosed += Debugger_FormClosed; @@ -97,6 +98,7 @@ namespace Mesen.GUI.Debugger MemoryViewer, TraceLogger, Assembler, - Debugger + Debugger, + ScriptWindow, } } diff --git a/GUI.NET/Debugger/FastColoredTextBox/AutocompleteItem.cs b/GUI.NET/Debugger/FastColoredTextBox/AutocompleteItem.cs new file mode 100644 index 00000000..59feec4d --- /dev/null +++ b/GUI.NET/Debugger/FastColoredTextBox/AutocompleteItem.cs @@ -0,0 +1,266 @@ +using System; +using System.Drawing; +using System.Drawing.Printing; + +namespace FastColoredTextBoxNS +{ + /// + /// Item of autocomplete menu + /// + public class AutocompleteItem + { + public string Text; + public int ImageIndex = -1; + public object Tag; + string toolTipTitle; + string toolTipText; + string menuText; + public AutocompleteMenu Parent { get; internal set; } + + + public AutocompleteItem() + { + } + + public AutocompleteItem(string text) + { + Text = text; + } + + public AutocompleteItem(string text, int imageIndex) + : this(text) + { + this.ImageIndex = imageIndex; + } + + public AutocompleteItem(string text, int imageIndex, string menuText) + : this(text, imageIndex) + { + this.menuText = menuText; + } + + public AutocompleteItem(string text, int imageIndex, string menuText, string toolTipTitle, string toolTipText) + : this(text, imageIndex, menuText) + { + this.toolTipTitle = toolTipTitle; + this.toolTipText = toolTipText; + } + + /// + /// Returns text for inserting into Textbox + /// + public virtual string GetTextForReplace() + { + return Text; + } + + /// + /// Compares fragment text with this item + /// + public virtual CompareResult Compare(string fragmentText) + { + if (Text.StartsWith(fragmentText, StringComparison.InvariantCultureIgnoreCase) && + Text != fragmentText) + return CompareResult.VisibleAndSelected; + + return CompareResult.Hidden; + } + + /// + /// Returns text for display into popup menu + /// + public override string ToString() + { + return menuText ?? Text; + } + + /// + /// This method is called after item inserted into text + /// + public virtual void OnSelected(AutocompleteMenu popupMenu, SelectedEventArgs e) + { + ; + } + + /// + /// Title for tooltip. + /// + /// Return null for disable tooltip for this item + public virtual string ToolTipTitle + { + get { return toolTipTitle; } + set { toolTipTitle = value; } + } + + /// + /// Tooltip text. + /// + /// For display tooltip text, ToolTipTitle must be not null + public virtual string ToolTipText + { + get{ return toolTipText; } + set { toolTipText = value; } + } + + /// + /// Menu text. This text is displayed in the drop-down menu. + /// + public virtual string MenuText + { + get { return menuText; } + set { menuText = value; } + } + + /// + /// Fore color of text of item + /// + public virtual Color ForeColor + { + get { return Color.Transparent; } + set { throw new NotImplementedException("Override this property to change color"); } + } + + /// + /// Back color of item + /// + public virtual Color BackColor + { + get { return Color.Transparent; } + set { throw new NotImplementedException("Override this property to change color"); } + } + } + + public enum CompareResult + { + /// + /// Item do not appears + /// + Hidden, + /// + /// Item appears + /// + Visible, + /// + /// Item appears and will selected + /// + VisibleAndSelected + } + + /// + /// Autocomplete item for code snippets + /// + /// Snippet can contain special char ^ for caret position. + public class SnippetAutocompleteItem : AutocompleteItem + { + public SnippetAutocompleteItem(string snippet) + { + Text = snippet.Replace("\r", ""); + ToolTipTitle = "Code snippet:"; + ToolTipText = Text; + } + + public override string ToString() + { + return MenuText ?? Text.Replace("\n", " ").Replace("^", ""); + } + + public override string GetTextForReplace() + { + return Text; + } + + public override void OnSelected(AutocompleteMenu popupMenu, SelectedEventArgs e) + { + e.Tb.BeginUpdate(); + e.Tb.Selection.BeginUpdate(); + //remember places + var p1 = popupMenu.Fragment.Start; + var p2 = e.Tb.Selection.Start; + //do auto indent + if (e.Tb.AutoIndent) + { + for (int iLine = p1.iLine + 1; iLine <= p2.iLine; iLine++) + { + e.Tb.Selection.Start = new Place(0, iLine); + e.Tb.DoAutoIndent(iLine); + } + } + e.Tb.Selection.Start = p1; + //move caret position right and find char ^ + while (e.Tb.Selection.CharBeforeStart != '^') + if (!e.Tb.Selection.GoRightThroughFolded()) + break; + //remove char ^ + e.Tb.Selection.GoLeft(true); + e.Tb.InsertText(""); + // + e.Tb.Selection.EndUpdate(); + e.Tb.EndUpdate(); + } + + /// + /// Compares fragment text with this item + /// + public override CompareResult Compare(string fragmentText) + { + if (Text.StartsWith(fragmentText, StringComparison.InvariantCultureIgnoreCase) && + Text != fragmentText) + return CompareResult.Visible; + + return CompareResult.Hidden; + } + } + + /// + /// This autocomplete item appears after dot + /// + public class MethodAutocompleteItem : AutocompleteItem + { + string firstPart; + string lowercaseText; + + public MethodAutocompleteItem(string text) + : base(text) + { + lowercaseText = Text.ToLower(); + } + + public override CompareResult Compare(string fragmentText) + { + int i = fragmentText.LastIndexOf('.'); + if (i < 0) + return CompareResult.Hidden; + string lastPart = fragmentText.Substring(i + 1); + firstPart = fragmentText.Substring(0, i); + + if(lastPart=="") return CompareResult.Visible; + if(Text.StartsWith(lastPart, StringComparison.InvariantCultureIgnoreCase)) + return CompareResult.VisibleAndSelected; + if(lowercaseText.Contains(lastPart.ToLower())) + return CompareResult.Visible; + + return CompareResult.Hidden; + } + + public override string GetTextForReplace() + { + return firstPart + "." + Text; + } + } + + /// + /// This Item does not check correspondence to current text fragment. + /// SuggestItem is intended for dynamic menus. + /// + public class SuggestItem : AutocompleteItem + { + public SuggestItem(string text, int imageIndex):base(text, imageIndex) + { + } + + public override CompareResult Compare(string fragmentText) + { + return CompareResult.Visible; + } + } +} diff --git a/GUI.NET/Debugger/FastColoredTextBox/AutocompleteMenu.cs b/GUI.NET/Debugger/FastColoredTextBox/AutocompleteMenu.cs new file mode 100644 index 00000000..308c8903 --- /dev/null +++ b/GUI.NET/Debugger/FastColoredTextBox/AutocompleteMenu.cs @@ -0,0 +1,750 @@ +using System; +using System.Collections.Generic; +using System.Windows.Forms; +using System.Drawing; +using System.ComponentModel; +using System.Drawing.Drawing2D; +using System.Text.RegularExpressions; + +namespace FastColoredTextBoxNS +{ + /// + /// Popup menu for autocomplete + /// + [Browsable(false)] + public class AutocompleteMenu : UserControl + { + AutocompleteListView listView; + public Range Fragment { get; internal set; } + + /// + /// Regex pattern for serach fragment around caret + /// + public string SearchPattern { get; set; } + /// + /// Minimum fragment length for popup + /// + public int MinFragmentLength { get; set; } + /// + /// User selects item + /// + public event EventHandler Selecting; + /// + /// It fires after item inserting + /// + public event EventHandler Selected; + /// + /// Occurs when popup menu is opening + /// + public event EventHandler Opening; + /// + /// Allow TAB for select menu item + /// + public bool AllowTabKey { get { return listView.AllowTabKey; } set { listView.AllowTabKey = value; } } + /// + /// Interval of menu appear (ms) + /// + public int AppearInterval { get { return listView.AppearInterval; } set { listView.AppearInterval = value; } } + + /// + /// Back color of selected item + /// + [DefaultValue(typeof(Color), "Orange")] + public Color SelectedColor + { + get { return listView.SelectedColor; } + set { listView.SelectedColor = value; } + } + + /// + /// Border color of hovered item + /// + [DefaultValue(typeof(Color), "Red")] + public Color HoveredColor + { + get { return listView.HoveredColor; } + set { listView.HoveredColor = value; } + } + + public AutocompleteMenu(FastColoredTextBox tb, Form parentForm) + { + // create a new popup and add the list view to it + Visible = false; + BorderStyle = BorderStyle.FixedSingle; + AutoSize = false; + Margin = Padding.Empty; + Padding = Padding.Empty; + BackColor = Color.White; + listView = new AutocompleteListView(this, tb, parentForm); + CalcSize(); + this.Controls.Add(listView); + SearchPattern = @"[\w\.]"; + MinFragmentLength = 2; + } + + public new Font Font + { + get { return listView.Font; } + set { listView.Font = value; } + } + + internal void OnOpening(CancelEventArgs args) + { + if (Opening != null) + Opening(this, args); + } + + public void Close() + { + listView.toolTip.Hide(listView); + this.Hide(); + } + + internal void CalcSize() + { + Size = new System.Drawing.Size(listView.Size.Width + 2, listView.Size.Height + 2); + } + + public virtual void OnSelecting() + { + listView.OnSelecting(); + } + + public void SelectNext(int shift) + { + listView.SelectNext(shift); + } + + internal void OnSelecting(SelectingEventArgs args) + { + if (Selecting != null) + Selecting(this, args); + } + + public void OnSelected(SelectedEventArgs args) + { + if (Selected != null) + Selected(this, args); + } + + public AutocompleteListView Items + { + get { return listView; } + } + + /// + /// Shows popup menu immediately + /// + /// If True - MinFragmentLength will be ignored + public void Show(bool forced) + { + Items.DoAutocomplete(forced); + } + + /// + /// Minimal size of menu + /// + public new Size MinimumSize + { + get { return Items.MinimumSize; } + set { Items.MinimumSize = value; } + } + + /// + /// Image list of menu + /// + public ImageList ImageList + { + get { return Items.ImageList; } + set { Items.ImageList = value; } + } + + /// + /// Tooltip duration (ms) + /// + public int ToolTipDuration + { + get { return Items.ToolTipDuration; } + set { Items.ToolTipDuration = value; } + } + + /// + /// Tooltip + /// + public ToolTip ToolTip + { + get { return Items.toolTip; } + set { Items.toolTip = value; } + } + } + + [System.ComponentModel.ToolboxItem(false)] + public class AutocompleteListView : UserControl + { + public event EventHandler FocussedItemIndexChanged; + + internal List visibleItems; + IEnumerable sourceItems = new List(); + int focussedItemIndex = 0; + int hoveredItemIndex = -1; + + private int ItemHeight + { + get { return Font.Height + 2; } + } + + private AutocompleteMenu Menu { get; set; } + int oldItemCount = 0; + FastColoredTextBox tb; + internal ToolTip toolTip = new ToolTip(); + System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer(); + + internal bool AllowTabKey { get; set; } + public ImageList ImageList { get; set; } + internal int AppearInterval { get { return timer.Interval; } set { timer.Interval = value; } } + internal int ToolTipDuration { get; set; } + + public override Size GetPreferredSize(Size proposedSize) + { + return Size; + } + + public Color SelectedColor { get; set; } + public Color HoveredColor { get; set; } + public int FocussedItemIndex + { + get { return focussedItemIndex; } + set + { + if (focussedItemIndex != value) + { + focussedItemIndex = value; + if (FocussedItemIndexChanged != null) + FocussedItemIndexChanged(this, EventArgs.Empty); + } + } + } + + public AutocompleteItem FocussedItem + { + get + { + if (FocussedItemIndex >= 0 && focussedItemIndex < visibleItems.Count) + return visibleItems[focussedItemIndex]; + return null; + } + set + { + FocussedItemIndex = visibleItems.IndexOf(value); + } + } + + public Form AutocompleteParent { get; set; } + + internal AutocompleteListView(AutocompleteMenu menu, FastColoredTextBox tb, Form parent) + { + Menu = menu; + AutocompleteParent = parent; + SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true); + base.Font = new Font(FontFamily.GenericSansSerif, 9); + visibleItems = new List(); + VerticalScroll.SmallChange = ItemHeight; + MaximumSize = new Size(Size.Width, 180); + toolTip.ShowAlways = false; + AppearInterval = 200; + timer.Tick += new EventHandler(timer_Tick); + SelectedColor = Color.Orange; + HoveredColor = Color.Red; + ToolTipDuration = 30000; + + this.tb = tb; + + tb.KeyDown += new KeyEventHandler(tb_KeyDown); + tb.SelectionChanged += new EventHandler(tb_SelectionChanged); + tb.KeyPressed += new KeyPressEventHandler(tb_KeyPressed); + + Form form = tb.FindForm(); + if (form != null) + { + form.LocationChanged += (o, e) => Menu.Close(); + form.ResizeBegin += (o, e) => Menu.Close(); + form.FormClosing += (o, e) => Menu.Close(); + form.LostFocus += (o, e) => Menu.Close(); + } + + tb.LostFocus += (o, e) => + { + if (!Menu.Focused) Menu.Close(); + }; + + tb.Scroll += (o, e) => Menu.Close(); + + this.VisibleChanged += (o, e) => + { + if (this.Visible) + DoSelectedVisible(); + }; + } + + void tb_KeyPressed(object sender, KeyPressEventArgs e) + { + bool backspaceORdel = e.KeyChar == '\b' || e.KeyChar == 0xff; + + /* + if (backspaceORdel) + prevSelection = tb.Selection.Start;*/ + + if (Menu.Visible && !backspaceORdel) + DoAutocomplete(false); + else + ResetTimer(timer); + } + + void timer_Tick(object sender, EventArgs e) + { + timer.Stop(); + DoAutocomplete(false); + } + + void ResetTimer(System.Windows.Forms.Timer timer) + { + timer.Stop(); + timer.Start(); + } + + internal void DoAutocomplete() + { + DoAutocomplete(false); + } + + internal void DoAutocomplete(bool forced) + { + if (!Menu.Enabled) + { + Menu.Close(); + return; + } + + visibleItems.Clear(); + FocussedItemIndex = 0; + VerticalScroll.Value = 0; + //some magic for update scrolls + AutoScrollMinSize += new Size(1, 0); + AutoScrollMinSize -= new Size(1, 0); + //get fragment around caret + Range fragment = tb.Selection.GetFragment(Menu.SearchPattern); + string text = fragment.Text; + //calc screen point for popup menu + Point point = tb.PlaceToPoint(fragment.End); + Point offset = tb.PointToScreen(point); + point = AutocompleteParent.PointToClient(offset); + point.Offset(2, tb.CharHeight); + // + if (forced || (text.Length >= Menu.MinFragmentLength + && tb.Selection.IsEmpty /*pops up only if selected range is empty*/ + && (tb.Selection.Start > fragment.Start || text.Length == 0/*pops up only if caret is after first letter*/))) + { + Menu.Fragment = fragment; + bool foundSelected = false; + //build popup menu + foreach (var item in sourceItems) + { + item.Parent = Menu; + CompareResult res = item.Compare(text); + if(res != CompareResult.Hidden) + visibleItems.Add(item); + if (res == CompareResult.VisibleAndSelected && !foundSelected) + { + foundSelected = true; + FocussedItemIndex = visibleItems.Count - 1; + } + } + + if (foundSelected) + { + AdjustScroll(); + DoSelectedVisible(); + } + } + + //show popup menu + if (Count > 0) + { + if (!Menu.Visible) + { + CancelEventArgs args = new CancelEventArgs(); + Menu.OnOpening(args); + if (!args.Cancel) + { + Menu.Location = point; + Menu.Parent = AutocompleteParent; + Menu.Show(); + Menu.BringToFront(); + } + } + else + Invalidate(); + } + else + Menu.Close(); + } + + void tb_SelectionChanged(object sender, EventArgs e) + { + /* + FastColoredTextBox tb = sender as FastColoredTextBox; + + if (Math.Abs(prevSelection.iChar - tb.Selection.Start.iChar) > 1 || + prevSelection.iLine != tb.Selection.Start.iLine) + Menu.Close(); + prevSelection = tb.Selection.Start;*/ + if (Menu.Visible) + { + bool needClose = false; + + if (!tb.Selection.IsEmpty) + needClose = true; + else + if (!Menu.Fragment.Contains(tb.Selection.Start)) + { + if (tb.Selection.Start.iLine == Menu.Fragment.End.iLine && tb.Selection.Start.iChar == Menu.Fragment.End.iChar + 1) + { + //user press key at end of fragment + char c = tb.Selection.CharBeforeStart; + if (!Regex.IsMatch(c.ToString(), Menu.SearchPattern))//check char + needClose = true; + } + else + needClose = true; + } + + if (needClose) + Menu.Close(); + } + + } + + void tb_KeyDown(object sender, KeyEventArgs e) + { + var tb = sender as FastColoredTextBox; + + if (Menu.Visible) + if (ProcessKey(e.KeyCode, e.Modifiers)) + e.Handled = true; + + if (!Menu.Visible) + { + if (tb.HotkeysMapping.ContainsKey(e.KeyData) && tb.HotkeysMapping[e.KeyData] == FCTBAction.AutocompleteMenu) + { + DoAutocomplete(); + e.Handled = true; + } + else + { + if (e.KeyCode == Keys.Escape && timer.Enabled) + timer.Stop(); + } + } + } + + void AdjustScroll() + { + Range fragment = tb.Selection.GetFragment(Menu.SearchPattern); + string text = fragment.Text; + //calc screen point for popup menu + Point point = tb.PlaceToPoint(fragment.End); + Point offset = tb.PointToScreen(point); + point = AutocompleteParent.PointToClient(offset); + point.Offset(2, tb.CharHeight); + if(Menu.Width + point.X > AutocompleteParent.ClientSize.Width - 10) { + point.X -= Menu.Width; + } + if(Menu.Height + point.Y > AutocompleteParent.ClientSize.Height - 10) { + point.Y -= Menu.Height + 15; + } + Menu.Location = point; + + if (oldItemCount == visibleItems.Count) + return; + + int needHeight = ItemHeight * visibleItems.Count + 1; + Height = Math.Min(needHeight, MaximumSize.Height); + Menu.CalcSize(); + + AutoScrollMinSize = new Size(0, needHeight); + oldItemCount = visibleItems.Count; + } + + protected override void OnPaint(PaintEventArgs e) + { + AdjustScroll(); + + var itemHeight = ItemHeight; + int startI = VerticalScroll.Value / itemHeight - 1; + int finishI = (VerticalScroll.Value + ClientSize.Height) / itemHeight + 1; + startI = Math.Max(startI, 0); + finishI = Math.Min(finishI, visibleItems.Count); + int y = 0; + int leftPadding = 18; + for (int i = startI; i < finishI; i++) + { + y = i * itemHeight - VerticalScroll.Value; + + var item = visibleItems[i]; + + if(item.BackColor != Color.Transparent) + using (var brush = new SolidBrush(item.BackColor)) + e.Graphics.FillRectangle(brush, 1, y, ClientSize.Width, itemHeight); + + if (ImageList != null && visibleItems[i].ImageIndex >= 0) + e.Graphics.DrawImage(ImageList.Images[item.ImageIndex], 1, y); + + if (i == FocussedItemIndex) + using (var selectedBrush = new LinearGradientBrush(new Point(0, y - 3), new Point(0, y + itemHeight), Color.Transparent, SelectedColor)) + using (var pen = new Pen(SelectedColor)) + { + e.Graphics.FillRectangle(selectedBrush, leftPadding, y, ClientSize.Width - 1 - leftPadding, itemHeight); + e.Graphics.DrawRectangle(pen, leftPadding, y, ClientSize.Width - 1 - leftPadding, itemHeight); + } + + if (i == hoveredItemIndex) + using(var pen = new Pen(HoveredColor)) + e.Graphics.DrawRectangle(pen, leftPadding, y, ClientSize.Width - leftPadding, itemHeight); + + using (var brush = new SolidBrush(item.ForeColor != Color.Transparent ? item.ForeColor : ForeColor)) + e.Graphics.DrawString(item.ToString(), Font, brush, leftPadding, y); + } + } + + protected override void OnScroll(ScrollEventArgs se) + { + base.OnScroll(se); + Invalidate(); + } + + protected override void OnMouseClick(MouseEventArgs e) + { + base.OnMouseClick(e); + + if (e.Button == System.Windows.Forms.MouseButtons.Left) + { + FocussedItemIndex = PointToItemIndex(e.Location); + DoSelectedVisible(); + Invalidate(); + } + } + + protected override void OnMouseDoubleClick(MouseEventArgs e) + { + base.OnMouseDoubleClick(e); + FocussedItemIndex = PointToItemIndex(e.Location); + Invalidate(); + OnSelecting(); + } + + internal virtual void OnSelecting() + { + if (FocussedItemIndex < 0 || FocussedItemIndex >= visibleItems.Count) + return; + tb.TextSource.Manager.BeginAutoUndoCommands(); + try + { + AutocompleteItem item = FocussedItem; + SelectingEventArgs args = new SelectingEventArgs() + { + Item = item, + SelectedIndex = FocussedItemIndex + }; + + Menu.OnSelecting(args); + + if (args.Cancel) + { + FocussedItemIndex = args.SelectedIndex; + Invalidate(); + return; + } + + if (!args.Handled) + { + var fragment = Menu.Fragment; + DoAutocomplete(item, fragment); + } + + Menu.Close(); + // + SelectedEventArgs args2 = new SelectedEventArgs() + { + Item = item, + Tb = Menu.Fragment.tb + }; + item.OnSelected(Menu, args2); + Menu.OnSelected(args2); + } + finally + { + tb.TextSource.Manager.EndAutoUndoCommands(); + } + } + + private void DoAutocomplete(AutocompleteItem item, Range fragment) + { + string newText = item.GetTextForReplace(); + + //replace text of fragment + var tb = fragment.tb; + + tb.BeginAutoUndo(); + tb.TextSource.Manager.ExecuteCommand(new SelectCommand(tb.TextSource)); + if (tb.Selection.ColumnSelectionMode) + { + var start = tb.Selection.Start; + var end = tb.Selection.End; + start.iChar = fragment.Start.iChar; + end.iChar = fragment.End.iChar; + tb.Selection.Start = start; + tb.Selection.End = end; + } + else + { + tb.Selection.Start = fragment.Start; + tb.Selection.End = fragment.End; + } + tb.InsertText(newText); + tb.TextSource.Manager.ExecuteCommand(new SelectCommand(tb.TextSource)); + tb.EndAutoUndo(); + tb.Focus(); + } + + int PointToItemIndex(Point p) + { + return (p.Y + VerticalScroll.Value) / ItemHeight; + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + ProcessKey(keyData, Keys.None); + + return base.ProcessCmdKey(ref msg, keyData); + } + + private bool ProcessKey(Keys keyData, Keys keyModifiers) + { + if (keyModifiers == Keys.None) + switch (keyData) + { + case Keys.Down: + SelectNext(+1); + return true; + case Keys.PageDown: + SelectNext(+10); + return true; + case Keys.Up: + SelectNext(-1); + return true; + case Keys.PageUp: + SelectNext(-10); + return true; + case Keys.Enter: + OnSelecting(); + return true; + case Keys.Tab: + if (!AllowTabKey) + break; + OnSelecting(); + return true; + case Keys.Escape: + Menu.Close(); + return true; + } + + return false; + } + + public void SelectNext(int shift) + { + FocussedItemIndex = Math.Max(0, Math.Min(FocussedItemIndex + shift, visibleItems.Count - 1)); + DoSelectedVisible(); + // + Invalidate(); + } + + private void DoSelectedVisible() + { + if (FocussedItem != null) + SetToolTip(FocussedItem); + + var y = FocussedItemIndex * ItemHeight - VerticalScroll.Value; + if (y < 0) + VerticalScroll.Value = FocussedItemIndex * ItemHeight; + if (y > ClientSize.Height - ItemHeight) + VerticalScroll.Value = Math.Min(VerticalScroll.Maximum, FocussedItemIndex * ItemHeight - ClientSize.Height + ItemHeight); + //some magic for update scrolls + AutoScrollMinSize -= new Size(1, 0); + AutoScrollMinSize += new Size(1, 0); + } + + private void SetToolTip(AutocompleteItem autocompleteItem) + { + var title = autocompleteItem.ToolTipTitle; + var text = autocompleteItem.ToolTipText; + + Control window = tb; + if (string.IsNullOrEmpty(title)) + { + toolTip.Hide(window); + return; + } + + var location = new Point(Right + 3 + Menu.Left, Menu.Top); + if (string.IsNullOrEmpty(text)) + { + toolTip.ToolTipTitle = null; + if (ToolTipDuration == 0) + toolTip.Show(title, window, location); + else + toolTip.Show(title, window, location, ToolTipDuration); + } + else + { + toolTip.ToolTipTitle = title; + if (ToolTipDuration == 0) + toolTip.Show(text, window, location); + else + toolTip.Show(text, window, location, ToolTipDuration); + } + } + + public int Count + { + get { return visibleItems.Count; } + } + + public void SetAutocompleteItems(ICollection items) + { + List list = new List(items.Count); + foreach (var item in items) + list.Add(new AutocompleteItem(item)); + SetAutocompleteItems(list); + } + + public void SetAutocompleteItems(IEnumerable items) + { + sourceItems = items; + } + } + + public class SelectingEventArgs : EventArgs + { + public AutocompleteItem Item { get; internal set; } + public bool Cancel {get;set;} + public int SelectedIndex{get;set;} + public bool Handled { get; set; } + } + + public class SelectedEventArgs : EventArgs + { + public AutocompleteItem Item { get; internal set; } + public FastColoredTextBox Tb { get; set; } + } +} diff --git a/GUI.NET/Debugger/FastColoredTextBox/Bookmarks.cs b/GUI.NET/Debugger/FastColoredTextBox/Bookmarks.cs new file mode 100644 index 00000000..ad220d98 --- /dev/null +++ b/GUI.NET/Debugger/FastColoredTextBox/Bookmarks.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Text; + +namespace FastColoredTextBoxNS +{ + /// + /// Base class for bookmark collection + /// + public abstract class BaseBookmarks : ICollection, IDisposable + { + #region ICollection + public abstract void Add(Bookmark item); + public abstract void Clear(); + public abstract bool Contains(Bookmark item); + public abstract void CopyTo(Bookmark[] array, int arrayIndex); + public abstract int Count { get; } + public abstract bool IsReadOnly { get; } + public abstract bool Remove(Bookmark item); + public abstract IEnumerator GetEnumerator(); + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region IDisposable + public abstract void Dispose(); + #endregion + + #region Additional properties + + public abstract void Add(int lineIndex, string bookmarkName); + public abstract void Add(int lineIndex); + public abstract bool Contains(int lineIndex); + public abstract bool Remove(int lineIndex); + public abstract Bookmark GetBookmark(int i); + + #endregion + } + + /// + /// Collection of bookmarks + /// + public class Bookmarks : BaseBookmarks + { + protected FastColoredTextBox tb; + protected List items = new List(); + protected int counter; + + public Bookmarks(FastColoredTextBox tb) + { + this.tb = tb; + tb.LineInserted += tb_LineInserted; + tb.LineRemoved += tb_LineRemoved; + } + + protected virtual void tb_LineRemoved(object sender, LineRemovedEventArgs e) + { + for(int i=0; i= e.Index) + { + if (items[i].LineIndex >= e.Index + e.Count) + { + items[i].LineIndex = items[i].LineIndex - e.Count; + continue; + } + + var was = e.Index <= 0; + foreach (var b in items) + if (b.LineIndex == e.Index - 1) + was = true; + + if(was) + { + items.RemoveAt(i); + i--; + }else + items[i].LineIndex = e.Index - 1; + + //if (items[i].LineIndex == e.Index + e.Count - 1) + //{ + // items[i].LineIndex = items[i].LineIndex - e.Count; + // continue; + //} + // + //items.RemoveAt(i); + //i--; + } + } + + protected virtual void tb_LineInserted(object sender, LineInsertedEventArgs e) + { + for (int i = 0; i < Count; i++) + if (items[i].LineIndex >= e.Index) + { + items[i].LineIndex = items[i].LineIndex + e.Count; + }else + if (items[i].LineIndex == e.Index - 1 && e.Count == 1) + { + if(tb[e.Index - 1].StartSpacesCount == tb[e.Index - 1].Count) + items[i].LineIndex = items[i].LineIndex + e.Count; + } + } + + public override void Dispose() + { + tb.LineInserted -= tb_LineInserted; + tb.LineRemoved -= tb_LineRemoved; + } + + public override IEnumerator GetEnumerator() + { + foreach (var item in items) + yield return item; + } + + public override void Add(int lineIndex, string bookmarkName) + { + Add(new Bookmark(tb, bookmarkName ?? "Bookmark " + counter, lineIndex)); + } + + public override void Add(int lineIndex) + { + Add(new Bookmark(tb, "Bookmark " + counter, lineIndex)); + } + + public override void Clear() + { + items.Clear(); + counter = 0; + } + + public override void Add(Bookmark bookmark) + { + foreach (var bm in items) + if (bm.LineIndex == bookmark.LineIndex) + return; + + items.Add(bookmark); + counter++; + tb.Invalidate(); + } + + public override bool Contains(Bookmark item) + { + return items.Contains(item); + } + + public override bool Contains(int lineIndex) + { + foreach (var item in items) + if (item.LineIndex == lineIndex) + return true; + return false; + } + + public override void CopyTo(Bookmark[] array, int arrayIndex) + { + items.CopyTo(array, arrayIndex); + } + + public override int Count + { + get { return items.Count; } + } + + public override bool IsReadOnly + { + get { return false; } + } + + public override bool Remove(Bookmark item) + { + tb.Invalidate(); + return items.Remove(item); + } + + /// + /// Removes bookmark by line index + /// + public override bool Remove(int lineIndex) + { + bool was = false; + for (int i = 0; i < Count; i++) + if (items[i].LineIndex == lineIndex) + { + items.RemoveAt(i); + i--; + was = true; + } + tb.Invalidate(); + + return was; + } + + /// + /// Returns Bookmark by index. + /// + public override Bookmark GetBookmark(int i) + { + return items[i]; + } + } + + /// + /// Bookmark of FastColoredTextbox + /// + public class Bookmark + { + public FastColoredTextBox TB { get; private set; } + /// + /// Name of bookmark + /// + public string Name { get; set; } + /// + /// Line index + /// + public int LineIndex {get; set; } + /// + /// Color of bookmark sign + /// + public Color Color { get; set; } + + /// + /// Scroll textbox to the bookmark + /// + public virtual void DoVisible() + { + TB.Selection.Start = new Place(0, LineIndex); + TB.DoRangeVisible(TB.Selection, true); + TB.Invalidate(); + } + + public Bookmark(FastColoredTextBox tb, string name, int lineIndex) + { + this.TB = tb; + this.Name = name; + this.LineIndex = lineIndex; + Color = tb.BookmarkColor; + } + + public virtual void Paint(Graphics gr, Rectangle lineRect) + { + var size = TB.CharHeight - 1; + using (var brush = new LinearGradientBrush(new Rectangle(0, lineRect.Top, size, size), Color.White, Color, 45)) + gr.FillEllipse(brush, 0, lineRect.Top, size, size); + using (var pen = new Pen(Color)) + gr.DrawEllipse(pen, 0, lineRect.Top, size, size); + } + } +} diff --git a/GUI.NET/Debugger/FastColoredTextBox/Char.cs b/GUI.NET/Debugger/FastColoredTextBox/Char.cs new file mode 100644 index 00000000..9f848c97 --- /dev/null +++ b/GUI.NET/Debugger/FastColoredTextBox/Char.cs @@ -0,0 +1,26 @@ +using System; + +namespace FastColoredTextBoxNS +{ + /// + /// Char and style + /// + public struct Char + { + /// + /// Unicode character + /// + public char c; + /// + /// Style bit mask + /// + /// Bit 1 in position n means that this char will rendering by FastColoredTextBox.Styles[n] + public StyleIndex style; + + public Char(char c) + { + this.c = c; + style = StyleIndex.None; + } + } +} diff --git a/GUI.NET/Debugger/FastColoredTextBox/CommandManager.cs b/GUI.NET/Debugger/FastColoredTextBox/CommandManager.cs new file mode 100644 index 00000000..7e61a613 --- /dev/null +++ b/GUI.NET/Debugger/FastColoredTextBox/CommandManager.cs @@ -0,0 +1,239 @@ +using System.Collections.Generic; +using System; + +namespace FastColoredTextBoxNS +{ + public class CommandManager + { + readonly int maxHistoryLength = 200; + LimitedStack history; + Stack redoStack = new Stack(); + public TextSource TextSource{ get; private set; } + public bool UndoRedoStackIsEnabled { get; set; } + + public CommandManager(TextSource ts) + { + history = new LimitedStack(maxHistoryLength); + TextSource = ts; + UndoRedoStackIsEnabled = true; + } + + public virtual void ExecuteCommand(Command cmd) + { + if (disabledCommands > 0) + return; + + //multirange ? + if (cmd.ts.CurrentTB.Selection.ColumnSelectionMode) + if (cmd is UndoableCommand) + //make wrapper + cmd = new MultiRangeCommand((UndoableCommand)cmd); + + + if (cmd is UndoableCommand) + { + //if range is ColumnRange, then create wrapper + (cmd as UndoableCommand).autoUndo = autoUndoCommands > 0; + history.Push(cmd as UndoableCommand); + } + + try + { + cmd.Execute(); + } + catch (ArgumentOutOfRangeException) + { + //OnTextChanging cancels enter of the text + if (cmd is UndoableCommand) + history.Pop(); + } + // + if (!UndoRedoStackIsEnabled) + ClearHistory(); + // + redoStack.Clear(); + // + TextSource.CurrentTB.OnUndoRedoStateChanged(); + } + + public void Undo() + { + if (history.Count > 0) + { + var cmd = history.Pop(); + // + BeginDisableCommands();//prevent text changing into handlers + try + { + cmd.Undo(); + } + finally + { + EndDisableCommands(); + } + // + redoStack.Push(cmd); + } + + //undo next autoUndo command + if (history.Count > 0) + { + if (history.Peek().autoUndo) + Undo(); + } + + TextSource.CurrentTB.OnUndoRedoStateChanged(); + } + + protected int disabledCommands = 0; + + private void EndDisableCommands() + { + disabledCommands--; + } + + private void BeginDisableCommands() + { + disabledCommands++; + } + + int autoUndoCommands = 0; + + public void EndAutoUndoCommands() + { + autoUndoCommands--; + if (autoUndoCommands == 0) + if (history.Count > 0) + history.Peek().autoUndo = false; + } + + public void BeginAutoUndoCommands() + { + autoUndoCommands++; + } + + internal void ClearHistory() + { + history.Clear(); + redoStack.Clear(); + TextSource.CurrentTB.OnUndoRedoStateChanged(); + } + + internal void Redo() + { + if (redoStack.Count == 0) + return; + UndoableCommand cmd; + BeginDisableCommands();//prevent text changing into handlers + try + { + cmd = redoStack.Pop(); + if (TextSource.CurrentTB.Selection.ColumnSelectionMode) + TextSource.CurrentTB.Selection.ColumnSelectionMode = false; + TextSource.CurrentTB.Selection.Start = cmd.sel.Start; + TextSource.CurrentTB.Selection.End = cmd.sel.End; + cmd.Execute(); + history.Push(cmd); + } + finally + { + EndDisableCommands(); + } + + //redo command after autoUndoable command + if (cmd.autoUndo) + Redo(); + + TextSource.CurrentTB.OnUndoRedoStateChanged(); + } + + public bool UndoEnabled + { + get + { + return history.Count > 0; + } + } + + public bool RedoEnabled + { + get + { + return redoStack.Count > 0; + } + } + } + + public abstract class Command + { + public TextSource ts; + public abstract void Execute(); + } + + internal class RangeInfo + { + public Place Start { get; set; } + public Place End { get; set; } + + public RangeInfo(Range r) + { + Start = r.Start; + End = r.End; + } + + internal int FromX + { + get + { + if (End.iLine < Start.iLine) return End.iChar; + if (End.iLine > Start.iLine) return Start.iChar; + return Math.Min(End.iChar, Start.iChar); + } + } + } + + public abstract class UndoableCommand : Command + { + internal RangeInfo sel; + internal RangeInfo lastSel; + internal bool autoUndo; + + public UndoableCommand(TextSource ts) + { + this.ts = ts; + sel = new RangeInfo(ts.CurrentTB.Selection); + } + + public virtual void Undo() + { + OnTextChanged(true); + } + + public override void Execute() + { + lastSel = new RangeInfo(ts.CurrentTB.Selection); + OnTextChanged(false); + } + + protected virtual void OnTextChanged(bool invert) + { + bool b = sel.Start.iLine < lastSel.Start.iLine; + if (invert) + { + if (b) + ts.OnTextChanged(sel.Start.iLine, sel.Start.iLine); + else + ts.OnTextChanged(sel.Start.iLine, lastSel.Start.iLine); + } + else + { + if (b) + ts.OnTextChanged(sel.Start.iLine, lastSel.Start.iLine); + else + ts.OnTextChanged(lastSel.Start.iLine, lastSel.Start.iLine); + } + } + + public abstract UndoableCommand Clone(); + } +} \ No newline at end of file diff --git a/GUI.NET/Debugger/FastColoredTextBox/Commands.cs b/GUI.NET/Debugger/FastColoredTextBox/Commands.cs new file mode 100644 index 00000000..c83ac962 --- /dev/null +++ b/GUI.NET/Debugger/FastColoredTextBox/Commands.cs @@ -0,0 +1,809 @@ +using System; +using System.Collections.Generic; + +namespace FastColoredTextBoxNS +{ + /// + /// Insert single char + /// + /// This operation includes also insertion of new line and removing char by backspace + public class InsertCharCommand : UndoableCommand + { + public char c; + char deletedChar = '\x0'; + + /// + /// Constructor + /// + /// Underlaying textbox + /// Inserting char + public InsertCharCommand(TextSource ts, char c): base(ts) + { + this.c = c; + } + + /// + /// Undo operation + /// + public override void Undo() + { + ts.OnTextChanging(); + switch (c) + { + case '\n': MergeLines(sel.Start.iLine, ts); break; + case '\r': break; + case '\b': + ts.CurrentTB.Selection.Start = lastSel.Start; + char cc = '\x0'; + if (deletedChar != '\x0') + { + ts.CurrentTB.ExpandBlock(ts.CurrentTB.Selection.Start.iLine); + InsertChar(deletedChar, ref cc, ts); + } + break; + case '\t': + ts.CurrentTB.ExpandBlock(sel.Start.iLine); + for (int i = sel.FromX; i < lastSel.FromX; i++) + ts[sel.Start.iLine].RemoveAt(sel.Start.iChar); + ts.CurrentTB.Selection.Start = sel.Start; + break; + default: + ts.CurrentTB.ExpandBlock(sel.Start.iLine); + ts[sel.Start.iLine].RemoveAt(sel.Start.iChar); + ts.CurrentTB.Selection.Start = sel.Start; + break; + } + + ts.NeedRecalc(new TextSource.TextChangedEventArgs(sel.Start.iLine, sel.Start.iLine)); + + base.Undo(); + } + + /// + /// Execute operation + /// + public override void Execute() + { + ts.CurrentTB.ExpandBlock(ts.CurrentTB.Selection.Start.iLine); + string s = c.ToString(); + ts.OnTextChanging(ref s); + if (s.Length == 1) + c = s[0]; + + if (String.IsNullOrEmpty(s)) + throw new ArgumentOutOfRangeException(); + + + if (ts.Count == 0) + InsertLine(ts); + InsertChar(c, ref deletedChar, ts); + + ts.NeedRecalc(new TextSource.TextChangedEventArgs(ts.CurrentTB.Selection.Start.iLine, ts.CurrentTB.Selection.Start.iLine)); + base.Execute(); + } + + internal static void InsertChar(char c, ref char deletedChar, TextSource ts) + { + var tb = ts.CurrentTB; + + switch (c) + { + case '\n': + if (!ts.CurrentTB.AllowInsertRemoveLines) + throw new ArgumentOutOfRangeException("Cant insert this char in ColumnRange mode"); + if (ts.Count == 0) + InsertLine(ts); + InsertLine(ts); + break; + case '\r': break; + case '\b'://backspace + if (tb.Selection.Start.iChar == 0 && tb.Selection.Start.iLine == 0) + return; + if (tb.Selection.Start.iChar == 0) + { + if (!ts.CurrentTB.AllowInsertRemoveLines) + throw new ArgumentOutOfRangeException("Cant insert this char in ColumnRange mode"); + if (tb.LineInfos[tb.Selection.Start.iLine - 1].VisibleState != VisibleState.Visible) + tb.ExpandBlock(tb.Selection.Start.iLine - 1); + deletedChar = '\n'; + MergeLines(tb.Selection.Start.iLine - 1, ts); + } + else + { + deletedChar = ts[tb.Selection.Start.iLine][tb.Selection.Start.iChar - 1].c; + ts[tb.Selection.Start.iLine].RemoveAt(tb.Selection.Start.iChar - 1); + tb.Selection.Start = new Place(tb.Selection.Start.iChar - 1, tb.Selection.Start.iLine); + } + break; + case '\t': + int spaceCountNextTabStop = tb.TabLength - (tb.Selection.Start.iChar % tb.TabLength); + if (spaceCountNextTabStop == 0) + spaceCountNextTabStop = tb.TabLength; + + for (int i = 0; i < spaceCountNextTabStop; i++) + ts[tb.Selection.Start.iLine].Insert(tb.Selection.Start.iChar, new Char(' ')); + + tb.Selection.Start = new Place(tb.Selection.Start.iChar + spaceCountNextTabStop, tb.Selection.Start.iLine); + break; + default: + ts[tb.Selection.Start.iLine].Insert(tb.Selection.Start.iChar, new Char(c)); + tb.Selection.Start = new Place(tb.Selection.Start.iChar + 1, tb.Selection.Start.iLine); + break; + } + } + + internal static void InsertLine(TextSource ts) + { + var tb = ts.CurrentTB; + + if (!tb.Multiline && tb.LinesCount > 0) + return; + + if (ts.Count == 0) + ts.InsertLine(0, ts.CreateLine()); + else + BreakLines(tb.Selection.Start.iLine, tb.Selection.Start.iChar, ts); + + tb.Selection.Start = new Place(0, tb.Selection.Start.iLine + 1); + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + + /// + /// Merge lines i and i+1 + /// + internal static void MergeLines(int i, TextSource ts) + { + var tb = ts.CurrentTB; + + if (i + 1 >= ts.Count) + return; + tb.ExpandBlock(i); + tb.ExpandBlock(i + 1); + int pos = ts[i].Count; + // + /* + if(ts[i].Count == 0) + ts.RemoveLine(i); + else*/ + if (ts[i + 1].Count == 0) + ts.RemoveLine(i + 1); + else + { + ts[i].AddRange(ts[i + 1]); + ts.RemoveLine(i + 1); + } + tb.Selection.Start = new Place(pos, i); + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + + internal static void BreakLines(int iLine, int pos, TextSource ts) + { + Line newLine = ts.CreateLine(); + for(int i=pos;i + /// Insert text + /// + public class InsertTextCommand : UndoableCommand + { + public string InsertedText; + + /// + /// Constructor + /// + /// Underlaying textbox + /// Text for inserting + public InsertTextCommand(TextSource ts, string insertedText): base(ts) + { + this.InsertedText = insertedText; + } + + /// + /// Undo operation + /// + public override void Undo() + { + ts.CurrentTB.Selection.Start = sel.Start; + ts.CurrentTB.Selection.End = lastSel.Start; + ts.OnTextChanging(); + ClearSelectedCommand.ClearSelected(ts); + base.Undo(); + } + + /// + /// Execute operation + /// + public override void Execute() + { + ts.OnTextChanging(ref InsertedText); + InsertText(InsertedText, ts); + base.Execute(); + } + + internal static void InsertText(string insertedText, TextSource ts) + { + var tb = ts.CurrentTB; + try + { + tb.Selection.BeginUpdate(); + char cc = '\x0'; + + if (ts.Count == 0) + { + InsertCharCommand.InsertLine(ts); + tb.Selection.Start = Place.Empty; + } + tb.ExpandBlock(tb.Selection.Start.iLine); + var len = insertedText.Length; + for (int i = 0; i < len; i++) + { + var c = insertedText[i]; + if(c == '\r' && (i >= len - 1 || insertedText[i + 1] != '\n')) + InsertCharCommand.InsertChar('\n', ref cc, ts); + else + InsertCharCommand.InsertChar(c, ref cc, ts); + } + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + finally { + tb.Selection.EndUpdate(); + } + } + + public override UndoableCommand Clone() + { + return new InsertTextCommand(ts, InsertedText); + } + } + + /// + /// Insert text into given ranges + /// + public class ReplaceTextCommand : UndoableCommand + { + string insertedText; + List ranges; + List prevText = new List(); + + /// + /// Constructor + /// + /// Underlaying textbox + /// List of ranges for replace + /// Text for inserting + public ReplaceTextCommand(TextSource ts, List ranges, string insertedText) + : base(ts) + { + //sort ranges by place + ranges.Sort((r1, r2)=> + { + if (r1.Start.iLine == r2.Start.iLine) + return r1.Start.iChar.CompareTo(r2.Start.iChar); + return r1.Start.iLine.CompareTo(r2.Start.iLine); + }); + // + this.ranges = ranges; + this.insertedText = insertedText; + lastSel = sel = new RangeInfo(ts.CurrentTB.Selection); + } + + /// + /// Undo operation + /// + public override void Undo() + { + var tb = ts.CurrentTB; + + ts.OnTextChanging(); + tb.BeginUpdate(); + + tb.Selection.BeginUpdate(); + for (int i = 0; i 0) + ts.OnTextChanged(ranges[0].Start.iLine, ranges[ranges.Count - 1].End.iLine); + + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + + /// + /// Execute operation + /// + public override void Execute() + { + var tb = ts.CurrentTB; + prevText.Clear(); + + ts.OnTextChanging(ref insertedText); + + tb.Selection.BeginUpdate(); + tb.BeginUpdate(); + for (int i = ranges.Count - 1; i >= 0; i--) + { + tb.Selection.Start = ranges[i].Start; + tb.Selection.End = ranges[i].End; + prevText.Add(tb.Selection.Text); + ClearSelected(ts); + if (insertedText != "") + InsertTextCommand.InsertText(insertedText, ts); + } + if(ranges.Count > 0) + ts.OnTextChanged(ranges[0].Start.iLine, ranges[ranges.Count - 1].End.iLine); + tb.EndUpdate(); + tb.Selection.EndUpdate(); + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + + lastSel = new RangeInfo(tb.Selection); + } + + public override UndoableCommand Clone() + { + return new ReplaceTextCommand(ts, new List(ranges), insertedText); + } + + internal static void ClearSelected(TextSource ts) + { + var tb = ts.CurrentTB; + + tb.Selection.Normalize(); + + Place start = tb.Selection.Start; + Place end = tb.Selection.End; + int fromLine = Math.Min(end.iLine, start.iLine); + int toLine = Math.Max(end.iLine, start.iLine); + int fromChar = tb.Selection.FromX; + int toChar = tb.Selection.ToX; + if (fromLine < 0) return; + // + if (fromLine == toLine) + ts[fromLine].RemoveRange(fromChar, toChar - fromChar); + else + { + ts[fromLine].RemoveRange(fromChar, ts[fromLine].Count - fromChar); + ts[toLine].RemoveRange(0, toChar); + ts.RemoveLine(fromLine + 1, toLine - fromLine - 1); + InsertCharCommand.MergeLines(fromLine, ts); + } + } + } + + /// + /// Clear selected text + /// + public class ClearSelectedCommand : UndoableCommand + { + string deletedText; + + /// + /// Construstor + /// + /// Underlaying textbox + public ClearSelectedCommand(TextSource ts): base(ts) + { + } + + /// + /// Undo operation + /// + public override void Undo() + { + ts.CurrentTB.Selection.Start = new Place(sel.FromX, Math.Min(sel.Start.iLine, sel.End.iLine)); + ts.OnTextChanging(); + InsertTextCommand.InsertText(deletedText, ts); + ts.OnTextChanged(sel.Start.iLine, sel.End.iLine); + ts.CurrentTB.Selection.Start = sel.Start; + ts.CurrentTB.Selection.End = sel.End; + } + + /// + /// Execute operation + /// + public override void Execute() + { + var tb = ts.CurrentTB; + + string temp = null; + ts.OnTextChanging(ref temp); + if (temp == "") + throw new ArgumentOutOfRangeException(); + + deletedText = tb.Selection.Text; + ClearSelected(ts); + lastSel = new RangeInfo(tb.Selection); + ts.OnTextChanged(lastSel.Start.iLine, lastSel.Start.iLine); + } + + internal static void ClearSelected(TextSource ts) + { + var tb = ts.CurrentTB; + + Place start = tb.Selection.Start; + Place end = tb.Selection.End; + int fromLine = Math.Min(end.iLine, start.iLine); + int toLine = Math.Max(end.iLine, start.iLine); + int fromChar = tb.Selection.FromX; + int toChar = tb.Selection.ToX; + if (fromLine < 0) return; + // + if (fromLine == toLine) + ts[fromLine].RemoveRange(fromChar, toChar - fromChar); + else + { + ts[fromLine].RemoveRange(fromChar, ts[fromLine].Count - fromChar); + ts[toLine].RemoveRange(0, toChar); + ts.RemoveLine(fromLine + 1, toLine - fromLine - 1); + InsertCharCommand.MergeLines(fromLine, ts); + } + // + tb.Selection.Start = new Place(fromChar, fromLine); + // + ts.NeedRecalc(new TextSource.TextChangedEventArgs(fromLine, toLine)); + } + + public override UndoableCommand Clone() + { + return new ClearSelectedCommand(ts); + } + } + + /// + /// Replaces text + /// + public class ReplaceMultipleTextCommand : UndoableCommand + { + List ranges; + List prevText = new List(); + + public class ReplaceRange + { + public Range ReplacedRange { get; set; } + public String ReplaceText { get; set; } + } + + /// + /// Constructor + /// + /// Underlaying textsource + /// List of ranges for replace + public ReplaceMultipleTextCommand(TextSource ts, List ranges) + : base(ts) + { + //sort ranges by place + ranges.Sort((r1, r2) => + { + if (r1.ReplacedRange.Start.iLine == r2.ReplacedRange.Start.iLine) + return r1.ReplacedRange.Start.iChar.CompareTo(r2.ReplacedRange.Start.iChar); + return r1.ReplacedRange.Start.iLine.CompareTo(r2.ReplacedRange.Start.iLine); + }); + // + this.ranges = ranges; + lastSel = sel = new RangeInfo(ts.CurrentTB.Selection); + } + + /// + /// Undo operation + /// + public override void Undo() + { + var tb = ts.CurrentTB; + + ts.OnTextChanging(); + + tb.Selection.BeginUpdate(); + for (int i = 0; i < ranges.Count; i++) + { + tb.Selection.Start = ranges[i].ReplacedRange.Start; + for (int j = 0; j < ranges[i].ReplaceText.Length; j++) + tb.Selection.GoRight(true); + ClearSelectedCommand.ClearSelected(ts); + var prevTextIndex = ranges.Count - 1 - i; + InsertTextCommand.InsertText(prevText[prevTextIndex], ts); + ts.OnTextChanged(ranges[i].ReplacedRange.Start.iLine, ranges[i].ReplacedRange.Start.iLine); + } + tb.Selection.EndUpdate(); + + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + + /// + /// Execute operation + /// + public override void Execute() + { + var tb = ts.CurrentTB; + prevText.Clear(); + + ts.OnTextChanging(); + + tb.Selection.BeginUpdate(); + for (int i = ranges.Count - 1; i >= 0; i--) + { + tb.Selection.Start = ranges[i].ReplacedRange.Start; + tb.Selection.End = ranges[i].ReplacedRange.End; + prevText.Add(tb.Selection.Text); + ClearSelectedCommand.ClearSelected(ts); + InsertTextCommand.InsertText(ranges[i].ReplaceText, ts); + ts.OnTextChanged(ranges[i].ReplacedRange.Start.iLine, ranges[i].ReplacedRange.End.iLine); + } + tb.Selection.EndUpdate(); + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + + lastSel = new RangeInfo(tb.Selection); + } + + public override UndoableCommand Clone() + { + return new ReplaceMultipleTextCommand(ts, new List(ranges)); + } + } + + /// + /// Removes lines + /// + public class RemoveLinesCommand : UndoableCommand + { + List iLines; + List prevText = new List(); + + /// + /// Constructor + /// + /// Underlaying textbox + /// List of ranges for replace + /// Text for inserting + public RemoveLinesCommand(TextSource ts, List iLines) + : base(ts) + { + //sort iLines + iLines.Sort(); + // + this.iLines = iLines; + lastSel = sel = new RangeInfo(ts.CurrentTB.Selection); + } + + /// + /// Undo operation + /// + public override void Undo() + { + var tb = ts.CurrentTB; + + ts.OnTextChanging(); + + tb.Selection.BeginUpdate(); + //tb.BeginUpdate(); + for (int i = 0; i < iLines.Count; i++) + { + var iLine = iLines[i]; + + if(iLine < ts.Count) + tb.Selection.Start = new Place(0, iLine); + else + tb.Selection.Start = new Place(ts[ts.Count - 1].Count, ts.Count - 1); + + InsertCharCommand.InsertLine(ts); + tb.Selection.Start = new Place(0, iLine); + var text = prevText[prevText.Count - i - 1]; + InsertTextCommand.InsertText(text, ts); + ts[iLine].IsChanged = true; + if (iLine < ts.Count - 1) + ts[iLine + 1].IsChanged = true; + else + ts[iLine - 1].IsChanged = true; + if(text.Trim() != string.Empty) + ts.OnTextChanged(iLine, iLine); + } + //tb.EndUpdate(); + tb.Selection.EndUpdate(); + + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + + /// + /// Execute operation + /// + public override void Execute() + { + var tb = ts.CurrentTB; + prevText.Clear(); + + ts.OnTextChanging(); + + tb.Selection.BeginUpdate(); + for(int i = iLines.Count - 1; i >= 0; i--) + { + var iLine = iLines[i]; + + prevText.Add(ts[iLine].Text);//backward + ts.RemoveLine(iLine); + //ts.OnTextChanged(ranges[i].Start.iLine, ranges[i].End.iLine); + } + tb.Selection.Start = new Place(0, 0); + tb.Selection.EndUpdate(); + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + + lastSel = new RangeInfo(tb.Selection); + } + + public override UndoableCommand Clone() + { + return new RemoveLinesCommand(ts, new List(iLines)); + } + } + + /// + /// Wrapper for multirange commands + /// + public class MultiRangeCommand : UndoableCommand + { + private UndoableCommand cmd; + private Range range; + private List commandsByRanges = new List(); + + public MultiRangeCommand(UndoableCommand command):base(command.ts) + { + this.cmd = command; + range = ts.CurrentTB.Selection.Clone(); + } + + public override void Execute() + { + commandsByRanges.Clear(); + var prevSelection = range.Clone(); + var iChar = -1; + var iStartLine = prevSelection.Start.iLine; + var iEndLine = prevSelection.End.iLine; + ts.CurrentTB.Selection.ColumnSelectionMode = false; + ts.CurrentTB.Selection.BeginUpdate(); + ts.CurrentTB.BeginUpdate(); + ts.CurrentTB.AllowInsertRemoveLines = false; + try + { + if (cmd is InsertTextCommand) + ExecuteInsertTextCommand(ref iChar, (cmd as InsertTextCommand).InsertedText); + else + if (cmd is InsertCharCommand && (cmd as InsertCharCommand).c != '\x0' && (cmd as InsertCharCommand).c != '\b')//if not DEL or BACKSPACE + ExecuteInsertTextCommand(ref iChar, (cmd as InsertCharCommand).c.ToString()); + else + ExecuteCommand(ref iChar); + } + catch (ArgumentOutOfRangeException) + { + } + finally + { + ts.CurrentTB.AllowInsertRemoveLines = true; + ts.CurrentTB.EndUpdate(); + + ts.CurrentTB.Selection = range; + if (iChar >= 0) + { + ts.CurrentTB.Selection.Start = new Place(iChar, iStartLine); + ts.CurrentTB.Selection.End = new Place(iChar, iEndLine); + } + ts.CurrentTB.Selection.ColumnSelectionMode = true; + ts.CurrentTB.Selection.EndUpdate(); + } + } + + private void ExecuteInsertTextCommand(ref int iChar, string text) + { + var lines = text.Split('\n'); + var iLine = 0; + foreach (var r in range.GetSubRanges(true)) + { + var line = ts.CurrentTB[r.Start.iLine]; + var lineIsEmpty = r.End < r.Start && line.StartSpacesCount == line.Count; + if (!lineIsEmpty) + { + var insertedText = lines[iLine%lines.Length]; + if (r.End < r.Start && insertedText!="") + { + //add forwarding spaces + insertedText = new string(' ', r.Start.iChar - r.End.iChar) + insertedText; + r.Start = r.End; + } + ts.CurrentTB.Selection = r; + var c = new InsertTextCommand(ts, insertedText); + c.Execute(); + if (ts.CurrentTB.Selection.End.iChar > iChar) + iChar = ts.CurrentTB.Selection.End.iChar; + commandsByRanges.Add(c); + } + iLine++; + } + } + + private void ExecuteCommand(ref int iChar) + { + foreach (var r in range.GetSubRanges(false)) + { + ts.CurrentTB.Selection = r; + var c = cmd.Clone(); + c.Execute(); + if (ts.CurrentTB.Selection.End.iChar > iChar) + iChar = ts.CurrentTB.Selection.End.iChar; + commandsByRanges.Add(c); + } + } + + public override void Undo() + { + ts.CurrentTB.BeginUpdate(); + ts.CurrentTB.Selection.BeginUpdate(); + try + { + for (int i = commandsByRanges.Count - 1; i >= 0; i--) + commandsByRanges[i].Undo(); + } + finally + { + ts.CurrentTB.Selection.EndUpdate(); + ts.CurrentTB.EndUpdate(); + } + ts.CurrentTB.Selection = range.Clone(); + ts.CurrentTB.OnTextChanged(range); + ts.CurrentTB.OnSelectionChanged(); + ts.CurrentTB.Selection.ColumnSelectionMode = true; + } + + public override UndoableCommand Clone() + { + throw new NotImplementedException(); + } + } + + /// + /// Remembers current selection and restore it after Undo + /// + public class SelectCommand : UndoableCommand + { + public SelectCommand(TextSource ts):base(ts) + { + } + + public override void Execute() + { + //remember selection + lastSel = new RangeInfo(ts.CurrentTB.Selection); + } + + protected override void OnTextChanged(bool invert) + { + } + + public override void Undo() + { + //restore selection + ts.CurrentTB.Selection = new Range(ts.CurrentTB, lastSel.Start, lastSel.End); + } + + public override UndoableCommand Clone() + { + var result = new SelectCommand(ts); + if(lastSel!=null) + result.lastSel = new RangeInfo(new Range(ts.CurrentTB, lastSel.Start, lastSel.End)); + return result; + } + } +} diff --git a/GUI.NET/Debugger/FastColoredTextBox/DocumentMap.cs b/GUI.NET/Debugger/FastColoredTextBox/DocumentMap.cs new file mode 100644 index 00000000..ae519c6f --- /dev/null +++ b/GUI.NET/Debugger/FastColoredTextBox/DocumentMap.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Drawing.Drawing2D; +using System.Text; +using System.Windows.Forms; + +namespace FastColoredTextBoxNS +{ + /// + /// Shows document map of FCTB + /// + public class DocumentMap : Control + { + public EventHandler TargetChanged; + + FastColoredTextBox target; + private float scale = 0.3f; + private bool needRepaint = true; + private Place startPlace = Place.Empty; + private bool scrollbarVisible = true; + + [Description("Target FastColoredTextBox")] + public FastColoredTextBox Target + { + get { return target; } + set + { + if (target != null) + UnSubscribe(target); + + target = value; + if (value != null) + { + Subscribe(target); + } + OnTargetChanged(); + } + } + + /// + /// Scale + /// + [Description("Scale")] + [DefaultValue(0.3f)] + public new float Scale + { + get { return scale; } + set + { + scale = value; + NeedRepaint(); + } + } + + /// + /// Scrollbar visibility + /// + [Description("Scrollbar visibility")] + [DefaultValue(true)] + public bool ScrollbarVisible + { + get { return scrollbarVisible; } + set + { + scrollbarVisible = value; + NeedRepaint(); + } + } + + public DocumentMap() + { + ForeColor = Color.Maroon; + SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true); + Application.Idle += Application_Idle; + } + + void Application_Idle(object sender, EventArgs e) + { + if(needRepaint) + Invalidate(); + } + + protected virtual void OnTargetChanged() + { + NeedRepaint(); + + if (TargetChanged != null) + TargetChanged(this, EventArgs.Empty); + } + + protected virtual void UnSubscribe(FastColoredTextBox target) + { + target.Scroll -= new ScrollEventHandler(Target_Scroll); + target.SelectionChangedDelayed -= new EventHandler(Target_SelectionChanged); + target.VisibleRangeChanged -= new EventHandler(Target_VisibleRangeChanged); + } + + protected virtual void Subscribe(FastColoredTextBox target) + { + target.Scroll += new ScrollEventHandler(Target_Scroll); + target.SelectionChangedDelayed += new EventHandler(Target_SelectionChanged); + target.VisibleRangeChanged += new EventHandler(Target_VisibleRangeChanged); + } + + protected virtual void Target_VisibleRangeChanged(object sender, EventArgs e) + { + NeedRepaint(); + } + + protected virtual void Target_SelectionChanged(object sender, EventArgs e) + { + NeedRepaint(); + } + + protected virtual void Target_Scroll(object sender, ScrollEventArgs e) + { + NeedRepaint(); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + NeedRepaint(); + } + + public void NeedRepaint() + { + needRepaint = true; + } + + protected override void OnPaint(PaintEventArgs e) + { + if (target == null) + return; + + var zoom = this.Scale * 100 / target.Zoom; + + if (zoom <= float.Epsilon) + return; + + //calc startPlace + var r = target.VisibleRange; + if (startPlace.iLine > r.Start.iLine) + startPlace.iLine = r.Start.iLine; + else + { + var endP = target.PlaceToPoint(r.End); + endP.Offset(0, -(int)(ClientSize.Height / zoom) + target.CharHeight); + var pp = target.PointToPlace(endP); + if (pp.iLine > startPlace.iLine) + startPlace.iLine = pp.iLine; + } + startPlace.iChar = 0; + //calc scroll pos + var linesCount = target.Lines.Count; + var sp1 = (float)r.Start.iLine / linesCount; + var sp2 = (float)r.End.iLine / linesCount; + + //scale graphics + e.Graphics.ScaleTransform(zoom, zoom); + //draw text + var size = new SizeF(ClientSize.Width / zoom, ClientSize.Height / zoom); + target.DrawText(e.Graphics, startPlace, size.ToSize()); + + //draw visible rect + var p0 = target.PlaceToPoint(startPlace); + var p1 = target.PlaceToPoint(r.Start); + var p2 = target.PlaceToPoint(r.End); + var y1 = p1.Y - p0.Y; + var y2 = p2.Y + target.CharHeight - p0.Y; + + e.Graphics.SmoothingMode = SmoothingMode.HighQuality; + + using (var brush = new SolidBrush(Color.FromArgb(50, ForeColor))) + using (var pen = new Pen(brush, 1 / zoom)) + { + var rect = new Rectangle(0, y1, (int)((ClientSize.Width - 1) / zoom), y2 - y1); + e.Graphics.FillRectangle(brush, rect); + e.Graphics.DrawRectangle(pen, rect); + } + + //draw scrollbar + if (scrollbarVisible) + { + e.Graphics.ResetTransform(); + e.Graphics.SmoothingMode = SmoothingMode.None; + + using (var brush = new SolidBrush(Color.FromArgb(200, ForeColor))) + { + var rect = new RectangleF(ClientSize.Width - 3, ClientSize.Height*sp1, 2, + ClientSize.Height*(sp2 - sp1)); + e.Graphics.FillRectangle(brush, rect); + } + } + + needRepaint = false; + } + + protected override void OnMouseDown(MouseEventArgs e) + { + if (e.Button == System.Windows.Forms.MouseButtons.Left) + Scroll(e.Location); + base.OnMouseDown(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (e.Button == System.Windows.Forms.MouseButtons.Left) + Scroll(e.Location); + base.OnMouseMove(e); + } + + private void Scroll(Point point) + { + if (target == null) + return; + + var zoom = this.Scale*100/target.Zoom; + + if (zoom <= float.Epsilon) + return; + + var p0 = target.PlaceToPoint(startPlace); + p0 = new Point(0, p0.Y + (int) (point.Y/zoom)); + var pp = target.PointToPlace(p0); + target.DoRangeVisible(new Range(target, pp, pp), true); + BeginInvoke((MethodInvoker)OnScroll); + } + + private void OnScroll() + { + Refresh(); + target.Refresh(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Application.Idle -= Application_Idle; + if (target != null) + UnSubscribe(target); + } + base.Dispose(disposing); + } + } +} diff --git a/GUI.NET/Debugger/FastColoredTextBox/EncodingDetector.cs b/GUI.NET/Debugger/FastColoredTextBox/EncodingDetector.cs new file mode 100644 index 00000000..2dc202d0 --- /dev/null +++ b/GUI.NET/Debugger/FastColoredTextBox/EncodingDetector.cs @@ -0,0 +1,363 @@ +// Copyright Tao Klerks, 2010-2012, tao@klerks.biz +// Licensed under the modified BSD license. + + +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +namespace FastColoredTextBoxNS +{ + public static class EncodingDetector + { + const long _defaultHeuristicSampleSize = 0x10000; //completely arbitrary - inappropriate for high numbers of files / high speed requirements + + public static Encoding DetectTextFileEncoding(string InputFilename) + { + using (FileStream textfileStream = File.OpenRead(InputFilename)) + { + return DetectTextFileEncoding(textfileStream, _defaultHeuristicSampleSize); + } + } + + public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize) + { + bool uselessBool = false; + return DetectTextFileEncoding(InputFileStream, _defaultHeuristicSampleSize, out uselessBool); + } + + public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize, out bool HasBOM) + { + Encoding encodingFound = null; + + long originalPos = InputFileStream.Position; + + InputFileStream.Position = 0; + + + //First read only what we need for BOM detection + byte[] bomBytes = new byte[InputFileStream.Length > 4 ? 4 : InputFileStream.Length]; + InputFileStream.Read(bomBytes, 0, bomBytes.Length); + + encodingFound = DetectBOMBytes(bomBytes); + + if (encodingFound != null) + { + InputFileStream.Position = originalPos; + HasBOM = true; + return encodingFound; + } + + + //BOM Detection failed, going for heuristics now. + // create sample byte array and populate it + byte[] sampleBytes = new byte[HeuristicSampleSize > InputFileStream.Length ? InputFileStream.Length : HeuristicSampleSize]; + Array.Copy(bomBytes, sampleBytes, bomBytes.Length); + if (InputFileStream.Length > bomBytes.Length) + InputFileStream.Read(sampleBytes, bomBytes.Length, sampleBytes.Length - bomBytes.Length); + InputFileStream.Position = originalPos; + + //test byte array content + encodingFound = DetectUnicodeInByteSampleByHeuristics(sampleBytes); + + HasBOM = false; + return encodingFound; + } + + public static Encoding DetectBOMBytes(byte[] BOMBytes) + { + if (BOMBytes.Length < 2) + return null; + + if (BOMBytes[0] == 0xff + && BOMBytes[1] == 0xfe + && (BOMBytes.Length < 4 + || BOMBytes[2] != 0 + || BOMBytes[3] != 0 + ) + ) + return Encoding.Unicode; + + if (BOMBytes[0] == 0xfe + && BOMBytes[1] == 0xff + ) + return Encoding.BigEndianUnicode; + + if (BOMBytes.Length < 3) + return null; + + if (BOMBytes[0] == 0xef && BOMBytes[1] == 0xbb && BOMBytes[2] == 0xbf) + return Encoding.UTF8; + + if (BOMBytes[0] == 0x2b && BOMBytes[1] == 0x2f && BOMBytes[2] == 0x76) + return Encoding.UTF7; + + if (BOMBytes.Length < 4) + return null; + + if (BOMBytes[0] == 0xff && BOMBytes[1] == 0xfe && BOMBytes[2] == 0 && BOMBytes[3] == 0) + return Encoding.UTF32; + + if (BOMBytes[0] == 0 && BOMBytes[1] == 0 && BOMBytes[2] == 0xfe && BOMBytes[3] == 0xff) + return Encoding.GetEncoding(12001); + + return null; + } + + public static Encoding DetectUnicodeInByteSampleByHeuristics(byte[] SampleBytes) + { + long oddBinaryNullsInSample = 0; + long evenBinaryNullsInSample = 0; + long suspiciousUTF8SequenceCount = 0; + long suspiciousUTF8BytesTotal = 0; + long likelyUSASCIIBytesInSample = 0; + + //Cycle through, keeping count of binary null positions, possible UTF-8 + // sequences from upper ranges of Windows-1252, and probable US-ASCII + // character counts. + + long currentPos = 0; + int skipUTF8Bytes = 0; + + while (currentPos < SampleBytes.Length) + { + //binary null distribution + if (SampleBytes[currentPos] == 0) + { + if (currentPos % 2 == 0) + evenBinaryNullsInSample++; + else + oddBinaryNullsInSample++; + } + + //likely US-ASCII characters + if (IsCommonUSASCIIByte(SampleBytes[currentPos])) + likelyUSASCIIBytesInSample++; + + //suspicious sequences (look like UTF-8) + if (skipUTF8Bytes == 0) + { + int lengthFound = DetectSuspiciousUTF8SequenceLength(SampleBytes, currentPos); + + if (lengthFound > 0) + { + suspiciousUTF8SequenceCount++; + suspiciousUTF8BytesTotal += lengthFound; + skipUTF8Bytes = lengthFound - 1; + } + } + else + { + skipUTF8Bytes--; + } + + currentPos++; + } + + //1: UTF-16 LE - in english / european environments, this is usually characterized by a + // high proportion of odd binary nulls (starting at 0), with (as this is text) a low + // proportion of even binary nulls. + // The thresholds here used (less than 20% nulls where you expect non-nulls, and more than + // 60% nulls where you do expect nulls) are completely arbitrary. + + if (((evenBinaryNullsInSample * 2.0) / SampleBytes.Length) < 0.2 + && ((oddBinaryNullsInSample * 2.0) / SampleBytes.Length) > 0.6 + ) + return Encoding.Unicode; + + + //2: UTF-16 BE - in english / european environments, this is usually characterized by a + // high proportion of even binary nulls (starting at 0), with (as this is text) a low + // proportion of odd binary nulls. + // The thresholds here used (less than 20% nulls where you expect non-nulls, and more than + // 60% nulls where you do expect nulls) are completely arbitrary. + + if (((oddBinaryNullsInSample * 2.0) / SampleBytes.Length) < 0.2 + && ((evenBinaryNullsInSample * 2.0) / SampleBytes.Length) > 0.6 + ) + return Encoding.BigEndianUnicode; + + + //3: UTF-8 - Martin Dürst outlines a method for detecting whether something CAN be UTF-8 content + // using regexp, in his w3c.org unicode FAQ entry: + // http://www.w3.org/International/questions/qa-forms-utf-8 + // adapted here for C#. + string potentiallyMangledString = Encoding.ASCII.GetString(SampleBytes); + Regex UTF8Validator = new Regex(@"\A(" + + @"[\x09\x0A\x0D\x20-\x7E]" + + @"|[\xC2-\xDF][\x80-\xBF]" + + @"|\xE0[\xA0-\xBF][\x80-\xBF]" + + @"|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}" + + @"|\xED[\x80-\x9F][\x80-\xBF]" + + @"|\xF0[\x90-\xBF][\x80-\xBF]{2}" + + @"|[\xF1-\xF3][\x80-\xBF]{3}" + + @"|\xF4[\x80-\x8F][\x80-\xBF]{2}" + + @")*\z"); + if (UTF8Validator.IsMatch(potentiallyMangledString)) + { + //Unfortunately, just the fact that it CAN be UTF-8 doesn't tell you much about probabilities. + //If all the characters are in the 0-127 range, no harm done, most western charsets are same as UTF-8 in these ranges. + //If some of the characters were in the upper range (western accented characters), however, they would likely be mangled to 2-byte by the UTF-8 encoding process. + // So, we need to play stats. + + // The "Random" likelihood of any pair of randomly generated characters being one + // of these "suspicious" character sequences is: + // 128 / (256 * 256) = 0.2%. + // + // In western text data, that is SIGNIFICANTLY reduced - most text data stays in the <127 + // character range, so we assume that more than 1 in 500,000 of these character + // sequences indicates UTF-8. The number 500,000 is completely arbitrary - so sue me. + // + // We can only assume these character sequences will be rare if we ALSO assume that this + // IS in fact western text - in which case the bulk of the UTF-8 encoded data (that is + // not already suspicious sequences) should be plain US-ASCII bytes. This, I + // arbitrarily decided, should be 80% (a random distribution, eg binary data, would yield + // approx 40%, so the chances of hitting this threshold by accident in random data are + // VERY low). + + if ((suspiciousUTF8SequenceCount * 500000.0 / SampleBytes.Length >= 1) //suspicious sequences + && ( + //all suspicious, so cannot evaluate proportion of US-Ascii + SampleBytes.Length - suspiciousUTF8BytesTotal == 0 + || + likelyUSASCIIBytesInSample * 1.0 / (SampleBytes.Length - suspiciousUTF8BytesTotal) >= 0.8 + ) + ) + return Encoding.UTF8; + } + + return null; + } + + private static bool IsCommonUSASCIIByte(byte testByte) + { + if (testByte == 0x0A //lf + || testByte == 0x0D //cr + || testByte == 0x09 //tab + || (testByte >= 0x20 && testByte <= 0x2F) //common punctuation + || (testByte >= 0x30 && testByte <= 0x39) //digits + || (testByte >= 0x3A && testByte <= 0x40) //common punctuation + || (testByte >= 0x41 && testByte <= 0x5A) //capital letters + || (testByte >= 0x5B && testByte <= 0x60) //common punctuation + || (testByte >= 0x61 && testByte <= 0x7A) //lowercase letters + || (testByte >= 0x7B && testByte <= 0x7E) //common punctuation + ) + return true; + else + return false; + } + + private static int DetectSuspiciousUTF8SequenceLength(byte[] SampleBytes, long currentPos) + { + int lengthFound = 0; + + if (SampleBytes.Length >= currentPos + 1 + && SampleBytes[currentPos] == 0xC2 + ) + { + if (SampleBytes[currentPos + 1] == 0x81 + || SampleBytes[currentPos + 1] == 0x8D + || SampleBytes[currentPos + 1] == 0x8F + ) + lengthFound = 2; + else if (SampleBytes[currentPos + 1] == 0x90 + || SampleBytes[currentPos + 1] == 0x9D + ) + lengthFound = 2; + else if (SampleBytes[currentPos + 1] >= 0xA0 + && SampleBytes[currentPos + 1] <= 0xBF + ) + lengthFound = 2; + } + else if (SampleBytes.Length >= currentPos + 1 + && SampleBytes[currentPos] == 0xC3 + ) + { + if (SampleBytes[currentPos + 1] >= 0x80 + && SampleBytes[currentPos + 1] <= 0xBF + ) + lengthFound = 2; + } + else if (SampleBytes.Length >= currentPos + 1 + && SampleBytes[currentPos] == 0xC5 + ) + { + if (SampleBytes[currentPos + 1] == 0x92 + || SampleBytes[currentPos + 1] == 0x93 + ) + lengthFound = 2; + else if (SampleBytes[currentPos + 1] == 0xA0 + || SampleBytes[currentPos + 1] == 0xA1 + ) + lengthFound = 2; + else if (SampleBytes[currentPos + 1] == 0xB8 + || SampleBytes[currentPos + 1] == 0xBD + || SampleBytes[currentPos + 1] == 0xBE + ) + lengthFound = 2; + } + else if (SampleBytes.Length >= currentPos + 1 + && SampleBytes[currentPos] == 0xC6 + ) + { + if (SampleBytes[currentPos + 1] == 0x92) + lengthFound = 2; + } + else if (SampleBytes.Length >= currentPos + 1 + && SampleBytes[currentPos] == 0xCB + ) + { + if (SampleBytes[currentPos + 1] == 0x86 + || SampleBytes[currentPos + 1] == 0x9C + ) + lengthFound = 2; + } + else if (SampleBytes.Length >= currentPos + 2 + && SampleBytes[currentPos] == 0xE2 + ) + { + if (SampleBytes[currentPos + 1] == 0x80) + { + if (SampleBytes[currentPos + 2] == 0x93 + || SampleBytes[currentPos + 2] == 0x94 + ) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0x98 + || SampleBytes[currentPos + 2] == 0x99 + || SampleBytes[currentPos + 2] == 0x9A + ) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0x9C + || SampleBytes[currentPos + 2] == 0x9D + || SampleBytes[currentPos + 2] == 0x9E + ) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0xA0 + || SampleBytes[currentPos + 2] == 0xA1 + || SampleBytes[currentPos + 2] == 0xA2 + ) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0xA6) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0xB0) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0xB9 + || SampleBytes[currentPos + 2] == 0xBA + ) + lengthFound = 3; + } + else if (SampleBytes[currentPos + 1] == 0x82 + && SampleBytes[currentPos + 2] == 0xAC + ) + lengthFound = 3; + else if (SampleBytes[currentPos + 1] == 0x84 + && SampleBytes[currentPos + 2] == 0xA2 + ) + lengthFound = 3; + } + + return lengthFound; + } + } +} diff --git a/GUI.NET/Debugger/FastColoredTextBox/ExportToHTML.cs b/GUI.NET/Debugger/FastColoredTextBox/ExportToHTML.cs new file mode 100644 index 00000000..c2e07b9b --- /dev/null +++ b/GUI.NET/Debugger/FastColoredTextBox/ExportToHTML.cs @@ -0,0 +1,222 @@ +using System.Text; +using System.Drawing; +using System.Collections.Generic; + +namespace FastColoredTextBoxNS +{ + /// + /// Exports colored text as HTML + /// + /// At this time only TextStyle renderer is supported. Other styles is not exported. + public class ExportToHTML + { + public string LineNumbersCSS = ""; + + /// + /// Use nbsp; instead space + /// + public bool UseNbsp { get; set; } + /// + /// Use nbsp; instead space in beginning of line + /// + public bool UseForwardNbsp { get; set; } + /// + /// Use original font + /// + public bool UseOriginalFont { get; set; } + /// + /// Use style tag instead style attribute + /// + public bool UseStyleTag { get; set; } + /// + /// Use 'br' tag instead of '\n' + /// + public bool UseBr { get; set; } + /// + /// Includes line numbers + /// + public bool IncludeLineNumbers { get; set; } + + FastColoredTextBox tb; + + public ExportToHTML() + { + UseNbsp = true; + UseOriginalFont = true; + UseStyleTag = true; + UseBr = true; + } + + public string GetHtml(FastColoredTextBox tb) + { + this.tb = tb; + Range sel = new Range(tb); + sel.SelectAll(); + return GetHtml(sel); + } + + public string GetHtml(Range r) + { + this.tb = r.tb; + Dictionary styles = new Dictionary(); + StringBuilder sb = new StringBuilder(); + StringBuilder tempSB = new StringBuilder(); + StyleIndex currentStyleId = StyleIndex.None; + r.Normalize(); + int currentLine = r.Start.iLine; + styles[currentStyleId] = null; + // + if (UseOriginalFont) + sb.AppendFormat("", + r.tb.Font.Name, r.tb.Font.SizeInPoints, r.tb.CharHeight); + + // + if (IncludeLineNumbers) + tempSB.AppendFormat("{0} ", currentLine + 1); + // + bool hasNonSpace = false; + foreach (Place p in r) + { + Char c = r.tb[p.iLine][p.iChar]; + if (c.style != currentStyleId) + { + Flush(sb, tempSB, currentStyleId); + currentStyleId = c.style; + styles[currentStyleId] = null; + } + + if (p.iLine != currentLine) + { + for (int i = currentLine; i < p.iLine; i++) + { + tempSB.Append(UseBr ? "
" : "\r\n"); + if (IncludeLineNumbers) + tempSB.AppendFormat("{0} ", i + 2); + } + currentLine = p.iLine; + hasNonSpace = false; + } + switch (c.c) + { + case ' ': + if ((hasNonSpace || !UseForwardNbsp) && !UseNbsp) + goto default; + + tempSB.Append(" "); + break; + case '<': + tempSB.Append("<"); + break; + case '>': + tempSB.Append(">"); + break; + case '&': + tempSB.Append("&"); + break; + default: + hasNonSpace = true; + tempSB.Append(c.c); + break; + } + } + Flush(sb, tempSB, currentStyleId); + + if (UseOriginalFont) + sb.Append("
"); + + //build styles + if (UseStyleTag) + { + tempSB.Length = 0; + tempSB.Append(""); + + sb.Insert(0, tempSB.ToString()); + } + + if (IncludeLineNumbers) + sb.Insert(0, LineNumbersCSS); + + return sb.ToString(); + } + + private string GetCss(StyleIndex styleIndex) + { + List