diff --git a/build/Makefile b/build/Makefile index b477ca8..0c150b0 100644 --- a/build/Makefile +++ b/build/Makefile @@ -77,10 +77,10 @@ YMFM_OBJS := $(patsubst $(YMFM_SRCDIR)/%.cpp,$(YMFM_OBJDIR)/%.o,$(YMFM_SRCS)) BOX16_SRCDIR := $(REPODIR)/src BOX16_OBJDIR := $(OBJDIR)/box16/obj -BOX16_OBJDIRS := $(BOX16_OBJDIR) $(BOX16_OBJDIR)/compat $(BOX16_OBJDIR)/cpu $(BOX16_OBJDIR)/gif $(BOX16_OBJDIR)/glad $(BOX16_OBJDIR)/imgui $(BOX16_OBJDIR)/overlay $(BOX16_OBJDIR)/vera $(BOX16_OBJDIR)/ym2151 +BOX16_OBJDIRS := $(BOX16_OBJDIR) $(BOX16_OBJDIR)/boxmon $(BOX16_OBJDIR)/compat $(BOX16_OBJDIR)/cpu $(BOX16_OBJDIR)/gif $(BOX16_OBJDIR)/glad $(BOX16_OBJDIR)/imgui $(BOX16_OBJDIR)/overlay $(BOX16_OBJDIR)/vera $(BOX16_OBJDIR)/ym2151 BOX16_INCDIRS := -I$(BOX16_SRCDIR) -I$(NFD_SRCDIR)/include -I$(VENDORSRC)/mINI/src/mini -I$(LPNG_SRCDIR) -I$(VENDORSRC)/rtmidi -I$(VENDORSRC)/ymfm/src -BOX16_SRCS := $(wildcard $(BOX16_SRCDIR)/*.cpp) $(BOX16_SRCDIR)/compat/compat.cpp $(wildcard $(BOX16_SRCDIR)/cpu/*.cpp) $(wildcard $(BOX16_SRCDIR)/gif/*.cpp) $(wildcard $(BOX16_SRCDIR)/glad/*.cpp) $(wildcard $(BOX16_SRCDIR)/imgui/*.cpp) $(wildcard $(BOX16_SRCDIR)/overlay/*.cpp) $(wildcard $(BOX16_SRCDIR)/vera/*.cpp) $(wildcard $(BOX16_SRCDIR)/ym2151/*.cpp) +BOX16_SRCS := $(wildcard $(BOX16_SRCDIR)/*.cpp) $(wildcard $(BOX16_SRCDIR)/boxmon/*.cpp) $(BOX16_SRCDIR)/compat/compat.cpp $(wildcard $(BOX16_SRCDIR)/cpu/*.cpp) $(wildcard $(BOX16_SRCDIR)/gif/*.cpp) $(wildcard $(BOX16_SRCDIR)/glad/*.cpp) $(wildcard $(BOX16_SRCDIR)/imgui/*.cpp) $(wildcard $(BOX16_SRCDIR)/overlay/*.cpp) $(wildcard $(BOX16_SRCDIR)/vera/*.cpp) $(wildcard $(BOX16_SRCDIR)/ym2151/*.cpp) BOX16_OBJS := $(patsubst $(BOX16_SRCDIR)/%.cpp,$(BOX16_OBJDIR)/%.o,$(BOX16_SRCS)) BOX16_CFLAGS := $(shell $(PKGCONFIG) --cflags alsa sdl2 gl zlib) $(CFLAGS) $(CWARNS) $(BOX16_INCDIRS) -include $(BOX16_SRCDIR)/compat/compat.h $(MYFLAGS) BOX16_LDFLAGS := $(DFLAGS) $(MYFLAGS) $(shell $(PKGCONFIG) --libs alsa sdl2 gl zlib) -lstdc++fs -ldl diff --git a/build/vs2022/box16.vcxproj b/build/vs2022/box16.vcxproj index eefe7db..a204c84 100644 --- a/build/vs2022/box16.vcxproj +++ b/build/vs2022/box16.vcxproj @@ -365,6 +365,10 @@ xcopy $(VendorDir)\zlib-1.2.13\lib\x64\*.dll "$(OutDir)" /E /I /F /Y + + + + @@ -424,6 +428,10 @@ xcopy $(VendorDir)\zlib-1.2.13\lib\x64\*.dll "$(OutDir)" /E /I /F /Y + + + + @@ -498,6 +506,7 @@ xcopy $(VendorDir)\zlib-1.2.13\lib\x64\*.dll "$(OutDir)" /E /I /F /Y + diff --git a/build/vs2022/box16.vcxproj.filters b/build/vs2022/box16.vcxproj.filters index 1c90d6e..5c6c3d6 100644 --- a/build/vs2022/box16.vcxproj.filters +++ b/build/vs2022/box16.vcxproj.filters @@ -38,6 +38,9 @@ {fe82bbb0-2a22-4543-a74d-8e837e76276e} + + {a21c457e-1f07-4a40-aee4-d5458508b4b5} + {b275878e-1323-473f-a92d-49303b9917bc} @@ -208,6 +211,15 @@ Source Files + + Source Files\boxmon + + + Source Files\boxmon + + + Source Files\boxmon + Source Files\glad @@ -217,6 +229,9 @@ Source Files + + Source Files\boxmon + @@ -432,6 +447,15 @@ Source Files + + Source Files\boxmon + + + Source Files\boxmon + + + Source Files\boxmon + Source Files\glad\KHR @@ -444,6 +468,9 @@ Source Files + + Source Files\boxmon + @@ -461,6 +488,9 @@ Source Files\cpu + + Source Files\boxmon + diff --git a/resources/icons.png b/resources/icons.png index 399b14b..3ad233a 100644 Binary files a/resources/icons.png and b/resources/icons.png differ diff --git a/src/boxmon/boxmon.cpp b/src/boxmon/boxmon.cpp new file mode 100644 index 0000000..cf3abe8 --- /dev/null +++ b/src/boxmon/boxmon.cpp @@ -0,0 +1,171 @@ +#include "boxmon.h" + +#include +#include + +#include "command.h" +#include "parser.h" + +boxmon::parser Console_parser; + +std::vector Console_history; +std::vector Command_history; + +static bool Console_suppress_output = false; +static bool Console_suppress_warnings = false; +static bool Console_suppress_errors = false; + +enum class parse_command_result { + ok, + parse_error, + not_found +}; + +void boxmon_system_init() +{ +} + +void boxmon_system_shutdown() +{ +} + +bool boxmon_load_file(const std::filesystem::path &path) +{ + std::ifstream infile(path, std::ios_base::in); + + if (!infile.is_open()) { + return false; + } + + boxmon::parser file_parser; + + int line_number = 0; + std::string line; + while (std::getline(infile, line)) { + ++line_number; + char const *input = line.c_str(); + file_parser.skip_whitespace(input); + if (*input == '\0') { + continue; + } + + std::string command_name; + if (!file_parser.parse_word(command_name, input)) { + std::stringstream ss; + ss << "Parse error on line " << line_number << ": " << line << std::endl; + Console_history.push_back({ boxmon::message_severity::error, ss.str() }); + continue; + } + + const boxmon::boxmon_command *cmd = boxmon::boxmon_command::find(command_name.c_str()); + if (cmd == nullptr) { + std::stringstream ss; + ss << "Unknown command on line " << line_number << ": \"" << command_name << "\"." << std::endl; + Console_history.push_back({ boxmon::message_severity::error, ss.str() }); + continue; + } + + if (!cmd->run(input, file_parser, false)) { + std::stringstream ss; + ss << "Parse error on line " << line_number << " while running \"" << command_name << "\" with args: " << input << std::endl; + Console_history.push_back({ boxmon::message_severity::error, ss.str() }); + } + } + return true; +} + +bool boxmon_do_console_command(const std::string &line) +{ + char const *input = line.c_str(); + Console_parser.skip_whitespace(input); + if (*input == '\0') { + return true; + } + + std::string command_name; + if (!Console_parser.parse_word(command_name, input)) { + std::stringstream ss; + ss << "Parse error: " << line << std::endl; + Console_history.push_back({ boxmon::message_severity::error, ss.str() }); + return false; + } + + const boxmon::boxmon_command *cmd = boxmon::boxmon_command::find(command_name.c_str()); + if (cmd == nullptr) { + std::stringstream ss; + ss << "Unknown command \"" << command_name << "\"" << std::endl; + Console_history.push_back({ boxmon::message_severity::error, ss.str() }); + return false; + } + + if (!cmd->run(input, Console_parser, false)) { + std::stringstream ss; + ss << "Parse error while running \"" << command_name << "\" with args: " << input << std::endl; + Console_history.push_back({ boxmon::message_severity::error, ss.str() }); + return false; + } + return true; +} + +void boxmon_console_printf(char const *format, ...) +{ + if (Console_suppress_output) { + return; + } + + va_list arglist; + va_start(arglist, format); + + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), format, arglist); + Console_history.push_back({ boxmon::message_severity::output, buffer }); + + va_end(arglist); +} + +void boxmon_warning_printf(char const *format, ...) +{ + if (Console_suppress_warnings) { + return; + } + + va_list arglist; + va_start(arglist, format); + + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), format, arglist); + Console_history.push_back({ boxmon::message_severity::warning, buffer }); + + va_end(arglist); +} + +void boxmon_error_printf(char const *format, ...) +{ + if (Console_suppress_errors) { + return; + } + + va_list arglist; + va_start(arglist, format); + + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), format, arglist); + Console_history.push_back({ boxmon::message_severity::error, buffer }); + + va_end(arglist); +} + +const std::vector &boxmon_get_console_history() +{ + return Console_history; +} + +const std::vector &boxmon_get_command_history() +{ + return Command_history; +} + +void boxmon_clear_console_history() +{ + Console_history.clear(); +} \ No newline at end of file diff --git a/src/boxmon/boxmon.h b/src/boxmon/boxmon.h new file mode 100644 index 0000000..d7d8416 --- /dev/null +++ b/src/boxmon/boxmon.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +void boxmon_system_init(); +void boxmon_system_shutdown(); + +bool boxmon_load_file(const std::filesystem::path &path); +bool boxmon_do_console_command(const std::string &command); + +void boxmon_console_printf(char const *format, ...); +void boxmon_warning_printf(char const *format, ...); +void boxmon_error_printf(char const *format, ...); + +namespace boxmon +{ + enum message_severity { + output, + warning, + error + }; + + using console_line_type = std::tuple; +} // namespace boxmon + +const std::vector &boxmon_get_console_history(); +const std::vector &boxmon_get_command_history(); + +void boxmon_clear_console_history(); diff --git a/src/boxmon/command.cpp b/src/boxmon/command.cpp new file mode 100644 index 0000000..14dd519 --- /dev/null +++ b/src/boxmon/command.cpp @@ -0,0 +1,656 @@ +#include "command.h" + +#include +#include +#include + +#include "boxmon.h" +#include "parser.h" + +#include "cpu/fake6502.h" +#include "cpu/mnemonics.h" +#include "debugger.h" +#include "glue.h" +#include "memory.h" +#include "vera/vera_video.h" + +namespace boxmon +{ + boxmon_command::boxmon_command(char const *name, char const *description, std::function fn) + : m_name(name), + m_description(description), + m_run(fn) + { + auto &command_list = get_command_list(); + command_list.insert({ name, this }); + } + + std::strong_ordering boxmon_command::operator<=>(char const *name) const + { + return strcmp(m_name, name) <=> 0; + } + + std::strong_ordering boxmon_command::operator<=>(const boxmon_command &cmd) const + { + return strcmp(m_name, cmd.m_name) <=> 0; + } + + bool boxmon_command::run(char const *&input, boxmon::parser &parser, bool help) const + { + return m_run != nullptr ? m_run(input, parser, help) : false; + } + + char const *boxmon_command::get_name() const + { + return m_name; + } + + char const *boxmon_command::get_description() const + { + return m_description; + } + + const boxmon_command *boxmon_command::find(char const *name) + { + const auto &command_list = get_command_list(); + const auto icmd = command_list.find(name); + if (icmd != command_list.end()) { + return icmd->second; + } + return nullptr; + } + + void boxmon_command::for_each(std::function fn) + { + const auto &command_list = get_command_list(); + for (auto cmd : command_list) { + fn(cmd.second); + } + } + + void boxmon_command::for_each_partial(char const *name, std::function fn) + { + const auto &command_list = get_command_list(); + for (auto cmd : command_list) { + if (strstr(cmd.second->get_name(), name) != nullptr) { + fn(cmd.second); + } else if (strstr(cmd.second->get_description(), name) != nullptr) { + fn(cmd.second); + } + } + } + + std::map &boxmon_command::get_command_list() + { + static std::map command_list; + return command_list; + } + + boxmon_alias::boxmon_alias(char const *name, const boxmon_command &cmd) + : boxmon_command(name, cmd.get_description(), [this](const char *input, boxmon::parser &parser, bool help) { return m_cmd.run(input, parser, help); }), + m_cmd(cmd) + { + } +} // namespace boxmon + +#include "symbols.h" + +BOXMON_COMMAND(help, "help []") +{ + if (help) { + boxmon_console_printf("Print extended use information about a command."); + boxmon_console_printf("If no command is specified, help returns a list of all commands the console will accept."); + return true; + } + + std::string command; + if (parser.parse_word(command, input)) { + auto const *cmd = boxmon::boxmon_command::find(command.c_str()); + if (cmd == nullptr) { + boxmon_warning_printf("Could not find any command named \"%s\"", command.c_str()); + } else { + boxmon_console_printf("%s", cmd->get_description()); + const char *help_input = ""; + return cmd->run(help_input, parser, true); + } + } else { + std::string name; + if (parser.parse_word(name, input)) { + if (auto *cmd = boxmon::boxmon_command::find(name.c_str()); cmd != nullptr) { + boxmon_console_printf("%s: %s", cmd->get_name(), cmd->get_description()); + return true; + } + } + boxmon::boxmon_command::for_each([](const boxmon::boxmon_command *cmd) { + boxmon_console_printf("%s: %s", cmd->get_name(), cmd->get_description()); + }); + } + return true; +} + +BOXMON_COMMAND(eval, "eval ") +{ + if (help) { + boxmon_console_printf("Evaluates an expression and prints the result to the console."); + boxmon_console_printf("Intermediate values are stored as signed 32-bit integers. Memory reads from dereferencing are treated as unsigned 8-bit integers."); + boxmon_console_printf("Expressions support most C-style mathematical, comparison, boolean, and bitwise operators:"); + boxmon_console_printf("Math: +, -, *, /, %, ^^, ()"); + boxmon_console_printf("\t+: Addition. 2+3 returns 5."); + boxmon_console_printf("\t-: Subtraction and negation. 2-3 returns -1. -2 returns -2."); + boxmon_console_printf("\t*: Multiplication. 2*3 returns 6."); + boxmon_console_printf("\t/: Division. 10/2 returns 5."); + boxmon_console_printf("\t%: Modulo. 4%3 returns 1."); + boxmon_console_printf("\t^^: Exponentiation. 2^^3 returns 8."); + boxmon_console_printf("\t(): Parenthesis. (1+2)*3 returns 9."); + boxmon_console_printf("Compare: ==, !=, <, >, <=, >="); + boxmon_console_printf("\t==: Equality. 2==2 returns 1 (true), 2==3 returns 0 (false)."); + boxmon_console_printf("\t!=: Inequality. 2!=2 returns 0 (false), 2!=3 returns 1 (true)."); + boxmon_console_printf("\t<: Less than. 2<3 returns 1 (true), 3<2 returns 0 (false)."); + boxmon_console_printf("\t>: Greater than. 2>3 returns 0 (false), 3>2 returns 1 (true)."); + boxmon_console_printf("\t<=: Less than or equal to. 2<=2 returns 1, 2<=3 returns 1."); + boxmon_console_printf("\t>=: Greater than or equal to. 2>=2 returns 1, 3>=2 returns 1."); + boxmon_console_printf("Bool: &&, ||, !"); + boxmon_console_printf("\t&&: Boolean AND. 1 && 1 returns 1, 1 && 0 returns 0, 0 && 0 returns 0."); + boxmon_console_printf("\t||: Boolean OR. 1 || 1 returns 1, 1 || 0 returns 1, 0 || 0 returns 0."); + boxmon_console_printf("\t!: Boolean NOT. !1 returns 0, !0 returns 1."); + boxmon_console_printf("Bitwise: &, |, ~, ^, <<, >>"); + boxmon_console_printf("\t&: Bitwise AND. 3&1 returns 1, 2&1 returns 0."); + boxmon_console_printf("\t|: Bitwise OR. 3|1 returns 3, 2|1 returns 3."); + boxmon_console_printf("\t~: Bitwise NOT. ~1 returns -2, ~2 returns -3. (Additional reading left to the user: Two's complement signed integers.)"); + boxmon_console_printf("\t^: Bitwise XOR. 3^1 returns 2, 2^1 returns 3."); + boxmon_console_printf("\t<<: Left shift. 1<<2 returns 4."); + boxmon_console_printf("\t>>: Right shift. 4>>2 returns 1."); + boxmon_console_printf("Additionally, the symbol @ will treat the value to its right as a memory address and attempt to retrieve the value at that address, similar to the C-style * for pointer dereferencing."); + boxmon_console_printf("\t@: Dereferencing. @3 returns the value stored at $0003."); + boxmon_console_printf("C-style precedence rules should apply to each of these operators."); + boxmon_console_printf("Expressions may include integer values and symbol names. Symbol names are substituted as the address associated with the symbol."); + boxmon_console_printf("If the same symbol name is defined multiple times, the selection process is undefined."); + return true; + } + const boxmon::expression *expr; + if (parser.parse_expression(expr, input, boxmon::expression_parse_flags_must_consume_all)) { + boxmon_console_printf("%d", expr->evaluate()); + return true; + } + + return false; +} + +BOXMON_COMMAND(break, "break [load|store|exec] [address [address] [if ]]") +{ + if (help) { + boxmon_console_printf("Create a breakpoint, optionally with a conditional expression."); + boxmon_console_printf("\tload: Break if the CPU attempts to load data from this address."); + boxmon_console_printf("\tstore: Break if the CPU attempts to store data to this address."); + boxmon_console_printf("\texec: Break if the CPU attempts to execute an instruction from this address."); + boxmon_console_printf("\taddress: One or more addresses to set as breakpoints."); + boxmon_console_printf("\tcond_expr: Conditional expression following the same rules and syntax as \"eval\". If specified, the breakpoint will only pause execution if the conditional expression evaluates to a non-zero value."); + boxmon_console_printf("\t (In the case of boolean comparisons, \"true\" evaluates to 1, \"false\" evaluates to 0.)"); + return true; + } + uint8_t breakpoint_flags = 0; + for (int option; parser.parse_option(option, { "exec", "load", "store" }, input);) { + breakpoint_flags |= (1 << option); + } + if (breakpoint_flags == 0) { + breakpoint_flags = DEBUG6502_EXEC; + } + + std::list bps; + for (boxmon::address_type bp; parser.parse_address(bp, input);) { + bps.push_back(bp); + } + + if (int option; parser.parse_option(option, { "if" }, input)) { + if (const boxmon::expression *expr = nullptr; parser.parse_expression(expr, input, boxmon::expression_parse_flags_must_consume_all)) { + for (auto bp : bps) { + breakpoint_flags |= DEBUG6502_CONDITION; + debugger_add_breakpoint(std::get<0>(bp), std::get<1>(bp), breakpoint_flags); + debugger_set_condition(std::get<0>(bp), std::get<1>(bp), expr->get_string()); + } + } + } else { + for (auto bp : bps) { + debugger_add_breakpoint(std::get<0>(bp), std::get<1>(bp), breakpoint_flags); + } + } + + return true; +} + +BOXMON_ALIAS(br, break); + +BOXMON_COMMAND(add_label, "add_label