diff --git a/Core/Assembler.cpp b/Core/Assembler.cpp index 000e813..505243e 100644 --- a/Core/Assembler.cpp +++ b/Core/Assembler.cpp @@ -415,6 +415,10 @@ Assembler::Assembler(shared_ptr labelManager) _labelManager = labelManager; } +Assembler::~Assembler() +{ +} + uint32_t Assembler::AssembleCode(string code, uint32_t startAddress, int16_t* assembledCode) { for(uint8_t i = 0; i < 255; i++) { diff --git a/Core/Assembler.h b/Core/Assembler.h index c1aa68e..e73995d 100644 --- a/Core/Assembler.h +++ b/Core/Assembler.h @@ -1,7 +1,7 @@ #pragma once #include "stdafx.h" -#include #include +#include "IAssembler.h" #include "CpuDisUtils.h" class LabelManager; @@ -21,24 +21,7 @@ struct LineData bool HasOpeningBracket = false; }; -enum AssemblerSpecialCodes -{ - OK = 0, - EndOfLine = -1, - ParsingError = -2, - OutOfRangeJump = -3, - LabelRedefinition = -4, - MissingOperand = -5, - OperandOutOfRange = -6, - InvalidHex = -7, - InvalidSpaces = -8, - TrailingText = -9, - UnknownLabel = -10, - InvalidInstruction = -11, - InvalidBinaryValue = -12, -}; - -class Assembler +class Assembler : public IAssembler { private: std::unordered_map> _availableModesByOpName; @@ -54,6 +37,7 @@ private: public: Assembler(shared_ptr labelManager); + virtual ~Assembler(); uint32_t AssembleCode(string code, uint32_t startAddress, int16_t* assembledCode); }; \ No newline at end of file diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index a445c8e..70737fd 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -69,6 +69,7 @@ + @@ -86,6 +87,7 @@ + @@ -279,6 +281,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index c1bd893..7754731 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -488,9 +488,6 @@ Debugger - - Debugger - SNES\Coprocessors\SPC7110 @@ -590,6 +587,15 @@ GB + + Debugger\Assembler + + + Debugger\Assembler + + + Debugger\Assembler + @@ -875,9 +881,6 @@ Debugger - - Debugger - SNES\Coprocessors\SPC7110 @@ -938,6 +941,12 @@ GB + + Debugger\Assembler + + + Debugger\Assembler + @@ -1027,5 +1036,8 @@ {b1753ff0-0c73-4acf-978b-1964222e01c6} + + {8abb14c6-e2ee-48cb-ad91-38402cb8510c} + \ No newline at end of file diff --git a/Core/CpuDebugger.cpp b/Core/CpuDebugger.cpp index d9dc610..c39ff44 100644 --- a/Core/CpuDebugger.cpp +++ b/Core/CpuDebugger.cpp @@ -20,6 +20,7 @@ #include "Console.h" #include "MemoryAccessCounter.h" #include "ExpressionEvaluator.h" +#include "Assembler.h" CpuDebugger::CpuDebugger(Debugger* debugger, CpuType cpuType) { @@ -39,6 +40,7 @@ CpuDebugger::CpuDebugger(Debugger* debugger, CpuType cpuType) _callstackManager.reset(new CallstackManager(debugger)); _breakpointManager.reset(new BreakpointManager(debugger, cpuType, _eventManager.get())); _step.reset(new StepRequest()); + _assembler.reset(new Assembler(_debugger->GetLabelManager())); if(GetState().PC == 0) { //Enable breaking on uninit reads when debugger is opened at power on @@ -240,6 +242,11 @@ shared_ptr CpuDebugger::GetEventManager() return _eventManager; } +shared_ptr CpuDebugger::GetAssembler() +{ + return _assembler; +} + shared_ptr CpuDebugger::GetCallstackManager() { return _callstackManager; diff --git a/Core/CpuDebugger.h b/Core/CpuDebugger.h index 2de234a..e978f97 100644 --- a/Core/CpuDebugger.h +++ b/Core/CpuDebugger.h @@ -17,6 +17,7 @@ class EventManager; class MemoryMappings; class BreakpointManager; class Sa1; +class Assembler; class CpuDebugger final : public IDebugger { @@ -31,6 +32,7 @@ class CpuDebugger final : public IDebugger Sa1* _sa1; shared_ptr _eventManager; + shared_ptr _assembler; shared_ptr _callstackManager; unique_ptr _breakpointManager; unique_ptr _step; @@ -55,6 +57,7 @@ public: void ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi); shared_ptr GetEventManager(); + shared_ptr GetAssembler(); shared_ptr GetCallstackManager(); BreakpointManager* GetBreakpointManager(); }; \ No newline at end of file diff --git a/Core/Debugger.cpp b/Core/Debugger.cpp index 9f1eedb..755af16 100644 --- a/Core/Debugger.cpp +++ b/Core/Debugger.cpp @@ -42,6 +42,7 @@ #include "InternalRegisters.h" #include "AluMulDiv.h" #include "Assembler.h" +#include "GbAssembler.h" #include "../Utilities/HexUtilities.h" #include "../Utilities/FolderUtilities.h" #include "../Utilities/IpsPatcher.h" @@ -74,7 +75,6 @@ Debugger::Debugger(shared_ptr console) _memoryAccessCounter.reset(new MemoryAccessCounter(this, console.get())); _ppuTools.reset(new PpuTools(_console.get(), _ppu.get())); _scriptManager.reset(new ScriptManager(this)); - _assembler.reset(new Assembler(_labelManager)); if(_cart->GetGameboy()) { _gbDebugger.reset(new GbDebugger(this)); @@ -715,9 +715,13 @@ shared_ptr Debugger::GetConsole() return _console; } -shared_ptr Debugger::GetAssembler() +shared_ptr Debugger::GetAssembler(CpuType cpuType) { - return _assembler; + if(cpuType == CpuType::Gameboy) { + return std::dynamic_pointer_cast(_gbDebugger->GetAssembler()); + } else { + return std::dynamic_pointer_cast(_cpuDebugger->GetAssembler()); + } } template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); diff --git a/Core/Debugger.h b/Core/Debugger.h index 151beb5..71bc1af 100644 --- a/Core/Debugger.h +++ b/Core/Debugger.h @@ -34,8 +34,8 @@ class NecDspDebugger; class Cx4Debugger; class GbDebugger; class Breakpoint; -class Assembler; class IEventManager; +class IAssembler; enum class EventType; enum class EvalResultType : int32_t; @@ -69,7 +69,6 @@ private: shared_ptr _disassembler; shared_ptr _ppuTools; shared_ptr _labelManager; - shared_ptr _assembler; unique_ptr _watchExpEval[(int)DebugUtilities::GetLastCpuType() + 1]; @@ -144,5 +143,5 @@ public: shared_ptr GetScriptManager(); shared_ptr GetCallstackManager(CpuType cpuType); shared_ptr GetConsole(); - shared_ptr GetAssembler(); + shared_ptr GetAssembler(CpuType cpuType); }; diff --git a/Core/GameboyDisUtils.cpp b/Core/GameboyDisUtils.cpp index b1544df..7b03a9b 100644 --- a/Core/GameboyDisUtils.cpp +++ b/Core/GameboyDisUtils.cpp @@ -8,14 +8,14 @@ #include"../Utilities/HexUtilities.h" constexpr const char* _opTemplate[256] = { - "NOP", "LD BC, e", "LD (BC), A", "INC BC", "INC B", "DEC B", "LD B, d", "RLCA", "LD (b), SP", "ADD HL, BC", "LD A, (BC)", "DEC BC", "INC C", "DEC C", "LD C, d", "RRCA", + "NOP", "LD BC, e", "LD (BC), A", "INC BC", "INC B", "DEC B", "LD B, d", "RLCA", "LD (a), SP", "ADD HL, BC", "LD A, (BC)", "DEC BC", "INC C", "DEC C", "LD C, d", "RRCA", "STOP", "LD DE, e", "LD (DE), A", "INC DE", "INC D", "DEC D", "LD D, d", "RLA", "JR r", "ADD HL, DE", "LD A, (DE)", "DEC DE", "INC E", "DEC E", "LD E, d", "RRA", "JR NZ, r", "LD HL, e", "LD (HL+), A", "INC HL", "INC H", "DEC H", "LD H, d", "DAA", "JR Z, r", "ADD HL, HL", "LD A, (HL+)", "DEC HL", "INC L", "DEC L", "LD L, d", "CPL", "JR NC, r", "LD SP, e", "LD (HL-), A", "INC SP", "INC (HL)", "DEC (HL)", "LD (HL), d", "SCF", "JR C, r", "ADD HL, SP", "LD A, (HL-)", "DEC SP", "INC A", "DEC A", "LD A, d", "CCF", "LD B, B", "LD B, C", "LD B, D", "LD B, E", "LD B, H", "LD B, L", "LD B, (HL)", "LD B, A", "LD C, B", "LD C, C", "LD C, D", "LD C, E", "LD C, H", "LD C, L", "LD C, (HL)", "LD C, A", "LD D, B", "LD D, C", "LD D, D", "LD D, E", "LD D, H", "LD D, L", "LD D, (HL)", "LD D, A", "LD E, B", "LD E, C", "LD E, D", "LD E, E", "LD E, H", "LD E, L", "LD E, (HL)", "LD E, A", "LD H, B", "LD H, C", "LD H, D", "LD H, E", "LD H, H", "LD H, L", "LD H, (HL)", "LD H, A", "LD L, B", "LD L, C", "LD L, D", "LD L, E", "LD L, H", "LD L, L", "LD L, (HL)", "LD L, A", - "LD (HL), B", "LD (HL), C", "LD (HL), D", "LD (HL), E","LD (HL), H", "LD (HL), L", "HALT", "LD (HL), A", "LD A, B", "LD A, C", "LD A, D", "LD A, E", "LD A, H", "LD A, L", "LD A, (HL)", "LD A, A", + "LD (HL), B", "LD (HL), C", "LD (HL), D", "LD (HL), E","LD (HL), H", "LD (HL), L", "HALT", "LD (HL), A","LD A, B", "LD A, C", "LD A, D", "LD A, E", "LD A, H", "LD A, L", "LD A, (HL)", "LD A, A", "ADD A, B", "ADD A, C", "ADD A, D", "ADD A, E", "ADD A, H", "ADD A, L", "ADD A, (HL)", "ADD A, A", "ADC A, B", "ADC A, C", "ADC A, D", "ADC A, E", "ADC A, H", "ADC A, L", "ADC A, (HL)", "ADC A, A", "SUB B", "SUB C", "SUB D", "SUB E", "SUB H", "SUB L", "SUB (HL)", "SUB A", "SBC A, B", "SBC A, C", "SBC A, D", "SBC A, E", "SBC A, H", "SBC A, L", "SBC A, (HL)", "SBC A, A", "AND B", "AND C", "AND D", "AND E", "AND H", "AND L", "AND (HL)", "AND A", "XOR B", "XOR C", "XOR D", "XOR E", "XOR H", "XOR L", "XOR (HL)", "XOR A", @@ -23,7 +23,7 @@ constexpr const char* _opTemplate[256] = { "RET NZ", "POP BC", "JP NZ, a", "JP a", "CALL NZ, a", "PUSH BC", "ADD A, d", "RST 00H", "RET Z", "RET", "JP Z, a", "PREFIX", "CALL Z, a","CALL a", "ADC A, d", "RST 08H", "RET NC", "POP DE", "JP NC, a", "ILL_D3", "CALL NC, a", "PUSH DE", "SUB d", "RST 10H", "RET C", "RETI", "JP C, a", "ILL_DB", "CALL C, a","ILL_DD", "SBC A, d", "RST 18H", "LDH (c), A", "POP HL", "LD ($FF00+C), A","ILL_E3","ILL_E4", "PUSH HL", "AND d", "RST 20H", "ADD SP, d", "JP HL", "LD (a), A", "ILL_EB", "ILL_EC", "ILL_ED", "XOR d", "RST 28H", - "LDH A, (c)", "POP AF", "LD A, ($FF00+C)","DI", "ILL_F4", "PUSH AF", "OR d", "RST 30H", "LD HL, SP+d", "LD SP, HL", "LD A, (a)", "EI", "ILL_FC", "ILL_FD", "CP d", "RST 38H" + "LDH A, (c)", "POP AF", "LD A, ($FF00+C)","DI", "ILL_F4", "PUSH AF", "OR d", "RST 30H", "LD HL, SP+d", "LD SP, HL", "LD A, (a)", "EI", "ILL_FC", "ILL_FD", "CP d", "RST 38H" }; constexpr const char* _cbTemplate[256] = { @@ -93,8 +93,7 @@ void GameboyDisUtils::GetDisassembly(DisassemblyInfo& info, string& out, uint32_ //Jump addresses, memory addresses case 'a': getOperand((uint16_t)(byteCode[1] | (byteCode[2] << 8))); break; - case 'b': str.WriteAll('$', HexUtilities::ToHex(byteCode[1])); break; - case 'c': str.WriteAll('$', HexUtilities::ToHex((uint16_t)(0xFF00 | byteCode[1]))); break; + case 'c': getOperand((uint16_t)(0xFF00 | byteCode[1])); break; //Immediate values case 'd': str.WriteAll("$", HexUtilities::ToHex(byteCode[1])); break; @@ -113,7 +112,6 @@ int32_t GameboyDisUtils::GetEffectiveAddress(DisassemblyInfo& info, Console* con return -1; } - uint8_t GameboyDisUtils::GetOpSize(uint8_t opCode) { return _opSize[opCode]; @@ -134,4 +132,9 @@ bool GameboyDisUtils::IsReturnInstruction(uint8_t opCode) opCode == 0xC0 || opCode == 0xC8 || opCode == 0xD0 || opCode == 0xD8 || //Conditional RET opCode == 0xC9 || opCode == 0xD9 //Unconditional RET/RETI ); +} + +string GameboyDisUtils::GetOpTemplate(uint8_t op, bool prefixed) +{ + return prefixed ? _cbTemplate[op] : _opTemplate[op]; } \ No newline at end of file diff --git a/Core/GameboyDisUtils.h b/Core/GameboyDisUtils.h index f9e83fb..eaf670b 100644 --- a/Core/GameboyDisUtils.h +++ b/Core/GameboyDisUtils.h @@ -15,4 +15,5 @@ public: static uint8_t GetOpSize(uint8_t opCode); static bool IsJumpToSub(uint8_t opCode); static bool IsReturnInstruction(uint8_t opCode); + static string GetOpTemplate(uint8_t op, bool prefixed); }; diff --git a/Core/GbAssembler.cpp b/Core/GbAssembler.cpp new file mode 100644 index 0000000..57ccaff --- /dev/null +++ b/Core/GbAssembler.cpp @@ -0,0 +1,410 @@ +#include "stdafx.h" +#include +#include "GbAssembler.h" +#include "LabelManager.h" +#include "GameboyDisUtils.h" +#include "../Utilities/StringUtilities.h" +#include "../Utilities/HexUtilities.h" + +static const std::regex labelRegex = std::regex("^\\s*([@_a-zA-Z][@_a-zA-Z0-9]*)", std::regex_constants::icase); + +GbAssembler::GbAssembler(shared_ptr labelManager) +{ + _labelManager = labelManager; + InitAssembler(); +} + +void GbAssembler::InitAssembler() +{ + for(int i = 0; i < 512; i++) { + string op = GameboyDisUtils::GetOpTemplate(i & 0xFF, i >= 256); + size_t spaceIndex = op.find(' '); + size_t commaIndex = op.find(','); + string opName; + + OpCodeEntry entry = {}; + if(spaceIndex != string::npos) { + opName = op.substr(0, spaceIndex); + entry.ParamCount = commaIndex != string::npos ? 2 : 1; + } else { + opName = op; + entry.ParamCount = 0; + } + + entry.OpCode = i < 256 ? i : ((i << 8) | 0xCB); + + std::transform(opName.begin(), opName.end(), opName.begin(), ::tolower); + if(_opCodes.find(opName) == _opCodes.end()) { + _opCodes[opName] = vector(); + } + + if(entry.ParamCount > 0) { + string operands = op.substr(spaceIndex + 1); + operands.erase(std::remove_if(operands.begin(), operands.end(), isspace), operands.end()); + if(entry.ParamCount == 2) { + vector operandList = StringUtilities::Split(operands, ','); + InitParamEntry(entry.Param1, operandList[0]); + InitParamEntry(entry.Param2, operandList[1]); + } else if(entry.ParamCount == 1) { + InitParamEntry(entry.Param1, operands); + } + } + _opCodes[opName].push_back(entry); + } +} + +void GbAssembler::InitParamEntry(ParamEntry& entry, string param) +{ + if(param == "a") { + entry.Type = ParamType::Short; + } else if(param == "d") { + entry.Type = ParamType::Byte; + } else if(param == "e") { + entry.Type = ParamType::Short; + } else if(param == "r") { + entry.Type = ParamType::RelAddress; + } else if(param == "(a)") { + entry.Type = ParamType::Address; + } else if(param == "(c)") { + entry.Type = ParamType::HighAddress; + } else if(param == "SP+d") { + entry.Type = ParamType::StackOffset; + } else { + std::transform(param.begin(), param.end(), param.begin(), ::tolower); + entry.Type = ParamType::Literal; + entry.Param = param; + } + entry.Param = param; +} + +bool GbAssembler::IsRegisterName(string op) +{ + return op == "hl" || op == "af" || op == "bc" || op == "de" || op == "a" || op == "b" || op == "c" || op == "d" || op == "e" || op == "f" || op == "l" || op == "h"; +} + +int GbAssembler::ReadValue(string operand, int min, int max, unordered_map& localLabels, bool firstPass) +{ + int value = 0; + switch(operand[0]) { + //Hex + case '$': value = HexUtilities::FromHex(operand.substr(1)); break; + + case '%': + //Binary + for(size_t i = 1; i < operand.size(); i++) { + value <<= 1; + value |= operand[i] == '1' ? 1 : 0; + } + break; + + default: + if(std::regex_match(operand, labelRegex)) { + if(firstPass) { + return 0; + } else if(localLabels.find(operand) != localLabels.end()) { + value = localLabels.find(operand)->second; + } else { + int labelAddress = _labelManager->GetLabelRelativeAddress(operand, CpuType::Gameboy); + if(labelAddress >= 0) { + //Matching label found + value = labelAddress; + } + } + } else { + //Decimal + for(int i = 0; i < operand.size(); i++) { + if(operand[i] != '-' && (operand[i] < '0' || operand[i] > '9')) { + return -1; + } + } + + try { + value = std::stoi(operand); + if(value < 0) { + value = max + value + 1; + } + } catch(std::exception&) { + return -1; + } + } + break; + } + + if(value < min || value > max) { + return -1; + } + + return value; +} + +bool GbAssembler::IsMatch(ParamEntry& entry, string operand, uint32_t address, unordered_map& localLabels, bool firstPass) +{ + if(entry.Type != ParamType::Literal && IsRegisterName(operand)) { + return false; + } + + switch(entry.Type) { + case ParamType::None: return false; + + case ParamType::Literal: { + string param = entry.Param; + std::transform(param.begin(), param.end(), param.begin(), ::tolower); + std::transform(operand.begin(), operand.end(), operand.begin(), ::tolower); + return operand == param; + } + + case ParamType::Byte: + return ReadValue(operand, -128, 0xFF, localLabels, firstPass) >= 0; + + case ParamType::Short: + return ReadValue(operand, -32768, 0xFFFF, localLabels, firstPass) >= 0; + + case ParamType::Address: + if(operand.size() > 2 && operand[0] == '(' && operand[operand.size() - 1] == ')') { + return ReadValue(operand.substr(1, operand.size() - 2), 0, 0xFFFF, localLabels, firstPass) >= 0; + } + return false; + + case ParamType::HighAddress: + if(operand.size() > 2 && operand[0] == '(' && operand[operand.size() - 1] == ')') { + return ReadValue(operand.substr(1, operand.size() - 2), 0xFF00, 0xFFFF, localLabels, firstPass) >= 0; + } + return false; + + case ParamType::StackOffset: + std::transform(operand.begin(), operand.end(), operand.begin(), ::tolower); + if(operand.size() > 3 && operand.substr(0, 3) == "sp+") { + return ReadValue(operand.substr(3), 0, 0xFF, localLabels, firstPass) >= 0; + } + return false; + + case ParamType::RelAddress: { + int value = ReadValue(operand, 0, 0xFFFF, localLabels, firstPass); + if(value >= 0) { + int offset = (value - (address + 2)); + return offset >= -128 && offset <= 127; + } else if(firstPass) { + return 0; + } + return false; + } + } + + return true; +} + +void GbAssembler::PushOp(uint16_t opCode, vector& output, uint32_t& address) +{ + if(opCode < 256) { + PushByte((uint8_t)opCode, output, address); + } else { + PushWord((uint16_t)opCode, output, address); + } +} + +void GbAssembler::PushByte(uint8_t operand, vector& output, uint32_t& address) +{ + output.push_back(operand); + address++; +} + +void GbAssembler::PushWord(uint16_t operand, vector& output, uint32_t& address) +{ + output.push_back((uint8_t)operand); + output.push_back((operand >> 8)); + address += 2; +} + +void GbAssembler::ProcessOperand(ParamEntry& entry, string operand, vector& output, uint32_t& address, unordered_map& localLabels, bool firstPass) +{ + switch(entry.Type) { + case ParamType::Byte: + PushByte((uint8_t)ReadValue(operand, -128, 255, localLabels, firstPass), output, address); + break; + + case ParamType::Short: + PushWord((uint16_t)ReadValue(operand, -32768, 65535, localLabels, firstPass), output, address); + break; + + case ParamType::Address: + if(operand.size() > 2 && operand[0] == '(' && operand[operand.size() - 1] == ')') { + PushWord((uint16_t)ReadValue(operand.substr(1, operand.size() - 2), 0, 0xFFFF, localLabels, firstPass), output, address); + } + break; + + case ParamType::HighAddress: + if(operand.size() > 2 && operand[0] == '(' && operand[operand.size() - 1] == ')') { + PushByte((uint8_t)ReadValue(operand.substr(1, operand.size() - 2), 0xFF00, 0xFFFF, localLabels, firstPass), output, address); + } + break; + + case ParamType::StackOffset: + std::transform(operand.begin(), operand.end(), operand.begin(), ::tolower); + if(operand.size() > 3 && operand.substr(0, 3) == "sp+") { + PushByte((uint8_t)ReadValue(operand.substr(3), 0, 0xFF, localLabels, firstPass), output, address); + } + break; + + case ParamType::RelAddress: { + int value = ReadValue(operand, 0, 0xFFFF, localLabels, firstPass); + int offset = (value - (address + 1)); + PushByte((uint8_t)offset, output, address); + break; + } + } +} + +void GbAssembler::RunPass(vector& output, string code, uint32_t address, int16_t* assembledCode, bool firstPass, unordered_map& localLabels) +{ + unordered_set currentPassLabels; + for(string line : StringUtilities::Split(code, '\n')) { + //Remove comment + size_t commentIndex = line.find(';'); + if(commentIndex != string::npos) { + line = line.substr(0, commentIndex); + } + + //Check if this is a label definition + size_t labelDefIndex = line.find(':'); + if(labelDefIndex != string::npos) { + std::smatch match; + string labelName = line.substr(0, labelDefIndex); + if(std::regex_search(labelName, match, labelRegex)) { + string label = match.str(1); + if(firstPass && currentPassLabels.find(label) != currentPassLabels.end()) { + output.push_back(AssemblerSpecialCodes::LabelRedefinition); + continue; + } else { + localLabels[label] = address; + currentPassLabels.emplace(label); + line = line.substr(labelDefIndex + 1); + } + } else { + output.push_back(AssemblerSpecialCodes::InvalidLabel); + continue; + } + } + + //Trim left spaces + size_t startIndex = line.find_first_not_of("\t "); + if(startIndex > 0 && startIndex != string::npos) { + line = line.substr(startIndex); + } + + //Check if this is a .db statement + if(line.size() > 3 && line.substr(0, 3) == ".db") { + line = line.substr(3); + for(string byte : StringUtilities::Split(line, ' ')) { + if(byte.empty()) { + continue; + } + + int value = ReadValue(byte, -128, 255, localLabels, true); + if(value >= 0) { + PushByte((uint8_t)value, output, address); + } + } + output.push_back(AssemblerSpecialCodes::EndOfLine); + continue; + } + + //Find op code name + size_t spaceIndex = line.find(' '); + string opName; + if(spaceIndex != string::npos) { + opName = line.substr(0, spaceIndex); + } else { + opName = line; + } + + if(opName.empty()) { + output.push_back(AssemblerSpecialCodes::EndOfLine); + continue; + } + + std::transform(opName.begin(), opName.end(), opName.begin(), ::tolower); + + if(_opCodes.find(opName) == _opCodes.end()) { + //No matching opcode found, mark it as invalid + output.push_back(AssemblerSpecialCodes::InvalidInstruction); + continue; + } + + //Find the operands given + int paramCount = 0; + vector operandList; + if(spaceIndex != string::npos) { + string operands = line.substr(spaceIndex + 1); + operands.erase(std::remove_if(operands.begin(), operands.end(), isspace), operands.end()); + if(!operands.empty()) { + size_t commaIndex = line.find(','); + if(commaIndex != string::npos) { + paramCount = 2; + operandList = StringUtilities::Split(operands, ','); + + bool invalid = operandList.size() > 2; + for(string operand : operandList) { + if(operand.empty()) { + invalid = true; + break; + } + } + + if(invalid) { + output.push_back(AssemblerSpecialCodes::InvalidOperands); + continue; + } + } else { + paramCount = 1; + operandList = { operands }; + } + } + } + + bool matchFound = false; + //Find a matching set of opcode + operands + for(OpCodeEntry& entry : _opCodes.find(opName)->second) { + if(entry.ParamCount == paramCount) { + if(paramCount == 0) { + PushOp(entry.OpCode, output, address); + matchFound = true; + break; + } else if(paramCount == 1) { + if(IsMatch(entry.Param1, operandList[0], address, localLabels, firstPass)) { + PushOp(entry.OpCode, output, address); + ProcessOperand(entry.Param1, operandList[0], output, address, localLabels, firstPass); + matchFound = true; + break; + } + } else if(paramCount == 2) { + if(IsMatch(entry.Param1, operandList[0], address, localLabels, firstPass) && IsMatch(entry.Param2, operandList[1], address, localLabels, firstPass)) { + PushOp(entry.OpCode, output, address); + ProcessOperand(entry.Param1, operandList[0], output, address, localLabels, firstPass); + ProcessOperand(entry.Param2, operandList[1], output, address, localLabels, firstPass); + matchFound = true; + break; + } + } + } + } + + if(!matchFound) { + output.push_back(AssemblerSpecialCodes::InvalidOperands); + } else { + output.push_back(AssemblerSpecialCodes::EndOfLine); + } + } +} + +uint32_t GbAssembler::AssembleCode(string code, uint32_t address, int16_t* assembledCode) +{ + vector output; + unordered_map localLabels; + + RunPass(output, code, address, assembledCode, true, localLabels); + output.clear(); + RunPass(output, code, address, assembledCode, false, localLabels); + + memcpy(assembledCode, output.data(), std::min(100000, (int)output.size()) * sizeof(uint16_t)); + return (uint32_t)output.size(); +} diff --git a/Core/GbAssembler.h b/Core/GbAssembler.h new file mode 100644 index 0000000..bd3b768 --- /dev/null +++ b/Core/GbAssembler.h @@ -0,0 +1,55 @@ +#pragma once +#include "stdafx.h" +#include "IAssembler.h" + +class LabelManager; + +enum class ParamType +{ + None, + Literal, + Byte, + Short, + Address, + HighAddress, + RelAddress, + StackOffset +}; + +struct ParamEntry +{ + string Param; + ParamType Type; +}; + +struct OpCodeEntry +{ + uint16_t OpCode; + int ParamCount; + ParamEntry Param1; + ParamEntry Param2; +}; + +class GbAssembler : public IAssembler +{ +private: + unordered_map> _opCodes; + shared_ptr _labelManager; + + void InitParamEntry(ParamEntry& entry, string param); + bool IsRegisterName(string op); + void InitAssembler(); + int ReadValue(string operand, int min, int max, unordered_map& localLabels, bool firstPass); + bool IsMatch(ParamEntry& entry, string operand, uint32_t address, unordered_map& localLabels, bool firstPass); + void PushOp(uint16_t opCode, vector& output, uint32_t& address); + void PushByte(uint8_t operand, vector& output, uint32_t& address); + void PushWord(uint16_t operand, vector& output, uint32_t& address); + void ProcessOperand(ParamEntry& entry, string operand, vector& output, uint32_t& address, unordered_map& localLabels, bool firstPass); + + void RunPass(vector& output, string code, uint32_t address, int16_t* assembledCode, bool firstPass, unordered_map& localLabels); + +public: + GbAssembler(shared_ptr labelManager); + + uint32_t AssembleCode(string code, uint32_t startAddress, int16_t* assembledCode); +}; \ No newline at end of file diff --git a/Core/GbDebugger.cpp b/Core/GbDebugger.cpp index 76bae34..cf66d96 100644 --- a/Core/GbDebugger.cpp +++ b/Core/GbDebugger.cpp @@ -16,6 +16,7 @@ #include "GameboyDisUtils.h" #include "GbEventManager.h" #include "BaseEventManager.h" +#include "GbAssembler.h" GbDebugger::GbDebugger(Debugger* debugger) { @@ -31,6 +32,11 @@ GbDebugger::GbDebugger(Debugger* debugger) _callstackManager.reset(new CallstackManager(debugger)); _breakpointManager.reset(new BreakpointManager(debugger, CpuType::Gameboy, _eventManager.get())); _step.reset(new StepRequest()); + _assembler.reset(new GbAssembler(debugger->GetLabelManager())); +} + +GbDebugger::~GbDebugger() +{ } void GbDebugger::Reset() @@ -159,6 +165,11 @@ shared_ptr GbDebugger::GetEventManager() return _eventManager; } +shared_ptr GbDebugger::GetAssembler() +{ + return _assembler; +} + shared_ptr GbDebugger::GetCallstackManager() { return _callstackManager; diff --git a/Core/GbDebugger.h b/Core/GbDebugger.h index e9755cb..219c622 100644 --- a/Core/GbDebugger.h +++ b/Core/GbDebugger.h @@ -13,6 +13,7 @@ class MemoryManager; class BreakpointManager; class EmuSettings; class GbEventManager; +class GbAssembler; class GbDebugger final : public IDebugger { @@ -28,12 +29,14 @@ class GbDebugger final : public IDebugger shared_ptr _callstackManager; unique_ptr _breakpointManager; unique_ptr _step; + shared_ptr _assembler; uint8_t _prevOpCode = 0xFF; uint32_t _prevProgramCounter = 0; public: GbDebugger(Debugger* debugger); + ~GbDebugger(); void Reset(); @@ -44,6 +47,7 @@ public: void ProcessInterrupt(uint32_t originalPc, uint32_t currentPc); shared_ptr GetEventManager(); + shared_ptr GetAssembler(); shared_ptr GetCallstackManager(); BreakpointManager* GetBreakpointManager(); }; \ No newline at end of file diff --git a/Core/IAssembler.h b/Core/IAssembler.h new file mode 100644 index 0000000..1c025ca --- /dev/null +++ b/Core/IAssembler.h @@ -0,0 +1,27 @@ +#pragma once +#include "stdafx.h" + +class IAssembler +{ +public: + virtual uint32_t AssembleCode(string code, uint32_t startAddress, int16_t* assembledCode) = 0; +}; + +enum AssemblerSpecialCodes +{ + OK = 0, + EndOfLine = -1, + ParsingError = -2, + OutOfRangeJump = -3, + LabelRedefinition = -4, + MissingOperand = -5, + OperandOutOfRange = -6, + InvalidHex = -7, + InvalidSpaces = -8, + TrailingText = -9, + UnknownLabel = -10, + InvalidInstruction = -11, + InvalidBinaryValue = -12, + InvalidOperands = -13, + InvalidLabel = -14, +}; diff --git a/InteropDLL/DebugApiWrapper.cpp b/InteropDLL/DebugApiWrapper.cpp index d5fd10d..19a76e5 100644 --- a/InteropDLL/DebugApiWrapper.cpp +++ b/InteropDLL/DebugApiWrapper.cpp @@ -106,7 +106,7 @@ extern "C" DllExport const char* __stdcall GetScriptLog(int32_t scriptId) { return GetDebugger()->GetScriptManager()->GetScriptLog(scriptId); } //DllExport void __stdcall DebugSetScriptTimeout(uint32_t timeout) { LuaScriptingContext::SetScriptTimeout(timeout); } - DllExport uint32_t __stdcall AssembleCode(char* code, uint32_t startAddress, int16_t* assembledOutput) { return GetDebugger()->GetAssembler()->AssembleCode(code, startAddress, assembledOutput); } + DllExport uint32_t __stdcall AssembleCode(CpuType cpuType, char* code, uint32_t startAddress, int16_t* assembledOutput) { return GetDebugger()->GetAssembler(cpuType)->AssembleCode(code, startAddress, assembledOutput); } DllExport void __stdcall SaveRomToDisk(char* filename, bool saveIpsFile, CdlStripOption cdlStripOption) { GetDebugger()->SaveRomToDisk(filename, saveIpsFile, cdlStripOption); } }; \ No newline at end of file diff --git a/Libretro/Makefile.common b/Libretro/Makefile.common index c471002..3a7236c 100644 --- a/Libretro/Makefile.common +++ b/Libretro/Makefile.common @@ -63,6 +63,7 @@ SOURCES_CXX := $(LIBRETRO_DIR)/libretro.cpp \ $(CORE_DIR)/GameServer.cpp \ $(CORE_DIR)/GameServerConnection.cpp \ $(CORE_DIR)/Gameboy.cpp \ + $(CORE_DIR)/GbAssembler.cpp \ $(CORE_DIR)/GbCpu.cpp \ $(CORE_DIR)/GbDmaController.cpp \ $(CORE_DIR)/GbPpu.cpp \ diff --git a/UI/Debugger/Controls/ctrlDisassemblyView.cs b/UI/Debugger/Controls/ctrlDisassemblyView.cs index 6b82f2c..bd0f485 100644 --- a/UI/Debugger/Controls/ctrlDisassemblyView.cs +++ b/UI/Debugger/Controls/ctrlDisassemblyView.cs @@ -511,7 +511,7 @@ namespace Mesen.GUI.Debugger.Controls if(range.Start.Address >= 0 && range.End.Address >= 0 && range.Start.Address <= range.End.Address) { int length = range.End.Address - range.Start.Address + 1; - DebugWindowManager.OpenAssembler(GetSelectedCode(), range.Start.Address, length); + DebugWindowManager.OpenAssembler(_manager.CpuType, GetSelectedCode(), range.Start.Address, length); } } @@ -650,7 +650,9 @@ namespace Mesen.GUI.Debugger.Controls bool showMarkAs = !_inSourceView && (_manager.CpuType == CpuType.Cpu || _manager.CpuType == CpuType.Sa1); mnuMarkSelectionAs.Visible = showMarkAs; - mnuEditSelectedCode.Visible = showMarkAs; + + bool showEditCode = !_inSourceView && (_manager.CpuType == CpuType.Cpu || _manager.CpuType == CpuType.Sa1 || _manager.CpuType == CpuType.Gameboy); + mnuEditSelectedCode.Visible = showEditCode; mnuAddToWatch.Enabled = active; mnuEditInMemoryTools.Enabled = active; diff --git a/UI/Debugger/DebugWindowManager.cs b/UI/Debugger/DebugWindowManager.cs index a1f3c19..f9e3617 100644 --- a/UI/Debugger/DebugWindowManager.cs +++ b/UI/Debugger/DebugWindowManager.cs @@ -111,13 +111,13 @@ namespace Mesen.GUI.Debugger throw new Exception("Invalid CPU type"); } - public static void OpenAssembler(string code = "", int startAddress = 0, int blockLength = 0) + public static void OpenAssembler(CpuType cpuType, string code = "", int startAddress = 0, int blockLength = 0) { if(_openedWindows.Count == 0) { DebugWorkspaceManager.GetWorkspace(); } - frmAssembler frm = new frmAssembler(code, startAddress, blockLength); + frmAssembler frm = new frmAssembler(cpuType, code, startAddress, blockLength); frm.Icon = Properties.Resources.Chip; _openedWindows.Add(frm); frm.FormClosed += Debugger_FormClosed; diff --git a/UI/Debugger/frmAssembler.cs b/UI/Debugger/frmAssembler.cs index cd66d66..c1e4e81 100644 --- a/UI/Debugger/frmAssembler.cs +++ b/UI/Debugger/frmAssembler.cs @@ -18,6 +18,7 @@ namespace Mesen.GUI.Debugger { public partial class frmAssembler : BaseForm { + private CpuType _cpuType; private int _startAddress; private int _blockLength; private bool _hasParsingErrors = false; @@ -27,9 +28,20 @@ namespace Mesen.GUI.Debugger private int _textVersion = 0; private bool _updating = false; - public frmAssembler(string code = "", int startAddress = 0, int blockLength = 0) + public frmAssembler(CpuType? cpuType = null, string code = "", int startAddress = 0, int blockLength = 0) { + if(cpuType != null) { + _cpuType = cpuType.Value; + } else { + _cpuType = EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy ? CpuType.Gameboy : CpuType.Cpu; + } + + if(_cpuType == CpuType.Sa1) { + _cpuType = CpuType.Cpu; + } + InitializeComponent(); + txtCode.ForeColor = Color.Black; AssemblerConfig cfg = ConfigManager.Config.Debug.Assembler; @@ -126,7 +138,7 @@ namespace Mesen.GUI.Debugger return false; } else { for(int i = 0; i < ctrlHexBox.ByteProvider.Length; i++) { - if(DebugApi.GetMemoryValue(SnesMemoryType.CpuMemory, (UInt32)(_startAddress + i)) != ctrlHexBox.ByteProvider.ReadByte(i)) { + if(DebugApi.GetMemoryValue(_cpuType.ToMemoryType(), (UInt32)(_startAddress + i)) != ctrlHexBox.ByteProvider.ReadByte(i)) { return false; } } @@ -153,7 +165,7 @@ namespace Mesen.GUI.Debugger int version = _textVersion; Task.Run(() => { - short[] byteCode = DebugApi.AssembleCode(text, (UInt32)_startAddress); + short[] byteCode = DebugApi.AssembleCode(_cpuType, text, (UInt32)_startAddress); this.BeginInvoke((Action)(() => { _updating = false; @@ -184,6 +196,8 @@ namespace Mesen.GUI.Debugger case AssemblerSpecialCodes.UnknownLabel: message = "Unknown label"; break; case AssemblerSpecialCodes.InvalidInstruction: message = "Invalid instruction"; break; case AssemblerSpecialCodes.InvalidBinaryValue: message = "Invalid binary value"; break; + case AssemblerSpecialCodes.InvalidOperands: message = "Invalid operands for instruction"; break; + case AssemblerSpecialCodes.InvalidLabel: message = "Invalid label name"; break; } errorList.Add(new ErrorDetail() { Message = message + " - " + codeLines[line-1], LineNumber = line }); line++; @@ -264,15 +278,22 @@ namespace Mesen.GUI.Debugger bytes.Add(0xEA); } - DebugApi.SetMemoryValues(SnesMemoryType.CpuMemory, (UInt32)_startAddress, bytes.ToArray(), bytes.Count); + frmDebugger debugger = null; + if(_cpuType == CpuType.Gameboy) { + DebugApi.SetMemoryValues(SnesMemoryType.GameboyMemory, (UInt32)_startAddress, bytes.ToArray(), bytes.Count); + debugger = DebugWindowManager.OpenDebugger(CpuType.Gameboy); + //TODO: CDL for gameboy + } else { + DebugApi.SetMemoryValues(SnesMemoryType.CpuMemory, (UInt32)_startAddress, bytes.ToArray(), bytes.Count); - AddressInfo absStart = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = _startAddress, Type = SnesMemoryType.CpuMemory }); - AddressInfo absEnd = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = _startAddress + bytes.Count, Type = SnesMemoryType.CpuMemory }); - if(absStart.Type == SnesMemoryType.PrgRom && absEnd.Type == SnesMemoryType.PrgRom && (absEnd.Address - absStart.Address) == bytes.Count) { - DebugApi.MarkBytesAs((uint)absStart.Address, (uint)absEnd.Address, CdlFlags.Code); + AddressInfo absStart = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = _startAddress, Type = SnesMemoryType.CpuMemory }); + AddressInfo absEnd = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = _startAddress + bytes.Count, Type = SnesMemoryType.CpuMemory }); + if(absStart.Type == SnesMemoryType.PrgRom && absEnd.Type == SnesMemoryType.PrgRom && (absEnd.Address - absStart.Address) == bytes.Count) { + DebugApi.MarkBytesAs((uint)absStart.Address, (uint)absEnd.Address, CdlFlags.Code); + } + debugger = DebugWindowManager.OpenDebugger(CpuType.Cpu); } - frmDebugger debugger = DebugWindowManager.OpenDebugger(CpuType.Cpu); if(debugger != null) { debugger.RefreshDisassembly(); } @@ -374,7 +395,9 @@ namespace Mesen.GUI.Debugger TrailingText = -9, UnknownLabel = -10, InvalidInstruction = -11, - InvalidBinaryValue = -12 + InvalidBinaryValue = -12, + InvalidOperands = -13, + InvalidLabel = -14, } private void lstErrors_DoubleClick(object sender, EventArgs e) diff --git a/UI/Forms/frmMain.cs b/UI/Forms/frmMain.cs index 2d1227b..a8949b4 100644 --- a/UI/Forms/frmMain.cs +++ b/UI/Forms/frmMain.cs @@ -481,8 +481,6 @@ namespace Mesen.GUI.Forms mnuDebugger.Visible = !isGameboyMode; mnuSpcDebugger.Enabled = !isGameboyMode; mnuSpcDebugger.Visible = !isGameboyMode; - mnuAssembler.Enabled = !isGameboyMode; - mnuAssembler.Visible = !isGameboyMode; sepCoprocessors.Visible = !isGameboyMode; } diff --git a/UI/Interop/DebugApi.cs b/UI/Interop/DebugApi.cs index ec41168..79a6151 100644 --- a/UI/Interop/DebugApi.cs +++ b/UI/Interop/DebugApi.cs @@ -176,12 +176,12 @@ namespace Mesen.GUI [DllImport(DllPath)] public static extern void SetCdlData([In]byte[] cdlData, Int32 length); [DllImport(DllPath)] public static extern void MarkBytesAs(UInt32 start, UInt32 end, CdlFlags type); - [DllImport(DllPath, EntryPoint = "AssembleCode")] private static extern UInt32 AssembleCodeWrapper([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string code, UInt32 startAddress, [In, Out]Int16[] assembledCodeBuffer); - public static Int16[] AssembleCode(string code, UInt32 startAddress) + [DllImport(DllPath, EntryPoint = "AssembleCode")] private static extern UInt32 AssembleCodeWrapper(CpuType cpuType, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string code, UInt32 startAddress, [In, Out]Int16[] assembledCodeBuffer); + public static Int16[] AssembleCode(CpuType cpuType, string code, UInt32 startAddress) { code = code.Replace(Environment.NewLine, "\n"); Int16[] assembledCode = new Int16[100000]; - UInt32 size = DebugApi.AssembleCodeWrapper(code, startAddress, assembledCode); + UInt32 size = DebugApi.AssembleCodeWrapper(cpuType, code, startAddress, assembledCode); Array.Resize(ref assembledCode, (int)size); return assembledCode; }