feat: Enhance ROM Loading with Validation and Disassembly Viewer Integration

- Added validation checks for ROM file existence and size constraints (minimum 32KB, maximum 8MB) during loading.
- Integrated a new disassembly viewer to track and display executed instructions, enhancing debugging capabilities.
- Updated CPU class to manage disassembly viewer instances, allowing for real-time instruction logging.
- Improved emulator's CMake configuration to include new source files related to the disassembly viewer.
- Enhanced pixel handling in the PPU to support BGRX format for better compatibility with SDL.
This commit is contained in:
scawful
2025-10-06 21:49:12 -04:00
parent 14eba4a765
commit 7f71fd9e80
9 changed files with 1262 additions and 483 deletions

View File

@@ -7,6 +7,7 @@ set(
app/emu/audio/internal/instructions.cc
app/emu/cpu/internal/addressing.cc
app/emu/cpu/internal/instructions.cc
app/emu/debug/disassembly_viewer.cc
app/emu/cpu/cpu.cc
app/emu/video/ppu.cc
app/emu/memory/dma.cc

View File

@@ -6,13 +6,29 @@
#include <sstream>
#include <vector>
#include "absl/strings/str_format.h"
#include "app/core/features.h"
#include "app/emu/cpu/internal/opcodes.h"
#include "app/emu/debug/disassembly_viewer.h"
#include "util/log.h"
namespace yaze {
namespace emu {
debug::DisassemblyViewer& Cpu::disassembly_viewer() {
if (disassembly_viewer_ == nullptr) {
disassembly_viewer_ = new debug::DisassemblyViewer();
}
return *disassembly_viewer_;
}
const debug::DisassemblyViewer& Cpu::disassembly_viewer() const {
if (disassembly_viewer_ == nullptr) {
const_cast<Cpu*>(this)->disassembly_viewer_ = new debug::DisassemblyViewer();
}
return *disassembly_viewer_;
}
void Cpu::Reset(bool hard) {
if (hard) {
A = 0;
@@ -1884,38 +1900,50 @@ void Cpu::ExecuteInstruction(uint8_t opcode) {
}
void Cpu::LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand,
bool immediate, bool accumulator_mode) {
bool immediate, bool accumulator_mode) {
if (core::FeatureFlags::get().kLogInstructions) {
// Build full 24-bit address
uint32_t full_address = (PB << 16) | PC;
// Extract operand bytes based on instruction size
std::vector<uint8_t> operand_bytes;
std::string operand_str;
if (operand) {
if (immediate) {
operand_str += "#";
}
if (accumulator_mode) {
// 8-bit operand
operand_bytes.push_back(operand & 0xFF);
operand_str += absl::StrFormat("$%02X", operand & 0xFF);
} else {
// 16-bit operand (little-endian)
operand_bytes.push_back(operand & 0xFF);
operand_bytes.push_back((operand >> 8) & 0xFF);
operand_str += absl::StrFormat("$%04X", operand);
}
}
// Get mnemonic
const std::string& mnemonic = opcode_to_mnemonic.at(opcode);
// Record to new disassembly viewer
disassembly_viewer().RecordInstruction(full_address, opcode, operand_bytes,
mnemonic, operand_str);
// Also maintain legacy log for compatibility
std::ostringstream oss;
oss << "$" << std::uppercase << std::setw(2) << std::setfill('0')
<< static_cast<int>(PB) << ":" << std::hex << PC << ": 0x"
<< std::setw(2) << std::setfill('0') << std::hex
<< static_cast<int>(opcode) << " " << opcode_to_mnemonic.at(opcode)
<< " ";
<< static_cast<int>(opcode) << " " << mnemonic << " " << operand_str;
// Log the operand.
std::string ops;
if (operand) {
if (immediate) {
ops += "#";
}
std::ostringstream oss_ops;
oss_ops << "$";
if (accumulator_mode) {
oss_ops << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(operand);
} else {
oss_ops << std::hex << std::setw(4) << std::setfill('0')
<< static_cast<int>(operand);
}
ops = oss_ops.str();
}
oss << ops << std::endl;
InstructionEntry entry(PC, opcode, ops, oss.str());
InstructionEntry entry(PC, opcode, operand_str, oss.str());
instruction_log_.push_back(entry);
// Also emit to the central logger for user/agent-controlled sinks.
// Also emit to the central logger for user/agent-controlled sinks
util::LogManager::instance().log(util::LogLevel::YAZE_DEBUG, "CPU",
oss.str());
} else {

View File

@@ -10,6 +10,11 @@
namespace yaze {
namespace emu {
// Forward declarations
namespace debug {
class DisassemblyViewer;
}
class InstructionEntry {
public:
// Constructor
@@ -47,7 +52,26 @@ class Cpu {
void Nmi() { nmi_wanted_ = true; }
std::vector<uint32_t> breakpoints_;
std::vector<InstructionEntry> instruction_log_;
std::vector<InstructionEntry> instruction_log_; // Legacy log for compatibility
// New disassembly viewer
debug::DisassemblyViewer& disassembly_viewer();
const debug::DisassemblyViewer& disassembly_viewer() const;
// Public register access for debugging and UI
uint16_t A = 0; // Accumulator
uint16_t X = 0; // X index register
uint16_t Y = 0; // Y index register
uint16_t D = 0; // Direct Page register
uint8_t DB = 0; // Data Bank register
uint8_t PB = 0; // Program Bank register
uint16_t PC = 0; // Program Counter
uint8_t status = 0b00110000; // Processor Status (P)
// Breakpoint management
void set_int_delay(bool delay) { int_delay_ = delay; }
debug::DisassemblyViewer* disassembly_viewer_ = nullptr;
// ======================================================
// Interrupt Vectors
@@ -62,17 +86,9 @@ class Cpu {
void DoInterrupt();
// ======================================================
// Registers
// Internal state
uint16_t A = 0; // Accumulator
uint16_t X = 0; // X index register
uint16_t Y = 0; // Y index register
uint16_t D = 0; // Direct Page register
uint8_t DB = 0; // Data Bank register
uint8_t PB = 0; // Program Bank register
uint16_t PC = 0; // Program Counter
uint8_t E = 1; // Emulation mode flag
uint8_t status = 0b00110000; // Processor Status (P)
// Mnemonic Value Binary Description
// N #$80 10000000 Negative
@@ -233,7 +249,6 @@ class Cpu {
}
}
void set_int_delay(bool delay) { int_delay_ = delay; }
// Addressing Modes

View File

@@ -0,0 +1,438 @@
#include "app/emu/debug/disassembly_viewer.h"
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <sstream>
#include "absl/strings/str_format.h"
#include "app/gui/style.h"
#include "imgui/imgui.h"
namespace yaze {
namespace emu {
namespace debug {
namespace {
// Color scheme for retro hacker aesthetic
constexpr ImVec4 kColorAddress(0.4f, 0.8f, 1.0f, 1.0f); // Cyan for addresses
constexpr ImVec4 kColorOpcode(0.8f, 0.8f, 0.8f, 1.0f); // Light gray for opcodes
constexpr ImVec4 kColorMnemonic(1.0f, 0.8f, 0.2f, 1.0f); // Gold for mnemonics
constexpr ImVec4 kColorOperand(0.6f, 1.0f, 0.6f, 1.0f); // Light green for operands
constexpr ImVec4 kColorComment(0.5f, 0.5f, 0.5f, 1.0f); // Gray for comments
constexpr ImVec4 kColorCurrentPC(1.0f, 0.3f, 0.3f, 1.0f); // Red for current PC
constexpr ImVec4 kColorBreakpoint(1.0f, 0.0f, 0.0f, 1.0f); // Bright red for breakpoints
constexpr ImVec4 kColorHotPath(1.0f, 0.6f, 0.0f, 1.0f); // Orange for hot paths
} // namespace
void DisassemblyViewer::RecordInstruction(uint32_t address, uint8_t opcode,
const std::vector<uint8_t>& operands,
const std::string& mnemonic,
const std::string& operand_str) {
auto it = instructions_.find(address);
if (it != instructions_.end()) {
// Instruction already recorded, just increment execution count
it->second.execution_count++;
} else {
// New instruction, add to map
DisassemblyEntry entry;
entry.address = address;
entry.opcode = opcode;
entry.operands = operands;
entry.mnemonic = mnemonic;
entry.operand_str = operand_str;
entry.size = 1 + operands.size();
entry.execution_count = 1;
entry.is_breakpoint = false;
entry.is_current_pc = false;
instructions_[address] = entry;
}
}
void DisassemblyViewer::Render(uint32_t current_pc,
const std::vector<uint32_t>& breakpoints) {
// Update current PC and breakpoint flags
for (auto& [addr, entry] : instructions_) {
entry.is_current_pc = (addr == current_pc);
entry.is_breakpoint = std::find(breakpoints.begin(), breakpoints.end(), addr)
!= breakpoints.end();
}
RenderToolbar();
RenderSearchBar();
RenderDisassemblyTable(current_pc, breakpoints);
}
void DisassemblyViewer::RenderToolbar() {
if (ImGui::BeginTable("##DisasmToolbar", 6, ImGuiTableFlags_None)) {
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_CLEAR_ALL " Clear")) {
Clear();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Clear all recorded instructions");
}
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_SAVE " Export")) {
// TODO: Open file dialog and export
ExportToFile("disassembly.asm");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Export disassembly to file");
}
ImGui::TableNextColumn();
if (ImGui::Checkbox("Auto-scroll", &auto_scroll_)) {
// Toggle auto-scroll
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Auto-scroll to current PC");
}
ImGui::TableNextColumn();
if (ImGui::Checkbox("Exec Count", &show_execution_counts_)) {
// Toggle execution count display
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Show execution counts");
}
ImGui::TableNextColumn();
if (ImGui::Checkbox("Hex Dump", &show_hex_dump_)) {
// Toggle hex dump display
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Show hex dump of instruction bytes");
}
ImGui::TableNextColumn();
ImGui::Text(ICON_MD_MEMORY " %zu instructions", instructions_.size());
ImGui::EndTable();
}
ImGui::Separator();
}
void DisassemblyViewer::RenderSearchBar() {
ImGui::PushItemWidth(-1.0f);
if (ImGui::InputTextWithHint("##DisasmSearch", ICON_MD_SEARCH " Search (address, mnemonic, operand)...",
search_filter_, IM_ARRAYSIZE(search_filter_))) {
// Search filter updated
}
ImGui::PopItemWidth();
}
void DisassemblyViewer::RenderDisassemblyTable(uint32_t current_pc,
const std::vector<uint32_t>& breakpoints) {
// Table flags for professional disassembly view
ImGuiTableFlags flags =
ImGuiTableFlags_Borders |
ImGuiTableFlags_RowBg |
ImGuiTableFlags_ScrollY |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Sortable |
ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Hideable;
// Calculate column count based on optional columns
int column_count = 4; // BP, Address, Mnemonic, Operand (always shown)
if (show_hex_dump_) column_count++;
if (show_execution_counts_) column_count++;
if (!ImGui::BeginTable("##DisasmTable", column_count, flags, ImVec2(0.0f, 0.0f))) {
return;
}
// Setup columns
ImGui::TableSetupColumn(ICON_MD_CIRCLE, ImGuiTableColumnFlags_WidthFixed, 25.0f); // Breakpoint indicator
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 80.0f);
if (show_hex_dump_) {
ImGui::TableSetupColumn("Hex", ImGuiTableColumnFlags_WidthFixed, 100.0f);
}
ImGui::TableSetupColumn("Mnemonic", ImGuiTableColumnFlags_WidthFixed, 80.0f);
ImGui::TableSetupColumn("Operand", ImGuiTableColumnFlags_WidthStretch);
if (show_execution_counts_) {
ImGui::TableSetupColumn(ICON_MD_TRENDING_UP " Count", ImGuiTableColumnFlags_WidthFixed, 80.0f);
}
ImGui::TableSetupScrollFreeze(0, 1); // Freeze header row
ImGui::TableHeadersRow();
// Render instructions
ImGuiListClipper clipper;
auto sorted_addrs = GetSortedAddresses();
clipper.Begin(sorted_addrs.size());
while (clipper.Step()) {
for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) {
uint32_t addr = sorted_addrs[row];
const auto& entry = instructions_[addr];
// Skip if doesn't pass filter
if (!PassesFilter(entry)) {
continue;
}
ImGui::TableNextRow();
// Highlight current PC row
if (entry.is_current_pc) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,
ImGui::GetColorU32(ImVec4(0.3f, 0.0f, 0.0f, 0.5f)));
}
// Column 0: Breakpoint indicator
ImGui::TableNextColumn();
if (entry.is_breakpoint) {
ImGui::TextColored(kColorBreakpoint, ICON_MD_STOP);
} else {
ImGui::TextDisabled(" ");
}
// Column 1: Address (clickable)
ImGui::TableNextColumn();
ImVec4 addr_color = GetAddressColor(entry, current_pc);
std::string addr_str = absl::StrFormat("$%02X:%04X",
(addr >> 16) & 0xFF, addr & 0xFFFF);
if (ImGui::Selectable(addr_str.c_str(), selected_address_ == addr,
ImGuiSelectableFlags_SpanAllColumns)) {
selected_address_ = addr;
}
// Context menu on right-click
if (ImGui::BeginPopupContextItem()) {
RenderContextMenu(addr);
ImGui::EndPopup();
}
// Column 2: Hex dump (optional)
if (show_hex_dump_) {
ImGui::TableNextColumn();
ImGui::TextColored(kColorOpcode, "%s", FormatHexDump(entry).c_str());
}
// Column 3: Mnemonic (clickable for documentation)
ImGui::TableNextColumn();
ImVec4 mnemonic_color = GetMnemonicColor(entry);
if (ImGui::Selectable(entry.mnemonic.c_str(), false)) {
// TODO: Open documentation for this mnemonic
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Click for instruction documentation");
}
// Column 4: Operand (clickable for jump-to-address)
ImGui::TableNextColumn();
ImGui::TextColored(kColorOperand, "%s", entry.operand_str.c_str());
// Column 5: Execution count (optional)
if (show_execution_counts_) {
ImGui::TableNextColumn();
// Color-code by execution frequency (hot path highlighting)
ImVec4 count_color = kColorComment;
if (entry.execution_count > 10000) {
count_color = kColorHotPath;
} else if (entry.execution_count > 1000) {
count_color = ImVec4(0.8f, 0.8f, 0.3f, 1.0f); // Yellow
}
ImGui::TextColored(count_color, "%llu", entry.execution_count);
}
}
}
// Auto-scroll to current PC
if (auto_scroll_ && scroll_to_address_ != current_pc) {
// Find row index of current PC
auto it = std::find(sorted_addrs.begin(), sorted_addrs.end(), current_pc);
if (it != sorted_addrs.end()) {
int row_index = std::distance(sorted_addrs.begin(), it);
ImGui::SetScrollY((row_index * ImGui::GetTextLineHeightWithSpacing()) -
(ImGui::GetWindowHeight() * 0.5f));
scroll_to_address_ = current_pc;
}
}
ImGui::EndTable();
}
void DisassemblyViewer::RenderContextMenu(uint32_t address) {
auto& entry = instructions_[address];
if (ImGui::MenuItem(ICON_MD_FLAG " Toggle Breakpoint")) {
// TODO: Implement breakpoint toggle callback
}
if (ImGui::MenuItem(ICON_MD_MY_LOCATION " Jump to Address")) {
JumpToAddress(address);
}
ImGui::Separator();
if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Copy Address")) {
ImGui::SetClipboardText(absl::StrFormat("$%06X", address).c_str());
}
if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Copy Instruction")) {
std::string instr = absl::StrFormat("%s %s", entry.mnemonic.c_str(),
entry.operand_str.c_str());
ImGui::SetClipboardText(instr.c_str());
}
ImGui::Separator();
if (ImGui::MenuItem(ICON_MD_INFO " Show Info")) {
// TODO: Show detailed instruction info
}
}
ImVec4 DisassemblyViewer::GetAddressColor(const DisassemblyEntry& entry,
uint32_t current_pc) const {
if (entry.is_current_pc) {
return kColorCurrentPC;
}
if (entry.is_breakpoint) {
return kColorBreakpoint;
}
return kColorAddress;
}
ImVec4 DisassemblyViewer::GetMnemonicColor(const DisassemblyEntry& entry) const {
// Color-code by instruction type
const std::string& mnemonic = entry.mnemonic;
// Branches and jumps
if (mnemonic.find('B') == 0 || mnemonic == "JMP" || mnemonic == "JSR" ||
mnemonic == "RTL" || mnemonic == "RTS" || mnemonic == "RTI") {
return ImVec4(0.8f, 0.4f, 1.0f, 1.0f); // Purple for control flow
}
// Loads
if (mnemonic.find("LD") == 0) {
return ImVec4(0.4f, 1.0f, 0.4f, 1.0f); // Green for loads
}
// Stores
if (mnemonic.find("ST") == 0) {
return ImVec4(1.0f, 0.6f, 0.4f, 1.0f); // Orange for stores
}
return kColorMnemonic;
}
std::string DisassemblyViewer::FormatHexDump(const DisassemblyEntry& entry) const {
std::stringstream ss;
ss << std::hex << std::uppercase << std::setfill('0');
// Opcode
ss << std::setw(2) << static_cast<int>(entry.opcode);
// Operands
for (const auto& operand_byte : entry.operands) {
ss << " " << std::setw(2) << static_cast<int>(operand_byte);
}
// Pad to consistent width (3 bytes max)
for (size_t i = entry.operands.size(); i < 2; i++) {
ss << " ";
}
return ss.str();
}
bool DisassemblyViewer::PassesFilter(const DisassemblyEntry& entry) const {
if (search_filter_[0] == '\0') {
return true; // No filter active
}
std::string filter_lower(search_filter_);
std::transform(filter_lower.begin(), filter_lower.end(),
filter_lower.begin(), ::tolower);
// Check address
std::string addr_str = absl::StrFormat("%06x", entry.address);
if (addr_str.find(filter_lower) != std::string::npos) {
return true;
}
// Check mnemonic
std::string mnemonic_lower = entry.mnemonic;
std::transform(mnemonic_lower.begin(), mnemonic_lower.end(),
mnemonic_lower.begin(), ::tolower);
if (mnemonic_lower.find(filter_lower) != std::string::npos) {
return true;
}
// Check operand
std::string operand_lower = entry.operand_str;
std::transform(operand_lower.begin(), operand_lower.end(),
operand_lower.begin(), ::tolower);
if (operand_lower.find(filter_lower) != std::string::npos) {
return true;
}
return false;
}
void DisassemblyViewer::Clear() {
instructions_.clear();
selected_address_ = 0;
scroll_to_address_ = 0;
}
bool DisassemblyViewer::ExportToFile(const std::string& filepath) const {
std::ofstream out(filepath);
if (!out.is_open()) {
return false;
}
out << "; YAZE Disassembly Export\n";
out << "; Total instructions: " << instructions_.size() << "\n";
out << "; Generated: " << __DATE__ << " " << __TIME__ << "\n\n";
auto sorted_addrs = GetSortedAddresses();
for (uint32_t addr : sorted_addrs) {
const auto& entry = instructions_.at(addr);
out << absl::StrFormat("$%02X:%04X: %-8s %-6s %-20s ; exec=%llu\n",
(addr >> 16) & 0xFF,
addr & 0xFFFF,
FormatHexDump(entry).c_str(),
entry.mnemonic.c_str(),
entry.operand_str.c_str(),
entry.execution_count);
}
out.close();
return true;
}
void DisassemblyViewer::JumpToAddress(uint32_t address) {
selected_address_ = address;
scroll_to_address_ = 0; // Force scroll update
auto_scroll_ = false; // Disable auto-scroll temporarily
}
std::vector<uint32_t> DisassemblyViewer::GetSortedAddresses() const {
std::vector<uint32_t> addrs;
addrs.reserve(instructions_.size());
for (const auto& [addr, _] : instructions_) {
addrs.push_back(addr);
}
std::sort(addrs.begin(), addrs.end());
return addrs;
}
} // namespace debug
} // namespace emu
} // namespace yaze

View File

@@ -0,0 +1,146 @@
#ifndef YAZE_APP_EMU_DEBUG_DISASSEMBLY_VIEWER_H_
#define YAZE_APP_EMU_DEBUG_DISASSEMBLY_VIEWER_H_
#include <cstdint>
#include <map>
#include <string>
#include <vector>
#include "app/emu/cpu/cpu.h"
#include "app/gfx/bitmap.h"
#include "app/gui/icons.h"
#include "imgui/imgui.h"
namespace yaze {
namespace emu {
namespace debug {
/**
* @brief Represents a single disassembled instruction with metadata
*/
struct DisassemblyEntry {
uint32_t address; // Full 24-bit address (bank:offset)
uint8_t opcode; // The opcode byte
std::vector<uint8_t> operands; // Operand bytes (0-2 bytes)
std::string mnemonic; // Instruction mnemonic (e.g., "LDA", "STA")
std::string operand_str; // Formatted operand string (e.g., "#$00", "($10),Y")
uint8_t size; // Total instruction size in bytes
uint64_t execution_count; // How many times this instruction was executed
bool is_breakpoint; // Whether a breakpoint is set at this address
bool is_current_pc; // Whether this is the current PC location
DisassemblyEntry()
: address(0), opcode(0), size(1), execution_count(0),
is_breakpoint(false), is_current_pc(false) {}
};
/**
* @brief Advanced disassembly viewer with sparse storage and interactive features
*
* This viewer provides a professional disassembly interface similar to modern
* debuggers and ROM hacking tools. Features include:
* - Sparse address-based storage (only stores executed instructions)
* - Optimized ImGui table rendering with virtual scrolling
* - Clickable addresses, opcodes, and operands
* - Context menus for setting breakpoints, jumping to addresses, etc.
* - Highlighting of current PC, breakpoints, and hot paths
* - Search and filter capabilities
* - Export to assembly file
*/
class DisassemblyViewer {
public:
DisassemblyViewer() = default;
~DisassemblyViewer() = default;
/**
* @brief Record an instruction execution
* @param address Full 24-bit address
* @param opcode The opcode byte
* @param operands Vector of operand bytes
* @param mnemonic Instruction mnemonic
* @param operand_str Formatted operand string
*/
void RecordInstruction(uint32_t address, uint8_t opcode,
const std::vector<uint8_t>& operands,
const std::string& mnemonic,
const std::string& operand_str);
/**
* @brief Render the disassembly viewer UI
* @param current_pc Current program counter (24-bit)
* @param breakpoints List of breakpoint addresses
*/
void Render(uint32_t current_pc, const std::vector<uint32_t>& breakpoints);
/**
* @brief Clear all recorded instructions
*/
void Clear();
/**
* @brief Get the number of unique instructions recorded
*/
size_t GetInstructionCount() const { return instructions_.size(); }
/**
* @brief Export disassembly to file
* @param filepath Path to output file
* @return true if successful
*/
bool ExportToFile(const std::string& filepath) const;
/**
* @brief Jump to a specific address in the viewer
* @param address Address to jump to
*/
void JumpToAddress(uint32_t address);
/**
* @brief Set whether to auto-scroll to current PC
*/
void SetAutoScroll(bool enabled) { auto_scroll_ = enabled; }
/**
* @brief Get sorted list of addresses for rendering
*/
std::vector<uint32_t> GetSortedAddresses() const;
/**
* @brief Check if the disassembly viewer is available
*/
bool IsAvailable() const { return !instructions_.empty(); }
private:
// Sparse storage: only store executed instructions
std::map<uint32_t, DisassemblyEntry> instructions_;
// UI state
char search_filter_[256] = "";
uint32_t selected_address_ = 0;
uint32_t scroll_to_address_ = 0;
bool auto_scroll_ = true;
bool show_execution_counts_ = true;
bool show_hex_dump_ = true;
// Rendering helpers
void RenderToolbar();
void RenderDisassemblyTable(uint32_t current_pc,
const std::vector<uint32_t>& breakpoints);
void RenderContextMenu(uint32_t address);
void RenderSearchBar();
// Formatting helpers
ImVec4 GetAddressColor(const DisassemblyEntry& entry, uint32_t current_pc) const;
ImVec4 GetMnemonicColor(const DisassemblyEntry& entry) const;
std::string FormatHexDump(const DisassemblyEntry& entry) const;
// Filter helper
bool PassesFilter(const DisassemblyEntry& entry) const;
};
} // namespace debug
} // namespace emu
} // namespace yaze
#endif // YAZE_APP_EMU_DEBUG_DISASSEMBLY_VIEWER_H_

File diff suppressed because it is too large Load Diff

View File

@@ -207,18 +207,30 @@ void Ppu::HandlePixel(int x, int y) {
}
}
int row = (y - 1) + (even_frame ? 0 : 239);
// SDL_PIXELFORMAT_ARGB8888 with pixelOutputFormat=0 (BGRX)
// Memory layout: [B][G][R][A] at offsets 0,1,2,3 respectively
// Convert 5-bit SNES color (0-31) to 8-bit (0-255) via (val << 3) | (val >> 2)
// Two pixels per X position for hi-res support:
// pixel1 at x*8 + 0..3, pixel2 at x*8 + 4..7
// First pixel (hi-res/main screen)
pixelBuffer[row * 2048 + x * 8 + 0 + pixelOutputFormat] =
((b2 << 3) | (b2 >> 2)) * brightness / 15;
((b2 << 3) | (b2 >> 2)) * brightness / 15; // Blue channel
pixelBuffer[row * 2048 + x * 8 + 1 + pixelOutputFormat] =
((g2 << 3) | (g2 >> 2)) * brightness / 15;
((g2 << 3) | (g2 >> 2)) * brightness / 15; // Green channel
pixelBuffer[row * 2048 + x * 8 + 2 + pixelOutputFormat] =
((r2 << 3) | (r2 >> 2)) * brightness / 15;
((r2 << 3) | (r2 >> 2)) * brightness / 15; // Red channel
pixelBuffer[row * 2048 + x * 8 + 3 + pixelOutputFormat] = 0xFF; // Alpha (opaque)
// Second pixel (lo-res/subscreen)
pixelBuffer[row * 2048 + x * 8 + 4 + pixelOutputFormat] =
((b << 3) | (b >> 2)) * brightness / 15;
((b << 3) | (b >> 2)) * brightness / 15; // Blue channel
pixelBuffer[row * 2048 + x * 8 + 5 + pixelOutputFormat] =
((g << 3) | (g >> 2)) * brightness / 15;
((g << 3) | (g >> 2)) * brightness / 15; // Green channel
pixelBuffer[row * 2048 + x * 8 + 6 + pixelOutputFormat] =
((r << 3) | (r >> 2)) * brightness / 15;
((r << 3) | (r >> 2)) * brightness / 15; // Red channel
pixelBuffer[row * 2048 + x * 8 + 7 + pixelOutputFormat] = 0xFF; // Alpha (opaque)
}
int Ppu::GetPixel(int x, int y, bool subscreen, int* r, int* g, int* b) {

View File

@@ -258,8 +258,9 @@ class Ppu {
// Initialize the frame buffer
void Init() {
frame_buffer_.resize(256 * 240, 0);
// Set to XBGR format (1) for compatibility with SDL_PIXELFORMAT_ARGB8888
pixelOutputFormat = 1;
// Set to BGRX format (0) for compatibility with SDL_PIXELFORMAT_ARGB8888
// Format 0 = BGRX: [B][G][R][A] byte order in memory (little-endian)
pixelOutputFormat = 0;
}
void Reset();

View File

@@ -279,6 +279,13 @@ absl::Status Rom::LoadFromFile(const std::string &filename,
return absl::InvalidArgumentError(
"Could not load ROM: parameter `filename` is empty.");
}
// Validate file exists before proceeding
if (!std::filesystem::exists(filename)) {
return absl::NotFoundError(
absl::StrCat("ROM file does not exist: ", filename));
}
filename_ = std::filesystem::absolute(filename).string();
short_name_ = filename_.substr(filename_.find_last_of("/\\") + 1);
@@ -288,9 +295,19 @@ absl::Status Rom::LoadFromFile(const std::string &filename,
absl::StrCat("Could not open ROM file: ", filename_));
}
// Get file size and resize rom_data_
// Get file size and validate
try {
size_ = std::filesystem::file_size(filename_);
// Validate ROM size (minimum 32KB, maximum 8MB for expanded ROMs)
if (size_ < 32768) {
return absl::InvalidArgumentError(
absl::StrFormat("ROM file too small (%zu bytes), minimum is 32KB", size_));
}
if (size_ > 8 * 1024 * 1024) {
return absl::InvalidArgumentError(
absl::StrFormat("ROM file too large (%zu bytes), maximum is 8MB", size_));
}
} catch (const std::filesystem::filesystem_error &e) {
// Try to get the file size from the open file stream
file.seekg(0, std::ios::end);
@@ -299,11 +316,30 @@ absl::Status Rom::LoadFromFile(const std::string &filename,
"Could not get file size: ", filename_, " - ", e.what()));
}
size_ = file.tellg();
// Validate size from stream
if (size_ < 32768 || size_ > 8 * 1024 * 1024) {
return absl::InvalidArgumentError(
absl::StrFormat("Invalid ROM size: %zu bytes", size_));
}
}
rom_data_.resize(size_);
// Read file into rom_data_
file.read(reinterpret_cast<char *>(rom_data_.data()), size_);
// Allocate and read ROM data
try {
rom_data_.resize(size_);
file.seekg(0, std::ios::beg);
file.read(reinterpret_cast<char *>(rom_data_.data()), size_);
if (!file) {
return absl::InternalError(
absl::StrFormat("Failed to read ROM data, read %zu of %zu bytes",
file.gcount(), size_));
}
} catch (const std::bad_alloc& e) {
return absl::ResourceExhaustedError(
absl::StrFormat("Failed to allocate memory for ROM (%zu bytes)", size_));
}
file.close();
if (!options.load_zelda3_content) {