backend-infra-engineer: Pre-0.2.2 snapshot (2023)
This commit is contained in:
36
src/app/CMakeLists.txt
Normal file
36
src/app/CMakeLists.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
add_executable(
|
||||
yaze
|
||||
app/yaze.cc
|
||||
app/rom.cc
|
||||
${YAZE_APP_EMU_SRC}
|
||||
${YAZE_APP_CORE_SRC}
|
||||
${YAZE_APP_EDITOR_SRC}
|
||||
${YAZE_APP_GFX_SRC}
|
||||
${YAZE_APP_ZELDA3_SRC}
|
||||
${YAZE_GUI_SRC}
|
||||
${IMGUI_SRC}
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
yaze PUBLIC
|
||||
lib/
|
||||
app/
|
||||
lib/SDL_mixer/include/
|
||||
${CMAKE_SOURCE_DIR}/src/
|
||||
${PNG_INCLUDE_DIRS}
|
||||
${SDL2_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
yaze PUBLIC
|
||||
${ABSL_TARGETS}
|
||||
${SDL_TARGETS}
|
||||
${PNG_LIBRARIES}
|
||||
${CMAKE_DL_LIBS}
|
||||
SDL2_mixer
|
||||
ImGui
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY})
|
||||
endif()
|
||||
@@ -1,20 +1,40 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
unsigned int SnesToPc(unsigned int addr) {
|
||||
std::shared_ptr<ExperimentFlags::Flags> ExperimentFlags::flags_;
|
||||
|
||||
uint32_t SnesToPc(uint32_t addr) {
|
||||
if (addr >= 0x808000) {
|
||||
addr -= 0x808000;
|
||||
}
|
||||
unsigned int temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
|
||||
uint32_t temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
|
||||
return (temp + 0x0);
|
||||
}
|
||||
|
||||
uint32_t PcToSnes(uint32_t addr) {
|
||||
if (addr >= 0x400000) return -1;
|
||||
addr = ((addr << 1) & 0x7F0000) | (addr & 0x7FFF) | 0x8000;
|
||||
return addr;
|
||||
}
|
||||
|
||||
uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr) {
|
||||
uint32_t result = 0;
|
||||
result = (bank << 16) | addr;
|
||||
return result;
|
||||
}
|
||||
|
||||
int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3) {
|
||||
return (addr1 << 16) | (addr2 << 8) | addr3;
|
||||
}
|
||||
@@ -58,6 +78,76 @@ bool StringReplace(std::string &str, const std::string &from,
|
||||
return true;
|
||||
}
|
||||
|
||||
void stle(uint8_t *const p_arr, size_t const p_index, unsigned const p_val) {
|
||||
uint8_t v = (p_val >> (8 * p_index)) & 0xff;
|
||||
|
||||
p_arr[p_index] = v;
|
||||
}
|
||||
|
||||
void stle0(uint8_t *const p_arr, unsigned const p_val) {
|
||||
stle(p_arr, 0, p_val);
|
||||
}
|
||||
|
||||
void stle1(uint8_t *const p_arr, unsigned const p_val) {
|
||||
stle(p_arr, 1, p_val);
|
||||
}
|
||||
|
||||
void stle2(uint8_t *const p_arr, unsigned const p_val) {
|
||||
stle(p_arr, 2, p_val);
|
||||
}
|
||||
|
||||
void stle3(uint8_t *const p_arr, unsigned const p_val) {
|
||||
stle(p_arr, 3, p_val);
|
||||
}
|
||||
void stle16b(uint8_t *const p_arr, uint16_t const p_val) {
|
||||
stle0(p_arr, p_val);
|
||||
stle1(p_arr, p_val);
|
||||
}
|
||||
// "Store little endian 16-bit value using a byte pointer, offset by an
|
||||
// index before dereferencing"
|
||||
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
|
||||
uint16_t const p_val) {
|
||||
stle16b(p_arr + (p_index * 2), p_val);
|
||||
}
|
||||
// "load little endian value at the given byte offset and shift to get its
|
||||
// value relative to the base offset (powers of 256, essentially)"
|
||||
unsigned ldle(uint8_t const *const p_arr, unsigned const p_index) {
|
||||
uint32_t v = p_arr[p_index];
|
||||
|
||||
v <<= (8 * p_index);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
// Helper function to get the first byte in a little endian number
|
||||
uint32_t ldle0(uint8_t const *const p_arr) { return ldle(p_arr, 0); }
|
||||
|
||||
// Helper function to get the second byte in a little endian number
|
||||
uint32_t ldle1(uint8_t const *const p_arr) { return ldle(p_arr, 1); }
|
||||
|
||||
// Helper function to get the third byte in a little endian number
|
||||
uint32_t ldle2(uint8_t const *const p_arr) { return ldle(p_arr, 2); }
|
||||
|
||||
// Helper function to get the third byte in a little endian number
|
||||
uint32_t ldle3(uint8_t const *const p_arr) { return ldle(p_arr, 3); }
|
||||
// Load little endian halfword (16-bit) dereferenced from
|
||||
uint16_t ldle16b(uint8_t const *const p_arr) {
|
||||
uint16_t v = 0;
|
||||
|
||||
v |= (ldle0(p_arr) | ldle1(p_arr));
|
||||
|
||||
return v;
|
||||
}
|
||||
// Load little endian halfword (16-bit) dereferenced from an arrays of bytes.
|
||||
// This version provides an index that will be multiplied by 2 and added to the
|
||||
// base address.
|
||||
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) {
|
||||
return ldle16b(p_arr + (2 * p_index));
|
||||
}
|
||||
|
||||
// Initialize the static member
|
||||
std::stack<ImGuiID> ImGuiIdIssuer::idStack;
|
||||
|
||||
} // namespace core
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
@@ -1,19 +1,235 @@
|
||||
#ifndef YAZE_CORE_COMMON_H
|
||||
#define YAZE_CORE_COMMON_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
unsigned int SnesToPc(unsigned int addr);
|
||||
class ExperimentFlags {
|
||||
public:
|
||||
struct Flags {
|
||||
// Load and render overworld sprites to the screen. Unstable.
|
||||
bool kDrawOverworldSprites = false;
|
||||
|
||||
// Bitmap manager abstraction to manage graphics bin of ROM.
|
||||
bool kUseBitmapManager = true;
|
||||
|
||||
// Log instructions to the GUI debugger.
|
||||
bool kLogInstructions = false;
|
||||
|
||||
// Flag to enable ImGui input config flags. Currently is
|
||||
// handled manually by controller class but should be
|
||||
// ported away from that eventually.
|
||||
bool kUseNewImGuiInput = false;
|
||||
|
||||
// Flag to enable the saving of all palettes to the ROM.
|
||||
bool kSaveAllPalettes = false;
|
||||
|
||||
// Flag to enable the change queue, which could have any anonymous
|
||||
// save routine for the ROM. In practice, just the overworld tilemap
|
||||
// and tile32 save.
|
||||
bool kSaveWithChangeQueue = false;
|
||||
|
||||
// Attempt to run the dungeon room draw routine when opening a room.
|
||||
bool kDrawDungeonRoomGraphics = true;
|
||||
|
||||
// Use the new platform specific file dialog wrappers.
|
||||
bool kNewFileDialogWrapper = true;
|
||||
|
||||
// Platform specific loading of fonts from the system. Currently
|
||||
// only supports macOS.
|
||||
bool kLoadSystemFonts = true;
|
||||
|
||||
bool kLoadTexturesAsStreaming = false;
|
||||
};
|
||||
|
||||
ExperimentFlags() = default;
|
||||
virtual ~ExperimentFlags() = default;
|
||||
auto flags() const {
|
||||
if (!flags_) {
|
||||
flags_ = std::make_shared<Flags>();
|
||||
}
|
||||
Flags *flags = flags_.get();
|
||||
return flags;
|
||||
}
|
||||
Flags *mutable_flags() {
|
||||
if (!flags_) {
|
||||
flags_ = std::make_shared<Flags>();
|
||||
}
|
||||
return flags_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
static std::shared_ptr<Flags> flags_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class NotifyValue {
|
||||
public:
|
||||
NotifyValue() : value_(), modified_(false), temp_value_() {}
|
||||
NotifyValue(const T &value)
|
||||
: value_(value), modified_(false), temp_value_() {}
|
||||
|
||||
void set(const T &value) {
|
||||
value_ = value;
|
||||
modified_ = true;
|
||||
}
|
||||
|
||||
const T &get() {
|
||||
modified_ = false;
|
||||
return value_;
|
||||
}
|
||||
|
||||
T &mutable_get() {
|
||||
modified_ = false;
|
||||
temp_value_ = value_;
|
||||
return temp_value_;
|
||||
}
|
||||
|
||||
void apply_changes() {
|
||||
if (temp_value_ != value_) {
|
||||
value_ = temp_value_;
|
||||
modified_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void operator=(const T &value) { set(value); }
|
||||
operator T() { return get(); }
|
||||
|
||||
bool modified() const { return modified_; }
|
||||
|
||||
private:
|
||||
T value_;
|
||||
bool modified_;
|
||||
T temp_value_;
|
||||
};
|
||||
|
||||
struct TaskCheckpoint {
|
||||
int task_index = 0;
|
||||
bool complete = false;
|
||||
// You can add more internal data or state-related variables here as needed
|
||||
};
|
||||
|
||||
class TaskTimer {
|
||||
public:
|
||||
// Starts the timer
|
||||
void StartTimer() { start_time_ = std::chrono::steady_clock::now(); }
|
||||
|
||||
// Checks if the task should finish based on the given timeout in seconds
|
||||
bool ShouldFinishTask(int timeout_seconds) {
|
||||
auto current_time = std::chrono::steady_clock::now();
|
||||
auto elapsed_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
current_time - start_time_);
|
||||
return elapsed_time.count() >= timeout_seconds;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::steady_clock::time_point start_time_;
|
||||
};
|
||||
|
||||
template <typename TFunc>
|
||||
class TaskManager {
|
||||
public:
|
||||
TaskManager() = default;
|
||||
~TaskManager() = default;
|
||||
|
||||
TaskManager(int totalTasks, int timeoutSeconds)
|
||||
: total_tasks_(totalTasks),
|
||||
timeout_seconds_(timeoutSeconds),
|
||||
task_index_(0),
|
||||
task_complete_(false) {}
|
||||
|
||||
void ExecuteTasks(const TFunc &taskFunc) {
|
||||
if (task_complete_) {
|
||||
return;
|
||||
}
|
||||
|
||||
StartTimer();
|
||||
|
||||
for (; task_index_ < total_tasks_; ++task_index_) {
|
||||
taskFunc(task_index_);
|
||||
|
||||
if (ShouldFinishTask()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (task_index_ == total_tasks_) {
|
||||
task_complete_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsTaskComplete() const { return task_complete_; }
|
||||
void SetTimeout(int timeout) { timeout_seconds_ = timeout; }
|
||||
|
||||
private:
|
||||
int total_tasks_;
|
||||
int timeout_seconds_;
|
||||
int task_index_;
|
||||
bool task_complete_;
|
||||
std::chrono::steady_clock::time_point start_time_;
|
||||
|
||||
void StartTimer() { start_time_ = std::chrono::steady_clock::now(); }
|
||||
|
||||
bool ShouldFinishTask() {
|
||||
auto current_time = std::chrono::steady_clock::now();
|
||||
auto elapsed_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
current_time - start_time_);
|
||||
return elapsed_time.count() >= timeout_seconds_;
|
||||
}
|
||||
};
|
||||
|
||||
class ImGuiIdIssuer {
|
||||
private:
|
||||
static std::stack<ImGuiID> idStack;
|
||||
|
||||
public:
|
||||
// Generate and push a new ID onto the stack
|
||||
static ImGuiID GetNewID() {
|
||||
static int counter = 1; // Start from 1 to ensure uniqueness
|
||||
ImGuiID child_id = ImGui::GetID((void *)(intptr_t)counter++);
|
||||
idStack.push(child_id);
|
||||
return child_id;
|
||||
}
|
||||
|
||||
// Pop all IDs from the stack (can be called explicitly or upon program exit)
|
||||
static void Cleanup() {
|
||||
while (!idStack.empty()) {
|
||||
idStack.pop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t SnesToPc(uint32_t addr);
|
||||
uint32_t PcToSnes(uint32_t addr);
|
||||
|
||||
uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr);
|
||||
|
||||
int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3);
|
||||
int HexToDec(char *input, int length);
|
||||
|
||||
bool StringReplace(std::string &str, const std::string &from,
|
||||
const std::string &to);
|
||||
|
||||
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
|
||||
uint16_t const p_val);
|
||||
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
|
||||
|
||||
void stle16b(uint8_t *const p_arr, uint16_t const p_val);
|
||||
void stle32b(uint8_t *const p_arr, uint32_t const p_val);
|
||||
|
||||
void stle32b_i(uint8_t *const p_arr, size_t const p_index,
|
||||
uint32_t const p_val);
|
||||
|
||||
} // namespace core
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
|
||||
#define BASIC_BUTTON(w) if (ImGui::Button(w))
|
||||
|
||||
#define TAB_BAR(w) if (ImGui::BeginTabBar(w)) {
|
||||
@@ -33,6 +35,22 @@
|
||||
ImGui::TableNextColumn(); \
|
||||
ImGui::Text(w);
|
||||
|
||||
#define BEGIN_TABLE(l, n, f) if (ImGui::BeginTable(l, n, f, ImVec2(0, 0))) {
|
||||
#define SETUP_COLUMN(l) ImGui::TableSetupColumn(l);
|
||||
|
||||
#define TABLE_HEADERS() \
|
||||
ImGui::TableHeadersRow(); \
|
||||
ImGui::TableNextRow();
|
||||
|
||||
#define NEXT_COLUMN() ImGui::TableNextColumn();
|
||||
|
||||
#define END_TABLE() \
|
||||
ImGui::EndTable(); \
|
||||
}
|
||||
|
||||
#define HOVER_HINT(string) \
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip(string);
|
||||
|
||||
#define PRINT_IF_ERROR(expression) \
|
||||
{ \
|
||||
auto error = expression; \
|
||||
@@ -41,6 +59,15 @@
|
||||
} \
|
||||
}
|
||||
|
||||
#define EXIT_IF_ERROR(expression) \
|
||||
{ \
|
||||
auto error = expression; \
|
||||
if (!error.ok()) { \
|
||||
std::cout << error.ToString() << std::endl; \
|
||||
return EXIT_FAILURE; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define RETURN_IF_ERROR(expression) \
|
||||
{ \
|
||||
auto error = expression; \
|
||||
@@ -60,11 +87,37 @@
|
||||
} \
|
||||
type_variable_name = std::move(*error_or_value);
|
||||
|
||||
#define ASSIGN_OR_LOG_ERROR(type_variable_name, expression) \
|
||||
ASSIGN_OR_LOG_ERROR_IMPL(APPEND_NUMBER(error_or_value, __LINE__), \
|
||||
type_variable_name, expression)
|
||||
|
||||
#define ASSIGN_OR_LOG_ERROR_IMPL(error_or_value, type_variable_name, \
|
||||
expression) \
|
||||
auto error_or_value = expression; \
|
||||
if (!error_or_value.ok()) { \
|
||||
std::cout << error_or_value.status().ToString() << std::endl; \
|
||||
} \
|
||||
type_variable_name = std::move(*error_or_value);
|
||||
|
||||
#define APPEND_NUMBER(expression, number) \
|
||||
APPEND_NUMBER_INNER(expression, number)
|
||||
|
||||
#define APPEND_NUMBER_INNER(expression, number) expression##number
|
||||
|
||||
#define TEXT_WITH_SEPARATOR(text) \
|
||||
ImGui::Text(text); \
|
||||
ImGui::Separator();
|
||||
|
||||
#define TABLE_BORDERS_RESIZABLE \
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable
|
||||
|
||||
#define CLEAR_AND_RETURN_STATUS(status) \
|
||||
if (!status.ok()) { \
|
||||
auto temp = status; \
|
||||
status = absl::OkStatus(); \
|
||||
return temp; \
|
||||
}
|
||||
|
||||
using ushort = unsigned short;
|
||||
using uint = unsigned int;
|
||||
using uchar = unsigned char;
|
||||
@@ -82,39 +135,7 @@ namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
// ============================================================================
|
||||
// Window Variables
|
||||
// ============================================================================
|
||||
|
||||
constexpr int kScreenWidth = 1200;
|
||||
constexpr int kScreenHeight = 800;
|
||||
|
||||
// ============================================================================
|
||||
// 65816 LanguageDefinition
|
||||
// ============================================================================
|
||||
|
||||
static const char *const kKeywords[] = {
|
||||
"ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI", "BNE",
|
||||
"BPL", "BRA", "BRL", "BVC", "BVS", "CLC", "CLD", "CLI", "CLV",
|
||||
"CMP", "CPX", "CPY", "DEC", "DEX", "DEY", "EOR", "INC", "INX",
|
||||
"INY", "JMP", "JSR", "JSL", "LDA", "LDX", "LDY", "LSR", "MVN",
|
||||
"NOP", "ORA", "PEA", "PER", "PHA", "PHB", "PHD", "PHP", "PHX",
|
||||
"PHY", "PLA", "PLB", "PLD", "PLP", "PLX", "PLY", "REP", "ROL",
|
||||
"ROR", "RTI", "RTL", "RTS", "SBC", "SEC", "SEI", "SEP", "STA",
|
||||
"STP", "STX", "STY", "STZ", "TAX", "TAY", "TCD", "TCS", "TDC",
|
||||
"TRB", "TSB", "TSC", "TSX", "TXA", "TXS", "TXY", "TYA", "TYX",
|
||||
"WAI", "WDM", "XBA", "XCE", "ORG", "LOROM", "HIROM", "NAMESPACE", "DB"};
|
||||
|
||||
static const char *const kIdentifiers[] = {
|
||||
"abort", "abs", "acos", "asin", "atan", "atexit",
|
||||
"atof", "atoi", "atol", "ceil", "clock", "cosh",
|
||||
"ctime", "div", "exit", "fabs", "floor", "fmod",
|
||||
"getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph",
|
||||
"ispunct", "isspace", "isupper", "kbhit", "log10", "log2",
|
||||
"log", "memcmp", "modf", "pow", "putchar", "putenv",
|
||||
"puts", "rand", "remove", "rename", "sinh", "sqrt",
|
||||
"srand", "strcat", "strcmp", "strerror", "time", "tolower",
|
||||
"toupper"};
|
||||
constexpr float kYazeVersion = 0.05;
|
||||
|
||||
// ============================================================================
|
||||
// Magic numbers
|
||||
@@ -135,15 +156,8 @@ constexpr ushort TileNameMask = 0x03FF;
|
||||
constexpr int Uncompressed3BPPSize = 0x0600;
|
||||
constexpr int UncompressedSheetSize = 0x0800;
|
||||
|
||||
constexpr int NumberOfSheets = 223;
|
||||
constexpr int LimitOfMap32 = 8864;
|
||||
constexpr int NumberOfRooms = 296;
|
||||
|
||||
constexpr int kNumOverworldMaps = 160;
|
||||
constexpr int Map32PerScreen = 256;
|
||||
constexpr int NumberOfMap16 = 3752; // 4096
|
||||
constexpr int NumberOfMap32 = Map32PerScreen * kNumOverworldMaps;
|
||||
constexpr int NumberOfOWSprites = 352;
|
||||
constexpr int NumberOfColors = 3143;
|
||||
|
||||
// ============================================================================
|
||||
@@ -156,12 +170,7 @@ constexpr int subtype1_tiles = 0x8000; // JP = Same
|
||||
constexpr int subtype2_tiles = 0x83F0; // JP = Same
|
||||
constexpr int subtype3_tiles = 0x84F0; // JP = Same
|
||||
constexpr int gfx_animated_pointer = 0x10275; // JP 0x10624 //long pointer
|
||||
constexpr int overworldgfxGroups2 = 0x6073; // 0x60B3
|
||||
|
||||
// 2 byte pointer bank 00 pc -> 0x4320
|
||||
constexpr int gfx_1_pointer = 0x6790; // CF80 ; 004F80
|
||||
constexpr int gfx_2_pointer = 0x6795; // D05F ; 00505F
|
||||
constexpr int gfx_3_pointer = 0x679A; // D13E ; 00513E
|
||||
constexpr int hud_palettes = 0xDD660;
|
||||
constexpr int maxGfx = 0xC3FB5;
|
||||
|
||||
@@ -169,174 +178,6 @@ constexpr int kTilesheetWidth = 128;
|
||||
constexpr int kTilesheetHeight = 32;
|
||||
constexpr int kTilesheetDepth = 8;
|
||||
|
||||
// ============================================================================
|
||||
// Overworld Related Variables
|
||||
// ============================================================================
|
||||
|
||||
constexpr int compressedAllMap32PointersHigh = 0x1794D;
|
||||
constexpr int compressedAllMap32PointersLow = 0x17B2D;
|
||||
constexpr int overworldgfxGroups = 0x05D97;
|
||||
constexpr int map16Tiles = 0x78000;
|
||||
constexpr int map32TilesTL = 0x18000;
|
||||
constexpr int map32TilesTR = 0x1B400;
|
||||
constexpr int map32TilesBL = 0x20000;
|
||||
constexpr int map32TilesBR = 0x23400;
|
||||
constexpr int overworldPalGroup1 = 0xDE6C8;
|
||||
constexpr int overworldPalGroup2 = 0xDE86C;
|
||||
constexpr int overworldPalGroup3 = 0xDE604;
|
||||
constexpr int overworldMapPalette = 0x7D1C;
|
||||
constexpr int overworldSpritePalette = 0x7B41;
|
||||
constexpr int overworldMapPaletteGroup = 0x75504;
|
||||
constexpr int overworldSpritePaletteGroup = 0x75580;
|
||||
constexpr int overworldSpriteset = 0x7A41;
|
||||
constexpr int overworldSpecialGFXGroup = 0x16821;
|
||||
constexpr int overworldSpecialPALGroup = 0x16831;
|
||||
|
||||
constexpr int overworldSpritesBegining = 0x4C881;
|
||||
constexpr int overworldSpritesAgahnim = 0x4CA21;
|
||||
constexpr int overworldSpritesZelda = 0x4C901;
|
||||
|
||||
constexpr int overworldItemsPointers = 0xDC2F9;
|
||||
constexpr int overworldItemsAddress = 0xDC8B9; // 1BC2F9
|
||||
constexpr int overworldItemsBank = 0xDC8BF;
|
||||
constexpr int overworldItemsEndData = 0xDC89C; // 0DC89E
|
||||
|
||||
constexpr int mapGfx = 0x7C9C;
|
||||
constexpr int overlayPointers = 0x77664;
|
||||
constexpr int overlayPointersBank = 0x0E;
|
||||
|
||||
constexpr int overworldTilesType = 0x71459;
|
||||
constexpr int overworldMessages = 0x3F51D;
|
||||
|
||||
constexpr int overworldMusicBegining = 0x14303;
|
||||
constexpr int overworldMusicZelda = 0x14303 + 0x40;
|
||||
constexpr int overworldMusicMasterSword = 0x14303 + 0x80;
|
||||
constexpr int overworldMusicAgahim = 0x14303 + 0xC0;
|
||||
constexpr int overworldMusicDW = 0x14403;
|
||||
|
||||
constexpr int overworldEntranceAllowedTilesLeft = 0xDB8C1;
|
||||
constexpr int overworldEntranceAllowedTilesRight = 0xDB917;
|
||||
|
||||
// 0x00 = small maps, 0x20 = large maps
|
||||
constexpr int overworldMapSize = 0x12844;
|
||||
|
||||
// 0x01 = small maps, 0x03 = large maps
|
||||
constexpr int overworldMapSizeHighByte = 0x12884;
|
||||
|
||||
// relative to the WORLD + 0x200 per map
|
||||
// large map that are not == parent id = same position as their parent!
|
||||
// eg for X position small maps :
|
||||
// 0000, 0200, 0400, 0600, 0800, 0A00, 0C00, 0E00
|
||||
// all Large map would be :
|
||||
// 0000, 0000, 0400, 0400, 0800, 0800, 0C00, 0C00
|
||||
|
||||
constexpr int overworldTransitionPositionY = 0x128C4;
|
||||
constexpr int overworldTransitionPositionX = 0x12944;
|
||||
|
||||
constexpr int overworldScreenSize = 0x1788D;
|
||||
|
||||
// ============================================================================
|
||||
// Overworld Exits/Entrances Variables
|
||||
// ============================================================================
|
||||
constexpr int OWExitRoomId = 0x15D8A; // 0x15E07 Credits sequences
|
||||
// 105C2 Ending maps
|
||||
// 105E2 Sprite Group Table for Ending
|
||||
constexpr int OWExitMapId = 0x15E28;
|
||||
constexpr int OWExitVram = 0x15E77;
|
||||
constexpr int OWExitYScroll = 0x15F15;
|
||||
constexpr int OWExitXScroll = 0x15FB3;
|
||||
constexpr int OWExitYPlayer = 0x16051;
|
||||
constexpr int OWExitXPlayer = 0x160EF;
|
||||
constexpr int OWExitYCamera = 0x1618D;
|
||||
constexpr int OWExitXCamera = 0x1622B;
|
||||
constexpr int OWExitDoorPosition = 0x15724;
|
||||
constexpr int OWExitUnk1 = 0x162C9;
|
||||
constexpr int OWExitUnk2 = 0x16318;
|
||||
constexpr int OWExitDoorType1 = 0x16367;
|
||||
constexpr int OWExitDoorType2 = 0x16405;
|
||||
constexpr int OWEntranceMap = 0xDB96F;
|
||||
constexpr int OWEntrancePos = 0xDBA71;
|
||||
constexpr int OWEntranceEntranceId = 0xDBB73;
|
||||
constexpr int OWHolePos = 0xDB800; //(0x13 entries, 2 bytes each) modified(less
|
||||
// 0x400) map16 coordinates for each hole
|
||||
constexpr int OWHoleArea =
|
||||
0xDB826; //(0x13 entries, 2 bytes each) corresponding
|
||||
// area numbers for each hole
|
||||
constexpr int OWHoleEntrance =
|
||||
0xDB84C; //(0x13 entries, 1 byte each) corresponding entrance numbers
|
||||
|
||||
constexpr int OWExitMapIdWhirlpool = 0x16AE5; // JP = ;016849
|
||||
constexpr int OWExitVramWhirlpool = 0x16B07; // JP = ;01686B
|
||||
constexpr int OWExitYScrollWhirlpool = 0x16B29; // JP = ;01688D
|
||||
constexpr int OWExitXScrollWhirlpool = 0x16B4B; // JP = ;016DE7
|
||||
constexpr int OWExitYPlayerWhirlpool = 0x16B6D; // JP = ;016E09
|
||||
constexpr int OWExitXPlayerWhirlpool = 0x16B8F; // JP = ;016E2B
|
||||
constexpr int OWExitYCameraWhirlpool = 0x16BB1; // JP = ;016E4D
|
||||
constexpr int OWExitXCameraWhirlpool = 0x16BD3; // JP = ;016E6F
|
||||
constexpr int OWExitUnk1Whirlpool = 0x16BF5; // JP = ;016E91
|
||||
constexpr int OWExitUnk2Whirlpool = 0x16C17; // JP = ;016EB3
|
||||
constexpr int OWWhirlpoolPosition = 0x16CF8; // JP = ;016F94
|
||||
|
||||
// ============================================================================
|
||||
// Dungeon Related Variables
|
||||
// ============================================================================
|
||||
|
||||
// That could be turned into a pointer :
|
||||
constexpr int dungeons_palettes_groups = 0x75460; // JP 0x67DD0
|
||||
constexpr int dungeons_main_bg_palette_pointers = 0xDEC4B; // JP Same
|
||||
constexpr int dungeons_palettes =
|
||||
0xDD734; // JP Same (where all dungeons palettes are)
|
||||
|
||||
// That could be turned into a pointer :
|
||||
constexpr int room_items_pointers = 0xDB69; // JP 0xDB67
|
||||
|
||||
constexpr int rooms_sprite_pointer = 0x4C298; // JP Same //2byte bank 09D62E
|
||||
constexpr int room_header_pointer = 0xB5DD; // LONG
|
||||
constexpr int room_header_pointers_bank = 0xB5E7; // JP Same
|
||||
|
||||
constexpr int gfx_groups_pointer = 0x6237;
|
||||
constexpr int room_object_layout_pointer = 0x882D;
|
||||
|
||||
constexpr int room_object_pointer = 0x874C; // Long pointer
|
||||
|
||||
constexpr int chests_length_pointer = 0xEBF6;
|
||||
constexpr int chests_data_pointer1 = 0xEBFB;
|
||||
// constexpr int chests_data_pointer2 = 0xEC0A; //Disabled for now could be used
|
||||
// for expansion constexpr int chests_data_pointer3 = 0xEC10; //Disabled for now
|
||||
// could be used for expansion
|
||||
|
||||
constexpr int blocks_length = 0x8896; // word value
|
||||
constexpr int blocks_pointer1 = 0x15AFA;
|
||||
constexpr int blocks_pointer2 = 0x15B01;
|
||||
constexpr int blocks_pointer3 = 0x15B08;
|
||||
constexpr int blocks_pointer4 = 0x15B0F;
|
||||
|
||||
constexpr int torch_data = 0x2736A; // JP 0x2704A
|
||||
constexpr int torches_length_pointer = 0x88C1;
|
||||
|
||||
constexpr int kSpriteBlocksetPointer = 0x5B57;
|
||||
constexpr int sprites_data =
|
||||
0x4D8B0; // It use the unused pointers to have more space //Save purpose
|
||||
constexpr int sprites_data_empty_room = 0x4D8AE;
|
||||
constexpr int sprites_end_data = 0x4EC9E;
|
||||
|
||||
constexpr int pit_pointer = 0x394AB;
|
||||
constexpr int pit_count = 0x394A6;
|
||||
|
||||
constexpr int doorPointers = 0xF83C0;
|
||||
|
||||
// doors
|
||||
constexpr int door_gfx_up = 0x4D9E;
|
||||
constexpr int door_gfx_down = 0x4E06;
|
||||
constexpr int door_gfx_cavexit_down = 0x4E06;
|
||||
constexpr int door_gfx_left = 0x4E66;
|
||||
constexpr int door_gfx_right = 0x4EC6;
|
||||
|
||||
constexpr int door_pos_up = 0x197E;
|
||||
constexpr int door_pos_down = 0x1996;
|
||||
constexpr int door_pos_left = 0x19AE;
|
||||
constexpr int door_pos_right = 0x19C6;
|
||||
|
||||
// TEXT EDITOR RELATED CONSTANTS
|
||||
constexpr int gfx_font = 0x70000; // 2bpp format
|
||||
constexpr int text_data = 0xE0000;
|
||||
@@ -482,7 +323,7 @@ constexpr int customAreaSpecificBGPalette =
|
||||
constexpr int customAreaSpecificBGASM = 0x140150;
|
||||
constexpr int customAreaSpecificBGEnabled =
|
||||
0x140140; // 1 byte, not 0 if enabled
|
||||
constexpr int overworldCustomMosaicArray = 0x1301F0;
|
||||
|
||||
// ============================================================================
|
||||
// Dungeon Map Related Variables
|
||||
// ============================================================================
|
||||
@@ -602,455 +443,6 @@ static const absl::string_view SecretItemNames[] = {
|
||||
|
||||
"Hole", "Warp", "Staircase", "Bombable", "Switch"};
|
||||
|
||||
static const absl::string_view Type1RoomObjectNames[] = {
|
||||
"Ceiling ↔",
|
||||
"Wall (top, north) ↔",
|
||||
"Wall (top, south) ↔",
|
||||
"Wall (bottom, north) ↔",
|
||||
"Wall (bottom, south) ↔",
|
||||
"Wall columns (north) ↔",
|
||||
"Wall columns (south) ↔",
|
||||
"Deep wall (north) ↔",
|
||||
"Deep wall (south) ↔",
|
||||
"Diagonal wall A ◤ (top) ↔",
|
||||
"Diagonal wall A ◣ (top) ↔",
|
||||
"Diagonal wall A ◥ (top) ↔",
|
||||
"Diagonal wall A ◢ (top) ↔",
|
||||
"Diagonal wall B ◤ (top) ↔",
|
||||
"Diagonal wall B ◣ (top) ↔",
|
||||
"Diagonal wall B ◥ (top) ↔",
|
||||
"Diagonal wall B ◢ (top) ↔",
|
||||
"Diagonal wall C ◤ (top) ↔",
|
||||
"Diagonal wall C ◣ (top) ↔",
|
||||
"Diagonal wall C ◥ (top) ↔",
|
||||
"Diagonal wall C ◢ (top) ↔",
|
||||
"Diagonal wall A ◤ (bottom) ↔",
|
||||
"Diagonal wall A ◣ (bottom) ↔",
|
||||
"Diagonal wall A ◥ (bottom) ↔",
|
||||
"Diagonal wall A ◢ (bottom) ↔",
|
||||
"Diagonal wall B ◤ (bottom) ↔",
|
||||
"Diagonal wall B ◣ (bottom) ↔",
|
||||
"Diagonal wall B ◥ (bottom) ↔",
|
||||
"Diagonal wall B ◢ (bottom) ↔",
|
||||
"Diagonal wall C ◤ (bottom) ↔",
|
||||
"Diagonal wall C ◣ (bottom) ↔",
|
||||
"Diagonal wall C ◥ (bottom) ↔",
|
||||
"Diagonal wall C ◢ (bottom) ↔",
|
||||
"Platform stairs ↔",
|
||||
"Rail ↔",
|
||||
"Pit edge ┏━┓ A (north) ↔",
|
||||
"Pit edge ┏━┓ B (north) ↔",
|
||||
"Pit edge ┏━┓ C (north) ↔",
|
||||
"Pit edge ┏━┓ D (north) ↔",
|
||||
"Pit edge ┏━┓ E (north) ↔",
|
||||
"Pit edge ┗━┛ (south) ↔",
|
||||
"Pit edge ━━━ (south) ↔",
|
||||
"Pit edge ━━━ (north) ↔",
|
||||
"Pit edge ━━┛ (south) ↔",
|
||||
"Pit edge ┗━━ (south) ↔",
|
||||
"Pit edge ━━┓ (north) ↔",
|
||||
"Pit edge ┏━━ (north) ↔",
|
||||
"Rail wall (north) ↔",
|
||||
"Rail wall (south) ↔",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Carpet ↔",
|
||||
"Carpet trim ↔",
|
||||
"Weird door", // TODO: WEIRD DOOR OBJECT NEEDS INVESTIGATION
|
||||
"Drapes (north) ↔",
|
||||
"Drapes (west, odd) ↔",
|
||||
"Statues ↔",
|
||||
"Columns ↔",
|
||||
"Wall decors (north) ↔",
|
||||
"Wall decors (south) ↔",
|
||||
"Chairs in pairs ↔",
|
||||
"Tall torches ↔",
|
||||
"Supports (north) ↔",
|
||||
"Water edge ┏━┓ (concave) ↔",
|
||||
"Water edge ┗━┛ (concave) ↔",
|
||||
"Water edge ┏━┓ (convex) ↔",
|
||||
"Water edge ┗━┛ (convex) ↔",
|
||||
"Water edge ┏━┛ (concave) ↔",
|
||||
"Water edge ┗━┓ (concave) ↔",
|
||||
"Water edge ┗━┓ (convex) ↔",
|
||||
"Water edge ┏━┛ (convex) ↔",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Supports (south) ↔",
|
||||
"Bar ↔",
|
||||
"Shelf A ↔",
|
||||
"Shelf B ↔",
|
||||
"Shelf C ↔",
|
||||
"Somaria path ↔",
|
||||
"Cannon hole A (north) ↔",
|
||||
"Cannon hole A (south) ↔",
|
||||
"Pipe path ↔",
|
||||
"Nothing",
|
||||
"Wall torches (north) ↔",
|
||||
"Wall torches (south) ↔",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Cannon hole B (north) ↔",
|
||||
"Cannon hole B (south) ↔",
|
||||
"Thick rail ↔",
|
||||
"Blocks ↔",
|
||||
"Long rail ↔",
|
||||
"Ceiling ↕",
|
||||
"Wall (top, west) ↕",
|
||||
"Wall (top, east) ↕",
|
||||
"Wall (bottom, west) ↕",
|
||||
"Wall (bottom, east) ↕",
|
||||
"Wall columns (west) ↕",
|
||||
"Wall columns (east) ↕",
|
||||
"Deep wall (west) ↕",
|
||||
"Deep wall (east) ↕",
|
||||
"Rail ↕",
|
||||
"Pit edge (west) ↕",
|
||||
"Pit edge (east) ↕",
|
||||
"Rail wall (west) ↕",
|
||||
"Rail wall (east) ↕",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Carpet ↕",
|
||||
"Carpet trim ↕",
|
||||
"Nothing",
|
||||
"Drapes (west) ↕",
|
||||
"Drapes (east) ↕",
|
||||
"Columns ↕",
|
||||
"Wall decors (west) ↕",
|
||||
"Wall decors (east) ↕",
|
||||
"Supports (west) ↕",
|
||||
"Water edge (west) ↕",
|
||||
"Water edge (east) ↕",
|
||||
"Supports (east) ↕",
|
||||
"Somaria path ↕",
|
||||
"Pipe path ↕",
|
||||
"Nothing",
|
||||
"Wall torches (west) ↕",
|
||||
"Wall torches (east) ↕",
|
||||
"Wall decors tight A (west) ↕",
|
||||
"Wall decors tight A (east) ↕",
|
||||
"Wall decors tight B (west) ↕",
|
||||
"Wall decors tight B (east) ↕",
|
||||
"Cannon hole (west) ↕",
|
||||
"Cannon hole (east) ↕",
|
||||
"Tall torches ↕",
|
||||
"Thick rail ↕",
|
||||
"Blocks ↕",
|
||||
"Long rail ↕",
|
||||
"Jump ledge (west) ↕",
|
||||
"Jump ledge (east) ↕",
|
||||
"Rug trim (west) ↕",
|
||||
"Rug trim (east) ↕",
|
||||
"Bar ↕",
|
||||
"Wall flair (west) ↕",
|
||||
"Wall flair (east) ↕",
|
||||
"Blue pegs ↕",
|
||||
"Orange pegs ↕",
|
||||
"Invisible floor ↕",
|
||||
"Fake pots ↕",
|
||||
"Hammer pegs ↕",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Diagonal ceiling A ◤",
|
||||
"Diagonal ceiling A ◣",
|
||||
"Diagonal ceiling A ◥",
|
||||
"Diagonal ceiling A ◢",
|
||||
"Pit ⇲",
|
||||
"Diagonal layer 2 mask A ◤",
|
||||
"Diagonal layer 2 mask A ◣",
|
||||
"Diagonal layer 2 mask A ◥",
|
||||
"Diagonal layer 2 mask A ◢",
|
||||
"Diagonal layer 2 mask B ◤", // TODO: VERIFY
|
||||
"Diagonal layer 2 mask B ◣", // TODO: VERIFY
|
||||
"Diagonal layer 2 mask B ◥", // TODO: VERIFY
|
||||
"Diagonal layer 2 mask B ◢", // TODO: VERIFY
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Jump ledge (north) ↔",
|
||||
"Jump ledge (south) ↔",
|
||||
"Rug ↔",
|
||||
"Rug trim (north) ↔",
|
||||
"Rug trim (south) ↔",
|
||||
"Archery game curtains ↔",
|
||||
"Wall flair (north) ↔",
|
||||
"Wall flair (south) ↔",
|
||||
"Blue pegs ↔",
|
||||
"Orange pegs ↔",
|
||||
"Invisible floor ↔",
|
||||
"Fake pressure plates ↔",
|
||||
"Fake pots ↔",
|
||||
"Hammer pegs ↔",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Ceiling (large) ⇲",
|
||||
"Chest platform (tall) ⇲",
|
||||
"Layer 2 pit mask (large) ⇲",
|
||||
"Layer 2 pit mask (medium) ⇲",
|
||||
"Floor 1 ⇲",
|
||||
"Floor 3 ⇲",
|
||||
"Layer 2 mask (large) ⇲",
|
||||
"Floor 4 ⇲",
|
||||
"Water floor ⇲ ",
|
||||
"Flood water (medium) ⇲ ",
|
||||
"Conveyor floor ⇲ ",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Moving wall (west) ⇲",
|
||||
"Moving wall (east) ⇲",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Icy floor A ⇲",
|
||||
"Icy floor B ⇲",
|
||||
"Moving wall flag", // TODO: WTF IS THIS?
|
||||
"Moving wall flag", // TODO: WTF IS THIS?
|
||||
"Moving wall flag", // TODO: WTF IS THIS?
|
||||
"Moving wall flag", // TODO: WTF IS THIS?
|
||||
"Layer 2 mask (medium) ⇲",
|
||||
"Flood water (large) ⇲",
|
||||
"Layer 2 swim mask ⇲",
|
||||
"Flood water B (large) ⇲",
|
||||
"Floor 2 ⇲",
|
||||
"Chest platform (short) ⇲",
|
||||
"Table / rock ⇲",
|
||||
"Spike blocks ⇲",
|
||||
"Spiked floor ⇲",
|
||||
"Floor 7 ⇲",
|
||||
"Tiled floor ⇲",
|
||||
"Rupee floor ⇲",
|
||||
"Conveyor upwards ⇲",
|
||||
"Conveyor downwards ⇲",
|
||||
"Conveyor leftwards ⇲",
|
||||
"Conveyor rightwards ⇲",
|
||||
"Heavy current water ⇲",
|
||||
"Floor 10 ⇲",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
};
|
||||
|
||||
static const absl::string_view Type2RoomObjectNames[] = {
|
||||
"Corner (top, concave) ▛",
|
||||
"Corner (top, concave) ▙",
|
||||
"Corner (top, concave) ▜",
|
||||
"Corner (top, concave) ▟",
|
||||
"Corner (top, convex) ▟",
|
||||
"Corner (top, convex) ▜",
|
||||
"Corner (top, convex) ▙",
|
||||
"Corner (top, convex) ▛",
|
||||
"Corner (bottom, concave) ▛",
|
||||
"Corner (bottom, concave) ▙",
|
||||
"Corner (bottom, concave) ▜",
|
||||
"Corner (bottom, concave) ▟",
|
||||
"Corner (bottom, convex) ▟",
|
||||
"Corner (bottom, convex) ▜",
|
||||
"Corner (bottom, convex) ▙",
|
||||
"Corner (bottom, convex) ▛",
|
||||
"Kinked corner north (bottom) ▜",
|
||||
"Kinked corner south (bottom) ▟",
|
||||
"Kinked corner north (bottom) ▛",
|
||||
"Kinked corner south (bottom) ▙",
|
||||
"Kinked corner west (bottom) ▙",
|
||||
"Kinked corner west (bottom) ▛",
|
||||
"Kinked corner east (bottom) ▟",
|
||||
"Kinked corner east (bottom) ▜",
|
||||
"Deep corner (concave) ▛",
|
||||
"Deep corner (concave) ▙",
|
||||
"Deep corner (concave) ▜",
|
||||
"Deep corner (concave) ▟",
|
||||
"Large brazier",
|
||||
"Statue",
|
||||
"Star tile (disabled)",
|
||||
"Star tile (enabled)",
|
||||
"Small torch (lit)",
|
||||
"Barrel",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Table",
|
||||
"Fairy statue",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Chair",
|
||||
"Bed",
|
||||
"Fireplace",
|
||||
"Mario portrait",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Interroom stairs (up)",
|
||||
"Interroom stairs (down)",
|
||||
"Interroom stairs B (down)",
|
||||
"Intraroom stairs north B", // TODO: VERIFY LAYER HANDLING
|
||||
"Intraroom stairs north (separate layers)",
|
||||
"Intraroom stairs north (merged layers)",
|
||||
"Intraroom stairs north (swim layer)",
|
||||
"Block",
|
||||
"Water ladder (north)",
|
||||
"Water ladder (south)", // TODO: NEEDS IN GAME VERIFICATION
|
||||
"Dam floodgate",
|
||||
"Interroom spiral stairs up (top)",
|
||||
"Interroom spiral stairs down (top)",
|
||||
"Interroom spiral stairs up (bottom)",
|
||||
"Interroom spiral stairs down (bottom)",
|
||||
"Sanctuary wall (north)",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Pew",
|
||||
"Magic bat altar",
|
||||
};
|
||||
|
||||
static const absl::string_view Type3RoomObjectNames[] = {
|
||||
"Waterfall face (empty)",
|
||||
"Waterfall face (short)",
|
||||
"Waterfall face (long)",
|
||||
"Somaria path endpoint",
|
||||
"Somaria path intersection ╋",
|
||||
"Somaria path corner ┏",
|
||||
"Somaria path corner ┗",
|
||||
"Somaria path corner ┓",
|
||||
"Somaria path corner ┛",
|
||||
"Somaria path intersection ┳",
|
||||
"Somaria path intersection ┻",
|
||||
"Somaria path intersection ┣",
|
||||
"Somaria path intersection ┫",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Somaria path 2-way endpoint",
|
||||
"Somaria path crossover",
|
||||
"Babasu hole (north)",
|
||||
"Babasu hole (south)",
|
||||
"9 blue rupees",
|
||||
"Telepathy tile",
|
||||
"Warp door", // TODO: NEEDS IN GAME VERIFICATION THAT THIS IS USELESS
|
||||
"Kholdstare's shell",
|
||||
"Hammer peg",
|
||||
"Prison cell",
|
||||
"Big key lock",
|
||||
"Chest",
|
||||
"Chest (open)",
|
||||
"Intraroom stairs south", // TODO: VERIFY LAYER HANDLING
|
||||
"Intraroom stairs south (separate layers)",
|
||||
"Intraroom stairs south (merged layers)",
|
||||
"Interroom straight stairs up (north, top)",
|
||||
"Interroom straight stairs down (north, top)",
|
||||
"Interroom straight stairs up (south, top)",
|
||||
"Interroom straight stairs down (south, top)",
|
||||
"Deep corner (convex) ▟",
|
||||
"Deep corner (convex) ▜",
|
||||
"Deep corner (convex) ▙",
|
||||
"Deep corner (convex) ▛",
|
||||
"Interroom straight stairs up (north, bottom)",
|
||||
"Interroom straight stairs down (north, bottom)",
|
||||
"Interroom straight stairs up (south, bottom)",
|
||||
"Interroom straight stairs down (south, bottom)",
|
||||
"Lamp cones",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Liftable large block",
|
||||
"Agahnim's altar",
|
||||
"Agahnim's boss room",
|
||||
"Pot",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Big chest",
|
||||
"Big chest (open)",
|
||||
"Intraroom stairs south (swim layer)",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Pipe end (south)",
|
||||
"Pipe end (north)",
|
||||
"Pipe end (east)",
|
||||
"Pipe end (west)",
|
||||
"Pipe corner ▛",
|
||||
"Pipe corner ▙",
|
||||
"Pipe corner ▜",
|
||||
"Pipe corner ▟",
|
||||
"Pipe-rock intersection ⯊",
|
||||
"Pipe-rock intersection ⯋",
|
||||
"Pipe-rock intersection ◖",
|
||||
"Pipe-rock intersection ◗",
|
||||
"Pipe crossover",
|
||||
"Bombable floor",
|
||||
"Fake bombable floor",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Warp tile",
|
||||
"Tool rack",
|
||||
"Furnace",
|
||||
"Tub (wide)",
|
||||
"Anvil",
|
||||
"Warp tile (disabled)",
|
||||
"Pressure plate",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Blue peg",
|
||||
"Orange peg",
|
||||
"Fortune teller room",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Bar corner ▛",
|
||||
"Bar corner ▙",
|
||||
"Bar corner ▜",
|
||||
"Bar corner ▟",
|
||||
"Decorative bowl",
|
||||
"Tub (tall)",
|
||||
"Bookcase",
|
||||
"Range",
|
||||
"Suitcase",
|
||||
"Bar bottles",
|
||||
"Arrow game hole (west)",
|
||||
"Arrow game hole (east)",
|
||||
"Vitreous goo gfx",
|
||||
"Fake pressure plate",
|
||||
"Medusa head",
|
||||
"4-way shooter block",
|
||||
"Pit",
|
||||
"Wall crack (north)",
|
||||
"Wall crack (south)",
|
||||
"Wall crack (west)",
|
||||
"Wall crack (east)",
|
||||
"Large decor",
|
||||
"Water grate (north)",
|
||||
"Water grate (south)",
|
||||
"Water grate (west)",
|
||||
"Water grate (east)",
|
||||
"Window sunlight",
|
||||
"Floor sunlight",
|
||||
"Trinexx's shell",
|
||||
"Layer 2 mask (full)",
|
||||
"Boss entrance",
|
||||
"Minigame chest",
|
||||
"Ganon door",
|
||||
"Triforce wall ornament",
|
||||
"Triforce floor tiles",
|
||||
"Freezor hole",
|
||||
"Pile of bones",
|
||||
"Vitreous goo damage",
|
||||
"Arrow tile ↑",
|
||||
"Arrow tile ↓",
|
||||
"Arrow tile →",
|
||||
"Nothing",
|
||||
};
|
||||
|
||||
static const absl::string_view TileTypeNames[] = {
|
||||
"$00 Nothing (standard floor)",
|
||||
"$01 Collision",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_mixer.h>
|
||||
#include <imgui/backends/imgui_impl_sdl.h>
|
||||
#include <imgui/backends/imgui_impl_sdlrenderer.h>
|
||||
#include <imgui/backends/imgui_impl_sdl2.h>
|
||||
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/core/platform/font_loader.h"
|
||||
#include "app/editor/master_editor.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/style.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/style.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -32,6 +33,21 @@ void InitializeKeymap() {
|
||||
io.KeyMap[ImGuiKey_LeftCtrl] = SDL_GetScancodeFromKey(SDLK_LCTRL);
|
||||
}
|
||||
|
||||
void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) {
|
||||
SDL_SetClipboardText(text);
|
||||
}
|
||||
|
||||
const char *ImGui_ImplSDL2_GetClipboardText(void *user_data) {
|
||||
return SDL_GetClipboardText();
|
||||
}
|
||||
|
||||
void InitializeClipboard() {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
|
||||
io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
|
||||
io.ClipboardUserData = nullptr;
|
||||
}
|
||||
|
||||
void HandleKeyDown(SDL_Event &event) {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
switch (event.key.keysym.sym) {
|
||||
@@ -81,10 +97,8 @@ void HandleMouseMovement(int &wheel) {
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Controller::isActive() const { return active_; }
|
||||
|
||||
absl::Status Controller::onEntry() {
|
||||
RETURN_IF_ERROR(CreateWindow())
|
||||
absl::Status Controller::OnEntry() {
|
||||
RETURN_IF_ERROR(CreateSDL_Window())
|
||||
RETURN_IF_ERROR(CreateRenderer())
|
||||
RETURN_IF_ERROR(CreateGuiContext())
|
||||
InitializeKeymap();
|
||||
@@ -93,7 +107,7 @@ absl::Status Controller::onEntry() {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void Controller::onInput() {
|
||||
void Controller::OnInput() {
|
||||
int wheel = 0;
|
||||
SDL_Event event;
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
@@ -124,7 +138,6 @@ void Controller::onInput() {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -133,33 +146,40 @@ void Controller::onInput() {
|
||||
HandleMouseMovement(wheel);
|
||||
}
|
||||
|
||||
void Controller::onLoad() { master_editor_.UpdateScreen(); }
|
||||
void Controller::OnLoad() { PRINT_IF_ERROR(master_editor_.Update()); }
|
||||
|
||||
void Controller::doRender() const {
|
||||
SDL_RenderClear(renderer_.get());
|
||||
void Controller::DoRender() const {
|
||||
ImGui::Render();
|
||||
ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData());
|
||||
SDL_RenderClear(renderer_.get());
|
||||
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
|
||||
SDL_RenderPresent(renderer_.get());
|
||||
}
|
||||
|
||||
void Controller::onExit() const {
|
||||
ImGui_ImplSDLRenderer_Shutdown();
|
||||
void Controller::OnExit() {
|
||||
master_editor_.Shutdown();
|
||||
Mix_CloseAudio();
|
||||
ImGui_ImplSDLRenderer2_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
absl::Status Controller::CreateWindow() {
|
||||
absl::Status Controller::CreateSDL_Window() {
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
|
||||
} else {
|
||||
SDL_DisplayMode displayMode;
|
||||
SDL_GetCurrentDisplayMode(0, &displayMode);
|
||||
int screenWidth = displayMode.w * 0.8;
|
||||
int screenHeight = displayMode.h * 0.8;
|
||||
|
||||
window_ = std::unique_ptr<SDL_Window, sdl_deleter>(
|
||||
SDL_CreateWindow("Yet Another Zelda3 Editor", // window title
|
||||
SDL_WINDOWPOS_UNDEFINED, // initial x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // initial y position
|
||||
kScreenWidth, // width, in pixels
|
||||
kScreenHeight, // height, in pixels
|
||||
screenWidth, // width, in pixels
|
||||
screenHeight, // height, in pixels
|
||||
SDL_WINDOW_RESIZABLE),
|
||||
sdl_deleter());
|
||||
if (window_ == nullptr) {
|
||||
@@ -167,7 +187,7 @@ absl::Status Controller::CreateWindow() {
|
||||
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
|
||||
}
|
||||
// Initialize SDL_mixer
|
||||
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
|
||||
if (Mix_OpenAudio(32000, MIX_DEFAULT_FORMAT, 2, 1024) < 0) {
|
||||
printf("SDL_mixer could not initialize! SDL_mixer Error: %s\n",
|
||||
Mix_GetError());
|
||||
}
|
||||
@@ -191,35 +211,86 @@ absl::Status Controller::CreateRenderer() {
|
||||
}
|
||||
|
||||
absl::Status Controller::CreateGuiContext() {
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
if (flags()->kUseNewImGuiInput) {
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||
}
|
||||
|
||||
// Initialize ImGui for SDL
|
||||
ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), renderer_.get());
|
||||
ImGui_ImplSDLRenderer_Init(renderer_.get());
|
||||
ImGui_ImplSDLRenderer2_Init(renderer_.get());
|
||||
|
||||
// Load available fonts
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/Karla-Regular.ttf", 14.0f);
|
||||
if (flags()->kLoadSystemFonts) {
|
||||
LoadSystemFonts();
|
||||
} else {
|
||||
RETURN_IF_ERROR(LoadFontFamilies());
|
||||
}
|
||||
|
||||
// merge in icons from Google Material Design
|
||||
// Set the default style
|
||||
gui::ColorsYaze();
|
||||
|
||||
// Build a new ImGui frame
|
||||
ImGui_ImplSDLRenderer2_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame(window_.get());
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Controller::LoadFontFamilies() const {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
|
||||
// Define constants
|
||||
static const char *KARLA_REGULAR = "assets/font/Karla-Regular.ttf";
|
||||
static const char *ROBOTO_MEDIUM = "assets/font/Roboto-Medium.ttf";
|
||||
static const char *COUSINE_REGULAR = "assets/font/Cousine-Regular.ttf";
|
||||
static const char *DROID_SANS = "assets/font/DroidSans.ttf";
|
||||
static const char *NOTO_SANS_JP = "assets/font/NotoSansJP.ttf";
|
||||
static const char *IBM_PLEX_JP = "assets/font/IBMPlexSansJP-Bold.ttf";
|
||||
static const float FONT_SIZE_DEFAULT = 14.0f;
|
||||
static const float FONT_SIZE_DROID_SANS = 16.0f;
|
||||
static const float ICON_FONT_SIZE = 18.0f;
|
||||
|
||||
// Icon configuration
|
||||
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
|
||||
ImFontConfig icons_config;
|
||||
icons_config.MergeMode = true;
|
||||
icons_config.GlyphOffset.y = 5.0f;
|
||||
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||
icons_config.PixelSnapH = true;
|
||||
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, 18.0f, &icons_config,
|
||||
icons_ranges);
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/Roboto-Medium.ttf", 14.0f);
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/Cousine-Regular.ttf", 14.0f);
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/DroidSans.ttf", 16.0f);
|
||||
|
||||
// Set the default style
|
||||
gui::ColorsYaze();
|
||||
// Japanese font configuration
|
||||
ImFontConfig japanese_font_config;
|
||||
japanese_font_config.MergeMode = true;
|
||||
icons_config.GlyphOffset.y = 5.0f;
|
||||
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||
icons_config.PixelSnapH = true;
|
||||
|
||||
// Build a new ImGui frame
|
||||
ImGui_ImplSDLRenderer_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame(window_.get());
|
||||
// List of fonts to be loaded
|
||||
std::vector<const char *> font_paths = {KARLA_REGULAR, ROBOTO_MEDIUM,
|
||||
COUSINE_REGULAR, IBM_PLEX_JP};
|
||||
|
||||
// Load fonts with associated icon and Japanese merges
|
||||
for (const auto &font_path : font_paths) {
|
||||
float font_size =
|
||||
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT;
|
||||
|
||||
if (!io.Fonts->AddFontFromFileTTF(font_path, font_size)) {
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("Failed to load font from %s", font_path));
|
||||
}
|
||||
|
||||
// Merge icon set
|
||||
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE,
|
||||
&icons_config, icons_ranges);
|
||||
|
||||
// Merge Japanese font
|
||||
io.Fonts->AddFontFromFileTTF(NOTO_SANS_JP, 18.0f, &japanese_font_config,
|
||||
io.Fonts->GetGlyphRangesJapanese());
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
#define YAZE_APP_CORE_CONTROLLER_H
|
||||
|
||||
#include <SDL.h>
|
||||
#include <imgui/backends/imgui_impl_sdl.h>
|
||||
#include <imgui/backends/imgui_impl_sdlrenderer.h>
|
||||
#include <imgui/backends/imgui_impl_sdl2.h>
|
||||
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/editor/master_editor.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/style.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/style.h"
|
||||
|
||||
int main(int argc, char **argv);
|
||||
|
||||
@@ -20,26 +22,34 @@ namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
class Controller {
|
||||
class Controller : public ExperimentFlags {
|
||||
public:
|
||||
bool isActive() const;
|
||||
absl::Status onEntry();
|
||||
void onInput();
|
||||
void onLoad();
|
||||
void onLoadDelta();
|
||||
void doRender() const;
|
||||
void onExit() const;
|
||||
bool IsActive() const { return active_; }
|
||||
absl::Status OnEntry();
|
||||
void OnInput();
|
||||
void OnLoad();
|
||||
void DoRender() const;
|
||||
void OnExit();
|
||||
|
||||
private:
|
||||
struct sdl_deleter {
|
||||
void operator()(SDL_Window *p) const { SDL_DestroyWindow(p); }
|
||||
void operator()(SDL_Renderer *p) const { SDL_DestroyRenderer(p); }
|
||||
void operator()(SDL_Window *p) const {
|
||||
if (p) {
|
||||
SDL_DestroyWindow(p);
|
||||
}
|
||||
}
|
||||
void operator()(SDL_Renderer *p) const {
|
||||
if (p) {
|
||||
SDL_DestroyRenderer(p);
|
||||
}
|
||||
}
|
||||
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
|
||||
};
|
||||
|
||||
absl::Status CreateWindow();
|
||||
absl::Status CreateSDL_Window();
|
||||
absl::Status CreateRenderer();
|
||||
absl::Status CreateGuiContext();
|
||||
absl::Status LoadFontFamilies() const;
|
||||
void CloseWindow() { active_ = false; }
|
||||
|
||||
friend int ::main(int argc, char **argv);
|
||||
|
||||
21
src/app/core/editor.h
Normal file
21
src/app/core/editor.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef YAZE_APP_CORE_EDITOR_H
|
||||
#define YAZE_APP_CORE_EDITOR_H
|
||||
|
||||
#include "absl/status/status.h"
|
||||
|
||||
class Editor {
|
||||
public:
|
||||
Editor() = default;
|
||||
virtual ~Editor() = default;
|
||||
|
||||
virtual absl::Status Cut() = 0;
|
||||
virtual absl::Status Copy() = 0;
|
||||
virtual absl::Status Paste() = 0;
|
||||
|
||||
virtual absl::Status Undo() = 0;
|
||||
virtual absl::Status Redo() = 0;
|
||||
|
||||
virtual absl::Status Update() = 0;
|
||||
};
|
||||
|
||||
#endif // YAZE_APP_CORE_EDITOR_H
|
||||
14
src/app/core/platform/app_delegate.h
Normal file
14
src/app/core/platform/app_delegate.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||
#define YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void InitializeCocoa();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||
218
src/app/core/platform/app_delegate.mm
Normal file
218
src/app/core/platform/app_delegate.mm
Normal file
@@ -0,0 +1,218 @@
|
||||
// AppDelegate.mm
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "app/core/controller.h"
|
||||
#import "app/core/editor.h"
|
||||
#import "app/core/platform/app_delegate.h"
|
||||
#import "app/core/platform/file_dialog.h"
|
||||
#import "app/rom.h"
|
||||
|
||||
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||
- (void)setupMenus;
|
||||
// - (void)changeApplicationIcon;
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
// - (void)changeApplicationIcon {
|
||||
// NSImage *newIcon = [NSImage imageNamed:@"newIcon"];
|
||||
// [NSApp setApplicationIconImage:newIcon];
|
||||
// }
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[self setupMenus];
|
||||
}
|
||||
|
||||
- (void)setupMenus {
|
||||
NSMenu *mainMenu = [NSApp mainMenu];
|
||||
|
||||
NSMenuItem *fileMenuItem = [mainMenu itemWithTitle:@"File"];
|
||||
if (!fileMenuItem) {
|
||||
NSMenu *fileMenu = [[NSMenu alloc] initWithTitle:@"File"];
|
||||
fileMenuItem = [[NSMenuItem alloc] initWithTitle:@"File" action:nil keyEquivalent:@""];
|
||||
[fileMenuItem setSubmenu:fileMenu];
|
||||
|
||||
NSMenuItem *openItem = [[NSMenuItem alloc] initWithTitle:@"Open"
|
||||
action:@selector(openFileAction:)
|
||||
keyEquivalent:@"o"];
|
||||
[fileMenu addItem:openItem];
|
||||
|
||||
// Open Recent
|
||||
NSMenu *openRecentMenu = [[NSMenu alloc] initWithTitle:@"Open Recent"];
|
||||
NSMenuItem *openRecentMenuItem = [[NSMenuItem alloc] initWithTitle:@"Open Recent"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[openRecentMenuItem setSubmenu:openRecentMenu];
|
||||
[fileMenu addItem:openRecentMenuItem];
|
||||
|
||||
// Add a separator
|
||||
[fileMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// Save
|
||||
NSMenuItem *saveItem = [[NSMenuItem alloc] initWithTitle:@"Save" action:nil keyEquivalent:@"s"];
|
||||
[fileMenu addItem:saveItem];
|
||||
|
||||
// Separator
|
||||
[fileMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// Options submenu
|
||||
NSMenu *optionsMenu = [[NSMenu alloc] initWithTitle:@"Options"];
|
||||
NSMenuItem *optionsMenuItem = [[NSMenuItem alloc] initWithTitle:@"Options"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[optionsMenuItem setSubmenu:optionsMenu];
|
||||
|
||||
// Flag checkmark field
|
||||
NSMenuItem *flagItem = [[NSMenuItem alloc] initWithTitle:@"Flag"
|
||||
action:@selector(toggleFlagAction:)
|
||||
keyEquivalent:@""];
|
||||
[flagItem setTarget:self];
|
||||
[flagItem setState:NSControlStateValueOff];
|
||||
[optionsMenu addItem:flagItem];
|
||||
[fileMenu addItem:optionsMenuItem];
|
||||
|
||||
[mainMenu insertItem:fileMenuItem atIndex:1];
|
||||
}
|
||||
|
||||
NSMenuItem *editMenuItem = [mainMenu itemWithTitle:@"Edit"];
|
||||
if (!editMenuItem) {
|
||||
NSMenu *editMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
|
||||
editMenuItem = [[NSMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:@""];
|
||||
[editMenuItem setSubmenu:editMenu];
|
||||
|
||||
NSMenuItem *undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo" action:nil keyEquivalent:@"z"];
|
||||
|
||||
[editMenu addItem:undoItem];
|
||||
|
||||
NSMenuItem *redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo" action:nil keyEquivalent:@"Z"];
|
||||
|
||||
[editMenu addItem:redoItem];
|
||||
|
||||
// Add a separator
|
||||
[editMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
NSMenuItem *cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut"
|
||||
action:@selector(cutAction:)
|
||||
keyEquivalent:@"x"];
|
||||
[editMenu addItem:cutItem];
|
||||
|
||||
NSMenuItem *copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy" action:nil keyEquivalent:@"c"];
|
||||
[editMenu addItem:copyItem];
|
||||
|
||||
NSMenuItem *pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste"
|
||||
action:nil
|
||||
keyEquivalent:@"v"];
|
||||
|
||||
[editMenu addItem:pasteItem];
|
||||
|
||||
// Add a separator
|
||||
[editMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
NSMenuItem *selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All"
|
||||
action:nil
|
||||
keyEquivalent:@"a"];
|
||||
|
||||
[editMenu addItem:selectAllItem];
|
||||
|
||||
[mainMenu insertItem:editMenuItem atIndex:2];
|
||||
}
|
||||
|
||||
NSMenuItem *viewMenuItem = [mainMenu itemWithTitle:@"View"];
|
||||
if (!viewMenuItem) {
|
||||
NSMenu *viewMenu = [[NSMenu alloc] initWithTitle:@"View"];
|
||||
viewMenuItem = [[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""];
|
||||
[viewMenuItem setSubmenu:viewMenu];
|
||||
|
||||
// Emulator view button
|
||||
NSMenuItem *emulatorViewItem = [[NSMenuItem alloc] initWithTitle:@"Emulator View"
|
||||
action:nil
|
||||
keyEquivalent:@"1"];
|
||||
|
||||
[viewMenu addItem:emulatorViewItem];
|
||||
|
||||
// Hex Editor View
|
||||
NSMenuItem *hexEditorViewItem = [[NSMenuItem alloc] initWithTitle:@"Hex Editor View"
|
||||
action:nil
|
||||
keyEquivalent:@"2"];
|
||||
|
||||
[viewMenu addItem:hexEditorViewItem];
|
||||
|
||||
// Disassembly view button
|
||||
NSMenuItem *disassemblyViewItem = [[NSMenuItem alloc] initWithTitle:@"Disassembly View"
|
||||
action:nil
|
||||
keyEquivalent:@"3"];
|
||||
|
||||
[viewMenu addItem:disassemblyViewItem];
|
||||
|
||||
// Memory view button
|
||||
NSMenuItem *memoryViewItem = [[NSMenuItem alloc] initWithTitle:@"Memory View"
|
||||
action:nil
|
||||
keyEquivalent:@"4"];
|
||||
|
||||
[viewMenu addItem:memoryViewItem];
|
||||
|
||||
// Add a separator
|
||||
[viewMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// Toggle fullscreen button
|
||||
NSMenuItem *toggleFullscreenItem = [[NSMenuItem alloc] initWithTitle:@"Toggle Fullscreen"
|
||||
action:nil
|
||||
keyEquivalent:@"f"];
|
||||
|
||||
[viewMenu addItem:toggleFullscreenItem];
|
||||
|
||||
[mainMenu insertItem:viewMenuItem atIndex:3];
|
||||
}
|
||||
|
||||
NSMenuItem *helpMenuItem = [mainMenu itemWithTitle:@"Help"];
|
||||
if (!helpMenuItem) {
|
||||
NSMenu *helpMenu = [[NSMenu alloc] initWithTitle:@"Help"];
|
||||
helpMenuItem = [[NSMenuItem alloc] initWithTitle:@"Help" action:nil keyEquivalent:@""];
|
||||
[helpMenuItem setSubmenu:helpMenu];
|
||||
|
||||
// URL to online documentation
|
||||
NSMenuItem *documentationItem = [[NSMenuItem alloc] initWithTitle:@"Documentation"
|
||||
action:nil
|
||||
keyEquivalent:@"?"];
|
||||
[helpMenu addItem:documentationItem];
|
||||
|
||||
[mainMenu insertItem:helpMenuItem atIndex:4];
|
||||
}
|
||||
}
|
||||
|
||||
// Action method for the New menu item
|
||||
- (void)newFileAction:(id)sender {
|
||||
NSLog(@"New File action triggered");
|
||||
}
|
||||
|
||||
- (void)toggleFlagAction:(id)sender {
|
||||
NSMenuItem *flagItem = (NSMenuItem *)sender;
|
||||
if ([flagItem state] == NSControlStateValueOff) {
|
||||
[flagItem setState:NSControlStateValueOn];
|
||||
} else {
|
||||
[flagItem setState:NSControlStateValueOff];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)openFileAction:(id)sender {
|
||||
yaze::app::SharedROM::shared_rom_->LoadFromFile(FileDialogWrapper::ShowOpenFileDialog());
|
||||
}
|
||||
|
||||
- (void)cutAction:(id)sender {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
- (void)openRecentFileAction:(id)sender {
|
||||
NSLog(@"Open Recent File action triggered");
|
||||
}
|
||||
|
||||
extern "C" void InitializeCocoa() {
|
||||
@autoreleasepool {
|
||||
AppDelegate *delegate = [[AppDelegate alloc] init];
|
||||
[NSApplication sharedApplication];
|
||||
[NSApp setDelegate:delegate];
|
||||
[NSApp finishLaunching];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
32
src/app/core/platform/clipboard.h
Normal file
32
src/app/core/platform/clipboard.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
|
||||
#define YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
void CopyImageToClipboard(const std::vector<uint8_t>& data);
|
||||
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <vector>
|
||||
|
||||
void CopyImageToClipboard(const std::vector<uint8_t>& data);
|
||||
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
#include <vector>
|
||||
|
||||
void CopyImageToClipboard(const std::vector<uint8_t>& data) {
|
||||
std::cout << "CopyImageToClipboard() is not implemented on Linux."
|
||||
<< std::endl;
|
||||
}
|
||||
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width,
|
||||
int& height) {
|
||||
std::cout << "GetImageFromClipboard() is not implemented on Linux."
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
|
||||
42
src/app/core/platform/clipboard.mm
Normal file
42
src/app/core/platform/clipboard.mm
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "clipboard.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
void CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
|
||||
NSData* data = [NSData dataWithBytes:pngData.data() length:pngData.size()];
|
||||
NSImage* image = [[NSImage alloc] initWithData:data];
|
||||
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
[pasteboard clearContents];
|
||||
[pasteboard writeObjects:@[ image ]];
|
||||
}
|
||||
|
||||
void GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& height) {
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
NSArray* classArray = [NSArray arrayWithObject:[NSImage class]];
|
||||
NSDictionary* options = [NSDictionary dictionary];
|
||||
|
||||
NSImage* image = [pasteboard readObjectsForClasses:classArray options:options].firstObject;
|
||||
if (!image) {
|
||||
width = height = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Assuming the image is in an RGBA format
|
||||
CGImageRef cgImage = [image CGImageForProposedRect:nil context:nil hints:nil];
|
||||
width = (int)CGImageGetWidth(cgImage);
|
||||
height = (int)CGImageGetHeight(cgImage);
|
||||
|
||||
size_t bytesPerRow = 4 * width;
|
||||
size_t totalBytes = bytesPerRow * height;
|
||||
pixel_data.resize(totalBytes);
|
||||
|
||||
CGContextRef context = CGBitmapContextCreate(
|
||||
pixel_data.data(), width, height, 8, bytesPerRow, CGColorSpaceCreateDeviceRGB(),
|
||||
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
||||
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
|
||||
CGContextRelease(context);
|
||||
}
|
||||
65
src/app/core/platform/file_dialog.h
Normal file
65
src/app/core/platform/file_dialog.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
// Include Windows-specific headers
|
||||
#include <shobjidl.h>
|
||||
#include <windows.h>
|
||||
|
||||
class FileDialogWrapper {
|
||||
public:
|
||||
static std::string ShowOpenFileDialog() {
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
|
||||
IFileDialog *pfd = NULL;
|
||||
HRESULT hr =
|
||||
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
|
||||
IID_IFileDialog, reinterpret_cast<void **>(&pfd));
|
||||
std::string file_path_windows;
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Show the dialog
|
||||
hr = pfd->Show(NULL);
|
||||
if (SUCCEEDED(hr)) {
|
||||
IShellItem *psiResult;
|
||||
hr = pfd->GetResult(&psiResult);
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Get the file path
|
||||
PWSTR pszFilePath;
|
||||
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
|
||||
char str[128];
|
||||
wcstombs(str, pszFilePath, 128);
|
||||
file_path_windows = str;
|
||||
psiResult->Release();
|
||||
CoTaskMemFree(pszFilePath);
|
||||
}
|
||||
}
|
||||
pfd->Release();
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
return file_path_windows;
|
||||
}
|
||||
};
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <string>
|
||||
|
||||
class FileDialogWrapper {
|
||||
public:
|
||||
static std::string ShowOpenFileDialog();
|
||||
};
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
class FileDialogWrapper {
|
||||
public:
|
||||
static std::string ShowOpenFileDialog() {
|
||||
// Linux-specific file dialog implementation using GTK
|
||||
// ...
|
||||
return "file_path_linux";
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
#error "Unsupported platform."
|
||||
#endif
|
||||
17
src/app/core/platform/file_dialog.mm
Normal file
17
src/app/core/platform/file_dialog.mm
Normal file
@@ -0,0 +1,17 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include "app/core/platform/file_dialog.h"
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
|
||||
[openPanel setCanChooseFiles:YES];
|
||||
[openPanel setCanChooseDirectories:NO];
|
||||
[openPanel setAllowsMultipleSelection:NO];
|
||||
|
||||
if ([openPanel runModal] == NSModalResponseOK) {
|
||||
NSURL* url = [[openPanel URLs] objectAtIndex:0];
|
||||
NSString* path = [url path];
|
||||
return std::string([path UTF8String]);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
81
src/app/core/platform/font_loader.cc
Normal file
81
src/app/core/platform/font_loader.cc
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "app/core/platform/font_loader.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
|
||||
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
|
||||
DWORD FontType, LPARAM lParam) {
|
||||
// Step 3: Load the font into ImGui
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.Fonts->AddFontFromFileTTF(lpelfe->lfFaceName, 16.0f);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void LoadSystemFonts() {
|
||||
HKEY hKey;
|
||||
std::vector<std::string> fontPaths;
|
||||
|
||||
// Open the registry key where fonts are listed
|
||||
if (RegOpenKeyEx(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"), 0,
|
||||
KEY_READ, &hKey) == ERROR_SUCCESS) {
|
||||
DWORD valueCount;
|
||||
DWORD maxValueNameSize;
|
||||
DWORD maxValueDataSize;
|
||||
|
||||
// Query the number of entries and the maximum size of the names and values
|
||||
RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount,
|
||||
&maxValueNameSize, &maxValueDataSize, NULL, NULL);
|
||||
|
||||
char* valueName = new char[maxValueNameSize + 1]; // +1 for null terminator
|
||||
BYTE* valueData = new BYTE[maxValueDataSize + 1]; // +1 for null terminator
|
||||
|
||||
// Enumerate all font entries
|
||||
for (DWORD i = 0; i < valueCount; i++) {
|
||||
DWORD valueNameSize = maxValueNameSize + 1; // +1 for null terminator
|
||||
DWORD valueDataSize = maxValueDataSize + 1; // +1 for null terminator
|
||||
DWORD valueType;
|
||||
|
||||
// Clear buffers
|
||||
memset(valueName, 0, valueNameSize);
|
||||
memset(valueData, 0, valueDataSize);
|
||||
|
||||
// Get the font name and file path
|
||||
if (RegEnumValue(hKey, i, valueName, &valueNameSize, NULL, &valueType,
|
||||
valueData, &valueDataSize) == ERROR_SUCCESS) {
|
||||
if (valueType == REG_SZ) {
|
||||
// Add the font file path to the vector
|
||||
std::string fontPath((char*)valueData);
|
||||
fontPaths.push_back(fontPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete[] valueName;
|
||||
delete[] valueData;
|
||||
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
for (const auto& fontPath : fontPaths) {
|
||||
io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 16.0f);
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
void LoadSystemFonts() {
|
||||
// Load Linux System Fonts into ImGui
|
||||
// ...
|
||||
}
|
||||
|
||||
#endif
|
||||
15
src/app/core/platform/font_loader.h
Normal file
15
src/app/core/platform/font_loader.h
Normal file
@@ -0,0 +1,15 @@
|
||||
// FontLoader.h
|
||||
#ifndef FONTLOADER_H
|
||||
#define FONTLOADER_H
|
||||
|
||||
// Function declaration for loading system fonts into ImGui
|
||||
void LoadSystemFonts();
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
// Windows specific function declaration for loading system fonts into ImGui
|
||||
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
|
||||
DWORD FontType, LPARAM lParam);
|
||||
#endif
|
||||
|
||||
#endif // FONTLOADER_H
|
||||
50
src/app/core/platform/font_loader.mm
Normal file
50
src/app/core/platform/font_loader.mm
Normal file
@@ -0,0 +1,50 @@
|
||||
// FontLoader.mm
|
||||
#include "app/core/platform/font_loader.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <CoreText/CoreText.h>
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "app/gui/icons.h"
|
||||
|
||||
void LoadSystemFonts() {
|
||||
// List of common macOS system fonts
|
||||
NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ];
|
||||
|
||||
for (NSString *fontName in fontNames) {
|
||||
NSFont *font = [NSFont fontWithName:fontName size:14.0];
|
||||
if (!font) {
|
||||
NSLog(@"Font not found: %@", fontName);
|
||||
continue;
|
||||
}
|
||||
|
||||
CTFontDescriptorRef fontDescriptor =
|
||||
CTFontDescriptorCreateWithNameAndSize((CFStringRef)font.fontName, font.pointSize);
|
||||
CFURLRef fontURL = (CFURLRef)CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontURLAttribute);
|
||||
NSString *fontPath = [(NSURL *)fontURL path];
|
||||
CFRelease(fontDescriptor);
|
||||
|
||||
if (fontPath != nil && [[NSFileManager defaultManager] isReadableFileAtPath:fontPath]) {
|
||||
// Load the font into ImGui
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
ImFontConfig icons_config;
|
||||
icons_config.MergeMode = true;
|
||||
icons_config.GlyphOffset.y = 5.0f;
|
||||
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||
icons_config.PixelSnapH = true;
|
||||
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
|
||||
static const float ICON_FONT_SIZE = 18.0f;
|
||||
ImFont *imFont = io.Fonts->AddFontFromFileTTF([fontPath UTF8String], 14.0f);
|
||||
if (!imFont) {
|
||||
NSLog(@"Failed to load font: %@", fontPath);
|
||||
}
|
||||
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE, &icons_config,
|
||||
icons_ranges);
|
||||
} else {
|
||||
NSLog(@"Font file not accessible: %@", fontPath);
|
||||
}
|
||||
|
||||
if (fontURL) {
|
||||
CFRelease(fontURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
|
||||
add_library(delta-service delta.proto)
|
||||
target_link_libraries(delta-service
|
||||
PUBLIC
|
||||
protobuf::libprotobuf
|
||||
gRPC::grpc
|
||||
gRPC::grpc++
|
||||
)
|
||||
|
||||
target_include_directories(delta-service
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${PROTOBUF_INCLUDE_PATH}
|
||||
)
|
||||
|
||||
get_target_property(grpc_cpp_plugin_location gRPC::grpc_cpp_plugin LOCATION)
|
||||
|
||||
# compile the message types
|
||||
protobuf_generate(TARGET delta-service LANGUAGE cpp)
|
||||
|
||||
# compile the GRPC services
|
||||
protobuf_generate(
|
||||
TARGET
|
||||
delta-service
|
||||
LANGUAGE
|
||||
grpc
|
||||
GENERATE_EXTENSIONS
|
||||
.grpc.pb.h
|
||||
.grpc.pb.cc
|
||||
PLUGIN
|
||||
"protoc-gen-grpc=${grpc_cpp_plugin_location}"
|
||||
)
|
||||
@@ -1,54 +0,0 @@
|
||||
#include "client.h"
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Channel;
|
||||
using grpc::ClientAsyncResponseReader;
|
||||
using grpc::ClientContext;
|
||||
using grpc::CompletionQueue;
|
||||
using grpc::Server;
|
||||
using grpc::ServerBuilder;
|
||||
using grpc::Status;
|
||||
|
||||
void Client::CreateChannel() {
|
||||
auto channel = grpc::CreateChannel("localhost:50051",
|
||||
grpc::InsecureChannelCredentials());
|
||||
stub_ = ::YazeDelta::NewStub(channel);
|
||||
}
|
||||
|
||||
absl::Status Client::InitRepo(std::string author_name,
|
||||
std::string project_name) {
|
||||
Repository new_repo;
|
||||
new_repo.set_author_name(author_name);
|
||||
new_repo.set_project_name(project_name);
|
||||
new_repo.mutable_tree()->Add();
|
||||
|
||||
InitRequest request;
|
||||
request.set_allocated_repo(&new_repo);
|
||||
|
||||
InitResponse response;
|
||||
Status status = stub_->Init(&rpc_context, request, &response);
|
||||
|
||||
if (!status.ok()) {
|
||||
std::cerr << status.error_code() << ": " << status.error_message()
|
||||
<< std::endl;
|
||||
return absl::InternalError(status.error_message());
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
@@ -1,44 +0,0 @@
|
||||
#ifndef YAZE_APP_DELTA_CLIENT_H
|
||||
#define YAZE_APP_DELTA_CLIENT_H
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Channel;
|
||||
using grpc::ClientAsyncResponseReader;
|
||||
using grpc::ClientContext;
|
||||
using grpc::CompletionQueue;
|
||||
using grpc::Server;
|
||||
using grpc::ServerBuilder;
|
||||
using grpc::Status;
|
||||
|
||||
class Client {
|
||||
public:
|
||||
Client() = default;
|
||||
void CreateChannel();
|
||||
absl::Status InitRepo(std::string author_name, std::string project_name);
|
||||
|
||||
private:
|
||||
ClientContext rpc_context;
|
||||
std::vector<Repository> repos_;
|
||||
std::unique_ptr<YazeDelta::Stub> stub_;
|
||||
};
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
@@ -1,32 +0,0 @@
|
||||
#if defined(_WIN32)
|
||||
#define main SDL_main
|
||||
#endif
|
||||
|
||||
#include "absl/debugging/failure_signal_handler.h"
|
||||
#include "absl/debugging/symbolize.h"
|
||||
#include "app/core/controller.h"
|
||||
#include "app/delta/viewer.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
absl::InitializeSymbolizer(argv[0]);
|
||||
|
||||
absl::FailureSignalHandlerOptions options;
|
||||
absl::InstallFailureSignalHandler(options);
|
||||
|
||||
yaze::app::core::Controller controller;
|
||||
yaze::app::delta::Viewer viewer;
|
||||
|
||||
auto entry_status = controller.onEntry();
|
||||
if (!entry_status.ok()) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
while (controller.isActive()) {
|
||||
controller.onInput();
|
||||
viewer.Update();
|
||||
controller.doRender();
|
||||
}
|
||||
controller.onExit();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
syntax = "proto3";
|
||||
option cc_enable_arenas = true;
|
||||
|
||||
service YazeDelta {
|
||||
rpc Init(InitRequest) returns (InitResponse) {}
|
||||
|
||||
rpc Clone(CloneRequest) returns (CloneResponse) {}
|
||||
|
||||
rpc Push(PushRequest) returns (PushResponse) {}
|
||||
rpc Pull(PullRequest) returns (PullResponse) {}
|
||||
|
||||
rpc Sync(stream SyncRequest) returns (stream SyncResponse) {}
|
||||
|
||||
rpc CreateBranch(CreateBranchRequest) returns (CreateBranchResponse) {}
|
||||
rpc DeleteBranch(DeleteBranchRequest) returns (DeleteBranchResponse) {}
|
||||
|
||||
rpc Merge(MergeRequest) returns (MergeResponse) {}
|
||||
rpc UndoMerge(UndoMergeRequest) returns (UndoMergeResponse) {}
|
||||
}
|
||||
|
||||
enum ChangeType {
|
||||
OVERWORLD_MAP = 0;
|
||||
DUNGEON_MAP = 1;
|
||||
MONOLOGUE = 2;
|
||||
PALETTE = 3;
|
||||
OBJECT = 4;
|
||||
ASSEMBLY = 5;
|
||||
MISC = 6;
|
||||
}
|
||||
|
||||
message Delta {
|
||||
int64 offset = 1;
|
||||
int64 length = 2;
|
||||
bytes data = 3;
|
||||
ChangeType type = 4;
|
||||
}
|
||||
|
||||
message Commit {
|
||||
int64 commit_id = 1;
|
||||
int64 parent_commit_id = 2;
|
||||
string author_name = 3;
|
||||
string message_header = 4;
|
||||
optional string message_body = 5;
|
||||
repeated Delta delta = 6;
|
||||
int64 signature = 7;
|
||||
}
|
||||
|
||||
message Branch {
|
||||
string branch_name = 1;
|
||||
optional string parent_name = 2;
|
||||
repeated Commit commits = 3;
|
||||
}
|
||||
|
||||
message Repository {
|
||||
string project_name = 1;
|
||||
string author_name = 2;
|
||||
int64 signature = 3;
|
||||
optional bool locked = 4;
|
||||
optional string password = 5;
|
||||
repeated Branch tree = 6;
|
||||
}
|
||||
|
||||
message InitRequest {
|
||||
Repository repo = 1;
|
||||
}
|
||||
|
||||
message InitResponse {
|
||||
int32 response = 1;
|
||||
}
|
||||
|
||||
message CloneRequest {}
|
||||
|
||||
message CloneResponse {}
|
||||
|
||||
message PushRequest {
|
||||
string author_name = 1;
|
||||
string repository_name= 2;
|
||||
string branch_name = 3;
|
||||
repeated Commit commits = 4;
|
||||
}
|
||||
message PushResponse {}
|
||||
|
||||
message PullRequest {
|
||||
string repository_name = 1;
|
||||
string branch_name = 2;
|
||||
repeated Commit commits = 3;
|
||||
}
|
||||
|
||||
message PullResponse {}
|
||||
|
||||
message SyncRequest {}
|
||||
|
||||
message SyncResponse {}
|
||||
|
||||
message CreateBranchRequest {}
|
||||
message CreateBranchResponse {}
|
||||
|
||||
message DeleteBranchRequest {}
|
||||
message DeleteBranchResponse {}
|
||||
|
||||
message MergeRequest {}
|
||||
message MergeResponse {}
|
||||
|
||||
message UndoMergeRequest {}
|
||||
message UndoMergeResponse {}
|
||||
@@ -1,87 +0,0 @@
|
||||
#include "service.h"
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Channel;
|
||||
using grpc::ClientAsyncResponseReader;
|
||||
using grpc::ClientContext;
|
||||
using grpc::CompletionQueue;
|
||||
using grpc::Server;
|
||||
using grpc::ServerBuilder;
|
||||
using grpc::Status;
|
||||
|
||||
namespace {
|
||||
auto FindRepository(std::vector<Repository>& repos, const std::string& name) {
|
||||
for (auto& repo : repos) {
|
||||
if (repo.project_name() == name) {
|
||||
return repo.mutable_tree();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto FindBranch(google::protobuf::RepeatedPtrField<Branch>* repo,
|
||||
const std::string& branch_name) {
|
||||
for (auto it = repo->begin(); it != repo->end(); ++it) {
|
||||
if (it->branch_name() == branch_name) {
|
||||
return it->mutable_commits();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Status DeltaService::Init(grpc::ServerContext* context,
|
||||
const InitRequest* request, InitResponse* reply) {
|
||||
std::filesystem::create_directories("./.yaze");
|
||||
repos_.push_back(request->repo());
|
||||
// std::ofstream commit_stream("./.yaze/commits");
|
||||
// for (const auto& repo : repos_) {
|
||||
// for (const auto& branch : repo.tree()) {
|
||||
// for (const auto& commit : branch.commits()) {
|
||||
// commit_stream << commit.DebugString();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status DeltaService::Push(grpc::ServerContext* context,
|
||||
const PushRequest* request, PushResponse* reply) {
|
||||
const auto& repository_name = request->repository_name();
|
||||
const auto& branch_name = request->branch_name();
|
||||
auto repo = FindRepository(repos_, repository_name);
|
||||
auto mutable_commits = FindBranch(repo, branch_name);
|
||||
auto size = request->commits().size();
|
||||
for (int i = 1; i < size; ++i) {
|
||||
*mutable_commits->Add() = request->commits().at(i);
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status DeltaService::Pull(grpc::ServerContext* context,
|
||||
const PullRequest* request, PullResponse* reply) {
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status DeltaService::Clone(grpc::ServerContext* context,
|
||||
const CloneRequest* request, CloneResponse* reply) {
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
@@ -1,48 +0,0 @@
|
||||
#ifndef YAZE_APP_DELTA_SERVICE_H
|
||||
#define YAZE_APP_DELTA_SERVICE_H
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Status;
|
||||
|
||||
class DeltaService final : public ::YazeDelta::Service {
|
||||
public:
|
||||
Status Init(grpc::ServerContext* context, const InitRequest* request,
|
||||
InitResponse* reply) override;
|
||||
|
||||
Status Push(grpc::ServerContext* context, const PushRequest* request,
|
||||
PushResponse* reply) override;
|
||||
|
||||
Status Pull(grpc::ServerContext* context, const PullRequest* request,
|
||||
PullResponse* reply) override;
|
||||
|
||||
Status Clone(grpc::ServerContext* context, const CloneRequest* request,
|
||||
CloneResponse* reply) override;
|
||||
|
||||
auto Repos() const { return repos_; }
|
||||
|
||||
private:
|
||||
std::vector<Repository> repos_;
|
||||
};
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
@@ -1,229 +0,0 @@
|
||||
#include "viewer.h"
|
||||
|
||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
namespace {
|
||||
|
||||
constexpr ImGuiWindowFlags kMainEditorFlags =
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
|
||||
|
||||
void NewMasterFrame() {
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui::NewFrame();
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
|
||||
ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
|
||||
|
||||
if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Viewer::Update() {
|
||||
NewMasterFrame();
|
||||
DrawYazeMenu();
|
||||
DrawFileDialog();
|
||||
|
||||
ImGui::Text(ICON_MD_CHANGE_HISTORY);
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("%s", rom_.GetTitle());
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Button(ICON_MD_SYNC);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_ARROW_UPWARD);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_ARROW_DOWNWARD);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_MERGE);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Button(ICON_MD_MANAGE_HISTORY);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_LAN);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_COMMIT);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_DIFFERENCE);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::SetNextItemWidth(75.f);
|
||||
ImGui::Button(ICON_MD_SEND);
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("Server Address", &client_address_);
|
||||
|
||||
ImGui::SetNextItemWidth(75.f);
|
||||
ImGui::Button(ICON_MD_DOWNLOAD);
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("Repository Source", &client_address_);
|
||||
|
||||
ImGui::Separator();
|
||||
DrawBranchTree();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void Viewer::DrawFileDialog() {
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
|
||||
if (ImGuiFileDialog::Instance()->IsOk()) {
|
||||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
rom_.LoadFromFile(filePathName);
|
||||
}
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewer::DrawYazeMenu() {
|
||||
MENU_BAR()
|
||||
DrawFileMenu();
|
||||
DrawViewMenu();
|
||||
END_MENU_BAR()
|
||||
}
|
||||
|
||||
void Viewer::DrawFileMenu() const {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
|
||||
".sfc,.smc", ".");
|
||||
}
|
||||
|
||||
MENU_ITEM2("Save", "Ctrl+S") {}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewer::DrawViewMenu() {
|
||||
static bool show_imgui_metrics = false;
|
||||
static bool show_imgui_style_editor = false;
|
||||
static bool show_memory_editor = false;
|
||||
static bool show_imgui_demo = false;
|
||||
|
||||
if (show_imgui_metrics) {
|
||||
ImGui::ShowMetricsWindow(&show_imgui_metrics);
|
||||
}
|
||||
|
||||
if (show_memory_editor) {
|
||||
static MemoryEditor mem_edit;
|
||||
mem_edit.DrawWindow("Memory Editor", (void*)&rom_, rom_.size());
|
||||
}
|
||||
|
||||
if (show_imgui_demo) {
|
||||
ImGui::ShowDemoWindow();
|
||||
}
|
||||
|
||||
if (show_imgui_style_editor) {
|
||||
ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor);
|
||||
ImGui::ShowStyleEditor();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
ImGui::MenuItem("HEX Editor", nullptr, &show_memory_editor);
|
||||
ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginMenu("GUI Tools")) {
|
||||
ImGui::MenuItem("Metrics (ImGui)", nullptr, &show_imgui_metrics);
|
||||
ImGui::MenuItem("Style Editor (ImGui)", nullptr,
|
||||
&show_imgui_style_editor);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewer::DrawBranchTree() {
|
||||
static ImGuiTableFlags flags =
|
||||
ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH |
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg |
|
||||
ImGuiTableFlags_NoBordersInBody;
|
||||
|
||||
if (ImGui::BeginTable("3ways", 3, flags)) {
|
||||
// The first column will use the default _WidthStretch when ScrollX is Off
|
||||
// and _WidthFixed when ScrollX is On
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide);
|
||||
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed,
|
||||
10 * 12.0f);
|
||||
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
|
||||
10 * 18.0f);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
// Simple storage to output a dummy file-system.
|
||||
struct MyTreeNode {
|
||||
const char* Name;
|
||||
const char* Type;
|
||||
int Size;
|
||||
int ChildIdx;
|
||||
int ChildCount;
|
||||
static void DisplayNode(const MyTreeNode* node,
|
||||
const MyTreeNode* all_nodes) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
const bool is_folder = (node->ChildCount > 0);
|
||||
if (is_folder) {
|
||||
bool open =
|
||||
ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextDisabled("--");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(node->Type);
|
||||
if (open) {
|
||||
for (int child_n = 0; child_n < node->ChildCount; child_n++)
|
||||
DisplayNode(&all_nodes[node->ChildIdx + child_n], all_nodes);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
} else {
|
||||
ImGui::TreeNodeEx(
|
||||
node->Name, ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet |
|
||||
ImGuiTreeNodeFlags_NoTreePushOnOpen |
|
||||
ImGuiTreeNodeFlags_SpanFullWidth);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", node->Size);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(node->Type);
|
||||
}
|
||||
}
|
||||
};
|
||||
static const MyTreeNode nodes[] = {
|
||||
{"lttp-redux", "Repository", -1, 1, 3},
|
||||
{"main", "Branch", -1, 4, 2},
|
||||
{"hyrule-castle", "Branch", -1, 4, 2},
|
||||
{"lost-woods", "Branch", -1, 6, 3},
|
||||
{"Added some bushes", "Commit", 1024, -1, -1},
|
||||
{"Constructed a new house", "Commit", 123000, -1, -1},
|
||||
{"File1_b.wav", "Commit", 456000, -1, -1},
|
||||
{"Image001.png", "Commit", 203128, -1, -1},
|
||||
{"Copy of Image001.png", "Commit", 203256, -1, -1},
|
||||
{"Copy of Image001 (Final2).png", "Commit", 203512, -1, -1},
|
||||
};
|
||||
|
||||
MyTreeNode::DisplayNode(&nodes[0], nodes);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
@@ -1,45 +0,0 @@
|
||||
#ifndef YAZE_APP_DELTA_VIEWER_H
|
||||
#define YAZE_APP_DELTA_VIEWER_H
|
||||
|
||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/delta/client.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
class Viewer {
|
||||
public:
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void DrawFileDialog();
|
||||
|
||||
void DrawYazeMenu();
|
||||
void DrawFileMenu() const;
|
||||
void DrawViewMenu();
|
||||
|
||||
void DrawBranchTree();
|
||||
|
||||
std::string client_address_;
|
||||
|
||||
ROM rom_;
|
||||
Client client_;
|
||||
};
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
35
src/app/editor/context/gfx_context.cc
Normal file
35
src/app/editor/context/gfx_context.cc
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "app/editor/context/gfx_context.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
absl::Status GfxContext::Update() { return absl::OkStatus(); }
|
||||
|
||||
gfx::Bitmap GfxContext::current_ow_gfx_bmp_;
|
||||
gfx::SNESPalette GfxContext::current_ow_palette_;
|
||||
gfx::Bitmap GfxContext::tile16_blockset_bmp_;
|
||||
gfx::Bitmap GfxContext::tile8_blockset_bmp_;
|
||||
std::vector<gfx::Bitmap> GfxContext::tile16_individual_bmp_;
|
||||
std::vector<gfx::Bitmap> GfxContext::tile8_individual_bmp_;
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
56
src/app/editor/context/gfx_context.h
Normal file
56
src/app/editor/context/gfx_context.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||
#define YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
|
||||
// Create a class which manages the current VRAM state of Link to the Past,
|
||||
// including static members for the Bitmaps and Palettes as well as the current
|
||||
// blockset as to update all of the overworld maps with the new blockset when it
|
||||
// is changed. This class will also manage the current tile16 and tile8
|
||||
// selection, as well as the current palette selection.
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
class GfxContext {
|
||||
public:
|
||||
absl::Status Update();
|
||||
|
||||
protected:
|
||||
static gfx::Bitmap current_ow_gfx_bmp_;
|
||||
|
||||
static gfx::SNESPalette current_ow_palette_;
|
||||
|
||||
static gfx::Bitmap tile16_blockset_bmp_;
|
||||
|
||||
static gfx::Bitmap tile8_blockset_bmp_;
|
||||
|
||||
// Bitmaps for the tile16 individual tiles
|
||||
static std::vector<gfx::Bitmap> tile16_individual_bmp_;
|
||||
|
||||
// Bitmaps for the tile8 individual tiles
|
||||
static std::vector<gfx::Bitmap> tile8_individual_bmp_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||
@@ -1,59 +1,378 @@
|
||||
#include "dungeon_editor.h"
|
||||
|
||||
#include "gui/icons.h"
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "app/core/common.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/dungeon/object_names.h"
|
||||
#include "app/zelda3/dungeon/room_names.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
void DungeonEditor::Update() {
|
||||
constexpr ImGuiTableFlags kDungeonObjectTableFlags =
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||
ImGuiTableFlags_BordersV;
|
||||
|
||||
using ImGui::TableHeadersRow;
|
||||
using ImGui::TableNextColumn;
|
||||
using ImGui::TableNextRow;
|
||||
using ImGui::TableSetupColumn;
|
||||
|
||||
absl::Status DungeonEditor::Update() {
|
||||
if (!is_loaded_ && rom()->isLoaded()) {
|
||||
for (int i = 0; i < 0x100; i++) {
|
||||
rooms_.emplace_back(zelda3::dungeon::Room(i));
|
||||
rooms_[i].LoadHeader();
|
||||
rooms_[i].LoadRoomFromROM();
|
||||
if (flags()->kDrawDungeonRoomGraphics) {
|
||||
rooms_[i].LoadRoomGraphics();
|
||||
}
|
||||
}
|
||||
graphics_bin_ = rom()->graphics_bin();
|
||||
full_palette_ =
|
||||
rom()->palette_group("dungeon_main")[current_palette_group_id_];
|
||||
current_palette_group_ =
|
||||
gfx::CreatePaletteGroupFromLargePalette(full_palette_);
|
||||
|
||||
// Create a vector of pointers to the current block bitmaps
|
||||
for (int block : rooms_[current_room_id_].blocks()) {
|
||||
room_gfx_sheets_.emplace_back(&graphics_bin_[block]);
|
||||
}
|
||||
|
||||
is_loaded_ = true;
|
||||
}
|
||||
|
||||
if (refresh_graphics_) {
|
||||
for (int block : rooms_[current_room_id_].blocks()) {
|
||||
graphics_bin_[block].ApplyPalette(
|
||||
current_palette_group_[current_palette_id_]);
|
||||
rom()->UpdateBitmap(&graphics_bin_[block]);
|
||||
}
|
||||
refresh_graphics_ = false;
|
||||
}
|
||||
|
||||
DrawToolset();
|
||||
|
||||
if (palette_showing_) {
|
||||
ImGui::Begin("Palette Editor", &palette_showing_, 0);
|
||||
current_palette_ =
|
||||
rom()->palette_group("dungeon_main")[current_palette_group_id_];
|
||||
gui::SelectablePalettePipeline(current_palette_id_, refresh_graphics_,
|
||||
current_palette_);
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("#DungeonEditTable", 3, kDungeonTableFlags,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Room Selector");
|
||||
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableSetupColumn("Object Selector");
|
||||
TableHeadersRow();
|
||||
TableNextRow();
|
||||
|
||||
TableNextColumn();
|
||||
DrawRoomSelector();
|
||||
|
||||
TableNextColumn();
|
||||
DrawDungeonTabView();
|
||||
|
||||
TableNextColumn();
|
||||
DrawTileSelector();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawToolset() {
|
||||
if (ImGui::BeginTable("DWToolset", 13, ImGuiTableFlags_SizingFixedFit,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("#undoTool");
|
||||
TableSetupColumn("#redoTool");
|
||||
TableSetupColumn("#separator");
|
||||
TableSetupColumn("#anyTool");
|
||||
|
||||
TableSetupColumn("#bg1Tool");
|
||||
TableSetupColumn("#bg2Tool");
|
||||
TableSetupColumn("#bg3Tool");
|
||||
TableSetupColumn("#separator");
|
||||
TableSetupColumn("#spriteTool");
|
||||
TableSetupColumn("#itemTool");
|
||||
TableSetupColumn("#doorTool");
|
||||
TableSetupColumn("#blockTool");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button(ICON_MD_UNDO)) {
|
||||
PRINT_IF_ERROR(Undo());
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button(ICON_MD_REDO)) {
|
||||
PRINT_IF_ERROR(Redo());
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(ICON_MD_MORE_VERT);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_FILTER_NONE,
|
||||
background_type_ == kBackgroundAny)) {
|
||||
background_type_ = kBackgroundAny;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_FILTER_1,
|
||||
background_type_ == kBackground1)) {
|
||||
background_type_ = kBackground1;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_FILTER_2,
|
||||
background_type_ == kBackground2)) {
|
||||
background_type_ = kBackground2;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_FILTER_3,
|
||||
background_type_ == kBackground3)) {
|
||||
background_type_ = kBackground3;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(ICON_MD_MORE_VERT);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) {
|
||||
placement_type_ = kSprite;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Sprites");
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) {
|
||||
placement_type_ = kItem;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Items");
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) {
|
||||
placement_type_ = kDoor;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Doors");
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_SQUARE, placement_type_ == kBlock)) {
|
||||
placement_type_ = kBlock;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Blocks");
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button(ICON_MD_PALETTE)) {
|
||||
palette_showing_ = !palette_showing_;
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawRoomSelector() {
|
||||
if (rom()->isLoaded()) {
|
||||
gui::InputHexWord("Room ID", ¤t_room_id_);
|
||||
gui::InputHex("Palette ID", ¤t_palette_id_);
|
||||
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
int i = 0;
|
||||
for (const auto each_room_name : zelda3::dungeon::kRoomNames) {
|
||||
ImGui::Selectable(each_room_name.data(), current_room_id_ == i,
|
||||
ImGuiSelectableFlags_AllowDoubleClick);
|
||||
if (ImGui::IsItemClicked()) {
|
||||
active_rooms_.push_back(i);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawDungeonTabView() {
|
||||
static int next_tab_id = 0;
|
||||
|
||||
if (ImGui::BeginTabBar("MyTabBar", kDungeonTabBarFlags)) {
|
||||
// TODO: Manage the room that is being added to the tab bar.
|
||||
if (ImGui::TabItemButton("+", kDungeonTabFlags)) {
|
||||
active_rooms_.push_back(next_tab_id++); // Add new tab
|
||||
}
|
||||
|
||||
// Submit our regular tabs
|
||||
for (int n = 0; n < active_rooms_.Size;) {
|
||||
bool open = true;
|
||||
|
||||
if (ImGui::BeginTabItem(
|
||||
zelda3::dungeon::kRoomNames[active_rooms_[n]].data(), &open,
|
||||
ImGuiTabItemFlags_None)) {
|
||||
DrawDungeonCanvas(active_rooms_[n]);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (!open)
|
||||
active_rooms_.erase(active_rooms_.Data + n);
|
||||
else
|
||||
n++;
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
||||
ImGui::BeginGroup();
|
||||
|
||||
gui::InputHexByte("Layout", &rooms_[room_id].layout);
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::InputHexByte("Blockset", &rooms_[room_id].blockset);
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::InputHexByte("Spriteset", &rooms_[room_id].spriteset);
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::InputHexByte("Palette", &rooms_[room_id].palette);
|
||||
|
||||
gui::InputHexByte("Floor1", &rooms_[room_id].floor1);
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::InputHexByte("Floor2", &rooms_[room_id].floor2);
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::InputHexWord("Message ID", &rooms_[room_id].message_id_);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::EndGroup();
|
||||
|
||||
canvas_.DrawBackground();
|
||||
canvas_.DrawContextMenu();
|
||||
canvas_.DrawGrid();
|
||||
canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawToolset() {
|
||||
if (ImGui::BeginTable("DWToolset", 9, toolset_table_flags_, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("#undoTool");
|
||||
ImGui::TableSetupColumn("#redoTool");
|
||||
ImGui::TableSetupColumn("#history");
|
||||
ImGui::TableSetupColumn("#separator");
|
||||
ImGui::TableSetupColumn("#bg1Tool");
|
||||
ImGui::TableSetupColumn("#bg2Tool");
|
||||
ImGui::TableSetupColumn("#bg3Tool");
|
||||
ImGui::TableSetupColumn("#itemTool");
|
||||
ImGui::TableSetupColumn("#spriteTool");
|
||||
void DungeonEditor::DrawRoomGraphics() {
|
||||
const auto height = 0x40;
|
||||
room_gfx_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
|
||||
room_gfx_canvas_.DrawContextMenu();
|
||||
room_gfx_canvas_.DrawTileSelector(32);
|
||||
if (is_loaded_) {
|
||||
auto blocks = rooms_[current_room_id_].blocks();
|
||||
int current_block = 0;
|
||||
for (int block : blocks) {
|
||||
int offset = height * (current_block + 1);
|
||||
int top_left_y = room_gfx_canvas_.zero_point().y + 2;
|
||||
if (current_block >= 1) {
|
||||
top_left_y = room_gfx_canvas_.zero_point().y + height * current_block;
|
||||
}
|
||||
room_gfx_canvas_.GetDrawList()->AddImage(
|
||||
(void*)graphics_bin_[block].texture(),
|
||||
ImVec2(room_gfx_canvas_.zero_point().x + 2, top_left_y),
|
||||
ImVec2(room_gfx_canvas_.zero_point().x + 0x100,
|
||||
room_gfx_canvas_.zero_point().y + offset));
|
||||
current_block += 1;
|
||||
}
|
||||
}
|
||||
room_gfx_canvas_.DrawGrid(32.0f);
|
||||
room_gfx_canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawTileSelector() {
|
||||
if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
|
||||
if (ImGui::BeginTabItem("Room Graphics")) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)3);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
DrawRoomGraphics();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("Object Renderer")) {
|
||||
DrawObjectRenderer();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawObjectRenderer() {
|
||||
if (ImGui::BeginTable("DungeonObjectEditorTable", 2, kDungeonObjectTableFlags,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Dungeon Objects", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableSetupColumn("Canvas");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_UNDO);
|
||||
ImGui::BeginChild("DungeonObjectButtons", ImVec2(250, 0), true);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_REDO);
|
||||
int selected_object = 0;
|
||||
int i = 0;
|
||||
for (const auto object_name : zelda3::dungeon::Type1RoomObjectNames) {
|
||||
if (ImGui::Selectable(object_name.data(), selected_object == i)) {
|
||||
selected_object = i;
|
||||
current_object_ = i;
|
||||
object_renderer_.LoadObject(i,
|
||||
rooms_[current_room_id_].mutable_blocks());
|
||||
rom()->RenderBitmap(object_renderer_.bitmap());
|
||||
object_loaded_ = true;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_MANAGE_HISTORY);
|
||||
ImGui::EndChild();
|
||||
|
||||
// Right side of the table - Canvas
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(ICON_MD_MORE_VERT);
|
||||
ImGui::BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1),
|
||||
true);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_FILTER_1);
|
||||
object_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
|
||||
object_canvas_.DrawContextMenu();
|
||||
object_canvas_.DrawTileSelector(32);
|
||||
if (object_loaded_) {
|
||||
object_canvas_.DrawBitmap(*object_renderer_.bitmap(), 0, 0);
|
||||
}
|
||||
object_canvas_.DrawGrid(32.0f);
|
||||
object_canvas_.DrawOverlay();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_FILTER_2);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_FILTER_3);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_GRASS);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_PEST_CONTROL_RODENT);
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
// if (object_loaded_) {
|
||||
// ImGui::Begin("Memory Viewer", &object_loaded_, 0);
|
||||
// auto memory = object_renderer_.memory();
|
||||
// static MemoryEditor mem_edit;
|
||||
// mem_edit.DrawContents((void*)object_renderer_.memory_ptr(),
|
||||
// memory.size());
|
||||
// ImGui::End();
|
||||
// }
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
|
||||
@@ -3,22 +3,97 @@
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
class DungeonEditor {
|
||||
|
||||
constexpr ImGuiTabItemFlags kDungeonTabFlags =
|
||||
ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip;
|
||||
|
||||
constexpr ImGuiTabBarFlags kDungeonTabBarFlags =
|
||||
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
|
||||
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
||||
ImGuiTabBarFlags_TabListPopupButton;
|
||||
|
||||
constexpr ImGuiTableFlags kDungeonTableFlags =
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||
ImGuiTableFlags_BordersV;
|
||||
|
||||
class DungeonEditor : public Editor,
|
||||
public SharedROM,
|
||||
public core::ExperimentFlags {
|
||||
public:
|
||||
void Update();
|
||||
absl::Status Update() override;
|
||||
absl::Status Cut() override { return absl::OkStatus(); }
|
||||
absl::Status Copy() override { return absl::OkStatus(); }
|
||||
absl::Status Paste() override { return absl::OkStatus(); }
|
||||
absl::Status Undo() override { return absl::OkStatus(); }
|
||||
absl::Status Redo() override { return absl::OkStatus(); }
|
||||
|
||||
private:
|
||||
void DrawToolset();
|
||||
void DrawRoomSelector();
|
||||
|
||||
void DrawDungeonTabView();
|
||||
void DrawDungeonCanvas(int room_id);
|
||||
|
||||
void DrawRoomGraphics();
|
||||
void DrawTileSelector();
|
||||
void DrawObjectRenderer();
|
||||
|
||||
enum BackgroundType {
|
||||
kNoBackground,
|
||||
kBackground1,
|
||||
kBackground2,
|
||||
kBackground3,
|
||||
kBackgroundAny,
|
||||
};
|
||||
enum PlacementType { kNoType, kSprite, kItem, kDoor, kBlock };
|
||||
|
||||
int background_type_ = kNoBackground;
|
||||
int placement_type_ = kNoType;
|
||||
int current_object_ = 0;
|
||||
|
||||
bool is_loaded_ = false;
|
||||
bool object_loaded_ = false;
|
||||
bool palette_showing_ = false;
|
||||
bool refresh_graphics_ = false;
|
||||
bool show_object_render_ = false;
|
||||
|
||||
uint16_t current_room_id_ = 0;
|
||||
uint64_t current_palette_id_ = 0;
|
||||
uint64_t current_palette_group_id_ = 0;
|
||||
|
||||
ImVector<int> active_rooms_;
|
||||
|
||||
PaletteEditor palette_editor_;
|
||||
gfx::SNESPalette current_palette_;
|
||||
gfx::SNESPalette full_palette_;
|
||||
gfx::PaletteGroup current_palette_group_;
|
||||
|
||||
gui::Canvas canvas_;
|
||||
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
|
||||
gui::Canvas room_gfx_canvas_;
|
||||
gui::Canvas object_canvas_;
|
||||
|
||||
gfx::Bitmap room_gfx_bmp_;
|
||||
gfx::BitmapTable graphics_bin_;
|
||||
|
||||
std::vector<gfx::Bitmap*> room_gfx_sheets_;
|
||||
std::vector<zelda3::dungeon::Room> rooms_;
|
||||
std::vector<gfx::BitmapManager> room_graphics_;
|
||||
zelda3::dungeon::DungeonObjectRenderer object_renderer_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
768
src/app/editor/graphics_editor.cc
Normal file
768
src/app/editor/graphics_editor.cc
Normal file
@@ -0,0 +1,768 @@
|
||||
#include "app/editor/graphics_editor.h"
|
||||
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/platform/clipboard.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/compression.h"
|
||||
#include "app/gfx/scad_format.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/gui/style.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
using ImGui::Button;
|
||||
using ImGui::InputInt;
|
||||
using ImGui::InputText;
|
||||
using ImGui::SameLine;
|
||||
|
||||
constexpr ImGuiTableFlags kGfxEditTableFlags =
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
|
||||
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
|
||||
ImGuiTableFlags_SizingFixedFit;
|
||||
|
||||
constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
|
||||
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
|
||||
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
||||
ImGuiTabBarFlags_TabListPopupButton;
|
||||
|
||||
absl::Status GraphicsEditor::Update() {
|
||||
TAB_BAR("##TabBar")
|
||||
status_ = UpdateGfxEdit();
|
||||
status_ = UpdateScadView();
|
||||
status_ = UpdateLinkGfxView();
|
||||
END_TAB_BAR()
|
||||
CLEAR_AND_RETURN_STATUS(status_)
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdateGfxEdit() {
|
||||
TAB_ITEM("Graphics Editor")
|
||||
|
||||
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
|
||||
ImVec2(0, 0))) {
|
||||
for (const auto& name : kGfxEditColumnNames)
|
||||
ImGui::TableSetupColumn(name.data());
|
||||
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
NEXT_COLUMN();
|
||||
status_ = UpdateGfxSheetList();
|
||||
|
||||
NEXT_COLUMN();
|
||||
if (rom()->isLoaded()) {
|
||||
DrawGfxEditToolset();
|
||||
status_ = UpdateGfxTabView();
|
||||
}
|
||||
|
||||
NEXT_COLUMN();
|
||||
if (rom()->isLoaded()) {
|
||||
status_ = UpdatePaletteColumn();
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
|
||||
END_TAB_ITEM()
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void GraphicsEditor::DrawGfxEditToolset() {
|
||||
if (ImGui::BeginTable("##GfxEditToolset", 9, ImGuiTableFlags_SizingFixedFit,
|
||||
ImVec2(0, 0))) {
|
||||
for (const auto& name :
|
||||
{"Select", "Pencil", "Fill", "Copy Sheet", "Paste Sheet", "Zoom Out",
|
||||
"Zoom In", "Current Color", "Tile Size"})
|
||||
ImGui::TableSetupColumn(name);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (Button(ICON_MD_SELECT_ALL)) {
|
||||
gfx_edit_mode_ = GfxEditMode::kSelect;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (Button(ICON_MD_DRAW)) {
|
||||
gfx_edit_mode_ = GfxEditMode::kPencil;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (Button(ICON_MD_FORMAT_COLOR_FILL)) {
|
||||
gfx_edit_mode_ = GfxEditMode::kFill;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (Button(ICON_MD_CONTENT_COPY)) {
|
||||
std::vector<uint8_t> png_data =
|
||||
rom()->bitmap_manager().GetBitmap(current_sheet_)->GetPngData();
|
||||
CopyImageToClipboard(png_data);
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (Button(ICON_MD_CONTENT_PASTE)) {
|
||||
std::vector<uint8_t> png_data;
|
||||
int width, height;
|
||||
GetImageFromClipboard(png_data, width, height);
|
||||
if (png_data.size() > 0) {
|
||||
rom()
|
||||
->bitmap_manager()
|
||||
.GetBitmap(current_sheet_)
|
||||
->LoadFromPngData(png_data, width, height);
|
||||
rom()->UpdateBitmap(rom()
|
||||
->mutable_bitmap_manager()
|
||||
->mutable_bitmap(current_sheet_)
|
||||
.get());
|
||||
}
|
||||
}
|
||||
HOVER_HINT("Paste from Clipboard");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (Button(ICON_MD_ZOOM_OUT)) {
|
||||
if (current_scale_ >= 0.0f) {
|
||||
current_scale_ -= 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (Button(ICON_MD_ZOOM_IN)) {
|
||||
if (current_scale_ <= 16.0f) {
|
||||
current_scale_ += 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
auto bitmap = rom()->bitmap_manager()[current_sheet_];
|
||||
auto palette = bitmap->palette();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
ImGui::SameLine();
|
||||
auto color =
|
||||
ImVec4(palette[i].GetRGB().x / 255.0f, palette[i].GetRGB().y / 255.0f,
|
||||
palette[i].GetRGB().z / 255.0f, 255.0f);
|
||||
if (ImGui::ColorButton(absl::StrFormat("Palette Color %d", i).c_str(),
|
||||
color)) {
|
||||
current_color_ = color;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
gui::InputHexByte("Tile Size", &tile_size_, 0x01);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdateGfxSheetList() {
|
||||
ImGui::BeginChild(
|
||||
"##GfxEditChild", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
for (auto& [key, value] : rom()->bitmap_manager()) {
|
||||
ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(),
|
||||
ImVec2(0x100 + 1, 0x40 + 1), true,
|
||||
ImGuiWindowFlags_NoDecoration);
|
||||
ImGui::PopStyleVar();
|
||||
gui::Canvas graphics_bin_canvas_;
|
||||
auto select_tile_event = [&]() {
|
||||
if (value.get()->IsActive()) {
|
||||
auto texture = value.get()->texture();
|
||||
graphics_bin_canvas_.GetDrawList()->AddImage(
|
||||
(void*)texture,
|
||||
ImVec2(graphics_bin_canvas_.zero_point().x + 2,
|
||||
graphics_bin_canvas_.zero_point().y + 2),
|
||||
ImVec2(graphics_bin_canvas_.zero_point().x +
|
||||
value.get()->width() * sheet_scale_,
|
||||
graphics_bin_canvas_.zero_point().y +
|
||||
value.get()->height() * sheet_scale_));
|
||||
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||
current_sheet_ = key;
|
||||
open_sheets_.insert(key);
|
||||
}
|
||||
|
||||
// Add a slightly transparent rectangle behind the text
|
||||
ImVec2 text_pos(graphics_bin_canvas_.zero_point().x + 2,
|
||||
graphics_bin_canvas_.zero_point().y + 2);
|
||||
ImVec2 text_size =
|
||||
ImGui::CalcTextSize(absl::StrFormat("%02X", key).c_str());
|
||||
ImVec2 rent_min(text_pos.x, text_pos.y);
|
||||
ImVec2 rent_max(text_pos.x + text_size.x, text_pos.y + text_size.y);
|
||||
|
||||
graphics_bin_canvas_.GetDrawList()->AddRectFilled(
|
||||
rent_min, rent_max, IM_COL32(0, 125, 0, 128));
|
||||
|
||||
graphics_bin_canvas_.GetDrawList()->AddText(
|
||||
text_pos, IM_COL32(125, 255, 125, 255),
|
||||
absl::StrFormat("%02X", key).c_str());
|
||||
}
|
||||
};
|
||||
|
||||
graphics_bin_canvas_.UpdateEvent(
|
||||
select_tile_event, ImVec2(0x100 + 1, 0x40 + 1), 0x20, sheet_scale_,
|
||||
/*grid_size=*/16.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::EndChild();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdateGfxTabView() {
|
||||
static int next_tab_id = 0;
|
||||
|
||||
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
|
||||
if (ImGui::TabItemButton(
|
||||
"+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) {
|
||||
open_sheets_.insert(next_tab_id++);
|
||||
}
|
||||
|
||||
for (auto& sheet_id : open_sheets_) {
|
||||
bool open = true;
|
||||
if (ImGui::BeginTabItem(absl::StrFormat("%d", sheet_id).c_str(), &open,
|
||||
ImGuiTabItemFlags_None)) {
|
||||
current_sheet_ = sheet_id;
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||
release_queue_.push(sheet_id);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
||||
release_queue_.push(sheet_id);
|
||||
child_window_sheets_.insert(sheet_id);
|
||||
}
|
||||
}
|
||||
|
||||
const auto child_id =
|
||||
absl::StrFormat("##GfxEditPaletteChildWindow%d", sheet_id);
|
||||
ImGui::BeginChild(child_id.c_str(), ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar |
|
||||
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
|
||||
|
||||
auto draw_tile_event = [&]() {
|
||||
gfx::Bitmap& current_bitmap =
|
||||
*rom()->mutable_bitmap_manager()->mutable_bitmap(sheet_id);
|
||||
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, current_bitmap,
|
||||
current_color_);
|
||||
rom()->UpdateBitmap(¤t_bitmap);
|
||||
};
|
||||
|
||||
auto size = ImVec2(0x80, 0x20);
|
||||
current_sheet_canvas_.UpdateColorPainter(
|
||||
*rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event,
|
||||
size, tile_size_, current_scale_, 8.0f);
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (!open) release_queue_.push(sheet_id);
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
// Release any tabs that were closed
|
||||
while (!release_queue_.empty()) {
|
||||
auto sheet_id = release_queue_.top();
|
||||
open_sheets_.erase(sheet_id);
|
||||
release_queue_.pop();
|
||||
}
|
||||
|
||||
// Draw any child windows that were created
|
||||
if (!child_window_sheets_.empty()) {
|
||||
int id_to_release = -1;
|
||||
for (const auto& id : child_window_sheets_) {
|
||||
bool active = true;
|
||||
ImGui::SetNextWindowPos(ImGui::GetIO().MousePos, ImGuiCond_Once);
|
||||
ImGui::SetNextWindowSize(ImVec2(0x100 + 1 * 16, 0x40 + 1 * 16),
|
||||
ImGuiCond_Once);
|
||||
ImGui::Begin(absl::StrFormat("##GfxEditPaletteChildWindow%d", id).c_str(),
|
||||
&active, ImGuiWindowFlags_AlwaysUseWindowPadding);
|
||||
current_sheet_ = id;
|
||||
current_sheet_canvas_.UpdateColorPainter(
|
||||
*rom()->bitmap_manager()[id], current_color_,
|
||||
[&]() {
|
||||
|
||||
},
|
||||
ImVec2(0x100, 0x40), tile_size_, current_scale_, 8.0f);
|
||||
ImGui::End();
|
||||
|
||||
if (active == false) {
|
||||
id_to_release = id;
|
||||
}
|
||||
}
|
||||
if (id_to_release != -1) {
|
||||
child_window_sheets_.erase(id_to_release);
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdatePaletteColumn() {
|
||||
auto palette_group = rom()->palette_group(
|
||||
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
|
||||
|
||||
auto palette = palette_group[edit_palette_index_];
|
||||
|
||||
if (rom()->isLoaded()) {
|
||||
gui::TextWithSeparators("ROM Palette");
|
||||
ImGui::SetNextItemWidth(100.f);
|
||||
ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_,
|
||||
kPaletteGroupAddressesKeys,
|
||||
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
|
||||
ImGui::SetNextItemWidth(100.f);
|
||||
gui::InputHex("Palette Group Index", &edit_palette_index_);
|
||||
}
|
||||
|
||||
gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_,
|
||||
palette);
|
||||
|
||||
if (refresh_graphics_) {
|
||||
rom()->bitmap_manager()[current_sheet_]->ApplyPaletteWithTransparent(
|
||||
palette, edit_palette_sub_index_);
|
||||
rom()->UpdateBitmap(
|
||||
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_).get());
|
||||
refresh_graphics_ = false;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdateLinkGfxView() {
|
||||
TAB_ITEM("Player Animations")
|
||||
|
||||
const auto link_gfx_offset = 0x80000;
|
||||
const auto link_gfx_length = 0x7000;
|
||||
|
||||
// Load Links graphics from the ROM
|
||||
RETURN_IF_ERROR(rom()->LoadLinkGraphics());
|
||||
|
||||
// Split it into the pose data frames
|
||||
// Create an animation step display for the poses
|
||||
// Allow the user to modify the frames used in an anim step
|
||||
// LinkOAM_AnimationSteps:
|
||||
// #_0D85FB
|
||||
|
||||
END_TAB_ITEM()
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdateScadView() {
|
||||
TAB_ITEM("Prototype")
|
||||
|
||||
RETURN_IF_ERROR(DrawToolset())
|
||||
|
||||
if (open_memory_editor_) {
|
||||
ImGui::Begin("Memory Editor", &open_memory_editor_);
|
||||
RETURN_IF_ERROR(DrawMemoryEditor())
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
BEGIN_TABLE("#gfxEditTable", 4, kGfxEditFlags)
|
||||
SETUP_COLUMN("File Import (BIN, CGX, ROM)")
|
||||
SETUP_COLUMN("Palette (COL)")
|
||||
ImGui::TableSetupColumn("Tilemaps and Objects (SCR, PNL, OBJ)",
|
||||
ImGuiTableColumnFlags_WidthFixed);
|
||||
SETUP_COLUMN("Graphics Preview")
|
||||
TABLE_HEADERS()
|
||||
NEXT_COLUMN() {
|
||||
status_ = DrawCgxImport();
|
||||
status_ = DrawClipboardImport();
|
||||
status_ = DrawFileImport();
|
||||
status_ = DrawExperimentalFeatures();
|
||||
}
|
||||
|
||||
NEXT_COLUMN() { status_ = DrawPaletteControls(); }
|
||||
|
||||
NEXT_COLUMN()
|
||||
gui::BitmapCanvasPipeline(scr_canvas_, scr_bitmap_, 0x200, 0x200, 0x20,
|
||||
scr_loaded_, false, 0);
|
||||
status_ = DrawScrImport();
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (super_donkey_) {
|
||||
if (refresh_graphics_) {
|
||||
for (int i = 0; i < graphics_bin_.size(); i++) {
|
||||
graphics_bin_[i].ApplyPalette(
|
||||
col_file_palette_group_[current_palette_index_]);
|
||||
rom()->UpdateBitmap(&graphics_bin_[i]);
|
||||
}
|
||||
refresh_graphics_ = false;
|
||||
}
|
||||
// Load the full graphics space from `super_donkey_1.bin`
|
||||
gui::GraphicsBinCanvasPipeline(0x100, 0x40, 0x20, num_sheets_to_load_, 3,
|
||||
super_donkey_, graphics_bin_);
|
||||
} else if (cgx_loaded_ && col_file_) {
|
||||
// Load the CGX graphics
|
||||
gui::BitmapCanvasPipeline(import_canvas_, cgx_bitmap_, 0x100, 16384, 0x20,
|
||||
cgx_loaded_, true, 5);
|
||||
} else {
|
||||
// Load the BIN/Clipboard Graphics
|
||||
gui::BitmapCanvasPipeline(import_canvas_, bin_bitmap_, 0x100, 16384, 0x20,
|
||||
gfx_loaded_, true, 2);
|
||||
}
|
||||
END_TABLE()
|
||||
|
||||
END_TAB_ITEM()
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawToolset() {
|
||||
if (ImGui::BeginTable("GraphicsToolset", 2, ImGuiTableFlags_SizingFixedFit,
|
||||
ImVec2(0, 0))) {
|
||||
for (const auto& name : kGfxToolsetColumnNames)
|
||||
ImGui::TableSetupColumn(name.data());
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (Button(ICON_MD_MEMORY)) {
|
||||
if (!open_memory_editor_) {
|
||||
open_memory_editor_ = true;
|
||||
} else {
|
||||
open_memory_editor_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
TEXT_COLUMN("Open Memory Editor") // Separator
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawCgxImport() {
|
||||
gui::TextWithSeparators("Cgx Import");
|
||||
InputInt("BPP", ¤t_bpp_);
|
||||
|
||||
InputText("##CGXFile", cgx_file_name_, sizeof(cgx_file_name_));
|
||||
SameLine();
|
||||
|
||||
gui::FileDialogPipeline("ImportCgxKey", ".CGX,.cgx\0", "Open CGX", [this]() {
|
||||
strncpy(cgx_file_path_,
|
||||
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(cgx_file_path_));
|
||||
strncpy(cgx_file_name_,
|
||||
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(),
|
||||
sizeof(cgx_file_name_));
|
||||
is_open_ = true;
|
||||
cgx_loaded_ = true;
|
||||
});
|
||||
gui::ButtonPipe("Copy CGX Path",
|
||||
[this]() { ImGui::SetClipboardText(cgx_file_path_); });
|
||||
|
||||
gui::ButtonPipe("Load CGX Data", [this]() {
|
||||
status_ = gfx::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_,
|
||||
decoded_cgx_, extra_cgx_data_);
|
||||
|
||||
cgx_bitmap_.InitializeFromData(0x80, 0x200, 8, decoded_cgx_);
|
||||
if (col_file_) {
|
||||
cgx_bitmap_.ApplyPalette(decoded_col_);
|
||||
rom()->RenderBitmap(&cgx_bitmap_);
|
||||
}
|
||||
});
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawScrImport() {
|
||||
InputText("##ScrFile", scr_file_name_, sizeof(scr_file_name_));
|
||||
|
||||
gui::FileDialogPipeline(
|
||||
"ImportScrKey", ".SCR,.scr,.BAK\0", "Open SCR", [this]() {
|
||||
strncpy(scr_file_path_,
|
||||
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(scr_file_path_));
|
||||
strncpy(scr_file_name_,
|
||||
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(),
|
||||
sizeof(scr_file_name_));
|
||||
is_open_ = true;
|
||||
scr_loaded_ = true;
|
||||
});
|
||||
|
||||
InputInt("SCR Mod", &scr_mod_value_);
|
||||
|
||||
gui::ButtonPipe("Load Scr Data", [this]() {
|
||||
status_ = gfx::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
|
||||
|
||||
decoded_scr_data_.resize(0x100 * 0x100);
|
||||
status_ = gfx::DrawScrWithCgx(current_bpp_, scr_data_, decoded_scr_data_,
|
||||
decoded_cgx_);
|
||||
|
||||
scr_bitmap_.InitializeFromData(0x100, 0x100, 8, decoded_scr_data_);
|
||||
if (scr_loaded_) {
|
||||
scr_bitmap_.ApplyPalette(decoded_col_);
|
||||
rom()->RenderBitmap(&scr_bitmap_);
|
||||
}
|
||||
});
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawPaletteControls() {
|
||||
gui::TextWithSeparators("COL Import");
|
||||
InputText("##ColFile", col_file_name_, sizeof(col_file_name_));
|
||||
SameLine();
|
||||
|
||||
gui::FileDialogPipeline(
|
||||
"ImportColKey", ".COL,.col,.BAK,.bak\0", "Open COL", [this]() {
|
||||
strncpy(col_file_path_,
|
||||
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(col_file_path_));
|
||||
strncpy(col_file_name_,
|
||||
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(),
|
||||
sizeof(col_file_name_));
|
||||
status_ = temp_rom_.LoadFromFile(col_file_path_,
|
||||
/*z3_load=*/false);
|
||||
auto col_data_ = gfx::GetColFileData(temp_rom_.data());
|
||||
if (col_file_palette_group_.size() != 0) {
|
||||
col_file_palette_group_.Clear();
|
||||
}
|
||||
col_file_palette_group_ = gfx::CreatePaletteGroupFromColFile(col_data_);
|
||||
col_file_palette_ = gfx::SNESPalette(col_data_);
|
||||
|
||||
// gigaleak dev format based code
|
||||
decoded_col_ = gfx::DecodeColFile(col_file_path_);
|
||||
col_file_ = true;
|
||||
is_open_ = true;
|
||||
});
|
||||
|
||||
gui::ButtonPipe("Copy COL Path",
|
||||
[this]() { ImGui::SetClipboardText(col_file_path_); });
|
||||
|
||||
if (rom()->isLoaded()) {
|
||||
gui::TextWithSeparators("ROM Palette");
|
||||
gui::InputHex("Palette Index", ¤t_palette_index_);
|
||||
ImGui::Combo("Palette", ¤t_palette_, kPaletteGroupAddressesKeys,
|
||||
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
|
||||
}
|
||||
|
||||
if (col_file_palette_.size() != 0) {
|
||||
gui::SelectablePalettePipeline(current_palette_index_, refresh_graphics_,
|
||||
col_file_palette_);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawObjImport() {
|
||||
gui::TextWithSeparators("OBJ Import");
|
||||
|
||||
InputText("##ObjFile", obj_file_path_, sizeof(obj_file_path_));
|
||||
SameLine();
|
||||
|
||||
gui::FileDialogPipeline(
|
||||
"ImportObjKey", ".obj,.OBJ,.bak,.BAK\0", "Open OBJ", [this]() {
|
||||
strncpy(file_path_,
|
||||
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(file_path_));
|
||||
status_ = temp_rom_.LoadFromFile(file_path_);
|
||||
is_open_ = true;
|
||||
});
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawTilemapImport() {
|
||||
gui::TextWithSeparators("Tilemap Import");
|
||||
|
||||
InputText("##TMapFile", tilemap_file_path_, sizeof(tilemap_file_path_));
|
||||
SameLine();
|
||||
|
||||
gui::FileDialogPipeline(
|
||||
"ImportTilemapKey", ".DAT,.dat,.BIN,.bin,.hex,.HEX\0", "Open Tilemap",
|
||||
[this]() {
|
||||
strncpy(tilemap_file_path_,
|
||||
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(tilemap_file_path_));
|
||||
status_ = tilemap_rom_.LoadFromFile(tilemap_file_path_);
|
||||
|
||||
// Extract the high and low bytes from the file.
|
||||
auto decomp_sheet = gfx::lc_lz2::DecompressV2(
|
||||
tilemap_rom_.data(), gfx::lc_lz2::kNintendoMode1);
|
||||
tilemap_loaded_ = true;
|
||||
is_open_ = true;
|
||||
});
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawFileImport() {
|
||||
gui::TextWithSeparators("BIN Import");
|
||||
|
||||
InputText("##ROMFile", file_path_, sizeof(file_path_));
|
||||
SameLine();
|
||||
|
||||
gui::FileDialogPipeline("ImportDlgKey", ".bin,.hex\0", "Open BIN", [this]() {
|
||||
strncpy(file_path_, ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(file_path_));
|
||||
status_ = temp_rom_.LoadFromFile(file_path_);
|
||||
is_open_ = true;
|
||||
});
|
||||
|
||||
gui::ButtonPipe("Copy File Path",
|
||||
[this]() { ImGui::SetClipboardText(file_path_); });
|
||||
|
||||
gui::InputHex("BIN Offset", ¤t_offset_);
|
||||
gui::InputHex("BIN Size", &bin_size_);
|
||||
|
||||
if (Button("Decompress BIN")) {
|
||||
if (strlen(file_path_) > 0) {
|
||||
RETURN_IF_ERROR(DecompressImportData(bin_size_))
|
||||
} else {
|
||||
return absl::InvalidArgumentError(
|
||||
"Please select a file before importing.");
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawClipboardImport() {
|
||||
gui::TextWithSeparators("Clipboard Import");
|
||||
gui::ButtonPipe("Paste from Clipboard", [this]() {
|
||||
const char* text = ImGui::GetClipboardText();
|
||||
if (text) {
|
||||
const auto clipboard_data = Bytes(text, text + strlen(text));
|
||||
ImGui::MemFree((void*)text);
|
||||
status_ = temp_rom_.LoadFromBytes(clipboard_data);
|
||||
is_open_ = true;
|
||||
open_memory_editor_ = true;
|
||||
}
|
||||
});
|
||||
gui::InputHex("Offset", &clipboard_offset_);
|
||||
gui::InputHex("Size", &clipboard_size_);
|
||||
gui::InputHex("Num Sheets", &num_sheets_to_load_);
|
||||
|
||||
gui::ButtonPipe("Decompress Clipboard Data", [this]() {
|
||||
if (temp_rom_.isLoaded()) {
|
||||
status_ = DecompressImportData(0x40000);
|
||||
} else {
|
||||
status_ = absl::InvalidArgumentError(
|
||||
"Please paste data into the clipboard before "
|
||||
"decompressing.");
|
||||
}
|
||||
});
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawExperimentalFeatures() {
|
||||
gui::TextWithSeparators("Experimental");
|
||||
if (Button("Decompress Super Donkey Full")) {
|
||||
if (strlen(file_path_) > 0) {
|
||||
RETURN_IF_ERROR(DecompressSuperDonkey())
|
||||
} else {
|
||||
return absl::InvalidArgumentError(
|
||||
"Please select `super_donkey_1.bin` before "
|
||||
"importing.");
|
||||
}
|
||||
}
|
||||
ImGui::SetItemTooltip(
|
||||
"Requires `super_donkey_1.bin` to be imported under the "
|
||||
"BIN import section.");
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawMemoryEditor() {
|
||||
std::string title = "Memory Editor";
|
||||
if (is_open_) {
|
||||
static MemoryEditor mem_edit;
|
||||
mem_edit.DrawWindow(title.c_str(), temp_rom_.data(), temp_rom_.size());
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DecompressImportData(int size) {
|
||||
ASSIGN_OR_RETURN(import_data_, gfx::lc_lz2::DecompressV2(
|
||||
temp_rom_.data(), current_offset_, size))
|
||||
|
||||
auto converted_sheet = gfx::SnesTo8bppSheet(import_data_, 3);
|
||||
bin_bitmap_.Create(core::kTilesheetWidth, 0x2000, core::kTilesheetDepth,
|
||||
converted_sheet);
|
||||
|
||||
if (rom()->isLoaded()) {
|
||||
auto palette_group = rom()->palette_group("ow_main");
|
||||
z3_rom_palette_ = palette_group[current_palette_];
|
||||
if (col_file_) {
|
||||
bin_bitmap_.ApplyPalette(col_file_palette_);
|
||||
} else {
|
||||
bin_bitmap_.ApplyPalette(z3_rom_palette_);
|
||||
}
|
||||
}
|
||||
|
||||
rom()->RenderBitmap(&bin_bitmap_);
|
||||
gfx_loaded_ = true;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DecompressSuperDonkey() {
|
||||
int i = 0;
|
||||
for (const auto& offset : kSuperDonkeyTiles) {
|
||||
int offset_value =
|
||||
std::stoi(offset, nullptr, 16); // convert hex string to int
|
||||
ASSIGN_OR_RETURN(
|
||||
auto decompressed_data,
|
||||
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
|
||||
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
|
||||
graphics_bin_[i] =
|
||||
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
|
||||
core::kTilesheetDepth, converted_sheet);
|
||||
if (col_file_) {
|
||||
graphics_bin_[i].ApplyPalette(
|
||||
col_file_palette_group_[current_palette_index_]);
|
||||
} else {
|
||||
// ROM palette
|
||||
auto palette_group =
|
||||
rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]);
|
||||
z3_rom_palette_ = palette_group[current_palette_index_];
|
||||
graphics_bin_[i].ApplyPalette(z3_rom_palette_);
|
||||
}
|
||||
|
||||
rom()->RenderBitmap(&graphics_bin_[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
for (const auto& offset : kSuperDonkeySprites) {
|
||||
int offset_value =
|
||||
std::stoi(offset, nullptr, 16); // convert hex string to int
|
||||
ASSIGN_OR_RETURN(
|
||||
auto decompressed_data,
|
||||
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
|
||||
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
|
||||
graphics_bin_[i] =
|
||||
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
|
||||
core::kTilesheetDepth, converted_sheet);
|
||||
if (col_file_) {
|
||||
graphics_bin_[i].ApplyPalette(
|
||||
col_file_palette_group_[current_palette_index_]);
|
||||
} else {
|
||||
// ROM palette
|
||||
auto palette_group =
|
||||
rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]);
|
||||
z3_rom_palette_ = palette_group[current_palette_index_];
|
||||
graphics_bin_[i].ApplyPalette(z3_rom_palette_);
|
||||
}
|
||||
|
||||
rom()->RenderBitmap(&graphics_bin_[i]);
|
||||
i++;
|
||||
}
|
||||
super_donkey_ = true;
|
||||
num_sheets_to_load_ = i;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
193
src/app/editor/graphics_editor.h
Normal file
193
src/app/editor/graphics_editor.h
Normal file
@@ -0,0 +1,193 @@
|
||||
#ifndef YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
// "99973","A3D80",
|
||||
|
||||
const std::string kSuperDonkeyTiles[] = {
|
||||
"97C05", "98219", "9871E", "98C00", "99084", "995AF", "99DE0", "9A27E",
|
||||
"9A741", "9AC31", "9B07E", "9B55C", "9B963", "9BB99", "9C009", "9C4B4",
|
||||
"9C92B", "9CDD6", "9D2C2", "9E037", "9E527", "9EA56", "9EF65", "9FCD1",
|
||||
"A0193", "A059E", "A0B17", "A0FB6", "A14A5", "A1988", "A1E66", "A232B",
|
||||
"A27F0", "A2B6E", "A302C", "A3453", "A38CA", "A42BB", "A470C", "A4BA9",
|
||||
"A5089", "A5385", "A5742", "A5BCC", "A6017", "A6361", "A66F8"};
|
||||
|
||||
const std::string kSuperDonkeySprites[] = {
|
||||
"A8E5D", "A9435", "A9934", "A9D83", "AA2F1", "AA6D4", "AABE4", "AB127",
|
||||
"AB65A", "ABBDD", "AC38D", "AC797", "ACCC8", "AD0AE", "AD245", "AD554",
|
||||
"ADAAC", "ADECC", "AE453", "AE9D2", "AEF40", "AF3C9", "AF92E", "AFE9D",
|
||||
"B03D2", "B09AC", "B0F0C", "B1430", "B1859", "B1E01", "B229A", "B2854",
|
||||
"B2D27", "B31D7", "B3B58", "B40B5", "B45A5", "B4D64", "B5031", "B555F",
|
||||
"B5F30", "B6858", "B70DD", "B7526", "B79EC", "B7C83", "B80F7", "B85CC",
|
||||
"B8A3F", "B8F97", "B94F2", "B9A20", "B9E9A", "BA3A2", "BA8F6", "BACDC",
|
||||
"BB1F9", "BB781", "BBCCA", "BC26D", "BC7D4", "BCBB0", "BD082", "BD5FC",
|
||||
"BE115", "BE5C2", "BEB63", "BF0CB", "BF607", "BFA55", "BFD71", "C017D",
|
||||
"C0567", "C0981", "C0BA7", "C116D", "C166A", "C1FE0", "C24CE", "C2B19"};
|
||||
|
||||
constexpr const char* kPaletteGroupAddressesKeys[] = {
|
||||
"ow_main", "ow_aux", "ow_animated", "hud",
|
||||
"global_sprites", "armors", "swords", "shields",
|
||||
"sprites_aux1", "sprites_aux2", "sprites_aux3", "dungeon_main",
|
||||
"grass", "3d_object", "ow_mini_map",
|
||||
};
|
||||
|
||||
static constexpr std::string_view kGfxEditColumnNames[] = {
|
||||
"Tilesheets", "Current Graphics", "Palette Controls"};
|
||||
|
||||
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
|
||||
"#memoryEditor",
|
||||
"##separator_gfx1",
|
||||
};
|
||||
|
||||
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Resizable |
|
||||
ImGuiTableFlags_SizingStretchSame;
|
||||
|
||||
class GraphicsEditor : public SharedROM {
|
||||
public:
|
||||
absl::Status Update();
|
||||
|
||||
private:
|
||||
enum class GfxEditMode {
|
||||
kSelect,
|
||||
kPencil,
|
||||
kFill,
|
||||
};
|
||||
|
||||
// Graphics Editor Tab
|
||||
absl::Status UpdateGfxEdit();
|
||||
absl::Status UpdateGfxSheetList();
|
||||
absl::Status UpdateGfxTabView();
|
||||
absl::Status UpdatePaletteColumn();
|
||||
void DrawGfxEditToolset();
|
||||
|
||||
// Link Graphics Edit Tab
|
||||
absl::Status UpdateLinkGfxView();
|
||||
|
||||
// Prototype Graphics Viewer
|
||||
absl::Status UpdateScadView();
|
||||
|
||||
// Import Functions
|
||||
absl::Status DrawCgxImport();
|
||||
absl::Status DrawScrImport();
|
||||
absl::Status DrawFileImport();
|
||||
absl::Status DrawObjImport();
|
||||
absl::Status DrawTilemapImport();
|
||||
|
||||
// Other Functions
|
||||
absl::Status DrawToolset();
|
||||
absl::Status DrawPaletteControls();
|
||||
absl::Status DrawClipboardImport();
|
||||
absl::Status DrawExperimentalFeatures();
|
||||
absl::Status DrawMemoryEditor();
|
||||
|
||||
absl::Status DecompressImportData(int size);
|
||||
absl::Status DecompressSuperDonkey();
|
||||
|
||||
// Member Variables
|
||||
ImVec4 current_color_;
|
||||
uint16_t current_sheet_ = 0;
|
||||
uint8_t tile_size_ = 0x01;
|
||||
std::set<uint16_t> open_sheets_;
|
||||
std::set<uint16_t> child_window_sheets_;
|
||||
std::stack<uint16_t> release_queue_;
|
||||
uint64_t edit_palette_group_name_index_ = 0;
|
||||
uint64_t edit_palette_group_index_ = 0;
|
||||
uint64_t edit_palette_index_ = 0;
|
||||
uint64_t edit_palette_sub_index_ = 0;
|
||||
float sheet_scale_ = 2.0f;
|
||||
float current_scale_ = 4.0f;
|
||||
|
||||
// Prototype Graphics Viewer
|
||||
int current_palette_ = 0;
|
||||
uint64_t current_offset_ = 0;
|
||||
uint64_t current_size_ = 0;
|
||||
uint64_t current_palette_index_ = 0;
|
||||
int current_bpp_ = 0;
|
||||
int scr_mod_value_ = 0;
|
||||
|
||||
uint64_t num_sheets_to_load_ = 1;
|
||||
uint64_t bin_size_ = 0;
|
||||
uint64_t clipboard_offset_ = 0;
|
||||
uint64_t clipboard_size_ = 0;
|
||||
|
||||
bool refresh_graphics_ = false;
|
||||
bool open_memory_editor_ = false;
|
||||
bool gfx_loaded_ = false;
|
||||
bool is_open_ = false;
|
||||
bool super_donkey_ = false;
|
||||
bool col_file_ = false;
|
||||
bool cgx_loaded_ = false;
|
||||
bool scr_loaded_ = false;
|
||||
bool obj_loaded_ = false;
|
||||
bool tilemap_loaded_ = false;
|
||||
|
||||
char file_path_[256] = "";
|
||||
char col_file_path_[256] = "";
|
||||
char col_file_name_[256] = "";
|
||||
char cgx_file_path_[256] = "";
|
||||
char cgx_file_name_[256] = "";
|
||||
char scr_file_path_[256] = "";
|
||||
char scr_file_name_[256] = "";
|
||||
char obj_file_path_[256] = "";
|
||||
char tilemap_file_path_[256] = "";
|
||||
char tilemap_file_name_[256] = "";
|
||||
|
||||
GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect;
|
||||
|
||||
ROM temp_rom_;
|
||||
ROM tilemap_rom_;
|
||||
zelda3::Overworld overworld_;
|
||||
MemoryEditor cgx_memory_editor_;
|
||||
MemoryEditor col_memory_editor_;
|
||||
PaletteEditor palette_editor_;
|
||||
Bytes import_data_;
|
||||
Bytes graphics_buffer_;
|
||||
std::vector<uint8_t> decoded_cgx_;
|
||||
std::vector<uint8_t> cgx_data_;
|
||||
std::vector<uint8_t> extra_cgx_data_;
|
||||
std::vector<SDL_Color> decoded_col_;
|
||||
std::vector<uint8_t> scr_data_;
|
||||
std::vector<uint8_t> decoded_scr_data_;
|
||||
gfx::Bitmap cgx_bitmap_;
|
||||
gfx::Bitmap scr_bitmap_;
|
||||
gfx::Bitmap bin_bitmap_;
|
||||
gfx::Bitmap link_full_sheet_;
|
||||
gfx::BitmapTable graphics_bin_;
|
||||
gfx::BitmapTable clipboard_graphics_bin_;
|
||||
gfx::BitmapTable link_graphics_;
|
||||
gfx::PaletteGroup col_file_palette_group_;
|
||||
gfx::SNESPalette z3_rom_palette_;
|
||||
gfx::SNESPalette col_file_palette_;
|
||||
gfx::SNESPalette link_palette_;
|
||||
gui::Canvas import_canvas_;
|
||||
gui::Canvas scr_canvas_;
|
||||
gui::Canvas super_donkey_canvas_;
|
||||
gui::Canvas current_sheet_canvas_;
|
||||
absl::Status status_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||
@@ -7,18 +7,27 @@
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/editor/assembly_editor.h"
|
||||
#include "app/core/platform/file_dialog.h"
|
||||
#include "app/editor/dungeon_editor.h"
|
||||
#include "app/editor/music_editor.h"
|
||||
#include "app/editor/graphics_editor.h"
|
||||
#include "app/editor/modules/assembly_editor.h"
|
||||
#include "app/editor/modules/music_editor.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/editor/overworld_editor.h"
|
||||
#include "app/editor/screen_editor.h"
|
||||
#include "app/editor/sprite_editor.h"
|
||||
#include "app/emu/emulator.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/gui/style.h"
|
||||
#include "app/gui/widgets.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
#include "gui/widgets.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -32,9 +41,9 @@ constexpr ImGuiWindowFlags kMainEditorFlags =
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
|
||||
|
||||
void NewMasterFrame() {
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui::NewFrame();
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowPos(gui::kZeroPos);
|
||||
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
|
||||
ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
|
||||
|
||||
@@ -44,8 +53,8 @@ void NewMasterFrame() {
|
||||
}
|
||||
}
|
||||
|
||||
bool BeginCentered(const char *name) {
|
||||
ImGuiIO const &io = ImGui::GetIO();
|
||||
bool BeginCentered(const char* name) {
|
||||
ImGuiIO const& io = ImGui::GetIO();
|
||||
ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
|
||||
ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
ImGuiWindowFlags flags =
|
||||
@@ -54,30 +63,65 @@ bool BeginCentered(const char *name) {
|
||||
return ImGui::Begin(name, nullptr, flags);
|
||||
}
|
||||
|
||||
void DisplayStatus(absl::Status &status) {
|
||||
if (BeginCentered("StatusWindow")) {
|
||||
ImGui::Text("%s", status.ToString().c_str());
|
||||
ImGui::Spacing();
|
||||
ImGui::NextColumn();
|
||||
ImGui::Columns(1);
|
||||
ImGui::Separator();
|
||||
ImGui::NewLine();
|
||||
ImGui::SameLine(270);
|
||||
if (ImGui::Button("OK", ImVec2(200, 0))) {
|
||||
status = absl::OkStatus();
|
||||
class RecentFilesManager {
|
||||
public:
|
||||
RecentFilesManager(const std::string& filename) : filename_(filename) {}
|
||||
|
||||
void AddFile(const std::string& filePath) {
|
||||
// Add a file to the list, avoiding duplicates
|
||||
auto it = std::find(recentFiles_.begin(), recentFiles_.end(), filePath);
|
||||
if (it == recentFiles_.end()) {
|
||||
recentFiles_.push_back(filePath);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void Save() {
|
||||
std::ofstream file(filename_);
|
||||
if (!file.is_open()) {
|
||||
return; // Handle the error appropriately
|
||||
}
|
||||
|
||||
for (const auto& filePath : recentFiles_) {
|
||||
file << filePath << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void Load() {
|
||||
std::ifstream file(filename_);
|
||||
if (!file.is_open()) {
|
||||
return; // Handle the error appropriately
|
||||
}
|
||||
|
||||
recentFiles_.clear();
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (!line.empty()) {
|
||||
recentFiles_.push_back(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetRecentFiles() const {
|
||||
return recentFiles_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string filename_;
|
||||
std::vector<std::string> recentFiles_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
using ImGui::BeginMenu;
|
||||
using ImGui::MenuItem;
|
||||
using ImGui::Text;
|
||||
|
||||
void MasterEditor::SetupScreen(std::shared_ptr<SDL_Renderer> renderer) {
|
||||
sdl_renderer_ = renderer;
|
||||
rom_.SetupRenderer(renderer);
|
||||
rom()->SetupRenderer(renderer);
|
||||
}
|
||||
|
||||
void MasterEditor::UpdateScreen() {
|
||||
absl::Status MasterEditor::Update() {
|
||||
NewMasterFrame();
|
||||
|
||||
DrawYazeMenu();
|
||||
@@ -86,34 +130,72 @@ void MasterEditor::UpdateScreen() {
|
||||
DrawAboutPopup();
|
||||
DrawInfoPopup();
|
||||
|
||||
if (rom()->isLoaded() && !rom_assets_loaded_) {
|
||||
// Initialize overworld graphics, maps, and palettes
|
||||
RETURN_IF_ERROR(overworld_editor_.LoadGraphics());
|
||||
rom_assets_loaded_ = true;
|
||||
}
|
||||
|
||||
TAB_BAR("##TabBar")
|
||||
DrawOverworldEditor();
|
||||
DrawDungeonEditor();
|
||||
DrawMusicEditor();
|
||||
DrawSpriteEditor();
|
||||
DrawScreenEditor();
|
||||
DrawPaletteEditor();
|
||||
|
||||
gui::RenderTabItem("Overworld", [&]() {
|
||||
current_editor_ = &overworld_editor_;
|
||||
status_ = overworld_editor_.Update();
|
||||
});
|
||||
|
||||
gui::RenderTabItem("Dungeon", [&]() {
|
||||
current_editor_ = &dungeon_editor_;
|
||||
status_ = dungeon_editor_.Update();
|
||||
});
|
||||
|
||||
gui::RenderTabItem("Graphics",
|
||||
[&]() { status_ = graphics_editor_.Update(); });
|
||||
gui::RenderTabItem("Sprites", [&]() { status_ = sprite_editor_.Update(); });
|
||||
gui::RenderTabItem("Palettes", [&]() { status_ = palette_editor_.Update(); });
|
||||
gui::RenderTabItem("Screens", [&]() { screen_editor_.Update(); });
|
||||
gui::RenderTabItem("Music", [&]() { music_editor_.Update(); });
|
||||
END_TAB_BAR()
|
||||
|
||||
ImGui::End();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void MasterEditor::DrawFileDialog() {
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
|
||||
if (ImGuiFileDialog::Instance()->IsOk()) {
|
||||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
status_ = rom_.LoadFromFile(filePathName);
|
||||
overworld_editor_.SetupROM(rom_);
|
||||
screen_editor_.SetupROM(rom_);
|
||||
palette_editor_.SetupROM(rom_);
|
||||
}
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
gui::FileDialogPipeline("ChooseFileDlgKey", ".sfc,.smc", std::nullopt, [&]() {
|
||||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
status_ = rom()->LoadFromFile(filePathName);
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
|
||||
// Load existing recent files
|
||||
manager.Load();
|
||||
|
||||
// Add a new file
|
||||
manager.AddFile(filePathName);
|
||||
|
||||
// Save the updated list
|
||||
manager.Save();
|
||||
});
|
||||
}
|
||||
|
||||
void MasterEditor::DrawStatusPopup() {
|
||||
if (!status_.ok()) {
|
||||
DisplayStatus(status_);
|
||||
show_status_ = true;
|
||||
prev_status_ = status_;
|
||||
}
|
||||
|
||||
if (show_status_ && (BeginCentered("StatusWindow"))) {
|
||||
Text("%s", prev_status_.ToString().c_str());
|
||||
ImGui::Spacing();
|
||||
ImGui::NextColumn();
|
||||
ImGui::Columns(1);
|
||||
ImGui::Separator();
|
||||
ImGui::NewLine();
|
||||
ImGui::SameLine(270);
|
||||
if (ImGui::Button("OK", gui::kDefaultModalSize)) {
|
||||
show_status_ = false;
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,13 +203,13 @@ void MasterEditor::DrawAboutPopup() {
|
||||
if (about_) ImGui::OpenPopup("About");
|
||||
if (ImGui::BeginPopupModal("About", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Yet Another Zelda3 Editor - v0.02");
|
||||
ImGui::Text("Written by: scawful");
|
||||
Text("Yet Another Zelda3 Editor - v0.05");
|
||||
Text("Written by: scawful");
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Special Thanks: Zarby89, JaredBrian");
|
||||
Text("Special Thanks: Zarby89, JaredBrian");
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button("Close", ImVec2(200, 0))) {
|
||||
if (ImGui::Button("Close", gui::kDefaultModalSize)) {
|
||||
about_ = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
@@ -139,10 +221,10 @@ void MasterEditor::DrawInfoPopup() {
|
||||
if (rom_info_) ImGui::OpenPopup("ROM Information");
|
||||
if (ImGui::BeginPopupModal("ROM Information", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Title: %s", rom_.GetTitle());
|
||||
ImGui::Text("ROM Size: %ld", rom_.size());
|
||||
Text("Title: %s", rom()->title());
|
||||
Text("ROM Size: %ld", rom()->size());
|
||||
|
||||
if (ImGui::Button("Close", ImVec2(200, 0))) {
|
||||
if (ImGui::Button("Close", gui::kDefaultModalSize)) {
|
||||
rom_info_ = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
@@ -151,52 +233,145 @@ void MasterEditor::DrawInfoPopup() {
|
||||
}
|
||||
|
||||
void MasterEditor::DrawYazeMenu() {
|
||||
static bool show_display_settings = false;
|
||||
static bool show_command_line_interface = false;
|
||||
|
||||
MENU_BAR()
|
||||
DrawFileMenu();
|
||||
DrawEditMenu();
|
||||
DrawViewMenu();
|
||||
DrawHelpMenu();
|
||||
|
||||
ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::GetStyle().ItemSpacing.x -
|
||||
ImGui::CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x - 150);
|
||||
// Modify the style of the button to have no background color
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||
if (ImGui::Button(ICON_MD_DISPLAY_SETTINGS)) {
|
||||
show_display_settings = !show_display_settings;
|
||||
}
|
||||
|
||||
if (ImGui::Button(ICON_MD_TERMINAL)) {
|
||||
show_command_line_interface = !show_command_line_interface;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
Text("%s", absl::StrCat("yaze v", core::kYazeVersion).c_str());
|
||||
|
||||
END_MENU_BAR()
|
||||
|
||||
if (show_display_settings) {
|
||||
ImGui::Begin("Display Settings", &show_display_settings,
|
||||
ImGuiWindowFlags_None);
|
||||
gui::DrawDisplaySettings();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (show_command_line_interface) {
|
||||
ImGui::Begin("Command Line Interface", &show_command_line_interface,
|
||||
ImGuiWindowFlags_None);
|
||||
Text("Enter a command:");
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawFileMenu() const {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
|
||||
".sfc,.smc", ".");
|
||||
void MasterEditor::DrawFileMenu() {
|
||||
static bool save_as_menu = false;
|
||||
|
||||
if (BeginMenu("File")) {
|
||||
if (MenuItem("Open", "Ctrl+O")) {
|
||||
if (flags()->kNewFileDialogWrapper) {
|
||||
auto file_name = FileDialogWrapper::ShowOpenFileDialog();
|
||||
PRINT_IF_ERROR(rom()->LoadFromFile(file_name));
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
manager.AddFile(file_name);
|
||||
manager.Save();
|
||||
} else {
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
|
||||
".sfc,.smc", ".");
|
||||
}
|
||||
}
|
||||
|
||||
MENU_ITEM2("Save", "Ctrl+S") {}
|
||||
MENU_ITEM("Save As..") {}
|
||||
if (BeginMenu("Open Recent")) {
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
if (manager.GetRecentFiles().empty()) {
|
||||
MenuItem("No Recent Files", nullptr, false, false);
|
||||
} else {
|
||||
for (const auto& filePath : manager.GetRecentFiles()) {
|
||||
if (MenuItem(filePath.c_str())) {
|
||||
status_ = rom()->LoadFromFile(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
MENU_ITEM2("Save", "Ctrl+S") { status_ = rom()->SaveToFile(backup_rom_); }
|
||||
MENU_ITEM("Save As..") { save_as_menu = true; }
|
||||
|
||||
if (rom()->isLoaded()) {
|
||||
MENU_ITEM("Reload") { status_ = rom()->Reload(); }
|
||||
MENU_ITEM("Close") { status_ = rom()->Close(); }
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// TODO: Make these options matter
|
||||
if (ImGui::BeginMenu("Options")) {
|
||||
static bool enabled = true;
|
||||
ImGui::MenuItem("Enabled", "", &enabled);
|
||||
ImGui::BeginChild("child", ImVec2(0, 60), true);
|
||||
for (int i = 0; i < 10; i++) ImGui::Text("Scrolling Text %d", i);
|
||||
ImGui::EndChild();
|
||||
static float f = 0.5f;
|
||||
static int n = 0;
|
||||
ImGui::SliderFloat("Value", &f, 0.0f, 1.0f);
|
||||
ImGui::InputFloat("Input", &f, 0.1f);
|
||||
ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0");
|
||||
if (BeginMenu("Options")) {
|
||||
MenuItem("Backup ROM", "", &backup_rom_);
|
||||
ImGui::Separator();
|
||||
Text("Experiment Flags");
|
||||
ImGui::Checkbox("Enable Texture Streaming",
|
||||
&mutable_flags()->kLoadTexturesAsStreaming);
|
||||
ImGui::Checkbox("Enable Overworld Sprites",
|
||||
&mutable_flags()->kDrawOverworldSprites);
|
||||
ImGui::Checkbox("Use Bitmap Manager",
|
||||
&mutable_flags()->kUseBitmapManager);
|
||||
ImGui::Checkbox("Log Instructions to Debugger",
|
||||
&mutable_flags()->kLogInstructions);
|
||||
ImGui::Checkbox("Use New ImGui Input",
|
||||
&mutable_flags()->kUseNewImGuiInput);
|
||||
ImGui::Checkbox("Save All Palettes", &mutable_flags()->kSaveAllPalettes);
|
||||
ImGui::Checkbox("Save With Change Queue",
|
||||
&mutable_flags()->kSaveWithChangeQueue);
|
||||
ImGui::Checkbox("Draw Dungeon Room Graphics",
|
||||
&mutable_flags()->kDrawDungeonRoomGraphics);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (MenuItem("Quit", "Ctrl+Q")) {
|
||||
// TODO: Implement quit confirmation dialog.
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (save_as_menu) {
|
||||
static std::string save_as_filename = "";
|
||||
ImGui::Begin("Save As..", &save_as_menu, ImGuiWindowFlags_AlwaysAutoResize);
|
||||
ImGui::InputText("Filename", &save_as_filename);
|
||||
if (ImGui::Button("Save", gui::kDefaultModalSize)) {
|
||||
status_ = rom()->SaveToFile(backup_rom_, save_as_filename);
|
||||
save_as_menu = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", gui::kDefaultModalSize)) {
|
||||
save_as_menu = false;
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawEditMenu() {
|
||||
if (ImGui::BeginMenu("Edit")) {
|
||||
MENU_ITEM2("Undo", "Ctrl+Z") { status_ = overworld_editor_.Undo(); }
|
||||
MENU_ITEM2("Redo", "Ctrl+Y") { status_ = overworld_editor_.Redo(); }
|
||||
if (BeginMenu("Edit")) {
|
||||
MENU_ITEM2("Undo", "Ctrl+Z") { status_ = current_editor_->Undo(); }
|
||||
MENU_ITEM2("Redo", "Ctrl+Y") { status_ = current_editor_->Redo(); }
|
||||
ImGui::Separator();
|
||||
MENU_ITEM2("Cut", "Ctrl+X") { status_ = overworld_editor_.Cut(); }
|
||||
MENU_ITEM2("Copy", "Ctrl+C") { status_ = overworld_editor_.Copy(); }
|
||||
MENU_ITEM2("Paste", "Ctrl+V") { status_ = overworld_editor_.Paste(); }
|
||||
MENU_ITEM2("Cut", "Ctrl+X") { status_ = current_editor_->Cut(); }
|
||||
MENU_ITEM2("Copy", "Ctrl+C") { status_ = current_editor_->Copy(); }
|
||||
MENU_ITEM2("Paste", "Ctrl+V") { status_ = current_editor_->Paste(); }
|
||||
ImGui::Separator();
|
||||
MENU_ITEM2("Find", "Ctrl+F") {}
|
||||
ImGui::Separator();
|
||||
@@ -207,11 +382,18 @@ void MasterEditor::DrawEditMenu() {
|
||||
|
||||
void MasterEditor::DrawViewMenu() {
|
||||
static bool show_imgui_metrics = false;
|
||||
static bool show_imgui_style_editor = false;
|
||||
static bool show_memory_editor = false;
|
||||
static bool show_asm_editor = false;
|
||||
static bool show_imgui_demo = false;
|
||||
static bool show_memory_viewer = false;
|
||||
static bool show_palette_editor = false;
|
||||
static bool show_emulator = false;
|
||||
|
||||
if (show_emulator) {
|
||||
ImGui::Begin("Emulator", &show_emulator, ImGuiWindowFlags_MenuBar);
|
||||
emulator_.Run();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (show_imgui_metrics) {
|
||||
ImGui::ShowMetricsWindow(&show_imgui_metrics);
|
||||
@@ -219,7 +401,7 @@ void MasterEditor::DrawViewMenu() {
|
||||
|
||||
if (show_memory_editor) {
|
||||
static MemoryEditor mem_edit;
|
||||
mem_edit.DrawWindow("Memory Editor", (void *)&rom_, rom_.size());
|
||||
mem_edit.DrawWindow("Memory Editor", (void*)&(*rom()), rom()->size());
|
||||
}
|
||||
|
||||
if (show_imgui_demo) {
|
||||
@@ -227,12 +409,12 @@ void MasterEditor::DrawViewMenu() {
|
||||
}
|
||||
|
||||
if (show_asm_editor) {
|
||||
assembly_editor_.Update();
|
||||
assembly_editor_.Update(show_asm_editor);
|
||||
}
|
||||
|
||||
if (show_imgui_style_editor) {
|
||||
ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor);
|
||||
ImGui::ShowStyleEditor();
|
||||
if (show_palette_editor) {
|
||||
ImGui::Begin("Palette Editor", &show_palette_editor);
|
||||
status_ = palette_editor_.Update();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
@@ -250,7 +432,7 @@ void MasterEditor::DrawViewMenu() {
|
||||
ImGui::TableNextRow();
|
||||
for (int column = 0; column < 3; column++) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Cell %d,%d", column, row);
|
||||
Text("Cell %d,%d", column, row);
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
@@ -259,62 +441,42 @@ void MasterEditor::DrawViewMenu() {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
ImGui::MenuItem("HEX Editor", nullptr, &show_memory_editor);
|
||||
ImGui::MenuItem("ASM Editor", nullptr, &show_asm_editor);
|
||||
ImGui::MenuItem("Memory Viewer", nullptr, &show_memory_viewer);
|
||||
ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginMenu("GUI Tools")) {
|
||||
ImGui::MenuItem("Metrics (ImGui)", nullptr, &show_imgui_metrics);
|
||||
ImGui::MenuItem("Style Editor (ImGui)", nullptr,
|
||||
&show_imgui_style_editor);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (BeginMenu("View")) {
|
||||
MenuItem("Emulator", nullptr, &show_emulator);
|
||||
MenuItem("HEX Editor", nullptr, &show_memory_editor);
|
||||
MenuItem("ASM Editor", nullptr, &show_asm_editor);
|
||||
MenuItem("Palette Editor", nullptr, &show_palette_editor);
|
||||
MenuItem("Memory Viewer", nullptr, &show_memory_viewer);
|
||||
MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
|
||||
MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawHelpMenu() {
|
||||
if (ImGui::BeginMenu("Help")) {
|
||||
if (ImGui::MenuItem("About")) about_ = true;
|
||||
static bool open_rom_help = false;
|
||||
if (BeginMenu("Help")) {
|
||||
if (MenuItem("How to open a ROM")) open_rom_help = true;
|
||||
if (MenuItem("About")) about_ = true;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawOverworldEditor() {
|
||||
TAB_ITEM("Overworld")
|
||||
status_ = overworld_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
if (open_rom_help) ImGui::OpenPopup("Open a ROM");
|
||||
if (ImGui::BeginPopupModal("Open a ROM", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
Text("File -> Open");
|
||||
Text("Select a ROM file to open");
|
||||
Text("Supported ROMs (headered or unheadered):");
|
||||
Text("The Legend of Zelda: A Link to the Past");
|
||||
Text("US Version 1.0");
|
||||
Text("JP Version 1.0");
|
||||
|
||||
void MasterEditor::DrawDungeonEditor() {
|
||||
TAB_ITEM("Dungeon")
|
||||
dungeon_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawPaletteEditor() {
|
||||
TAB_ITEM("Palettes")
|
||||
status_ = palette_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawScreenEditor() {
|
||||
TAB_ITEM("Screens")
|
||||
screen_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawMusicEditor() {
|
||||
TAB_ITEM("Music")
|
||||
music_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawSpriteEditor() {
|
||||
TAB_ITEM("Sprites")
|
||||
END_TAB_ITEM()
|
||||
if (ImGui::Button("Close", gui::kDefaultModalSize)) {
|
||||
open_rom_help = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
|
||||
@@ -8,28 +8,40 @@
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/editor/assembly_editor.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/editor/context/gfx_context.h"
|
||||
#include "app/editor/dungeon_editor.h"
|
||||
#include "app/editor/music_editor.h"
|
||||
#include "app/editor/graphics_editor.h"
|
||||
#include "app/editor/modules/assembly_editor.h"
|
||||
#include "app/editor/modules/music_editor.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/editor/overworld_editor.h"
|
||||
#include "app/editor/palette_editor.h"
|
||||
#include "app/editor/screen_editor.h"
|
||||
#include "app/editor/sprite_editor.h"
|
||||
#include "app/emu/emulator.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
class MasterEditor {
|
||||
class MasterEditor : public SharedROM,
|
||||
public GfxContext,
|
||||
public core::ExperimentFlags {
|
||||
public:
|
||||
MasterEditor() { current_editor_ = &overworld_editor_; }
|
||||
|
||||
void SetupScreen(std::shared_ptr<SDL_Renderer> renderer);
|
||||
void UpdateScreen();
|
||||
absl::Status Update();
|
||||
|
||||
void Shutdown() { overworld_editor_.Shutdown(); }
|
||||
|
||||
private:
|
||||
void DrawFileDialog();
|
||||
@@ -38,31 +50,34 @@ class MasterEditor {
|
||||
void DrawInfoPopup();
|
||||
|
||||
void DrawYazeMenu();
|
||||
void DrawFileMenu() const;
|
||||
void DrawFileMenu();
|
||||
void DrawEditMenu();
|
||||
void DrawViewMenu();
|
||||
void DrawHelpMenu();
|
||||
|
||||
void DrawOverworldEditor();
|
||||
void DrawDungeonEditor();
|
||||
void DrawPaletteEditor();
|
||||
void DrawMusicEditor();
|
||||
void DrawScreenEditor();
|
||||
void DrawSpriteEditor();
|
||||
|
||||
bool about_ = false;
|
||||
bool rom_info_ = false;
|
||||
bool backup_rom_ = true;
|
||||
bool show_status_ = false;
|
||||
bool rom_assets_loaded_ = false;
|
||||
|
||||
absl::Status status_;
|
||||
absl::Status prev_status_;
|
||||
|
||||
std::shared_ptr<SDL_Renderer> sdl_renderer_;
|
||||
absl::Status status_;
|
||||
|
||||
emu::Emulator emulator_;
|
||||
|
||||
AssemblyEditor assembly_editor_;
|
||||
DungeonEditor dungeon_editor_;
|
||||
GraphicsEditor graphics_editor_;
|
||||
MusicEditor music_editor_;
|
||||
OverworldEditor overworld_editor_;
|
||||
PaletteEditor palette_editor_;
|
||||
ScreenEditor screen_editor_;
|
||||
MusicEditor music_editor_;
|
||||
ROM rom_;
|
||||
SpriteEditor sprite_editor_;
|
||||
|
||||
Editor *current_editor_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
#include "assembly_editor.h"
|
||||
|
||||
#include "app/gui/widgets.h"
|
||||
#include "core/constants.h"
|
||||
#include "gui/widgets.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
AssemblyEditor::AssemblyEditor() {
|
||||
text_editor_.SetLanguageDefinition(gui::widgets::GetAssemblyLanguageDef());
|
||||
text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef());
|
||||
text_editor_.SetPalette(TextEditor::GetDarkPalette());
|
||||
}
|
||||
|
||||
void AssemblyEditor::Update() {
|
||||
ImGui::Begin("Assembly Editor", &file_is_loaded_);
|
||||
void AssemblyEditor::Update(bool& is_loaded) {
|
||||
ImGui::Begin("Assembly Editor", &is_loaded);
|
||||
MENU_BAR()
|
||||
DrawFileMenu();
|
||||
DrawEditMenu();
|
||||
@@ -32,8 +32,6 @@ void AssemblyEditor::Update() {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AssemblyEditor::InlineUpdate() {
|
||||
ChangeActiveFile("assets/asm/template_song.asm");
|
||||
auto cpos = text_editor_.GetCursorPosition();
|
||||
@@ -48,10 +46,49 @@ void AssemblyEditor::InlineUpdate() {
|
||||
text_editor_.Render("##asm_editor", ImVec2(0, 0));
|
||||
}
|
||||
|
||||
void AssemblyEditor::ChangeActiveFile(const std::string& filename) {
|
||||
void AssemblyEditor::ChangeActiveFile(const std::string_view& filename) {
|
||||
current_file_ = filename;
|
||||
}
|
||||
|
||||
void AssemblyEditor::DrawFileView() {
|
||||
ImGui::BeginTable("##table_view", 4,
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg);
|
||||
|
||||
// Table headers
|
||||
ImGui::TableSetupColumn("Files", ImGuiTableColumnFlags_WidthFixed, 150.0f);
|
||||
ImGui::TableSetupColumn("Line", ImGuiTableColumnFlags_WidthFixed, 60.0f);
|
||||
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 100.0f);
|
||||
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
// Table data
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
// TODO: Add tree view of files
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
// TODO: Add line number
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
// TODO: Add address per line
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
auto cpos = text_editor_.GetCursorPosition();
|
||||
SetEditorText();
|
||||
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||
text_editor_.CanUndo() ? "*" : " ",
|
||||
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||
current_file_.c_str());
|
||||
|
||||
text_editor_.Render("##asm_editor");
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
void AssemblyEditor::DrawFileMenu() {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||
@@ -16,13 +16,16 @@ class AssemblyEditor {
|
||||
public:
|
||||
AssemblyEditor();
|
||||
|
||||
void Update();
|
||||
void Update(bool &is_loaded);
|
||||
void InlineUpdate();
|
||||
void ChangeActiveFile(const std::string &);
|
||||
void ChangeActiveFile(const std::string_view &);
|
||||
|
||||
private:
|
||||
void DrawFileMenu();
|
||||
void DrawEditMenu();
|
||||
|
||||
void DrawFileView();
|
||||
|
||||
void SetEditorText();
|
||||
|
||||
bool file_is_loaded_ = false;
|
||||
182
src/app/editor/modules/gfx_group_editor.cc
Normal file
182
src/app/editor/modules/gfx_group_editor.cc
Normal file
@@ -0,0 +1,182 @@
|
||||
#include "gfx_group_editor.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/gui/widgets.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
using ImGui::SameLine;
|
||||
using ImGui::TableHeadersRow;
|
||||
using ImGui::TableNextColumn;
|
||||
using ImGui::TableNextRow;
|
||||
using ImGui::TableSetupColumn;
|
||||
|
||||
absl::Status GfxGroupEditor::Update() {
|
||||
if (ImGui::BeginTabBar("GfxGroupEditor")) {
|
||||
if (ImGui::BeginTabItem("Main")) {
|
||||
gui::InputHexByte("Selected Blockset", &selected_blockset_);
|
||||
ImGui::Text("Values");
|
||||
if (ImGui::BeginTable("##BlocksetTable", 2, ImGuiTableFlags_Borders,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||
TableHeadersRow();
|
||||
TableNextRow();
|
||||
TableNextColumn();
|
||||
{
|
||||
ImGui::BeginGroup();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
ImGui::SetNextItemWidth(100.f);
|
||||
gui::InputHexByte(("##blockset0" + std::to_string(i)).c_str(),
|
||||
&rom()->main_blockset_ids[selected_blockset_][i]);
|
||||
if (i != 3 && i != 7) {
|
||||
SameLine();
|
||||
}
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
TableNextColumn();
|
||||
{
|
||||
ImGui::BeginGroup();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int sheet_id = rom()->main_blockset_ids[selected_blockset_][i];
|
||||
auto &sheet = *rom()->bitmap_manager()[sheet_id];
|
||||
if (sheet_id != last_sheet_id_) {
|
||||
last_sheet_id_ = sheet_id;
|
||||
auto palette_group = rom()->palette_group("ow_main");
|
||||
auto palette = palette_group[preview_palette_id_];
|
||||
sheet.ApplyPalette(palette);
|
||||
rom()->UpdateBitmap(&sheet);
|
||||
}
|
||||
gui::BitmapCanvasPipeline(blockset_canvas_, sheet, 256, 0x10 * 0x04,
|
||||
0x20, true, false, 22);
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("Rooms")) {
|
||||
gui::InputHexByte("Selected Blockset", &selected_roomset_);
|
||||
|
||||
ImGui::Text("Values - Overwrites 4 of main blockset");
|
||||
if (ImGui::BeginTable("##Roomstable", 2, ImGuiTableFlags_Borders,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||
TableHeadersRow();
|
||||
TableNextRow();
|
||||
TableNextColumn();
|
||||
{
|
||||
ImGui::BeginGroup();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ImGui::SetNextItemWidth(100.f);
|
||||
gui::InputHexByte(("##roomset0" + std::to_string(i)).c_str(),
|
||||
&rom()->room_blockset_ids[selected_roomset_][i]);
|
||||
if (i != 3 && i != 7) {
|
||||
SameLine();
|
||||
}
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
TableNextColumn();
|
||||
{
|
||||
ImGui::BeginGroup();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int sheet_id = rom()->room_blockset_ids[selected_roomset_][i];
|
||||
auto &sheet = *rom()->bitmap_manager()[sheet_id];
|
||||
gui::BitmapCanvasPipeline(roomset_canvas_, sheet, 256, 0x10 * 0x04,
|
||||
0x20, true, false, 23);
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("Sprites")) {
|
||||
gui::InputHexByte("Selected Spriteset", &selected_spriteset_);
|
||||
|
||||
ImGui::Text("Values");
|
||||
if (ImGui::BeginTable("##SpritesTable", 2, ImGuiTableFlags_Borders,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||
TableHeadersRow();
|
||||
TableNextRow();
|
||||
TableNextColumn();
|
||||
{
|
||||
ImGui::BeginGroup();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ImGui::SetNextItemWidth(100.f);
|
||||
gui::InputHexByte(("##spriteset0" + std::to_string(i)).c_str(),
|
||||
&rom()->spriteset_ids[selected_spriteset_][i]);
|
||||
if (i != 3 && i != 7) {
|
||||
SameLine();
|
||||
}
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
TableNextColumn();
|
||||
{
|
||||
ImGui::BeginGroup();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int sheet_id = rom()->spriteset_ids[selected_spriteset_][i];
|
||||
auto sheet = *rom()->bitmap_manager()[sheet_id];
|
||||
gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256,
|
||||
0x10 * 0x04, 0x20, true, false, 24);
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem("Palettes")) {
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Palette: ");
|
||||
ImGui::InputInt("##PreviewPaletteID", &preview_palette_id_);
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void GfxGroupEditor::InitBlockset(gfx::Bitmap tile16_blockset) {
|
||||
tile16_blockset_bmp_ = tile16_blockset;
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
57
src/app/editor/modules/gfx_group_editor.h
Normal file
57
src/app/editor/modules/gfx_group_editor.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/widgets.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
class GfxGroupEditor : public SharedROM {
|
||||
public:
|
||||
absl::Status Update();
|
||||
|
||||
void InitBlockset(gfx::Bitmap tile16_blockset);
|
||||
|
||||
private:
|
||||
int preview_palette_id_ = 0;
|
||||
int last_sheet_id_ = 0;
|
||||
uint8_t selected_blockset_ = 0;
|
||||
uint8_t selected_roomset_ = 0;
|
||||
uint8_t selected_spriteset_ = 0;
|
||||
|
||||
gui::Canvas blockset_canvas_;
|
||||
gui::Canvas roomset_canvas_;
|
||||
gui::Canvas spriteset_canvas_;
|
||||
|
||||
gfx::SNESPalette palette_;
|
||||
gfx::PaletteGroup palette_group_;
|
||||
gfx::Bitmap tile16_blockset_bmp_;
|
||||
|
||||
std::vector<Bytes> tile16_individual_data_;
|
||||
std::vector<gfx::Bitmap> tile16_individual_;
|
||||
|
||||
gui::BitmapViewer gfx_group_viewer_;
|
||||
zelda3::Overworld overworld_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
#endif // YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
||||
@@ -4,13 +4,13 @@
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/assembly_editor.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
#include "snes_spc/demo/demo_util.h"
|
||||
#include "snes_spc/demo/wave_writer.h"
|
||||
#include "snes_spc/snes_spc/spc.h"
|
||||
#include "app/editor/modules/assembly_editor.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
// #include "snes_spc/demo/demo_util.h"
|
||||
// #include "snes_spc/demo/wave_writer.h"
|
||||
// #include "snes_spc/snes_spc/spc.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -21,46 +21,46 @@ namespace {
|
||||
#define BUF_SIZE 2048
|
||||
|
||||
void PlaySPC() {
|
||||
/* Create emulator and filter */
|
||||
SNES_SPC* snes_spc = spc_new();
|
||||
SPC_Filter* filter = spc_filter_new();
|
||||
if (!snes_spc || !filter) error("Out of memory");
|
||||
// /* Create emulator and filter */
|
||||
// SNES_SPC* snes_spc = spc_new();
|
||||
// SPC_Filter* filter = spc_filter_new();
|
||||
// if (!snes_spc || !filter) error("Out of memory");
|
||||
|
||||
/* Load SPC */
|
||||
{
|
||||
/* Load file into memory */
|
||||
long spc_size;
|
||||
void* spc = load_file("assets/music/hyrule_field.spc", &spc_size);
|
||||
// /* Load SPC */
|
||||
// {
|
||||
// /* Load file into memory */
|
||||
// long spc_size;
|
||||
// void* spc = load_file("assets/music/hyrule_field.spc", &spc_size);
|
||||
|
||||
/* Load SPC data into emulator */
|
||||
error(spc_load_spc(snes_spc, spc, spc_size));
|
||||
free(spc); /* emulator makes copy of data */
|
||||
// /* Load SPC data into emulator */
|
||||
// error(spc_load_spc(snes_spc, spc, spc_size));
|
||||
// free(spc); /* emulator makes copy of data */
|
||||
|
||||
/* Most SPC files have garbage data in the echo buffer, so clear that */
|
||||
spc_clear_echo(snes_spc);
|
||||
// /* Most SPC files have garbage data in the echo buffer, so clear that */
|
||||
// spc_clear_echo(snes_spc);
|
||||
|
||||
/* Clear filter before playing */
|
||||
spc_filter_clear(filter);
|
||||
}
|
||||
// /* Clear filter before playing */
|
||||
// spc_filter_clear(filter);
|
||||
// }
|
||||
|
||||
/* Record 20 seconds to wave file */
|
||||
wave_open(spc_sample_rate, "out.wav");
|
||||
wave_enable_stereo();
|
||||
while (wave_sample_count() < 30 * spc_sample_rate * 2) {
|
||||
/* Play into buffer */
|
||||
short buf[BUF_SIZE];
|
||||
error(spc_play(snes_spc, BUF_SIZE, buf));
|
||||
// /* Record 20 seconds to wave file */
|
||||
// wave_open(spc_sample_rate, "out.wav");
|
||||
// wave_enable_stereo();
|
||||
// while (wave_sample_count() < 30 * spc_sample_rate * 2) {
|
||||
// /* Play into buffer */
|
||||
// short buf[BUF_SIZE];
|
||||
// error(spc_play(snes_spc, BUF_SIZE, buf));
|
||||
|
||||
/* Filter samples */
|
||||
spc_filter_run(filter, buf, BUF_SIZE);
|
||||
// /* Filter samples */
|
||||
// spc_filter_run(filter, buf, BUF_SIZE);
|
||||
|
||||
wave_write(buf, BUF_SIZE);
|
||||
}
|
||||
// wave_write(buf, BUF_SIZE);
|
||||
// }
|
||||
|
||||
/* Cleanup */
|
||||
spc_filter_delete(filter);
|
||||
spc_delete(snes_spc);
|
||||
wave_close();
|
||||
// /* Cleanup */
|
||||
// spc_filter_delete(filter);
|
||||
// spc_delete(snes_spc);
|
||||
// wave_close();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -223,10 +223,10 @@ void MusicEditor::DrawToolset() {
|
||||
|
||||
if (is_playing) {
|
||||
if (!has_loaded_song) {
|
||||
PlaySPC();
|
||||
current_song_ = Mix_LoadMUS("out.wav");
|
||||
Mix_PlayMusic(current_song_, -1);
|
||||
has_loaded_song = true;
|
||||
// PlaySPC();
|
||||
// current_song_ = Mix_LoadMUS("out.wav");
|
||||
// Mix_PlayMusic(current_song_, -1);
|
||||
// has_loaded_song = true;
|
||||
}
|
||||
|
||||
// // If there is no music playing
|
||||
@@ -252,11 +252,13 @@ void MusicEditor::DrawToolset() {
|
||||
ImGui::Combo("#songs_in_game", &selected_option, kGameSongs, 30);
|
||||
|
||||
gui::ItemLabel("Controls: ", gui::ItemLabelFlags::Left);
|
||||
if (ImGui::BeginTable("SongToolset", 5, toolset_table_flags_, ImVec2(0, 0))) {
|
||||
if (ImGui::BeginTable("SongToolset", 6, toolset_table_flags_, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("#play");
|
||||
ImGui::TableSetupColumn("#rewind");
|
||||
ImGui::TableSetupColumn("#fastforward");
|
||||
ImGui::TableSetupColumn("#volume");
|
||||
ImGui::TableSetupColumn("#debug");
|
||||
|
||||
ImGui::TableSetupColumn("#slider");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
@@ -271,6 +273,9 @@ void MusicEditor::DrawToolset() {
|
||||
BUTTON_COLUMN(ICON_MD_FAST_REWIND)
|
||||
BUTTON_COLUMN(ICON_MD_FAST_FORWARD)
|
||||
BUTTON_COLUMN(ICON_MD_VOLUME_UP)
|
||||
if (ImGui::Button(ICON_MD_ACCESS_TIME)) {
|
||||
music_tracker_.LoadSongs(*rom());
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SliderInt("Volume", ¤t_volume, 0, 100);
|
||||
ImGui::EndTable();
|
||||
@@ -5,13 +5,15 @@
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/assembly_editor.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
#include "snes_spc/demo/demo_util.h"
|
||||
#include "snes_spc/demo/wave_writer.h"
|
||||
#include "snes_spc/snes_spc/spc.h"
|
||||
#include "app/editor/modules/assembly_editor.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/music/tracker.h"
|
||||
// #include "snes_spc/demo/demo_util.h"
|
||||
// #include "snes_spc/demo/wave_writer.h"
|
||||
// #include "snes_spc/snes_spc/spc.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -52,7 +54,7 @@ static const char* kGameSongs[] = {"Title",
|
||||
static constexpr absl::string_view kSongNotes[] = {
|
||||
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C",
|
||||
"C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C"};
|
||||
class MusicEditor {
|
||||
class MusicEditor : public SharedROM {
|
||||
public:
|
||||
void Update();
|
||||
|
||||
@@ -63,7 +65,9 @@ class MusicEditor {
|
||||
void DrawSongToolset();
|
||||
void DrawToolset();
|
||||
|
||||
Mix_Music* current_song_ = NULL;
|
||||
zelda3::Tracker music_tracker_;
|
||||
|
||||
// Mix_Music* current_song_ = NULL;
|
||||
|
||||
AssemblyEditor assembly_editor_;
|
||||
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
|
||||
288
src/app/editor/modules/palette_editor.cc
Normal file
288
src/app/editor/modules/palette_editor.cc
Normal file
@@ -0,0 +1,288 @@
|
||||
#include "palette_editor.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/color.h"
|
||||
#include "app/gui/icons.h"
|
||||
|
||||
static inline float ImSaturate(float f) {
|
||||
return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
|
||||
}
|
||||
|
||||
#define IM_F32_TO_INT8_SAT(_VAL) \
|
||||
((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255
|
||||
|
||||
int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
#ifdef IMGUI_USE_STB_SPRINTF
|
||||
int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
|
||||
#else
|
||||
int w = vsnprintf(buf, buf_size, fmt, args);
|
||||
#endif
|
||||
va_end(args);
|
||||
if (buf == nullptr) return w;
|
||||
if (w == -1 || w >= (int)buf_size) w = (int)buf_size - 1;
|
||||
buf[w] = 0;
|
||||
return w;
|
||||
}
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
absl::Status PaletteEditor::Update() {
|
||||
if (ImGui::BeginTable("paletteEditorTable", 2,
|
||||
ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Resizable |
|
||||
ImGuiTableFlags_SizingStretchSame,
|
||||
ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("Palette Groups",
|
||||
ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
ImGui::TableHeadersRow();
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
for (int category = 0; category < kNumPalettes; ++category) {
|
||||
if (ImGui::TreeNode(kPaletteCategoryNames[category].data())) {
|
||||
status_ = DrawPaletteGroup(category);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Test Column");
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
CLEAR_AND_RETURN_STATUS(status_)
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void PaletteEditor::EditColorInPalette(gfx::SNESPalette& palette, int index) {
|
||||
if (index >= palette.size()) {
|
||||
// Handle error: the index is out of bounds
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current color
|
||||
auto currentColor = palette.GetColor(index).GetRGB();
|
||||
if (ImGui::ColorPicker4("Color Picker", (float*)&palette[index])) {
|
||||
// The color was modified, update it in the palette
|
||||
palette(index, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditor::ResetColorToOriginal(
|
||||
gfx::SNESPalette& palette, int index,
|
||||
const gfx::SNESPalette& originalPalette) {
|
||||
if (index >= palette.size() || index >= originalPalette.size()) {
|
||||
// Handle error: the index is out of bounds
|
||||
return;
|
||||
}
|
||||
|
||||
auto originalColor = originalPalette.GetColor(index).GetRGB();
|
||||
palette(index, originalColor);
|
||||
}
|
||||
|
||||
absl::Status PaletteEditor::DrawPaletteGroup(int category) {
|
||||
if (!rom()->isLoaded()) {
|
||||
return absl::NotFoundError("ROM not open, no palettes to display");
|
||||
}
|
||||
|
||||
const auto size =
|
||||
rom()->palette_group(kPaletteGroupNames[category].data()).size();
|
||||
auto palettes = rom()->palette_group(kPaletteGroupNames[category].data());
|
||||
static bool edit_color = false;
|
||||
for (int j = 0; j < size; j++) {
|
||||
ImGui::Text("%d", j);
|
||||
|
||||
auto palette = palettes[j];
|
||||
auto pal_size = palette.size();
|
||||
|
||||
for (int n = 0; n < pal_size; n++) {
|
||||
ImGui::PushID(n);
|
||||
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
auto popup_id =
|
||||
absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
|
||||
|
||||
// Small icon of the color in the palette
|
||||
if (gui::SNESColorButton(popup_id, palette[n], palette_button_flags)) {
|
||||
edit_color = true;
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupContextItem(popup_id.c_str())) {
|
||||
RETURN_IF_ERROR(HandleColorPopup(palette, category, j, n))
|
||||
}
|
||||
|
||||
if (edit_color) {
|
||||
// The color button was clicked, open the popup
|
||||
if (ImGui::ColorEdit4(popup_id.c_str(),
|
||||
gfx::ToFloatArray(palette[n]).data(),
|
||||
palette_button_flags)) {
|
||||
EditColorInPalette(palette, n);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status PaletteEditor::HandleColorPopup(gfx::SNESPalette& palette, int i,
|
||||
int j, int n) {
|
||||
auto col = gfx::ToFloatArray(palette[n]);
|
||||
if (ImGui::ColorEdit4("Edit Color", col.data(), color_popup_flags)) {
|
||||
RETURN_IF_ERROR(rom()->UpdatePaletteColor(kPaletteGroupNames[i].data(), j,
|
||||
n, palette[n]))
|
||||
}
|
||||
|
||||
if (ImGui::Button("Copy as..", ImVec2(-1, 0))) ImGui::OpenPopup("Copy");
|
||||
|
||||
if (ImGui::BeginPopup("Copy")) {
|
||||
int cr = IM_F32_TO_INT8_SAT(col[0]);
|
||||
int cg = IM_F32_TO_INT8_SAT(col[1]);
|
||||
int cb = IM_F32_TO_INT8_SAT(col[2]);
|
||||
char buf[64];
|
||||
|
||||
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
|
||||
col[1], col[2]);
|
||||
|
||||
if (ImGui::Selectable(buf)) ImGui::SetClipboardText(buf);
|
||||
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
|
||||
if (ImGui::Selectable(buf)) ImGui::SetClipboardText(buf);
|
||||
CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
|
||||
if (ImGui::Selectable(buf)) ImGui::SetClipboardText(buf);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void PaletteEditor::DisplayPalette(gfx::SNESPalette& palette, bool loaded) {
|
||||
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
|
||||
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
|
||||
ImGuiColorEditFlags_NoDragDrop |
|
||||
ImGuiColorEditFlags_NoOptions;
|
||||
|
||||
// Generate a default palette. The palette will persist and can be edited.
|
||||
static bool init = false;
|
||||
if (loaded && !init) {
|
||||
InitializeSavedPalette(palette);
|
||||
init = true;
|
||||
}
|
||||
|
||||
static ImVec4 backup_color;
|
||||
bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags);
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
open_popup |= ImGui::Button("Palette");
|
||||
if (open_popup) {
|
||||
ImGui::OpenPopup("mypicker");
|
||||
backup_color = color;
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("mypicker")) {
|
||||
TEXT_WITH_SEPARATOR("Current Overworld Palette");
|
||||
ImGui::ColorPicker4("##picker", (float*)&color,
|
||||
misc_flags | ImGuiColorEditFlags_NoSidePreview |
|
||||
ImGuiColorEditFlags_NoSmallPreview);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginGroup(); // Lock X position
|
||||
ImGui::Text("Current ==>");
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Previous");
|
||||
|
||||
if (ImGui::Button("Update Map Palette")) {
|
||||
}
|
||||
|
||||
ImGui::ColorButton(
|
||||
"##current", color,
|
||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||
ImVec2(60, 40));
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::ColorButton(
|
||||
"##previous", backup_color,
|
||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||
ImVec2(60, 40)))
|
||||
color = backup_color;
|
||||
|
||||
// List of Colors in Overworld Palette
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Palette");
|
||||
for (int n = 0; n < IM_ARRAYSIZE(saved_palette_); n++) {
|
||||
ImGui::PushID(n);
|
||||
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
if (ImGui::ColorButton("##palette", saved_palette_[n],
|
||||
palette_button_flags_2, ImVec2(20, 20)))
|
||||
color = ImVec4(saved_palette_[n].x, saved_palette_[n].y,
|
||||
saved_palette_[n].z, color.w); // Preserve alpha!
|
||||
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
|
||||
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 3);
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
|
||||
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 4);
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditor::DrawPortablePalette(gfx::SNESPalette& palette) {
|
||||
static bool init = false;
|
||||
if (!init) {
|
||||
InitializeSavedPalette(palette);
|
||||
init = true;
|
||||
}
|
||||
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)100);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
ImGui::BeginGroup(); // Lock X position
|
||||
ImGui::Text("Palette");
|
||||
for (int n = 0; n < IM_ARRAYSIZE(saved_palette_); n++) {
|
||||
ImGui::PushID(n);
|
||||
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
if (ImGui::ColorButton("##palette", saved_palette_[n],
|
||||
palette_button_flags_2, ImVec2(20, 20)))
|
||||
ImVec4(saved_palette_[n].x, saved_palette_[n].y, saved_palette_[n].z,
|
||||
1.0f); // Preserve alpha!
|
||||
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
|
||||
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 3);
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
|
||||
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 4);
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
122
src/app/editor/modules/palette_editor.h
Normal file
122
src/app/editor/modules/palette_editor.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
constexpr int kNumPalettes = 11;
|
||||
|
||||
static constexpr absl::string_view kPaletteCategoryNames[] = {
|
||||
"Sword", "Shield", "Clothes", "World Colors",
|
||||
"Area Colors", "Enemies", "Dungeons", "World Map",
|
||||
"Dungeon Map", "Triforce", "Crystal"};
|
||||
|
||||
static constexpr absl::string_view kPaletteGroupNames[] = {
|
||||
"swords", "shields", "armors", "ow_main",
|
||||
"ow_aux", "global_sprites", "dungeon_main", "ow_mini_map",
|
||||
"ow_mini_map", "3d_object", "3d_object"};
|
||||
|
||||
struct PaletteChange {
|
||||
std::string groupName;
|
||||
size_t paletteIndex;
|
||||
size_t colorIndex;
|
||||
gfx::SNESColor originalColor;
|
||||
gfx::SNESColor newColor;
|
||||
};
|
||||
|
||||
class PaletteEditorHistory {
|
||||
public:
|
||||
// Record a change in the palette editor
|
||||
void RecordChange(const std::string& groupName, size_t paletteIndex,
|
||||
size_t colorIndex, const gfx::SNESColor& originalColor,
|
||||
const gfx::SNESColor& newColor) {
|
||||
// Check size and remove the oldest if necessary
|
||||
if (recentChanges.size() >= maxHistorySize) {
|
||||
recentChanges.pop_front();
|
||||
}
|
||||
|
||||
// Push the new change
|
||||
recentChanges.push_back(
|
||||
{groupName, paletteIndex, colorIndex, originalColor, newColor});
|
||||
}
|
||||
|
||||
// Get recent changes for display in the palette editor
|
||||
const std::deque<PaletteChange>& GetRecentChanges() const {
|
||||
return recentChanges;
|
||||
}
|
||||
|
||||
// Restore the original color
|
||||
gfx::SNESColor GetOriginalColor(const std::string& groupName,
|
||||
size_t paletteIndex,
|
||||
size_t colorIndex) const {
|
||||
for (const auto& change : recentChanges) {
|
||||
if (change.groupName == groupName &&
|
||||
change.paletteIndex == paletteIndex &&
|
||||
change.colorIndex == colorIndex) {
|
||||
return change.originalColor;
|
||||
}
|
||||
}
|
||||
// Handle error or return default (this is just an example,
|
||||
// handle as appropriate for your application)
|
||||
return gfx::SNESColor();
|
||||
}
|
||||
|
||||
private:
|
||||
std::deque<PaletteChange> recentChanges;
|
||||
static const size_t maxHistorySize = 50; // or any other number you deem fit
|
||||
};
|
||||
|
||||
class PaletteEditor : public SharedROM {
|
||||
public:
|
||||
absl::Status Update();
|
||||
absl::Status DrawPaletteGroups();
|
||||
|
||||
void EditColorInPalette(gfx::SNESPalette& palette, int index);
|
||||
void ResetColorToOriginal(gfx::SNESPalette& palette, int index,
|
||||
const gfx::SNESPalette& originalPalette);
|
||||
void DisplayPalette(gfx::SNESPalette& palette, bool loaded);
|
||||
void DrawPortablePalette(gfx::SNESPalette& palette);
|
||||
|
||||
private:
|
||||
absl::Status DrawPaletteGroup(int category);
|
||||
absl::Status HandleColorPopup(gfx::SNESPalette& palette, int i, int j, int n);
|
||||
|
||||
void InitializeSavedPalette(const gfx::SNESPalette& palette) {
|
||||
for (int n = 0; n < palette.size(); n++) {
|
||||
saved_palette_[n].x = palette.GetColor(n).GetRGB().x / 255;
|
||||
saved_palette_[n].y = palette.GetColor(n).GetRGB().y / 255;
|
||||
saved_palette_[n].z = palette.GetColor(n).GetRGB().z / 255;
|
||||
saved_palette_[n].w = 255; // Alpha
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status status_;
|
||||
|
||||
PaletteEditorHistory history_;
|
||||
|
||||
ImVec4 saved_palette_[256] = {};
|
||||
ImVec4 current_color_;
|
||||
|
||||
ImGuiColorEditFlags color_popup_flags =
|
||||
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha;
|
||||
ImGuiColorEditFlags palette_button_flags =
|
||||
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoTooltip;
|
||||
ImGuiColorEditFlags palette_button_flags_2 = ImGuiColorEditFlags_NoAlpha |
|
||||
ImGuiColorEditFlags_NoPicker |
|
||||
ImGuiColorEditFlags_NoTooltip;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
298
src/app/editor/modules/tile16_editor.cc
Normal file
298
src/app/editor/modules/tile16_editor.cc
Normal file
@@ -0,0 +1,298 @@
|
||||
#include "tile16_editor.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
using ImGui::BeginChild;
|
||||
using ImGui::BeginTabBar;
|
||||
using ImGui::BeginTabItem;
|
||||
using ImGui::BeginTable;
|
||||
using ImGui::Combo;
|
||||
using ImGui::EndChild;
|
||||
using ImGui::EndTabBar;
|
||||
using ImGui::EndTabItem;
|
||||
using ImGui::TableHeadersRow;
|
||||
using ImGui::TableNextColumn;
|
||||
using ImGui::TableNextRow;
|
||||
using ImGui::TableSetupColumn;
|
||||
|
||||
absl::Status Tile16Editor::Update() {
|
||||
// Create a tab for Tile16 Editing
|
||||
static bool start_task = false;
|
||||
if (ImGui::Button("Test")) {
|
||||
start_task = true;
|
||||
}
|
||||
|
||||
if (start_task && !map_blockset_loaded_) {
|
||||
LoadTile8();
|
||||
}
|
||||
|
||||
// Create a tab bar for Tile16 Editing and Tile16 Transfer
|
||||
if (BeginTabBar("Tile16 Editor Tabs")) {
|
||||
if (BeginTabItem("Tile16 Editing")) {
|
||||
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Tiles", ImGuiTableColumnFlags_WidthFixed,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableHeadersRow();
|
||||
TableNextRow();
|
||||
TableNextColumn();
|
||||
RETURN_IF_ERROR(UpdateBlockset());
|
||||
|
||||
TableNextColumn();
|
||||
RETURN_IF_ERROR(UpdateTile16Edit());
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
// Create a tab for Tile16 Transfer
|
||||
if (BeginTabItem("Tile16 Transfer")) {
|
||||
if (BeginTable("#Tile16TransferTable", 2,
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
|
||||
ImGui::GetContentRegionAvail().x / 2);
|
||||
TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
|
||||
ImGui::GetContentRegionAvail().x / 2);
|
||||
TableHeadersRow();
|
||||
TableNextRow();
|
||||
|
||||
TableNextColumn();
|
||||
RETURN_IF_ERROR(UpdateBlockset());
|
||||
|
||||
TableNextColumn();
|
||||
RETURN_IF_ERROR(UpdateTransferTileCanvas());
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::UpdateBlockset() {
|
||||
gui::BitmapCanvasPipeline(blockset_canvas_, tile16_blockset_bmp_, 0x100,
|
||||
(8192 * 2), 0x20, map_blockset_loaded_, true, 55);
|
||||
|
||||
if (!blockset_canvas_.Points().empty()) {
|
||||
uint16_t x = blockset_canvas_.Points().front().x / 32;
|
||||
uint16_t y = blockset_canvas_.Points().front().y / 32;
|
||||
|
||||
notify_tile16.mutable_get() = x + (y * 8);
|
||||
notify_tile16.apply_changes();
|
||||
if (notify_tile16.modified()) {
|
||||
current_tile16_bmp_ = tile16_individual_[notify_tile16];
|
||||
current_tile16_bmp_.ApplyPalette(
|
||||
rom()->palette_group("ow_main")[current_palette_]);
|
||||
rom()->RenderBitmap(¤t_tile16_bmp_);
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::UpdateTile16Edit() {
|
||||
if (ImGui::BeginChild("Tile8 Selector",
|
||||
ImVec2(ImGui::GetContentRegionAvail().x, 0x100),
|
||||
true)) {
|
||||
tile8_source_canvas_.DrawBackground(
|
||||
ImVec2(core::kTilesheetWidth * 2, core::kTilesheetHeight * 0x10 * 2));
|
||||
tile8_source_canvas_.DrawContextMenu();
|
||||
tile8_source_canvas_.DrawTileSelector(16);
|
||||
tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 2.0f);
|
||||
tile8_source_canvas_.DrawGrid(16.0f);
|
||||
tile8_source_canvas_.DrawOverlay();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
if (ImGui::BeginChild("Tile16 Editor Options",
|
||||
ImVec2(ImGui::GetContentRegionAvail().x, 0x50), true)) {
|
||||
tile16_edit_canvas_.DrawBackground(ImVec2(0x40, 0x40));
|
||||
tile16_edit_canvas_.DrawContextMenu();
|
||||
// if (current_tile8_bmp_.modified()) {
|
||||
// rom()->UpdateBitmap(¤t_tile8_bmp_);
|
||||
// current_tile8_bmp_.set_modified(false);
|
||||
// }
|
||||
tile16_edit_canvas_.DrawBitmap(current_tile16_bmp_, 0, 0, 4.0f);
|
||||
tile16_edit_canvas_.HandleTileEdits(
|
||||
tile8_source_canvas_, current_gfx_individual_, current_tile8_bmp_,
|
||||
current_tile8_, 2.0f);
|
||||
|
||||
tile16_edit_canvas_.DrawGrid(128.0f);
|
||||
tile16_edit_canvas_.DrawOverlay();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
DrawTileEditControls();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void Tile16Editor::DrawTileEditControls() {
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Options:");
|
||||
gui::InputHexByte("Palette", ¬ify_palette.mutable_get());
|
||||
notify_palette.apply_changes();
|
||||
if (notify_palette.modified()) {
|
||||
current_gfx_bmp_.ApplyPalette(
|
||||
rom()->palette_group("ow_main")[notify_palette.get()]);
|
||||
current_tile16_bmp_.ApplyPalette(
|
||||
rom()->palette_group("ow_main")[notify_palette.get()]);
|
||||
rom()->UpdateBitmap(¤t_gfx_bmp_);
|
||||
}
|
||||
|
||||
ImGui::Checkbox("X Flip", &x_flip);
|
||||
ImGui::Checkbox("Y Flip", &y_flip);
|
||||
ImGui::Checkbox("Priority Tile", &priority_tile);
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::UpdateTransferTileCanvas() {
|
||||
// Create a button for loading another ROM
|
||||
if (ImGui::Button("Load ROM")) {
|
||||
ImGuiFileDialog::Instance()->OpenDialog(
|
||||
"ChooseTransferFileDlgKey", "Open Transfer ROM", ".sfc,.smc", ".");
|
||||
}
|
||||
gui::FileDialogPipeline(
|
||||
"ChooseTransferFileDlgKey", ".sfc,.smc", std::nullopt, [&]() {
|
||||
std::string filePathName =
|
||||
ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
transfer_status_ = transfer_rom_.LoadFromFile(filePathName);
|
||||
transfer_started_ = true;
|
||||
});
|
||||
|
||||
if (transfer_started_ && !transfer_blockset_loaded_) {
|
||||
PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData())
|
||||
graphics_bin_ = transfer_rom_.graphics_bin();
|
||||
|
||||
// Load the Link to the Past overworld.
|
||||
PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_))
|
||||
transfer_overworld_.SetCurrentMap(0);
|
||||
palette_ = transfer_overworld_.AreaPalette();
|
||||
|
||||
// Create the tile16 blockset image
|
||||
gui::BuildAndRenderBitmapPipeline(0x80, 0x2000, 0x80,
|
||||
transfer_overworld_.Tile16Blockset(),
|
||||
*rom(), transfer_blockset_bmp_, palette_);
|
||||
transfer_blockset_loaded_ = true;
|
||||
}
|
||||
|
||||
// Create a canvas for holding the tiles which will be exported
|
||||
gui::BitmapCanvasPipeline(transfer_canvas_, transfer_blockset_bmp_, 0x100,
|
||||
(8192 * 2), 0x20, transfer_blockset_loaded_, true,
|
||||
3);
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
using core::TaskManager;
|
||||
|
||||
absl::Status Tile16Editor::InitBlockset(
|
||||
const gfx::Bitmap& tile16_blockset_bmp, gfx::Bitmap current_gfx_bmp,
|
||||
const std::vector<gfx::Bitmap>& tile16_individual) {
|
||||
tile16_blockset_bmp_ = tile16_blockset_bmp;
|
||||
tile16_individual_ = tile16_individual;
|
||||
current_gfx_bmp_ = current_gfx_bmp;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::LoadTile8() {
|
||||
current_gfx_individual_.reserve(128);
|
||||
|
||||
// Define the task function
|
||||
std::function<void(int)> taskFunc = [&](int index) {
|
||||
auto current_gfx_data = current_gfx_bmp_.mutable_data();
|
||||
std::vector<uint8_t> tile_data;
|
||||
tile_data.reserve(0x40);
|
||||
for (int i = 0; i < 0x40; i++) {
|
||||
tile_data.emplace_back(0x00);
|
||||
}
|
||||
|
||||
// Copy the pixel data for the current tile into the vector
|
||||
for (int ty = 0; ty < 8; ty++) {
|
||||
for (int tx = 0; tx < 8; tx++) {
|
||||
int position = tx + (ty * 0x08);
|
||||
uint8_t value =
|
||||
current_gfx_data[(index % 16 * 32) + (index / 16 * 32 * 0x80) +
|
||||
(ty * 0x80) + tx];
|
||||
tile_data[position] = value;
|
||||
}
|
||||
}
|
||||
|
||||
current_gfx_individual_.emplace_back();
|
||||
current_gfx_individual_[index].Create(0x08, 0x08, 0x80, tile_data);
|
||||
current_gfx_individual_[index].ApplyPalette(
|
||||
rom()->palette_group("ow_main")[current_palette_]);
|
||||
rom()->RenderBitmap(¤t_gfx_individual_[index]);
|
||||
};
|
||||
|
||||
// Create the task manager
|
||||
static bool started = false;
|
||||
if (!started) {
|
||||
task_manager_ = TaskManager<std::function<void(int)>>(127, 1);
|
||||
started = true;
|
||||
}
|
||||
task_manager_.ExecuteTasks(taskFunc);
|
||||
|
||||
if (task_manager_.IsTaskComplete()) {
|
||||
// All tasks are complete
|
||||
current_tile8_bmp_ = current_gfx_individual_[0];
|
||||
map_blockset_loaded_ = true;
|
||||
}
|
||||
|
||||
// auto current_gfx_data = current_gfx_bmp_.mutable_data();
|
||||
// for (int i = 0; i < 128; i++) {
|
||||
// std::vector<uint8_t> tile_data(0x40, 0x00);
|
||||
|
||||
// Copy the pixel data for the current tile into the vector
|
||||
// for (int ty = 0; ty < 8; ty++) {
|
||||
// for (int tx = 0; tx < 8; tx++) {
|
||||
// int position = tx + (ty * 0x10);
|
||||
// uint8_t value = current_gfx_data[(i % 16 * 32) + (i / 16 * 32 * 0x80)
|
||||
// +
|
||||
// (ty * 0x80) + tx];
|
||||
// tile_data[position] = value;
|
||||
// }
|
||||
// }
|
||||
|
||||
// current_gfx_individual_data_.emplace_back(tile_data);
|
||||
// current_gfx_individual_.emplace_back();
|
||||
// current_gfx_individual_[i].Create(0x08, 0x08, 0x80, tile_data);
|
||||
// current_gfx_individual_[i].ApplyPalette(
|
||||
// rom()->palette_group("ow_main")[current_palette_]);
|
||||
// rom()->RenderBitmap(¤t_gfx_individual_[i]);
|
||||
// }
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
107
src/app/editor/modules/tile16_editor.h
Normal file
107
src/app/editor/modules/tile16_editor.h
Normal file
@@ -0,0 +1,107 @@
|
||||
#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||
#define YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
class Tile16Editor : public SharedROM {
|
||||
public:
|
||||
absl::Status Update();
|
||||
|
||||
absl::Status UpdateBlockset();
|
||||
absl::Status UpdateTile16Edit();
|
||||
|
||||
void DrawTileEditControls();
|
||||
|
||||
absl::Status UpdateTransferTileCanvas();
|
||||
|
||||
absl::Status InitBlockset(const gfx::Bitmap& tile16_blockset_bmp,
|
||||
gfx::Bitmap current_gfx_bmp,
|
||||
const std::vector<gfx::Bitmap>& tile16_individual);
|
||||
|
||||
absl::Status LoadTile8();
|
||||
|
||||
private:
|
||||
bool map_blockset_loaded_ = false;
|
||||
bool transfer_started_ = false;
|
||||
bool transfer_blockset_loaded_ = false;
|
||||
|
||||
int current_tile16_ = 0;
|
||||
int current_tile8_ = 0;
|
||||
uint8_t current_palette_ = 0;
|
||||
|
||||
core::NotifyValue<uint8_t> notify_tile16;
|
||||
core::NotifyValue<uint8_t> notify_palette;
|
||||
|
||||
// Canvas dimensions
|
||||
int canvas_width;
|
||||
int canvas_height;
|
||||
|
||||
// Texture ID for the canvas
|
||||
int texture_id;
|
||||
|
||||
// Various options for the Tile16 Editor
|
||||
bool x_flip;
|
||||
bool y_flip;
|
||||
bool priority_tile;
|
||||
int tile_size;
|
||||
|
||||
// Tile16 blockset for selecting the tile to edit
|
||||
gui::Canvas blockset_canvas_;
|
||||
gfx::Bitmap tile16_blockset_bmp_;
|
||||
|
||||
// Canvas for editing the selected tile
|
||||
gui::Canvas tile16_edit_canvas_;
|
||||
gfx::Bitmap current_tile16_bmp_;
|
||||
gfx::Bitmap current_tile8_bmp_;
|
||||
|
||||
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
|
||||
gui::Canvas tile8_source_canvas_;
|
||||
gfx::Bitmap current_gfx_bmp_;
|
||||
|
||||
gui::Canvas transfer_canvas_;
|
||||
gfx::Bitmap transfer_blockset_bmp_;
|
||||
gfx::Bitmap transfer_current_bmp_;
|
||||
|
||||
std::vector<Bytes> tile16_individual_data_;
|
||||
std::vector<gfx::Bitmap> tile16_individual_;
|
||||
|
||||
std::vector<gfx::Bitmap> current_gfx_individual_;
|
||||
|
||||
std::vector<uint8_t> current_tile16_data_;
|
||||
|
||||
PaletteEditor palette_editor_;
|
||||
|
||||
gfx::SNESPalette palette_;
|
||||
zelda3::Overworld transfer_overworld_;
|
||||
|
||||
gfx::BitmapTable graphics_bin_;
|
||||
|
||||
ROM transfer_rom_;
|
||||
absl::Status transfer_status_;
|
||||
|
||||
core::TaskManager<std::function<void(int)>> task_manager_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
#endif // YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||
@@ -9,143 +9,312 @@
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/palette_editor.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/gui/style.h"
|
||||
#include "app/gui/widgets.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
using ImGui::Button;
|
||||
using ImGui::Separator;
|
||||
using ImGui::TableHeadersRow;
|
||||
using ImGui::TableNextColumn;
|
||||
using ImGui::TableNextRow;
|
||||
using ImGui::TableSetupColumn;
|
||||
using ImGui::Text;
|
||||
|
||||
absl::Status OverworldEditor::Update() {
|
||||
// Initialize overworld graphics, maps, and palettes
|
||||
if (rom_.isLoaded() && !all_gfx_loaded_) {
|
||||
RETURN_IF_ERROR(LoadGraphics())
|
||||
if (rom()->isLoaded() && !all_gfx_loaded_) {
|
||||
RETURN_IF_ERROR(tile16_editor_.InitBlockset(
|
||||
tile16_blockset_bmp_, current_gfx_bmp_, tile16_individual_));
|
||||
gfx_group_editor_.InitBlockset(tile16_blockset_bmp_);
|
||||
all_gfx_loaded_ = true;
|
||||
} else if (!rom()->isLoaded() && all_gfx_loaded_) {
|
||||
// TODO: Destroy the overworld graphics canvas.
|
||||
// Reset the editor if the ROM is unloaded
|
||||
Shutdown();
|
||||
all_gfx_loaded_ = false;
|
||||
map_blockset_loaded_ = false;
|
||||
}
|
||||
|
||||
// Draws the toolset for editing the Overworld.
|
||||
RETURN_IF_ERROR(DrawToolset())
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginTable("#owEditTable", 2, ow_edit_flags, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
ImGui::TableSetupColumn("Tile Selector");
|
||||
ImGui::TableHeadersRow();
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::BeginTable(kOWEditTable.data(), 2, kOWEditFlags, ImVec2(0, 0))) {
|
||||
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableSetupColumn("Tile Selector", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||
TableHeadersRow();
|
||||
TableNextRow();
|
||||
TableNextColumn();
|
||||
DrawOverworldCanvas();
|
||||
ImGui::TableNextColumn();
|
||||
TableNextColumn();
|
||||
DrawTileSelector();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
if (!status_.ok()) {
|
||||
auto temp = status_;
|
||||
status_ = absl::OkStatus();
|
||||
return temp;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
absl::Status OverworldEditor::DrawToolset() {
|
||||
if (ImGui::BeginTable("OWToolset", 17, toolset_table_flags, ImVec2(0, 0))) {
|
||||
static bool show_gfx_group = false;
|
||||
static bool show_tile16 = false;
|
||||
|
||||
if (ImGui::BeginTable("OWToolset", 19, kToolsetTableFlags, ImVec2(0, 0))) {
|
||||
for (const auto &name : kToolsetColumnNames)
|
||||
ImGui::TableSetupColumn(name.data());
|
||||
|
||||
BUTTON_COLUMN(ICON_MD_UNDO) // Undo
|
||||
BUTTON_COLUMN(ICON_MD_REDO) // Redo
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
|
||||
BUTTON_COLUMN(ICON_MD_ZOOM_OUT) // Zoom Out
|
||||
BUTTON_COLUMN(ICON_MD_ZOOM_IN) // Zoom In
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
|
||||
BUTTON_COLUMN(ICON_MD_DRAW) // Draw Tile
|
||||
BUTTON_COLUMN(ICON_MD_DOOR_FRONT) // Entrances
|
||||
BUTTON_COLUMN(ICON_MD_DOOR_BACK) // Exits
|
||||
BUTTON_COLUMN(ICON_MD_GRASS) // Items
|
||||
BUTTON_COLUMN(ICON_MD_PEST_CONTROL_RODENT) // Sprites
|
||||
BUTTON_COLUMN(ICON_MD_ADD_LOCATION) // Transports
|
||||
BUTTON_COLUMN(ICON_MD_MUSIC_NOTE) // Music
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
|
||||
ImGui::TableNextColumn(); // Palette
|
||||
NEXT_COLUMN()
|
||||
if (ImGui::Button(ICON_MD_UNDO)) {
|
||||
status_ = Undo();
|
||||
}
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (ImGui::Button(ICON_MD_REDO)) {
|
||||
status_ = Redo();
|
||||
}
|
||||
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (ImGui::Button(ICON_MD_ZOOM_OUT)) {
|
||||
ow_map_canvas_.ZoomOut();
|
||||
}
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (ImGui::Button(ICON_MD_ZOOM_IN)) {
|
||||
ow_map_canvas_.ZoomIn();
|
||||
}
|
||||
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (ImGui::Button(ICON_MD_DRAW)) {
|
||||
current_mode = EditingMode::DRAW_TILE;
|
||||
}
|
||||
HOVER_HINT("Draw Tile")
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (ImGui::Button(ICON_MD_DOOR_FRONT)) {
|
||||
current_mode = EditingMode::ENTRANCES;
|
||||
}
|
||||
HOVER_HINT("Entrances")
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (ImGui::Button(ICON_MD_DOOR_BACK)) {
|
||||
current_mode = EditingMode::EXITS;
|
||||
}
|
||||
HOVER_HINT("Exits")
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (ImGui::Button(ICON_MD_GRASS)) {
|
||||
current_mode = EditingMode::ITEMS;
|
||||
}
|
||||
HOVER_HINT("Items")
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (ImGui::Button(ICON_MD_PEST_CONTROL_RODENT)) {
|
||||
current_mode = EditingMode::SPRITES;
|
||||
}
|
||||
HOVER_HINT("Sprites")
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (ImGui::Button(ICON_MD_ADD_LOCATION)) {
|
||||
current_mode = EditingMode::TRANSPORTS;
|
||||
}
|
||||
HOVER_HINT("Transports")
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (ImGui::Button(ICON_MD_MUSIC_NOTE)) {
|
||||
current_mode = EditingMode::MUSIC;
|
||||
}
|
||||
HOVER_HINT("Music")
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::Button(ICON_MD_GRID_VIEW)) {
|
||||
show_tile16 = !show_tile16;
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::Button(ICON_MD_TABLE_CHART)) {
|
||||
show_gfx_group = !show_gfx_group;
|
||||
}
|
||||
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
|
||||
|
||||
TableNextColumn(); // Palette
|
||||
palette_editor_.DisplayPalette(palette_, overworld_.isLoaded());
|
||||
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
|
||||
|
||||
TableNextColumn(); // Experimental
|
||||
ImGui::Checkbox("Experimental", &show_experimental);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
if (show_experimental) {
|
||||
RETURN_IF_ERROR(DrawExperimentalModal())
|
||||
}
|
||||
|
||||
if (show_tile16) {
|
||||
// Create a table in ImGui for the Tile16 Editor
|
||||
ImGui::Begin("Tile16 Editor", &show_tile16);
|
||||
RETURN_IF_ERROR(tile16_editor_.Update())
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (show_gfx_group) {
|
||||
ImGui::Begin("Gfx Group Editor", &show_gfx_group);
|
||||
RETURN_IF_ERROR(gfx_group_editor_.Update())
|
||||
ImGui::End();
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void OverworldEditor::DrawOverworldMapSettings() {
|
||||
if (ImGui::BeginTable("#mapSettings", 8, ow_map_flags, ImVec2(0, 0), -1)) {
|
||||
for (const auto &name : kOverworldSettingsColumnNames)
|
||||
ImGui::TableSetupColumn(name.data());
|
||||
if (ImGui::BeginTable(kOWMapTable.data(), 7, kOWMapFlags, ImVec2(0, 0), -1)) {
|
||||
for (const auto &name : {"##1stCol", "##gfxCol", "##palCol", "##sprgfxCol",
|
||||
"##sprpalCol", "##msgidCol", "##2ndCol"})
|
||||
ImGui::TableSetupColumn(name);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(50.f);
|
||||
ImGui::InputInt("Current Map", ¤t_map_);
|
||||
TableNextColumn();
|
||||
ImGui::SetNextItemWidth(120.f);
|
||||
ImGui::Combo("##world", ¤t_world_, kWorldList.data(), 3);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
TableNextColumn();
|
||||
gui::InputHexByte(
|
||||
"Gfx",
|
||||
overworld_.mutable_overworld_map(current_map_)->mutable_area_graphics(),
|
||||
kInputFieldSize);
|
||||
|
||||
TableNextColumn();
|
||||
gui::InputHexByte(
|
||||
"Palette",
|
||||
overworld_.mutable_overworld_map(current_map_)->mutable_area_palette(),
|
||||
kInputFieldSize);
|
||||
|
||||
TableNextColumn();
|
||||
gui::InputHexByte("Spr Gfx",
|
||||
overworld_.mutable_overworld_map(current_map_)
|
||||
->mutable_sprite_graphics(game_state_),
|
||||
kInputFieldSize);
|
||||
|
||||
TableNextColumn();
|
||||
gui::InputHexByte("Spr Palette",
|
||||
overworld_.mutable_overworld_map(current_map_)
|
||||
->mutable_sprite_palette(game_state_),
|
||||
kInputFieldSize);
|
||||
|
||||
TableNextColumn();
|
||||
gui::InputHexByte(
|
||||
"Msg Id",
|
||||
overworld_.mutable_overworld_map(current_map_)->mutable_message_id(),
|
||||
kInputFieldSize);
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::SetNextItemWidth(100.f);
|
||||
ImGui::Combo("##world", ¤t_world_,
|
||||
"Light World\0Dark World\0Extra World\0");
|
||||
ImGui::Combo("##World", &game_state_, kGamePartComboString.data(), 3);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("GFX");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(kInputFieldSize);
|
||||
ImGui::InputText("##mapGFX", map_gfx_, kByteSize);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Palette");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(kInputFieldSize);
|
||||
ImGui::InputText("##mapPal", map_palette_, kByteSize);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Spr GFX");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(kInputFieldSize);
|
||||
ImGui::InputText("##sprGFX", spr_gfx_, kByteSize);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Spr Palette");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(kInputFieldSize);
|
||||
ImGui::InputText("##sprPal", spr_palette_, kByteSize);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Msg ID");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(50.f);
|
||||
ImGui::InputText("##msgid", spr_palette_, kMessageIdSize);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Checkbox("Show grid", &opt_enable_grid); // TODO
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void OverworldEditor::DrawOverworldEntrances() {
|
||||
for (const auto &each : overworld_.Entrances()) {
|
||||
void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0,
|
||||
ImVec2 scrolling) {
|
||||
for (auto &each : overworld_.Entrances()) {
|
||||
if (each.map_id_ < 0x40 + (current_world_ * 0x40) &&
|
||||
each.map_id_ >= (current_world_ * 0x40)) {
|
||||
ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16,
|
||||
ImVec4(210, 24, 210, 150));
|
||||
std::string str = absl::StrFormat("%#x", each.entrance_id_);
|
||||
ow_map_canvas_.DrawText(str, each.x_ - 4, each.y_ - 2);
|
||||
|
||||
// Check if this entrance is being clicked and dragged
|
||||
if (IsMouseHoveringOverEntrance(each, canvas_p0, scrolling) &&
|
||||
ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
||||
dragged_entrance_ = &each;
|
||||
is_dragging_entrance_ = true;
|
||||
if (ImGui::BeginDragDropSource()) {
|
||||
ImGui::SetDragDropPayload("ENTRANCE_PAYLOAD", &each,
|
||||
sizeof(zelda3::OverworldEntrance));
|
||||
Text("Moving Entrance ID: %s", str.c_str());
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
} else if (is_dragging_entrance_ && dragged_entrance_ == &each &&
|
||||
ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
||||
// Adjust the X and Y position of the entrance rectangle based on the
|
||||
// mouse position when it is released after being dragged
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
const ImVec2 origin(canvas_p0.x + scrolling.x,
|
||||
canvas_p0.y + scrolling.y);
|
||||
dragged_entrance_->x_ = io.MousePos.x - origin.x - 8;
|
||||
dragged_entrance_->y_ = io.MousePos.y - origin.y - 8;
|
||||
is_dragging_entrance_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OverworldEditor::IsMouseHoveringOverEntrance(
|
||||
const zelda3::OverworldEntrance &entrance, ImVec2 canvas_p0,
|
||||
ImVec2 scrolling) {
|
||||
// Get the mouse position relative to the canvas
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
||||
|
||||
// Check if the mouse is hovering over the entrance
|
||||
if (mouse_pos.x >= entrance.x_ && mouse_pos.x <= entrance.x_ + 16 &&
|
||||
mouse_pos.y >= entrance.y_ && mouse_pos.y <= entrance.y_ + 16) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void OverworldEditor::DrawOverworldSprites() {
|
||||
for (const auto &sprite : overworld_.Sprites(game_state_)) {
|
||||
// Get the sprite's bitmap and real X and Y positions
|
||||
auto id = sprite.id();
|
||||
const gfx::Bitmap &sprite_bitmap = sprite_previews_[id];
|
||||
int realX = sprite.GetRealX();
|
||||
int realY = sprite.GetRealY();
|
||||
|
||||
// Draw the sprite's bitmap onto the canvas at its real X and Y positions
|
||||
ow_map_canvas_.DrawBitmap(sprite_bitmap, realX, realY);
|
||||
ow_map_canvas_.DrawRect(realX, realY, sprite.Width(), sprite.Height(),
|
||||
ImVec4(255, 0, 0, 150));
|
||||
std::string str = absl::StrFormat("%s", sprite.Name());
|
||||
ow_map_canvas_.DrawText(str, realX - 4, realY - 2);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void OverworldEditor::DrawOverworldMaps() {
|
||||
@@ -153,42 +322,169 @@ void OverworldEditor::DrawOverworldMaps() {
|
||||
int yy = 0;
|
||||
for (int i = 0; i < 0x40; i++) {
|
||||
int world_index = i + (current_world_ * 0x40);
|
||||
int map_x = (xx * 0x200);
|
||||
int map_y = (yy * 0x200);
|
||||
ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y);
|
||||
int map_x = (xx * 0x200 * ow_map_canvas_.global_scale());
|
||||
int map_y = (yy * 0x200 * ow_map_canvas_.global_scale());
|
||||
ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y,
|
||||
ow_map_canvas_.global_scale());
|
||||
xx++;
|
||||
if (xx >= 8) {
|
||||
yy++;
|
||||
xx = 0;
|
||||
}
|
||||
DrawOverworldEntrances();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
void OverworldEditor::DrawOverworldEdits() {
|
||||
auto mouse_position = ow_map_canvas_.drawn_tile_position();
|
||||
auto canvas_size = ow_map_canvas_.canvas_size();
|
||||
int x = mouse_position.x / canvas_size.x;
|
||||
int y = mouse_position.y / canvas_size.y;
|
||||
|
||||
// Determine which overworld map the user is currently editing.
|
||||
DetermineActiveMap(mouse_position);
|
||||
|
||||
// Render the updated map bitmap.
|
||||
RenderUpdatedMapBitmap(mouse_position,
|
||||
tile16_individual_data_[current_tile16_]);
|
||||
}
|
||||
|
||||
void OverworldEditor::DetermineActiveMap(const ImVec2 &mouse_position) {
|
||||
// Assuming each small map is 256x256 pixels (adjust as needed)
|
||||
constexpr int small_map_size = 512;
|
||||
|
||||
// Calculate which small map the mouse is currently over
|
||||
int map_x = mouse_position.x / small_map_size;
|
||||
int map_y = mouse_position.y / small_map_size;
|
||||
|
||||
// Calculate the index of the map in the `maps_bmp_` vector
|
||||
current_map_ = map_x + map_y * 8;
|
||||
}
|
||||
|
||||
void OverworldEditor::RenderUpdatedMapBitmap(const ImVec2 &click_position,
|
||||
const Bytes &tile_data) {
|
||||
// Calculate the tile position relative to the current active map
|
||||
constexpr int tile_size = 16; // Tile size is 16x16 pixels
|
||||
|
||||
// Calculate the tile index for x and y based on the click_position
|
||||
int tile_index_x = (static_cast<int>(click_position.x) % 512) / tile_size;
|
||||
int tile_index_y = (static_cast<int>(click_position.y) % 512) / tile_size;
|
||||
|
||||
// Calculate the pixel start position based on tile index and tile size
|
||||
ImVec2 start_position;
|
||||
start_position.x = tile_index_x * tile_size;
|
||||
start_position.y = tile_index_y * tile_size;
|
||||
|
||||
// Get the current map's bitmap from the BitmapTable
|
||||
gfx::Bitmap ¤t_bitmap = maps_bmp_[current_map_];
|
||||
|
||||
// Update the bitmap's pixel data based on the start_position and tile_data
|
||||
for (int y = 0; y < tile_size; ++y) {
|
||||
for (int x = 0; x < tile_size; ++x) {
|
||||
int pixel_index = (start_position.y + y) * current_bitmap.width() +
|
||||
(start_position.x + x);
|
||||
current_bitmap.WriteToPixel(pixel_index, tile_data[y * tile_size + x]);
|
||||
}
|
||||
}
|
||||
|
||||
// Render the updated bitmap to the canvas
|
||||
rom()->UpdateBitmap(¤t_bitmap);
|
||||
}
|
||||
|
||||
void OverworldEditor::SaveOverworldChanges() {
|
||||
// Store the changes made by the user to the ROM (or project file)
|
||||
rom()->QueueChanges([&]() {
|
||||
PRINT_IF_ERROR(overworld_.SaveOverworldMaps());
|
||||
if (!overworld_.CreateTile32Tilemap()) {
|
||||
// overworld_.SaveMap16Tiles();
|
||||
PRINT_IF_ERROR(overworld_.SaveMap32Tiles());
|
||||
} else {
|
||||
std::cout << "Failed to create tile32 tilemap" << std::endl;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OverworldEditor::CheckForOverworldEdits() {
|
||||
if (!blockset_canvas_.Points().empty()) {
|
||||
// User has selected a tile they want to draw from the blockset.
|
||||
int x = blockset_canvas_.Points().front().x / 32;
|
||||
int y = blockset_canvas_.Points().front().y / 32;
|
||||
current_tile16_ = x + (y * 8);
|
||||
if (ow_map_canvas_.DrawTilePainter(tile16_individual_[current_tile16_],
|
||||
16)) {
|
||||
// Update the overworld map.
|
||||
DrawOverworldEdits();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OverworldEditor::CheckForCurrentMap() {
|
||||
// DetermineActiveMap(ImGui::GetIO().MousePos);
|
||||
// 4096x4096, 512x512 maps and some are larges maps 1024x1024
|
||||
auto current_map_x = current_map_ % 8;
|
||||
auto current_map_y = current_map_ / 8;
|
||||
auto large_map_size = 1024;
|
||||
auto map_size = 512;
|
||||
|
||||
auto mouse_position = ImGui::GetIO().MousePos;
|
||||
|
||||
// Assuming each small map is 256x256 pixels (adjust as needed)
|
||||
constexpr int small_map_size = 512;
|
||||
|
||||
// Calculate which small map the mouse is currently over
|
||||
int map_x = mouse_position.x / small_map_size;
|
||||
int map_y = mouse_position.y / small_map_size;
|
||||
|
||||
// Calculate the index of the map in the `maps_bmp_` vector
|
||||
current_map_ = map_x + map_y * 8;
|
||||
|
||||
if (overworld_.overworld_map(current_map_).IsLargeMap()) {
|
||||
// Draw an outline around the current map
|
||||
ow_map_canvas_.DrawOutline(current_map_x * large_map_size,
|
||||
current_map_y * large_map_size, large_map_size,
|
||||
large_map_size);
|
||||
} else {
|
||||
// Draw an outline around the current map
|
||||
ow_map_canvas_.DrawOutline(current_map_x * map_size,
|
||||
current_map_y * map_size, map_size, map_size);
|
||||
}
|
||||
|
||||
static int prev_map_;
|
||||
|
||||
if (current_map_ != prev_map_) {
|
||||
// Update the current map's tile16 blockset
|
||||
// core::BuildAndRenderBitmapPipeline(
|
||||
// 0x80, 0x2000, 0x80, maps_bmp_[current_map_].mutable_data(), *rom(),
|
||||
// maps_bmp_[current_map_], palette_);
|
||||
|
||||
prev_map_ = current_map_;
|
||||
}
|
||||
}
|
||||
|
||||
// Overworld Editor canvas
|
||||
// Allows the user to make changes to the overworld map.
|
||||
void OverworldEditor::DrawOverworldCanvas() {
|
||||
DrawOverworldMapSettings();
|
||||
ImGui::Separator();
|
||||
if (all_gfx_loaded_) {
|
||||
DrawOverworldMapSettings();
|
||||
Separator();
|
||||
}
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)7);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar |
|
||||
ImGuiWindowFlags_AlwaysHorizontalScrollbar)) {
|
||||
ow_map_canvas_.DrawBackground(ImVec2(0x200 * 8, 0x200 * 8));
|
||||
ImGui::PopStyleVar(2);
|
||||
ow_map_canvas_.DrawContextMenu();
|
||||
if (overworld_.isLoaded()) {
|
||||
DrawOverworldMaps();
|
||||
// User has selected a tile they want to draw from the blockset.
|
||||
if (!blockset_canvas_.Points().empty()) {
|
||||
int x = blockset_canvas_.Points().front().x / 32;
|
||||
int y = blockset_canvas_.Points().front().y / 32;
|
||||
std::cout << x << " " << y << std::endl;
|
||||
current_tile16_ = x + (y * 8);
|
||||
std::cout << current_tile16_ << std::endl;
|
||||
ow_map_canvas_.DrawTilePainter(tile16_individual_[current_tile16_], 16);
|
||||
DrawOverworldEntrances(ow_map_canvas_.zero_point(),
|
||||
ow_map_canvas_.Scrolling());
|
||||
if (flags()->kDrawOverworldSprites) {
|
||||
DrawOverworldSprites();
|
||||
}
|
||||
CheckForCurrentMap();
|
||||
CheckForOverworldEdits();
|
||||
}
|
||||
ow_map_canvas_.DrawGrid(64.0f);
|
||||
ow_map_canvas_.DrawOverlay();
|
||||
@@ -196,21 +492,6 @@ void OverworldEditor::DrawOverworldCanvas() {
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Tile 16 Selector
|
||||
// Displays all the tiles in the game.
|
||||
void OverworldEditor::DrawTile16Selector() {
|
||||
blockset_canvas_.DrawBackground(ImVec2(0x100 + 1, (8192 * 2) + 1));
|
||||
blockset_canvas_.DrawContextMenu();
|
||||
blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 2, map_blockset_loaded_);
|
||||
blockset_canvas_.DrawTileSelector(32);
|
||||
blockset_canvas_.DrawGrid(32.0f);
|
||||
blockset_canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Tile 8 Selector
|
||||
// Displays all the individual tiles that make up a tile16.
|
||||
void OverworldEditor::DrawTile8Selector() {
|
||||
@@ -218,65 +499,46 @@ void OverworldEditor::DrawTile8Selector() {
|
||||
ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1));
|
||||
graphics_bin_canvas_.DrawContextMenu();
|
||||
if (all_gfx_loaded_) {
|
||||
for (const auto &[key, value] : graphics_bin_) {
|
||||
// for (const auto &[key, value] : graphics_bin_) {
|
||||
for (auto &[key, value] : rom()->bitmap_manager()) {
|
||||
int offset = 0x40 * (key + 1);
|
||||
int top_left_y = graphics_bin_canvas_.GetZeroPoint().y + 2;
|
||||
int top_left_y = graphics_bin_canvas_.zero_point().y + 2;
|
||||
if (key >= 1) {
|
||||
top_left_y = graphics_bin_canvas_.GetZeroPoint().y + 0x40 * key;
|
||||
top_left_y = graphics_bin_canvas_.zero_point().y + 0x40 * key;
|
||||
}
|
||||
auto texture = value.get()->texture();
|
||||
graphics_bin_canvas_.GetDrawList()->AddImage(
|
||||
(void *)value.GetTexture(),
|
||||
ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 2, top_left_y),
|
||||
ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 0x100,
|
||||
graphics_bin_canvas_.GetZeroPoint().y + offset));
|
||||
(void *)texture,
|
||||
ImVec2(graphics_bin_canvas_.zero_point().x + 2, top_left_y),
|
||||
ImVec2(graphics_bin_canvas_.zero_point().x + 0x100,
|
||||
graphics_bin_canvas_.zero_point().y + offset));
|
||||
}
|
||||
}
|
||||
graphics_bin_canvas_.DrawGrid(16.0f);
|
||||
graphics_bin_canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Displays the graphics tilesheets that are available on the current selected
|
||||
// overworld map.
|
||||
void OverworldEditor::DrawAreaGraphics() {
|
||||
current_gfx_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
|
||||
current_gfx_canvas_.DrawContextMenu();
|
||||
current_gfx_canvas_.DrawTileSelector(32);
|
||||
current_gfx_canvas_.DrawBitmap(current_gfx_bmp_, 2, overworld_.isLoaded());
|
||||
current_gfx_canvas_.DrawGrid(32.0f);
|
||||
current_gfx_canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void OverworldEditor::DrawTileSelector() {
|
||||
if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
|
||||
if (ImGui::BeginTabBar(kTileSelectorTab.data(),
|
||||
ImGuiTabBarFlags_FittingPolicyScroll)) {
|
||||
if (ImGui::BeginTabItem("Tile16")) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)2);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
DrawTile16Selector();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
gui::BitmapCanvasPipeline(blockset_canvas_, tile16_blockset_bmp_, 0x100,
|
||||
(8192 * 2), 0x20, map_blockset_loaded_, true,
|
||||
1);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Tile8")) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)1);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
if (ImGui::BeginChild("##tile8viewer", ImGui::GetContentRegionAvail(),
|
||||
true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
DrawTile8Selector();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Area Graphics")) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)3);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
DrawAreaGraphics();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
gui::BitmapCanvasPipeline(current_gfx_canvas_, current_gfx_bmp_, 256,
|
||||
0x10 * 0x40, 0x20, overworld_.isLoaded(), true,
|
||||
3);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
@@ -287,36 +549,40 @@ void OverworldEditor::DrawTileSelector() {
|
||||
|
||||
absl::Status OverworldEditor::LoadGraphics() {
|
||||
// Load all of the graphics data from the game.
|
||||
PRINT_IF_ERROR(rom_.LoadAllGraphicsData())
|
||||
graphics_bin_ = rom_.GetGraphicsBin();
|
||||
PRINT_IF_ERROR(rom()->LoadAllGraphicsData())
|
||||
graphics_bin_ = rom()->graphics_bin();
|
||||
|
||||
// Load the Link to the Past overworld.
|
||||
RETURN_IF_ERROR(overworld_.Load(rom_))
|
||||
RETURN_IF_ERROR(overworld_.Load(*rom()))
|
||||
palette_ = overworld_.AreaPalette();
|
||||
current_gfx_bmp_.Create(0x80, 0x200, 0x40, overworld_.AreaGraphics());
|
||||
current_gfx_bmp_.ApplyPalette(palette_);
|
||||
rom_.RenderBitmap(¤t_gfx_bmp_);
|
||||
|
||||
// Create the area graphics image
|
||||
gui::BuildAndRenderBitmapPipeline(0x80, 0x200, 0x40,
|
||||
overworld_.AreaGraphics(), *rom(),
|
||||
current_gfx_bmp_, palette_);
|
||||
|
||||
// Create the tile16 blockset image
|
||||
tile16_blockset_bmp_.Create(0x80, 8192, 0x80, overworld_.Tile16Blockset());
|
||||
tile16_blockset_bmp_.ApplyPalette(palette_);
|
||||
rom_.RenderBitmap(&tile16_blockset_bmp_);
|
||||
gui::BuildAndRenderBitmapPipeline(0x80, 0x2000, 0x80,
|
||||
overworld_.Tile16Blockset(), *rom(),
|
||||
tile16_blockset_bmp_, palette_);
|
||||
map_blockset_loaded_ = true;
|
||||
|
||||
// Copy the tile16 data into individual tiles.
|
||||
auto tile16_data = overworld_.Tile16Blockset();
|
||||
|
||||
std::cout << tile16_data.size() << std::endl;
|
||||
|
||||
// Loop through the tiles and copy their pixel data into separate vectors
|
||||
for (int i = 0; i < 4096; i++) {
|
||||
// Create a new vector for the pixel data of the current tile
|
||||
Bytes tile_data;
|
||||
for (int j = 0; j < 32 * 32; j++) tile_data.push_back(0x00);
|
||||
Bytes tile_data(16 * 16, 0x00); // More efficient initialization
|
||||
|
||||
// Copy the pixel data for the current tile into the vector
|
||||
for (int ty = 0; ty < 32; ty++) {
|
||||
for (int tx = 0; tx < 32; tx++) {
|
||||
int position = (tx + (ty * 0x20));
|
||||
uchar value = tile16_data[i + tx + (ty * 0x80)];
|
||||
for (int ty = 0; ty < 16; ty++) {
|
||||
for (int tx = 0; tx < 16; tx++) {
|
||||
int position = tx + (ty * 0x10);
|
||||
uchar value =
|
||||
tile16_data[(i % 8 * 16) + (i / 8 * 16 * 0x80) + (ty * 0x80) + tx];
|
||||
tile_data[position] = value;
|
||||
}
|
||||
}
|
||||
@@ -327,23 +593,87 @@ absl::Status OverworldEditor::LoadGraphics() {
|
||||
|
||||
// Render the bitmaps of each tile.
|
||||
for (int id = 0; id < 4096; id++) {
|
||||
gfx::Bitmap new_tile16;
|
||||
tile16_individual_.emplace_back(new_tile16);
|
||||
tile16_individual_[id].Create(0x10, 0x10, 0x80,
|
||||
tile16_individual_data_[id]);
|
||||
tile16_individual_[id].ApplyPalette(palette_);
|
||||
rom_.RenderBitmap(&tile16_individual_[id]);
|
||||
tile16_individual_.emplace_back();
|
||||
gui::BuildAndRenderBitmapPipeline(0x10, 0x10, 0x80,
|
||||
tile16_individual_data_[id], *rom(),
|
||||
tile16_individual_[id], palette_);
|
||||
}
|
||||
|
||||
// Render the overworld maps loaded from the ROM.
|
||||
for (int i = 0; i < core::kNumOverworldMaps; ++i) {
|
||||
for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
|
||||
overworld_.SetCurrentMap(i);
|
||||
auto palette = overworld_.AreaPalette();
|
||||
maps_bmp_[i].Create(0x200, 0x200, 0x200, overworld_.BitmapData());
|
||||
maps_bmp_[i].ApplyPalette(palette);
|
||||
rom_.RenderBitmap(&(maps_bmp_[i]));
|
||||
gui::BuildAndRenderBitmapPipeline(0x200, 0x200, 0x200,
|
||||
overworld_.BitmapData(), *rom(),
|
||||
maps_bmp_[i], palette);
|
||||
}
|
||||
|
||||
if (flags()->kDrawOverworldSprites) {
|
||||
RETURN_IF_ERROR(LoadSpriteGraphics());
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status OverworldEditor::LoadSpriteGraphics() {
|
||||
// Render the sprites for each Overworld map
|
||||
for (int i = 0; i < 3; i++)
|
||||
for (auto const &sprite : overworld_.Sprites(i)) {
|
||||
int width = sprite.Width();
|
||||
int height = sprite.Height();
|
||||
int depth = 0x40;
|
||||
auto spr_gfx = sprite.PreviewGraphics();
|
||||
sprite_previews_[sprite.id()].Create(width, height, depth, spr_gfx);
|
||||
sprite_previews_[sprite.id()].ApplyPalette(palette_);
|
||||
rom()->RenderBitmap(&(sprite_previews_[sprite.id()]));
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status OverworldEditor::DrawExperimentalModal() {
|
||||
ImGui::Begin("Experimental", &show_experimental);
|
||||
|
||||
gui::TextWithSeparators("PROTOTYPE OVERWORLD TILEMAP LOADER");
|
||||
Text("Please provide two files:");
|
||||
Text("One based on MAPn.DAT, which represents the overworld tilemap");
|
||||
Text("One based on MAPDATn.DAT, which is the tile32 configurations.");
|
||||
Text("Currently, loading CGX for this component is NOT supported. ");
|
||||
Text("Please load a US ROM of LTTP (JP ROM support coming soon).");
|
||||
Text(
|
||||
"Once you've loaded the files, you can click the button below to load "
|
||||
"the tilemap into the editor");
|
||||
|
||||
ImGui::InputText("##TilemapFile", &ow_tilemap_filename_);
|
||||
ImGui::SameLine();
|
||||
gui::FileDialogPipeline(
|
||||
"ImportTilemapsKey", ".DAT,.dat\0", "Tilemap Hex File", [this]() {
|
||||
ow_tilemap_filename_ = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
});
|
||||
|
||||
ImGui::InputText("##Tile32ConfigurationFile",
|
||||
&tile32_configuration_filename_);
|
||||
ImGui::SameLine();
|
||||
gui::FileDialogPipeline("ImportTile32Key", ".DAT,.dat\0", "Tile32 Hex File",
|
||||
[this]() {
|
||||
tile32_configuration_filename_ =
|
||||
ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
});
|
||||
|
||||
if (ImGui::Button("Load Prototype Overworld with ROM graphics")) {
|
||||
RETURN_IF_ERROR(LoadGraphics())
|
||||
all_gfx_loaded_ = true;
|
||||
}
|
||||
|
||||
gui::TextWithSeparators("Configuration");
|
||||
|
||||
gui::InputHexShort("Tilemap File Offset (High)", &tilemap_file_offset_high_);
|
||||
gui::InputHexShort("Tilemap File Offset (Low)", &tilemap_file_offset_low_);
|
||||
|
||||
gui::InputHexShort("LW Maps to Load", &light_maps_to_load_);
|
||||
gui::InputHexShort("DW Maps to Load", &dark_maps_to_load_);
|
||||
gui::InputHexShort("SP Maps to Load", &sp_maps_to_load_);
|
||||
|
||||
ImGui::End();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,19 @@
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/palette_editor.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/editor/modules/gfx_group_editor.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/editor/modules/tile16_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -31,43 +36,98 @@ static constexpr uint kTile8DisplayHeight = 64;
|
||||
static constexpr float kInputFieldSize = 30.f;
|
||||
|
||||
static constexpr absl::string_view kToolsetColumnNames[] = {
|
||||
"#undoTool", "#redoTool", "#drawTool", "#separator2",
|
||||
"#zoomOutTool", "#zoomInTool", "#separator", "#history",
|
||||
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
|
||||
"#transportTool", "#musicTool"};
|
||||
"#undoTool", "#redoTool", "#drawTool", "#separator2",
|
||||
"#zoomOutTool", "#zoomInTool", "#separator", "#history",
|
||||
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
|
||||
"#transportTool", "#musicTool", "#separator3", "#tilemapTool"};
|
||||
|
||||
static constexpr absl::string_view kOverworldSettingsColumnNames[] = {
|
||||
"##1stCol", "##gfxCol", "##palCol", "##sprgfxCol",
|
||||
"##sprpalCol", "##msgidCol", "##2ndCol"};
|
||||
constexpr ImGuiTableFlags kOWMapFlags = ImGuiTableFlags_Borders;
|
||||
constexpr ImGuiTableFlags kToolsetTableFlags = ImGuiTableFlags_SizingFixedFit;
|
||||
constexpr ImGuiTableFlags kOWEditFlags =
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||
ImGuiTableFlags_BordersV;
|
||||
|
||||
class OverworldEditor {
|
||||
constexpr absl::string_view kWorldList =
|
||||
"Light World\0Dark World\0Extra World\0";
|
||||
|
||||
constexpr absl::string_view kGamePartComboString = "Part 0\0Part 1\0Part 2\0";
|
||||
|
||||
constexpr absl::string_view kTileSelectorTab = "##TileSelectorTabBar";
|
||||
constexpr absl::string_view kOWEditTable = "##OWEditTable";
|
||||
constexpr absl::string_view kOWMapTable = "#MapSettingsTable";
|
||||
|
||||
class OverworldEditor : public Editor,
|
||||
public SharedROM,
|
||||
public core::ExperimentFlags {
|
||||
public:
|
||||
absl::Status Update();
|
||||
absl::Status Undo() const { return absl::UnimplementedError("Undo"); }
|
||||
absl::Status Redo() const { return absl::UnimplementedError("Redo"); }
|
||||
absl::Status Cut() const { return absl::UnimplementedError("Cut"); }
|
||||
absl::Status Copy() const { return absl::UnimplementedError("Copy"); }
|
||||
absl::Status Paste() const { return absl::UnimplementedError("Paste"); }
|
||||
void SetupROM(ROM &rom) { rom_ = rom; }
|
||||
absl::Status Update() final;
|
||||
absl::Status Undo() { return absl::UnimplementedError("Undo"); }
|
||||
absl::Status Redo() { return absl::UnimplementedError("Redo"); }
|
||||
absl::Status Cut() { return absl::UnimplementedError("Cut"); }
|
||||
absl::Status Copy() { return absl::UnimplementedError("Copy"); }
|
||||
absl::Status Paste() { return absl::UnimplementedError("Paste"); }
|
||||
|
||||
auto overworld() { return &overworld_; }
|
||||
|
||||
void Shutdown() {
|
||||
for (auto &bmp : tile16_individual_) {
|
||||
bmp.Cleanup();
|
||||
}
|
||||
for (auto &[i, bmp] : maps_bmp_) {
|
||||
bmp.Cleanup();
|
||||
}
|
||||
for (auto &[i, bmp] : graphics_bin_) {
|
||||
bmp.Cleanup();
|
||||
}
|
||||
for (auto &[i, bmp] : current_graphics_set_) {
|
||||
bmp.Cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status LoadGraphics();
|
||||
|
||||
private:
|
||||
absl::Status DrawToolset();
|
||||
void DrawOverworldMapSettings();
|
||||
|
||||
void DrawOverworldEntrances();
|
||||
void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling);
|
||||
void DrawOverworldMaps();
|
||||
void DrawOverworldSprites();
|
||||
|
||||
void DrawOverworldEdits();
|
||||
void RenderUpdatedMapBitmap(const ImVec2 &click_position,
|
||||
const Bytes &tile_data);
|
||||
void SaveOverworldChanges();
|
||||
void DetermineActiveMap(const ImVec2 &mouse_position);
|
||||
|
||||
void CheckForOverworldEdits();
|
||||
void CheckForCurrentMap();
|
||||
void DrawOverworldCanvas();
|
||||
|
||||
void DrawTile16Selector();
|
||||
void DrawTile8Selector();
|
||||
void DrawAreaGraphics();
|
||||
void DrawTileSelector();
|
||||
absl::Status LoadGraphics();
|
||||
|
||||
absl::Status LoadSpriteGraphics();
|
||||
absl::Status DrawExperimentalModal();
|
||||
|
||||
enum class EditingMode {
|
||||
DRAW_TILE,
|
||||
ENTRANCES,
|
||||
EXITS,
|
||||
ITEMS,
|
||||
SPRITES,
|
||||
TRANSPORTS,
|
||||
MUSIC
|
||||
};
|
||||
|
||||
EditingMode current_mode = EditingMode::DRAW_TILE;
|
||||
|
||||
int current_world_ = 0;
|
||||
int current_map_ = 0;
|
||||
int current_tile16_ = 0;
|
||||
int selected_tile_ = 0;
|
||||
int game_state_ = 0;
|
||||
char map_gfx_[3] = "";
|
||||
char map_palette_[3] = "";
|
||||
char spr_gfx_[3] = "";
|
||||
@@ -75,41 +135,58 @@ class OverworldEditor {
|
||||
char message_id_[5] = "";
|
||||
char staticgfx[16];
|
||||
|
||||
uint32_t tilemap_file_offset_high_ = 0;
|
||||
uint32_t tilemap_file_offset_low_ = 0;
|
||||
uint32_t light_maps_to_load_ = 0x51;
|
||||
uint32_t dark_maps_to_load_ = 0x2A;
|
||||
uint32_t sp_maps_to_load_ = 0x07;
|
||||
|
||||
bool opt_enable_grid = true;
|
||||
bool all_gfx_loaded_ = false;
|
||||
bool map_blockset_loaded_ = false;
|
||||
bool selected_tile_loaded_ = false;
|
||||
bool update_selected_tile_ = true;
|
||||
bool is_dragging_entrance_ = false;
|
||||
bool show_tile16_editor_ = false;
|
||||
bool show_gfx_group_editor_ = false;
|
||||
|
||||
ImGuiTableFlags toolset_table_flags = ImGuiTableFlags_SizingFixedFit;
|
||||
ImGuiTableFlags ow_map_flags = ImGuiTableFlags_Borders;
|
||||
ImGuiTableFlags ow_edit_flags = ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Resizable |
|
||||
ImGuiTableFlags_SizingStretchSame;
|
||||
bool IsMouseHoveringOverEntrance(const zelda3::OverworldEntrance &entrance,
|
||||
ImVec2 canvas_p, ImVec2 scrolling);
|
||||
zelda3::OverworldEntrance *dragged_entrance_;
|
||||
|
||||
bool show_experimental = false;
|
||||
std::string ow_tilemap_filename_ = "";
|
||||
std::string tile32_configuration_filename_ = "";
|
||||
|
||||
Bytes selected_tile_data_;
|
||||
std::unordered_map<int, gfx::Bitmap> graphics_bin_;
|
||||
std::unordered_map<int, gfx::Bitmap> current_graphics_set_;
|
||||
std::unordered_map<int, gfx::Bitmap> maps_bmp_;
|
||||
std::unordered_map<int, gfx::Bitmap> sprite_previews_;
|
||||
|
||||
std::vector<Bytes> tile16_individual_data_;
|
||||
std::vector<gfx::Bitmap> tile16_individual_;
|
||||
|
||||
ROM rom_;
|
||||
std::vector<Bytes> tile8_individual_data_;
|
||||
std::vector<gfx::Bitmap> tile8_individual_;
|
||||
|
||||
Tile16Editor tile16_editor_;
|
||||
GfxGroupEditor gfx_group_editor_;
|
||||
PaletteEditor palette_editor_;
|
||||
zelda3::Overworld overworld_;
|
||||
|
||||
gui::Canvas ow_map_canvas_;
|
||||
gui::Canvas current_gfx_canvas_;
|
||||
gui::Canvas blockset_canvas_;
|
||||
gui::Canvas graphics_bin_canvas_;
|
||||
|
||||
gfx::SNESPalette palette_;
|
||||
gfx::Bitmap selected_tile_bmp_;
|
||||
gfx::Bitmap tile16_blockset_bmp_;
|
||||
gfx::Bitmap current_gfx_bmp_;
|
||||
gfx::Bitmap all_gfx_bmp;
|
||||
|
||||
gui::Canvas ow_map_canvas_;
|
||||
gui::Canvas current_gfx_canvas_;
|
||||
gui::Canvas blockset_canvas_;
|
||||
gui::Canvas graphics_bin_canvas_;
|
||||
gfx::BitmapTable maps_bmp_;
|
||||
gfx::BitmapTable graphics_bin_;
|
||||
gfx::BitmapTable current_graphics_set_;
|
||||
gfx::BitmapTable sprite_previews_;
|
||||
|
||||
absl::Status status_;
|
||||
};
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
#include "palette_editor.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
absl::Status PaletteEditor::Update() {
|
||||
for (int i = 0; i < 11; ++i) {
|
||||
if (ImGui::TreeNode(kPaletteCategoryNames[i].data())) {
|
||||
auto size = rom_.GetPaletteGroup(kPaletteGroupNames[i].data()).size;
|
||||
auto palettes = rom_.GetPaletteGroup(kPaletteGroupNames[i].data());
|
||||
for (int j = 0; j < size; j++) {
|
||||
ImGui::Text("%d", j);
|
||||
auto palette = palettes[j];
|
||||
for (int n = 0; n < size; n++) {
|
||||
ImGui::PushID(n);
|
||||
if ((n % 8) != 0)
|
||||
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
ImGuiColorEditFlags palette_button_flags =
|
||||
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker;
|
||||
if (ImGui::ColorButton("##palette", palette[n].RGB(),
|
||||
palette_button_flags, ImVec2(20, 20)))
|
||||
current_color_ =
|
||||
ImVec4(palette[n].rgb.x, palette[n].rgb.y, palette[n].rgb.z,
|
||||
current_color_.w); // Preserve alpha!
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void PaletteEditor::DisplayPalette(gfx::SNESPalette& palette, bool loaded) {
|
||||
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
|
||||
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
|
||||
ImGuiColorEditFlags_NoDragDrop |
|
||||
ImGuiColorEditFlags_NoOptions;
|
||||
|
||||
// Generate a default palette. The palette will persist and can be edited.
|
||||
static bool init = false;
|
||||
static ImVec4 saved_palette[256] = {};
|
||||
if (loaded && !init) {
|
||||
for (int n = 0; n < palette.size_; n++) {
|
||||
saved_palette[n].x = palette.GetColor(n).rgb.x / 255;
|
||||
saved_palette[n].y = palette.GetColor(n).rgb.y / 255;
|
||||
saved_palette[n].z = palette.GetColor(n).rgb.z / 255;
|
||||
saved_palette[n].w = 255; // Alpha
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
static ImVec4 backup_color;
|
||||
bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags);
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
open_popup |= ImGui::Button("Palette");
|
||||
if (open_popup) {
|
||||
ImGui::OpenPopup("mypicker");
|
||||
backup_color = color;
|
||||
}
|
||||
if (ImGui::BeginPopup("mypicker")) {
|
||||
ImGui::Text("Current Overworld Palette");
|
||||
ImGui::Separator();
|
||||
ImGui::ColorPicker4("##picker", (float*)&color,
|
||||
misc_flags | ImGuiColorEditFlags_NoSidePreview |
|
||||
ImGuiColorEditFlags_NoSmallPreview);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginGroup(); // Lock X position
|
||||
ImGui::Text("Current ==>");
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Previous");
|
||||
|
||||
ImGui::ColorButton(
|
||||
"##current", color,
|
||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||
ImVec2(60, 40));
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::ColorButton(
|
||||
"##previous", backup_color,
|
||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||
ImVec2(60, 40)))
|
||||
color = backup_color;
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Palette");
|
||||
for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) {
|
||||
ImGui::PushID(n);
|
||||
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha |
|
||||
ImGuiColorEditFlags_NoPicker |
|
||||
ImGuiColorEditFlags_NoTooltip;
|
||||
if (ImGui::ColorButton("##palette", saved_palette[n],
|
||||
palette_button_flags, ImVec2(20, 20)))
|
||||
color = ImVec4(saved_palette[n].x, saved_palette[n].y,
|
||||
saved_palette[n].z, color.w); // Preserve alpha!
|
||||
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
|
||||
memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3);
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
|
||||
memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4);
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
@@ -1,42 +0,0 @@
|
||||
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
static constexpr absl::string_view kPaletteCategoryNames[] = {
|
||||
"Sword", "Shield", "Clothes", "World Colors",
|
||||
"Area Colors", "Enemies", "Dungeons", "World Map",
|
||||
"Dungeon Map", "Triforce", "Crystal"};
|
||||
|
||||
static constexpr absl::string_view kPaletteGroupNames[] = {
|
||||
"swords", "shields", "armors", "ow_main",
|
||||
"ow_aux", "global_sprites", "dungeon_main", "ow_mini_map",
|
||||
"ow_mini_map", "3d_object", "3d_object"};
|
||||
|
||||
class PaletteEditor {
|
||||
public:
|
||||
absl::Status Update();
|
||||
void DisplayPalette(gfx::SNESPalette& palette, bool loaded);
|
||||
|
||||
auto SetupROM(ROM& rom) { rom_ = rom; }
|
||||
|
||||
private:
|
||||
ImVec4 current_color_;
|
||||
ROM rom_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
@@ -15,9 +15,9 @@
|
||||
#include "app/core/constants.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -32,38 +32,14 @@ void ScreenEditor::Update() {
|
||||
DrawNamingScreenEditor();
|
||||
DrawOverworldMapEditor();
|
||||
DrawDungeonMapsEditor();
|
||||
DrawMosaicEditor();
|
||||
END_TAB_BAR()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawWorldGrid(int world, int h, int w) {
|
||||
const float time = (float)ImGui::GetTime();
|
||||
|
||||
int i = 0;
|
||||
if (world == 1) {
|
||||
i = 64;
|
||||
} else if (world == 2) {
|
||||
i = 128;
|
||||
}
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++) {
|
||||
if (x > 0) ImGui::SameLine();
|
||||
ImGui::PushID(y * 4 + x);
|
||||
std::string label = absl::StrCat(" #", absl::StrFormat("%x", i));
|
||||
if (ImGui::Selectable(label.c_str(), mosaic_tiles_[i] != 0, 0,
|
||||
ImVec2(35, 25))) {
|
||||
mosaic_tiles_[i] ^= 1;
|
||||
}
|
||||
ImGui::PopID();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawInventoryMenuEditor() {
|
||||
TAB_ITEM("Inventory Menu")
|
||||
|
||||
static bool create = false;
|
||||
if (!create && rom_.isLoaded()) {
|
||||
if (!create && rom()->isLoaded()) {
|
||||
inventory_.Create();
|
||||
palette_ = inventory_.Palette();
|
||||
create = true;
|
||||
@@ -117,40 +93,6 @@ void ScreenEditor::DrawDungeonMapsEditor() {
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawMosaicEditor() {
|
||||
TAB_ITEM("Mosaic Transitions")
|
||||
|
||||
if (ImGui::BeginTable("Worlds", 3, ImGuiTableFlags_Borders)) {
|
||||
ImGui::TableSetupColumn("Light World");
|
||||
ImGui::TableSetupColumn("Dark World");
|
||||
ImGui::TableSetupColumn("Special World");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
DrawWorldGrid(0);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
DrawWorldGrid(1);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
DrawWorldGrid(2, 4);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
gui::InputHex("Routine Location", &overworldCustomMosaicASM);
|
||||
|
||||
if (ImGui::Button("Generate Mosaic Assembly")) {
|
||||
auto mosaic =
|
||||
rom_.PatchOverworldMosaic(mosaic_tiles_, overworldCustomMosaicASM);
|
||||
if (!mosaic.ok()) {
|
||||
std::cout << mosaic;
|
||||
}
|
||||
}
|
||||
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawToolset() {
|
||||
static bool show_bg1 = true;
|
||||
static bool show_bg2 = true;
|
||||
|
||||
@@ -9,30 +9,22 @@
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/color.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/inventory.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/color.h"
|
||||
#include "gui/icons.h"
|
||||
#include "app/zelda3/screen/inventory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
using MosaicArray = std::array<int, core::kNumOverworldMaps>;
|
||||
static int overworldCustomMosaicASM = 0x1301D0;
|
||||
|
||||
class ScreenEditor {
|
||||
class ScreenEditor : public SharedROM {
|
||||
public:
|
||||
ScreenEditor();
|
||||
void SetupROM(ROM &rom) {
|
||||
rom_ = rom;
|
||||
inventory_.SetupROM(rom_);
|
||||
}
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void DrawMosaicEditor();
|
||||
void DrawTitleScreenEditor();
|
||||
void DrawNamingScreenEditor();
|
||||
void DrawOverworldMapEditor();
|
||||
@@ -41,11 +33,7 @@ class ScreenEditor {
|
||||
|
||||
void DrawToolset();
|
||||
void DrawInventoryToolset();
|
||||
void DrawWorldGrid(int world, int h = 8, int w = 8);
|
||||
|
||||
char mosaic_tiles_[core::kNumOverworldMaps];
|
||||
|
||||
ROM rom_;
|
||||
Bytes all_gfx_;
|
||||
zelda3::Inventory inventory_;
|
||||
gfx::SNESPalette palette_;
|
||||
|
||||
11
src/app/editor/sprite_editor.cc
Normal file
11
src/app/editor/sprite_editor.cc
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "sprite_editor.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
absl::Status SpriteEditor::Update() { return absl::OkStatus(); }
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
19
src/app/editor/sprite_editor.h
Normal file
19
src/app/editor/sprite_editor.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
||||
|
||||
#include "absl/status/status.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
class SpriteEditor {
|
||||
public:
|
||||
absl::Status Update();
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
||||
139
src/app/emu/audio/apu.cc
Normal file
139
src/app/emu/audio/apu.cc
Normal file
@@ -0,0 +1,139 @@
|
||||
#include "app/emu/audio/apu.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/audio/dsp.h"
|
||||
#include "app/emu/audio/spc700.h"
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void APU::Init() {
|
||||
// Set the clock frequency
|
||||
clock_.SetFrequency(kApuClockSpeed);
|
||||
|
||||
// Initialize Digital Signal Processor Callbacks
|
||||
dsp_.SetSampleFetcher([this](uint16_t address) -> uint8_t {
|
||||
return this->FetchSampleFromRam(address);
|
||||
});
|
||||
|
||||
dsp_.SetSamplePusher(
|
||||
[this](int16_t sample) { this->PushToAudioBuffer(sample); });
|
||||
}
|
||||
|
||||
void APU::Reset() {
|
||||
clock_.ResetAccumulatedTime();
|
||||
spc700_.Reset();
|
||||
dsp_.Reset();
|
||||
}
|
||||
|
||||
void APU::Update() {
|
||||
auto cycles_to_run = clock_.GetCycleCount();
|
||||
|
||||
for (auto i = 0; i < cycles_to_run; ++i) {
|
||||
// Update the APU
|
||||
UpdateChannelSettings();
|
||||
|
||||
// Update the SPC700
|
||||
uint8_t opcode = spc700_.read(spc700_.PC);
|
||||
spc700_.ExecuteInstructions(opcode);
|
||||
spc700_.PC++;
|
||||
}
|
||||
|
||||
ProcessSamples();
|
||||
}
|
||||
|
||||
void APU::Notify(uint32_t address, uint8_t data) {
|
||||
if (address < 0x2140 || address > 0x2143) {
|
||||
return;
|
||||
}
|
||||
auto offset = address - 0x2140;
|
||||
spc700_.write(offset, data);
|
||||
|
||||
// HACK - This is a temporary solution to get the APU to play audio
|
||||
ports_[address - 0x2140] = data;
|
||||
switch (address) {
|
||||
case 0x2140:
|
||||
if (data == BEGIN_SIGNAL) {
|
||||
SignalReady();
|
||||
}
|
||||
break;
|
||||
case 0x2141:
|
||||
// TODO: Handle data byte transfer here
|
||||
break;
|
||||
case 0x2142:
|
||||
// TODO: Handle the setup of destination address
|
||||
break;
|
||||
case 0x2143:
|
||||
// TODO: Handle additional communication/commands
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void APU::ProcessSamples() {
|
||||
// Fetch sample data from AudioRam
|
||||
// Iterate over all voices
|
||||
for (uint8_t voice_num = 0; voice_num < 8; voice_num++) {
|
||||
// Fetch the sample data for the current voice from AudioRam
|
||||
uint8_t sample = FetchSampleForVoice(voice_num);
|
||||
|
||||
// Process the sample through DSP
|
||||
int16_t processed_sample = dsp_.ProcessSample(voice_num, sample);
|
||||
|
||||
// Add the processed sample to the audio buffer
|
||||
audio_samples_.push_back(processed_sample);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t APU::FetchSampleForVoice(uint8_t voice_num) {
|
||||
uint16_t address = CalculateAddressForVoice(voice_num);
|
||||
return aram_.read(address);
|
||||
}
|
||||
|
||||
uint16_t APU::CalculateAddressForVoice(uint8_t voice_num) {
|
||||
// TODO: Calculate the address for the specified voice
|
||||
return voice_num;
|
||||
}
|
||||
|
||||
int16_t APU::GetNextSample() {
|
||||
if (!audio_samples_.empty()) {
|
||||
int16_t sample = audio_samples_.front();
|
||||
audio_samples_.erase(audio_samples_.begin());
|
||||
return sample;
|
||||
}
|
||||
return 0; // TODO: Return the last sample instead of 0.
|
||||
}
|
||||
|
||||
const std::vector<int16_t>& APU::GetAudioSamples() const {
|
||||
return audio_samples_;
|
||||
}
|
||||
|
||||
void APU::UpdateChannelSettings() {
|
||||
// TODO: Implement this method to update the channel settings.
|
||||
}
|
||||
|
||||
int16_t APU::GenerateSample(int channel) {
|
||||
// TODO: Implement this method to generate a sample for the specified channel.
|
||||
}
|
||||
|
||||
void APU::ApplyEnvelope(int channel) {
|
||||
// TODO: Implement this method to apply an envelope to the specified channel.
|
||||
}
|
||||
|
||||
uint8_t APU::ReadDspMemory(uint16_t address) {
|
||||
return dsp_.ReadGlobalReg(address);
|
||||
}
|
||||
|
||||
void APU::WriteDspMemory(uint16_t address, uint8_t value) {
|
||||
dsp_.WriteGlobalReg(address, value);
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
137
src/app/emu/audio/apu.h
Normal file
137
src/app/emu/audio/apu.h
Normal file
@@ -0,0 +1,137 @@
|
||||
#ifndef YAZE_APP_EMU_APU_H_
|
||||
#define YAZE_APP_EMU_APU_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/audio/dsp.h"
|
||||
#include "app/emu/audio/spc700.h"
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
/**
|
||||
*
|
||||
* 64 kilobytes of RAM are mapped across the 16-bit memory space of the SPC-700.
|
||||
* Some regions of this space are overlaid with special hardware functions.
|
||||
*
|
||||
* Range Note
|
||||
* $0000-00EF Zero Page RAM
|
||||
* $00F0-00FF Sound CPU Registers
|
||||
* $0100-01FF Stack Page RAM
|
||||
* $0200-FFBF RAM
|
||||
* $FFC0-FFFF IPL ROM or RAM
|
||||
*
|
||||
* The region at $FFC0-FFFF will normally read from the 64-byte IPL ROM, but the
|
||||
* underlying RAM can always be written to, and the high bit of the Control
|
||||
* register $F1 can be cleared to unmap the IPL ROM and allow read access to
|
||||
* this RAM.
|
||||
*
|
||||
*/
|
||||
|
||||
const int kApuClockSpeed = 1024000; // 1.024 MHz
|
||||
const int apuSampleRate = 32000; // 32 KHz
|
||||
const int apuClocksPerSample = 64; // 64 clocks per sample
|
||||
|
||||
class APU : public Observer {
|
||||
public:
|
||||
APU(MemoryImpl &memory, AudioRam &aram, Clock &clock)
|
||||
: aram_(aram), clock_(clock), memory_(memory) {}
|
||||
|
||||
void Init();
|
||||
void Reset();
|
||||
void Update();
|
||||
void Notify(uint32_t address, uint8_t data) override;
|
||||
|
||||
void ProcessSamples();
|
||||
uint8_t FetchSampleForVoice(uint8_t voice_num);
|
||||
uint16_t CalculateAddressForVoice(uint8_t voice_num);
|
||||
int16_t GetNextSample();
|
||||
|
||||
// Called upon a reset
|
||||
void Initialize() {
|
||||
spc700_.Reset();
|
||||
dsp_.Reset();
|
||||
SignalReady();
|
||||
}
|
||||
|
||||
// Set Port 0 = $AA and Port 1 = $BB
|
||||
void SignalReady() {
|
||||
memory_.WriteByte(0x2140, READY_SIGNAL_0);
|
||||
memory_.WriteByte(0x2141, READY_SIGNAL_1);
|
||||
}
|
||||
|
||||
void WriteToPort(uint8_t portNum, uint8_t value) {
|
||||
ports_[portNum] = value;
|
||||
switch (portNum) {
|
||||
case 0:
|
||||
memory_.WriteByte(0x2140, value);
|
||||
break;
|
||||
case 1:
|
||||
memory_.WriteByte(0x2141, value);
|
||||
break;
|
||||
case 2:
|
||||
memory_.WriteByte(0x2142, value);
|
||||
break;
|
||||
case 3:
|
||||
memory_.WriteByte(0x2143, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateClock(int delta_time) { clock_.UpdateClock(delta_time); }
|
||||
|
||||
// Method to fetch a sample from AudioRam
|
||||
uint8_t FetchSampleFromRam(uint16_t address) const {
|
||||
return aram_.read(address);
|
||||
}
|
||||
|
||||
// Method to push a processed sample to the audio buffer
|
||||
void PushToAudioBuffer(int16_t sample) { audio_samples_.push_back(sample); }
|
||||
|
||||
// Returns the audio samples for the current frame
|
||||
const std::vector<int16_t> &GetAudioSamples() const;
|
||||
|
||||
private:
|
||||
// Constants for communication
|
||||
static const uint8_t READY_SIGNAL_0 = 0xAA;
|
||||
static const uint8_t READY_SIGNAL_1 = 0xBB;
|
||||
static const uint8_t BEGIN_SIGNAL = 0xCC;
|
||||
|
||||
// Port buffers (equivalent to $2140 to $2143 for the main CPU)
|
||||
uint8_t ports_[4] = {0};
|
||||
|
||||
// Updates internal state based on APU register settings
|
||||
void UpdateChannelSettings();
|
||||
|
||||
// Generates a sample for an audio channel
|
||||
int16_t GenerateSample(int channel);
|
||||
|
||||
// Applies an envelope to an audio channel
|
||||
void ApplyEnvelope(int channel);
|
||||
|
||||
// Handles DSP (Digital Signal Processor) memory reads and writes
|
||||
uint8_t ReadDspMemory(uint16_t address);
|
||||
void WriteDspMemory(uint16_t address, uint8_t value);
|
||||
|
||||
// Member variables to store internal APU state and resources
|
||||
AudioRam &aram_;
|
||||
Clock &clock_;
|
||||
MemoryImpl &memory_;
|
||||
|
||||
DigitalSignalProcessor dsp_;
|
||||
Spc700 spc700_{aram_};
|
||||
std::vector<int16_t> audio_samples_;
|
||||
|
||||
std::function<void()> ready_callback_;
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
281
src/app/emu/audio/dsp.cc
Normal file
281
src/app/emu/audio/dsp.cc
Normal file
@@ -0,0 +1,281 @@
|
||||
#include "app/emu/audio/dsp.h"
|
||||
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void DigitalSignalProcessor::Reset() {}
|
||||
|
||||
uint8_t DigitalSignalProcessor::ReadVoiceReg(uint8_t voice, uint8_t reg) const {
|
||||
voice %= kNumVoices;
|
||||
switch (reg % kNumVoiceRegs) {
|
||||
case 0:
|
||||
return voices_[voice].vol_left;
|
||||
case 1:
|
||||
return voices_[voice].vol_right;
|
||||
case 2:
|
||||
return voices_[voice].pitch_low;
|
||||
case 3:
|
||||
return voices_[voice].pitch_high;
|
||||
case 4:
|
||||
return voices_[voice].source_number;
|
||||
case 5:
|
||||
return voices_[voice].adsr1;
|
||||
case 6:
|
||||
return voices_[voice].adsr2;
|
||||
case 7:
|
||||
return voices_[voice].gain;
|
||||
case 8:
|
||||
return voices_[voice].envx;
|
||||
case 9:
|
||||
return voices_[voice].outx;
|
||||
default:
|
||||
return 0; // This shouldn't happen, but it's good to have a default
|
||||
// case
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value) {
|
||||
voice %= kNumVoices;
|
||||
switch (reg % kNumVoiceRegs) {
|
||||
case 0:
|
||||
voices_[voice].vol_left = static_cast<int8_t>(value);
|
||||
break;
|
||||
case 1:
|
||||
voices_[voice].vol_right = static_cast<int8_t>(value);
|
||||
break;
|
||||
case 2:
|
||||
voices_[voice].pitch_low = value;
|
||||
break;
|
||||
case 3:
|
||||
voices_[voice].pitch_high = value;
|
||||
break;
|
||||
case 4:
|
||||
voices_[voice].source_number = value;
|
||||
break;
|
||||
case 5:
|
||||
voices_[voice].adsr1 = value;
|
||||
break;
|
||||
case 6:
|
||||
voices_[voice].adsr2 = value;
|
||||
break;
|
||||
case 7:
|
||||
voices_[voice].gain = value;
|
||||
break;
|
||||
// Note: envx and outx are read-only, so they don't have cases here
|
||||
}
|
||||
}
|
||||
|
||||
// Set the callbacks
|
||||
void DigitalSignalProcessor::SetSampleFetcher(SampleFetcher fetcher) { sample_fetcher_ = fetcher; }
|
||||
|
||||
void DigitalSignalProcessor::SetSamplePusher(SamplePusher pusher) { sample_pusher_ = pusher; }
|
||||
|
||||
int16_t DigitalSignalProcessor::DecodeSample(uint8_t voice_num) {
|
||||
Voice const& voice = voices_[voice_num];
|
||||
uint16_t sample_address = voice.source_number;
|
||||
|
||||
// Use the callback to fetch the sample
|
||||
int16_t sample = static_cast<int16_t>(sample_fetcher_(sample_address) << 8);
|
||||
return sample;
|
||||
}
|
||||
|
||||
int16_t DigitalSignalProcessor::ProcessSample(uint8_t voice_num, int16_t sample) {
|
||||
Voice const& voice = voices_[voice_num];
|
||||
|
||||
// Adjust the pitch (for simplicity, we're just adjusting the sample value)
|
||||
sample += voice.pitch_low + (voice.pitch_high << 8);
|
||||
|
||||
// Apply volume (separate for left and right for stereo sound)
|
||||
int16_t left_sample = (sample * voice.vol_left) / 255;
|
||||
int16_t right_sample = (sample * voice.vol_right) / 255;
|
||||
|
||||
// Combine stereo samples into a single 16-bit value
|
||||
return (left_sample + right_sample) / 2;
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::MixSamples() {
|
||||
int16_t mixed_sample = 0;
|
||||
|
||||
for (uint8_t i = 0; i < kNumVoices; i++) {
|
||||
int16_t decoded_sample = DecodeSample(i);
|
||||
int16_t processed_sample = ProcessSample(i, decoded_sample);
|
||||
mixed_sample += processed_sample;
|
||||
}
|
||||
|
||||
// Clamp the mixed sample to 16-bit range
|
||||
if (mixed_sample > 32767) {
|
||||
mixed_sample = 32767;
|
||||
} else if (mixed_sample < -32768) {
|
||||
mixed_sample = -32768;
|
||||
}
|
||||
|
||||
// Use the callback to push the mixed sample
|
||||
sample_pusher_(mixed_sample);
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::UpdateEnvelope(uint8_t voice) {
|
||||
uint8_t adsr1 = ReadVoiceReg(voice, 0x05);
|
||||
uint8_t adsr2 = ReadVoiceReg(voice, 0x06);
|
||||
uint8_t gain = ReadVoiceReg(voice, 0x07);
|
||||
|
||||
uint8_t enableADSR = (adsr1 & 0x80) >> 7;
|
||||
|
||||
if (enableADSR) {
|
||||
// Handle ADSR envelope
|
||||
Voice& voice_obj = voices_[voice];
|
||||
switch (voice_obj.state) {
|
||||
case VoiceState::ATTACK:
|
||||
// Update amplitude based on attack rate
|
||||
voice_obj.current_amplitude += AttackRate(adsr1);
|
||||
if (voice_obj.current_amplitude >= ENVELOPE_MAX) {
|
||||
voice_obj.current_amplitude = ENVELOPE_MAX;
|
||||
voice_obj.state = VoiceState::DECAY;
|
||||
}
|
||||
break;
|
||||
case VoiceState::DECAY:
|
||||
// Update amplitude based on decay rate
|
||||
voice_obj.current_amplitude -= DecayRate(adsr2);
|
||||
if (voice_obj.current_amplitude <= voice_obj.decay_level) {
|
||||
voice_obj.current_amplitude = voice_obj.decay_level;
|
||||
voice_obj.state = VoiceState::SUSTAIN;
|
||||
}
|
||||
break;
|
||||
case VoiceState::SUSTAIN:
|
||||
// Keep amplitude at the calculated decay level
|
||||
voice_obj.current_amplitude = voice_obj.decay_level;
|
||||
break;
|
||||
case VoiceState::RELEASE:
|
||||
// Update amplitude based on release rate
|
||||
voice_obj.current_amplitude -= ReleaseRate(adsr2);
|
||||
if (voice_obj.current_amplitude <= 0) {
|
||||
voice_obj.current_amplitude = 0;
|
||||
voice_obj.state = VoiceState::OFF;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Handle Gain envelope
|
||||
// Extract mode from the gain byte
|
||||
uint8_t mode = (gain & 0xE0) >> 5;
|
||||
uint8_t rate = gain & 0x1F;
|
||||
|
||||
Voice& voice_obj = voices_[voice];
|
||||
|
||||
switch (mode) {
|
||||
case 0: // Direct Designation
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
voice_obj.current_amplitude =
|
||||
rate << 3; // Multiplying by 8 to scale to 0-255
|
||||
break;
|
||||
|
||||
case 6: // Increase Mode (Linear)
|
||||
voice_obj.current_amplitude += gainTimings[0][rate];
|
||||
if (voice_obj.current_amplitude > ENVELOPE_MAX) {
|
||||
voice_obj.current_amplitude = ENVELOPE_MAX;
|
||||
}
|
||||
break;
|
||||
|
||||
case 7: // Increase Mode (Bent Line)
|
||||
// Hypothetical behavior: Increase linearly at first, then increase
|
||||
// more slowly You'll likely need to adjust this based on your
|
||||
// specific requirements
|
||||
if (voice_obj.current_amplitude < (ENVELOPE_MAX / 2)) {
|
||||
voice_obj.current_amplitude += gainTimings[1][rate];
|
||||
} else {
|
||||
voice_obj.current_amplitude += gainTimings[1][rate] / 2;
|
||||
}
|
||||
if (voice_obj.current_amplitude > ENVELOPE_MAX) {
|
||||
voice_obj.current_amplitude = ENVELOPE_MAX;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4: // Decrease Mode (Linear)
|
||||
if (voice_obj.current_amplitude < gainTimings[2][rate]) {
|
||||
voice_obj.current_amplitude = 0;
|
||||
} else {
|
||||
voice_obj.current_amplitude -= gainTimings[2][rate];
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: // Decrease Mode (Exponential)
|
||||
voice_obj.current_amplitude -=
|
||||
(voice_obj.current_amplitude * gainTimings[3][rate]) / ENVELOPE_MAX;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Default behavior can be handled here if necessary
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::update_voice_state(uint8_t voice_num) {
|
||||
if (voice_num >= kNumVoices) return;
|
||||
|
||||
Voice& voice = voices_[voice_num];
|
||||
switch (voice.state) {
|
||||
case VoiceState::OFF:
|
||||
// Reset current amplitude
|
||||
voice.current_amplitude = 0;
|
||||
break;
|
||||
|
||||
case VoiceState::ATTACK:
|
||||
// Increase the current amplitude at a rate defined by the ATTACK
|
||||
// setting
|
||||
voice.current_amplitude += AttackRate(voice.adsr1);
|
||||
if (voice.current_amplitude >= ENVELOPE_MAX) {
|
||||
voice.current_amplitude = ENVELOPE_MAX;
|
||||
voice.state = VoiceState::DECAY;
|
||||
voice.decay_level = CalculateDecayLevel(voice.adsr2);
|
||||
}
|
||||
break;
|
||||
|
||||
case VoiceState::DECAY:
|
||||
// Decrease the current amplitude at a rate defined by the DECAY setting
|
||||
voice.current_amplitude -= DecayRate(voice.adsr2);
|
||||
if (voice.current_amplitude <= voice.decay_level) {
|
||||
voice.current_amplitude = voice.decay_level;
|
||||
voice.state = VoiceState::SUSTAIN;
|
||||
}
|
||||
break;
|
||||
|
||||
case VoiceState::SUSTAIN:
|
||||
// Keep the current amplitude at the decay level
|
||||
break;
|
||||
|
||||
case VoiceState::RELEASE:
|
||||
// Decrease the current amplitude at a rate defined by the RELEASE
|
||||
// setting
|
||||
voice.current_amplitude -= ReleaseRate(voice.adsr2);
|
||||
if (voice.current_amplitude == 0) {
|
||||
voice.state = VoiceState::OFF;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::process_envelope(uint8_t voice_num) {
|
||||
if (voice_num >= kNumVoices) return;
|
||||
|
||||
Voice& voice = voices_[voice_num];
|
||||
|
||||
// Update the voice state first (based on keys, etc.)
|
||||
update_voice_state(voice_num);
|
||||
|
||||
// Calculate the envelope value based on the current amplitude
|
||||
voice.envx = calculate_envelope_value(voice.current_amplitude);
|
||||
|
||||
// Apply the envelope value to the audio output
|
||||
apply_envelope_to_output(voice_num);
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
315
src/app/emu/audio/dsp.h
Normal file
315
src/app/emu/audio/dsp.h
Normal file
@@ -0,0 +1,315 @@
|
||||
#ifndef YAZE_APP_EMU_AUDIO_S_DSP_H
|
||||
#define YAZE_APP_EMU_AUDIO_S_DSP_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
using SampleFetcher = std::function<uint8_t(uint16_t)>;
|
||||
using SamplePusher = std::function<void(int16_t)>;
|
||||
|
||||
/**
|
||||
*
|
||||
* The S-DSP is a digital signal processor generating the sound data.
|
||||
*
|
||||
* A DSP register can be selected with $F2, after which it can be read or
|
||||
* written at $F3. Often it is useful to load the register address into A, and
|
||||
* the value to send in Y, so that MOV $F2, YA can be used to do both in one
|
||||
* 16-bit instruction.
|
||||
*
|
||||
* The DSP register address space only has 7 bits. The high bit of $F2, if set,
|
||||
* will make the selected register read-only via $F3.
|
||||
*
|
||||
* When initializing the DSP registers for the first time, take care not to
|
||||
* accidentally enable echo writeback via FLG, because it will immediately begin
|
||||
* overwriting values in RAM.
|
||||
*
|
||||
* Voices
|
||||
* There are 8 voices, numbered 0 to 7.
|
||||
* Each voice X has 10 registers in the range $X0-$X9.
|
||||
*
|
||||
* Name Address Bits Notes
|
||||
* VOL (L) $X0 SVVV VVVV Left channel volume, signed.
|
||||
* VOL (R) $X1 SVVV VVVV Right channel volume, signed.
|
||||
* P (L) $X2 LLLL LLLL Low 8 bits of sample pitch.
|
||||
* P (H) $X3 --HH HHHH High 6 bits of sample pitch.
|
||||
* SCRN $X4 SSSS SSSS Selects a sample source entry from the
|
||||
* directory ADSR (1) $X5 EDDD AAAA ADSR enable (E), decay rate (D),
|
||||
* attack rate (A).
|
||||
* ADSR (2) $X6 SSSR RRRR Sustain level (S), release rate (R).
|
||||
* GAIN $X7 0VVV VVVV 1MMV VVVV Mode (M), value (V).
|
||||
* ENVX $X8 0VVV VVVV Reads current 7-bit value of ADSR/GAIN
|
||||
* envelope.
|
||||
* OUTX $X9 SVVV VVVV Reads signed 8-bit value of current
|
||||
* sample wave multiplied by ENVX, before applying VOL.
|
||||
*
|
||||
*/
|
||||
|
||||
class DigitalSignalProcessor {
|
||||
private:
|
||||
static const size_t kNumVoices = 8;
|
||||
static const size_t kNumVoiceRegs = 10;
|
||||
static const size_t kNumGlobalRegs = 15;
|
||||
|
||||
enum class VoiceState { OFF, ATTACK, DECAY, SUSTAIN, RELEASE };
|
||||
|
||||
struct Voice {
|
||||
int8_t vol_left; // x0
|
||||
int8_t vol_right; // x1
|
||||
uint8_t pitch_low; // x2
|
||||
uint8_t pitch_high; // x3
|
||||
uint8_t source_number; // x4
|
||||
uint8_t adsr1; // x5
|
||||
uint8_t adsr2; // x6
|
||||
uint8_t gain; // x7
|
||||
uint8_t envx; // x8 (read-only)
|
||||
int8_t outx; // x9 (read-only)
|
||||
|
||||
VoiceState state = VoiceState::OFF;
|
||||
uint16_t current_amplitude = 0; // Current amplitude value used for ADSR
|
||||
uint16_t decay_level; // Calculated decay level based on ADSR settings
|
||||
};
|
||||
Voice voices_[8];
|
||||
|
||||
// Global DSP registers
|
||||
uint8_t mvol_left; // 0C
|
||||
uint8_t mvol_right; // 0D
|
||||
uint8_t evol_left; // 0E
|
||||
uint8_t evol_right; // 0F
|
||||
uint8_t kon; // 10
|
||||
uint8_t koff; // 11
|
||||
uint8_t flags; // 12
|
||||
uint8_t endx; // 13 (read-only)
|
||||
|
||||
// Global registers
|
||||
std::vector<uint8_t> globalRegs = std::vector<uint8_t>(kNumGlobalRegs, 0x00);
|
||||
|
||||
static const uint16_t ENVELOPE_MAX = 2047; // $7FF
|
||||
|
||||
// Attack times in ms
|
||||
const std::vector<uint32_t> attackTimes = {
|
||||
4100, 2600, 1500, 1000, 640, 380, 260, 160, 96, 64, 40, 24, 16, 10, 6, 0};
|
||||
|
||||
// Decay times in ms
|
||||
const std::vector<uint32_t> decayTimes = {1200, 740, 440, 290,
|
||||
180, 110, 74, 37};
|
||||
|
||||
// Release times in ms
|
||||
const std::vector<uint32_t> releaseTimes = {
|
||||
// "Infinite" is represented by a large value, e.g., UINT32_MAX
|
||||
UINT32_MAX, 38000, 28000, 24000, 19000, 14000, 12000, 9400,
|
||||
7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500,
|
||||
1200, 880, 740, 590, 440, 370, 290, 220,
|
||||
180, 150, 110, 92, 74, 55, 37, 18};
|
||||
|
||||
// Gain timings for decrease linear, decrease exponential, etc.
|
||||
// Organized by mode: [Linear Increase, Bentline Increase, Linear Decrease,
|
||||
// Exponential Decrease]
|
||||
const std::vector<std::vector<uint32_t>> gainTimings = {
|
||||
{UINT32_MAX, 3100, 2600, 2000, 1500, 1300, 1000, 770, 640, 510, 380,
|
||||
320, 260, 190, 160, 130, 96, 80, 64, 48, 40, 32,
|
||||
24, 20, 16, 12, 10, 8, 6, 4, 2},
|
||||
{UINT32_MAX, 5400, 4600, 3500, 2600, 2300, 1800, 1300, 1100, 900,
|
||||
670, 560, 450, 340, 280, 220, 170, 140, 110, 84,
|
||||
70, 56, 42, 35, 28, 21, 18, 14, 11, 7,
|
||||
/*3.5=*/3},
|
||||
// Repeating the Linear Increase timings for Linear Decrease, since they
|
||||
// are the same.
|
||||
{UINT32_MAX, 3100, 2600, 2000, 1500, 1300, 1000, 770, 640, 510, 380,
|
||||
320, 260, 190, 160, 130, 96, 80, 64, 48, 40, 32,
|
||||
24, 20, 16, 12, 10, 8, 6, 4, 2},
|
||||
{UINT32_MAX, 38000, 28000, 24000, 19000, 14000, 12000, 9400,
|
||||
7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500,
|
||||
1200, 880, 740, 590, 440, 370, 290, 220,
|
||||
180, 150, 110, 92, 55, 37, 18}};
|
||||
|
||||
// DSP Period Table
|
||||
const std::vector<std::vector<uint16_t>> DigitalSignalProcessorPeriodTable = {
|
||||
// ... Your DSP period table here ...
|
||||
};
|
||||
|
||||
// DSP Period Offset
|
||||
const std::vector<uint16_t> DigitalSignalProcessorPeriodOffset = {
|
||||
// ... Your DSP period offsets here ...
|
||||
};
|
||||
|
||||
uint8_t calculate_envelope_value(uint16_t amplitude) const {
|
||||
// Convert the 16-bit amplitude to an 8-bit envelope value
|
||||
return amplitude >> 8;
|
||||
}
|
||||
|
||||
void apply_envelope_to_output(uint8_t voice_num) {
|
||||
Voice& voice = voices_[voice_num];
|
||||
|
||||
// Scale the OUTX by the envelope value
|
||||
// This might be a linear scaling, or more complex operations can be used
|
||||
voice.outx = (voice.outx * voice.envx) / 255;
|
||||
}
|
||||
|
||||
SampleFetcher sample_fetcher_;
|
||||
SamplePusher sample_pusher_;
|
||||
|
||||
public:
|
||||
DigitalSignalProcessor() = default;
|
||||
|
||||
void Reset();
|
||||
|
||||
void SetSampleFetcher(std::function<uint8_t(uint16_t)> fetcher);
|
||||
void SetSamplePusher(std::function<void(int16_t)> pusher);
|
||||
|
||||
// Read a byte from a voice register
|
||||
uint8_t ReadVoiceReg(uint8_t voice, uint8_t reg) const;
|
||||
|
||||
// Write a byte to a voice register
|
||||
void WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value);
|
||||
|
||||
// Read a byte from a global register
|
||||
uint8_t ReadGlobalReg(uint8_t reg) const {
|
||||
return globalRegs[reg % kNumGlobalRegs];
|
||||
}
|
||||
|
||||
// Write a byte to a global register
|
||||
void WriteGlobalReg(uint8_t reg, uint8_t value) {
|
||||
globalRegs[reg % kNumGlobalRegs] = value;
|
||||
}
|
||||
|
||||
int16_t DecodeSample(uint8_t voice_num);
|
||||
int16_t ProcessSample(uint8_t voice_num, int16_t sample);
|
||||
void MixSamples();
|
||||
|
||||
// Trigger a voice to start playing
|
||||
void trigger_voice(uint8_t voice_num) {
|
||||
if (voice_num >= kNumVoices) return;
|
||||
|
||||
Voice& voice = voices_[voice_num];
|
||||
voice.state = VoiceState::ATTACK;
|
||||
// Initialize other state management variables if needed
|
||||
}
|
||||
|
||||
// Release a voice (e.g., note release in ADSR)
|
||||
void release_voice(uint8_t voice_num) {
|
||||
if (voice_num >= kNumVoices) return;
|
||||
|
||||
Voice& voice = voices_[voice_num];
|
||||
if (voice.state != VoiceState::OFF) {
|
||||
voice.state = VoiceState::RELEASE;
|
||||
}
|
||||
// Update other state management variables if needed
|
||||
}
|
||||
|
||||
// Calculate envelope for a given voice
|
||||
void UpdateEnvelope(uint8_t voice);
|
||||
|
||||
// Voice-related functions (implementations)
|
||||
void set_voice_volume(int voice_num, int8_t left, int8_t right) {
|
||||
voices_[voice_num].vol_left = left;
|
||||
voices_[voice_num].vol_right = right;
|
||||
}
|
||||
|
||||
void set_voice_pitch(int voice_num, uint16_t pitch) {
|
||||
voices_[voice_num].pitch_low = pitch & 0xFF;
|
||||
voices_[voice_num].pitch_high = (pitch >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
void set_voice_source_number(int voice_num, uint8_t srcn) {
|
||||
voices_[voice_num].source_number = srcn;
|
||||
}
|
||||
|
||||
void set_voice_adsr(int voice_num, uint8_t adsr1, uint8_t adsr2) {
|
||||
voices_[voice_num].adsr1 = adsr1;
|
||||
voices_[voice_num].adsr2 = adsr2;
|
||||
}
|
||||
|
||||
void set_voice_gain(int voice_num, uint8_t gain) {
|
||||
voices_[voice_num].gain = gain;
|
||||
}
|
||||
|
||||
uint8_t read_voice_envx(int voice_num) { return voices_[voice_num].envx; }
|
||||
|
||||
int8_t read_voice_outx(int voice_num) { return voices_[voice_num].outx; }
|
||||
|
||||
// Global DSP functions
|
||||
void set_master_volume(int8_t left, int8_t right) {
|
||||
mvol_left = left;
|
||||
mvol_right = right;
|
||||
}
|
||||
|
||||
void set_echo_volume(int8_t left, int8_t right) {
|
||||
evol_left = left;
|
||||
evol_right = right;
|
||||
}
|
||||
|
||||
void update_voice_state(uint8_t voice_num);
|
||||
|
||||
// Override the key_on and key_off methods to utilize the new state management
|
||||
void key_on(uint8_t value) {
|
||||
for (uint8_t i = 0; i < kNumVoices; i++) {
|
||||
if (value & (1 << i)) {
|
||||
trigger_voice(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void key_off(uint8_t value) {
|
||||
for (uint8_t i = 0; i < kNumVoices; i++) {
|
||||
if (value & (1 << i)) {
|
||||
release_voice(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_flags(uint8_t value) {
|
||||
flags = value;
|
||||
// More logic may be needed here depending on flag behaviors
|
||||
}
|
||||
|
||||
uint8_t read_endx() { return endx; }
|
||||
|
||||
uint16_t AttackRate(uint8_t adsr1) {
|
||||
// Convert the ATTACK portion of adsr1 into a rate of amplitude change
|
||||
// You might need to adjust this logic based on the exact ADSR
|
||||
// implementation details
|
||||
return (adsr1 & 0x0F) * 16; // Just a hypothetical conversion
|
||||
}
|
||||
|
||||
uint16_t DecayRate(uint8_t adsr2) {
|
||||
// Convert the DECAY portion of adsr2 into a rate of amplitude change
|
||||
return ((adsr2 >> 4) & 0x07) * 8; // Hypothetical conversion
|
||||
}
|
||||
|
||||
uint16_t ReleaseRate(uint8_t adsr2) {
|
||||
// Convert the RELEASE portion of adsr2 into a rate of amplitude change
|
||||
return (adsr2 & 0x0F) * 16; // Hypothetical conversion
|
||||
}
|
||||
|
||||
uint16_t CalculateDecayLevel(uint8_t adsr2) {
|
||||
// Calculate the decay level based on the SUSTAIN portion of adsr2
|
||||
// This is the level the amplitude will decay to before entering the SUSTAIN
|
||||
// phase Again, adjust based on your implementation details
|
||||
return ((adsr2 >> 4) & 0x07) * 256; // Hypothetical conversion
|
||||
}
|
||||
|
||||
// Envelope processing for all voices
|
||||
// Goes through each voice and processes its envelope.
|
||||
void process_envelopes() {
|
||||
for (size_t i = 0; i < kNumVoices; ++i) {
|
||||
process_envelope(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Envelope processing for a specific voice
|
||||
// For a given voice, update its state (ADSR), calculate the envelope value,
|
||||
// and apply the envelope to the audio output.
|
||||
void process_envelope(uint8_t voice_num);
|
||||
};
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_AUDIO_S_DSP_H
|
||||
94
src/app/emu/audio/internal/addressing.cc
Normal file
94
src/app/emu/audio/internal/addressing.cc
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "app/emu/audio/spc700.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
// Immediate
|
||||
uint8_t Spc700::imm() {
|
||||
PC++;
|
||||
return read(PC);
|
||||
}
|
||||
|
||||
// Direct page
|
||||
uint8_t Spc700::dp() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
return read((PSW.P << 8) + offset);
|
||||
}
|
||||
|
||||
uint8_t& Spc700::mutable_dp() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
return mutable_read((PSW.P << 8) + offset);
|
||||
}
|
||||
|
||||
uint8_t Spc700::get_dp_addr() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
return (PSW.P << 8) + offset;
|
||||
}
|
||||
|
||||
// Direct page indexed by X
|
||||
uint8_t Spc700::dp_plus_x() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
return read((PSW.P << 8) + offset + X);
|
||||
}
|
||||
|
||||
// Direct page indexed by Y
|
||||
uint8_t Spc700::dp_plus_y() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
return read((PSW.P << 8) + offset + Y);
|
||||
}
|
||||
|
||||
// Indexed indirect (add index before 16-bit lookup).
|
||||
uint16_t Spc700::dp_plus_x_indirect() {
|
||||
PC++;
|
||||
uint16_t addr = read_16(PC + X);
|
||||
return addr;
|
||||
}
|
||||
|
||||
// Indirect indexed (add index after 16-bit lookup).
|
||||
uint16_t Spc700::dp_indirect_plus_y() {
|
||||
PC++;
|
||||
uint16_t offset = read_16(PC);
|
||||
return offset + Y;
|
||||
}
|
||||
|
||||
uint16_t Spc700::abs() {
|
||||
PC++;
|
||||
uint16_t addr = read(PC) | (read(PC) << 8);
|
||||
return addr;
|
||||
}
|
||||
|
||||
int8_t Spc700::rel() {
|
||||
PC++;
|
||||
return static_cast<int8_t>(read(PC));
|
||||
}
|
||||
|
||||
uint8_t Spc700::i() { return read((PSW.P << 8) + X); }
|
||||
|
||||
uint8_t Spc700::i_postinc() {
|
||||
uint8_t value = read((PSW.P << 8) + X);
|
||||
X++;
|
||||
return value;
|
||||
}
|
||||
|
||||
uint16_t Spc700::addr_plus_i() {
|
||||
PC++;
|
||||
uint16_t addr = read(PC) | (read(PC) << 8);
|
||||
return read(addr) + X;
|
||||
}
|
||||
|
||||
uint16_t Spc700::addr_plus_i_indexed() {
|
||||
PC++;
|
||||
uint16_t addr = read(PC) | (read(PC) << 8);
|
||||
addr += X;
|
||||
return read(addr) | (read(addr + 1) << 8);
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
361
src/app/emu/audio/internal/instructions.cc
Normal file
361
src/app/emu/audio/internal/instructions.cc
Normal file
@@ -0,0 +1,361 @@
|
||||
#include "app/emu/audio/spc700.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void Spc700::MOV(uint8_t& dest, uint8_t operand) {
|
||||
dest = operand;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::MOV_ADDR(uint16_t address, uint8_t operand) {
|
||||
write(address, operand);
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::ADC(uint8_t& dest, uint8_t operand) {
|
||||
uint16_t result = dest + operand + PSW.C;
|
||||
PSW.V = ((A ^ result) & (operand ^ result) & 0x80);
|
||||
PSW.C = (result > 0xFF);
|
||||
PSW.Z = ((result & 0xFF) == 0);
|
||||
PSW.N = (result & 0x80);
|
||||
PSW.H = ((A ^ operand ^ result) & 0x10);
|
||||
dest = result & 0xFF;
|
||||
}
|
||||
|
||||
void Spc700::SBC(uint8_t& dest, uint8_t operand) {
|
||||
uint16_t result = dest - operand - (1 - PSW.C);
|
||||
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x80);
|
||||
PSW.C = (result < 0x100);
|
||||
PSW.Z = ((result & 0xFF) == 0);
|
||||
PSW.N = (result & 0x80);
|
||||
PSW.H = ((dest ^ operand ^ result) & 0x10);
|
||||
dest = result & 0xFF;
|
||||
}
|
||||
|
||||
void Spc700::CMP(uint8_t& dest, uint8_t operand) {
|
||||
uint16_t result = dest - operand;
|
||||
PSW.C = (result < 0x100);
|
||||
PSW.Z = ((result & 0xFF) == 0);
|
||||
PSW.N = (result & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::AND(uint8_t& dest, uint8_t operand) {
|
||||
dest &= operand;
|
||||
PSW.Z = (dest == 0);
|
||||
PSW.N = (dest & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::OR(uint8_t& dest, uint8_t operand) {
|
||||
dest |= operand;
|
||||
PSW.Z = (dest == 0);
|
||||
PSW.N = (dest & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::EOR(uint8_t& dest, uint8_t operand) {
|
||||
dest ^= operand;
|
||||
PSW.Z = (dest == 0);
|
||||
PSW.N = (dest & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::ASL(uint8_t operand) {
|
||||
PSW.C = (operand & 0x80);
|
||||
operand <<= 1;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
// A = value;
|
||||
}
|
||||
|
||||
void Spc700::LSR(uint8_t& operand) {
|
||||
PSW.C = (operand & 0x01);
|
||||
operand >>= 1;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::ROL(uint8_t operand, bool isImmediate) {
|
||||
uint8_t value = isImmediate ? imm() : operand;
|
||||
uint8_t carry = PSW.C;
|
||||
PSW.C = (value & 0x80);
|
||||
value <<= 1;
|
||||
value |= carry;
|
||||
PSW.Z = (value == 0);
|
||||
PSW.N = (value & 0x80);
|
||||
// operand = value;
|
||||
}
|
||||
|
||||
void Spc700::XCN(uint8_t operand, bool isImmediate) {
|
||||
uint8_t value = isImmediate ? imm() : operand;
|
||||
value = ((value & 0xF0) >> 4) | ((value & 0x0F) << 4);
|
||||
PSW.Z = (value == 0);
|
||||
PSW.N = (value & 0x80);
|
||||
// operand = value;
|
||||
}
|
||||
|
||||
void Spc700::INC(uint8_t& operand) {
|
||||
operand++;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::DEC(uint8_t& operand) {
|
||||
operand--;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::MOVW(uint16_t& dest, uint16_t operand) {
|
||||
dest = operand;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x8000);
|
||||
}
|
||||
|
||||
void Spc700::INCW(uint16_t& operand) {
|
||||
operand++;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x8000);
|
||||
}
|
||||
|
||||
void Spc700::DECW(uint16_t& operand) {
|
||||
operand--;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x8000);
|
||||
}
|
||||
|
||||
void Spc700::ADDW(uint16_t& dest, uint16_t operand) {
|
||||
uint32_t result = dest + operand;
|
||||
PSW.C = (result > 0xFFFF);
|
||||
PSW.Z = ((result & 0xFFFF) == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
PSW.V = ((dest ^ result) & (operand ^ result) & 0x8000);
|
||||
dest = result & 0xFFFF;
|
||||
}
|
||||
|
||||
void Spc700::SUBW(uint16_t& dest, uint16_t operand) {
|
||||
uint32_t result = dest - operand;
|
||||
PSW.C = (result < 0x10000);
|
||||
PSW.Z = ((result & 0xFFFF) == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x8000);
|
||||
dest = result & 0xFFFF;
|
||||
}
|
||||
|
||||
void Spc700::CMPW(uint16_t operand) {
|
||||
uint32_t result = YA - operand;
|
||||
PSW.C = (result < 0x10000);
|
||||
PSW.Z = ((result & 0xFFFF) == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
}
|
||||
|
||||
void Spc700::MUL(uint8_t operand) {
|
||||
uint16_t result = A * operand;
|
||||
YA = result;
|
||||
PSW.Z = (result == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
}
|
||||
|
||||
void Spc700::DIV(uint8_t operand) {
|
||||
if (operand == 0) {
|
||||
// Handle divide by zero error
|
||||
return;
|
||||
}
|
||||
uint8_t quotient = A / operand;
|
||||
uint8_t remainder = A % operand;
|
||||
A = quotient;
|
||||
Y = remainder;
|
||||
PSW.Z = (quotient == 0);
|
||||
PSW.N = (quotient & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::BRA(int8_t offset) { PC += offset; }
|
||||
|
||||
void Spc700::BEQ(int8_t offset) {
|
||||
if (PSW.Z) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BNE(int8_t offset) {
|
||||
if (!PSW.Z) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BCS(int8_t offset) {
|
||||
if (PSW.C) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BCC(int8_t offset) {
|
||||
if (!PSW.C) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BVS(int8_t offset) {
|
||||
if (PSW.V) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BVC(int8_t offset) {
|
||||
if (!PSW.V) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BMI(int8_t offset) {
|
||||
if (PSW.N) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BPL(int8_t offset) {
|
||||
if (!PSW.N) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BBS(uint8_t bit, uint8_t operand) {
|
||||
if (operand & (1 << bit)) {
|
||||
PC += rel();
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BBC(uint8_t bit, uint8_t operand) {
|
||||
if (!(operand & (1 << bit))) {
|
||||
PC += rel();
|
||||
}
|
||||
}
|
||||
|
||||
// CBNE DBNZ
|
||||
// JMP
|
||||
void Spc700::JMP(uint16_t address) { PC = address; }
|
||||
|
||||
void Spc700::CALL(uint16_t address) {
|
||||
uint16_t return_address = PC + 2;
|
||||
write(SP, return_address & 0xFF);
|
||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||
SP -= 2;
|
||||
PC = address;
|
||||
}
|
||||
|
||||
void Spc700::PCALL(uint8_t offset) {
|
||||
uint16_t return_address = PC + 2;
|
||||
write(SP, return_address & 0xFF);
|
||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||
SP -= 2;
|
||||
PC += offset;
|
||||
}
|
||||
|
||||
void Spc700::TCALL(uint8_t offset) {
|
||||
uint16_t return_address = PC + 2;
|
||||
write(SP, return_address & 0xFF);
|
||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||
SP -= 2;
|
||||
PC = 0xFFDE + offset;
|
||||
}
|
||||
|
||||
void Spc700::BRK() {
|
||||
uint16_t return_address = PC + 2;
|
||||
write(SP, return_address & 0xFF);
|
||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||
SP -= 2;
|
||||
PC = 0xFFDE;
|
||||
}
|
||||
|
||||
void Spc700::RET() {
|
||||
uint16_t return_address = read(SP) | (read(SP + 1) << 8);
|
||||
SP += 2;
|
||||
PC = return_address;
|
||||
}
|
||||
|
||||
void Spc700::RETI() {
|
||||
uint16_t return_address = read(SP) | (read(SP + 1) << 8);
|
||||
SP += 2;
|
||||
PC = return_address;
|
||||
PSW.I = 1;
|
||||
}
|
||||
|
||||
void Spc700::PUSH(uint8_t operand) {
|
||||
write(SP, operand);
|
||||
SP--;
|
||||
}
|
||||
|
||||
void Spc700::POP(uint8_t& operand) {
|
||||
SP++;
|
||||
operand = read(SP);
|
||||
}
|
||||
|
||||
void Spc700::SET1(uint8_t bit, uint8_t& operand) { operand |= (1 << bit); }
|
||||
|
||||
void Spc700::CLR1(uint8_t bit, uint8_t& operand) { operand &= ~(1 << bit); }
|
||||
|
||||
void Spc700::TSET1(uint8_t bit, uint8_t& operand) {
|
||||
PSW.C = (operand & (1 << bit));
|
||||
operand |= (1 << bit);
|
||||
}
|
||||
|
||||
void Spc700::TCLR1(uint8_t bit, uint8_t& operand) {
|
||||
PSW.C = (operand & (1 << bit));
|
||||
operand &= ~(1 << bit);
|
||||
}
|
||||
|
||||
void Spc700::AND1(uint8_t bit, uint8_t& operand) {
|
||||
operand &= (1 << bit);
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::OR1(uint8_t bit, uint8_t& operand) {
|
||||
operand |= (1 << bit);
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::EOR1(uint8_t bit, uint8_t& operand) {
|
||||
operand ^= (1 << bit);
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::NOT1(uint8_t bit, uint8_t& operand) {
|
||||
operand ^= (1 << bit);
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::MOV1(uint8_t bit, uint8_t& operand) {
|
||||
PSW.C = (operand & (1 << bit));
|
||||
operand |= (1 << bit);
|
||||
}
|
||||
|
||||
void Spc700::CLRC() { PSW.C = 0; }
|
||||
|
||||
void Spc700::SETC() { PSW.C = 1; }
|
||||
|
||||
void Spc700::NOTC() { PSW.C = !PSW.C; }
|
||||
|
||||
void Spc700::CLRV() { PSW.V = 0; }
|
||||
|
||||
void Spc700::CLRP() { PSW.P = 0; }
|
||||
|
||||
void Spc700::SETP() { PSW.P = 1; }
|
||||
|
||||
void Spc700::EI() { PSW.I = 1; }
|
||||
|
||||
void Spc700::DI() { PSW.I = 0; }
|
||||
|
||||
void Spc700::NOP() { PC++; }
|
||||
|
||||
void Spc700::SLEEP() {}
|
||||
|
||||
void Spc700::STOP() {}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
263
src/app/emu/audio/internal/opcodes.h
Normal file
263
src/app/emu/audio/internal/opcodes.h
Normal file
@@ -0,0 +1,263 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
const std::unordered_map<uint8_t, std::string> spc_opcode_map = {
|
||||
{0x00, "NOP"},
|
||||
{0x01, "TCALL0"},
|
||||
{0x02, "SET1 direct.0"},
|
||||
{0x03, "BBS direct.0,rel"},
|
||||
{0x04, "OR A,direct"},
|
||||
{0x05, "OR A,abs"},
|
||||
{0x06, "OR A,(X)"},
|
||||
{0x07, "OR A,(direct+X)"},
|
||||
{0x08, "OR A,#imm"},
|
||||
{0x09, "OR direct,imm"},
|
||||
{0x0A, "OR1 C,membit"},
|
||||
{0x0B, "ASL direct"},
|
||||
{0x0C, "ASL abs"},
|
||||
{0x0D, "PUSH PSW"},
|
||||
{0x0E, "TSET1 abs"},
|
||||
{0x0F, "BRK"},
|
||||
{0x10, "BPL rel"},
|
||||
{0x11, "TCALL1"},
|
||||
{0x12, "CLR1 direct.0"},
|
||||
{0x13, "BBC direct.0,rel"},
|
||||
{0x14, "OR A,direct+X"},
|
||||
{0x15, "OR A,abs+X"},
|
||||
{0x16, "OR A,abs+Y"},
|
||||
{0x17, "OR A,(direct)+Y"},
|
||||
{0x18, "OR direct,direct"},
|
||||
{0x19, "OR (X),(Y)"},
|
||||
{0x1A, "DECW direct"},
|
||||
{0x1B, "ASL direct+X"},
|
||||
{0x1C, "ASL A"},
|
||||
{0x1D, "DEC X"},
|
||||
{0x1E, "CMP X,abs"},
|
||||
{0x1F, "JMP (abs+X)"},
|
||||
{0x20, "CLRP"},
|
||||
{0x21, "TCALL2"},
|
||||
{0x22, "SET1 direct.1"},
|
||||
{0x23, "BBS direct.1,rel"},
|
||||
{0x24, "AND A,direct"},
|
||||
{0x25, "AND A,abs"},
|
||||
{0x26, "AND A,(X)"},
|
||||
{0x27, "AND A,(direct+X)"},
|
||||
{0x28, "AND A,#imm"},
|
||||
{0x29, "AND direct,imm"},
|
||||
{0x2A, "OR1 C,/membit"},
|
||||
{0x2B, "ROL direct"},
|
||||
{0x2C, "ROL abs"},
|
||||
{0x2D, "PUSH A"},
|
||||
{0x2E, "CBNE direct,rel"},
|
||||
{0x2F, "BRA rel"},
|
||||
{0x30, "BMI rel"},
|
||||
{0x31, "TCALL3"},
|
||||
{0x32, "CLR1 direct.1"},
|
||||
{0x33, "BBC direct.1,rel"},
|
||||
{0x34, "AND A,direct+X"},
|
||||
{0x35, "AND A,abs+X"},
|
||||
{0x36, "AND A,abs+Y"},
|
||||
{0x37, "AND A,(direct)+Y"},
|
||||
{0x38, "AND direct,direct"},
|
||||
{0x39, "AND (X),(Y)"},
|
||||
{0x3A, "INCW direct"},
|
||||
{0x3B, "ROL direct+X"},
|
||||
{0x3C, "ROL A"},
|
||||
{0x3D, "INC X"},
|
||||
{0x3E, "CMP X,direct"},
|
||||
{0x3F, "CALL abs"},
|
||||
{0x40, "SETP"},
|
||||
{0x41, "TCALL4"},
|
||||
{0x42, "SET1 direct.2"},
|
||||
{0x43, "BBS direct.2,rel"},
|
||||
{0x44, "EOR A,direct"},
|
||||
{0x45, "EOR A,abs"},
|
||||
{0x46, "EOR A,(X)"},
|
||||
{0x47, "EOR A,(direct+X)"},
|
||||
{0x48, "EOR A,#imm"},
|
||||
{0x49, "EOR direct,imm"},
|
||||
{0x4A, "AND1 C,membit"},
|
||||
{0x4B, "LSR direct"},
|
||||
{0x4C, "LSR abs"},
|
||||
{0x4D, "PUSH X"},
|
||||
{0x4E, "TCLR1 abs"},
|
||||
{0x4F, "PCALL addr"},
|
||||
{0x50, "BVC rel"},
|
||||
{0x51, "TCALL5"},
|
||||
{0x52, "CLR1 direct.2"},
|
||||
{0x53, "BBC direct.2,rel"},
|
||||
{0x54, "EOR A,direct+X"},
|
||||
{0x55, "EOR A,abs+X"},
|
||||
{0x56, "EOR A,abs+Y"},
|
||||
{0x57, "EOR A,(direct)+Y"},
|
||||
{0x58, "EOR direct,direct"},
|
||||
{0x59, "EOR (X),(Y)"},
|
||||
{0x5A, "CMPW YA,direct"},
|
||||
{0x5B, "LSR direct+X"},
|
||||
{0x5C, "LSR A"},
|
||||
{0x5D, "MOV X,A"},
|
||||
{0x5E, "CMP Y,abs"},
|
||||
{0x5F, "JMP abs"},
|
||||
{0x60, "CLRC"},
|
||||
{0x61, "TCALL6"},
|
||||
{0x62, "SET1 direct.3"},
|
||||
{0x63, "BBS direct.3,rel"},
|
||||
{0x64, "CMP A,direct"},
|
||||
{0x65, "CMP A,abs"},
|
||||
{0x66, "CMP A,(X)"},
|
||||
{0x67, "CMP A,(direct+X)"},
|
||||
{0x68, "CMP A,#imm"},
|
||||
{0x69, "CMP direct,imm"},
|
||||
{0x6A, "AND1 C,/membit"},
|
||||
{0x6B, "ROR direct"},
|
||||
{0x6C, "ROR abs"},
|
||||
{0x6D, "PUSH Y"},
|
||||
{0x6E, "DBNZ direct,rel"},
|
||||
{0x6F, "RET"},
|
||||
{0x70, "BVS rel"},
|
||||
{0x71, "TCALL7"},
|
||||
{0x72, "CLR1 direct.3"},
|
||||
{0x73, "BBC direct.3,rel"},
|
||||
{0x74, "CMP A,direct+X"},
|
||||
{0x75, "CMP A,abs+X"},
|
||||
{0x76, "CMP A,abs+Y"},
|
||||
{0x77, "CMP A,(direct)+Y"},
|
||||
{0x78, "CMP direct,direct"},
|
||||
{0x79, "CMP (X),(Y)"},
|
||||
{0x7A, "ADDW YA,direct"},
|
||||
{0x7B, "ROR direct+X"},
|
||||
{0x7C, "ROR A"},
|
||||
{0x7D, "MOV A,X"},
|
||||
{0x7E, "CMP Y,direct"},
|
||||
{0x7F, "RETI"},
|
||||
{0x80, "SETC"},
|
||||
{0x81, "TCALL8"},
|
||||
{0x82, "SET1 direct.4"},
|
||||
{0x83, "BBS direct.4,rel"},
|
||||
{0x84, "ADC A,direct"},
|
||||
{0x85, "ADC A,abs"},
|
||||
{0x86, "ADC A,(X)"},
|
||||
{0x87, "ADC A,(direct+X)"},
|
||||
{0x88, "ADC A,#imm"},
|
||||
{0x89, "ADC direct,imm"},
|
||||
{0x8A, "EOR1 C,membit"},
|
||||
{0x8B, "DEC direct"},
|
||||
{0x8C, "DEC abs"},
|
||||
{0x8D, "MOV Y,#imm"},
|
||||
{0x8E, "POP PSW"},
|
||||
{0x8F, "MOV direct,#imm"},
|
||||
{0x90, "BCC rel"},
|
||||
{0x91, "TCALL9"},
|
||||
{0x92, "CLR1 direct.4"},
|
||||
{0x93, "BBC direct.4,rel"},
|
||||
{0x94, "ADC A,direct+X"},
|
||||
{0x95, "ADC A,abs+X"},
|
||||
{0x96, "ADC A,abs+Y"},
|
||||
{0x97, "ADC A,(direct)+Y"},
|
||||
{0x98, "ADC direct,direct"},
|
||||
{0x99, "ADC (X),(Y)"},
|
||||
{0x9A, "SUBW YA,direct"},
|
||||
{0x9B, "DEC direct+X"},
|
||||
{0x9C, "DEC A"},
|
||||
{0x9D, "MOV X,SP"},
|
||||
{0x9E, "DIV YA,X"},
|
||||
{0x9F, "XCN A"},
|
||||
{0xA0, "EI"},
|
||||
{0xA1, "TCALL10"},
|
||||
{0xA2, "SET1 direct.5"},
|
||||
{0xA3, "BBS direct.5,rel"},
|
||||
{0xA4, "SBC A,direct"},
|
||||
{0xA5, "SBC A,abs"},
|
||||
{0xA6, "SBC A,(X)"},
|
||||
{0xA7, "SBC A,(direct+X)"},
|
||||
{0xA8, "SBC A,#imm"},
|
||||
{0xA9, "SBC direct,imm"},
|
||||
{0xAA, "MOV1 C,membit"},
|
||||
{0xAB, "INC direct"},
|
||||
{0xAC, "INC abs"},
|
||||
{0xAD, "CMP Y,#imm"},
|
||||
{0xAE, "POP A"},
|
||||
{0xAF, "MOV (X)+,A"},
|
||||
{0xB0, "BCS rel"},
|
||||
{0xB1, "TCALL11"},
|
||||
{0xB2, "CLR1 direct.5"},
|
||||
{0xB3, "BBC direct.5,rel"},
|
||||
{0xB4, "SBC A,direct+X"},
|
||||
{0xB5, "SBC A,abs+X"},
|
||||
{0xB6, "SBC A,abs+Y"},
|
||||
{0xB7, "SBC A,(direct)+Y"},
|
||||
{0xB8, "SBC direct,direct"},
|
||||
{0xB9, "SBC (X),(Y)"},
|
||||
{0xBA, "MOVW YA,direct"},
|
||||
{0xBB, "INC direct+X"},
|
||||
{0xBC, "INC A"},
|
||||
{0xBD, "MOV SP,X"},
|
||||
{0xBE, "DAS"},
|
||||
{0xBF, "MOV A,(X)+"},
|
||||
{0xC0, "DI"},
|
||||
{0xC1, "TCALL12"},
|
||||
{0xC2, "SET1 direct.6"},
|
||||
{0xC3, "BBS direct.6,rel"},
|
||||
{0xC4, "MOV direct,A"},
|
||||
{0xC5, "MOV abs,A"},
|
||||
{0xC6, "MOV (X),A"},
|
||||
{0xC7, "MOV (direct+X),A"},
|
||||
{0xC8, "CMP X,#imm"},
|
||||
{0xC9, "MOV abs,X"},
|
||||
{0xCA, "MOV1 membit,C"},
|
||||
{0xCB, "MOV direct,Y"},
|
||||
{0xCC, "MOV abs,Y"},
|
||||
{0xCD, "MOV X,#imm"},
|
||||
{0xCE, "POP X"},
|
||||
{0xCF, "MUL YA"},
|
||||
{0xD0, "BNE rel"},
|
||||
{0xD1, "TCALL13"},
|
||||
{0xD2, "CLR1 direct.6"},
|
||||
{0xD3, "BBC direct.6,rel"},
|
||||
{0xD4, "MOV direct+X,A"},
|
||||
{0xD5, "MOV abs+X,A"},
|
||||
{0xD6, "MOV abs+Y,A"},
|
||||
{0xD7, "MOV (direct)+Y,A"},
|
||||
{0xD8, "MOV direct,X"},
|
||||
{0xD9, "MOV direct+Y,X"},
|
||||
{0xDA, "MOVW direct,YA"},
|
||||
{0xDB, "MOV direct+X,Y"},
|
||||
{0xDC, "DEC Y"},
|
||||
{0xDD, "MOV A,Y"},
|
||||
{0xDE, "CBNE direct+X,rel"},
|
||||
{0xDF, "DAA"},
|
||||
{0xE0, "CLRV"},
|
||||
{0xE1, "TCALL14"},
|
||||
{0xE2, "SET1 direct.7"},
|
||||
{0xE3, "BBS direct.7,rel"},
|
||||
{0xE4, "MOV A,direct"},
|
||||
{0xE5, "MOV A,abs"},
|
||||
{0xE6, "MOV A,(X)"},
|
||||
{0xE7, "MOV A,(direct+X)"},
|
||||
{0xE8, "MOV A,#imm"},
|
||||
{0xE9, "MOV X,abs"},
|
||||
{0xEA, "NOT1 membit"},
|
||||
{0xEB, "MOV Y,direct"},
|
||||
{0xEC, "MOV Y,abs"},
|
||||
{0xED, "NOTC"},
|
||||
{0xEE, "POP Y"},
|
||||
{0xEF, "SLEEP"},
|
||||
{0xF0, "BEQ rel"},
|
||||
{0xF1, "TCALL15"},
|
||||
{0xF2, "CLR1 direct.7"},
|
||||
{0xF3, "BBC direct.7,rel"},
|
||||
{0xF4, "MOV A,direct+X"},
|
||||
{0xF5, "MOV A,abs+X"},
|
||||
{0xF6, "MOV A,abs+Y"},
|
||||
{0xF7, "MOV A,(direct)+Y"},
|
||||
{0xF8, "MOV X,direct"},
|
||||
{0xF9, "MOV X,direct+Y"},
|
||||
{0xFA, "MOV direct,S"},
|
||||
{0xFB, "MOV Y,direct+X"},
|
||||
{0xFC, "INC Y"},
|
||||
{0xFD, "MOV Y,A"},
|
||||
{0xFE, "DBNZ Y,rel"},
|
||||
{0xFF, "STOP"}};
|
||||
957
src/app/emu/audio/spc700.cc
Normal file
957
src/app/emu/audio/spc700.cc
Normal file
@@ -0,0 +1,957 @@
|
||||
#include "app/emu/audio/spc700.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/audio/internal/opcodes.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void Spc700::Reset() {
|
||||
PC = 0;
|
||||
A = 0;
|
||||
X = 0;
|
||||
Y = 0;
|
||||
SP = 0xFF;
|
||||
PSW = ByteToFlags(0x00);
|
||||
aram_.reset();
|
||||
}
|
||||
|
||||
void Spc700::BootIplRom() {
|
||||
PC = 0xFFC0;
|
||||
A = 0;
|
||||
X = 0;
|
||||
Y = 0;
|
||||
int i = 0;
|
||||
while (PC != 0xFFC0 + 0x3F) {
|
||||
uint8_t opcode = read(PC);
|
||||
ExecuteInstructions(opcode);
|
||||
PC++;
|
||||
i++;
|
||||
|
||||
if (i > 1000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::ExecuteInstructions(uint8_t opcode) {
|
||||
uint16_t initialPC = PC;
|
||||
switch (opcode) {
|
||||
// 8-bit Move Memory to Register
|
||||
case 0xE8: // MOV A, #imm
|
||||
{
|
||||
MOV(A, imm());
|
||||
break;
|
||||
}
|
||||
case 0xE6: // MOV A, (X)
|
||||
{
|
||||
MOV(A, X);
|
||||
break;
|
||||
}
|
||||
case 0xBF: // MOV A, (X)+
|
||||
{
|
||||
MOV(A, X);
|
||||
X++;
|
||||
break;
|
||||
}
|
||||
case 0xE4: // MOV A, dp
|
||||
{
|
||||
MOV(A, dp());
|
||||
break;
|
||||
}
|
||||
case 0xF4: // MOV A, dp+X
|
||||
{
|
||||
MOV(A, dp_plus_x());
|
||||
break;
|
||||
}
|
||||
case 0xE5: // MOV A, !abs
|
||||
{
|
||||
MOV(A, read(abs()));
|
||||
break;
|
||||
}
|
||||
case 0xF5: // MOV A, !abs+X
|
||||
{
|
||||
MOV(A, abs() + X);
|
||||
break;
|
||||
}
|
||||
case 0xF6: // MOV A, !abs+Y
|
||||
{
|
||||
MOV(A, abs() + Y);
|
||||
break;
|
||||
}
|
||||
case 0xE7: // MOV A, [dp+X]
|
||||
{
|
||||
MOV(A, read(dp_plus_x_indirect()));
|
||||
break;
|
||||
}
|
||||
case 0xF7: // MOV A, [dp]+Y
|
||||
{
|
||||
MOV(A, read(dp_indirect_plus_y()));
|
||||
break;
|
||||
}
|
||||
case 0xCD: // MOV X, #imm
|
||||
{
|
||||
MOV(X, imm());
|
||||
break;
|
||||
}
|
||||
case 0xF8: // MOV X, dp
|
||||
{
|
||||
MOV(X, dp());
|
||||
break;
|
||||
}
|
||||
case 0xF9: // MOV X, dp+Y
|
||||
{
|
||||
MOV(X, dp_plus_y());
|
||||
break;
|
||||
}
|
||||
case 0xE9: // MOV X, !abs
|
||||
{
|
||||
MOV(X, abs());
|
||||
break;
|
||||
}
|
||||
case 0x8D: // MOV Y, #imm
|
||||
{
|
||||
MOV(Y, imm());
|
||||
break;
|
||||
}
|
||||
case 0xEB: // MOV Y, dp
|
||||
{
|
||||
MOV(Y, dp());
|
||||
break;
|
||||
}
|
||||
case 0xFB: // MOV Y, dp+X
|
||||
{
|
||||
MOV(Y, dp_plus_x());
|
||||
break;
|
||||
}
|
||||
case 0xEC: // MOV Y, !abs
|
||||
{
|
||||
MOV(Y, abs());
|
||||
break;
|
||||
}
|
||||
|
||||
// 8-bit move register to memory
|
||||
case 0xC6: // MOV (X), A
|
||||
{
|
||||
MOV_ADDR(X, A);
|
||||
break;
|
||||
}
|
||||
case 0xAF: // MOV (X)+, A
|
||||
{
|
||||
MOV_ADDR(X, A);
|
||||
break;
|
||||
}
|
||||
case 0xC4: // MOV dp, A
|
||||
{
|
||||
MOV_ADDR(get_dp_addr(), A);
|
||||
break;
|
||||
}
|
||||
case 0xD4: // MOV dp+X, A
|
||||
{
|
||||
MOV_ADDR(get_dp_addr() + X, A);
|
||||
break;
|
||||
}
|
||||
case 0xC5: // MOV !abs, A
|
||||
{
|
||||
MOV_ADDR(abs(), A);
|
||||
break;
|
||||
}
|
||||
case 0xD5: // MOV !abs+X, A
|
||||
{
|
||||
MOV_ADDR(abs() + X, A);
|
||||
break;
|
||||
}
|
||||
case 0xD6: // MOV !abs+Y, A
|
||||
{
|
||||
MOV_ADDR(abs() + Y, A);
|
||||
break;
|
||||
}
|
||||
case 0xC7: // MOV [dp+X], A
|
||||
{
|
||||
MOV_ADDR(dp_plus_x_indirect(), A);
|
||||
break;
|
||||
}
|
||||
case 0xD7: // MOV [dp]+Y, A
|
||||
{
|
||||
MOV_ADDR(dp_indirect_plus_y(), A);
|
||||
break;
|
||||
}
|
||||
case 0xD8: // MOV dp, X
|
||||
{
|
||||
MOV_ADDR(get_dp_addr(), X);
|
||||
break;
|
||||
}
|
||||
case 0xD9: // MOV dp+Y, X
|
||||
{
|
||||
MOV_ADDR(get_dp_addr() + Y, X);
|
||||
break;
|
||||
}
|
||||
case 0xC9: // MOV !abs, X
|
||||
{
|
||||
MOV_ADDR(abs(), X);
|
||||
break;
|
||||
}
|
||||
case 0xCB: // MOV dp, Y
|
||||
{
|
||||
MOV_ADDR(get_dp_addr(), Y);
|
||||
break;
|
||||
}
|
||||
case 0xDB: // MOV dp+X, Y
|
||||
{
|
||||
MOV_ADDR(get_dp_addr() + X, Y);
|
||||
break;
|
||||
}
|
||||
case 0xCC: // MOV !abs, Y
|
||||
{
|
||||
MOV_ADDR(abs(), Y);
|
||||
break;
|
||||
}
|
||||
|
||||
// . 8-bit move register to register / special direct page moves
|
||||
case 0x7D: // MOV A, X
|
||||
{
|
||||
MOV(A, X);
|
||||
break;
|
||||
}
|
||||
case 0xDD: // MOV A, Y
|
||||
{
|
||||
MOV(A, Y);
|
||||
break;
|
||||
}
|
||||
case 0x5D: // MOV X, A
|
||||
{
|
||||
MOV(X, A);
|
||||
break;
|
||||
}
|
||||
case 0xFD: // MOV Y, A
|
||||
{
|
||||
MOV(Y, A);
|
||||
break;
|
||||
}
|
||||
case 0x9D: // MOV X, SP
|
||||
{
|
||||
MOV(X, SP);
|
||||
break;
|
||||
}
|
||||
case 0xBD: // MOV SP, X
|
||||
{
|
||||
MOV(SP, X);
|
||||
break;
|
||||
}
|
||||
case 0xFA: // MOV dp, dp
|
||||
{
|
||||
MOV_ADDR(get_dp_addr(), dp());
|
||||
break;
|
||||
}
|
||||
case 0x8F: // MOV dp, #imm
|
||||
{
|
||||
MOV_ADDR(get_dp_addr(), imm());
|
||||
break;
|
||||
}
|
||||
|
||||
// . 8-bit arithmetic
|
||||
case 0x88: // ADC A, #imm
|
||||
{
|
||||
ADC(A, imm());
|
||||
break;
|
||||
}
|
||||
case 0x86: // ADC A, (X)
|
||||
{
|
||||
ADC(A, X);
|
||||
break;
|
||||
}
|
||||
case 0x84: // ADC A, dp
|
||||
{
|
||||
ADC(A, dp());
|
||||
break;
|
||||
}
|
||||
case 0x94: // ADC A, dp+X
|
||||
{
|
||||
ADC(A, dp_plus_x());
|
||||
break;
|
||||
}
|
||||
case 0x85: // ADC A, !abs
|
||||
{
|
||||
ADC(A, abs());
|
||||
break;
|
||||
}
|
||||
case 0x95: // ADC A, !abs+X
|
||||
{
|
||||
ADC(A, abs() + X);
|
||||
break;
|
||||
}
|
||||
case 0x96: // ADC A, !abs+Y
|
||||
{
|
||||
ADC(A, abs() + Y);
|
||||
break;
|
||||
}
|
||||
case 0x87: // ADC A, [dp+X]
|
||||
{
|
||||
ADC(A, dp_plus_x_indirect());
|
||||
break;
|
||||
}
|
||||
case 0x97: // ADC A, [dp]+Y
|
||||
{
|
||||
ADC(A, dp_indirect_plus_y());
|
||||
break;
|
||||
}
|
||||
case 0x99: // ADC (X), (Y)
|
||||
break;
|
||||
case 0x89: // ADC dp, dp
|
||||
{
|
||||
ADC(mutable_dp(), dp());
|
||||
break;
|
||||
}
|
||||
case 0x98: // ADC dp, #imm
|
||||
{
|
||||
ADC(mutable_dp(), imm());
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xA8: // SBC A, #imm
|
||||
{
|
||||
SBC(A, imm());
|
||||
break;
|
||||
}
|
||||
case 0xA6: // SBC A, (X)
|
||||
{
|
||||
SBC(A, mutable_read(X));
|
||||
break;
|
||||
}
|
||||
case 0xA4: // SBC A, dp
|
||||
{
|
||||
SBC(A, dp());
|
||||
break;
|
||||
}
|
||||
case 0xB4: // SBC A, dp+X
|
||||
{
|
||||
SBC(A, dp_plus_x());
|
||||
break;
|
||||
}
|
||||
case 0xA5: // SBC A, !abs
|
||||
{
|
||||
SBC(A, abs());
|
||||
break;
|
||||
}
|
||||
case 0xB5: // SBC A, !abs+X
|
||||
{
|
||||
SBC(A, abs() + X);
|
||||
break;
|
||||
}
|
||||
case 0xB6: // SBC A, !abs+Y
|
||||
{
|
||||
SBC(A, abs() + Y);
|
||||
break;
|
||||
}
|
||||
case 0xA7: // SBC A, [dp+X]
|
||||
{
|
||||
SBC(A, dp_plus_x_indirect());
|
||||
break;
|
||||
}
|
||||
case 0xB7: // SBC A, [dp]+Y
|
||||
{
|
||||
SBC(A, dp_indirect_plus_y());
|
||||
break;
|
||||
}
|
||||
case 0xB9: // SBC (X), (Y)
|
||||
{
|
||||
SBC(mutable_read(X), mutable_read(Y));
|
||||
break;
|
||||
}
|
||||
case 0xA9: // SBC dp, dp
|
||||
{
|
||||
SBC(mutable_dp(), dp());
|
||||
break;
|
||||
}
|
||||
case 0xB8: // SBC dp, #imm
|
||||
{
|
||||
SBC(mutable_dp(), imm());
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x68: // CMP A, #imm
|
||||
{
|
||||
CMP(A, imm());
|
||||
break;
|
||||
}
|
||||
case 0x66: // CMP A, (X)
|
||||
{
|
||||
CMP(A, read(X));
|
||||
break;
|
||||
}
|
||||
case 0x64: // CMP A, dp
|
||||
{
|
||||
CMP(A, dp());
|
||||
break;
|
||||
}
|
||||
case 0x74: // CMP A, dp+X
|
||||
{
|
||||
CMP(A, dp_plus_x());
|
||||
break;
|
||||
}
|
||||
case 0x65: // CMP A, !abs
|
||||
{
|
||||
CMP(A, abs());
|
||||
break;
|
||||
}
|
||||
case 0x75: // CMP A, !abs+X
|
||||
{
|
||||
CMP(A, abs() + X);
|
||||
break;
|
||||
}
|
||||
case 0x76: // CMP A, !abs+Y
|
||||
{
|
||||
CMP(A, abs() + Y);
|
||||
break;
|
||||
}
|
||||
case 0x67: // CMP A, [dp+X]
|
||||
break;
|
||||
case 0x77: // CMP A, [dp]+Y
|
||||
break;
|
||||
case 0x79: // CMP (X), (Y)
|
||||
break;
|
||||
case 0x69: // CMP dp, dp
|
||||
{
|
||||
CMP(mutable_dp(), dp());
|
||||
break;
|
||||
}
|
||||
case 0x78: // CMP dp, #imm
|
||||
{
|
||||
CMP(mutable_dp(), imm());
|
||||
break;
|
||||
}
|
||||
case 0xC8: // CMP X, #imm
|
||||
{
|
||||
CMP(X, imm());
|
||||
break;
|
||||
}
|
||||
case 0x3E: // CMP X, dp
|
||||
{
|
||||
CMP(X, dp());
|
||||
break;
|
||||
}
|
||||
case 0x1E: // CMP X, !abs
|
||||
{
|
||||
CMP(X, abs());
|
||||
break;
|
||||
}
|
||||
case 0xAD: // CMP Y, #imm
|
||||
{
|
||||
CMP(Y, imm());
|
||||
break;
|
||||
}
|
||||
case 0x7E: // CMP Y, dp
|
||||
{
|
||||
CMP(Y, dp());
|
||||
break;
|
||||
}
|
||||
case 0x5E: // CMP Y, !abs
|
||||
{
|
||||
CMP(Y, abs());
|
||||
break;
|
||||
}
|
||||
|
||||
// 8-bit boolean logic
|
||||
case 0x28: // AND A, #imm
|
||||
{
|
||||
AND(A, imm());
|
||||
break;
|
||||
}
|
||||
case 0x26: // AND A, (X)
|
||||
{
|
||||
AND(A, mutable_read(X));
|
||||
break;
|
||||
}
|
||||
case 0x24: // AND A, dp
|
||||
{
|
||||
AND(A, dp());
|
||||
break;
|
||||
}
|
||||
case 0x34: // AND A, dp+X
|
||||
{
|
||||
AND(A, dp_plus_x());
|
||||
break;
|
||||
}
|
||||
case 0x25: // AND A, !abs
|
||||
{
|
||||
AND(A, abs());
|
||||
break;
|
||||
}
|
||||
case 0x35: // AND A, !abs+X
|
||||
{
|
||||
AND(A, abs() + X);
|
||||
break;
|
||||
}
|
||||
case 0x36: // AND A, !abs+Y
|
||||
{
|
||||
AND(A, abs() + Y);
|
||||
break;
|
||||
}
|
||||
case 0x27: // AND A, [dp+X]
|
||||
{
|
||||
AND(A, dp_plus_x_indirect());
|
||||
break;
|
||||
}
|
||||
case 0x37: // AND A, [dp]+Y
|
||||
{
|
||||
AND(A, dp_indirect_plus_y());
|
||||
break;
|
||||
}
|
||||
case 0x39: // AND (X), (Y)
|
||||
{
|
||||
AND(mutable_read(X), mutable_read(Y));
|
||||
break;
|
||||
}
|
||||
case 0x29: // AND dp, dp
|
||||
{
|
||||
AND(mutable_dp(), dp());
|
||||
break;
|
||||
}
|
||||
case 0x38: // AND dp, #imm
|
||||
{
|
||||
AND(mutable_dp(), imm());
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x08: // OR A, #imm
|
||||
{
|
||||
OR(A, imm());
|
||||
break;
|
||||
}
|
||||
case 0x06: // OR A, (X)
|
||||
{
|
||||
OR(A, mutable_read(X));
|
||||
break;
|
||||
}
|
||||
case 0x04: // OR A, dp
|
||||
{
|
||||
OR(A, dp());
|
||||
break;
|
||||
}
|
||||
case 0x14: // OR A, dp+X
|
||||
{
|
||||
OR(A, dp_plus_x());
|
||||
break;
|
||||
}
|
||||
case 0x05: // OR A, !abs
|
||||
{
|
||||
OR(A, abs());
|
||||
break;
|
||||
}
|
||||
case 0x15: // OR A, !abs+X
|
||||
{
|
||||
OR(A, abs() + X);
|
||||
break;
|
||||
}
|
||||
case 0x16: // OR A, !abs+Y
|
||||
{
|
||||
OR(A, abs() + Y);
|
||||
break;
|
||||
}
|
||||
case 0x07: // OR A, [dp+X]
|
||||
{
|
||||
OR(A, dp_plus_x_indirect());
|
||||
break;
|
||||
}
|
||||
case 0x17: // OR A, [dp]+Y
|
||||
{
|
||||
OR(A, dp_indirect_plus_y());
|
||||
break;
|
||||
}
|
||||
case 0x19: // OR (X), (Y)
|
||||
OR(mutable_read(X), mutable_read(Y));
|
||||
break;
|
||||
case 0x09: // OR dp, dp
|
||||
OR(mutable_dp(), dp());
|
||||
break;
|
||||
case 0x18: // OR dp, #imm
|
||||
OR(mutable_dp(), imm());
|
||||
break;
|
||||
case 0x48: // EOR A, #imm
|
||||
EOR(A, imm());
|
||||
break;
|
||||
case 0x46: // EOR A, (X)
|
||||
EOR(A, mutable_read(X));
|
||||
break;
|
||||
case 0x44: // EOR A, dp
|
||||
EOR(A, dp());
|
||||
break;
|
||||
case 0x54: // EOR A, dp+X
|
||||
EOR(A, dp_plus_x());
|
||||
break;
|
||||
case 0x45: // EOR A, !abs
|
||||
EOR(A, abs());
|
||||
break;
|
||||
case 0x55: // EOR A, !abs+X
|
||||
EOR(A, abs() + X);
|
||||
break;
|
||||
case 0x56: // EOR A, !abs+Y
|
||||
EOR(A, abs() + Y);
|
||||
break;
|
||||
case 0x47: // EOR A, [dp+X]
|
||||
EOR(A, dp_plus_x_indirect());
|
||||
break;
|
||||
case 0x57: // EOR A, [dp]+Y
|
||||
EOR(A, dp_indirect_plus_y());
|
||||
break;
|
||||
case 0x59: // EOR (X), (Y)
|
||||
EOR(mutable_read(X), mutable_read(Y));
|
||||
break;
|
||||
case 0x49: // EOR dp, dp
|
||||
EOR(mutable_dp(), dp());
|
||||
break;
|
||||
case 0x58: // EOR dp, #imm
|
||||
EOR(mutable_dp(), imm());
|
||||
break;
|
||||
|
||||
// . 8-bit increment / decrement
|
||||
|
||||
case 0xBC: // INC A
|
||||
INC(A);
|
||||
break;
|
||||
case 0xAB: // INC dp
|
||||
INC(mutable_dp());
|
||||
break;
|
||||
case 0xBB: // INC dp+X
|
||||
INC(mutable_read((PSW.P << 8) + dp_plus_x()));
|
||||
break;
|
||||
case 0xAC: // INC !abs
|
||||
INC(mutable_read(abs()));
|
||||
break;
|
||||
case 0x3D: // INC X
|
||||
INC(X);
|
||||
break;
|
||||
case 0xFC: // INC Y
|
||||
INC(Y);
|
||||
break;
|
||||
case 0x9C: // DEC A
|
||||
DEC(A);
|
||||
break;
|
||||
case 0x8B: // DEC dp
|
||||
DEC(mutable_dp());
|
||||
break;
|
||||
case 0x9B: // DEC dp+X
|
||||
DEC(mutable_read((PSW.P << 8) + dp_plus_x()));
|
||||
break;
|
||||
case 0x8C: // DEC !abs
|
||||
DEC(mutable_read(abs()));
|
||||
break;
|
||||
case 0x1D: // DEC X
|
||||
DEC(X);
|
||||
break;
|
||||
case 0xDC: // DEC Y
|
||||
DEC(Y);
|
||||
break;
|
||||
|
||||
// 8-bit shift / rotation
|
||||
|
||||
case 0x1C: // ASL A
|
||||
ASL(A);
|
||||
break;
|
||||
case 0x0B: // ASL dp
|
||||
ASL(dp());
|
||||
break;
|
||||
case 0x1B: // ASL dp+X
|
||||
ASL(dp_plus_x());
|
||||
break;
|
||||
case 0x0C: // ASL !abs
|
||||
ASL(abs());
|
||||
break;
|
||||
case 0x5C: // LSR A
|
||||
LSR(A);
|
||||
break;
|
||||
case 0x4B: // LSR dp
|
||||
LSR(mutable_dp());
|
||||
break;
|
||||
case 0x5B: // LSR dp+X
|
||||
LSR(mutable_read((PSW.P << 8) + dp_plus_x()));
|
||||
break;
|
||||
case 0x4C: // LSR !abs
|
||||
LSR(mutable_read(abs()));
|
||||
break;
|
||||
|
||||
case 0x3C: // ROL A
|
||||
ROL(A);
|
||||
break;
|
||||
case 0x2B: // ROL dp
|
||||
ROL(dp());
|
||||
break;
|
||||
case 0x3B: // ROL dp+X
|
||||
ROL(dp_plus_x());
|
||||
break;
|
||||
case 0x2C: // ROL !abs
|
||||
ROL(abs());
|
||||
break;
|
||||
case 0x7C: // ROR A
|
||||
// ROR(A);
|
||||
break;
|
||||
case 0x6B: // ROR dp
|
||||
// ROR(dp());
|
||||
break;
|
||||
case 0x7B: // ROR dp+X
|
||||
// ROR(dp_plus_x());
|
||||
break;
|
||||
case 0x6C: // ROR !abs
|
||||
// ROR(abs());
|
||||
break;
|
||||
case 0x9F: // XCN A Exchange nibbles of A
|
||||
XCN(A);
|
||||
break;
|
||||
|
||||
// . 16-bit operations
|
||||
|
||||
case 0xBA: // MOVW YA, dp
|
||||
MOVW(YA, dp());
|
||||
break;
|
||||
case 0xDA: // MOVW dp, YA
|
||||
MOVW(mutable_read_16(dp()), YA);
|
||||
break;
|
||||
case 0x3A: // INCW dp
|
||||
INCW(mutable_read_16(dp()));
|
||||
break;
|
||||
case 0x1A: // DECW dp
|
||||
DECW(mutable_read_16(dp()));
|
||||
break;
|
||||
case 0x7A: // ADDW YA, dp
|
||||
ADDW(YA, dp());
|
||||
break;
|
||||
case 0x9A: // SUBW YA, dp
|
||||
SUBW(YA, dp());
|
||||
break;
|
||||
case 0x5A: // CMPW YA, dp
|
||||
// CMPW(YA, dp());
|
||||
break;
|
||||
case 0xCF: // MUL YA
|
||||
MUL(YA);
|
||||
break;
|
||||
case 0x9E: // DIV YA, X
|
||||
// DIV(YA, X);
|
||||
break;
|
||||
|
||||
// . decimal adjust
|
||||
|
||||
case 0xDF: // DAA A
|
||||
break;
|
||||
case 0xBE: // DAS A
|
||||
break;
|
||||
|
||||
// . branching
|
||||
|
||||
case 0x2F: // BRA rel
|
||||
BRA(rel());
|
||||
break;
|
||||
case 0xF0: // BEQ rel
|
||||
BEQ(rel());
|
||||
break;
|
||||
case 0xD0: // BNE rel
|
||||
BNE(rel());
|
||||
break;
|
||||
case 0xB0: // BCS rel
|
||||
BCS(rel());
|
||||
break;
|
||||
case 0x90: // BCC rel
|
||||
BCC(rel());
|
||||
break;
|
||||
case 0x70: // BVS rel
|
||||
BVS(rel());
|
||||
break;
|
||||
case 0x50: // BVC rel
|
||||
BVC(rel());
|
||||
break;
|
||||
case 0x30: // BMI rel
|
||||
BMI(rel());
|
||||
break;
|
||||
case 0x10: // BPL rel
|
||||
BPL(rel());
|
||||
break;
|
||||
case 0x2E: // CBNE dp, rel
|
||||
break;
|
||||
case 0xDE: // CBNE dp+X, rel
|
||||
break;
|
||||
case 0x6E: // DBNZ dp, rel
|
||||
break;
|
||||
case 0xFE: // DBNZ Y, rel
|
||||
break;
|
||||
case 0x5F: // JMP !abs
|
||||
JMP(abs());
|
||||
break;
|
||||
case 0x1F: // JMP [!abs+X]
|
||||
// JMP_INDIRECT(abs() + X);
|
||||
break;
|
||||
|
||||
// . subroutines
|
||||
case 0x3F: // CALL !abs
|
||||
{
|
||||
CALL(abs());
|
||||
break;
|
||||
}
|
||||
case 0x4F: // PCALL up
|
||||
{
|
||||
PCALL(imm());
|
||||
break;
|
||||
}
|
||||
case 0x6F: // RET
|
||||
{
|
||||
RET();
|
||||
break;
|
||||
}
|
||||
case 0x7F: // RETI
|
||||
{
|
||||
RETI();
|
||||
break;
|
||||
}
|
||||
|
||||
// . stack
|
||||
case 0x2D: // PUSH A
|
||||
{
|
||||
PUSH(A);
|
||||
break;
|
||||
}
|
||||
case 0x4D: // PUSH X
|
||||
{
|
||||
PUSH(X);
|
||||
break;
|
||||
}
|
||||
case 0x6D: // PUSH Y
|
||||
{
|
||||
PUSH(Y);
|
||||
break;
|
||||
}
|
||||
case 0x0D: // PUSH PSW
|
||||
{
|
||||
PUSH(FlagsToByte(PSW));
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xAE: // POP A
|
||||
{
|
||||
POP(A);
|
||||
break;
|
||||
}
|
||||
case 0xCE: // POP X
|
||||
{
|
||||
POP(X);
|
||||
break;
|
||||
}
|
||||
case 0xEE: // POP Y
|
||||
{
|
||||
POP(Y);
|
||||
break;
|
||||
}
|
||||
case 0x8E: // POP PSW
|
||||
{
|
||||
uint8_t flags_byte;
|
||||
POP(flags_byte);
|
||||
PSW = ByteToFlags(flags_byte);
|
||||
break;
|
||||
}
|
||||
|
||||
// . memory bit operations
|
||||
|
||||
case 0xEA: // NOT1 abs, bit
|
||||
// NOT1(abs(), bit());
|
||||
break;
|
||||
case 0xAA: // MOV1 C, abs, bit
|
||||
break;
|
||||
case 0xCA: // MOV1 abs, bit, C
|
||||
break;
|
||||
case 0x4A: // AND1 C, abs, bit
|
||||
break;
|
||||
case 0x6A: // AND1 C, /abs, bit
|
||||
break;
|
||||
case 0x0A: // OR1 C, abs, bit
|
||||
break;
|
||||
case 0x2A: // OR1 C, /abs, bit
|
||||
break;
|
||||
case 0x8A: // EOR1 C, abs, bit
|
||||
break;
|
||||
|
||||
// . status flags
|
||||
|
||||
case 0x60: // CLRC
|
||||
CLRC();
|
||||
break;
|
||||
case 0x80: // SETC
|
||||
SETC();
|
||||
break;
|
||||
case 0xED: // NOTC
|
||||
NOTC();
|
||||
break;
|
||||
case 0xE0: // CLRV
|
||||
CLRV();
|
||||
break;
|
||||
case 0x20: // CLRP
|
||||
CLRP();
|
||||
break;
|
||||
case 0x40: // SETP
|
||||
SETP();
|
||||
break;
|
||||
case 0xA0: // EI
|
||||
EI();
|
||||
break;
|
||||
case 0xC0: // DI
|
||||
DI();
|
||||
break;
|
||||
|
||||
// .no-operation and haltF
|
||||
case 0x00: // NOP
|
||||
{
|
||||
NOP();
|
||||
break;
|
||||
}
|
||||
case 0xEF: // SLEEP
|
||||
{
|
||||
SLEEP();
|
||||
break;
|
||||
}
|
||||
case 0x0F: // STOP
|
||||
{
|
||||
STOP();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
std::cout << "Unknown opcode: " << std::hex << opcode << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
LogInstruction(initialPC, opcode);
|
||||
}
|
||||
|
||||
void Spc700::LogInstruction(uint16_t initial_pc, uint8_t opcode) {
|
||||
std::string mnemonic = spc_opcode_map.at(opcode);
|
||||
|
||||
std::stringstream log_entry_stream;
|
||||
log_entry_stream << "\033[1;36m$" << std::hex << std::setw(4)
|
||||
<< std::setfill('0') << initial_pc << "\033[0m";
|
||||
log_entry_stream << " \033[1;32m" << std::hex << std::setw(2)
|
||||
<< std::setfill('0') << static_cast<int>(opcode) << "\033[0m"
|
||||
<< " \033[1;35m" << std::setw(18) << std::left
|
||||
<< std::setfill(' ') << mnemonic << "\033[0m";
|
||||
|
||||
log_entry_stream << " \033[1;33mA: " << std::hex << std::setw(2)
|
||||
<< std::setfill('0') << std::right << static_cast<int>(A)
|
||||
<< "\033[0m";
|
||||
log_entry_stream << " \033[1;33mX: " << std::hex << std::setw(2)
|
||||
<< std::setfill('0') << std::right << static_cast<int>(X)
|
||||
<< "\033[0m";
|
||||
log_entry_stream << " \033[1;33mY: " << std::hex << std::setw(2)
|
||||
<< std::setfill('0') << std::right << static_cast<int>(Y)
|
||||
<< "\033[0m";
|
||||
std::string log_entry = log_entry_stream.str();
|
||||
|
||||
std::cerr << log_entry << std::endl;
|
||||
|
||||
// Append the log entry to the log
|
||||
log_.push_back(log_entry);
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
267
src/app/emu/audio/spc700.h
Normal file
267
src/app/emu/audio/spc700.h
Normal file
@@ -0,0 +1,267 @@
|
||||
#ifndef YAZE_APP_EMU_SPC700_H
|
||||
#define YAZE_APP_EMU_SPC700_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class AudioRam {
|
||||
public:
|
||||
virtual ~AudioRam() = default;
|
||||
virtual void reset() = 0;
|
||||
virtual uint8_t read(uint16_t address) const = 0;
|
||||
virtual uint8_t& mutable_read(uint16_t address) = 0;
|
||||
virtual void write(uint16_t address, uint8_t value) = 0;
|
||||
};
|
||||
|
||||
class AudioRamImpl : public AudioRam {
|
||||
static const int ARAM_SIZE = 0x10000;
|
||||
std::vector<uint8_t> ram = std::vector<uint8_t>(ARAM_SIZE, 0);
|
||||
|
||||
public:
|
||||
AudioRamImpl() = default;
|
||||
void reset() override { ram = std::vector<uint8_t>(ARAM_SIZE, 0); }
|
||||
|
||||
uint8_t read(uint16_t address) const override {
|
||||
return ram[address % ARAM_SIZE];
|
||||
}
|
||||
|
||||
uint8_t& mutable_read(uint16_t address) override {
|
||||
return ram.at(address % ARAM_SIZE);
|
||||
}
|
||||
|
||||
void write(uint16_t address, uint8_t value) override {
|
||||
ram[address % ARAM_SIZE] = value;
|
||||
}
|
||||
};
|
||||
|
||||
class Spc700 {
|
||||
private:
|
||||
AudioRam& aram_;
|
||||
std::vector<std::string> log_;
|
||||
|
||||
const uint8_t ipl_rom_[64]{
|
||||
0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0, 0xFC, 0x8F, 0xAA,
|
||||
0xF4, 0x8F, 0xBB, 0xF5, 0x78, 0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19,
|
||||
0xEB, 0xF4, 0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5, 0xCB,
|
||||
0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB, 0x01, 0x10, 0xEF, 0x7E,
|
||||
0xF4, 0x10, 0xEB, 0xBA, 0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4,
|
||||
0xDD, 0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF};
|
||||
|
||||
public:
|
||||
explicit Spc700(AudioRam& aram) : aram_(aram) {}
|
||||
|
||||
// Registers
|
||||
uint8_t A = 0x00; // 8-bit accumulator
|
||||
uint8_t X = 0x00; // 8-bit index
|
||||
uint8_t Y = 0x00; // 8-bit index
|
||||
uint16_t YA = 0x00; // 16-bit pair of A (lsb) and Y (msb)
|
||||
uint16_t PC = 0xFFC0; // program counter
|
||||
uint8_t SP = 0x00; // stack pointer
|
||||
|
||||
struct Flags {
|
||||
uint8_t N : 1; // Negative flag
|
||||
uint8_t V : 1; // Overflow flag
|
||||
uint8_t P : 1; // Direct page flag
|
||||
uint8_t B : 1; // Break flag
|
||||
uint8_t H : 1; // Half-carry flag
|
||||
uint8_t I : 1; // Interrupt enable
|
||||
uint8_t Z : 1; // Zero flag
|
||||
uint8_t C : 1; // Carry flag
|
||||
};
|
||||
Flags PSW; // Processor status word
|
||||
|
||||
uint8_t FlagsToByte(Flags flags) {
|
||||
return (flags.N << 7) | (flags.V << 6) | (flags.P << 5) | (flags.B << 4) |
|
||||
(flags.H << 3) | (flags.I << 2) | (flags.Z << 1) | (flags.C);
|
||||
}
|
||||
|
||||
Flags ByteToFlags(uint8_t byte) {
|
||||
Flags flags;
|
||||
flags.N = (byte & 0x80) >> 7;
|
||||
flags.V = (byte & 0x40) >> 6;
|
||||
flags.P = (byte & 0x20) >> 5;
|
||||
flags.B = (byte & 0x10) >> 4;
|
||||
flags.H = (byte & 0x08) >> 3;
|
||||
flags.I = (byte & 0x04) >> 2;
|
||||
flags.Z = (byte & 0x02) >> 1;
|
||||
flags.C = (byte & 0x01);
|
||||
return flags;
|
||||
}
|
||||
|
||||
void Reset();
|
||||
|
||||
void BootIplRom();
|
||||
|
||||
void ExecuteInstructions(uint8_t opcode);
|
||||
void LogInstruction(uint16_t initial_pc, uint8_t opcode);
|
||||
|
||||
// Read a byte from the memory-mapped registers
|
||||
uint8_t read(uint16_t address) {
|
||||
if (address < 0xFFC0) {
|
||||
return aram_.read(address);
|
||||
} else {
|
||||
// Check if register is set to unmap the IPL ROM
|
||||
if (read(0xF1) & 0x80) {
|
||||
return aram_.read(address);
|
||||
}
|
||||
return ipl_rom_[address - 0xFFC0];
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t& mutable_read(uint16_t address) {
|
||||
if (address < 0xFFC0) {
|
||||
return aram_.mutable_read(address);
|
||||
} else {
|
||||
// NOTE: Mutable access to IPL ROM is not allowed
|
||||
return aram_.mutable_read(address);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t& mutable_read_16(uint16_t address) {
|
||||
if (address < 0xFFC0) {
|
||||
return *reinterpret_cast<uint16_t*>(&aram_.mutable_read(address));
|
||||
} else {
|
||||
// NOTE: Mutable access to IPL ROM is not allowed
|
||||
return *reinterpret_cast<uint16_t*>(&aram_.mutable_read(address));
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t read_16(uint16_t address) {
|
||||
if (address < 0xFFC0) {
|
||||
return (aram_.read(address) | (aram_.read(address + 1) << 8));
|
||||
} else {
|
||||
// Check if register is set to unmap the IPL ROM
|
||||
if (read(0xF1) & 0x80) {
|
||||
return aram_.read(address);
|
||||
}
|
||||
return ipl_rom_[address - 0xFFC0];
|
||||
}
|
||||
}
|
||||
|
||||
// Write a byte to the memory-mapped registers
|
||||
void write(uint16_t address, uint8_t value) {
|
||||
if (address < 0xFFC0) {
|
||||
aram_.write(address, value);
|
||||
} else {
|
||||
// Check if register is set to unmap the IPL ROM
|
||||
if (read(0xF1) & 0x80) {
|
||||
aram_.write(address, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// Addressing modes
|
||||
|
||||
// Immediate
|
||||
uint8_t imm();
|
||||
|
||||
// Direct page
|
||||
uint8_t dp();
|
||||
uint8_t& mutable_dp();
|
||||
|
||||
uint8_t get_dp_addr();
|
||||
|
||||
// Direct page indexed by X
|
||||
uint8_t dp_plus_x();
|
||||
|
||||
// Direct page indexed by Y
|
||||
uint8_t dp_plus_y();
|
||||
|
||||
// Indexed indirect (add index before 16-bit lookup).
|
||||
uint16_t dp_plus_x_indirect();
|
||||
|
||||
// Indirect indexed (add index after 16-bit lookup).
|
||||
uint16_t dp_indirect_plus_y();
|
||||
|
||||
uint16_t abs();
|
||||
|
||||
int8_t rel();
|
||||
|
||||
uint8_t i();
|
||||
|
||||
uint8_t i_postinc();
|
||||
|
||||
uint16_t addr_plus_i();
|
||||
|
||||
uint16_t addr_plus_i_indexed();
|
||||
|
||||
// ==========================================================================
|
||||
// Instructions
|
||||
|
||||
void MOV(uint8_t& dest, uint8_t operand);
|
||||
void MOV_ADDR(uint16_t address, uint8_t operand);
|
||||
void ADC(uint8_t& dest, uint8_t operand);
|
||||
void SBC(uint8_t& dest, uint8_t operand);
|
||||
void CMP(uint8_t& dest, uint8_t operand);
|
||||
void AND(uint8_t& dest, uint8_t operand);
|
||||
void OR(uint8_t& dest, uint8_t operand);
|
||||
void EOR(uint8_t& dest, uint8_t operand);
|
||||
void ASL(uint8_t operand);
|
||||
void LSR(uint8_t& operand);
|
||||
void ROL(uint8_t operand, bool isImmediate = false);
|
||||
void XCN(uint8_t operand, bool isImmediate = false);
|
||||
void INC(uint8_t& operand);
|
||||
void DEC(uint8_t& operand);
|
||||
void MOVW(uint16_t& dest, uint16_t operand);
|
||||
void INCW(uint16_t& operand);
|
||||
void DECW(uint16_t& operand);
|
||||
void ADDW(uint16_t& dest, uint16_t operand);
|
||||
void SUBW(uint16_t& dest, uint16_t operand);
|
||||
void CMPW(uint16_t operand);
|
||||
void MUL(uint8_t operand);
|
||||
void DIV(uint8_t operand);
|
||||
void BRA(int8_t offset);
|
||||
void BEQ(int8_t offset);
|
||||
void BNE(int8_t offset);
|
||||
void BCS(int8_t offset);
|
||||
void BCC(int8_t offset);
|
||||
void BVS(int8_t offset);
|
||||
void BVC(int8_t offset);
|
||||
void BMI(int8_t offset);
|
||||
void BPL(int8_t offset);
|
||||
void BBS(uint8_t bit, uint8_t operand);
|
||||
void BBC(uint8_t bit, uint8_t operand);
|
||||
void JMP(uint16_t address);
|
||||
void CALL(uint16_t address);
|
||||
void PCALL(uint8_t offset);
|
||||
void TCALL(uint8_t offset);
|
||||
void BRK();
|
||||
void RET();
|
||||
void RETI();
|
||||
void PUSH(uint8_t operand);
|
||||
void POP(uint8_t& operand);
|
||||
void SET1(uint8_t bit, uint8_t& operand);
|
||||
void CLR1(uint8_t bit, uint8_t& operand);
|
||||
void TSET1(uint8_t bit, uint8_t& operand);
|
||||
void TCLR1(uint8_t bit, uint8_t& operand);
|
||||
void AND1(uint8_t bit, uint8_t& operand);
|
||||
void OR1(uint8_t bit, uint8_t& operand);
|
||||
void EOR1(uint8_t bit, uint8_t& operand);
|
||||
void NOT1(uint8_t bit, uint8_t& operand);
|
||||
void MOV1(uint8_t bit, uint8_t& operand);
|
||||
void CLRC();
|
||||
void SETC();
|
||||
void NOTC();
|
||||
void CLRV();
|
||||
void CLRP();
|
||||
void SETP();
|
||||
void EI();
|
||||
void DI();
|
||||
void NOP();
|
||||
void SLEEP();
|
||||
void STOP();
|
||||
|
||||
// CBNE DBNZ
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
#endif // YAZE_APP_EMU_SPC700_H
|
||||
63
src/app/emu/cpu/clock.h
Normal file
63
src/app/emu/cpu/clock.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef YAZE_APP_EMU_CLOCK_H_
|
||||
#define YAZE_APP_EMU_CLOCK_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class Clock {
|
||||
public:
|
||||
virtual ~Clock() = default;
|
||||
virtual void UpdateClock(double delta) = 0;
|
||||
virtual unsigned long long GetCycleCount() const = 0;
|
||||
virtual void ResetAccumulatedTime() = 0;
|
||||
virtual void SetFrequency(float new_frequency) = 0;
|
||||
virtual float GetFrequency() const = 0;
|
||||
};
|
||||
|
||||
class ClockImpl : public Clock {
|
||||
public:
|
||||
ClockImpl() = default;
|
||||
virtual ~ClockImpl() = default;
|
||||
|
||||
void UpdateCycleCount(double deltaTime) {
|
||||
accumulatedTime += deltaTime;
|
||||
double cycleTime = 1.0 / frequency;
|
||||
|
||||
while (accumulatedTime >= cycleTime) {
|
||||
Cycle();
|
||||
accumulatedTime -= cycleTime;
|
||||
}
|
||||
}
|
||||
|
||||
void Cycle() {
|
||||
cycle++;
|
||||
cycleCount++;
|
||||
}
|
||||
|
||||
void UpdateClock(double delta) override {
|
||||
UpdateCycleCount(delta);
|
||||
ResetAccumulatedTime();
|
||||
}
|
||||
|
||||
void ResetAccumulatedTime() override { accumulatedTime = 0.0; }
|
||||
unsigned long long GetCycleCount() const override { return cycleCount; }
|
||||
float GetFrequency() const override { return frequency; }
|
||||
void SetFrequency(float new_frequency) override {
|
||||
this->frequency = new_frequency;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t cycle = 0; // Current cycle
|
||||
float frequency = 0.0; // Frequency of the clock in Hz
|
||||
unsigned long long cycleCount = 0; // Total number of cycles executed
|
||||
double accumulatedTime = 0.0; // Accumulated time since the last cycle update
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_CLOCK_H_
|
||||
1995
src/app/emu/cpu/cpu.cc
Normal file
1995
src/app/emu/cpu/cpu.cc
Normal file
File diff suppressed because it is too large
Load Diff
726
src/app/emu/cpu/cpu.h
Normal file
726
src/app/emu/cpu/cpu.h
Normal file
@@ -0,0 +1,726 @@
|
||||
#ifndef YAZE_APP_EMU_CPU_H_
|
||||
#define YAZE_APP_EMU_CPU_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/common.h"
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/cpu/internal/opcodes.h"
|
||||
#include "app/emu/debug/log.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class InstructionEntry {
|
||||
public:
|
||||
// Constructor
|
||||
InstructionEntry(uint32_t addr, uint8_t op, const std::string& ops,
|
||||
const std::string& instr)
|
||||
: address(addr), opcode(op), operands(ops), instruction(instr) {}
|
||||
|
||||
// Getters for the class members
|
||||
uint32_t GetAddress() const { return address; }
|
||||
uint8_t GetOpcode() const { return opcode; }
|
||||
const std::string& GetOperands() const { return operands; }
|
||||
const std::string& GetInstruction() const { return instruction; }
|
||||
|
||||
uint32_t address; // Memory address of the instruction
|
||||
uint8_t opcode; // Opcode of the instruction
|
||||
std::string operands; // Operand(s) of the instruction, if any
|
||||
std::string instruction; // Human-readable instruction text
|
||||
};
|
||||
|
||||
const int kCpuClockSpeed = 21477272; // 21.477272 MHz
|
||||
|
||||
class CPU : public Memory, public Loggable, public core::ExperimentFlags {
|
||||
public:
|
||||
explicit CPU(Memory& mem, Clock& vclock) : memory(mem), clock(vclock) {}
|
||||
enum class UpdateMode { Run, Step, Pause };
|
||||
|
||||
void Init(bool verbose = false) { clock.SetFrequency(kCpuClockSpeed); }
|
||||
|
||||
void Update(UpdateMode mode = UpdateMode::Run, int stepCount = 1);
|
||||
|
||||
void ExecuteInstruction(uint8_t opcode);
|
||||
void LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand,
|
||||
bool immediate, bool accumulator_mode);
|
||||
|
||||
void UpdatePC(uint8_t instruction_length) { PC += instruction_length; }
|
||||
|
||||
uint8_t GetInstructionLength(uint8_t opcode);
|
||||
uint16_t SP() const override { return memory.SP(); }
|
||||
void SetSP(uint16_t value) override { memory.SetSP(value); }
|
||||
void set_next_pc(uint16_t value) { next_pc_ = value; }
|
||||
void UpdateClock(int delta_time) { clock.UpdateClock(delta_time); }
|
||||
|
||||
bool IsBreakpoint(uint32_t address) {
|
||||
return std::find(breakpoints_.begin(), breakpoints_.end(), address) !=
|
||||
breakpoints_.end();
|
||||
}
|
||||
void SetBreakpoint(uint32_t address) { breakpoints_.push_back(address); }
|
||||
void ClearBreakpoint(uint32_t address) {
|
||||
breakpoints_.erase(
|
||||
std::remove(breakpoints_.begin(), breakpoints_.end(), address),
|
||||
breakpoints_.end());
|
||||
}
|
||||
void ClearBreakpoints() {
|
||||
breakpoints_.clear();
|
||||
breakpoints_.shrink_to_fit();
|
||||
}
|
||||
auto GetBreakpoints() { return breakpoints_; }
|
||||
|
||||
std::vector<uint32_t> breakpoints_;
|
||||
std::vector<InstructionEntry> instruction_log_;
|
||||
|
||||
// ======================================================
|
||||
// Interrupt Vectors
|
||||
// Emulation mode, e = 1 Native mode, e = 0
|
||||
//
|
||||
// 0xFFFE,FF - IRQ/BRK 0xFFEE,EF - IRQ
|
||||
// 0xFFFC,FD - RESET
|
||||
// 0xFFFA,FB - NMI 0xFFEA,EB - NMI
|
||||
// 0xFFF8,F9 - ABORT 0xFFE8,E9 - ABORT
|
||||
// 0xFFE6,E7 - BRK
|
||||
// 0xFFF4,F5 - COP 0xFFE4,E5 - COP
|
||||
void HandleInterrupts();
|
||||
|
||||
// ======================================================
|
||||
// Registers
|
||||
|
||||
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
|
||||
// V #$40 01000000 Overflow
|
||||
// M #$20 00100000 Accumulator size (0 = 16-bit, 1 = 8-bit)
|
||||
// X #$10 00010000 Index size (0 = 16-bit, 1 = 8-bit)
|
||||
// D #$08 00001000 Decimal
|
||||
// I #$04 00000100 IRQ disable
|
||||
// Z #$02 00000010 Zero
|
||||
// C #$01 00000001 Carry
|
||||
// E 6502 emulation mode
|
||||
// B #$10 00010000 Break (emulation mode only)
|
||||
|
||||
// Setting flags in the status register
|
||||
bool m() { return GetAccumulatorSize() ? 1 : 0; }
|
||||
int GetAccumulatorSize() const { return status & 0x20; }
|
||||
int GetIndexSize() const { return status & 0x10; }
|
||||
void set_16_bit_mode() {
|
||||
SetAccumulatorSize(true);
|
||||
SetIndexSize(true);
|
||||
}
|
||||
void set_8_bit_mode() {
|
||||
SetAccumulatorSize(false);
|
||||
SetIndexSize(false);
|
||||
}
|
||||
void SetAccumulatorSize(bool set) { SetFlag(0x20, set); }
|
||||
void SetIndexSize(bool set) { SetFlag(0x10, set); }
|
||||
|
||||
// Set individual flags
|
||||
void SetNegativeFlag(bool set) { SetFlag(0x80, set); }
|
||||
void SetOverflowFlag(bool set) { SetFlag(0x40, set); }
|
||||
void SetBreakFlag(bool set) { SetFlag(0x10, set); }
|
||||
void SetDecimalFlag(bool set) { SetFlag(0x08, set); }
|
||||
void SetInterruptFlag(bool set) { SetFlag(0x04, set); }
|
||||
void SetZeroFlag(bool set) { SetFlag(0x02, set); }
|
||||
void SetCarryFlag(bool set) { SetFlag(0x01, set); }
|
||||
|
||||
// Get individual flags
|
||||
bool GetNegativeFlag() const { return GetFlag(0x80); }
|
||||
bool GetOverflowFlag() const { return GetFlag(0x40); }
|
||||
bool GetBreakFlag() const { return GetFlag(0x10); }
|
||||
bool GetDecimalFlag() const { return GetFlag(0x08); }
|
||||
bool GetInterruptFlag() const { return GetFlag(0x04); }
|
||||
bool GetZeroFlag() const { return GetFlag(0x02); }
|
||||
bool GetCarryFlag() const { return GetFlag(0x01); }
|
||||
|
||||
enum class AccessType { Control, Data };
|
||||
|
||||
// ==========================================================================
|
||||
// Addressing Modes
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Data Bank Register if locating data
|
||||
// Program Bank Register if transferring control
|
||||
// High: Second operand byte
|
||||
// Low: First operand byte
|
||||
//
|
||||
// LDA addr
|
||||
uint32_t Absolute(AccessType access_type = AccessType::Data);
|
||||
|
||||
// Effective Address:
|
||||
// The Data Bank Register is concatened with the 16-bit operand
|
||||
// the 24-bit result is added to the X Index Register
|
||||
// based on the emulation mode (16:X=0, 8:X=1)
|
||||
//
|
||||
// LDA addr, X
|
||||
uint32_t AbsoluteIndexedX();
|
||||
|
||||
// Effective Address:
|
||||
// The Data Bank Register is concatened with the 16-bit operand
|
||||
// the 24-bit result is added to the Y Index Register
|
||||
// based on the emulation mode (16:Y=0, 8:Y=1)
|
||||
//
|
||||
// LDA addr, Y
|
||||
uint32_t AbsoluteIndexedY();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Program Bank Register (PBR)
|
||||
// High/low: The Indirect Address
|
||||
// Indirect Address: Located in the Program Bank at the sum of
|
||||
// the operand double byte and X based on the
|
||||
// emulation mode
|
||||
// JMP (addr, X)
|
||||
uint16_t AbsoluteIndexedIndirect();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Program Bank Register (PBR)
|
||||
// High/low: The Indirect Address
|
||||
// Indirect Address: Located in Bank Zero, at the operand double byte
|
||||
//
|
||||
// JMP (addr)
|
||||
uint16_t AbsoluteIndirect();
|
||||
|
||||
// Effective Address:
|
||||
// Bank/High/Low: The 24-bit Indirect Address
|
||||
// Indirect Address: Located in Bank Zero, at the operand double byte
|
||||
//
|
||||
// JMP [addr]
|
||||
uint32_t AbsoluteIndirectLong();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Third operand byte
|
||||
// High: Second operand byte
|
||||
// Low: First operand byte
|
||||
//
|
||||
// LDA long
|
||||
uint32_t AbsoluteLong();
|
||||
|
||||
// Effective Address:
|
||||
// The 24-bit operand is added to X based on the emulation mode
|
||||
//
|
||||
// LDA long, X
|
||||
uint32_t AbsoluteLongIndexedX();
|
||||
|
||||
// Source Effective Address:
|
||||
// Bank: Second operand byte
|
||||
// High/Low: The 16-bit value in X, if X is 8-bit high byte is 0
|
||||
//
|
||||
// Destination Effective Address:
|
||||
// Bank: First operand byte
|
||||
// High/Low: The 16-bit value in Y, if Y is 8-bit high byte is 0
|
||||
//
|
||||
// Length:
|
||||
// The number of bytes to be moved: 16-bit value in Acculumator C plus 1.
|
||||
//
|
||||
// MVN src, dst
|
||||
void BlockMove(uint16_t source, uint16_t dest, uint16_t length);
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Zero
|
||||
// High/low: Direct Page Register plus operand byte
|
||||
//
|
||||
// LDA dp
|
||||
uint16_t DirectPage();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Zero
|
||||
// High/low: Direct Page Register plus operand byte plus X
|
||||
// based on the emulation mode
|
||||
//
|
||||
// LDA dp, X
|
||||
uint16_t DirectPageIndexedX();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Zero
|
||||
// High/low: Direct Page Register plus operand byte plus Y
|
||||
// based on the emulation mode
|
||||
// LDA dp, Y
|
||||
uint16_t DirectPageIndexedY();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Data bank register
|
||||
// High/low: The indirect address
|
||||
// Indirect Address: Located in the direct page at the sum of the direct page
|
||||
// register, the operand byte, and X based on the emulation mode in bank zero.
|
||||
//
|
||||
// LDA (dp, X)
|
||||
uint16_t DirectPageIndexedIndirectX();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Data bank register
|
||||
// High/low: The 16-bit indirect address
|
||||
// Indirect Address: The operand byte plus the direct page register in bank
|
||||
// zero.
|
||||
//
|
||||
// LDA (dp)
|
||||
uint16_t DirectPageIndirect();
|
||||
|
||||
// Effective Address:
|
||||
// Bank/High/Low: The 24-bit indirect address
|
||||
// Indirect address: The operand byte plus the direct page
|
||||
// register in bank zero.
|
||||
//
|
||||
// LDA [dp]
|
||||
uint32_t DirectPageIndirectLong();
|
||||
|
||||
// Effective Address:
|
||||
// Found by concatenating the data bank to the double-byte
|
||||
// indirect address, then adding Y based on the emulation mode.
|
||||
//
|
||||
// Indirect Address: Located in the Direct Page at the sum of the direct page
|
||||
// register and the operand byte, in bank zero.
|
||||
//
|
||||
// LDA (dp), Y
|
||||
uint16_t DirectPageIndirectIndexedY();
|
||||
|
||||
// Effective Address:
|
||||
// Found by adding to the triple-byte indirect address Y based on the
|
||||
// emulation mode. Indrect Address: Located in the Direct Page at the sum
|
||||
// of the direct page register and the operand byte in bank zero.
|
||||
// Indirect Address:
|
||||
// Located in the Direct Page at the sum of the direct page register and
|
||||
// the operand byte in bank zero.
|
||||
//
|
||||
// LDA (dp), Y
|
||||
uint32_t DirectPageIndirectLongIndexedY();
|
||||
|
||||
// 8-bit data: Data Operand Byte
|
||||
// 16-bit data 65816 native mode m or x = 0
|
||||
// Data High: Second Operand Byte
|
||||
// Data Low: First Operand Byte
|
||||
//
|
||||
// LDA #const
|
||||
uint16_t Immediate(bool index_size = false);
|
||||
|
||||
uint16_t StackRelative();
|
||||
|
||||
// Effective Address:
|
||||
// The Data Bank Register is concatenated to the Indirect Address;
|
||||
// the 24-bit result is added to Y (16 bits if x = 0; else 8 bits)
|
||||
// Indirect Address:
|
||||
// Located at the 16-bit sum of the 8-bit operand and the 16-bit stack
|
||||
// pointer
|
||||
//
|
||||
// LDA (sr, S), Y
|
||||
uint32_t StackRelativeIndirectIndexedY();
|
||||
|
||||
// Memory access routines
|
||||
uint8_t ReadByte(uint32_t address) const override {
|
||||
return memory.ReadByte(address);
|
||||
}
|
||||
uint16_t ReadWord(uint32_t address) const override {
|
||||
return memory.ReadWord(address);
|
||||
}
|
||||
uint32_t ReadWordLong(uint32_t address) const override {
|
||||
return memory.ReadWordLong(address);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ReadByteVector(uint32_t address,
|
||||
uint16_t size) const override {
|
||||
return memory.ReadByteVector(address, size);
|
||||
}
|
||||
|
||||
void WriteByte(uint32_t address, uint8_t value) override {
|
||||
memory.WriteByte(address, value);
|
||||
}
|
||||
|
||||
void WriteWord(uint32_t address, uint16_t value) override {
|
||||
memory.WriteWord(address, value);
|
||||
}
|
||||
void WriteLong(uint32_t address, uint32_t value) override {
|
||||
memory.WriteLong(address, value);
|
||||
}
|
||||
|
||||
uint8_t FetchByte() {
|
||||
uint32_t address = (PB << 16) | PC + 1;
|
||||
uint8_t byte = memory.ReadByte(address);
|
||||
return byte;
|
||||
}
|
||||
|
||||
uint16_t FetchWord() {
|
||||
uint32_t address = (PB << 16) | PC + 1;
|
||||
uint16_t value = memory.ReadWord(address);
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t FetchLong() {
|
||||
uint32_t value = memory.ReadWordLong((PB << 16) | PC + 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
int8_t FetchSignedByte() { return static_cast<int8_t>(FetchByte()); }
|
||||
|
||||
int16_t FetchSignedWord() {
|
||||
auto offset = static_cast<int16_t>(FetchWord());
|
||||
return offset;
|
||||
}
|
||||
|
||||
uint8_t FetchByteDirectPage(uint8_t operand) {
|
||||
uint16_t distance = D * 0x100;
|
||||
|
||||
// Calculate the effective address in the Direct Page
|
||||
uint16_t effectiveAddress = operand + distance;
|
||||
|
||||
// Fetch the byte from memory
|
||||
uint8_t fetchedByte = memory.ReadByte(effectiveAddress);
|
||||
|
||||
next_pc_ = PC + 1;
|
||||
|
||||
return fetchedByte;
|
||||
}
|
||||
|
||||
uint16_t ReadByteOrWord(uint32_t address) {
|
||||
if (GetAccumulatorSize()) {
|
||||
// 8-bit mode
|
||||
return memory.ReadByte(address) & 0xFF;
|
||||
} else {
|
||||
// 16-bit mode
|
||||
return memory.ReadWord(address);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// Instructions
|
||||
|
||||
// ADC: Add with carry
|
||||
void ADC(uint16_t operand);
|
||||
|
||||
// AND: Logical AND
|
||||
void AND(uint32_t address, bool immediate = false);
|
||||
void ANDAbsoluteLong(uint32_t address);
|
||||
|
||||
// ASL: Arithmetic shift left
|
||||
void ASL(uint16_t address);
|
||||
|
||||
// BCC: Branch if carry clear
|
||||
void BCC(int8_t offset);
|
||||
|
||||
// BCS: Branch if carry set
|
||||
void BCS(int8_t offset);
|
||||
|
||||
// BEQ: Branch if equal
|
||||
void BEQ(int8_t offset);
|
||||
|
||||
// BIT: Bit test
|
||||
void BIT(uint16_t address);
|
||||
|
||||
// BMI: Branch if minus
|
||||
void BMI(int8_t offset);
|
||||
|
||||
// BNE: Branch if not equal
|
||||
void BNE(int8_t offset);
|
||||
|
||||
// BPL: Branch if plus
|
||||
void BPL(int8_t offset);
|
||||
|
||||
// BRA: Branch always
|
||||
void BRA(int8_t offset);
|
||||
|
||||
// BRK: Force interrupt
|
||||
void BRK();
|
||||
|
||||
// BRL: Branch always long
|
||||
void BRL(int16_t offset);
|
||||
|
||||
// BVC: Branch if overflow clear
|
||||
void BVC(int8_t offset);
|
||||
|
||||
// BVS: Branch if overflow set
|
||||
void BVS(int8_t offset);
|
||||
|
||||
// CLC: Clear carry flag
|
||||
void CLC();
|
||||
|
||||
// CLD: Clear decimal mode
|
||||
void CLD();
|
||||
|
||||
// CLI: Clear interrupt disable bit
|
||||
void CLI();
|
||||
|
||||
// CLV: Clear overflow flag
|
||||
void CLV();
|
||||
|
||||
// CMP: Compare
|
||||
void CMP(uint32_t address, bool immediate = false);
|
||||
|
||||
// COP: Coprocessor enable
|
||||
void COP();
|
||||
|
||||
// CPX: Compare X register
|
||||
void CPX(uint32_t address, bool immediate = false);
|
||||
|
||||
// CPY: Compare Y register
|
||||
void CPY(uint32_t address, bool immediate = false);
|
||||
|
||||
// DEC: Decrement memory
|
||||
void DEC(uint32_t address, bool accumulator = false);
|
||||
|
||||
// DEX: Decrement X register
|
||||
void DEX();
|
||||
|
||||
// DEY: Decrement Y register
|
||||
void DEY();
|
||||
|
||||
// EOR: Exclusive OR
|
||||
void EOR(uint32_t address, bool immediate = false);
|
||||
|
||||
// INC: Increment memory
|
||||
void INC(uint32_t address, bool accumulator = false);
|
||||
|
||||
// INX: Increment X register
|
||||
void INX();
|
||||
|
||||
// INY: Increment Y register
|
||||
void INY();
|
||||
|
||||
// JMP: Jump
|
||||
void JMP(uint16_t address);
|
||||
|
||||
// JML: Jump long
|
||||
void JML(uint32_t address);
|
||||
|
||||
// JSR: Jump to subroutine
|
||||
void JSR(uint16_t address);
|
||||
|
||||
// JSL: Jump to subroutine long
|
||||
void JSL(uint32_t address);
|
||||
|
||||
// LDA: Load accumulator
|
||||
void LDA(uint16_t address, bool immediate = false, bool direct_page = false,
|
||||
bool data_bank = false);
|
||||
|
||||
// LDX: Load X register
|
||||
void LDX(uint16_t address, bool immediate = false);
|
||||
|
||||
// LDY: Load Y register
|
||||
void LDY(uint16_t address, bool immediate = false);
|
||||
|
||||
// LSR: Logical shift right
|
||||
void LSR(uint16_t address, bool accumulator = false);
|
||||
|
||||
// MVN: Block move next
|
||||
void MVN(uint16_t source, uint16_t dest, uint16_t length);
|
||||
|
||||
// MVP: Block move previous
|
||||
void MVP(uint16_t source, uint16_t dest, uint16_t length);
|
||||
|
||||
// NOP: No operation
|
||||
void NOP();
|
||||
|
||||
// ORA: Logical inclusive OR
|
||||
void ORA(uint16_t address, bool immediate = false);
|
||||
|
||||
// PEA: Push effective absolute address
|
||||
void PEA();
|
||||
|
||||
// PEI: Push effective indirect address
|
||||
void PEI();
|
||||
|
||||
// PER: Push effective relative address
|
||||
void PER();
|
||||
|
||||
// PHA: Push accumulator
|
||||
void PHA();
|
||||
|
||||
// PHB: Push data bank register
|
||||
void PHB();
|
||||
|
||||
// PHD: Push direct page register
|
||||
void PHD();
|
||||
|
||||
// PHK: Push program bank register
|
||||
void PHK();
|
||||
|
||||
// PHP: Push processor status (flags)
|
||||
void PHP();
|
||||
|
||||
// PHX: Push X register
|
||||
void PHX();
|
||||
|
||||
// PHY: Push Y register
|
||||
void PHY();
|
||||
|
||||
// PLA: Pull accumulator
|
||||
void PLA();
|
||||
|
||||
// PLB: Pull data bank register
|
||||
void PLB();
|
||||
|
||||
// PLD: Pull direct page register
|
||||
void PLD();
|
||||
|
||||
// PLP: Pull processor status (flags)
|
||||
void PLP();
|
||||
|
||||
// PLX: Pull X register
|
||||
void PLX();
|
||||
|
||||
// PLY: Pull Y register
|
||||
void PLY();
|
||||
|
||||
// REP: Reset processor status bits
|
||||
void REP();
|
||||
|
||||
// ROL: Rotate left
|
||||
void ROL(uint32_t address, bool accumulator = false);
|
||||
|
||||
// ROR: Rotate right
|
||||
void ROR(uint32_t address, bool accumulator = false);
|
||||
|
||||
// RTI: Return from interrupt
|
||||
void RTI();
|
||||
|
||||
// RTL: Return from subroutine long
|
||||
void RTL();
|
||||
|
||||
// RTS: Return from subroutine
|
||||
void RTS();
|
||||
|
||||
// SBC: Subtract with carry
|
||||
void SBC(uint32_t operand, bool immediate = false);
|
||||
|
||||
// SEC: Set carry flag
|
||||
void SEC();
|
||||
|
||||
// SED: Set decimal mode
|
||||
void SED();
|
||||
|
||||
// SEI: Set interrupt disable status
|
||||
void SEI();
|
||||
|
||||
// SEP: Set processor status bits
|
||||
void SEP();
|
||||
|
||||
// STA: Store accumulator
|
||||
void STA(uint32_t address);
|
||||
|
||||
// STP: Stop the processor
|
||||
void STP();
|
||||
|
||||
// STX: Store X register
|
||||
void STX(uint16_t address);
|
||||
|
||||
// STY: Store Y register
|
||||
void STY(uint16_t address);
|
||||
|
||||
// STZ: Store zero
|
||||
void STZ(uint16_t address);
|
||||
|
||||
// TAX: Transfer accumulator to X
|
||||
void TAX();
|
||||
|
||||
// TAY: Transfer accumulator to Y
|
||||
void TAY();
|
||||
|
||||
// TCD: Transfer 16-bit accumulator to direct page register
|
||||
void TCD();
|
||||
|
||||
// TCS: Transfer 16-bit accumulator to stack pointer
|
||||
void TCS();
|
||||
|
||||
// TDC: Transfer direct page register to 16-bit accumulator
|
||||
void TDC();
|
||||
|
||||
// TRB: Test and reset bits
|
||||
void TRB(uint16_t address);
|
||||
|
||||
// TSB: Test and set bits
|
||||
void TSB(uint16_t address);
|
||||
|
||||
// TSC: Transfer stack pointer to 16-bit accumulator
|
||||
void TSC();
|
||||
|
||||
// TSX: Transfer stack pointer to X
|
||||
void TSX();
|
||||
|
||||
// TXA: Transfer X to accumulator
|
||||
void TXA();
|
||||
|
||||
// TXS: Transfer X to stack pointer
|
||||
void TXS();
|
||||
|
||||
// TXY: Transfer X to Y
|
||||
void TXY();
|
||||
|
||||
// TYA: Transfer Y to accumulator
|
||||
void TYA();
|
||||
|
||||
// TYX: Transfer Y to X
|
||||
void TYX();
|
||||
|
||||
// WAI: Wait for interrupt
|
||||
void WAI();
|
||||
|
||||
// WDM: Reserved for future expansion
|
||||
void WDM();
|
||||
|
||||
// XBA: Exchange B and A
|
||||
void XBA();
|
||||
|
||||
// XCE: Exchange carry and emulation bits
|
||||
void XCE();
|
||||
|
||||
private:
|
||||
void compare(uint16_t register_value, uint16_t memory_value) {
|
||||
uint16_t result;
|
||||
if (GetIndexSize()) {
|
||||
// 8-bit mode
|
||||
uint8_t result8 = static_cast<uint8_t>(register_value) -
|
||||
static_cast<uint8_t>(memory_value);
|
||||
result = result8;
|
||||
SetNegativeFlag(result & 0x80); // Negative flag for 8-bit
|
||||
} else {
|
||||
// 16-bit mode
|
||||
result = register_value - memory_value;
|
||||
SetNegativeFlag(result & 0x8000); // Negative flag for 16-bit
|
||||
}
|
||||
SetZeroFlag(result == 0); // Zero flag
|
||||
SetCarryFlag(register_value >= memory_value); // Carry flag
|
||||
}
|
||||
|
||||
void SetFlag(uint8_t mask, bool set) {
|
||||
if (set) {
|
||||
status |= mask; // Set the bit
|
||||
} else {
|
||||
status &= ~mask; // Clear the bit
|
||||
}
|
||||
}
|
||||
|
||||
bool GetFlag(uint8_t mask) const { return (status & mask) != 0; }
|
||||
void PushByte(uint8_t value) override { memory.PushByte(value); }
|
||||
void PushWord(uint16_t value) override { memory.PushWord(value); }
|
||||
uint8_t PopByte() override { return memory.PopByte(); }
|
||||
uint16_t PopWord() override { return memory.PopWord(); }
|
||||
void PushLong(uint32_t value) override { memory.PushLong(value); }
|
||||
uint32_t PopLong() override { return memory.PopLong(); }
|
||||
void ClearMemory() override { memory.ClearMemory(); }
|
||||
uint8_t operator[](int i) const override { return 0; }
|
||||
uint8_t at(int i) const override { return 0; }
|
||||
|
||||
uint16_t last_call_frame_;
|
||||
uint16_t next_pc_;
|
||||
|
||||
Memory& memory;
|
||||
Clock& clock;
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_CPU_H_
|
||||
123
src/app/emu/cpu/internal/addressing.cc
Normal file
123
src/app/emu/cpu/internal/addressing.cc
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
uint32_t CPU::Absolute(CPU::AccessType access_type) {
|
||||
auto operand = FetchWord();
|
||||
uint32_t bank =
|
||||
(access_type == CPU::AccessType::Data) ? (DB << 16) : (PB << 16);
|
||||
return bank | (operand & 0xFFFF);
|
||||
}
|
||||
|
||||
uint32_t CPU::AbsoluteIndexedX() {
|
||||
uint16_t address = memory.ReadWord((PB << 16) | (PC + 1));
|
||||
uint32_t effective_address = (DB << 16) | ((address + X) & 0xFFFF);
|
||||
return effective_address;
|
||||
}
|
||||
|
||||
uint32_t CPU::AbsoluteIndexedY() {
|
||||
uint16_t address = memory.ReadWord((PB << 16) | (PC + 1));
|
||||
uint32_t effective_address = (DB << 16) | address + Y;
|
||||
return effective_address;
|
||||
}
|
||||
|
||||
uint16_t CPU::AbsoluteIndexedIndirect() {
|
||||
uint16_t address = FetchWord() + X;
|
||||
return memory.ReadWord((DB << 16) | address & 0xFFFF);
|
||||
}
|
||||
|
||||
uint16_t CPU::AbsoluteIndirect() {
|
||||
uint16_t address = FetchWord();
|
||||
return memory.ReadWord((PB << 16) | address);
|
||||
}
|
||||
|
||||
uint32_t CPU::AbsoluteIndirectLong() {
|
||||
uint16_t address = FetchWord();
|
||||
return memory.ReadWordLong((PB << 16) | address);
|
||||
}
|
||||
|
||||
uint32_t CPU::AbsoluteLong() { return FetchLong(); }
|
||||
|
||||
uint32_t CPU::AbsoluteLongIndexedX() { return FetchLong() + X; }
|
||||
|
||||
void CPU::BlockMove(uint16_t source, uint16_t dest, uint16_t length) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
memory.WriteByte(dest + i, memory.ReadByte(source + i));
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t CPU::DirectPage() {
|
||||
uint8_t dp = FetchByte();
|
||||
return D + dp;
|
||||
}
|
||||
|
||||
uint16_t CPU::DirectPageIndexedX() {
|
||||
uint8_t operand = FetchByte();
|
||||
uint16_t x_by_mode = GetAccumulatorSize() ? X : X & 0xFF;
|
||||
return D + operand + x_by_mode;
|
||||
}
|
||||
|
||||
uint16_t CPU::DirectPageIndexedY() {
|
||||
uint8_t operand = FetchByte();
|
||||
return (operand + Y) & 0xFF;
|
||||
}
|
||||
|
||||
uint16_t CPU::DirectPageIndexedIndirectX() {
|
||||
uint8_t operand = FetchByte();
|
||||
uint16_t indirect_address = D + operand + X;
|
||||
uint16_t effective_address = memory.ReadWord(indirect_address & 0xFFFF);
|
||||
return effective_address;
|
||||
}
|
||||
|
||||
uint16_t CPU::DirectPageIndirect() {
|
||||
uint8_t dp = FetchByte();
|
||||
uint16_t effective_address = D + dp;
|
||||
return memory.ReadWord(effective_address);
|
||||
}
|
||||
|
||||
uint32_t CPU::DirectPageIndirectLong() {
|
||||
uint8_t dp = FetchByte();
|
||||
uint16_t effective_address = D + dp;
|
||||
return memory.ReadWordLong((0x00 << 0x10) | effective_address);
|
||||
}
|
||||
|
||||
uint16_t CPU::DirectPageIndirectIndexedY() {
|
||||
uint8_t operand = FetchByte();
|
||||
uint16_t indirect_address = D + operand;
|
||||
return memory.ReadWord(indirect_address) + Y;
|
||||
}
|
||||
|
||||
uint32_t CPU::DirectPageIndirectLongIndexedY() {
|
||||
uint8_t operand = FetchByte();
|
||||
uint16_t indirect_address = D + operand;
|
||||
uint16_t y_by_mode = GetAccumulatorSize() ? Y : Y & 0xFF;
|
||||
uint32_t effective_address =
|
||||
memory.ReadWordLong(indirect_address) + y_by_mode;
|
||||
return effective_address;
|
||||
}
|
||||
|
||||
uint16_t CPU::Immediate(bool index_size) {
|
||||
bool bit_mode = index_size ? GetIndexSize() : GetAccumulatorSize();
|
||||
if (bit_mode) {
|
||||
return memory.ReadByte((PB << 16) | PC + 1);
|
||||
} else {
|
||||
return memory.ReadWord((PB << 16) | PC + 1);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t CPU::StackRelative() {
|
||||
uint8_t sr = FetchByte();
|
||||
uint16_t effective_address = SP() + sr;
|
||||
return effective_address;
|
||||
}
|
||||
|
||||
uint32_t CPU::StackRelativeIndirectIndexedY() {
|
||||
uint8_t sr = FetchByte();
|
||||
return (DB << 0x10) | (memory.ReadWord(SP() + sr) + Y);
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
820
src/app/emu/cpu/internal/instructions.cc
Normal file
820
src/app/emu/cpu/internal/instructions.cc
Normal file
@@ -0,0 +1,820 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
/**
|
||||
* 65816 Instruction Set
|
||||
*
|
||||
* TODO: STP, WDM
|
||||
*/
|
||||
|
||||
void CPU::ADC(uint16_t operand) {
|
||||
bool C = GetCarryFlag();
|
||||
if (GetAccumulatorSize()) { // 8-bit mode
|
||||
uint16_t result = static_cast<uint16_t>(A & 0xFF) +
|
||||
static_cast<uint16_t>(operand) + (C ? 1 : 0);
|
||||
SetCarryFlag(result > 0xFF); // Update the carry flag
|
||||
|
||||
// Update the overflow flag
|
||||
bool overflow = (~(A ^ operand) & (A ^ result) & 0x80) != 0;
|
||||
SetOverflowFlag(overflow);
|
||||
|
||||
// Update the accumulator with proper wrap-around
|
||||
A = (A & 0xFF00) | (result & 0xFF);
|
||||
|
||||
SetZeroFlag((A & 0xFF) == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
} else {
|
||||
uint32_t result =
|
||||
static_cast<uint32_t>(A) + static_cast<uint32_t>(operand) + (C ? 1 : 0);
|
||||
SetCarryFlag(result > 0xFFFF); // Update the carry flag
|
||||
|
||||
// Update the overflow flag
|
||||
bool overflow = (~(A ^ operand) & (A ^ result) & 0x8000) != 0;
|
||||
SetOverflowFlag(overflow);
|
||||
|
||||
// Update the accumulator
|
||||
A = result & 0xFFFF;
|
||||
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::AND(uint32_t value, bool isImmediate) {
|
||||
uint16_t operand;
|
||||
if (GetAccumulatorSize()) { // 8-bit mode
|
||||
operand = isImmediate ? value : memory.ReadByte(value);
|
||||
A &= operand;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
} else { // 16-bit mode
|
||||
operand = isImmediate ? value : memory.ReadWord(value);
|
||||
A &= operand;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
// New function for absolute long addressing mode
|
||||
void CPU::ANDAbsoluteLong(uint32_t address) {
|
||||
uint32_t operand32 = memory.ReadWordLong(address);
|
||||
A &= operand32;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x8000);
|
||||
}
|
||||
|
||||
void CPU::ASL(uint16_t address) {
|
||||
uint8_t value = memory.ReadByte(address);
|
||||
SetCarryFlag(!(value & 0x80)); // Set carry flag if bit 7 is set
|
||||
value <<= 1; // Shift left
|
||||
value &= 0xFE; // Clear bit 0
|
||||
memory.WriteByte(address, value);
|
||||
SetNegativeFlag(!value);
|
||||
SetZeroFlag(value);
|
||||
}
|
||||
|
||||
void CPU::BCC(int8_t offset) {
|
||||
if (!GetCarryFlag()) { // If the carry flag is clear
|
||||
next_pc_ = offset;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::BCS(int8_t offset) {
|
||||
if (GetCarryFlag()) { // If the carry flag is set
|
||||
next_pc_ = offset;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::BEQ(int8_t offset) {
|
||||
if (GetZeroFlag()) { // If the zero flag is set
|
||||
next_pc_ = offset;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::BIT(uint16_t address) {
|
||||
uint8_t value = memory.ReadByte(address);
|
||||
SetNegativeFlag(value & 0x80);
|
||||
SetOverflowFlag(value & 0x40);
|
||||
SetZeroFlag((A & value) == 0);
|
||||
}
|
||||
|
||||
void CPU::BMI(int8_t offset) {
|
||||
if (GetNegativeFlag()) { // If the negative flag is set
|
||||
next_pc_ = offset;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::BNE(int8_t offset) {
|
||||
if (!GetZeroFlag()) { // If the zero flag is clear
|
||||
// PC += offset;
|
||||
next_pc_ = offset;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::BPL(int8_t offset) {
|
||||
if (!GetNegativeFlag()) { // If the negative flag is clear
|
||||
next_pc_ = offset;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::BRA(int8_t offset) { next_pc_ = offset; }
|
||||
|
||||
void CPU::BRK() {
|
||||
next_pc_ = PC + 2; // Increment the program counter by 2
|
||||
memory.PushWord(next_pc_);
|
||||
memory.PushByte(status);
|
||||
SetInterruptFlag(true);
|
||||
try {
|
||||
next_pc_ = memory.ReadWord(0xFFFE);
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << "BRK: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::BRL(int16_t offset) { next_pc_ = offset; }
|
||||
|
||||
void CPU::BVC(int8_t offset) {
|
||||
if (!GetOverflowFlag()) { // If the overflow flag is clear
|
||||
next_pc_ = offset;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::BVS(int8_t offset) {
|
||||
if (GetOverflowFlag()) { // If the overflow flag is set
|
||||
next_pc_ = offset;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::CLC() { status &= ~0x01; }
|
||||
|
||||
void CPU::CLD() { status &= ~0x08; }
|
||||
|
||||
void CPU::CLI() { status &= ~0x04; }
|
||||
|
||||
void CPU::CLV() { status &= ~0x40; }
|
||||
|
||||
// n Set if MSB of result is set; else cleared
|
||||
// z Set if result is zero; else cleared
|
||||
// c Set if no borrow; else cleared
|
||||
void CPU::CMP(uint32_t value, bool isImmediate) {
|
||||
if (GetAccumulatorSize()) { // 8-bit
|
||||
uint8_t result;
|
||||
if (isImmediate) {
|
||||
result = A - (value & 0xFF);
|
||||
} else {
|
||||
uint8_t memory_value = memory.ReadByte(value);
|
||||
result = A - memory_value;
|
||||
}
|
||||
SetZeroFlag(result == 0);
|
||||
SetNegativeFlag(result & 0x80);
|
||||
SetCarryFlag(A >= (value & 0xFF));
|
||||
} else { // 16-bit
|
||||
uint16_t result;
|
||||
if (isImmediate) {
|
||||
result = A - (value & 0xFFFF);
|
||||
} else {
|
||||
uint16_t memory_value = memory.ReadWord(value);
|
||||
result = A - memory_value;
|
||||
}
|
||||
SetZeroFlag(result == 0);
|
||||
SetNegativeFlag(result & 0x8000);
|
||||
SetCarryFlag(A >= (value & 0xFFFF));
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::COP() {
|
||||
next_pc_ += 2; // Increment the program counter by 2
|
||||
memory.PushWord(next_pc_);
|
||||
memory.PushByte(status);
|
||||
SetInterruptFlag(true);
|
||||
if (E) {
|
||||
next_pc_ = memory.ReadWord(0xFFF4);
|
||||
} else {
|
||||
next_pc_ = memory.ReadWord(0xFFE4);
|
||||
}
|
||||
SetDecimalFlag(false);
|
||||
}
|
||||
|
||||
void CPU::CPX(uint32_t value, bool isImmediate) {
|
||||
if (GetIndexSize()) { // 8-bit
|
||||
uint8_t memory_value = isImmediate ? value : memory.ReadByte(value);
|
||||
compare(X, memory_value);
|
||||
} else { // 16-bit
|
||||
uint16_t memory_value = isImmediate ? value : memory.ReadWord(value);
|
||||
compare(X, memory_value);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::CPY(uint32_t value, bool isImmediate) {
|
||||
if (GetIndexSize()) { // 8-bit
|
||||
uint8_t memory_value = isImmediate ? value : memory.ReadByte(value);
|
||||
compare(Y, memory_value);
|
||||
} else { // 16-bit
|
||||
uint16_t memory_value = isImmediate ? value : memory.ReadWord(value);
|
||||
compare(Y, memory_value);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::DEC(uint32_t address, bool accumulator) {
|
||||
if (accumulator) {
|
||||
if (GetAccumulatorSize()) { // 8-bit
|
||||
A = (A - 1) & 0xFF;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
} else { // 16-bit
|
||||
A = (A - 1) & 0xFFFF;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x8000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetAccumulatorSize()) {
|
||||
uint8_t value = memory.ReadByte(address);
|
||||
value--;
|
||||
memory.WriteByte(address, value);
|
||||
SetZeroFlag(value == 0);
|
||||
SetNegativeFlag(value & 0x80);
|
||||
} else {
|
||||
uint16_t value = memory.ReadWord(address);
|
||||
value--;
|
||||
memory.WriteWord(address, value);
|
||||
SetZeroFlag(value == 0);
|
||||
SetNegativeFlag(value & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::DEX() {
|
||||
if (GetIndexSize()) { // 8-bit
|
||||
X = static_cast<uint8_t>(X - 1);
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x80);
|
||||
} else { // 16-bit
|
||||
X = static_cast<uint16_t>(X - 1);
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::DEY() {
|
||||
if (GetIndexSize()) { // 8-bit
|
||||
Y = static_cast<uint8_t>(Y - 1);
|
||||
SetZeroFlag(Y == 0);
|
||||
SetNegativeFlag(Y & 0x80);
|
||||
} else { // 16-bit
|
||||
Y = static_cast<uint16_t>(Y - 1);
|
||||
SetZeroFlag(Y == 0);
|
||||
SetNegativeFlag(Y & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::EOR(uint32_t address, bool isImmediate) {
|
||||
if (GetAccumulatorSize()) {
|
||||
A ^= isImmediate ? address : memory.ReadByte(address);
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
} else {
|
||||
A ^= isImmediate ? address : memory.ReadWord(address);
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::INC(uint32_t address, bool accumulator) {
|
||||
if (accumulator) {
|
||||
if (GetAccumulatorSize()) { // 8-bit
|
||||
A = (A + 1) & 0xFF;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
} else { // 16-bit
|
||||
A = (A + 1) & 0xFFFF;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x8000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetAccumulatorSize()) {
|
||||
uint8_t value = memory.ReadByte(address);
|
||||
value++;
|
||||
memory.WriteByte(address, value);
|
||||
SetNegativeFlag(value & 0x80);
|
||||
SetZeroFlag(value == 0);
|
||||
} else {
|
||||
uint16_t value = memory.ReadWord(address);
|
||||
value++;
|
||||
memory.WriteWord(address, value);
|
||||
SetNegativeFlag(value & 0x8000);
|
||||
SetZeroFlag(value == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::INX() {
|
||||
if (GetIndexSize()) { // 8-bit
|
||||
X = static_cast<uint8_t>(X + 1);
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x80);
|
||||
} else { // 16-bit
|
||||
X = static_cast<uint16_t>(X + 1);
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::INY() {
|
||||
if (GetIndexSize()) { // 8-bit
|
||||
Y = static_cast<uint8_t>(Y + 1);
|
||||
SetZeroFlag(Y == 0);
|
||||
SetNegativeFlag(Y & 0x80);
|
||||
} else { // 16-bit
|
||||
Y = static_cast<uint16_t>(Y + 1);
|
||||
SetZeroFlag(Y == 0);
|
||||
SetNegativeFlag(Y & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::JMP(uint16_t address) {
|
||||
next_pc_ = address; // Set program counter to the new address
|
||||
}
|
||||
|
||||
void CPU::JML(uint32_t address) {
|
||||
next_pc_ = static_cast<uint16_t>(address & 0xFFFF);
|
||||
// Set the PBR to the upper 8 bits of the address
|
||||
PB = static_cast<uint8_t>((address >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
void CPU::JSR(uint16_t address) {
|
||||
memory.PushWord(PC); // Push the program counter onto the stack
|
||||
next_pc_ = address; // Set program counter to the new address
|
||||
}
|
||||
|
||||
void CPU::JSL(uint32_t address) {
|
||||
memory.PushLong(PC); // Push the program counter onto the stack as a long
|
||||
// value (24 bits)
|
||||
next_pc_ = address; // Set program counter to the new address
|
||||
}
|
||||
|
||||
void CPU::LDA(uint16_t address, bool isImmediate, bool direct_page, bool data_bank) {
|
||||
uint8_t bank = PB;
|
||||
if (direct_page) {
|
||||
bank = 0;
|
||||
}
|
||||
if (GetAccumulatorSize()) {
|
||||
A = isImmediate ? address : memory.ReadByte((bank << 16) | address);
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
} else {
|
||||
A = isImmediate ? address : memory.ReadWord((bank << 16) | address);
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::LDX(uint16_t address, bool isImmediate) {
|
||||
if (GetIndexSize()) {
|
||||
X = isImmediate ? address : memory.ReadByte(address);
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x80);
|
||||
} else {
|
||||
X = isImmediate ? address : memory.ReadWord(address);
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::LDY(uint16_t address, bool isImmediate) {
|
||||
if (GetIndexSize()) {
|
||||
Y = isImmediate ? address : memory.ReadByte(address);
|
||||
SetZeroFlag(Y == 0);
|
||||
SetNegativeFlag(Y & 0x80);
|
||||
} else {
|
||||
Y = isImmediate ? address : memory.ReadWord(address);
|
||||
SetZeroFlag(Y == 0);
|
||||
SetNegativeFlag(Y & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::LSR(uint16_t address, bool accumulator) {
|
||||
if (accumulator) {
|
||||
if (GetAccumulatorSize()) { // 8-bit
|
||||
SetCarryFlag(A & 0x01);
|
||||
A >>= 1;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(false);
|
||||
} else { // 16-bit
|
||||
SetCarryFlag(A & 0x0001);
|
||||
A >>= 1;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
uint8_t value = memory.ReadByte(address);
|
||||
SetCarryFlag(value & 0x01);
|
||||
value >>= 1;
|
||||
memory.WriteByte(address, value);
|
||||
SetNegativeFlag(false);
|
||||
SetZeroFlag(value == 0);
|
||||
}
|
||||
|
||||
void CPU::MVN(uint16_t source, uint16_t dest, uint16_t length) {
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
memory.WriteByte(dest, memory.ReadByte(source));
|
||||
source++;
|
||||
dest++;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::MVP(uint16_t source, uint16_t dest, uint16_t length) {
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
memory.WriteByte(dest, memory.ReadByte(source));
|
||||
source--;
|
||||
dest--;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::NOP() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
void CPU::ORA(uint16_t address, bool isImmediate) {
|
||||
if (GetAccumulatorSize()) {
|
||||
A |= isImmediate ? address : memory.ReadByte(address);
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
} else {
|
||||
A |= isImmediate ? address : memory.ReadWord(address);
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::PEA() {
|
||||
uint16_t address = FetchWord();
|
||||
memory.PushWord(address);
|
||||
}
|
||||
|
||||
void CPU::PEI() {
|
||||
uint16_t address = FetchWord();
|
||||
memory.PushWord(memory.ReadWord(address));
|
||||
}
|
||||
|
||||
void CPU::PER() {
|
||||
uint16_t address = FetchWord();
|
||||
memory.PushWord(PC + address);
|
||||
}
|
||||
|
||||
void CPU::PHA() {
|
||||
if (GetAccumulatorSize()) {
|
||||
memory.PushByte(static_cast<uint8_t>(A));
|
||||
} else {
|
||||
memory.PushWord(A);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::PHB() { memory.PushByte(DB); }
|
||||
|
||||
void CPU::PHD() { memory.PushWord(D); }
|
||||
|
||||
void CPU::PHK() { memory.PushByte(PB); }
|
||||
|
||||
void CPU::PHP() { memory.PushByte(status); }
|
||||
|
||||
void CPU::PHX() {
|
||||
if (GetIndexSize()) {
|
||||
memory.PushByte(static_cast<uint8_t>(X));
|
||||
} else {
|
||||
memory.PushWord(X);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::PHY() {
|
||||
if (GetIndexSize()) {
|
||||
memory.PushByte(static_cast<uint8_t>(Y));
|
||||
} else {
|
||||
memory.PushWord(Y);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::PLA() {
|
||||
if (GetAccumulatorSize()) {
|
||||
A = memory.PopByte();
|
||||
SetNegativeFlag((A & 0x80) != 0);
|
||||
} else {
|
||||
A = memory.PopWord();
|
||||
SetNegativeFlag((A & 0x8000) != 0);
|
||||
}
|
||||
SetZeroFlag(A == 0);
|
||||
}
|
||||
|
||||
void CPU::PLB() {
|
||||
DB = memory.PopByte();
|
||||
SetNegativeFlag((DB & 0x80) != 0);
|
||||
SetZeroFlag(DB == 0);
|
||||
}
|
||||
|
||||
// Pull Direct Page Register from Stack
|
||||
void CPU::PLD() {
|
||||
D = memory.PopWord();
|
||||
SetNegativeFlag((D & 0x8000) != 0);
|
||||
SetZeroFlag(D == 0);
|
||||
}
|
||||
|
||||
// Pull Processor Status Register from Stack
|
||||
void CPU::PLP() { status = memory.PopByte(); }
|
||||
|
||||
void CPU::PLX() {
|
||||
if (GetIndexSize()) {
|
||||
X = memory.PopByte();
|
||||
SetNegativeFlag((A & 0x80) != 0);
|
||||
} else {
|
||||
X = memory.PopWord();
|
||||
SetNegativeFlag((A & 0x8000) != 0);
|
||||
}
|
||||
|
||||
SetZeroFlag(X == 0);
|
||||
}
|
||||
|
||||
void CPU::PLY() {
|
||||
if (GetIndexSize()) {
|
||||
Y = memory.PopByte();
|
||||
SetNegativeFlag((A & 0x80) != 0);
|
||||
} else {
|
||||
Y = memory.PopWord();
|
||||
SetNegativeFlag((A & 0x8000) != 0);
|
||||
}
|
||||
SetZeroFlag(Y == 0);
|
||||
}
|
||||
|
||||
void CPU::REP() {
|
||||
auto byte = FetchByte();
|
||||
status &= ~byte;
|
||||
}
|
||||
|
||||
void CPU::ROL(uint32_t address, bool accumulator) {
|
||||
if (accumulator) {
|
||||
if (GetAccumulatorSize()) { // 8-bit
|
||||
uint8_t carry = GetCarryFlag() ? 0x01 : 0x00;
|
||||
SetCarryFlag(A & 0x80);
|
||||
A <<= 1;
|
||||
A |= carry;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
} else { // 16-bit
|
||||
uint8_t carry = GetCarryFlag() ? 0x01 : 0x00;
|
||||
SetCarryFlag(A & 0x8000);
|
||||
A <<= 1;
|
||||
A |= carry;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x8000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t value = memory.ReadByte(address);
|
||||
uint8_t carry = GetCarryFlag() ? 0x01 : 0x00;
|
||||
SetCarryFlag(value & 0x80);
|
||||
value <<= 1;
|
||||
value |= carry;
|
||||
memory.WriteByte(address, value);
|
||||
SetNegativeFlag(value & 0x80);
|
||||
SetZeroFlag(value == 0);
|
||||
}
|
||||
|
||||
void CPU::ROR(uint32_t address, bool accumulator) {
|
||||
if (accumulator) {
|
||||
if (GetAccumulatorSize()) { // 8-bit
|
||||
uint8_t carry = GetCarryFlag() ? 0x80 : 0x00;
|
||||
SetCarryFlag(A & 0x01);
|
||||
A >>= 1;
|
||||
A |= carry;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
} else { // 16-bit
|
||||
uint8_t carry = GetCarryFlag() ? 0x8000 : 0x00;
|
||||
SetCarryFlag(A & 0x0001);
|
||||
A >>= 1;
|
||||
A |= carry;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x8000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t value = memory.ReadByte(address);
|
||||
uint8_t carry = GetCarryFlag() ? 0x80 : 0x00;
|
||||
SetCarryFlag(value & 0x01);
|
||||
value >>= 1;
|
||||
value |= carry;
|
||||
memory.WriteByte(address, value);
|
||||
SetNegativeFlag(value & 0x80);
|
||||
SetZeroFlag(value == 0);
|
||||
}
|
||||
|
||||
void CPU::RTI() {
|
||||
status = memory.PopByte();
|
||||
PC = memory.PopWord();
|
||||
}
|
||||
|
||||
void CPU::RTL() {
|
||||
next_pc_ = memory.PopWord();
|
||||
PB = memory.PopByte();
|
||||
}
|
||||
|
||||
void CPU::RTS() {
|
||||
last_call_frame_ = memory.PopWord();
|
||||
}
|
||||
|
||||
void CPU::SBC(uint32_t value, bool isImmediate) {
|
||||
uint16_t operand;
|
||||
if (!GetAccumulatorSize()) { // 16-bit mode
|
||||
operand = isImmediate ? value : memory.ReadWord(value);
|
||||
uint16_t result = A - operand - (GetCarryFlag() ? 0 : 1);
|
||||
SetCarryFlag(!(result > 0xFFFF)); // Update the carry flag
|
||||
|
||||
// Update the overflow flag
|
||||
bool overflow = ((A ^ operand) & (A ^ result) & 0x8000) != 0;
|
||||
SetOverflowFlag(overflow);
|
||||
|
||||
// Update the accumulator
|
||||
A = result & 0xFFFF;
|
||||
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x8000);
|
||||
} else { // 8-bit mode
|
||||
operand = isImmediate ? value : memory.ReadByte(value);
|
||||
uint8_t result = A - operand - (GetCarryFlag() ? 0 : 1);
|
||||
SetCarryFlag(!(result > 0xFF)); // Update the carry flag
|
||||
|
||||
// Update the overflow flag
|
||||
bool overflow = ((A ^ operand) & (A ^ result) & 0x80) != 0;
|
||||
SetOverflowFlag(overflow);
|
||||
|
||||
// Update the accumulator
|
||||
A = result & 0xFF;
|
||||
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::SEC() { status |= 0x01; }
|
||||
|
||||
void CPU::SED() { status |= 0x08; }
|
||||
|
||||
void CPU::SEI() { status |= 0x04; }
|
||||
|
||||
void CPU::SEP() {
|
||||
auto byte = FetchByte();
|
||||
status |= byte;
|
||||
}
|
||||
|
||||
void CPU::STA(uint32_t address) {
|
||||
if (GetAccumulatorSize()) {
|
||||
memory.WriteByte(address, static_cast<uint8_t>(A));
|
||||
} else {
|
||||
memory.WriteWord(address, A);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make this work with the Clock class of the CPU
|
||||
|
||||
void CPU::STP() {
|
||||
// During the next phase 2 clock cycle, stop the processors oscillator input
|
||||
// The processor is effectively shut down until a reset occurs (RES` pin).
|
||||
}
|
||||
|
||||
void CPU::STX(uint16_t address) {
|
||||
if (GetIndexSize()) {
|
||||
memory.WriteByte(address, static_cast<uint8_t>(X));
|
||||
} else {
|
||||
memory.WriteWord(address, X);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::STY(uint16_t address) {
|
||||
if (GetIndexSize()) {
|
||||
memory.WriteByte(address, static_cast<uint8_t>(Y));
|
||||
} else {
|
||||
memory.WriteWord(address, Y);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::STZ(uint16_t address) {
|
||||
if (GetAccumulatorSize()) {
|
||||
memory.WriteByte(address, 0x00);
|
||||
} else {
|
||||
memory.WriteWord(address, 0x0000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::TAX() {
|
||||
X = A;
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x80);
|
||||
}
|
||||
|
||||
void CPU::TAY() {
|
||||
Y = A;
|
||||
SetZeroFlag(Y == 0);
|
||||
SetNegativeFlag(Y & 0x80);
|
||||
}
|
||||
|
||||
void CPU::TCD() {
|
||||
D = A;
|
||||
SetZeroFlag(D == 0);
|
||||
SetNegativeFlag(D & 0x80);
|
||||
}
|
||||
|
||||
void CPU::TCS() { memory.SetSP(A); }
|
||||
|
||||
void CPU::TDC() {
|
||||
A = D;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
}
|
||||
|
||||
void CPU::TRB(uint16_t address) {
|
||||
uint8_t value = memory.ReadByte(address);
|
||||
SetZeroFlag((A & value) == 0);
|
||||
value &= ~A;
|
||||
memory.WriteByte(address, value);
|
||||
}
|
||||
|
||||
void CPU::TSB(uint16_t address) {
|
||||
uint8_t value = memory.ReadByte(address);
|
||||
SetZeroFlag((A & value) == 0);
|
||||
value |= A;
|
||||
memory.WriteByte(address, value);
|
||||
}
|
||||
|
||||
void CPU::TSC() {
|
||||
A = SP();
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
}
|
||||
|
||||
void CPU::TSX() {
|
||||
X = SP();
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x80);
|
||||
}
|
||||
|
||||
void CPU::TXA() {
|
||||
A = X;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
}
|
||||
|
||||
void CPU::TXS() { memory.SetSP(X); }
|
||||
|
||||
void CPU::TXY() {
|
||||
Y = X;
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x80);
|
||||
}
|
||||
|
||||
void CPU::TYA() {
|
||||
A = Y;
|
||||
SetZeroFlag(A == 0);
|
||||
SetNegativeFlag(A & 0x80);
|
||||
}
|
||||
|
||||
void CPU::TYX() {
|
||||
X = Y;
|
||||
SetZeroFlag(Y == 0);
|
||||
SetNegativeFlag(Y & 0x80);
|
||||
}
|
||||
|
||||
// TODO: Make this communicate with the SNES class
|
||||
|
||||
void CPU::WAI() {
|
||||
// Pull the RDY pin low
|
||||
// Power consumption is reduced(?)
|
||||
// RDY remains low until an external hardware interupt
|
||||
// (NMI, IRQ, ABORT, or RESET) is received from the SNES class
|
||||
}
|
||||
|
||||
void CPU::XBA() {
|
||||
uint8_t lowByte = A & 0xFF;
|
||||
uint8_t highByte = (A >> 8) & 0xFF;
|
||||
A = (lowByte << 8) | highByte;
|
||||
}
|
||||
|
||||
void CPU::XCE() {
|
||||
uint8_t carry = status & 0x01;
|
||||
status &= ~0x01;
|
||||
status |= E;
|
||||
E = carry;
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
61
src/app/emu/cpu/internal/opcodes.h
Normal file
61
src/app/emu/cpu/internal/opcodes.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
const std::unordered_map<uint8_t, std::string> opcode_to_mnemonic = {
|
||||
{0x00, "BRK"}, {0x01, "ORA"}, {0x02, "COP"}, {0x03, "ORA"}, {0x04, "TSB"},
|
||||
{0x05, "ORA"}, {0x06, "ASL"}, {0x07, "ORA"}, {0x08, "PHP"}, {0x09, "ORA"},
|
||||
{0x0A, "ASL"}, {0x0B, "PHD"}, {0x0C, "TSB"}, {0x0D, "ORA"}, {0x0E, "ASL"},
|
||||
{0x0F, "ORA"}, {0x10, "BPL"}, {0x11, "ORA"}, {0x12, "ORA"}, {0x13, "ORA"},
|
||||
{0x14, "TRB"}, {0x15, "ORA"}, {0x16, "ASL"}, {0x17, "ORA"}, {0x18, "CLC"},
|
||||
{0x19, "ORA"}, {0x1A, "INC"}, {0x1B, "TCS"}, {0x1C, "TRB"}, {0x1D, "ORA"},
|
||||
{0x1E, "ASL"}, {0x1F, "ORA"}, {0x20, "JSR"}, {0x21, "AND"}, {0x22, "JSL"},
|
||||
{0x23, "AND"}, {0x24, "BIT"}, {0x25, "AND"}, {0x26, "ROL"}, {0x27, "AND"},
|
||||
{0x28, "PLP"}, {0x29, "AND"}, {0x2A, "ROL"}, {0x2B, "PLD"}, {0x2C, "BIT"},
|
||||
{0x2D, "AND"}, {0x2E, "ROL"}, {0x2F, "AND"}, {0x30, "BMI"}, {0x31, "AND"},
|
||||
{0x32, "AND"}, {0x33, "AND"}, {0x34, "BIT"}, {0x35, "AND"}, {0x36, "ROL"},
|
||||
{0x37, "AND"}, {0x38, "SEC"}, {0x39, "AND"}, {0x3A, "DEC"}, {0x3B, "TSC"},
|
||||
{0x3C, "BIT"}, {0x3D, "AND"}, {0x3E, "ROL"}, {0x3F, "AND"}, {0x40, "RTI"},
|
||||
{0x41, "EOR"}, {0x42, "WDM"}, {0x43, "EOR"}, {0x44, "MVP"}, {0x45, "EOR"},
|
||||
{0x46, "LSR"}, {0x47, "EOR"}, {0x48, "PHA"}, {0x49, "EOR"}, {0x4A, "LSR"},
|
||||
{0x4B, "PHK"}, {0x4C, "JMP"}, {0x4D, "EOR"}, {0x4E, "LSR"}, {0x4F, "EOR"},
|
||||
{0x50, "BVC"}, {0x51, "EOR"}, {0x52, "EOR"}, {0x53, "EOR"}, {0x54, "MVN"},
|
||||
{0x55, "EOR"}, {0x56, "LSR"}, {0x57, "EOR"}, {0x58, "CLI"}, {0x59, "EOR"},
|
||||
{0x5A, "PHY"}, {0x5B, "TCD"}, {0x5C, "JMP"}, {0x5D, "EOR"}, {0x5E, "LSR"},
|
||||
{0x5F, "EOR"}, {0x60, "RTS"}, {0x61, "ADC"}, {0x62, "PER"}, {0x63, "ADC"},
|
||||
{0x64, "STZ"}, {0x65, "ADC"}, {0x66, "ROR"}, {0x67, "ADC"}, {0x68, "PLA"},
|
||||
{0x69, "ADC"}, {0x6A, "ROR"}, {0x6B, "RTL"}, {0x6C, "JMP"}, {0x6D, "ADC"},
|
||||
{0x6E, "ROR"}, {0x6F, "ADC"}, {0x70, "BVS"}, {0x71, "ADC"}, {0x72, "ADC"},
|
||||
{0x73, "ADC"}, {0x74, "STZ"}, {0x75, "ADC"}, {0x76, "ROR"}, {0x77, "ADC"},
|
||||
{0x78, "SEI"}, {0x79, "ADC"}, {0x7A, "PLY"}, {0x7B, "TDC"}, {0x7C, "JMP"},
|
||||
{0x7D, "ADC"}, {0x7E, "ROR"}, {0x7F, "ADC"}, {0x80, "BRA"}, {0x81, "STA"},
|
||||
{0x82, "BRL"}, {0x83, "STA"}, {0x84, "STY"}, {0x85, "STA"}, {0x86, "STX"},
|
||||
{0x87, "STA"}, {0x88, "DEY"}, {0x89, "BIT"}, {0x8A, "TXA"}, {0x8B, "PHB"},
|
||||
{0x8C, "STY"}, {0x8D, "STA"}, {0x8E, "STX"}, {0x8F, "STA"}, {0x90, "BCC"},
|
||||
{0x91, "STA"}, {0x92, "STA"}, {0x93, "STA"}, {0x94, "STY"}, {0x95, "STA"},
|
||||
{0x96, "STX"}, {0x97, "STA"}, {0x98, "TYA"}, {0x99, "STA"}, {0x9A, "TXS"},
|
||||
{0x9B, "TXY"}, {0x9C, "STZ"}, {0x9D, "STA"}, {0x9E, "STZ"}, {0x9F, "STA"},
|
||||
{0xA0, "LDY"}, {0xA1, "LDA"}, {0xA2, "LDX"}, {0xA3, "LDA"}, {0xA4, "LDY"},
|
||||
{0xA5, "LDA"}, {0xA6, "LDX"}, {0xA7, "LDA"}, {0xA8, "TAY"}, {0xA9, "LDA"},
|
||||
{0xAA, "TAX"}, {0xAB, "PLB"}, {0xAC, "LDY"}, {0xAD, "LDA"}, {0xAE, "LDX"},
|
||||
{0xAF, "LDA"}, {0xB0, "BCS"}, {0xB1, "LDA"}, {0xB2, "LDA"}, {0xB3, "LDA"},
|
||||
{0xB4, "LDY"}, {0xB5, "LDA"}, {0xB6, "LDX"}, {0xB7, "LDA"}, {0xB8, "CLV"},
|
||||
{0xB9, "LDA"}, {0xBA, "TSX"}, {0xBB, "TYX"}, {0xBC, "LDY"}, {0xBD, "LDA"},
|
||||
{0xBE, "LDX"}, {0xBF, "LDA"}, {0xC0, "CPY"}, {0xC1, "CMP"}, {0xC2, "REP"},
|
||||
{0xC3, "CMP"}, {0xC4, "CPY"}, {0xC5, "CMP"}, {0xC6, "DEC"}, {0xC7, "CMP"},
|
||||
{0xC8, "INY"}, {0xC9, "CMP"}, {0xCA, "DEX"}, {0xCB, "WAI"}, {0xCC, "CPY"},
|
||||
{0xCD, "CMP"}, {0xCE, "DEC"}, {0xCF, "CMP"}, {0xD0, "BNE"}, {0xD1, "CMP"},
|
||||
{0xD2, "CMP"}, {0xD3, "CMP"}, {0xD4, "PEI"}, {0xD5, "CMP"}, {0xD6, "DEC"},
|
||||
{0xD7, "CMP"}, {0xD8, "CLD"}, {0xD9, "CMP"}, {0xDA, "PHX"}, {0xDB, "STP"},
|
||||
{0xDC, "JMP"}, {0xDD, "CMP"}, {0xDE, "DEC"}, {0xDF, "CMP"}, {0xE0, "CPX"},
|
||||
{0xE1, "SBC"}, {0xE2, "SEP"}, {0xE3, "SBC"}, {0xE4, "CPX"}, {0xE5, "SBC"},
|
||||
{0xE6, "INC"}, {0xE7, "SBC"}, {0xE8, "INX"}, {0xE9, "SBC"}, {0xEA, "NOP"},
|
||||
{0xEB, "XBA"}, {0xEC, "CPX"}, {0xED, "SBC"}, {0xEE, "INC"}, {0xEF, "SBC"},
|
||||
{0xF0, "BEQ"}, {0xF1, "SBC"}, {0xF2, "SBC"}, {0xF3, "SBC"}, {0xF4, "PEA"},
|
||||
{0xF5, "SBC"}, {0xF6, "INC"}, {0xF7, "SBC"}, {0xF8, "SED"}, {0xF9, "SBC"},
|
||||
{0xFA, "PLX"}, {0xFB, "XCE"}, {0xFC, "JSR"}, {0xFD, "SBC"}, {0xFE, "INC"},
|
||||
{0xFF, "SBC"}
|
||||
|
||||
};
|
||||
118
src/app/emu/debug/asm_parser.h
Normal file
118
src/app/emu/debug/asm_parser.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/cpu/internal/opcodes.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class AsmParser {
|
||||
public:
|
||||
std::vector<uint8_t> Parse(const std::string& instruction) {
|
||||
std::smatch match;
|
||||
if (!std::regex_match(instruction, match, instruction_regex_)) {
|
||||
throw std::runtime_error("Invalid instruction format: " + instruction);
|
||||
}
|
||||
|
||||
std::string mnemonic = match[1];
|
||||
std::string addressing_mode = match[2];
|
||||
std::string operand = match[3];
|
||||
|
||||
std::string lookup_string = mnemonic.substr(0, 3);
|
||||
|
||||
auto opcode_entry = mnemonic_to_opcode_.find(mnemonic);
|
||||
if (opcode_entry == mnemonic_to_opcode_.end()) {
|
||||
throw std::runtime_error(
|
||||
"Unknown mnemonic or addressing mode: " + mnemonic + addressing_mode);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> bytes = {opcode_entry->second};
|
||||
// AppendOperandBytes(bytes, operand, addressing_mode);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void CreateInternalOpcodeMap() {
|
||||
for (const auto& opcode_entry : opcode_to_mnemonic) {
|
||||
std::string name = opcode_entry.second;
|
||||
uint8_t opcode = opcode_entry.first;
|
||||
mnemonic_to_opcode_[name] = opcode;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void AppendOperandBytes(std::vector<uint8_t>& bytes,
|
||||
const std::string& operand,
|
||||
const std::string& addressing_mode) {
|
||||
if (addressing_mode == ".b") {
|
||||
bytes.push_back(static_cast<uint8_t>(std::stoi(operand, nullptr, 16)));
|
||||
} else if (addressing_mode == ".w") {
|
||||
uint16_t word_operand =
|
||||
static_cast<uint16_t>(std::stoi(operand, nullptr, 16));
|
||||
bytes.push_back(static_cast<uint8_t>(word_operand & 0xFF));
|
||||
bytes.push_back(static_cast<uint8_t>((word_operand >> 8) & 0xFF));
|
||||
} else if (addressing_mode == ".l") {
|
||||
uint32_t long_operand =
|
||||
static_cast<uint32_t>(std::stoul(operand, nullptr, 16));
|
||||
bytes.push_back(static_cast<uint8_t>(long_operand & 0xFF));
|
||||
bytes.push_back(static_cast<uint8_t>((long_operand >> 8) & 0xFF));
|
||||
bytes.push_back(static_cast<uint8_t>((long_operand >> 16) & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
enum class AddressingMode {
|
||||
kAbsolute,
|
||||
kAbsoluteLong,
|
||||
kAbsoluteIndexedIndirect,
|
||||
kAbsoluteIndexedX,
|
||||
kAbsoluteIndexedY,
|
||||
kAbsoluteIndirect,
|
||||
kAbsoluteIndirectLong,
|
||||
kAbsoluteLongIndexedX,
|
||||
kAccumulator,
|
||||
kBlockMove,
|
||||
kDirectPage,
|
||||
kDirectPageIndexedX,
|
||||
kDirectPageIndexedY,
|
||||
kDirectPageIndirect,
|
||||
kDirectPageIndirectIndexedY,
|
||||
kDirectPageIndirectLong,
|
||||
kDirectPageIndirectLongIndexedY,
|
||||
kDirectPageIndirectIndexedX,
|
||||
kDirectPageIndirectLongIndexedX,
|
||||
kImmediate,
|
||||
kImplied,
|
||||
kProgramCounterRelative,
|
||||
kProgramCounterRelativeLong,
|
||||
kStackRelative,
|
||||
kStackRelativeIndirectIndexedY,
|
||||
kStackRelativeIndirectIndexedYLong,
|
||||
kStack,
|
||||
kStackRelativeIndexedY,
|
||||
};
|
||||
|
||||
AddressingMode InferAddressingModeFromOperand(const std::string& operand) {
|
||||
if (operand[0] == '$') {
|
||||
return AddressingMode::kAbsolute;
|
||||
} else if (operand[0] == '#') {
|
||||
return AddressingMode::kImmediate;
|
||||
} else {
|
||||
return AddressingMode::kImplied;
|
||||
}
|
||||
}
|
||||
|
||||
const std::regex instruction_regex_{R"((\w+)\s*(\.\w)?\s*(\$\w+|\#\w+|\w+))"};
|
||||
std::unordered_map<std::string, uint8_t> mnemonic_to_opcode_;
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
56
src/app/emu/debug/debugger.h
Normal file
56
src/app/emu/debug/debugger.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef YAZE_APP_EMU_DEBUG_DEBUGGER_H_
|
||||
#define YAZE_APP_EMU_DEBUG_DEBUGGER_H_
|
||||
|
||||
#include "app/emu/audio/apu.h"
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
#include "app/emu/video/ppu.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class Debugger {
|
||||
public:
|
||||
Debugger() = default;
|
||||
// Attach the debugger to the emulator
|
||||
// Debugger(CPU &cpu, PPU &ppu, APU &apu);
|
||||
|
||||
// Set a breakpoint
|
||||
void SetBreakpoint(uint16_t address);
|
||||
|
||||
// Remove a breakpoint
|
||||
void RemoveBreakpoint(uint16_t address);
|
||||
|
||||
// Step through the code
|
||||
void Step();
|
||||
|
||||
// Inspect memory
|
||||
uint8_t InspectMemory(uint16_t address);
|
||||
|
||||
// Modify memory
|
||||
void ModifyMemory(uint16_t address, uint8_t value);
|
||||
|
||||
// Inspect registers
|
||||
uint8_t InspectRegister(uint8_t reg);
|
||||
|
||||
// Modify registers
|
||||
void ModifyRegister(uint8_t reg, uint8_t value);
|
||||
|
||||
// Handle other debugger tasks
|
||||
// ...
|
||||
|
||||
private:
|
||||
// References to the emulator's components
|
||||
// CPU &cpu;
|
||||
// PPU &ppu;
|
||||
// APU &apu;
|
||||
|
||||
// Breakpoints, watchpoints, etc.
|
||||
// ...
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_DBG_H_
|
||||
43
src/app/emu/debug/log.h
Normal file
43
src/app/emu/debug/log.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef YAZE_APP_EMU_LOG_H_
|
||||
#define YAZE_APP_EMU_LOG_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
// Logger.h
|
||||
class Logger {
|
||||
public:
|
||||
static Logger& GetInstance() {
|
||||
static Logger instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void Log(const std::string& message) const {
|
||||
// Write log messages to a file or console
|
||||
std::cout << message << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
Logger() = default;
|
||||
Logger(const Logger&) = delete;
|
||||
Logger& operator=(const Logger&) = delete;
|
||||
};
|
||||
|
||||
// Loggable.h
|
||||
class Loggable {
|
||||
protected:
|
||||
Logger& logger_ = Logger::GetInstance();
|
||||
|
||||
virtual ~Loggable() = default;
|
||||
virtual void LogMessage(const std::string& message) { logger_.Log(message); }
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_LOG_H_
|
||||
387
src/app/emu/emulator.cc
Normal file
387
src/app/emu/emulator.cc
Normal file
@@ -0,0 +1,387 @@
|
||||
#include "app/emu/emulator.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/constants.h"
|
||||
#include "app/emu/snes.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
namespace {
|
||||
bool ShouldDisplay(const InstructionEntry& entry, const char* filter,
|
||||
bool showAll) {
|
||||
// Implement logic to determine if the entry should be displayed based on the
|
||||
// filter and showAll flag
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
using ImGui::NextColumn;
|
||||
using ImGui::SameLine;
|
||||
using ImGui::Separator;
|
||||
using ImGui::TableNextColumn;
|
||||
using ImGui::Text;
|
||||
|
||||
void Emulator::Run() {
|
||||
if (!snes_.running() && rom()->isLoaded()) {
|
||||
snes_.SetupMemory(*rom());
|
||||
snes_.Init(*rom());
|
||||
}
|
||||
|
||||
RenderNavBar();
|
||||
|
||||
if (running_) {
|
||||
HandleEvents();
|
||||
if (!step_) {
|
||||
snes_.Run();
|
||||
}
|
||||
}
|
||||
|
||||
RenderEmulator();
|
||||
}
|
||||
|
||||
void Emulator::RenderNavBar() {
|
||||
MENU_BAR()
|
||||
if (ImGui::BeginMenu("Options")) {
|
||||
MENU_ITEM("Input") {}
|
||||
MENU_ITEM("Audio") {}
|
||||
MENU_ITEM("Video") {}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
END_MENU_BAR()
|
||||
|
||||
if (ImGui::Button(ICON_MD_PLAY_ARROW)) {
|
||||
loading_ = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Start Emulation");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_PAUSE)) {
|
||||
snes_.SetCpuMode(1);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Pause Emulation");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_SKIP_NEXT)) {
|
||||
// Step through Code logic
|
||||
snes_.StepRun();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Step Through Code");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_REFRESH)) {
|
||||
// Reset Emulator logic
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Reset Emulator");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_STOP)) {
|
||||
// Stop Emulation logic
|
||||
running_ = false;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Stop Emulation");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_SAVE)) {
|
||||
// Save State logic
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Save State");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_SYSTEM_UPDATE_ALT)) {
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Load State");
|
||||
}
|
||||
|
||||
// Additional elements
|
||||
SameLine();
|
||||
if (ImGui::Button(ICON_MD_SETTINGS)) {
|
||||
// Settings logic
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Settings");
|
||||
}
|
||||
|
||||
SameLine();
|
||||
if (ImGui::Button(ICON_MD_INFO)) {
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("About Debugger");
|
||||
}
|
||||
// About Debugger logic
|
||||
}
|
||||
static bool show_memory_viewer = false;
|
||||
|
||||
SameLine();
|
||||
if (ImGui::Button(ICON_MD_MEMORY)) {
|
||||
show_memory_viewer = !show_memory_viewer;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Memory Viewer");
|
||||
}
|
||||
|
||||
if (show_memory_viewer) {
|
||||
ImGui::Begin("Memory Viewer", &show_memory_viewer);
|
||||
RenderMemoryViewer();
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::HandleEvents() {
|
||||
// Handle user input events
|
||||
// ...
|
||||
}
|
||||
|
||||
void Emulator::RenderEmulator() {
|
||||
if (ImGui::BeginTable("##Emulator", 3,
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) {
|
||||
ImGui::TableSetupColumn("CPU");
|
||||
ImGui::TableSetupColumn("PPU");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
TableNextColumn();
|
||||
RenderCpuInstructionLog(snes_.cpu().instruction_log_);
|
||||
|
||||
TableNextColumn();
|
||||
RenderSnesPpu();
|
||||
RenderBreakpointList();
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("##", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
|
||||
RenderCpuState(snes_.cpu());
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::RenderSnesPpu() {
|
||||
ImVec2 size = ImVec2(320, 240);
|
||||
if (snes_.running()) {
|
||||
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 240), true,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x - size.x) * 0.5f);
|
||||
ImGui::SetCursorPosY((ImGui::GetWindowSize().y - size.y) * 0.5f);
|
||||
ImGui::Image((void*)snes_.ppu().GetScreen()->texture(), size, ImVec2(0, 0),
|
||||
ImVec2(1, 1));
|
||||
ImGui::EndChild();
|
||||
|
||||
} else {
|
||||
ImGui::Text("Emulator output not available.");
|
||||
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 240), true,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
|
||||
ImGui::SetCursorPosX(((ImGui::GetWindowSize().x * 0.5f) - size.x) * 0.5f);
|
||||
ImGui::SetCursorPosY(((ImGui::GetWindowSize().y * 0.5f) - size.y) * 0.5f);
|
||||
ImGui::Dummy(size);
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
void Emulator::RenderBreakpointList() {
|
||||
Text("Breakpoints");
|
||||
Separator();
|
||||
static char breakpoint_input[10] = "";
|
||||
static int current_memory_mode = 0;
|
||||
|
||||
static bool read_mode = false;
|
||||
static bool write_mode = false;
|
||||
static bool execute_mode = false;
|
||||
|
||||
if (ImGui::Combo("##TypeOfMemory", ¤t_memory_mode, "PRG\0RAM\0")) {
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Read", &read_mode);
|
||||
SameLine();
|
||||
ImGui::Checkbox("Write", &write_mode);
|
||||
SameLine();
|
||||
ImGui::Checkbox("Execute", &execute_mode);
|
||||
|
||||
// Breakpoint input fields and buttons
|
||||
if (ImGui::InputText("##BreakpointInput", breakpoint_input, 10,
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
int breakpoint = std::stoi(breakpoint_input, nullptr, 16);
|
||||
snes_.cpu().SetBreakpoint(breakpoint);
|
||||
memset(breakpoint_input, 0, sizeof(breakpoint_input));
|
||||
}
|
||||
SameLine();
|
||||
if (ImGui::Button("Add")) {
|
||||
int breakpoint = std::stoi(breakpoint_input, nullptr, 16);
|
||||
snes_.cpu().SetBreakpoint(breakpoint);
|
||||
memset(breakpoint_input, 0, sizeof(breakpoint_input));
|
||||
}
|
||||
SameLine();
|
||||
if (ImGui::Button("Clear")) {
|
||||
snes_.cpu().ClearBreakpoints();
|
||||
}
|
||||
Separator();
|
||||
auto breakpoints = snes_.cpu().GetBreakpoints();
|
||||
if (!breakpoints.empty()) {
|
||||
Text("Breakpoints:");
|
||||
ImGui::BeginChild("BreakpointsList", ImVec2(0, 100), true);
|
||||
for (auto breakpoint : breakpoints) {
|
||||
if (ImGui::Selectable(absl::StrFormat("0x%04X", breakpoint).c_str())) {
|
||||
// Jump to breakpoint
|
||||
// snes_.Cpu().JumpToBreakpoint(breakpoint);
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
Separator();
|
||||
gui::InputHexByte("PB", &manual_pb_, 1, 25.f);
|
||||
gui::InputHexWord("PC", &manual_pc_, 25.f);
|
||||
if (ImGui::Button("Set Current Address")) {
|
||||
snes_.cpu().PC = manual_pc_;
|
||||
snes_.cpu().PB = manual_pb_;
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::RenderCpuState(CPU& cpu) {
|
||||
if (ImGui::CollapsingHeader("Register Values",
|
||||
ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Columns(2, "RegistersColumns");
|
||||
Separator();
|
||||
Text("A: 0x%04X", cpu.A);
|
||||
NextColumn();
|
||||
Text("D: 0x%04X", cpu.D);
|
||||
NextColumn();
|
||||
Text("X: 0x%04X", cpu.X);
|
||||
NextColumn();
|
||||
Text("DB: 0x%02X", cpu.DB);
|
||||
NextColumn();
|
||||
Text("Y: 0x%04X", cpu.Y);
|
||||
NextColumn();
|
||||
Text("PB: 0x%02X", cpu.PB);
|
||||
NextColumn();
|
||||
Text("PC: 0x%04X", cpu.PC);
|
||||
NextColumn();
|
||||
Text("E: %d", cpu.E);
|
||||
NextColumn();
|
||||
ImGui::Columns(1);
|
||||
Separator();
|
||||
}
|
||||
|
||||
// Call Stack
|
||||
if (ImGui::CollapsingHeader("Call Stack", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
// For each return address in the call stack:
|
||||
Text("Return Address: 0x%08X", 0xFFFFFF); // Placeholder
|
||||
}
|
||||
|
||||
snes_.SetCpuMode(0);
|
||||
}
|
||||
|
||||
void Emulator::RenderMemoryViewer() {
|
||||
static MemoryEditor mem_edit;
|
||||
if (ImGui::Button("RAM")) {
|
||||
mem_edit.GotoAddrAndHighlight(0x7E0000, 0x7E0001);
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("MemoryViewerTable", 2,
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) {
|
||||
ImGui::TableSetupColumn("Bookmarks");
|
||||
ImGui::TableSetupColumn("Memory");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::CollapsingHeader("Bookmarks", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
// Input for adding a new bookmark
|
||||
static char nameBuf[256];
|
||||
static uint64_t uint64StringBuf;
|
||||
ImGui::InputText("Name", nameBuf, IM_ARRAYSIZE(nameBuf));
|
||||
gui::InputHex("Address", &uint64StringBuf);
|
||||
if (ImGui::Button("Add Bookmark")) {
|
||||
bookmarks.push_back({nameBuf, uint64StringBuf});
|
||||
memset(nameBuf, 0, sizeof(nameBuf));
|
||||
uint64StringBuf = 0;
|
||||
}
|
||||
|
||||
// Tree view of bookmarks
|
||||
for (const auto& bookmark : bookmarks) {
|
||||
if (ImGui::TreeNode(bookmark.name.c_str(), ICON_MD_STAR)) {
|
||||
auto bookmark_string = absl::StrFormat(
|
||||
"%s: 0x%08X", bookmark.name.c_str(), bookmark.value);
|
||||
if (ImGui::Selectable(bookmark_string.c_str())) {
|
||||
mem_edit.GotoAddrAndHighlight(static_cast<ImU64>(bookmark.value),
|
||||
1);
|
||||
}
|
||||
SameLine();
|
||||
if (ImGui::Button("Delete")) {
|
||||
// Logic to delete the bookmark
|
||||
bookmarks.erase(std::remove_if(bookmarks.begin(), bookmarks.end(),
|
||||
[&](const Bookmark& b) {
|
||||
return b.name == bookmark.name &&
|
||||
b.value == bookmark.value;
|
||||
}),
|
||||
bookmarks.end());
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
mem_edit.DrawContents((void*)snes_.Memory()->data(),
|
||||
snes_.Memory()->size());
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::RenderCpuInstructionLog(
|
||||
const std::vector<InstructionEntry>& instructionLog) {
|
||||
if (ImGui::CollapsingHeader("CPU Instruction Log")) {
|
||||
// Filtering options
|
||||
static char filterBuf[256];
|
||||
ImGui::InputText("Filter", filterBuf, IM_ARRAYSIZE(filterBuf));
|
||||
SameLine();
|
||||
if (ImGui::Button("Clear")) { /* Clear filter logic */
|
||||
}
|
||||
|
||||
// Toggle for showing all opcodes
|
||||
static bool showAllOpcodes = true;
|
||||
ImGui::Checkbox("Show All Opcodes", &showAllOpcodes);
|
||||
|
||||
// Instruction list
|
||||
ImGui::BeginChild("InstructionList", ImVec2(0, 0),
|
||||
ImGuiChildFlags_None);
|
||||
for (const auto& entry : instructionLog) {
|
||||
if (ShouldDisplay(entry, filterBuf, showAllOpcodes)) {
|
||||
if (ImGui::Selectable(
|
||||
absl::StrFormat("%06X: %s %s", entry.address,
|
||||
opcode_to_mnemonic.at(entry.opcode),
|
||||
entry.operands)
|
||||
.c_str())) {
|
||||
// Logic to handle click (e.g., jump to address, set breakpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
53
src/app/emu/emulator.h
Normal file
53
src/app/emu/emulator.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef YAZE_APP_CORE_EMULATOR_H
|
||||
#define YAZE_APP_CORE_EMULATOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/snes.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class Emulator : public SharedROM {
|
||||
public:
|
||||
void Run();
|
||||
|
||||
private:
|
||||
void RenderNavBar();
|
||||
void HandleEvents();
|
||||
|
||||
void RenderEmulator();
|
||||
void RenderSnesPpu();
|
||||
void RenderBreakpointList();
|
||||
void RenderCpuState(CPU& cpu);
|
||||
void RenderMemoryViewer();
|
||||
|
||||
struct Bookmark {
|
||||
std::string name;
|
||||
uint64_t value;
|
||||
};
|
||||
std::vector<Bookmark> bookmarks;
|
||||
|
||||
void RenderCpuInstructionLog(
|
||||
const std::vector<InstructionEntry>& instructionLog);
|
||||
|
||||
SNES snes_;
|
||||
uint16_t manual_pc_ = 0;
|
||||
uint8_t manual_pb_ = 0;
|
||||
|
||||
bool power_ = false;
|
||||
bool loading_ = false;
|
||||
bool running_ = false;
|
||||
bool step_ = true;
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_CORE_EMULATOR_H
|
||||
75
src/app/emu/memory/dma.cc
Normal file
75
src/app/emu/memory/dma.cc
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "app/emu/memory/dma.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void DMA::StartDMATransfer(uint8_t channelMask) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if ((channelMask & (1 << i)) != 0) {
|
||||
Channel& ch = channels[i];
|
||||
|
||||
// Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, DASn)
|
||||
// ...
|
||||
|
||||
// Determine the transfer direction based on the DMAPn register
|
||||
bool fromMemory = (ch.DMAPn & 0x80) != 0;
|
||||
|
||||
// Determine the transfer size based on the DMAPn register
|
||||
bool transferTwoBytes = (ch.DMAPn & 0x40) != 0;
|
||||
|
||||
// Perform the DMA transfer based on the channel parameters
|
||||
std::cout << "Starting DMA transfer for channel " << i << std::endl;
|
||||
|
||||
for (uint16_t j = 0; j < ch.DASn; ++j) {
|
||||
// Read a byte or two bytes from memory based on the transfer size
|
||||
// ...
|
||||
|
||||
// Write the data to the B-bus address (BBADn) if transferring from
|
||||
// memory
|
||||
// ...
|
||||
|
||||
// Update the A1Tn register based on the transfer direction
|
||||
if (fromMemory) {
|
||||
ch.A1Tn += transferTwoBytes ? 2 : 1;
|
||||
} else {
|
||||
ch.A1Tn -= transferTwoBytes ? 2 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the channel registers after the transfer (e.g., A1Tn, DASn)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
MDMAEN = channelMask; // Set the MDMAEN register to the channel mask
|
||||
}
|
||||
|
||||
void DMA::EnableHDMATransfers(uint8_t channelMask) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if ((channelMask & (1 << i)) != 0) {
|
||||
Channel& ch = channels[i];
|
||||
|
||||
// Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, A2An, NLTRn)
|
||||
// ...
|
||||
|
||||
// Perform the HDMA setup based on the channel parameters
|
||||
std::cout << "Enabling HDMA transfer for channel " << i << std::endl;
|
||||
|
||||
// Read the HDMA table from memory starting at A1Tn
|
||||
// ...
|
||||
|
||||
// Update the A2An register based on the HDMA table
|
||||
// ...
|
||||
|
||||
// Update the NLTRn register based on the HDMA table
|
||||
// ...
|
||||
}
|
||||
}
|
||||
HDMAEN = channelMask; // Set the HDMAEN register to the channel mask
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
59
src/app/emu/memory/dma.h
Normal file
59
src/app/emu/memory/dma.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef YAZE_APP_EMU_MEMORY_DMA_H
|
||||
#define YAZE_APP_EMU_MEMORY_DMA_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
// Direct Memory Address
|
||||
class DMA {
|
||||
public:
|
||||
DMA() {
|
||||
// Initialize DMA and HDMA channels
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
channels[i].DMAPn = 0;
|
||||
channels[i].BBADn = 0;
|
||||
channels[i].UNUSEDn = 0;
|
||||
channels[i].A1Tn = 0xFFFFFF;
|
||||
channels[i].DASn = 0xFFFF;
|
||||
channels[i].A2An = 0xFFFF;
|
||||
channels[i].NLTRn = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
// DMA Transfer Modes
|
||||
enum class DMA_TRANSFER_TYPE {
|
||||
OAM,
|
||||
PPUDATA,
|
||||
CGDATA,
|
||||
FILL_VRAM,
|
||||
CLEAR_VRAM,
|
||||
RESET_VRAM
|
||||
};
|
||||
|
||||
// Functions for handling DMA and HDMA transfers
|
||||
void StartDMATransfer(uint8_t channels);
|
||||
void EnableHDMATransfers(uint8_t channels);
|
||||
|
||||
// Structure for DMA and HDMA channel registers
|
||||
struct Channel {
|
||||
uint8_t DMAPn; // DMA/HDMA parameters
|
||||
uint8_t BBADn; // B-bus address
|
||||
uint8_t UNUSEDn; // Unused byte
|
||||
uint32_t A1Tn; // DMA Current Address / HDMA Table Start Address
|
||||
uint16_t DASn; // DMA Byte-Counter / HDMA indirect table address
|
||||
uint16_t A2An; // HDMA Table Current Address
|
||||
uint8_t NLTRn; // HDMA Line-Counter
|
||||
};
|
||||
Channel channels[8];
|
||||
|
||||
uint8_t MDMAEN = 0; // Start DMA transfer
|
||||
uint8_t HDMAEN = 0; // Enable HDMA transfers
|
||||
};
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_MEMORY_DMA_H
|
||||
82
src/app/emu/memory/memory.cc
Normal file
82
src/app/emu/memory/memory.cc
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/debug/log.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void DrawSnesMemoryMapping(const MemoryImpl& memory) {
|
||||
// Using those as a base value to create width/height that are factor of the
|
||||
// size of our font
|
||||
const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x;
|
||||
const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
|
||||
const char* column_names[] = {
|
||||
"Offset", "0x00", "0x01", "0x02", "0x03", "0x04", "0x05", "0x06", "0x07",
|
||||
"0x08", "0x09", "0x0A", "0x0B", "0x0C", "0x0D", "0x0E", "0x0F", "0x10",
|
||||
"0x11", "0x12", "0x13", "0x14", "0x15", "0x16", "0x17", "0x18", "0x19",
|
||||
"0x1A", "0x1B", "0x1C", "0x1D", "0x1E", "0x1F"};
|
||||
const int columns_count = IM_ARRAYSIZE(column_names);
|
||||
const int rows_count = 16;
|
||||
|
||||
static ImGuiTableFlags table_flags =
|
||||
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX |
|
||||
ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter |
|
||||
ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_Hideable |
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_HighlightHoveredColumn;
|
||||
static bool bools[columns_count * rows_count] = {};
|
||||
static int frozen_cols = 1;
|
||||
static int frozen_rows = 2;
|
||||
ImGui::CheckboxFlags("_ScrollX", &table_flags, ImGuiTableFlags_ScrollX);
|
||||
ImGui::CheckboxFlags("_ScrollY", &table_flags, ImGuiTableFlags_ScrollY);
|
||||
ImGui::CheckboxFlags("_NoBordersInBody", &table_flags,
|
||||
ImGuiTableFlags_NoBordersInBody);
|
||||
ImGui::CheckboxFlags("_HighlightHoveredColumn", &table_flags,
|
||||
ImGuiTableFlags_HighlightHoveredColumn);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
ImGui::SliderInt("Frozen columns", &frozen_cols, 0, 2);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
ImGui::SliderInt("Frozen rows", &frozen_rows, 0, 2);
|
||||
|
||||
if (ImGui::BeginTable("table_angled_headers", columns_count, table_flags,
|
||||
ImVec2(0.0f, TEXT_BASE_HEIGHT * 12))) {
|
||||
ImGui::TableSetupColumn(
|
||||
column_names[0],
|
||||
ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_NoReorder);
|
||||
for (int n = 1; n < columns_count; n++)
|
||||
ImGui::TableSetupColumn(column_names[n],
|
||||
ImGuiTableColumnFlags_AngledHeader |
|
||||
ImGuiTableColumnFlags_WidthFixed);
|
||||
ImGui::TableSetupScrollFreeze(frozen_cols, frozen_rows);
|
||||
|
||||
ImGui::TableAngledHeadersRow();
|
||||
ImGui::TableHeadersRow();
|
||||
for (int row = 0; row < rows_count; row++) {
|
||||
ImGui::PushID(row);
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("Offset 0x%04X", row);
|
||||
for (int column = 1; column < columns_count; column++)
|
||||
if (ImGui::TableSetColumnIndex(column)) {
|
||||
ImGui::PushID(column);
|
||||
ImGui::Checkbox("", &bools[row * columns_count + column]);
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
403
src/app/emu/memory/memory.h
Normal file
403
src/app/emu/memory/memory.h
Normal file
@@ -0,0 +1,403 @@
|
||||
#ifndef MEM_H
|
||||
#define MEM_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/debug/log.h"
|
||||
|
||||
// LoROM (Mode 20):
|
||||
|
||||
// Banks Offset Purpose
|
||||
// 00-3F 0000-1FFF LowRAM (shadowed from 7E)
|
||||
// 2000-2FFF PPU1, APU
|
||||
// 3000-3FFF SFX, DSP, etc.
|
||||
// 4000-41FF Controller
|
||||
// 4200-5FFF PPU2, DMA, etc.
|
||||
// 6000-7FFF Expansion RAM (reserved)
|
||||
// 8000-FFFF 32k ROM Chunk
|
||||
// 40-7C 0000-7FFF 32k ROM Chunk
|
||||
// 8000-FFFF 32k ROM Chunk
|
||||
// 7D 0000-FFFF SRAM
|
||||
// 7E 0000-1FFF LowRAM
|
||||
// 2000-FFFF System RAM
|
||||
// 7F 0000-FFFF System RAM
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
enum ROMSpeed { SLOW_ROM = 0x00, FAST_ROM = 0x07 };
|
||||
|
||||
enum BankSize { LOW_ROM = 0x00, HI_ROM = 0x01 };
|
||||
|
||||
enum ROMType {
|
||||
ROM_DEFAULT = 0x00,
|
||||
ROM_RAM = 0x01,
|
||||
ROM_SRAM = 0x02,
|
||||
ROM_DSP1 = 0x03,
|
||||
ROM_DSP1_RAM = 0x04,
|
||||
ROM_DSP1_SRAM = 0x05,
|
||||
FX = 0x06
|
||||
};
|
||||
|
||||
enum ROMSize {
|
||||
SIZE_2_MBIT = 0x08,
|
||||
SIZE_4_MBIT = 0x09,
|
||||
SIZE_8_MBIT = 0x0A,
|
||||
SIZE_16_MBIT = 0x0B,
|
||||
SIZE_32_MBIT = 0x0C
|
||||
};
|
||||
|
||||
enum SRAMSize {
|
||||
NO_SRAM = 0x00,
|
||||
SRAM_16_KBIT = 0x01,
|
||||
SRAM_32_KBIT = 0x02,
|
||||
SRAM_64_KBIT = 0x03
|
||||
};
|
||||
|
||||
enum CountryCode {
|
||||
JAPAN = 0x00,
|
||||
USA = 0x01,
|
||||
EUROPE_OCEANIA_ASIA = 0x02,
|
||||
// ... and other countries
|
||||
};
|
||||
|
||||
enum License {
|
||||
INVALID = 0,
|
||||
NINTENDO = 1,
|
||||
ZAMUSE = 5,
|
||||
CAPCOM = 8,
|
||||
// ... and other licenses
|
||||
};
|
||||
|
||||
class ROMInfo {
|
||||
public:
|
||||
std::string title;
|
||||
ROMSpeed romSpeed;
|
||||
BankSize bankSize;
|
||||
ROMType romType;
|
||||
ROMSize romSize;
|
||||
SRAMSize sramSize;
|
||||
CountryCode countryCode;
|
||||
License license;
|
||||
uint8_t version;
|
||||
uint16_t checksumComplement;
|
||||
uint16_t checksum;
|
||||
uint16_t nmiVblVector;
|
||||
uint16_t resetVector;
|
||||
};
|
||||
|
||||
class Observer {
|
||||
public:
|
||||
virtual ~Observer() = default;
|
||||
virtual void Notify(uint32_t address, uint8_t data) = 0;
|
||||
};
|
||||
|
||||
constexpr uint32_t kROMStart = 0x008000;
|
||||
constexpr uint32_t kROMSize = 0x200000;
|
||||
constexpr uint32_t kRAMStart = 0x7E0000;
|
||||
constexpr uint32_t kRAMSize = 0x20000;
|
||||
constexpr uint32_t kVRAMStart = 0x210000;
|
||||
constexpr uint32_t kVRAMSize = 0x10000;
|
||||
constexpr uint32_t kOAMStart = 0x218000;
|
||||
constexpr uint32_t kOAMSize = 0x220;
|
||||
|
||||
// memory.h
|
||||
class Memory {
|
||||
public:
|
||||
virtual ~Memory() = default;
|
||||
virtual uint8_t ReadByte(uint32_t address) const = 0;
|
||||
virtual uint16_t ReadWord(uint32_t address) const = 0;
|
||||
virtual uint32_t ReadWordLong(uint32_t address) const = 0;
|
||||
virtual std::vector<uint8_t> ReadByteVector(uint32_t address,
|
||||
uint16_t length) const = 0;
|
||||
|
||||
virtual void WriteByte(uint32_t address, uint8_t value) = 0;
|
||||
virtual void WriteWord(uint32_t address, uint16_t value) = 0;
|
||||
virtual void WriteLong(uint32_t address, uint32_t value) = 0;
|
||||
|
||||
virtual void PushByte(uint8_t value) = 0;
|
||||
virtual uint8_t PopByte() = 0;
|
||||
virtual void PushWord(uint16_t value) = 0;
|
||||
virtual uint16_t PopWord() = 0;
|
||||
virtual void PushLong(uint32_t value) = 0;
|
||||
virtual uint32_t PopLong() = 0;
|
||||
|
||||
virtual uint16_t SP() const = 0;
|
||||
virtual void SetSP(uint16_t value) = 0;
|
||||
|
||||
virtual void ClearMemory() = 0;
|
||||
|
||||
virtual uint8_t operator[](int i) const = 0;
|
||||
virtual uint8_t at(int i) const = 0;
|
||||
};
|
||||
|
||||
enum class MemoryMapping { SNES_LOROM = 0, PC_ADDRESS = 1 };
|
||||
|
||||
class MemoryImpl : public Memory, public Loggable {
|
||||
public:
|
||||
void Initialize(const std::vector<uint8_t>& romData, bool verbose = false,
|
||||
MemoryMapping mapping = MemoryMapping::SNES_LOROM) {
|
||||
verbose_ = verbose;
|
||||
mapping_ = mapping;
|
||||
if (mapping == MemoryMapping::PC_ADDRESS) {
|
||||
memory_.resize(romData.size());
|
||||
std::copy(romData.begin(), romData.end(), memory_.begin());
|
||||
return;
|
||||
}
|
||||
|
||||
memory_.resize(0x1000000); // 16 MB
|
||||
|
||||
const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB
|
||||
const size_t SRAM_SIZE = 0x10000; // 64 KB
|
||||
const size_t SYSTEM_RAM_SIZE = 0x20000; // 128 KB
|
||||
const size_t EXPANSION_RAM_SIZE = 0x2000; // 8 KB
|
||||
const size_t HARDWARE_REGISTERS_SIZE = 0x4000; // 16 KB
|
||||
|
||||
// Clear memory
|
||||
std::fill(memory_.begin(), memory_.end(), 0);
|
||||
|
||||
// Load ROM data into memory based on LoROM mapping
|
||||
size_t romSize = romData.size();
|
||||
size_t romAddress = 0;
|
||||
for (size_t bank = 0x00; bank <= 0x3F; ++bank) {
|
||||
for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) {
|
||||
if (romAddress < romSize) {
|
||||
std::copy(romData.begin() + romAddress,
|
||||
romData.begin() + romAddress + ROM_CHUNK_SIZE,
|
||||
memory_.begin() + (bank << 16) + offset);
|
||||
romAddress += ROM_CHUNK_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize SRAM at banks 0x7D and 0xFD
|
||||
std::fill(memory_.begin() + (0x7D << 16), memory_.begin() + (0x7E << 16),
|
||||
0);
|
||||
std::fill(memory_.begin() + (0xFD << 16), memory_.begin() + (0xFE << 16),
|
||||
0);
|
||||
|
||||
// Initialize System RAM at banks 0x7E and 0x7F
|
||||
std::fill(memory_.begin() + (0x7E << 16),
|
||||
memory_.begin() + (0x7E << 16) + SYSTEM_RAM_SIZE, 0);
|
||||
|
||||
// Initialize Shadow RAM at banks 0x00-0x3F and 0x80-0xBF
|
||||
for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) {
|
||||
std::fill(memory_.begin() + (bank << 16),
|
||||
memory_.begin() + (bank << 16) + 0x2000, 0);
|
||||
}
|
||||
|
||||
// Initialize Hardware Registers at banks 0x00-0x3F and 0x80-0xBF
|
||||
for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) {
|
||||
std::fill(
|
||||
memory_.begin() + (bank << 16) + 0x2000,
|
||||
memory_.begin() + (bank << 16) + 0x2000 + HARDWARE_REGISTERS_SIZE, 0);
|
||||
}
|
||||
|
||||
// Initialize Expansion RAM at banks 0x00-0x3F and 0x80-0xBF
|
||||
for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) {
|
||||
std::fill(memory_.begin() + (bank << 16) + 0x6000,
|
||||
memory_.begin() + (bank << 16) + 0x6000 + EXPANSION_RAM_SIZE,
|
||||
0);
|
||||
}
|
||||
|
||||
// Initialize Reset and NMI Vectors at bank 0xFF
|
||||
std::fill(memory_.begin() + (0xFF << 16) + 0xFF00,
|
||||
memory_.begin() + (0xFF << 16) + 0xFFFF + 1, 0);
|
||||
|
||||
// Copy data into rom_ vector
|
||||
rom_.resize(kROMSize);
|
||||
std::copy(memory_.begin() + kROMStart,
|
||||
memory_.begin() + kROMStart + kROMSize, rom_.begin());
|
||||
|
||||
// Copy data into ram_ vector
|
||||
ram_.resize(kRAMSize);
|
||||
std::copy(memory_.begin() + kRAMStart,
|
||||
memory_.begin() + kRAMStart + kRAMSize, ram_.begin());
|
||||
|
||||
// Copy data into vram_ vector
|
||||
vram_.resize(kVRAMSize);
|
||||
std::copy(memory_.begin() + kVRAMStart,
|
||||
memory_.begin() + kVRAMStart + kVRAMSize, vram_.begin());
|
||||
|
||||
// Copy data into oam_ vector
|
||||
oam_.resize(kOAMSize);
|
||||
std::copy(memory_.begin() + kOAMStart,
|
||||
memory_.begin() + kOAMStart + kOAMSize, oam_.begin());
|
||||
}
|
||||
|
||||
uint8_t ReadByte(uint32_t address) const override {
|
||||
uint32_t mapped_address = GetMappedAddress(address);
|
||||
NotifyObservers(mapped_address, /*data=*/0);
|
||||
return memory_.at(mapped_address);
|
||||
}
|
||||
uint16_t ReadWord(uint32_t address) const override {
|
||||
uint32_t mapped_address = GetMappedAddress(address);
|
||||
NotifyObservers(mapped_address, /*data=*/0);
|
||||
return static_cast<uint16_t>(memory_.at(mapped_address)) |
|
||||
(static_cast<uint16_t>(memory_.at(mapped_address + 1)) << 8);
|
||||
}
|
||||
uint32_t ReadWordLong(uint32_t address) const override {
|
||||
uint32_t mapped_address = GetMappedAddress(address);
|
||||
NotifyObservers(mapped_address, /*data=*/0);
|
||||
return static_cast<uint32_t>(memory_.at(mapped_address)) |
|
||||
(static_cast<uint32_t>(memory_.at(mapped_address + 1)) << 8) |
|
||||
(static_cast<uint32_t>(memory_.at(mapped_address + 2)) << 16);
|
||||
}
|
||||
std::vector<uint8_t> ReadByteVector(uint32_t address,
|
||||
uint16_t length) const override {
|
||||
uint32_t mapped_address = GetMappedAddress(address);
|
||||
NotifyObservers(mapped_address, /*data=*/0);
|
||||
return std::vector<uint8_t>(memory_.begin() + mapped_address,
|
||||
memory_.begin() + mapped_address + length);
|
||||
}
|
||||
|
||||
void WriteByte(uint32_t address, uint8_t value) override {
|
||||
uint32_t mapped_address = GetMappedAddress(address);
|
||||
memory_[mapped_address] = value;
|
||||
}
|
||||
void WriteWord(uint32_t address, uint16_t value) override {
|
||||
uint32_t mapped_address = GetMappedAddress(address);
|
||||
memory_.at(mapped_address) = value & 0xFF;
|
||||
memory_.at(mapped_address + 1) = (value >> 8) & 0xFF;
|
||||
}
|
||||
void WriteLong(uint32_t address, uint32_t value) override {
|
||||
uint32_t mapped_address = GetMappedAddress(address);
|
||||
memory_.at(mapped_address) = value & 0xFF;
|
||||
memory_.at(mapped_address + 1) = (value >> 8) & 0xFF;
|
||||
memory_.at(mapped_address + 2) = (value >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
// Stack operations
|
||||
void PushByte(uint8_t value) override {
|
||||
if (SP_ > 0x0100) {
|
||||
memory_.at(SP_--) = value;
|
||||
} else {
|
||||
// Handle stack underflow
|
||||
std::cout << "Stack underflow!" << std::endl;
|
||||
throw std::runtime_error("Stack underflow!");
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PopByte() override {
|
||||
if (SP_ < 0x1FF) {
|
||||
return memory_.at(++SP_);
|
||||
} else {
|
||||
// Handle stack overflow
|
||||
std::cout << "Stack overflow!" << std::endl;
|
||||
throw std::runtime_error("Stack overflow!");
|
||||
}
|
||||
}
|
||||
|
||||
void PushWord(uint16_t value) override {
|
||||
PushByte(value >> 8);
|
||||
PushByte(value & 0xFF);
|
||||
}
|
||||
|
||||
uint16_t PopWord() override {
|
||||
uint8_t low = PopByte();
|
||||
uint8_t high = PopByte();
|
||||
return (static_cast<uint16_t>(high) << 8) | low;
|
||||
}
|
||||
|
||||
void PushLong(uint32_t value) override {
|
||||
PushByte(value >> 16);
|
||||
PushByte(value >> 8);
|
||||
PushByte(value & 0xFF);
|
||||
}
|
||||
|
||||
uint32_t PopLong() override {
|
||||
uint8_t low = PopByte();
|
||||
uint8_t mid = PopByte();
|
||||
uint8_t high = PopByte();
|
||||
return (static_cast<uint32_t>(high) << 16) |
|
||||
(static_cast<uint32_t>(mid) << 8) | low;
|
||||
}
|
||||
|
||||
void AddObserver(Observer* observer) { observers_.push_back(observer); }
|
||||
|
||||
// Stack Pointer access.
|
||||
uint16_t SP() const override { return SP_; }
|
||||
void SetSP(uint16_t value) override { SP_ = value; }
|
||||
void ClearMemory() override { std::fill(memory_.begin(), memory_.end(), 0); }
|
||||
|
||||
uint8_t at(int i) const override { return memory_[i]; }
|
||||
uint8_t operator[](int i) const override {
|
||||
if (i > memory_.size()) {
|
||||
std::cout << i << " out of bounds \n";
|
||||
return memory_[0];
|
||||
}
|
||||
return memory_[i];
|
||||
}
|
||||
|
||||
auto size() const { return memory_.size(); }
|
||||
auto begin() const { return memory_.begin(); }
|
||||
auto end() const { return memory_.end(); }
|
||||
auto data() const { return memory_.data(); }
|
||||
|
||||
// Define memory regions
|
||||
std::vector<uint8_t> rom_;
|
||||
std::vector<uint8_t> ram_;
|
||||
std::vector<uint8_t> vram_;
|
||||
std::vector<uint8_t> oam_;
|
||||
|
||||
private:
|
||||
uint32_t GetMappedAddress(uint32_t address) const {
|
||||
uint8_t bank = address >> 16;
|
||||
uint32_t offset = address & 0xFFFF;
|
||||
|
||||
if (mapping_ == MemoryMapping::PC_ADDRESS) {
|
||||
return address;
|
||||
}
|
||||
|
||||
if (bank <= 0x3F) {
|
||||
if (address <= 0x1FFF) {
|
||||
return (0x7E << 16) + offset; // Shadow RAM
|
||||
} else if (address <= 0x5FFF) {
|
||||
return (bank << 16) + (offset - 0x2000) + 0x2000; // Hardware Registers
|
||||
} else if (address <= 0x7FFF) {
|
||||
return offset - 0x6000 + 0x6000; // Expansion RAM
|
||||
} else {
|
||||
// Return lorom mapping
|
||||
return (bank << 16) + (offset - 0x8000) + 0x8000; // ROM
|
||||
}
|
||||
} else if (bank == 0x7D) {
|
||||
return offset + 0x7D0000; // SRAM
|
||||
} else if (bank == 0x7E || bank == 0x7F) {
|
||||
return offset + 0x7E0000; // System RAM
|
||||
} else if (bank >= 0x80) {
|
||||
// Handle HiROM and mirrored areas
|
||||
}
|
||||
|
||||
return address; // Return the original address if no mapping is defined
|
||||
}
|
||||
|
||||
void NotifyObservers(uint32_t address, uint8_t data) const {
|
||||
for (auto observer : observers_) {
|
||||
observer->Notify(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
bool verbose_ = false;
|
||||
|
||||
std::vector<Observer*> observers_;
|
||||
|
||||
// Memory (64KB)
|
||||
std::vector<uint8_t> memory_;
|
||||
|
||||
// Stack Pointer
|
||||
uint16_t SP_ = 0x01FF;
|
||||
|
||||
MemoryMapping mapping_ = MemoryMapping::SNES_LOROM;
|
||||
};
|
||||
|
||||
void DrawSnesMemoryMapping(const MemoryImpl& memory);
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // MEM_H
|
||||
189
src/app/emu/memory/mock_memory.h
Normal file
189
src/app/emu/memory/mock_memory.h
Normal file
@@ -0,0 +1,189 @@
|
||||
#ifndef YAZE_TEST_MOCK_MOCK_MEMORY_H
|
||||
#define YAZE_TEST_MOCK_MOCK_MEMORY_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
using yaze::app::emu::Clock;
|
||||
using yaze::app::emu::CPU;
|
||||
using yaze::app::emu::Memory;
|
||||
|
||||
class MockClock : public Clock {
|
||||
public:
|
||||
MOCK_METHOD(void, UpdateClock, (double delta), (override));
|
||||
MOCK_METHOD(unsigned long long, GetCycleCount, (), (const, override));
|
||||
MOCK_METHOD(void, ResetAccumulatedTime, (), (override));
|
||||
MOCK_METHOD(void, SetFrequency, (float new_frequency), (override));
|
||||
MOCK_METHOD(float, GetFrequency, (), (const, override));
|
||||
};
|
||||
|
||||
// 0x1000000 is 16 MB, simplifying the memory layout for testing
|
||||
// 2 MB is = 0x200000
|
||||
|
||||
class MockMemory : public Memory {
|
||||
public:
|
||||
MOCK_CONST_METHOD1(ReadByte, uint8_t(uint32_t address));
|
||||
MOCK_CONST_METHOD1(ReadWord, uint16_t(uint32_t address));
|
||||
MOCK_CONST_METHOD1(ReadWordLong, uint32_t(uint32_t address));
|
||||
MOCK_METHOD(std::vector<uint8_t>, ReadByteVector,
|
||||
(uint32_t address, uint16_t length), (const, override));
|
||||
|
||||
MOCK_METHOD2(WriteByte, void(uint32_t address, uint8_t value));
|
||||
MOCK_METHOD2(WriteWord, void(uint32_t address, uint16_t value));
|
||||
MOCK_METHOD2(WriteLong, void(uint32_t address, uint32_t value));
|
||||
|
||||
MOCK_METHOD1(PushByte, void(uint8_t value));
|
||||
MOCK_METHOD0(PopByte, uint8_t());
|
||||
MOCK_METHOD1(PushWord, void(uint16_t value));
|
||||
MOCK_METHOD0(PopWord, uint16_t());
|
||||
MOCK_METHOD1(PushLong, void(uint32_t value));
|
||||
MOCK_METHOD0(PopLong, uint32_t());
|
||||
|
||||
MOCK_CONST_METHOD0(SP, uint16_t());
|
||||
MOCK_METHOD1(SetSP, void(uint16_t value));
|
||||
|
||||
MOCK_METHOD1(SetMemory, void(const std::vector<uint8_t>& data));
|
||||
MOCK_METHOD1(LoadData, void(const std::vector<uint8_t>& data));
|
||||
|
||||
MOCK_METHOD0(ClearMemory, void());
|
||||
|
||||
MOCK_CONST_METHOD1(at, uint8_t(int i));
|
||||
uint8_t operator[](int i) const override { return memory_[i]; }
|
||||
|
||||
void SetMemoryContents(const std::vector<uint8_t>& data) {
|
||||
if (data.size() > memory_.size()) {
|
||||
memory_.resize(data.size());
|
||||
}
|
||||
std::copy(data.begin(), data.end(), memory_.begin());
|
||||
}
|
||||
|
||||
void SetMemoryContents(const std::vector<uint16_t>& data) {
|
||||
if (data.size() > memory_.size()) {
|
||||
memory_.resize(data.size());
|
||||
}
|
||||
int i = 0;
|
||||
for (const auto& each : data) {
|
||||
memory_[i] = each & 0xFF;
|
||||
memory_[i + 1] = (each >> 8) & 0xFF;
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void InsertMemory(const uint64_t address, const std::vector<uint8_t>& data) {
|
||||
if (address > memory_.size()) {
|
||||
memory_.resize(address + data.size());
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (const auto& each : data) {
|
||||
memory_[address + i] = each;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize(const std::vector<uint8_t>& romData) {
|
||||
// 16 MB, simplifying the memory layout for testing
|
||||
memory_.resize(0x1000000);
|
||||
|
||||
// Clear memory
|
||||
std::fill(memory_.begin(), memory_.end(), 0);
|
||||
|
||||
// Load ROM data into mock memory
|
||||
size_t romSize = romData.size();
|
||||
size_t romAddress = 0;
|
||||
const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB
|
||||
for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) {
|
||||
for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) {
|
||||
if (romAddress < romSize) {
|
||||
std::copy(romData.begin() + romAddress,
|
||||
romData.begin() + romAddress + ROM_CHUNK_SIZE,
|
||||
memory_.begin() + (bank << 16) + offset);
|
||||
romAddress += ROM_CHUNK_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Init() {
|
||||
ON_CALL(*this, ReadByte(::testing::_))
|
||||
.WillByDefault(
|
||||
[this](uint32_t address) { return memory_.at(address); });
|
||||
ON_CALL(*this, ReadWord(::testing::_))
|
||||
.WillByDefault([this](uint32_t address) {
|
||||
return static_cast<uint16_t>(memory_.at(address)) |
|
||||
(static_cast<uint16_t>(memory_.at(address + 1)) << 8);
|
||||
});
|
||||
ON_CALL(*this, ReadWordLong(::testing::_))
|
||||
.WillByDefault([this](uint32_t address) {
|
||||
return static_cast<uint32_t>(memory_.at(address)) |
|
||||
(static_cast<uint32_t>(memory_.at(address + 1)) << 8) |
|
||||
(static_cast<uint32_t>(memory_.at(address + 2)) << 16);
|
||||
});
|
||||
ON_CALL(*this, ReadByteVector(::testing::_, ::testing::_))
|
||||
.WillByDefault([this](uint32_t address, uint16_t length) {
|
||||
std::vector<uint8_t> data;
|
||||
for (int i = 0; i < length; i++) {
|
||||
data.push_back(memory_.at(address + i));
|
||||
}
|
||||
return data;
|
||||
});
|
||||
ON_CALL(*this, WriteByte(::testing::_, ::testing::_))
|
||||
.WillByDefault([this](uint32_t address, uint8_t value) {
|
||||
memory_[address] = value;
|
||||
});
|
||||
ON_CALL(*this, WriteWord(::testing::_, ::testing::_))
|
||||
.WillByDefault([this](uint32_t address, uint16_t value) {
|
||||
memory_[address] = value & 0xFF;
|
||||
memory_[address + 1] = (value >> 8) & 0xFF;
|
||||
});
|
||||
ON_CALL(*this, PushByte(::testing::_)).WillByDefault([this](uint8_t value) {
|
||||
memory_.at(SP_--) = value;
|
||||
});
|
||||
ON_CALL(*this, PopByte()).WillByDefault([this]() {
|
||||
uint8_t value = memory_.at(SP_);
|
||||
this->SetSP(SP_ + 1);
|
||||
return value;
|
||||
});
|
||||
ON_CALL(*this, PushWord(::testing::_))
|
||||
.WillByDefault([this](uint16_t value) {
|
||||
memory_.at(SP_) = value & 0xFF;
|
||||
memory_.at(SP_ + 1) = (value >> 8) & 0xFF;
|
||||
this->SetSP(SP_ - 2);
|
||||
});
|
||||
ON_CALL(*this, PopWord()).WillByDefault([this]() {
|
||||
uint16_t value = static_cast<uint16_t>(memory_.at(SP_)) |
|
||||
(static_cast<uint16_t>(memory_.at(SP_ + 1)) << 8);
|
||||
this->SetSP(SP_ + 2);
|
||||
return value;
|
||||
});
|
||||
ON_CALL(*this, PushLong(::testing::_))
|
||||
.WillByDefault([this](uint32_t value) {
|
||||
memory_.at(SP_) = value & 0xFF;
|
||||
memory_.at(SP_ + 1) = (value >> 8) & 0xFF;
|
||||
memory_.at(SP_ + 2) = (value >> 16) & 0xFF;
|
||||
});
|
||||
ON_CALL(*this, PopLong()).WillByDefault([this]() {
|
||||
uint32_t value = static_cast<uint32_t>(memory_.at(SP_)) |
|
||||
(static_cast<uint32_t>(memory_.at(SP_ + 1)) << 8) |
|
||||
(static_cast<uint32_t>(memory_.at(SP_ + 2)) << 16);
|
||||
this->SetSP(SP_ + 3);
|
||||
return value;
|
||||
});
|
||||
ON_CALL(*this, SP()).WillByDefault([this]() { return SP_; });
|
||||
ON_CALL(*this, SetSP(::testing::_)).WillByDefault([this](uint16_t value) {
|
||||
SP_ = value;
|
||||
});
|
||||
ON_CALL(*this, ClearMemory()).WillByDefault([this]() {
|
||||
memory_.resize(64000, 0x00);
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<uint8_t> memory_;
|
||||
uint16_t SP_ = 0x01FF;
|
||||
};
|
||||
|
||||
#endif // YAZE_TEST_MOCK_MOCK_MEMORY_H
|
||||
376
src/app/emu/snes.cc
Normal file
376
src/app/emu/snes.cc
Normal file
@@ -0,0 +1,376 @@
|
||||
#include "app/emu/snes.h"
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "app/emu/audio/apu.h"
|
||||
#include "app/emu/audio/spc700.h"
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
#include "app/emu/debug/debugger.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
#include "app/emu/video/ppu.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
namespace {
|
||||
|
||||
uint16_t GetHeaderOffset(const Memory& memory) {
|
||||
uint8_t mapMode = memory[(0x00 << 16) + 0xFFD5];
|
||||
uint16_t offset;
|
||||
|
||||
switch (mapMode & 0x07) {
|
||||
case 0: // LoROM
|
||||
offset = 0x7FC0;
|
||||
break;
|
||||
case 1: // HiROM
|
||||
offset = 0xFFC0;
|
||||
break;
|
||||
case 5: // ExHiROM
|
||||
offset = 0x40;
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument(
|
||||
"Unable to locate supported ROM mapping mode in the provided ROM "
|
||||
"file. Please try another ROM file.");
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
void audio_callback(void* userdata, uint8_t* stream, int len) {
|
||||
auto* apu = static_cast<APU*>(userdata);
|
||||
auto* buffer = reinterpret_cast<int16_t*>(stream);
|
||||
|
||||
for (int i = 0; i < len / 2; i++) { // Assuming 16-bit samples
|
||||
buffer[i] = apu->GetNextSample(); // This function should be implemented in
|
||||
// APU to fetch the next sample
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ROMInfo SNES::ReadRomHeader(uint32_t offset) {
|
||||
ROMInfo romInfo;
|
||||
|
||||
// Read cartridge title
|
||||
char title[22];
|
||||
for (int i = 0; i < 21; ++i) {
|
||||
title[i] = cpu_.ReadByte(offset + i);
|
||||
}
|
||||
title[21] = '\0'; // Null-terminate the string
|
||||
romInfo.title = std::string(title);
|
||||
|
||||
// Read ROM speed and memory map mode
|
||||
uint8_t romSpeedAndMapMode = cpu_.ReadByte(offset + 0x15);
|
||||
romInfo.romSpeed = (ROMSpeed)(romSpeedAndMapMode & 0x07);
|
||||
romInfo.bankSize = (BankSize)((romSpeedAndMapMode >> 5) & 0x01);
|
||||
|
||||
// Read ROM type
|
||||
romInfo.romType = (ROMType)cpu_.ReadByte(offset + 0x16);
|
||||
|
||||
// Read ROM size
|
||||
romInfo.romSize = (ROMSize)cpu_.ReadByte(offset + 0x17);
|
||||
|
||||
// Read RAM size
|
||||
romInfo.sramSize = (SRAMSize)cpu_.ReadByte(offset + 0x18);
|
||||
|
||||
// Read country code
|
||||
romInfo.countryCode = (CountryCode)cpu_.ReadByte(offset + 0x19);
|
||||
|
||||
// Read license
|
||||
romInfo.license = (License)cpu_.ReadByte(offset + 0x1A);
|
||||
|
||||
// Read ROM version
|
||||
romInfo.version = cpu_.ReadByte(offset + 0x1B);
|
||||
|
||||
// Read checksum complement
|
||||
romInfo.checksumComplement = cpu_.ReadWord(offset + 0x1E);
|
||||
|
||||
// Read checksum
|
||||
romInfo.checksum = cpu_.ReadWord(offset + 0x1C);
|
||||
|
||||
// Read NMI VBL vector
|
||||
romInfo.nmiVblVector = cpu_.ReadWord(offset + 0x3E);
|
||||
|
||||
// Read reset vector
|
||||
romInfo.resetVector = cpu_.ReadWord(offset + 0x3C);
|
||||
|
||||
return romInfo;
|
||||
}
|
||||
|
||||
void SNES::Init(ROM& rom) {
|
||||
// Perform a long jump into a FastROM bank (if the ROM speed is FastROM)
|
||||
// Disable the emulation flag (switch to 65816 native mode)
|
||||
cpu_.E = 0;
|
||||
|
||||
// Initialize CPU
|
||||
cpu_.Init();
|
||||
|
||||
// Read the ROM header
|
||||
auto header_offset = GetHeaderOffset(memory_);
|
||||
rom_info_ = ReadRomHeader((0x00 << 16) + header_offset);
|
||||
cpu_.PB = 0x00;
|
||||
cpu_.PC = 0x8000;
|
||||
|
||||
// Initialize PPU
|
||||
ppu_.Init();
|
||||
|
||||
// Initialize APU
|
||||
apu_.Init();
|
||||
|
||||
// Initialize SDL_Mixer to play the audio samples
|
||||
// Mix_HookMusic(audio_callback, &apu);
|
||||
|
||||
// Disable interrupts and rendering
|
||||
memory_.WriteByte(0x4200, 0x00); // NMITIMEN
|
||||
memory_.WriteByte(0x420C, 0x00); // HDMAEN
|
||||
|
||||
// Disable screen
|
||||
memory_.WriteByte(0x2100, 0x8F); // INIDISP
|
||||
|
||||
// Fill Work-RAM with zeros using two 64KiB fixed address DMA transfers to
|
||||
// WMDATA
|
||||
// TODO: Make this load from work ram, potentially in Memory class
|
||||
std::memset((void*)memory_.ram_.data(), 0, sizeof(memory_.ram_));
|
||||
|
||||
// Reset PPU registers to a known good state
|
||||
memory_.WriteByte(0x4201, 0xFF); // WRIO
|
||||
|
||||
// Objects
|
||||
memory_.WriteByte(0x2101, 0x00); // OBSEL
|
||||
memory_.WriteByte(0x2102, 0x00); // OAMADDL
|
||||
memory_.WriteByte(0x2103, 0x00); // OAMADDH
|
||||
|
||||
// Backgrounds
|
||||
memory_.WriteByte(0x2105, 0x00); // BGMODE
|
||||
memory_.WriteByte(0x2106, 0x00); // MOSAIC
|
||||
|
||||
memory_.WriteByte(0x2107, 0x00); // BG1SC
|
||||
memory_.WriteByte(0x2108, 0x00); // BG2SC
|
||||
memory_.WriteByte(0x2109, 0x00); // BG3SC
|
||||
memory_.WriteByte(0x210A, 0x00); // BG4SC
|
||||
|
||||
memory_.WriteByte(0x210B, 0x00); // BG12NBA
|
||||
memory_.WriteByte(0x210C, 0x00); // BG34NBA
|
||||
|
||||
// Scroll Registers
|
||||
memory_.WriteByte(0x210D, 0x00); // BG1HOFS
|
||||
memory_.WriteByte(0x210E, 0xFF); // BG1VOFS
|
||||
|
||||
memory_.WriteByte(0x210F, 0x00); // BG2HOFS
|
||||
memory_.WriteByte(0x2110, 0xFF); // BG2VOFS
|
||||
|
||||
memory_.WriteByte(0x2111, 0x00); // BG3HOFS
|
||||
memory_.WriteByte(0x2112, 0xFF); // BG3VOFS
|
||||
|
||||
memory_.WriteByte(0x2113, 0x00); // BG4HOFS
|
||||
memory_.WriteByte(0x2114, 0xFF); // BG4VOFS
|
||||
|
||||
// VRAM Registers
|
||||
memory_.WriteByte(0x2115, 0x80); // VMAIN
|
||||
|
||||
// Mode 7
|
||||
memory_.WriteByte(0x211A, 0x00); // M7SEL
|
||||
memory_.WriteByte(0x211B, 0x01); // M7A
|
||||
memory_.WriteByte(0x211C, 0x00); // M7B
|
||||
memory_.WriteByte(0x211D, 0x00); // M7C
|
||||
memory_.WriteByte(0x211E, 0x01); // M7D
|
||||
memory_.WriteByte(0x211F, 0x00); // M7X
|
||||
memory_.WriteByte(0x2120, 0x00); // M7Y
|
||||
|
||||
// Windows
|
||||
memory_.WriteByte(0x2123, 0x00); // W12SEL
|
||||
memory_.WriteByte(0x2124, 0x00); // W34SEL
|
||||
memory_.WriteByte(0x2125, 0x00); // WOBJSEL
|
||||
memory_.WriteByte(0x2126, 0x00); // WH0
|
||||
memory_.WriteByte(0x2127, 0x00); // WH1
|
||||
memory_.WriteByte(0x2128, 0x00); // WH2
|
||||
memory_.WriteByte(0x2129, 0x00); // WH3
|
||||
memory_.WriteByte(0x212A, 0x00); // WBGLOG
|
||||
memory_.WriteByte(0x212B, 0x00); // WOBJLOG
|
||||
|
||||
// Layer Enable
|
||||
memory_.WriteByte(0x212C, 0x00); // TM
|
||||
memory_.WriteByte(0x212D, 0x00); // TS
|
||||
memory_.WriteByte(0x212E, 0x00); // TMW
|
||||
memory_.WriteByte(0x212F, 0x00); // TSW
|
||||
|
||||
// Color Math
|
||||
memory_.WriteByte(0x2130, 0x30); // CGWSEL
|
||||
memory_.WriteByte(0x2131, 0x00); // CGADSUB
|
||||
memory_.WriteByte(0x2132, 0xE0); // COLDATA
|
||||
|
||||
// Misc
|
||||
memory_.WriteByte(0x2133, 0x00); // SETINI
|
||||
|
||||
// Psuedo-Init
|
||||
memory_.WriteWord(0x2140, 0xBBAA);
|
||||
|
||||
running_ = true;
|
||||
scanline = 0;
|
||||
}
|
||||
|
||||
void SNES::Run() {
|
||||
const double targetFPS = 60.0; // 60 frames per second
|
||||
const double frame_time = 1.0 / targetFPS;
|
||||
double frame_accumulated_time = 0.0;
|
||||
|
||||
auto last_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
if (running_) {
|
||||
auto current_time = std::chrono::high_resolution_clock::now();
|
||||
double delta_time =
|
||||
std::chrono::duration<double>(current_time - last_time).count();
|
||||
last_time = current_time;
|
||||
|
||||
frame_accumulated_time += delta_time;
|
||||
|
||||
// Update the CPU
|
||||
cpu_.UpdateClock(delta_time);
|
||||
cpu_.Update(GetCpuMode());
|
||||
|
||||
// Update the PPU
|
||||
ppu_.UpdateClock(delta_time);
|
||||
ppu_.Update();
|
||||
|
||||
// Update the APU
|
||||
apu_.UpdateClock(delta_time);
|
||||
apu_.Update();
|
||||
|
||||
if (frame_accumulated_time >= frame_time) {
|
||||
// renderer.Render();
|
||||
frame_accumulated_time -= frame_time;
|
||||
}
|
||||
|
||||
HandleInput();
|
||||
}
|
||||
}
|
||||
|
||||
void SNES::StepRun() {
|
||||
// Update the CPU
|
||||
cpu_.UpdateClock(0.0);
|
||||
cpu_.Update(CPU::UpdateMode::Step);
|
||||
|
||||
// Update the PPU
|
||||
ppu_.UpdateClock(0.0);
|
||||
ppu_.Update();
|
||||
|
||||
// Update the APU
|
||||
apu_.UpdateClock(0.0);
|
||||
apu_.Update();
|
||||
|
||||
HandleInput();
|
||||
}
|
||||
|
||||
// Enable NMI Interrupts
|
||||
void SNES::EnableVBlankInterrupts() {
|
||||
v_blank_flag_ = false;
|
||||
|
||||
// Clear the RDNMI VBlank flag
|
||||
memory_.ReadByte(0x4210); // RDNMI
|
||||
|
||||
// Enable vblank NMI interrupts and Joypad auto-read
|
||||
memory_.WriteByte(0x4200, 0x81); // NMITIMEN
|
||||
}
|
||||
|
||||
// Wait until the VBlank routine has been processed
|
||||
void SNES::WaitForVBlank() {
|
||||
v_blank_flag_ = true;
|
||||
|
||||
// Loop until `v_blank_flag_` is clear
|
||||
while (v_blank_flag_) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
// NMI Interrupt Service Routine
|
||||
void SNES::NmiIsr() {
|
||||
// Switch to a FastROM bank (assuming NmiIsr is in bank 0x80)
|
||||
// ...
|
||||
|
||||
// Push CPU registers to stack
|
||||
cpu_.PHP();
|
||||
|
||||
// Reset DB and DP registers
|
||||
cpu_.DB = 0x80; // Assuming bank 0x80, can be changed to 0x00
|
||||
cpu_.D = 0;
|
||||
|
||||
if (v_blank_flag_) {
|
||||
VBlankRoutine();
|
||||
|
||||
// Clear `v_blank_flag_`
|
||||
v_blank_flag_ = false;
|
||||
}
|
||||
|
||||
// Increment 32-bit frame_counter_
|
||||
frame_counter_++;
|
||||
|
||||
// Restore CPU registers
|
||||
cpu_.PHB();
|
||||
}
|
||||
|
||||
// VBlank routine
|
||||
void SNES::VBlankRoutine() {
|
||||
// Read the joypad state
|
||||
// ...
|
||||
|
||||
// Update the PPU
|
||||
// ...
|
||||
|
||||
// Update the APU
|
||||
// ...
|
||||
}
|
||||
|
||||
void SNES::StartApuDataTransfer() {
|
||||
// 2. Setting the starting address
|
||||
const uint16_t startAddress = 0x0200;
|
||||
memory_.WriteByte(0x2142, startAddress & 0xFF); // Lower byte
|
||||
memory_.WriteByte(0x2143, startAddress >> 8); // Upper byte
|
||||
memory_.WriteByte(0x2141, 0xCC); // Any non-zero value
|
||||
memory_.WriteByte(0x2140, 0xCC); // Signal to start
|
||||
|
||||
const int DATA_SIZE = 0x1000; // 4 KiB
|
||||
|
||||
// 3. Sending data (simplified)
|
||||
// Assuming a buffer `audioData` containing the audio program/data
|
||||
uint8_t audioData[DATA_SIZE]; // Define DATA_SIZE and populate audioData as
|
||||
// needed
|
||||
for (int i = 0; i < DATA_SIZE; ++i) {
|
||||
memory_.WriteByte(0x2141, audioData[i]);
|
||||
memory_.WriteByte(0x2140, i & 0xFF);
|
||||
while (memory_.ReadByte(0x2140) != (i & 0xFF))
|
||||
; // Wait for acknowledgment
|
||||
}
|
||||
|
||||
// 4. Running the SPC700 program
|
||||
memory_.WriteByte(0x2142, startAddress & 0xFF); // Lower byte
|
||||
memory_.WriteByte(0x2143, startAddress >> 8); // Upper byte
|
||||
memory_.WriteByte(0x2141, 0x00); // Zero to start the program
|
||||
memory_.WriteByte(0x2140, 0xCE); // Increment by 2
|
||||
while (memory_.ReadByte(0x2140) != 0xCE)
|
||||
; // Wait for acknowledgment
|
||||
}
|
||||
|
||||
void SNES::HandleInput() {
|
||||
// ...
|
||||
}
|
||||
|
||||
void SNES::SaveState(const std::string& path) {
|
||||
// ...
|
||||
}
|
||||
|
||||
void SNES::LoadState(const std::string& path) {
|
||||
// ...
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
120
src/app/emu/snes.h
Normal file
120
src/app/emu/snes.h
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef YAZE_APP_EMU_SNES_H
|
||||
#define YAZE_APP_EMU_SNES_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "app/emu/audio/apu.h"
|
||||
#include "app/emu/audio/spc700.h"
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
#include "app/emu/debug/debugger.h"
|
||||
#include "app/emu/memory/dma.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
#include "app/emu/video/ppu.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class SNES : public DMA {
|
||||
public:
|
||||
SNES() = default;
|
||||
~SNES() = default;
|
||||
|
||||
ROMInfo ReadRomHeader(uint32_t offset);
|
||||
|
||||
// Initialization
|
||||
void Init(ROM& rom);
|
||||
|
||||
// Main emulation loop
|
||||
void Run();
|
||||
|
||||
// Step through a single instruction
|
||||
void StepRun();
|
||||
|
||||
// Enable NMI Interrupts
|
||||
void EnableVBlankInterrupts();
|
||||
|
||||
// Wait until the VBlank routine has been processed
|
||||
void WaitForVBlank();
|
||||
|
||||
// NMI Interrupt Service Routine
|
||||
void NmiIsr();
|
||||
|
||||
// VBlank routine
|
||||
void VBlankRoutine();
|
||||
|
||||
// Boot the APU with the IPL ROM
|
||||
void BootApuWithIPL();
|
||||
void StartApuDataTransfer();
|
||||
|
||||
// Controller input handling
|
||||
void HandleInput();
|
||||
|
||||
// Save/Load game state
|
||||
void SaveState(const std::string& path);
|
||||
void LoadState(const std::string& path);
|
||||
|
||||
bool running() const { return running_; }
|
||||
|
||||
auto cpu() -> CPU& { return cpu_; }
|
||||
auto ppu() -> Ppu& { return ppu_; }
|
||||
auto Memory() -> MemoryImpl* { return &memory_; }
|
||||
|
||||
void SetCpuMode(int mode) { cpu_mode_ = mode; }
|
||||
CPU::UpdateMode GetCpuMode() const {
|
||||
return static_cast<CPU::UpdateMode>(cpu_mode_);
|
||||
}
|
||||
|
||||
void SetupMemory(ROM& rom) {
|
||||
// Setup observers for the memory space
|
||||
memory_.AddObserver(&apu_);
|
||||
memory_.AddObserver(&ppu_);
|
||||
|
||||
// Load the ROM into memory and set up the memory mapping
|
||||
rom_data = rom.vector();
|
||||
memory_.Initialize(rom_data);
|
||||
}
|
||||
|
||||
private:
|
||||
void WriteToRegister(uint16_t address, uint8_t value) {
|
||||
memory_.WriteByte(address, value);
|
||||
}
|
||||
|
||||
// Components of the SNES
|
||||
MemoryImpl memory_;
|
||||
ClockImpl clock_;
|
||||
AudioRamImpl audio_ram_;
|
||||
|
||||
CPU cpu_{memory_, clock_};
|
||||
Ppu ppu_{memory_, clock_};
|
||||
APU apu_{memory_, audio_ram_, clock_};
|
||||
|
||||
// Helper classes
|
||||
ROMInfo rom_info_;
|
||||
Debugger debugger;
|
||||
|
||||
// Currently loaded ROM
|
||||
std::vector<uint8_t> rom_data;
|
||||
|
||||
// Byte flag to indicate if the VBlank routine should be executed or not
|
||||
std::atomic<bool> v_blank_flag_;
|
||||
|
||||
// 32-bit counter to track the number of NMI interrupts
|
||||
std::atomic<uint32_t> frame_counter_;
|
||||
|
||||
// Other private member variables
|
||||
bool running_ = false;
|
||||
int scanline;
|
||||
int cpu_mode_ = 0;
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_SNES_H
|
||||
435
src/app/emu/video/ppu.cc
Normal file
435
src/app/emu/video/ppu.cc
Normal file
@@ -0,0 +1,435 @@
|
||||
#include "app/emu/video/ppu.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
using namespace PpuRegisters;
|
||||
|
||||
void Ppu::Update() {
|
||||
auto cycles_to_run = clock_.GetCycleCount();
|
||||
|
||||
UpdateInternalState(cycles_to_run);
|
||||
|
||||
// Render however many scanlines we're supposed to.
|
||||
if (current_scanline_ < visibleScanlines) {
|
||||
// Render the current scanline
|
||||
RenderScanline();
|
||||
|
||||
// Increment the current scanline
|
||||
current_scanline_++;
|
||||
}
|
||||
}
|
||||
|
||||
void Ppu::UpdateInternalState(int cycles) {
|
||||
// Update the Ppu's internal state based on the number of cycles
|
||||
cycle_count_ += cycles;
|
||||
|
||||
// Check if it's time to move to the next scanline
|
||||
if (cycle_count_ >= cyclesPerScanline) {
|
||||
current_scanline_++;
|
||||
cycle_count_ -= cyclesPerScanline;
|
||||
|
||||
// If we've reached the end of the frame, reset to the first scanline
|
||||
if (current_scanline_ >= totalScanlines) {
|
||||
current_scanline_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ppu::RenderScanline() {
|
||||
for (int y = 0; y < 240; ++y) {
|
||||
for (int x = 0; x < 256; ++x) {
|
||||
// Calculate the color index based on the x and y coordinates
|
||||
uint8_t color_index = (x + y) % 8;
|
||||
|
||||
// Set the pixel in the frame buffer to the calculated color index
|
||||
frame_buffer_[y * 256 + x] = color_index;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the tile data from VRAM, tile map data from memory, and palette data
|
||||
// from CGRAM
|
||||
// UpdateTileData(); // Fetches the tile data from VRAM and stores it in an
|
||||
// internal buffer
|
||||
UpdateTileMapData(); // Fetches the tile map data from memory and stores it
|
||||
// in an internal buffer
|
||||
UpdatePaletteData(); // Fetches the palette data from CGRAM and stores it in
|
||||
// an internal buffer
|
||||
|
||||
// Render the background layers, taking into account the current mode and
|
||||
// layer priorities
|
||||
for (int layer = 1; layer <= 4; ++layer) {
|
||||
RenderBackground(layer); // Renders the specified background layer into an
|
||||
// internal layer buffer
|
||||
}
|
||||
|
||||
// Render the sprite layer, taking into account sprite priorities and
|
||||
// transparency
|
||||
RenderSprites(); // Renders the sprite layer into an internal sprite buffer
|
||||
|
||||
// Apply effects to the layers, such as scaling, rotation, and blending
|
||||
ApplyEffects(); // Applies effects to the layers based on the current mode
|
||||
// and register settings
|
||||
|
||||
// Combine the layers into a single image and store it in the frame buffer
|
||||
ComposeLayers(); // Combines the layers into a single image and stores it in
|
||||
// the frame buffer
|
||||
|
||||
// Display the frame buffer on the screen
|
||||
DisplayFrameBuffer();
|
||||
}
|
||||
|
||||
void Ppu::Notify(uint32_t address, uint8_t data) {
|
||||
// Handle communication in the Ppu.
|
||||
if (address >= 0x2100 && address <= 0x213F) {
|
||||
// Handle register notification
|
||||
switch (address) {
|
||||
case INIDISP:
|
||||
enable_forced_blanking_ = (data >> 7) & 0x01;
|
||||
break;
|
||||
case OBJSEL:
|
||||
oam_size_.base_selection = (data >> 2) & 0x03;
|
||||
oam_size_.name_selection = (data >> 4) & 0x07;
|
||||
oam_size_.object_size = data & 0x03;
|
||||
break;
|
||||
case OAMADDL:
|
||||
oam_address_.oam_address_low = data;
|
||||
break;
|
||||
case OAMADDH:
|
||||
oam_address_.oam_address_msb = data & 0x01;
|
||||
oam_address_.oam_priority_rotation = (data >> 1) & 0x01;
|
||||
break;
|
||||
case OAMDATA:
|
||||
// Write the data to OAM
|
||||
break;
|
||||
case BGMODE:
|
||||
// Update the Ppu mode settings
|
||||
UpdateModeSettings();
|
||||
break;
|
||||
case MOSAIC:
|
||||
mosaic_.bg_enable = (data >> 7) & 0x01;
|
||||
mosaic_.mosaic_size = data & 0x0F;
|
||||
break;
|
||||
case BG1SC:
|
||||
bgsc_[0] = BGSC(data);
|
||||
break;
|
||||
case BG2SC:
|
||||
bgsc_[1] = BGSC(data);
|
||||
break;
|
||||
case BG3SC:
|
||||
bgsc_[2] = BGSC(data);
|
||||
break;
|
||||
case BG4SC:
|
||||
bgsc_[3] = BGSC(data);
|
||||
break;
|
||||
case BG12NBA:
|
||||
bgnba_[0] = BGNBA(data);
|
||||
break;
|
||||
case BG34NBA:
|
||||
bgnba_[1] = BGNBA(data);
|
||||
break;
|
||||
case BG1HOFS:
|
||||
bghofs_[0].horizontal_scroll = data;
|
||||
break;
|
||||
case BG2HOFS:
|
||||
bghofs_[1].horizontal_scroll = data;
|
||||
break;
|
||||
case BG3HOFS:
|
||||
bghofs_[2].horizontal_scroll = data;
|
||||
break;
|
||||
case BG4HOFS:
|
||||
bghofs_[3].horizontal_scroll = data;
|
||||
break;
|
||||
case BG1VOFS:
|
||||
bgvofs_[0].vertical_scroll = data;
|
||||
break;
|
||||
case BG2VOFS:
|
||||
bgvofs_[1].vertical_scroll = data;
|
||||
break;
|
||||
case BG3VOFS:
|
||||
bgvofs_[2].vertical_scroll = data;
|
||||
break;
|
||||
case BG4VOFS:
|
||||
bgvofs_[3].vertical_scroll = data;
|
||||
break;
|
||||
case VMAIN:
|
||||
vmain_.increment_size = data & 0x03;
|
||||
vmain_.remapping = (data >> 2) & 0x03;
|
||||
vmain_.address_increment_mode = (data >> 4) & 0x01;
|
||||
break;
|
||||
case VMADDL:
|
||||
vmaddl_.address_low = data;
|
||||
break;
|
||||
case VMADDH:
|
||||
vmaddh_.address_high = data;
|
||||
break;
|
||||
case M7SEL:
|
||||
m7sel_.flip_horizontal = data & 0x01;
|
||||
m7sel_.flip_vertical = (data >> 1) & 0x01;
|
||||
m7sel_.fill = (data >> 2) & 0x01;
|
||||
m7sel_.tilemap_repeat = (data >> 3) & 0x01;
|
||||
break;
|
||||
case M7A:
|
||||
m7a_.matrix_a = data;
|
||||
break;
|
||||
case M7B:
|
||||
m7b_.matrix_b = data;
|
||||
break;
|
||||
case M7C:
|
||||
m7c_.matrix_c = data;
|
||||
break;
|
||||
case M7D:
|
||||
m7d_.matrix_d = data;
|
||||
break;
|
||||
case M7X:
|
||||
m7x_.center_x = data;
|
||||
break;
|
||||
case M7Y:
|
||||
m7y_.center_y = data;
|
||||
break;
|
||||
case CGADD:
|
||||
cgadd_.address = data;
|
||||
break;
|
||||
case CGDATA:
|
||||
// Write the data to CGRAM
|
||||
break;
|
||||
case W12SEL:
|
||||
w12sel_.enable_bg1_a = data & 0x01;
|
||||
w12sel_.invert_bg1_a = (data >> 1) & 0x01;
|
||||
w12sel_.enable_bg1_b = (data >> 2) & 0x01;
|
||||
w12sel_.invert_bg1_b = (data >> 3) & 0x01;
|
||||
w12sel_.enable_bg2_c = (data >> 4) & 0x01;
|
||||
w12sel_.invert_bg2_c = (data >> 5) & 0x01;
|
||||
w12sel_.enable_bg2_d = (data >> 6) & 0x01;
|
||||
w12sel_.invert_bg2_d = (data >> 7) & 0x01;
|
||||
break;
|
||||
case W34SEL:
|
||||
w34sel_.enable_bg3_e = data & 0x01;
|
||||
w34sel_.invert_bg3_e = (data >> 1) & 0x01;
|
||||
w34sel_.enable_bg3_f = (data >> 2) & 0x01;
|
||||
w34sel_.invert_bg3_f = (data >> 3) & 0x01;
|
||||
w34sel_.enable_bg4_g = (data >> 4) & 0x01;
|
||||
w34sel_.invert_bg4_g = (data >> 5) & 0x01;
|
||||
w34sel_.enable_bg4_h = (data >> 6) & 0x01;
|
||||
w34sel_.invert_bg4_h = (data >> 7) & 0x01;
|
||||
break;
|
||||
case WOBJSEL:
|
||||
wobjsel_.enable_obj_i = data & 0x01;
|
||||
wobjsel_.invert_obj_i = (data >> 1) & 0x01;
|
||||
wobjsel_.enable_obj_j = (data >> 2) & 0x01;
|
||||
wobjsel_.invert_obj_j = (data >> 3) & 0x01;
|
||||
wobjsel_.enable_color_k = (data >> 4) & 0x01;
|
||||
wobjsel_.invert_color_k = (data >> 5) & 0x01;
|
||||
wobjsel_.enable_color_l = (data >> 6) & 0x01;
|
||||
wobjsel_.invert_color_l = (data >> 7) & 0x01;
|
||||
break;
|
||||
case WH0:
|
||||
wh0_.left_position = data;
|
||||
break;
|
||||
case WH1:
|
||||
wh1_.right_position = data;
|
||||
break;
|
||||
case WH2:
|
||||
wh2_.left_position = data;
|
||||
break;
|
||||
case WH3:
|
||||
wh3_.right_position = data;
|
||||
break;
|
||||
case TM:
|
||||
tm_.enable_layer = (data >> 5) & 0x01; //
|
||||
break;
|
||||
case TS:
|
||||
ts_.enable_layer = (data >> 5) & 0x01;
|
||||
break;
|
||||
case TMW:
|
||||
tmw_.enable_window = (data >> 5) & 0x01;
|
||||
break;
|
||||
case TSW:
|
||||
tsw_.enable_window = (data >> 5) & 0x01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ppu::UpdateModeSettings() {
|
||||
// Read the Ppu mode settings from the Ppu registers
|
||||
uint8_t modeRegister = memory_.ReadByte(PpuRegisters::INIDISP);
|
||||
|
||||
// Mode is stored in the lower 3 bits
|
||||
auto mode = static_cast<BackgroundMode>(modeRegister & 0x07);
|
||||
|
||||
// Update the tilemap, tile data, and palette settings
|
||||
switch (mode) {
|
||||
case BackgroundMode::Mode0:
|
||||
// Mode 0: 4 layers, each 2bpp (4 colors)
|
||||
break;
|
||||
|
||||
case BackgroundMode::Mode1:
|
||||
// Mode 1: 2 layers, 4bpp (16 colors), 1 layer, 2bpp (4 colors)
|
||||
break;
|
||||
|
||||
case BackgroundMode::Mode2:
|
||||
// Mode 2: 2 layers, 4bpp (16 colors), 1 layer for offset-per-tile
|
||||
break;
|
||||
|
||||
case BackgroundMode::Mode3:
|
||||
// Mode 3: 1 layer, 8bpp (256 colors), 1 layer, 4bpp (16 colors)
|
||||
break;
|
||||
|
||||
case BackgroundMode::Mode4:
|
||||
// Mode 4: 1 layer, 8bpp (256 colors), 1 layer, 2bpp (4 colors)
|
||||
break;
|
||||
|
||||
case BackgroundMode::Mode5:
|
||||
// Mode 5: 1 layer, 4bpp (16 colors), 1 layer, 2bpp (4 colors) hi-res
|
||||
break;
|
||||
|
||||
case BackgroundMode::Mode6:
|
||||
// Mode 6: 1 layer, 4bpp (16 colors), 1 layer for offset-per-tile, hi-res
|
||||
break;
|
||||
|
||||
case BackgroundMode::Mode7:
|
||||
// Mode 7: 1 layer, 8bpp (256 colors), rotation/scaling
|
||||
break;
|
||||
|
||||
default:
|
||||
// Invalid mode setting, handle the error or set default settings
|
||||
// ...
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the internal state of the Ppu based on the mode settings
|
||||
// Update tile data, tilemaps, sprites, and palette based on the mode settings
|
||||
UpdateTileData();
|
||||
UpdatePaletteData();
|
||||
}
|
||||
|
||||
// Internal methods to handle Ppu rendering and operations
|
||||
void Ppu::UpdateTileData() {
|
||||
// Fetch tile data from VRAM and store it in the internal buffer
|
||||
for (uint16_t address = 0; address < tile_data_size_; ++address) {
|
||||
tile_data_[address] = memory_.ReadByte(vram_base_address_ + address);
|
||||
}
|
||||
|
||||
// Update the tilemap entries based on the fetched tile data
|
||||
for (uint16_t entryIndex = 0; entryIndex < tilemap_.entries.size();
|
||||
++entryIndex) {
|
||||
uint16_t tilemapAddress =
|
||||
tilemap_base_address_ + entryIndex * sizeof(TilemapEntry);
|
||||
// Assume ReadWord reads a 16-bit value from VRAM
|
||||
uint16_t tileData = memory_.ReadWord(tilemapAddress);
|
||||
|
||||
// Extract tilemap entry attributes from the tile data
|
||||
TilemapEntry entry;
|
||||
// Tile number is stored in the lower 10 bits
|
||||
entry.tileNumber = tileData & 0x03FF;
|
||||
|
||||
// Palette is stored in bits 10-12
|
||||
entry.palette = (tileData >> 10) & 0x07;
|
||||
|
||||
// Priority is stored in bit 13
|
||||
entry.priority = (tileData >> 13) & 0x01;
|
||||
|
||||
// Horizontal flip is stored in bit 14
|
||||
entry.hFlip = (tileData >> 14) & 0x01;
|
||||
|
||||
// Vertical flip is stored in bit 15
|
||||
entry.vFlip = (tileData >> 15) & 0x01;
|
||||
|
||||
tilemap_.entries[entryIndex] = entry;
|
||||
}
|
||||
|
||||
// Update the sprites based on the fetched tile data
|
||||
for (uint16_t spriteIndex = 0; spriteIndex < sprites_.size(); ++spriteIndex) {
|
||||
uint16_t spriteAddress = spriteIndex * sizeof(SpriteAttributes);
|
||||
uint16_t spriteData = memory_.ReadWord(spriteAddress);
|
||||
|
||||
// Extract sprite attributes from the sprite data
|
||||
SpriteAttributes sprite;
|
||||
|
||||
sprite.x = memory_.ReadByte(spriteAddress);
|
||||
sprite.y = memory_.ReadByte(spriteAddress + 1);
|
||||
|
||||
// Tile number is stored in the lower 9
|
||||
sprite.tile = spriteData & 0x01FF;
|
||||
|
||||
// bits Palette is stored in bits 9-11
|
||||
sprite.palette = (spriteData >> 9) & 0x07;
|
||||
|
||||
// Priority is stored in bits 12-13
|
||||
sprite.priority = (spriteData >> 12) & 0x03;
|
||||
|
||||
// Horizontal flip is stored in bit 14
|
||||
sprite.hFlip = (spriteData >> 14) & 0x01;
|
||||
|
||||
// Vertical flip is stored in bit 15
|
||||
sprite.vFlip = (spriteData >> 15) & 0x01;
|
||||
|
||||
sprites_[spriteIndex] = sprite;
|
||||
}
|
||||
}
|
||||
|
||||
void Ppu::UpdateTileMapData() {}
|
||||
|
||||
void Ppu::RenderBackground(int layer) {
|
||||
auto bg1_tilemap_info = BGSC(0);
|
||||
auto bg1_chr_data = BGNBA(0);
|
||||
auto bg2_tilemap_info = BGSC(0);
|
||||
auto bg2_chr_data = BGNBA(0);
|
||||
auto bg3_tilemap_info = BGSC(0);
|
||||
auto bg3_chr_data = BGNBA(0);
|
||||
auto bg4_tilemap_info = BGSC(0);
|
||||
auto bg4_chr_data = BGNBA(0);
|
||||
|
||||
switch (layer) {
|
||||
case 1:
|
||||
// Render the first background layer
|
||||
bg1_tilemap_info = BGSC(memory_.ReadByte(BG1SC));
|
||||
bg1_chr_data = BGNBA(memory_.ReadByte(BG12NBA));
|
||||
break;
|
||||
case 2:
|
||||
// Render the second background layer
|
||||
bg2_tilemap_info = BGSC(memory_.ReadByte(BG2SC));
|
||||
bg2_chr_data = BGNBA(memory_.ReadByte(BG12NBA));
|
||||
break;
|
||||
case 3:
|
||||
// Render the third background layer
|
||||
bg3_tilemap_info = BGSC(memory_.ReadByte(BG3SC));
|
||||
bg3_chr_data = BGNBA(memory_.ReadByte(BG34NBA));
|
||||
break;
|
||||
case 4:
|
||||
// Render the fourth background layer
|
||||
bg4_tilemap_info = BGSC(memory_.ReadByte(BG4SC));
|
||||
bg4_chr_data = BGNBA(memory_.ReadByte(BG34NBA));
|
||||
break;
|
||||
default:
|
||||
// Invalid layer, do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Ppu::RenderSprites() {}
|
||||
|
||||
void Ppu::UpdatePaletteData() {}
|
||||
|
||||
void Ppu::ApplyEffects() {}
|
||||
|
||||
void Ppu::ComposeLayers() {}
|
||||
|
||||
void Ppu::DisplayFrameBuffer() {
|
||||
if (!screen_->IsActive()) {
|
||||
screen_->Create(256, 240, 24, frame_buffer_);
|
||||
rom()->RenderBitmap(screen_.get());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
393
src/app/emu/video/ppu.h
Normal file
393
src/app/emu/video/ppu.h
Normal file
@@ -0,0 +1,393 @@
|
||||
#ifndef YAZE_APP_EMU_PPU_H
|
||||
#define YAZE_APP_EMU_PPU_H
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
#include "app/emu/video/ppu_registers.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
using namespace yaze::app::emu::PpuRegisters;
|
||||
|
||||
class PpuInterface {
|
||||
public:
|
||||
virtual ~PpuInterface() = default;
|
||||
|
||||
// Memory Interactions
|
||||
virtual void Write(uint16_t address, uint8_t data) = 0;
|
||||
virtual uint8_t Read(uint16_t address) const = 0;
|
||||
|
||||
// Rendering Controls
|
||||
virtual void RenderFrame() = 0;
|
||||
virtual void RenderScanline() = 0;
|
||||
virtual void RenderBackground(int layer) = 0;
|
||||
virtual void RenderSprites() = 0;
|
||||
|
||||
// State Management
|
||||
virtual void Init() = 0;
|
||||
virtual void Reset() = 0;
|
||||
virtual void Update(double deltaTime) = 0;
|
||||
virtual void UpdateClock(double deltaTime) = 0;
|
||||
virtual void UpdateInternalState(int cycles) = 0;
|
||||
|
||||
// Data Access
|
||||
virtual const std::vector<uint8_t>& GetFrameBuffer() const = 0;
|
||||
virtual std::shared_ptr<gfx::Bitmap> GetScreen() const = 0;
|
||||
|
||||
// Mode and Setting Updates
|
||||
virtual void UpdateModeSettings() = 0;
|
||||
virtual void UpdateTileData() = 0;
|
||||
virtual void UpdateTileMapData() = 0;
|
||||
virtual void UpdatePaletteData() = 0;
|
||||
|
||||
// Layer Composition
|
||||
virtual void ApplyEffects() = 0;
|
||||
virtual void ComposeLayers() = 0;
|
||||
|
||||
// Display Output
|
||||
virtual void DisplayFrameBuffer() = 0;
|
||||
|
||||
// Notification (Observer pattern)
|
||||
virtual void Notify(uint32_t address, uint8_t data) = 0;
|
||||
};
|
||||
|
||||
// Enum representing different background modes
|
||||
enum class BackgroundMode {
|
||||
Mode0, // 4 layers, each 2bpp (4 colors)
|
||||
Mode1, // 2 layers, 4bpp (16 colors), 1 layer, 2bpp (4 colors)
|
||||
Mode2, // 2 layers, 4bpp (16 colors), 1 layer for offset-per-tile
|
||||
Mode3, // 1 layer, 8bpp (256 colors), 1 layer, 4bpp (16 colors)
|
||||
Mode4, // 1 layer, 8bpp (256 colors), 1 layer, 2bpp (4 colors)
|
||||
// 1 layer for offset-per-tile
|
||||
Mode5, // 1 layer, 4bpp (16 colors), 1 layer, 2bpp (4 colors) hi-res
|
||||
Mode6, // 1 layer, 4bpp (16 colors), 1 layer for offset-per-tile, hi-res
|
||||
Mode7, // 1 layer, 8bpp (256 colors), rotation/scaling
|
||||
};
|
||||
|
||||
// Enum representing sprite sizes
|
||||
enum class SpriteSize { Size8x8, Size16x16, Size32x32, Size64x64 };
|
||||
|
||||
// Struct representing a sprite's attributes
|
||||
struct SpriteAttributes {
|
||||
uint8_t x; // X position of the sprite
|
||||
uint8_t y; // Y position of the sprite
|
||||
uint16_t tile; // Tile number for the sprite
|
||||
uint8_t palette; // Palette number for the sprite
|
||||
uint8_t priority; // Priority for the sprite
|
||||
bool hFlip; // Horizontal flip flag
|
||||
bool vFlip; // Vertical flip flag
|
||||
};
|
||||
|
||||
// Struct representing a tilemap entry
|
||||
struct TilemapEntry {
|
||||
uint16_t tileNumber; // Tile number for the tile
|
||||
uint8_t palette; // Palette number for the tile
|
||||
uint8_t priority; // Priority for the tile
|
||||
bool hFlip; // Horizontal flip flag
|
||||
bool vFlip; // Vertical flip flag
|
||||
};
|
||||
|
||||
// Struct representing a tilemap
|
||||
struct Tilemap {
|
||||
std::vector<TilemapEntry> entries; // Entries for the tilemap
|
||||
};
|
||||
|
||||
// Struct representing a color
|
||||
struct Color {
|
||||
uint8_t r; // Red component
|
||||
uint8_t g; // Green component
|
||||
uint8_t b; // Blue component
|
||||
};
|
||||
|
||||
// Registers
|
||||
struct OAMSize {
|
||||
uint8_t base_selection : 3;
|
||||
uint8_t name_selection : 2;
|
||||
uint8_t object_size : 3;
|
||||
};
|
||||
|
||||
struct OAMAddress {
|
||||
uint8_t oam_address_low : 8;
|
||||
uint8_t oam_address_msb : 1;
|
||||
uint8_t oam_priority_rotation : 1;
|
||||
uint8_t unused : 6;
|
||||
};
|
||||
|
||||
struct TileMapLocation {
|
||||
uint8_t SC_size : 2;
|
||||
uint8_t tile_map_address : 5;
|
||||
uint8_t unused : 1;
|
||||
};
|
||||
|
||||
struct CharacterLocation {
|
||||
uint8_t BG1_address : 4;
|
||||
uint8_t BG2_address : 4;
|
||||
uint8_t BG3_address : 4;
|
||||
uint8_t BG4_address : 4;
|
||||
};
|
||||
|
||||
struct VideoPortControl {
|
||||
uint8_t increment_rate : 2;
|
||||
uint8_t full_graphic : 2;
|
||||
uint8_t increment_mode : 1;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct ScreenDisplay {
|
||||
uint8_t brightness : 4;
|
||||
uint8_t disable_screen : 1;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct ScreenMode {
|
||||
uint8_t general_screen_mode : 3;
|
||||
uint8_t priority : 1;
|
||||
uint8_t BG1_tile_size : 1;
|
||||
uint8_t BG2_tile_size : 1;
|
||||
uint8_t BG3_tile_size : 1;
|
||||
uint8_t BG4_tile_size : 1;
|
||||
};
|
||||
|
||||
struct ScrollRegister {
|
||||
uint8_t offset : 8;
|
||||
uint8_t mode7_bits : 3;
|
||||
uint8_t unused : 5;
|
||||
};
|
||||
|
||||
struct MainSubScreenDesignation {
|
||||
uint8_t BG1_enable : 1;
|
||||
uint8_t BG2_enable : 1;
|
||||
uint8_t BG3_enable : 1;
|
||||
uint8_t BG4_enable : 1;
|
||||
uint8_t sprites_enable : 1;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct WindowMaskSettings {
|
||||
uint8_t BG1_clip_in_out : 1;
|
||||
uint8_t BG1_enable : 1;
|
||||
uint8_t BG2_clip_in_out : 1;
|
||||
uint8_t BG2_enable : 1;
|
||||
uint8_t BG3_clip_in_out : 1;
|
||||
uint8_t BG3_enable : 1;
|
||||
uint8_t BG4_clip_in_out : 1;
|
||||
uint8_t BG4_enable : 1;
|
||||
};
|
||||
|
||||
struct WindowMaskSettings2 {
|
||||
uint8_t sprites_clip_in_out : 1;
|
||||
uint8_t sprites_enable : 1;
|
||||
uint8_t color_windows_clip_in_out : 1;
|
||||
uint8_t color_windows_enable : 1;
|
||||
uint8_t unused : 4;
|
||||
};
|
||||
|
||||
struct WindowPosition {
|
||||
uint8_t position : 8;
|
||||
};
|
||||
|
||||
struct MaskLogicSettings {
|
||||
uint8_t BG1_mask_logic : 2;
|
||||
uint8_t BG2_mask_logic : 2;
|
||||
uint8_t BG3_mask_logic : 2;
|
||||
uint8_t BG4_mask_logic : 2;
|
||||
};
|
||||
|
||||
// Counter/IRQ/NMI Registers
|
||||
struct CounterIrqNmiRegisters {
|
||||
uint8_t softwareLatchHvCounter; // Register $2137
|
||||
uint16_t horizontalScanLocation; // Register $213C
|
||||
uint16_t verticalScanLocation; // Register $213D
|
||||
uint8_t counterEnable; // Register $4200
|
||||
uint16_t horizontalIrqTrigger; // Register $4207/$4208
|
||||
uint16_t verticalIrqTrigger; // Register $4209/$420A
|
||||
uint8_t nmiRegister; // Register $4210
|
||||
uint8_t irqRegister; // Register $4211
|
||||
uint8_t statusRegisterIrq; // Register $4212
|
||||
};
|
||||
|
||||
// Joypad Registers
|
||||
struct JoypadRegisters {
|
||||
uint16_t joypadData[4]; // Register $4218 to $421F
|
||||
uint8_t oldStyleJoypadRegisters[2]; // Registers $4016/$4217
|
||||
};
|
||||
|
||||
// DMA Registers
|
||||
struct DmaRegisters {
|
||||
uint8_t startDmaTransfer; // Register $420B
|
||||
uint8_t enableHDmaTransfer; // Register $420C
|
||||
uint8_t dmacontrol_register_ister[8]; // Register $43?0
|
||||
uint8_t dmaDestinationAddress[8]; // Register $43?1
|
||||
uint32_t dmaSourceAddress[8]; // Register $43?2/$43?3/$43?4
|
||||
uint16_t bytesToTransfer[8]; // Register $43?5/$43?6/$43?7
|
||||
uint16_t hdmaCountPointer[8]; // Register $43?8/$43?9
|
||||
uint8_t scanlinesLeft[8]; // Register $43?A
|
||||
};
|
||||
|
||||
// WRAM access Registers
|
||||
struct WramAccessRegisters {
|
||||
uint8_t dataByte; // Register $2180
|
||||
uint32_t address; // Register $2181/$2182/$2183
|
||||
};
|
||||
|
||||
struct Tile {
|
||||
uint16_t index; // Index of the tile in VRAM
|
||||
uint8_t palette; // Palette number used for this tile
|
||||
bool flip_x; // Horizontal flip flag
|
||||
bool flip_y; // Vertical flip flag
|
||||
uint8_t priority; // Priority of this tile
|
||||
};
|
||||
|
||||
struct BackgroundLayer {
|
||||
enum class Size { SIZE_32x32, SIZE_64x32, SIZE_32x64, SIZE_64x64 };
|
||||
|
||||
enum class ColorDepth { BPP_2, BPP_4, BPP_8 };
|
||||
|
||||
Size size; // Size of the background layer
|
||||
ColorDepth color_depth; // Color depth of the background layer
|
||||
std::vector<Tile> tilemap; // Tilemap data
|
||||
std::vector<uint8_t> tile_data; // Tile data in VRAM
|
||||
uint16_t tilemap_base_address; // Base address of the tilemap in VRAM
|
||||
uint16_t tile_data_base_address; // Base address of the tile data in VRAM
|
||||
uint8_t scroll_x; // Horizontal scroll offset
|
||||
uint8_t scroll_y; // Vertical scroll offset
|
||||
bool enabled; // Whether the background layer is enabled
|
||||
};
|
||||
|
||||
const int kPpuClockSpeed = 5369318; // 5.369318 MHz
|
||||
|
||||
class Ppu : public Observer, public SharedROM {
|
||||
public:
|
||||
// Initializes the PPU with the necessary resources and dependencies
|
||||
Ppu(Memory& memory, Clock& clock) : memory_(memory), clock_(clock) {}
|
||||
|
||||
// Initialize the frame buffer
|
||||
void Init() {
|
||||
clock_.SetFrequency(kPpuClockSpeed);
|
||||
frame_buffer_.resize(256 * 240, 0);
|
||||
screen_ = std::make_shared<gfx::Bitmap>(256, 240, 8, 0x100);
|
||||
screen_->SetActive(false);
|
||||
}
|
||||
|
||||
// Resets the PPU to its initial state
|
||||
void Reset() { std::fill(frame_buffer_.begin(), frame_buffer_.end(), 0); }
|
||||
|
||||
// Runs the PPU for one frame.
|
||||
void Update();
|
||||
void UpdateClock(double delta_time) { clock_.UpdateClock(delta_time); }
|
||||
void UpdateInternalState(int cycles);
|
||||
|
||||
// Renders a scanline of the screen
|
||||
void RenderScanline();
|
||||
|
||||
void Notify(uint32_t address, uint8_t data) override;
|
||||
|
||||
// Returns the pixel data for the current frame
|
||||
const std::vector<uint8_t>& GetFrameBuffer() const { return frame_buffer_; }
|
||||
|
||||
auto GetScreen() const { return screen_; }
|
||||
|
||||
private:
|
||||
// Updates internal state based on PPU register settings
|
||||
void UpdateModeSettings();
|
||||
|
||||
// Internal methods to handle PPU rendering and operations
|
||||
void UpdateTileData();
|
||||
|
||||
// Fetches the tile map data from memory and stores it in an internal buffer
|
||||
void UpdateTileMapData();
|
||||
|
||||
// Renders a background layer
|
||||
void RenderBackground(int layer);
|
||||
|
||||
// Renders sprites (also known as objects)
|
||||
void RenderSprites();
|
||||
|
||||
// Fetches the palette data from CGRAM and stores it in an internal buffer
|
||||
void UpdatePaletteData();
|
||||
|
||||
// Applies effects to the layers based on the current mode and register
|
||||
void ApplyEffects();
|
||||
|
||||
// Combines the layers into a single image and stores it in the frame buffer
|
||||
void ComposeLayers();
|
||||
|
||||
// Sends the frame buffer to the display hardware (e.g., SDL2)
|
||||
void DisplayFrameBuffer();
|
||||
|
||||
// ===========================================================
|
||||
// Member variables to store internal PPU state and resources
|
||||
Memory& memory_;
|
||||
Clock& clock_;
|
||||
|
||||
// PPU registers
|
||||
OAMSize oam_size_;
|
||||
OAMAddress oam_address_;
|
||||
Mosaic mosaic_;
|
||||
std::array<BGSC, 4> bgsc_;
|
||||
std::array<BGNBA, 4> bgnba_;
|
||||
std::array<BGHOFS, 4> bghofs_;
|
||||
std::array<BGVOFS, 4> bgvofs_;
|
||||
struct VMAIN vmain_;
|
||||
struct VMADDL vmaddl_;
|
||||
struct VMADDH vmaddh_;
|
||||
// struct VMDATAL vmdatal_;
|
||||
// struct VMDATAH vmdatah_;
|
||||
struct M7SEL m7sel_;
|
||||
struct M7A m7a_;
|
||||
struct M7B m7b_;
|
||||
struct M7C m7c_;
|
||||
struct M7D m7d_;
|
||||
struct M7X m7x_;
|
||||
struct M7Y m7y_;
|
||||
struct CGADD cgadd_;
|
||||
struct CGDATA cgdata_;
|
||||
struct W12SEL w12sel_;
|
||||
struct W34SEL w34sel_;
|
||||
struct WOBJSEL wobjsel_;
|
||||
struct WH0 wh0_;
|
||||
struct WH1 wh1_;
|
||||
struct WH2 wh2_;
|
||||
struct WH3 wh3_;
|
||||
struct WBGLOG wbglog_;
|
||||
struct WOBJLOG wobjlog_;
|
||||
struct TM tm_;
|
||||
struct TS ts_;
|
||||
struct TSW tsw_;
|
||||
struct TMW tmw_;
|
||||
struct SETINI setini_;
|
||||
|
||||
Tilemap tilemap_;
|
||||
BackgroundMode bg_mode_;
|
||||
std::array<BackgroundLayer, 4> bg_layers_;
|
||||
std::vector<SpriteAttributes> sprites_;
|
||||
std::vector<uint8_t> tile_data_;
|
||||
std::vector<uint8_t> frame_buffer_;
|
||||
std::shared_ptr<gfx::Bitmap> screen_;
|
||||
|
||||
uint16_t tile_data_size_;
|
||||
uint16_t vram_base_address_;
|
||||
uint16_t tilemap_base_address_;
|
||||
uint16_t screen_brightness_ = 0x00;
|
||||
|
||||
bool enable_forced_blanking_ = false;
|
||||
|
||||
int cycle_count_ = 0;
|
||||
int current_scanline_ = 0;
|
||||
const int cyclesPerScanline = 341; // SNES PPU has 341 cycles per scanline
|
||||
const int totalScanlines = 262; // SNES PPU has 262 scanlines per frame
|
||||
const int visibleScanlines = 224; // SNES PPU renders 224 visible scanlines
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_PPU_H
|
||||
422
src/app/emu/video/ppu_registers.h
Normal file
422
src/app/emu/video/ppu_registers.h
Normal file
@@ -0,0 +1,422 @@
|
||||
#ifndef YAZE_APP_EMU_VIDEO_PPU_REGISTERS_H
|
||||
#define YAZE_APP_EMU_VIDEO_PPU_REGISTERS_H
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
namespace PpuRegisters {
|
||||
|
||||
constexpr uint16_t INIDISP = 0x2100;
|
||||
|
||||
// OAM Size Register ($2101): Controls the size of the object/sprite, the base
|
||||
// address, and the name selection for the OAM (Object Attribute Memory).
|
||||
constexpr uint16_t OBJSEL = 0x2101;
|
||||
|
||||
// OAM Address Register ($2102-$2103): Sets the address for accessing OAM data.
|
||||
constexpr uint16_t OAMADDL = 0x2102;
|
||||
constexpr uint16_t OAMADDH = 0x2103;
|
||||
|
||||
// OAM Data Register ($2104): Holds the data to be written to the OAM at a
|
||||
// specified address.
|
||||
constexpr uint16_t OAMDATA = 0x2104;
|
||||
|
||||
// OAM Data Read Register ($2138): Allows reading data from the OAM.
|
||||
|
||||
// Screen Display Register ($2100): Controls screen on/off and brightness.
|
||||
|
||||
// Screen Mode Register ($2105): Defines the screen mode and character size for
|
||||
// each background layer.
|
||||
constexpr uint16_t BGMODE = 0x2105;
|
||||
|
||||
// Screen Pixelation Register ($2106): Sets the pixel size and screen
|
||||
// designation for the mosaic display.
|
||||
constexpr uint16_t MOSAIC = 0x2106;
|
||||
|
||||
// BGx VRAM Location Registers ($2107-$210A)
|
||||
// Define the location in VRAM where the background screen data is stored.
|
||||
constexpr uint16_t BG1SC = 0x2107;
|
||||
constexpr uint16_t BG2SC = 0x2108;
|
||||
constexpr uint16_t BG3SC = 0x2109;
|
||||
constexpr uint16_t BG4SC = 0x210A;
|
||||
|
||||
// BGx & BGy VRAM Location Registers ($210B-$210C):
|
||||
// Set the base address for BG character data in VRAM.
|
||||
constexpr uint16_t BG12NBA = 0x210B;
|
||||
constexpr uint16_t BG34NBA = 0x210C;
|
||||
|
||||
// BGx Scroll Registers ($210D-$2114): Control the horizontal and vertical
|
||||
// scroll values for each background layer.
|
||||
constexpr uint16_t BG1HOFS = 0x210D;
|
||||
constexpr uint16_t BG1VOFS = 0x210E;
|
||||
constexpr uint16_t BG2HOFS = 0x210F;
|
||||
constexpr uint16_t BG2VOFS = 0x2110;
|
||||
constexpr uint16_t BG3HOFS = 0x2111;
|
||||
constexpr uint16_t BG3VOFS = 0x2112;
|
||||
constexpr uint16_t BG4HOFS = 0x2113;
|
||||
constexpr uint16_t BG4VOFS = 0x2114;
|
||||
|
||||
// Video Port Control Register ($2115): Designates the VRAM address increment
|
||||
// value.
|
||||
constexpr uint16_t VMAIN = 0x2115;
|
||||
|
||||
// Video Port Address Register ($2116-$2117): Sets the initial address for
|
||||
// reading from or writing to VRAM.
|
||||
constexpr uint16_t VMADDL = 0x2116;
|
||||
constexpr uint16_t VMADDH = 0x2117;
|
||||
|
||||
constexpr uint16_t VMDATAL = 0x2118;
|
||||
constexpr uint16_t VMDATAH = 0x2119;
|
||||
constexpr uint16_t M7SEL = 0x211A;
|
||||
constexpr uint16_t M7A = 0x211B;
|
||||
constexpr uint16_t M7B = 0x211C;
|
||||
constexpr uint16_t M7C = 0x211D;
|
||||
constexpr uint16_t M7D = 0x211E;
|
||||
constexpr uint16_t M7X = 0x211F;
|
||||
constexpr uint16_t M7Y = 0x2120;
|
||||
constexpr uint16_t CGADD = 0x2121;
|
||||
constexpr uint16_t CGDATA = 0x2122;
|
||||
constexpr uint16_t W12SEL = 0x2123;
|
||||
constexpr uint16_t W34SEL = 0x2124;
|
||||
constexpr uint16_t WOBJSEL = 0x2125;
|
||||
constexpr uint16_t WH0 = 0x2126;
|
||||
constexpr uint16_t WH1 = 0x2127;
|
||||
constexpr uint16_t WH2 = 0x2128;
|
||||
constexpr uint16_t WH3 = 0x2129;
|
||||
constexpr uint16_t WBGLOG = 0x212A;
|
||||
constexpr uint16_t WOBJLOG = 0x212B;
|
||||
constexpr uint16_t TM = 0x212C;
|
||||
constexpr uint16_t TS = 0x212D;
|
||||
constexpr uint16_t TMW = 0x212E;
|
||||
constexpr uint16_t TSW = 0x212F;
|
||||
constexpr uint16_t CGWSEL = 0x2130;
|
||||
constexpr uint16_t CGADSUB = 0x2131;
|
||||
constexpr uint16_t COLDATA = 0x2132;
|
||||
constexpr uint16_t SETINI = 0x2133;
|
||||
constexpr uint16_t MPYL = 0x2134;
|
||||
constexpr uint16_t MPYM = 0x2135;
|
||||
constexpr uint16_t MPYH = 0x2136;
|
||||
constexpr uint16_t SLHV = 0x2137;
|
||||
constexpr uint16_t OAMDATAREAD = 0x2138;
|
||||
constexpr uint16_t VMDATALREAD = 0x2139;
|
||||
constexpr uint16_t VMDATAHREAD = 0x213A;
|
||||
constexpr uint16_t CGDATAREAD = 0x213B;
|
||||
constexpr uint16_t OPHCT = 0x213C;
|
||||
constexpr uint16_t OPVCT = 0x213D;
|
||||
constexpr uint16_t STAT77 = 0x213E;
|
||||
constexpr uint16_t STAT78 = 0x213F;
|
||||
|
||||
struct INIDISP {
|
||||
uint8_t brightness : 4;
|
||||
uint8_t forced_blanking : 1;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct OBJSEL {
|
||||
uint8_t name_base_address : 2;
|
||||
uint8_t name_secondary_select : 1;
|
||||
uint8_t sprite_size : 2;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct OAMADDL {
|
||||
uint8_t address : 8;
|
||||
};
|
||||
|
||||
struct OAMADDH {
|
||||
uint8_t high_bit : 1;
|
||||
uint8_t priority_rotation : 1;
|
||||
uint8_t unused : 6;
|
||||
};
|
||||
|
||||
struct OAMDATA {
|
||||
uint8_t data : 8;
|
||||
};
|
||||
|
||||
struct BGMODE {
|
||||
uint8_t bg_mode : 3;
|
||||
uint8_t bg3_priority : 1;
|
||||
uint8_t tile_size : 4;
|
||||
};
|
||||
|
||||
struct Mosaic {
|
||||
uint8_t bg_enable : 4;
|
||||
uint8_t mosaic_size : 4;
|
||||
};
|
||||
|
||||
struct BGSC {
|
||||
BGSC() = default;
|
||||
~BGSC() = default;
|
||||
explicit BGSC(uint8_t value)
|
||||
: horizontal_tilemap_count(value & 0x01),
|
||||
vertical_tilemap_count((value >> 1) & 0x01),
|
||||
vram_address((value >> 2) & 0x3F) {}
|
||||
uint8_t horizontal_tilemap_count : 1;
|
||||
uint8_t vertical_tilemap_count : 1;
|
||||
uint8_t vram_address : 6;
|
||||
};
|
||||
|
||||
struct BGNBA {
|
||||
BGNBA() = default;
|
||||
~BGNBA() = default;
|
||||
explicit BGNBA(uint8_t value)
|
||||
: chr_base_address_2(value & 0x0F),
|
||||
chr_base_address_1((value >> 4) & 0x0F) {}
|
||||
uint8_t chr_base_address_2 : 4;
|
||||
uint8_t chr_base_address_1 : 4;
|
||||
};
|
||||
|
||||
struct BGHOFS {
|
||||
uint16_t horizontal_scroll : 10;
|
||||
uint8_t unused : 6;
|
||||
};
|
||||
|
||||
struct BGVOFS {
|
||||
uint16_t vertical_scroll : 10;
|
||||
uint8_t unused : 6;
|
||||
};
|
||||
|
||||
struct VMAIN {
|
||||
uint8_t increment_size : 2;
|
||||
uint8_t remapping : 2;
|
||||
uint8_t address_increment_mode : 1;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct VMADDL {
|
||||
uint8_t address_low : 8;
|
||||
};
|
||||
|
||||
struct VMADDH {
|
||||
uint8_t address_high : 8;
|
||||
};
|
||||
|
||||
struct VMDATA {
|
||||
uint8_t data : 8;
|
||||
};
|
||||
|
||||
struct M7SEL {
|
||||
uint8_t flip_horizontal : 1;
|
||||
uint8_t flip_vertical : 1;
|
||||
uint8_t fill : 1;
|
||||
uint8_t tilemap_repeat : 1;
|
||||
uint8_t unused : 4;
|
||||
};
|
||||
|
||||
struct M7A {
|
||||
int16_t matrix_a : 16;
|
||||
};
|
||||
|
||||
struct M7B {
|
||||
int16_t matrix_b : 16;
|
||||
};
|
||||
|
||||
struct M7C {
|
||||
int16_t matrix_c : 16;
|
||||
};
|
||||
|
||||
struct M7D {
|
||||
int16_t matrix_d : 16;
|
||||
};
|
||||
|
||||
struct M7X {
|
||||
uint16_t center_x : 13;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct M7Y {
|
||||
uint16_t center_y : 13;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct CGADD {
|
||||
uint8_t address : 8;
|
||||
};
|
||||
|
||||
struct CGDATA {
|
||||
uint16_t data : 15;
|
||||
uint8_t unused : 1;
|
||||
};
|
||||
|
||||
struct W12SEL {
|
||||
uint8_t enable_bg1_a : 1;
|
||||
uint8_t invert_bg1_a : 1;
|
||||
uint8_t enable_bg1_b : 1;
|
||||
uint8_t invert_bg1_b : 1;
|
||||
uint8_t enable_bg2_c : 1;
|
||||
uint8_t invert_bg2_c : 1;
|
||||
uint8_t enable_bg2_d : 1;
|
||||
uint8_t invert_bg2_d : 1;
|
||||
};
|
||||
|
||||
struct W34SEL {
|
||||
uint8_t enable_bg3_e : 1;
|
||||
uint8_t invert_bg3_e : 1;
|
||||
uint8_t enable_bg3_f : 1;
|
||||
uint8_t invert_bg3_f : 1;
|
||||
uint8_t enable_bg4_g : 1;
|
||||
uint8_t invert_bg4_g : 1;
|
||||
uint8_t enable_bg4_h : 1;
|
||||
uint8_t invert_bg4_h : 1;
|
||||
};
|
||||
|
||||
struct WOBJSEL {
|
||||
uint8_t enable_obj_i : 1;
|
||||
uint8_t invert_obj_i : 1;
|
||||
uint8_t enable_obj_j : 1;
|
||||
uint8_t invert_obj_j : 1;
|
||||
uint8_t enable_color_k : 1;
|
||||
uint8_t invert_color_k : 1;
|
||||
uint8_t enable_color_l : 1;
|
||||
uint8_t invert_color_l : 1;
|
||||
};
|
||||
|
||||
struct WH0 {
|
||||
uint8_t left_position : 8;
|
||||
};
|
||||
|
||||
struct WH1 {
|
||||
uint8_t right_position : 8;
|
||||
};
|
||||
|
||||
struct WH2 {
|
||||
uint8_t left_position : 8;
|
||||
};
|
||||
|
||||
struct WH3 {
|
||||
uint8_t right_position : 8;
|
||||
};
|
||||
|
||||
struct WBGLOG {
|
||||
uint8_t mask_logic_bg4 : 2;
|
||||
uint8_t mask_logic_bg3 : 2;
|
||||
uint8_t mask_logic_bg2 : 2;
|
||||
uint8_t mask_logic_bg1 : 2;
|
||||
};
|
||||
|
||||
struct WOBJLOG {
|
||||
uint8_t unused : 4;
|
||||
uint8_t mask_logic_color : 2;
|
||||
uint8_t mask_logic_obj : 2;
|
||||
};
|
||||
|
||||
struct TM {
|
||||
uint8_t enable_layer : 5;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct TS {
|
||||
uint8_t enable_layer : 5;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct TMW {
|
||||
uint8_t enable_window : 5;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct TSW {
|
||||
uint8_t enable_window : 5;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct CGWSEL {
|
||||
uint8_t direct_color : 1;
|
||||
uint8_t fixed_subscreen : 1;
|
||||
uint8_t sub_color_window : 2;
|
||||
uint8_t main_color_window : 2;
|
||||
uint8_t unused : 2;
|
||||
};
|
||||
|
||||
struct CGADSUB {
|
||||
uint8_t enable_layer : 5;
|
||||
uint8_t backdrop : 1;
|
||||
uint8_t half : 1;
|
||||
uint8_t add_subtract : 1;
|
||||
};
|
||||
|
||||
struct COLDATA {
|
||||
uint8_t value : 4;
|
||||
uint8_t channel_select : 3;
|
||||
uint8_t unused : 1;
|
||||
};
|
||||
|
||||
struct SETINI {
|
||||
uint8_t screen_interlace : 1;
|
||||
uint8_t obj_interlace : 1;
|
||||
uint8_t overscan : 1;
|
||||
uint8_t hi_res : 1;
|
||||
uint8_t extbg : 1;
|
||||
uint8_t external_sync : 1;
|
||||
uint8_t unused : 2;
|
||||
};
|
||||
|
||||
struct MPYL {
|
||||
uint8_t multiplication_result_low : 8;
|
||||
};
|
||||
|
||||
struct MPYM {
|
||||
uint8_t multiplication_result_mid : 8;
|
||||
};
|
||||
|
||||
struct MPYH {
|
||||
uint8_t multiplication_result_high : 8;
|
||||
};
|
||||
|
||||
struct SLHV {
|
||||
uint8_t software_latch : 8;
|
||||
};
|
||||
|
||||
struct OAMDATAREAD {
|
||||
uint8_t oam_data_read : 8;
|
||||
};
|
||||
|
||||
struct VMDATALREAD {
|
||||
uint8_t vram_data_read_low : 8;
|
||||
};
|
||||
|
||||
struct VMDATAHREAD {
|
||||
uint8_t vram_data_read_high : 8;
|
||||
};
|
||||
|
||||
struct CGDATAREAD {
|
||||
uint8_t cgram_data_read : 8;
|
||||
};
|
||||
|
||||
struct OPHCT {
|
||||
uint16_t horizontal_counter_output : 9;
|
||||
uint8_t unused : 7;
|
||||
};
|
||||
|
||||
struct OPVCT {
|
||||
uint16_t vertical_counter_output : 9;
|
||||
uint8_t unused : 7;
|
||||
};
|
||||
|
||||
struct STAT77 {
|
||||
uint8_t ppu1_version : 4;
|
||||
uint8_t master_slave : 1;
|
||||
uint8_t sprite_tile_overflow : 1;
|
||||
uint8_t sprite_overflow : 1;
|
||||
uint8_t unused : 1;
|
||||
};
|
||||
|
||||
struct STAT78 {
|
||||
uint8_t ppu2_version : 4;
|
||||
uint8_t ntsc_pal : 1;
|
||||
uint8_t counter_latch_value : 1;
|
||||
uint8_t interlace_field : 1;
|
||||
uint8_t unused : 1;
|
||||
};
|
||||
|
||||
} // namespace PpuRegisters
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_VIDEO_PPU_REGISTERS_H
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "bitmap.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <png.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -23,35 +24,180 @@ void GrayscalePalette(SDL_Palette *palette) {
|
||||
palette->colors[i].b = i * 31;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bitmap::Bitmap(int width, int height, int depth, uchar *data) {
|
||||
Create(width, height, depth, data);
|
||||
void PngWriteCallback(png_structp png_ptr, png_bytep data, png_size_t length) {
|
||||
std::vector<uint8_t> *p = (std::vector<uint8_t> *)png_get_io_ptr(png_ptr);
|
||||
p->insert(p->end(), data, data + length);
|
||||
}
|
||||
|
||||
bool ConvertSurfaceToPNG(SDL_Surface *surface, std::vector<uint8_t> &buffer) {
|
||||
png_structp png_ptr = png_create_write_struct("1.6.40", NULL, NULL, NULL);
|
||||
if (!png_ptr) {
|
||||
SDL_Log("Failed to create PNG write struct");
|
||||
return false;
|
||||
}
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr) {
|
||||
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
|
||||
SDL_Log("Failed to create PNG info struct");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
SDL_Log("Error during PNG write");
|
||||
return false;
|
||||
}
|
||||
|
||||
png_set_write_fn(png_ptr, &buffer, PngWriteCallback, NULL);
|
||||
|
||||
png_colorp pal_ptr;
|
||||
|
||||
/* Prepare chunks */
|
||||
int colortype = PNG_COLOR_MASK_COLOR;
|
||||
int i = 0;
|
||||
SDL_Palette *pal;
|
||||
if (surface->format->BytesPerPixel > 0 &&
|
||||
surface->format->BytesPerPixel <= 8 && (pal = surface->format->palette)) {
|
||||
SDL_Log("Writing PNG image with palette");
|
||||
colortype |= PNG_COLOR_MASK_PALETTE;
|
||||
pal_ptr = (png_colorp)malloc(pal->ncolors * sizeof(png_color));
|
||||
for (i = 0; i < pal->ncolors; i++) {
|
||||
pal_ptr[i].red = pal->colors[i].r;
|
||||
pal_ptr[i].green = pal->colors[i].g;
|
||||
pal_ptr[i].blue = pal->colors[i].b;
|
||||
}
|
||||
png_set_PLTE(png_ptr, info_ptr, pal_ptr, pal->ncolors);
|
||||
free(pal_ptr);
|
||||
}
|
||||
|
||||
auto depth = surface->format->BitsPerPixel;
|
||||
|
||||
// Set image attributes.
|
||||
png_set_IHDR(png_ptr, info_ptr, surface->w, surface->h, depth, colortype,
|
||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
||||
PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
png_set_bgr(png_ptr);
|
||||
|
||||
// Write the image data.
|
||||
std::vector<png_bytep> row_pointers(surface->h);
|
||||
for (int y = 0; y < surface->h; ++y) {
|
||||
row_pointers[y] = (png_bytep)(surface->pixels) + y * surface->pitch;
|
||||
}
|
||||
|
||||
png_set_rows(png_ptr, info_ptr, row_pointers.data());
|
||||
|
||||
SDL_Log("Writing PNG image...");
|
||||
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
|
||||
SDL_Log("PNG image write complete");
|
||||
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PngReadCallback(png_structp png_ptr, png_bytep outBytes,
|
||||
png_size_t byteCountToRead) {
|
||||
png_voidp io_ptr = png_get_io_ptr(png_ptr);
|
||||
if (!io_ptr) return;
|
||||
|
||||
std::vector<uint8_t> *png_data =
|
||||
reinterpret_cast<std::vector<uint8_t> *>(io_ptr);
|
||||
size_t pos = png_data->size() - byteCountToRead;
|
||||
memcpy(outBytes, png_data->data() + pos, byteCountToRead);
|
||||
png_data->resize(pos); // Reduce the buffer size
|
||||
}
|
||||
|
||||
void ConvertPngToSurface(const std::vector<uint8_t> &png_data,
|
||||
SDL_Surface **outSurface) {
|
||||
std::vector<uint8_t> data(png_data);
|
||||
png_structp png_ptr = png_create_read_struct("1.6.40", NULL, NULL, NULL);
|
||||
if (!png_ptr) {
|
||||
throw std::runtime_error("Failed to create PNG read struct");
|
||||
}
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr) {
|
||||
png_destroy_read_struct(&png_ptr, NULL, NULL);
|
||||
throw std::runtime_error("Failed to create PNG info struct");
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
throw std::runtime_error("Error during PNG read");
|
||||
}
|
||||
|
||||
// Set our custom read function
|
||||
png_set_read_fn(png_ptr, &data, PngReadCallback);
|
||||
|
||||
// Read the PNG info
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
|
||||
uint32_t width = png_get_image_width(png_ptr, info_ptr);
|
||||
uint32_t height = png_get_image_height(png_ptr, info_ptr);
|
||||
png_byte color_type = png_get_color_type(png_ptr, info_ptr);
|
||||
png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
|
||||
|
||||
// Set up transformations, e.g., strip 16-bit PNGs down to 8-bit, expand
|
||||
// palettes, etc.
|
||||
if (bit_depth == 16) {
|
||||
png_set_strip_16(png_ptr);
|
||||
}
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_PALETTE) {
|
||||
png_set_palette_to_rgb(png_ptr);
|
||||
}
|
||||
|
||||
// PNG files pack pixels, expand them
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
|
||||
png_set_expand_gray_1_2_4_to_8(png_ptr);
|
||||
}
|
||||
|
||||
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
|
||||
png_set_tRNS_to_alpha(png_ptr);
|
||||
}
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY ||
|
||||
color_type == PNG_COLOR_TYPE_PALETTE) {
|
||||
png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
|
||||
}
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY ||
|
||||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
||||
png_set_gray_to_rgb(png_ptr);
|
||||
}
|
||||
|
||||
// Update info structure with transformations
|
||||
png_read_update_info(png_ptr, info_ptr);
|
||||
|
||||
// Read the file
|
||||
std::vector<png_bytep> row_pointers(height);
|
||||
std::vector<uint8_t> raw_data(width * height *
|
||||
4); // Assuming 4 bytes per pixel (RGBA)
|
||||
for (size_t y = 0; y < height; y++) {
|
||||
row_pointers[y] = &raw_data[y * width * 4];
|
||||
}
|
||||
|
||||
png_read_image(png_ptr, row_pointers.data());
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
|
||||
// Create SDL_Surface from raw pixel data
|
||||
*outSurface = SDL_CreateRGBSurfaceWithFormatFrom(
|
||||
raw_data.data(), width, height, 32, width * 4, SDL_PIXELFORMAT_RGBA32);
|
||||
if (*outSurface == nullptr) {
|
||||
SDL_Log("SDL_CreateRGBSurfaceWithFormatFrom failed: %s\n", SDL_GetError());
|
||||
} else {
|
||||
SDL_Log("Successfully created SDL_Surface from PNG data");
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bitmap::Bitmap(int width, int height, int depth, int data_size) {
|
||||
Create(width, height, depth, data_size);
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(int width, int height, int depth, uchar *data, int data_size) {
|
||||
Create(width, height, depth, data, data_size);
|
||||
}
|
||||
|
||||
// Pass raw pixel data directly to the surface
|
||||
void Bitmap::Create(int width, int height, int depth, uchar *data) {
|
||||
active_ = true;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
depth_ = depth;
|
||||
pixel_data_ = data;
|
||||
surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
|
||||
SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_,
|
||||
SDL_PIXELFORMAT_INDEX8),
|
||||
SDL_Surface_Deleter());
|
||||
surface_->pixels = pixel_data_;
|
||||
GrayscalePalette(surface_->format->palette);
|
||||
}
|
||||
|
||||
// Reserves data to later draw to surface via pointer
|
||||
void Bitmap::Create(int width, int height, int depth, int size) {
|
||||
active_ = true;
|
||||
@@ -69,23 +215,7 @@ void Bitmap::Create(int width, int height, int depth, int size) {
|
||||
GrayscalePalette(surface_->format->palette);
|
||||
}
|
||||
|
||||
// Pass raw pixel data directly to the surface
|
||||
void Bitmap::Create(int width, int height, int depth, uchar *data, int size) {
|
||||
active_ = true;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
depth_ = depth;
|
||||
pixel_data_ = data;
|
||||
data_size_ = size;
|
||||
surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
|
||||
SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_,
|
||||
SDL_PIXELFORMAT_INDEX8),
|
||||
SDL_Surface_Deleter());
|
||||
surface_->pixels = pixel_data_;
|
||||
GrayscalePalette(surface_->format->palette);
|
||||
}
|
||||
|
||||
void Bitmap::Create(int width, int height, int depth, Bytes data) {
|
||||
void Bitmap::Create(int width, int height, int depth, const Bytes &data) {
|
||||
active_ = true;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
@@ -100,9 +230,62 @@ void Bitmap::Create(int width, int height, int depth, Bytes data) {
|
||||
GrayscalePalette(surface_->format->palette);
|
||||
}
|
||||
|
||||
void Bitmap::Apply(Bytes data) {
|
||||
pixel_data_ = data.data();
|
||||
data_ = data;
|
||||
// Creates the texture that will be displayed to the screen.
|
||||
void Bitmap::CreateTexture(SDL_Renderer *renderer) {
|
||||
// Ensure width and height are non-zero
|
||||
if (width_ <= 0 || height_ <= 0) {
|
||||
SDL_Log("Invalid texture dimensions: width=%d, height=%d\n", width_,
|
||||
height_);
|
||||
return;
|
||||
}
|
||||
|
||||
texture_ = std::shared_ptr<SDL_Texture>{
|
||||
SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB888,
|
||||
SDL_TEXTUREACCESS_STREAMING, width_, height_),
|
||||
SDL_Texture_Deleter{}};
|
||||
if (texture_ == nullptr) {
|
||||
SDL_Log("SDL_CreateTextureFromSurface failed: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_Surface *converted_surface =
|
||||
SDL_ConvertSurfaceFormat(surface_.get(), SDL_PIXELFORMAT_ARGB8888, 0);
|
||||
if (converted_surface) {
|
||||
// Create texture from the converted surface
|
||||
converted_surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
|
||||
converted_surface, SDL_Surface_Deleter());
|
||||
} else {
|
||||
// Handle the error
|
||||
SDL_Log("SDL_ConvertSurfaceFormat failed: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_LockTexture(texture_.get(), nullptr, (void **)&texture_pixels,
|
||||
&converted_surface_->pitch);
|
||||
|
||||
memcpy(texture_pixels, converted_surface_->pixels,
|
||||
converted_surface_->h * converted_surface_->pitch);
|
||||
|
||||
SDL_UnlockTexture(texture_.get());
|
||||
}
|
||||
|
||||
void Bitmap::UpdateTexture(SDL_Renderer *renderer) {
|
||||
SDL_Surface *converted_surface =
|
||||
SDL_ConvertSurfaceFormat(surface_.get(), SDL_PIXELFORMAT_ARGB8888, 0);
|
||||
if (converted_surface) {
|
||||
// Create texture from the converted surface
|
||||
converted_surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
|
||||
converted_surface, SDL_Surface_Deleter());
|
||||
} else {
|
||||
// Handle the error
|
||||
SDL_Log("SDL_ConvertSurfaceFormat failed: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_LockTexture(texture_.get(), nullptr, (void **)&texture_pixels,
|
||||
&converted_surface_->pitch);
|
||||
|
||||
memcpy(texture_pixels, converted_surface_->pixels,
|
||||
converted_surface_->h * converted_surface_->pitch);
|
||||
|
||||
SDL_UnlockTexture(texture_.get());
|
||||
}
|
||||
|
||||
// Creates the texture that will be displayed to the screen.
|
||||
@@ -112,22 +295,107 @@ void Bitmap::CreateTexture(std::shared_ptr<SDL_Renderer> renderer) {
|
||||
SDL_Texture_Deleter{}};
|
||||
}
|
||||
|
||||
void Bitmap::UpdateTexture(std::shared_ptr<SDL_Renderer> renderer) {
|
||||
// SDL_DestroyTexture(texture_.get());
|
||||
// texture_ = nullptr;
|
||||
texture_ = std::shared_ptr<SDL_Texture>{
|
||||
SDL_CreateTextureFromSurface(renderer.get(), surface_.get()),
|
||||
SDL_Texture_Deleter{}};
|
||||
}
|
||||
|
||||
void Bitmap::SaveSurfaceToFile(std::string_view filename) {
|
||||
SDL_SaveBMP(surface_.get(), filename.data());
|
||||
}
|
||||
|
||||
void Bitmap::SetSurface(SDL_Surface *surface) {
|
||||
surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
|
||||
surface, SDL_Surface_Deleter());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Bitmap::GetPngData() {
|
||||
ConvertSurfaceToPNG(surface_.get(), png_data_);
|
||||
return png_data_;
|
||||
}
|
||||
|
||||
void Bitmap::LoadFromPngData(const std::vector<uint8_t> &png_data, int width,
|
||||
int height) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
SDL_Surface *surface = surface_.get();
|
||||
ConvertPngToSurface(png_data, &surface);
|
||||
surface_.reset(surface);
|
||||
}
|
||||
|
||||
// Convert SNESPalette to SDL_Palette for surface.
|
||||
void Bitmap::ApplyPalette(const SNESPalette &palette) {
|
||||
palette_ = palette;
|
||||
for (int i = 0; i < palette.size_; ++i) {
|
||||
if (palette.GetColor(i).transparent) {
|
||||
SDL_UnlockSurface(surface_.get());
|
||||
for (int i = 0; i < palette.size(); ++i) {
|
||||
if (palette.GetColor(i).IsTransparent()) {
|
||||
surface_->format->palette->colors[i].r = 0;
|
||||
surface_->format->palette->colors[i].g = 0;
|
||||
surface_->format->palette->colors[i].b = 0;
|
||||
surface_->format->palette->colors[i].a = 0;
|
||||
} else {
|
||||
surface_->format->palette->colors[i].r = palette.GetColor(i).rgb.x;
|
||||
surface_->format->palette->colors[i].g = palette.GetColor(i).rgb.y;
|
||||
surface_->format->palette->colors[i].b = palette.GetColor(i).rgb.z;
|
||||
surface_->format->palette->colors[i].a = palette.GetColor(i).rgb.w;
|
||||
surface_->format->palette->colors[i].r = palette.GetColor(i).GetRGB().x;
|
||||
surface_->format->palette->colors[i].g = palette.GetColor(i).GetRGB().y;
|
||||
surface_->format->palette->colors[i].b = palette.GetColor(i).GetRGB().z;
|
||||
surface_->format->palette->colors[i].a = palette.GetColor(i).GetRGB().w;
|
||||
}
|
||||
}
|
||||
SDL_LockSurface(surface_.get());
|
||||
}
|
||||
|
||||
void Bitmap::ApplyPaletteWithTransparent(const SNESPalette &palette,
|
||||
int index) {
|
||||
auto start_index = index * 7;
|
||||
palette_ = palette.sub_palette(start_index, start_index + 7);
|
||||
std::vector<ImVec4> colors;
|
||||
colors.push_back(ImVec4(0, 0, 0, 0));
|
||||
for (int i = start_index; i < start_index + 7; ++i) {
|
||||
colors.push_back(palette.GetColor(i).GetRGB());
|
||||
}
|
||||
|
||||
SDL_UnlockSurface(surface_.get());
|
||||
int i = 0;
|
||||
for (auto &each : colors) {
|
||||
surface_->format->palette->colors[i].r = each.x;
|
||||
surface_->format->palette->colors[i].g = each.y;
|
||||
surface_->format->palette->colors[i].b = each.z;
|
||||
surface_->format->palette->colors[i].a = each.w;
|
||||
i++;
|
||||
}
|
||||
SDL_LockSurface(surface_.get());
|
||||
}
|
||||
|
||||
void Bitmap::ApplyPalette(const std::vector<SDL_Color> &palette) {
|
||||
SDL_UnlockSurface(surface_.get());
|
||||
for (int i = 0; i < palette.size(); ++i) {
|
||||
surface_->format->palette->colors[i].r = palette[i].r;
|
||||
surface_->format->palette->colors[i].g = palette[i].g;
|
||||
surface_->format->palette->colors[i].b = palette[i].b;
|
||||
surface_->format->palette->colors[i].a = palette[i].a;
|
||||
}
|
||||
SDL_LockSurface(surface_.get());
|
||||
}
|
||||
|
||||
void Bitmap::InitializeFromData(uint32_t width, uint32_t height, uint32_t depth,
|
||||
const Bytes &data) {
|
||||
active_ = true;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
depth_ = depth;
|
||||
data_ = data;
|
||||
data_size_ = data.size();
|
||||
pixel_data_ = data_.data();
|
||||
|
||||
surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
|
||||
SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_,
|
||||
SDL_PIXELFORMAT_INDEX8),
|
||||
SDL_Surface_Deleter());
|
||||
|
||||
surface_->pixels = pixel_data_;
|
||||
GrayscalePalette(surface_->format->palette);
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
@@ -19,33 +20,136 @@ namespace gfx {
|
||||
class Bitmap {
|
||||
public:
|
||||
Bitmap() = default;
|
||||
Bitmap(int width, int height, int depth, uchar *data);
|
||||
|
||||
Bitmap(int width, int height, int depth, int data_size);
|
||||
Bitmap(int width, int height, int depth, uchar *data, int data_size);
|
||||
|
||||
void Create(int width, int height, int depth, uchar *data);
|
||||
void Create(int width, int height, int depth, int data_size);
|
||||
void Create(int width, int height, int depth, uchar *data, int data_size);
|
||||
void Create(int width, int height, int depth, Bytes data);
|
||||
|
||||
void Apply(Bytes data);
|
||||
|
||||
void CreateTexture(std::shared_ptr<SDL_Renderer> renderer);
|
||||
|
||||
void ApplyPalette(const SNESPalette &palette);
|
||||
|
||||
void WriteToPixel(int position, uchar value) {
|
||||
this->pixel_data_[position] = value;
|
||||
Bitmap(int width, int height, int depth, const Bytes &data)
|
||||
: width_(width), height_(height), depth_(depth), data_(data) {
|
||||
InitializeFromData(width, height, depth, data);
|
||||
}
|
||||
|
||||
int GetWidth() const { return width_; }
|
||||
int GetHeight() const { return height_; }
|
||||
auto GetSize() const { return data_size_; }
|
||||
auto GetData() const { return pixel_data_; }
|
||||
auto GetByte(int i) const { return pixel_data_[i]; }
|
||||
auto GetTexture() const { return texture_.get(); }
|
||||
auto GetSurface() const { return surface_.get(); }
|
||||
void Create(int width, int height, int depth, int data_size);
|
||||
void Create(int width, int height, int depth, const Bytes &data);
|
||||
|
||||
void InitializeFromData(uint32_t width, uint32_t height, uint32_t depth,
|
||||
const Bytes &data);
|
||||
|
||||
void CreateTexture(std::shared_ptr<SDL_Renderer> renderer);
|
||||
void UpdateTexture(std::shared_ptr<SDL_Renderer> renderer);
|
||||
void CreateTexture(SDL_Renderer *renderer);
|
||||
void UpdateTexture(SDL_Renderer *renderer);
|
||||
|
||||
void SaveSurfaceToFile(std::string_view filename);
|
||||
void SetSurface(SDL_Surface *surface);
|
||||
std::vector<uint8_t> GetPngData();
|
||||
void LoadFromPngData(const std::vector<uint8_t> &png_data, int width,
|
||||
int height);
|
||||
|
||||
void ApplyPalette(const SNESPalette &palette);
|
||||
void ApplyPaletteWithTransparent(const SNESPalette &palette, int index);
|
||||
void ApplyPalette(const std::vector<SDL_Color> &palette);
|
||||
|
||||
void WriteToPixel(int position, uchar value) {
|
||||
if (pixel_data_ == nullptr) {
|
||||
pixel_data_ = data_.data();
|
||||
}
|
||||
pixel_data_[position] = value;
|
||||
modified_ = true;
|
||||
}
|
||||
|
||||
void WriteWordToPixel(int position, uint16_t value) {
|
||||
if (pixel_data_ == nullptr) {
|
||||
pixel_data_ = data_.data();
|
||||
}
|
||||
pixel_data_[position] = value & 0xFF;
|
||||
pixel_data_[position + 1] = (value >> 8) & 0xFF;
|
||||
modified_ = true;
|
||||
}
|
||||
|
||||
void Get8x8Tile(int tile_index, int x, int y, std::vector<uint8_t> &tile_data,
|
||||
int &tile_data_offset) {
|
||||
int tile_offset = tile_index * 64;
|
||||
int tile_x = x * 8;
|
||||
int tile_y = y * 8;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int row_offset = tile_offset + (i * 8);
|
||||
for (int j = 0; j < 8; j++) {
|
||||
int pixel_offset = row_offset + j;
|
||||
int pixel_value = data_[pixel_offset];
|
||||
tile_data[tile_data_offset] = pixel_value;
|
||||
tile_data_offset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WriteColor(int position, const ImVec4 &color) {
|
||||
// Convert ImVec4 (RGBA) to SDL_Color (RGBA)
|
||||
SDL_Color sdl_color;
|
||||
sdl_color.r = static_cast<Uint8>(color.x * 255);
|
||||
sdl_color.g = static_cast<Uint8>(color.y * 255);
|
||||
sdl_color.b = static_cast<Uint8>(color.z * 255);
|
||||
sdl_color.a = static_cast<Uint8>(color.w * 255);
|
||||
|
||||
// Map SDL_Color to the nearest color index in the surface's palette
|
||||
Uint8 index =
|
||||
SDL_MapRGB(surface_->format, sdl_color.r, sdl_color.g, sdl_color.b);
|
||||
|
||||
// Write the color index to the pixel data
|
||||
pixel_data_[position] = index;
|
||||
modified_ = true;
|
||||
}
|
||||
|
||||
void Cleanup() {
|
||||
// Reset texture_
|
||||
if (texture_) {
|
||||
texture_.reset();
|
||||
}
|
||||
|
||||
// Reset surface_ and its pixel data
|
||||
if (surface_) {
|
||||
surface_->pixels = nullptr;
|
||||
surface_.reset();
|
||||
}
|
||||
|
||||
// Reset data_
|
||||
data_.clear();
|
||||
|
||||
// Reset other members if necessary
|
||||
active_ = false;
|
||||
pixel_data_ = nullptr;
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
depth_ = 0;
|
||||
data_size_ = 0;
|
||||
palette_.Clear();
|
||||
}
|
||||
|
||||
auto sdl_palette() {
|
||||
if (surface_ == nullptr) {
|
||||
throw std::runtime_error("Surface is null.");
|
||||
}
|
||||
return surface_->format->palette;
|
||||
}
|
||||
auto palette() const { return palette_; }
|
||||
auto palette_size() const { return palette_.size(); }
|
||||
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
auto depth() const { return depth_; }
|
||||
auto size() const { return data_size_; }
|
||||
auto data() const { return data_.data(); }
|
||||
auto &mutable_data() { return data_; }
|
||||
auto mutable_pixel_data() { return pixel_data_; }
|
||||
auto surface() const { return surface_.get(); }
|
||||
auto mutable_surface() { return surface_.get(); }
|
||||
void set_data(const Bytes &data) { data_ = data; }
|
||||
|
||||
auto vector() const { return data_; }
|
||||
auto at(int i) const { return data_[i]; }
|
||||
auto texture() const { return texture_.get(); }
|
||||
auto modified() const { return modified_; }
|
||||
void set_modified(bool modified) { modified_ = modified; }
|
||||
auto IsActive() const { return active_; }
|
||||
auto SetActive(bool active) { active_ = active; }
|
||||
|
||||
private:
|
||||
struct SDL_Texture_Deleter {
|
||||
@@ -71,13 +175,75 @@ class Bitmap {
|
||||
int height_ = 0;
|
||||
int depth_ = 0;
|
||||
int data_size_ = 0;
|
||||
|
||||
bool freed_ = false;
|
||||
bool active_ = false;
|
||||
bool modified_ = false;
|
||||
void *texture_pixels = nullptr;
|
||||
|
||||
uchar *pixel_data_;
|
||||
Bytes data_;
|
||||
|
||||
std::vector<uint8_t> png_data_;
|
||||
|
||||
gfx::SNESPalette palette_;
|
||||
std::shared_ptr<SDL_Texture> texture_ = nullptr;
|
||||
std::shared_ptr<SDL_Surface> surface_ = nullptr;
|
||||
std::shared_ptr<SDL_Surface> converted_surface_ = nullptr;
|
||||
};
|
||||
|
||||
using BitmapTable = std::unordered_map<int, gfx::Bitmap>;
|
||||
|
||||
class BitmapManager {
|
||||
private:
|
||||
std::unordered_map<int, std::shared_ptr<gfx::Bitmap>> bitmap_cache_;
|
||||
|
||||
public:
|
||||
void LoadBitmap(int id, const Bytes &data, int width, int height, int depth) {
|
||||
bitmap_cache_[id] =
|
||||
std::make_shared<gfx::Bitmap>(width, height, depth, data);
|
||||
}
|
||||
|
||||
std::shared_ptr<gfx::Bitmap> const &CopyBitmap(const gfx::Bitmap &bitmap,
|
||||
int id) {
|
||||
auto new_bitmap = std::make_shared<gfx::Bitmap>(
|
||||
bitmap.width(), bitmap.height(), bitmap.depth(), bitmap.vector());
|
||||
bitmap_cache_[id] = new_bitmap;
|
||||
return new_bitmap;
|
||||
}
|
||||
|
||||
std::shared_ptr<gfx::Bitmap> const &operator[](int id) {
|
||||
auto it = bitmap_cache_.find(id);
|
||||
if (it != bitmap_cache_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto mutable_bitmap(int id) { return bitmap_cache_[id]; }
|
||||
|
||||
using value_type = std::pair<const int, std::shared_ptr<gfx::Bitmap>>;
|
||||
using iterator =
|
||||
std::unordered_map<int, std::shared_ptr<gfx::Bitmap>>::iterator;
|
||||
using const_iterator =
|
||||
std::unordered_map<int, std::shared_ptr<gfx::Bitmap>>::const_iterator;
|
||||
|
||||
iterator begin() noexcept { return bitmap_cache_.begin(); }
|
||||
iterator end() noexcept { return bitmap_cache_.end(); }
|
||||
const_iterator begin() const noexcept { return bitmap_cache_.begin(); }
|
||||
const_iterator end() const noexcept { return bitmap_cache_.end(); }
|
||||
const_iterator cbegin() const noexcept { return bitmap_cache_.cbegin(); }
|
||||
const_iterator cend() const noexcept { return bitmap_cache_.cend(); }
|
||||
|
||||
std::shared_ptr<gfx::Bitmap> const &GetBitmap(int id) {
|
||||
auto it = bitmap_cache_.find(id);
|
||||
if (it != bitmap_cache_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr; // or handle the error accordingly
|
||||
}
|
||||
|
||||
void ClearCache() { bitmap_cache_.clear(); }
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
1186
src/app/gfx/compression.cc
Normal file
1186
src/app/gfx/compression.cc
Normal file
File diff suppressed because it is too large
Load Diff
213
src/app/gfx/compression.h
Normal file
213
src/app/gfx/compression.h
Normal file
@@ -0,0 +1,213 @@
|
||||
#ifndef YAZE_APP_GFX_COMPRESSION_H
|
||||
#define YAZE_APP_GFX_COMPRESSION_H
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/constants.h"
|
||||
|
||||
#define BUILD_HEADER(command, length) (command << 5) + (length - 1)
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gfx {
|
||||
|
||||
namespace lc_lz2 {
|
||||
|
||||
constexpr int kCommandDirectCopy = 0;
|
||||
constexpr int kCommandByteFill = 1;
|
||||
constexpr int kCommandWordFill = 2;
|
||||
constexpr int kCommandIncreasingFill = 3;
|
||||
constexpr int kCommandRepeatingBytes = 4;
|
||||
constexpr int kCommandLongLength = 7;
|
||||
constexpr int kMaxLengthNormalHeader = 32;
|
||||
constexpr int kMaxLengthCompression = 1024;
|
||||
constexpr int kNintendoMode1 = 0;
|
||||
constexpr int kNintendoMode2 = 1;
|
||||
constexpr int kSnesByteMax = 0xFF;
|
||||
constexpr int kCommandMod = 0x07;
|
||||
constexpr int kExpandedMod = 0xE0;
|
||||
constexpr int kExpandedLengthMod = 0x3FF;
|
||||
constexpr int kNormalLengthMod = 0x1F;
|
||||
constexpr int kCompressionStringMod = 7 << 5;
|
||||
|
||||
// Represents a command in the compression algorithm.
|
||||
struct CompressionCommand {
|
||||
// The command arguments for each possible command.
|
||||
std::array<std::array<char, 2>, 5> arguments;
|
||||
|
||||
// The size of each possible command.
|
||||
std::array<uint, 5> cmd_size;
|
||||
|
||||
// The size of the data processed by each possible command.
|
||||
std::array<uint, 5> data_size;
|
||||
};
|
||||
|
||||
using CommandArgumentArray = std::array<std::array<char, 2>, 5>;
|
||||
using CommandSizeArray = std::array<uint, 5>;
|
||||
using DataSizeArray = std::array<uint, 5>;
|
||||
|
||||
// Represents a piece of compressed data.
|
||||
struct CompressionPiece {
|
||||
char command;
|
||||
int length;
|
||||
int argument_length;
|
||||
std::string argument;
|
||||
std::shared_ptr<CompressionPiece> next = nullptr;
|
||||
CompressionPiece() = default;
|
||||
CompressionPiece(int cmd, int len, std::string args, int arg_len)
|
||||
: command(cmd), length(len), argument_length(arg_len), argument(args) {}
|
||||
};
|
||||
using CompressionPiece = struct CompressionPiece;
|
||||
using CompressionPiecePointer = std::shared_ptr<CompressionPiece>;
|
||||
|
||||
void PrintCompressionPiece(const CompressionPiecePointer& piece);
|
||||
|
||||
void PrintCompressionChain(const CompressionPiecePointer& chain_head);
|
||||
|
||||
// Compression V1
|
||||
|
||||
void CheckByteRepeat(const uchar* rom_data, DataSizeArray& data_size_taken,
|
||||
CommandArgumentArray& cmd_args, uint& src_data_pos,
|
||||
const uint last_pos);
|
||||
|
||||
void CheckWordRepeat(const uchar* rom_data, DataSizeArray& data_size_taken,
|
||||
CommandArgumentArray& cmd_args, uint& src_data_pos,
|
||||
const uint last_pos);
|
||||
|
||||
void CheckIncByte(const uchar* rom_data, DataSizeArray& data_size_taken,
|
||||
CommandArgumentArray& cmd_args, uint& src_data_pos,
|
||||
const uint last_pos);
|
||||
|
||||
void CheckIntraCopy(const uchar* rom_data, DataSizeArray& data_size_taken,
|
||||
CommandArgumentArray& cmd_args, uint& src_data_pos,
|
||||
const uint last_pos, uint start);
|
||||
|
||||
void ValidateForByteGain(const DataSizeArray& data_size_taken,
|
||||
const CommandSizeArray& cmd_size, uint& max_win,
|
||||
uint& cmd_with_max);
|
||||
|
||||
void CompressionCommandAlternative(const uchar* rom_data,
|
||||
CompressionPiecePointer& compressed_chain,
|
||||
const CommandSizeArray& cmd_size,
|
||||
const CommandArgumentArray& cmd_args,
|
||||
uint& src_data_pos, uint& comp_accumulator,
|
||||
uint& cmd_with_max, uint& max_win);
|
||||
|
||||
// Compression V2
|
||||
|
||||
void CheckByteRepeatV2(const uchar* data, uint& src_pos, const uint last_pos,
|
||||
CompressionCommand& cmd);
|
||||
|
||||
void CheckWordRepeatV2(const uchar* data, uint& src_pos, const uint last_pos,
|
||||
CompressionCommand& cmd);
|
||||
|
||||
void CheckIncByteV2(const uchar* data, uint& src_pos, const uint last_pos,
|
||||
CompressionCommand& cmd);
|
||||
|
||||
void CheckIntraCopyV2(const uchar* data, uint& src_pos, const uint last_pos,
|
||||
uint start, CompressionCommand& cmd);
|
||||
|
||||
void ValidateForByteGainV2(const CompressionCommand& cmd, uint& max_win,
|
||||
uint& cmd_with_max);
|
||||
|
||||
void CompressionCommandAlternativeV2(const uchar* data,
|
||||
const CompressionCommand& cmd,
|
||||
CompressionPiecePointer& compressed_chain,
|
||||
uint& src_pos, uint& comp_accumulator,
|
||||
uint& cmd_with_max, uint& max_win);
|
||||
|
||||
absl::StatusOr<Bytes> CompressV2(const uchar* data, const int start,
|
||||
const int length, int mode = 1,
|
||||
bool check = false);
|
||||
|
||||
absl::StatusOr<Bytes> CompressGraphics(const uchar* data, const int pos,
|
||||
const int length);
|
||||
absl::StatusOr<Bytes> CompressOverworld(const uchar* data, const int pos,
|
||||
const int length);
|
||||
|
||||
absl::StatusOr<CompressionPiecePointer> SplitCompressionPiece(
|
||||
CompressionPiecePointer& piece, int mode);
|
||||
|
||||
Bytes CreateCompressionString(CompressionPiecePointer& start, int mode);
|
||||
|
||||
absl::Status ValidateCompressionResult(CompressionPiecePointer& chain_head,
|
||||
int mode, int start, int src_data_pos);
|
||||
|
||||
CompressionPiecePointer MergeCopy(CompressionPiecePointer& start);
|
||||
|
||||
// Compression V3
|
||||
|
||||
struct CompressionContext {
|
||||
std::vector<uint8_t> data;
|
||||
std::vector<uint8_t> compressed_data;
|
||||
std::vector<CompressionPiece> compression_pieces;
|
||||
std::vector<uint8_t> compression_string;
|
||||
uint src_pos;
|
||||
uint last_pos;
|
||||
uint start;
|
||||
uint comp_accumulator = 0;
|
||||
uint cmd_with_max = kCommandDirectCopy;
|
||||
uint max_win = 0;
|
||||
CompressionCommand current_cmd = {};
|
||||
int mode;
|
||||
|
||||
// Constructor to initialize the context
|
||||
CompressionContext(const std::vector<uint8_t>& data_, const int start,
|
||||
const int length)
|
||||
: data(data_), src_pos(start), last_pos(start + length - 1), mode(0) {}
|
||||
|
||||
// Constructor to initialize the context
|
||||
CompressionContext(const std::vector<uint8_t>& data_, const int start,
|
||||
const int length, int mode_)
|
||||
: data(data_),
|
||||
src_pos(start),
|
||||
last_pos(start + length - 1),
|
||||
mode(mode_) {}
|
||||
};
|
||||
|
||||
void CheckByteRepeatV3(CompressionContext& context);
|
||||
void CheckWordRepeatV3(CompressionContext& context);
|
||||
void CheckIncByteV3(CompressionContext& context);
|
||||
void CheckIntraCopyV3(CompressionContext& context);
|
||||
|
||||
void InitializeCompression(CompressionContext& context);
|
||||
void CheckAvailableCompressionCommands(CompressionContext& context);
|
||||
void DetermineBestCompression(CompressionContext& context);
|
||||
void HandleDirectCopy(CompressionContext& context);
|
||||
void AddCompressionToChain(CompressionContext& context);
|
||||
absl::Status ValidateCompressionResultV3(const CompressionContext& context);
|
||||
|
||||
absl::StatusOr<CompressionPiece> SplitCompressionPieceV3(
|
||||
CompressionPiece& piece, int mode);
|
||||
void FinalizeCompression(CompressionContext& context);
|
||||
|
||||
absl::StatusOr<Bytes> CompressV3(const std::vector<uint8_t> data,
|
||||
const int start, const int length,
|
||||
int mode = 1, bool check = false);
|
||||
|
||||
// Decompression
|
||||
|
||||
std::string SetBuffer(const std::vector<uint8_t>& data, int src_pos,
|
||||
int comp_accumulator);
|
||||
std::string SetBuffer(const uchar* data, int src_pos, int comp_accumulator);
|
||||
void memfill(const uchar* data, Bytes& buffer, int buffer_pos, int offset,
|
||||
int length);
|
||||
|
||||
absl::StatusOr<Bytes> DecompressV2(const uchar* data, int offset,
|
||||
int size = 0x800, int mode = 1);
|
||||
absl::StatusOr<Bytes> DecompressGraphics(const uchar* data, int pos, int size);
|
||||
absl::StatusOr<Bytes> DecompressOverworld(const uchar* data, int pos, int size);
|
||||
absl::StatusOr<Bytes> DecompressOverworld(const std::vector<uint8_t> data,
|
||||
int pos, int size);
|
||||
|
||||
} // namespace lc_lz2
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GFX_COMPRESSION_H
|
||||
281
src/app/gfx/scad_format.cc
Normal file
281
src/app/gfx/scad_format.cc
Normal file
@@ -0,0 +1,281 @@
|
||||
#include "scad_format.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gfx {
|
||||
|
||||
void FindMetastamp() {
|
||||
int matching_position = -1;
|
||||
bool matched = false;
|
||||
Bytes cgx_rom;
|
||||
Bytes raw_data_;
|
||||
for (int i = 0;
|
||||
i < cgx_rom.size() - sizeof(kMatchedBytes) - kOffsetFromMatchedBytesEnd;
|
||||
i++) {
|
||||
raw_data_.push_back(cgx_rom[i]);
|
||||
bool is_match = std::equal(std::begin(kMatchedBytes),
|
||||
std::end(kMatchedBytes), &cgx_rom[i]);
|
||||
if (is_match) {
|
||||
matching_position = i;
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
int bpp_marker_position =
|
||||
matching_position + sizeof(kMatchedBytes) + kOffsetFromMatchedBytesEnd;
|
||||
int bpp_marker = cgx_rom[bpp_marker_position];
|
||||
std::string bpp_type = (bpp_marker == 0x31) ? "8bpp" : "4bpp";
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status LoadCgx(uint8_t bpp, std::string_view filename,
|
||||
std::vector<uint8_t>& cgx_data,
|
||||
std::vector<uint8_t>& cgx_loaded,
|
||||
std::vector<uint8_t>& cgx_header) {
|
||||
std::ifstream file(filename.data(), std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return absl::NotFoundError("CGX file not found.");
|
||||
}
|
||||
std::vector<uint8_t> file_content((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
cgx_data =
|
||||
std::vector<uint8_t>(file_content.begin(), file_content.end() - 0x500);
|
||||
file.seekg(cgx_data.size() + 0x100);
|
||||
cgx_header = std::vector<uint8_t>((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
|
||||
if (bpp > 8) {
|
||||
cgx_loaded = gfx::Bpp8SnesToIndexed(cgx_data, 40);
|
||||
return absl::OkStatus();
|
||||
}
|
||||
cgx_loaded = gfx::Bpp8SnesToIndexed(cgx_data, bpp);
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status LoadScr(std::string_view filename, uint8_t input_value,
|
||||
std::vector<uint8_t>& map_data) {
|
||||
std::ifstream file(filename.data(), std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return absl::NotFoundError("SCR/PNL/MAP file not found.");
|
||||
}
|
||||
|
||||
// Check if file extension is PNL
|
||||
bool pnl = false;
|
||||
if (filename.find("PNL") != std::string::npos) {
|
||||
std::vector<uint8_t> scr_data;
|
||||
map_data.resize(0x8000);
|
||||
scr_data.resize(0x8000);
|
||||
|
||||
// Read from file for 0x8000 bytes
|
||||
std::vector<uint8_t> file_content((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
scr_data = std::vector<uint8_t>(file_content.begin(), file_content.end());
|
||||
|
||||
int md = 0x100;
|
||||
|
||||
for (int i = input_value * 0x400; i < 0x1000 + input_value * 0x400;
|
||||
i += 2) {
|
||||
auto b1_pos = (i - (input_value * 0x400));
|
||||
map_data[b1_pos] = gfx::TileInfoToShort(
|
||||
gfx::GetTilesInfo((ushort)scr_data[md + (i * 2)]));
|
||||
|
||||
auto b2_pos = (i - (input_value * 0x400) + 1);
|
||||
map_data[b2_pos] = gfx::TileInfoToShort(
|
||||
gfx::GetTilesInfo((ushort)scr_data[md + (i * 2) + 2]));
|
||||
}
|
||||
// 0x900
|
||||
|
||||
} else {
|
||||
int offset = 0;
|
||||
std::vector<uint8_t> scr_data;
|
||||
map_data.resize(0x2000);
|
||||
scr_data.resize(0x2000);
|
||||
|
||||
// read from file for 0x2000 bytes
|
||||
std::vector<uint8_t> file_content((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
scr_data = std::vector<uint8_t>(file_content.begin(), file_content.end());
|
||||
|
||||
for (int i = 0; i < 0x1000 - offset; i++) {
|
||||
map_data[i] = gfx::TileInfoToShort(
|
||||
gfx::GetTilesInfo((ushort)scr_data[((i + offset) * 2)]));
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DrawScrWithCgx(uint8_t bpp, std::vector<uint8_t>& map_data,
|
||||
std::vector<uint8_t>& map_bitmap_data,
|
||||
std::vector<uint8_t>& cgx_loaded) {
|
||||
const std::vector<uint16_t> dimensions = {0x000, 0x400, 0x800, 0xC00};
|
||||
uint8_t p = 0;
|
||||
for (const auto each_dimension : dimensions) {
|
||||
p = each_dimension;
|
||||
// for each tile on the tile buffer
|
||||
for (int i = 0; i < 0x400; i++) {
|
||||
if (map_data[i + p] != 0xFFFF) {
|
||||
auto t = gfx::GetTilesInfo(map_data[i + p]);
|
||||
|
||||
for (auto yl = 0; yl < 8; yl++) {
|
||||
for (auto xl = 0; xl < 8; xl++) {
|
||||
int mx = xl * (1 - t.horizontal_mirror_) +
|
||||
(7 - xl) * (t.horizontal_mirror_);
|
||||
int my =
|
||||
yl * (1 - t.vertical_mirror_) + (7 - yl) * (t.vertical_mirror_);
|
||||
|
||||
int ty = (t.id_ / 16) * 1024;
|
||||
int tx = (t.id_ % 16) * 8;
|
||||
auto pixel = cgx_loaded[(tx + ty) + (yl * 128) + xl];
|
||||
|
||||
int index = (((i % 32) * 8) + ((i / 32) * 2048) + mx + (my * 256));
|
||||
|
||||
if (bpp != 8) {
|
||||
map_bitmap_data[index] =
|
||||
(uchar)((pixel & 0xFF) + t.palette_ * 16);
|
||||
} else {
|
||||
map_bitmap_data[index] = (uchar)(pixel & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
std::vector<SDL_Color> DecodeColFile(const std::string_view filename) {
|
||||
std::vector<SDL_Color> decoded_col;
|
||||
std::ifstream file(filename.data(), std::ios::binary | std::ios::ate);
|
||||
|
||||
if (!file.is_open()) {
|
||||
return decoded_col; // Return an empty vector if the file couldn't be
|
||||
// opened.
|
||||
}
|
||||
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<char> buffer(size);
|
||||
if (file.read(buffer.data(), size)) {
|
||||
buffer.resize(size - 0x200);
|
||||
|
||||
int k = 0;
|
||||
for (size_t i = 0; i < buffer.size() / 2; i++) {
|
||||
uint16_t current_color = static_cast<unsigned char>(buffer[k]) |
|
||||
(static_cast<unsigned char>(buffer[k + 1]) << 8);
|
||||
|
||||
SDL_Color color;
|
||||
color.r = (current_color & 31) << 3;
|
||||
color.g = ((current_color >> 5) & 31) << 3;
|
||||
color.b = ((current_color >> 10) & 31) << 3;
|
||||
color.a = (i & 0xF) == 0 ? 0 : 255;
|
||||
|
||||
decoded_col.push_back(color);
|
||||
k += 2;
|
||||
}
|
||||
}
|
||||
|
||||
return decoded_col;
|
||||
}
|
||||
|
||||
absl::Status DecodeObjFile(
|
||||
std::string_view filename, std::vector<uint8_t>& obj_data,
|
||||
std::vector<uint8_t> actual_obj_data,
|
||||
std::unordered_map<std::string, std::vector<uint8_t>> decoded_obj,
|
||||
std::vector<uint8_t>& decoded_extra_obj, int& obj_loaded) {
|
||||
std::vector<uint8_t> header_obj;
|
||||
int obj_range;
|
||||
int expected_cut;
|
||||
if (obj_loaded == 0) {
|
||||
obj_range = 0x180;
|
||||
expected_cut = 0x500;
|
||||
} else {
|
||||
obj_range = 0x300;
|
||||
expected_cut = 0x900;
|
||||
}
|
||||
|
||||
std::ifstream file(filename.data(), std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return absl::NotFoundError("OBJ file not found.");
|
||||
}
|
||||
|
||||
std::vector<uint8_t> file_content((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
obj_data = file_content;
|
||||
file.close();
|
||||
|
||||
int cut = obj_data.size() & 0x0FFF;
|
||||
actual_obj_data =
|
||||
std::vector<uint8_t>(obj_data.begin(), obj_data.end() - cut);
|
||||
decoded_extra_obj =
|
||||
std::vector<uint8_t>(obj_data.begin() + actual_obj_data.size(),
|
||||
obj_data.begin() + actual_obj_data.size() + 0x100);
|
||||
header_obj = std::vector<uint8_t>(
|
||||
actual_obj_data.begin() + actual_obj_data.size(), actual_obj_data.end());
|
||||
|
||||
if (cut > expected_cut) {
|
||||
std::vector<uint8_t> scad_data;
|
||||
int j = 0;
|
||||
int k = (obj_loaded == 0) ? 63 : 127;
|
||||
|
||||
for (size_t i = 0; i < (actual_obj_data.size() / 6); i++) {
|
||||
std::vector<uint8_t> data = {
|
||||
actual_obj_data[k * 6 + 0 + j], // display
|
||||
actual_obj_data[k * 6 + 1 + j], // unknown
|
||||
actual_obj_data[k * 6 + 2 + j], // y-disp
|
||||
actual_obj_data[k * 6 + 3 + j], // x-disp
|
||||
actual_obj_data[k * 6 + 5 + j], // props
|
||||
actual_obj_data[k * 6 + 4 + j] // tile
|
||||
};
|
||||
scad_data.insert(scad_data.end(), data.begin(), data.end());
|
||||
|
||||
k = k - 1;
|
||||
if (k == -1) {
|
||||
k = (obj_loaded == 0) ? 63 : 127;
|
||||
j = j + ((k + 1) * 6);
|
||||
}
|
||||
}
|
||||
|
||||
int extra_data_range = 0x400 * (obj_loaded + 1) + 0x100;
|
||||
for (int i = 0; i < extra_data_range; i++) {
|
||||
scad_data.push_back(header_obj[i]);
|
||||
}
|
||||
|
||||
obj_data = scad_data;
|
||||
actual_obj_data =
|
||||
std::vector<uint8_t>(obj_data.begin(), obj_data.end() - cut);
|
||||
}
|
||||
|
||||
decoded_obj.clear();
|
||||
for (int k = 0; k < 128; k++) {
|
||||
decoded_obj["frame " + std::to_string(k)] = std::vector<uint8_t>(obj_range);
|
||||
for (int i = 0; i < obj_range; i++) {
|
||||
try {
|
||||
decoded_obj["frame " + std::to_string(k)][i] =
|
||||
obj_data[i + (obj_range * k)];
|
||||
} catch (...) {
|
||||
decoded_obj["frame " + std::to_string(k)][i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
78
src/app/gfx/scad_format.h
Normal file
78
src/app/gfx/scad_format.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef YAZE_APP_GFX_scad_format_H
|
||||
#define YAZE_APP_GFX_scad_format_H
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/constants.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gfx {
|
||||
|
||||
// キャラクタ(.SCH)ファイル
|
||||
// ヘッダー情報
|
||||
// アドレス 説明
|
||||
// 00000 - 00003 ファイルタイプ "SCH"
|
||||
// 00004 - 00008 ビットモード "?BIT"
|
||||
// 00009 - 00013 バージョンナンバー "Ver-????\n"
|
||||
// 00014 - 00017 ヘッダーサイズ
|
||||
// 00018 - 0001B ハード名 "SFC" or "CGB" or "GB"
|
||||
// 0001C - 0001C BG/OBJフラグ(AGBの時)
|
||||
// 0001D - 0001D Color Pallette Number
|
||||
// 0001D - 000FF 予約
|
||||
// 00100 - 001FF Color Path
|
||||
struct CgxHeader {
|
||||
char file_type[4];
|
||||
char bit_mode[5];
|
||||
char version_number[9];
|
||||
uint32_t header_size;
|
||||
char hardware_name[4];
|
||||
uint8_t bg_obj_flag;
|
||||
uint8_t color_palette_number;
|
||||
uint8_t reserved[0xE3];
|
||||
uint8_t color_path[0x100];
|
||||
};
|
||||
|
||||
constexpr uint16_t kMatchedBytes[] = {0x4E, 0x41, 0x4B, 0x31, 0x39, 0x38, 0x39};
|
||||
constexpr uint16_t kOffsetFromMatchedBytesEnd = 0x1D;
|
||||
|
||||
void FindMetastamp();
|
||||
|
||||
absl::Status LoadScr(std::string_view filename, uint8_t input_value,
|
||||
std::vector<uint8_t>& map_data);
|
||||
|
||||
absl::Status LoadCgx(uint8_t bpp, std::string_view filename,
|
||||
std::vector<uint8_t>& cgx_data,
|
||||
std::vector<uint8_t>& cgx_loaded,
|
||||
std::vector<uint8_t>& cgx_header);
|
||||
|
||||
absl::Status DrawScrWithCgx(uint8_t bpp, std::vector<uint8_t>& map_bitmap_data,
|
||||
std::vector<uint8_t>& map_data,
|
||||
std::vector<uint8_t>& cgx_loaded);
|
||||
|
||||
std::vector<SDL_Color> DecodeColFile(const std::string_view filename);
|
||||
|
||||
absl::Status DecodeObjFile(
|
||||
std::string_view filename, std::vector<uint8_t>& obj_data,
|
||||
std::vector<uint8_t> actual_obj_data,
|
||||
std::unordered_map<std::string, std::vector<uint8_t>> decoded_obj,
|
||||
std::vector<uint8_t>& decoded_extra_obj, int& obj_loaded);
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GFX_scad_format_H
|
||||
@@ -10,91 +10,129 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_map.h" // for flat_hash_map
|
||||
#include "absl/status/status.h" // for Status
|
||||
#include "app/core/constants.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gfx {
|
||||
|
||||
ushort ConvertRGBtoSNES(const snes_color color) {
|
||||
uchar red = color.red / 8;
|
||||
uchar green = color.green / 8;
|
||||
uchar blue = color.blue / 8;
|
||||
return blue * 1024 + green * 32 + red;
|
||||
// Define a hash map to hold the addresses of different palette groups
|
||||
const absl::flat_hash_map<std::string, uint32_t> paletteGroupAddresses = {
|
||||
{"ow_main", core::overworldPaletteMain},
|
||||
{"ow_aux", core::overworldPaletteAuxialiary},
|
||||
{"ow_animated", core::overworldPaletteAnimated},
|
||||
{"hud", core::hudPalettes},
|
||||
{"global_sprites", core::globalSpritePalettesLW},
|
||||
{"armors", core::armorPalettes},
|
||||
{"swords", core::swordPalettes},
|
||||
{"shields", core::shieldPalettes},
|
||||
{"sprites_aux1", core::spritePalettesAux1},
|
||||
{"sprites_aux2", core::spritePalettesAux2},
|
||||
{"sprites_aux3", core::spritePalettesAux3},
|
||||
{"dungeon_main", core::dungeonMainPalettes},
|
||||
{"grass", core::hardcodedGrassLW},
|
||||
{"3d_object", core::triforcePalette},
|
||||
{"ow_mini_map", core::overworldMiniMapPalettes},
|
||||
};
|
||||
|
||||
// Define a hash map to hold the number of colors in each palette group
|
||||
const absl::flat_hash_map<std::string, uint32_t> paletteGroupColorCounts = {
|
||||
{"ow_main", 35}, {"ow_aux", 21}, {"ow_animated", 7},
|
||||
{"hud", 32}, {"global_sprites", 60}, {"armors", 15},
|
||||
{"swords", 3}, {"shields", 4}, {"sprites_aux1", 7},
|
||||
{"sprites_aux2", 7}, {"sprites_aux3", 7}, {"dungeon_main", 90},
|
||||
{"grass", 1}, {"3d_object", 8}, {"ow_mini_map", 128},
|
||||
};
|
||||
|
||||
constexpr uint16_t SNES_RED_MASK = 32;
|
||||
constexpr uint16_t SNES_GREEN_MASK = 32;
|
||||
constexpr uint16_t SNES_BLUE_MASK = 32;
|
||||
|
||||
constexpr uint16_t SNES_GREEN_SHIFT = 32;
|
||||
constexpr uint16_t SNES_BLUE_SHIFT = 1024;
|
||||
|
||||
uint16_t ConvertRGBtoSNES(const snes_color& color) {
|
||||
uint16_t red = color.red / 8;
|
||||
uint16_t green = color.green / 8;
|
||||
uint16_t blue = color.blue / 8;
|
||||
return (blue * SNES_BLUE_SHIFT) + (green * SNES_GREEN_SHIFT) + red;
|
||||
}
|
||||
|
||||
snes_color ConvertSNEStoRGB(const ushort color) {
|
||||
snes_color toret;
|
||||
|
||||
toret.red = ((color) % 32) * 8;
|
||||
toret.green = ((color / 32) % 32) * 8;
|
||||
toret.blue = ((color / 1024) % 32) * 8;
|
||||
|
||||
toret.red = toret.red + toret.red / 32;
|
||||
toret.green = toret.green + toret.green / 32;
|
||||
toret.blue = toret.blue + toret.blue / 32;
|
||||
return toret;
|
||||
uint16_t ConvertRGBtoSNES(const ImVec4& color) {
|
||||
snes_color new_color;
|
||||
new_color.red = color.x * 255;
|
||||
new_color.green = color.y * 255;
|
||||
new_color.blue = color.z * 255;
|
||||
return ConvertRGBtoSNES(new_color);
|
||||
}
|
||||
|
||||
snes_palette* Extract(const char* data, const unsigned int offset,
|
||||
const unsigned int palette_size) {
|
||||
snes_palette* toret = nullptr; // palette_create(palette_size, 0)
|
||||
unsigned colnum = 0;
|
||||
for (int i = 0; i < palette_size * 2; i += 2) {
|
||||
unsigned short snes_color;
|
||||
snes_color = ((uchar)data[offset + i + 1]) << 8;
|
||||
snes_color = snes_color | ((uchar)data[offset + i]);
|
||||
toret->colors[colnum] = ConvertSNEStoRGB(snes_color);
|
||||
colnum++;
|
||||
snes_color ConvertSNEStoRGB(uint16_t color_snes) {
|
||||
snes_color result;
|
||||
|
||||
result.red = (color_snes % SNES_RED_MASK) * 8;
|
||||
result.green = ((color_snes / SNES_GREEN_MASK) % SNES_GREEN_MASK) * 8;
|
||||
result.blue = ((color_snes / SNES_BLUE_SHIFT) % SNES_BLUE_MASK) * 8;
|
||||
|
||||
result.red += result.red / SNES_RED_MASK;
|
||||
result.green += result.green / SNES_GREEN_MASK;
|
||||
result.blue += result.blue / SNES_BLUE_MASK;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<snes_color> Extract(const char* data, unsigned int offset,
|
||||
unsigned int palette_size) {
|
||||
std::vector<snes_color> palette(palette_size);
|
||||
for (unsigned int i = 0; i < palette_size * 2; i += 2) {
|
||||
uint16_t snes_color = (static_cast<uint8_t>(data[offset + i + 1]) << 8) |
|
||||
static_cast<uint8_t>(data[offset + i]);
|
||||
palette[i / 2] = ConvertSNEStoRGB(snes_color);
|
||||
}
|
||||
return toret;
|
||||
return palette;
|
||||
}
|
||||
|
||||
char* Convert(const snes_palette pal) {
|
||||
char* toret = (char*)malloc(pal.size * 2);
|
||||
for (unsigned int i = 0; i < pal.size; i++) {
|
||||
unsigned short snes_data = ConvertRGBtoSNES(pal.colors[i]);
|
||||
toret[i * 2] = snes_data & 0xFF;
|
||||
toret[i * 2 + 1] = snes_data >> 8;
|
||||
std::vector<char> Convert(const std::vector<snes_color>& palette) {
|
||||
std::vector<char> data(palette.size() * 2);
|
||||
for (unsigned int i = 0; i < palette.size(); i++) {
|
||||
uint16_t snes_data = ConvertRGBtoSNES(palette[i]);
|
||||
data[i * 2] = snes_data & 0xFF;
|
||||
data[i * 2 + 1] = snes_data >> 8;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
SNESColor ReadColorFromROM(int offset, const uchar* rom) {
|
||||
short color = (ushort)((rom[offset + 1]) << 8) | rom[offset];
|
||||
snes_color new_color;
|
||||
new_color.red = (color & 0x1F) * 8;
|
||||
new_color.green = ((color >> 5) & 0x1F) * 8;
|
||||
new_color.blue = ((color >> 10) & 0x1F) * 8;
|
||||
SNESColor snes_color(new_color);
|
||||
return snes_color;
|
||||
}
|
||||
|
||||
SNESColor GetCgxColor(uint16_t color) {
|
||||
ImVec4 rgb;
|
||||
rgb.x = (color & 0x1F) * 8;
|
||||
rgb.y = ((color & 0x3E0) >> 5) * 8;
|
||||
rgb.z = ((color & 0x7C00) >> 10) * 8;
|
||||
SNESColor toret;
|
||||
toret.SetRGB(rgb);
|
||||
return toret;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
std::vector<SNESColor> GetColFileData(uchar* data) {
|
||||
std::vector<SNESColor> colors;
|
||||
colors.reserve(256);
|
||||
colors.resize(256);
|
||||
|
||||
SNESColor::SNESColor() : rgb(ImVec4(0.f, 0.f, 0.f, 0.f)) {}
|
||||
for (int i = 0; i < 512; i += 2) {
|
||||
colors[i / 2] = GetCgxColor((uint16_t)((data[i + 1] << 8) + data[i]));
|
||||
}
|
||||
|
||||
SNESColor::SNESColor(snes_color val) {
|
||||
rgb.x = val.red;
|
||||
rgb.y = val.green;
|
||||
rgb.z = val.blue;
|
||||
}
|
||||
|
||||
SNESColor::SNESColor(ImVec4 val) : rgb(val) {
|
||||
snes_color col;
|
||||
col.red = (uchar)val.x;
|
||||
col.blue = (uchar)val.y;
|
||||
col.green = (uchar)val.z;
|
||||
snes = ConvertRGBtoSNES(col);
|
||||
}
|
||||
|
||||
void SNESColor::setRgb(ImVec4 val) {
|
||||
rgb = val;
|
||||
snes_color col;
|
||||
col.red = val.x;
|
||||
col.blue = val.y;
|
||||
col.green = val.z;
|
||||
snes = ConvertRGBtoSNES(col);
|
||||
}
|
||||
|
||||
void SNESColor::setSNES(snes_color val) {
|
||||
rgb = ImVec4(val.red, val.green, val.blue, 255.f);
|
||||
}
|
||||
|
||||
void SNESColor::setSNES(uint16_t val) {
|
||||
snes = val;
|
||||
snes_color col = ConvertSNEStoRGB(val);
|
||||
rgb = ImVec4(col.red, col.green, col.blue, 0.f);
|
||||
return colors;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -110,10 +148,10 @@ SNESPalette::SNESPalette(char* data) : size_(sizeof(data) / 2) {
|
||||
assert((sizeof(data) % 4 == 0) && (sizeof(data) <= 32));
|
||||
for (unsigned i = 0; i < sizeof(data); i += 2) {
|
||||
SNESColor col;
|
||||
col.snes = static_cast<uchar>(data[i + 1]) << 8;
|
||||
col.snes = col.snes | static_cast<uchar>(data[i]);
|
||||
snes_color mColor = ConvertSNEStoRGB(col.snes);
|
||||
col.rgb = ImVec4(mColor.red, mColor.green, mColor.blue, 1.f);
|
||||
col.SetSNES(static_cast<uchar>(data[i + 1]) << 8);
|
||||
col.SetSNES(col.GetSNES() | static_cast<uchar>(data[i]));
|
||||
snes_color mColor = ConvertSNEStoRGB(col.GetSNES());
|
||||
col.SetRGB(ImVec4(mColor.red, mColor.green, mColor.blue, 1.f));
|
||||
colors.push_back(col);
|
||||
}
|
||||
}
|
||||
@@ -123,10 +161,10 @@ SNESPalette::SNESPalette(const unsigned char* snes_pal)
|
||||
assert((sizeof(snes_pal) % 4 == 0) && (sizeof(snes_pal) <= 32));
|
||||
for (unsigned i = 0; i < sizeof(snes_pal); i += 2) {
|
||||
SNESColor col;
|
||||
col.snes = snes_pal[i + 1] << (uint16_t)8;
|
||||
col.snes = col.snes | snes_pal[i];
|
||||
snes_color mColor = ConvertSNEStoRGB(col.snes);
|
||||
col.rgb = ImVec4(mColor.red, mColor.green, mColor.blue, 1.f);
|
||||
col.SetSNES(snes_pal[i + 1] << (uint16_t)8);
|
||||
col.SetSNES(col.GetSNES() | snes_pal[i]);
|
||||
snes_color mColor = ConvertSNEStoRGB(col.GetSNES());
|
||||
col.SetRGB(ImVec4(mColor.red, mColor.green, mColor.blue, 1.f));
|
||||
colors.push_back(col);
|
||||
}
|
||||
}
|
||||
@@ -134,7 +172,7 @@ SNESPalette::SNESPalette(const unsigned char* snes_pal)
|
||||
SNESPalette::SNESPalette(const std::vector<ImVec4>& cols) {
|
||||
for (const auto& each : cols) {
|
||||
SNESColor scol;
|
||||
scol.setRgb(each);
|
||||
scol.SetRGB(each);
|
||||
colors.push_back(scol);
|
||||
}
|
||||
size_ = cols.size();
|
||||
@@ -143,7 +181,7 @@ SNESPalette::SNESPalette(const std::vector<ImVec4>& cols) {
|
||||
SNESPalette::SNESPalette(const std::vector<snes_color>& cols) {
|
||||
for (const auto& each : cols) {
|
||||
SNESColor scol;
|
||||
scol.setSNES(each);
|
||||
scol.SetSNES(ConvertRGBtoSNES(each));
|
||||
colors.push_back(scol);
|
||||
}
|
||||
size_ = cols.size();
|
||||
@@ -156,32 +194,15 @@ SNESPalette::SNESPalette(const std::vector<SNESColor>& cols) {
|
||||
size_ = cols.size();
|
||||
}
|
||||
|
||||
void SNESPalette::Create(const std::vector<SNESColor>& cols) {
|
||||
for (const auto each : cols) {
|
||||
colors.push_back(each);
|
||||
}
|
||||
size_ = cols.size();
|
||||
}
|
||||
|
||||
char* SNESPalette::encode() {
|
||||
auto data = new char[size_ * 2];
|
||||
for (unsigned int i = 0; i < size_; i++) {
|
||||
std::cout << colors[i].snes << std::endl;
|
||||
data[i * 2] = (char)(colors[i].snes & 0xFF);
|
||||
data[i * 2 + 1] = (char)(colors[i].snes >> 8);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
SDL_Palette* SNESPalette::GetSDL_Palette() {
|
||||
auto sdl_palette = std::make_shared<SDL_Palette>();
|
||||
sdl_palette->ncolors = size_;
|
||||
|
||||
auto color = std::vector<SDL_Color>(size_);
|
||||
for (int i = 0; i < size_; i++) {
|
||||
color[i].r = (uint8_t)colors[i].rgb.x * 100;
|
||||
color[i].g = (uint8_t)colors[i].rgb.y * 100;
|
||||
color[i].b = (uint8_t)colors[i].rgb.z * 100;
|
||||
color[i].r = (uint8_t)colors[i].GetRGB().x * 100;
|
||||
color[i].g = (uint8_t)colors[i].GetRGB().y * 100;
|
||||
color[i].b = (uint8_t)colors[i].GetRGB().z * 100;
|
||||
color[i].a = 0;
|
||||
std::cout << "Color " << i << " added (R:" << color[i].r
|
||||
<< " G:" << color[i].g << " B:" << color[i].b << ")" << std::endl;
|
||||
@@ -190,7 +211,87 @@ SDL_Palette* SNESPalette::GetSDL_Palette() {
|
||||
return sdl_palette.get();
|
||||
}
|
||||
|
||||
PaletteGroup::PaletteGroup(uint8_t mSize) : size(mSize) {}
|
||||
SNESPalette ReadPaletteFromROM(int offset, int num_colors, const uchar* rom) {
|
||||
int color_offset = 0;
|
||||
std::vector<gfx::SNESColor> colors(num_colors);
|
||||
|
||||
while (color_offset < num_colors) {
|
||||
short color = (ushort)((rom[offset + 1]) << 8) | rom[offset];
|
||||
gfx::snes_color new_color;
|
||||
new_color.red = (color & 0x1F) * 8;
|
||||
new_color.green = ((color >> 5) & 0x1F) * 8;
|
||||
new_color.blue = ((color >> 10) & 0x1F) * 8;
|
||||
colors[color_offset].SetSNES(ConvertRGBtoSNES(new_color));
|
||||
if (color_offset == 0) {
|
||||
colors[color_offset].SetTransparent(true);
|
||||
}
|
||||
color_offset++;
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
gfx::SNESPalette palette(colors);
|
||||
return palette;
|
||||
}
|
||||
|
||||
uint32_t GetPaletteAddress(const std::string& group_name, size_t palette_index,
|
||||
size_t color_index) {
|
||||
// Retrieve the base address for the palette group
|
||||
uint32_t base_address = paletteGroupAddresses.at(group_name);
|
||||
|
||||
// Retrieve the number of colors for each palette in the group
|
||||
uint32_t colors_per_palette = paletteGroupColorCounts.at(group_name);
|
||||
|
||||
// Calculate the address for thes specified color in the ROM
|
||||
uint32_t address = base_address + (palette_index * colors_per_palette * 2) +
|
||||
(color_index * 2);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
std::array<float, 4> ToFloatArray(const SNESColor& color) {
|
||||
std::array<float, 4> colorArray;
|
||||
colorArray[0] = color.GetRGB().x / 255.0f;
|
||||
colorArray[1] = color.GetRGB().y / 255.0f;
|
||||
colorArray[2] = color.GetRGB().z / 255.0f;
|
||||
colorArray[3] = color.GetRGB().w;
|
||||
return colorArray;
|
||||
}
|
||||
|
||||
PaletteGroup::PaletteGroup(uint8_t mSize) : size_(mSize) {}
|
||||
|
||||
PaletteGroup CreatePaletteGroupFromColFile(
|
||||
std::vector<SNESColor>& palette_rows) {
|
||||
PaletteGroup toret;
|
||||
|
||||
for (int i = 0; i < palette_rows.size(); i += 8) {
|
||||
SNESPalette palette;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
palette.AddColor(palette_rows[i + j].GetRomRGB());
|
||||
}
|
||||
toret.AddPalette(palette);
|
||||
}
|
||||
return toret;
|
||||
}
|
||||
|
||||
// Take a SNESPalette with N many colors and divide it into palettes of 8 colors
|
||||
// each
|
||||
PaletteGroup CreatePaletteGroupFromLargePalette(SNESPalette& palette) {
|
||||
PaletteGroup toret;
|
||||
|
||||
std::cout << "Palette size is " << palette.size() << std::endl;
|
||||
|
||||
for (int i = 0; i < palette.size(); i += 8) {
|
||||
SNESPalette new_palette;
|
||||
if (i + 8 < palette.size()) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
new_palette.AddColor(palette[i + j]);
|
||||
}
|
||||
}
|
||||
|
||||
toret.AddPalette(new_palette);
|
||||
}
|
||||
return toret;
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace app
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/casts.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
|
||||
namespace yaze {
|
||||
@@ -18,47 +20,111 @@ namespace app {
|
||||
namespace gfx {
|
||||
|
||||
struct snes_color {
|
||||
uchar red;
|
||||
uchar blue;
|
||||
uchar green;
|
||||
uint16_t red; /**< Red component of the color. */
|
||||
uint16_t blue; /**< Blue component of the color. */
|
||||
uint16_t green; /**< Green component of the color. */
|
||||
};
|
||||
using snes_color = struct snes_color;
|
||||
|
||||
struct snes_palette {
|
||||
uint id;
|
||||
uint size;
|
||||
snes_color* colors;
|
||||
uint id; /**< ID of the palette. */
|
||||
uint size; /**< Size of the palette. */
|
||||
snes_color* colors; /**< Pointer to the colors in the palette. */
|
||||
};
|
||||
using snes_palette = struct snes_palette;
|
||||
|
||||
ushort ConvertRGBtoSNES(const snes_color color);
|
||||
snes_color ConvertSNEStoRGB(const ushort snes_color);
|
||||
snes_palette* Extract(const char* data, const unsigned int offset,
|
||||
const unsigned int palette_size);
|
||||
char* Convert(const snes_palette pal);
|
||||
uint16_t ConvertRGBtoSNES(const snes_color& color);
|
||||
uint16_t ConvertRGBtoSNES(const ImVec4& color);
|
||||
snes_color ConvertSNEStoRGB(uint16_t snes_color);
|
||||
|
||||
/**
|
||||
* @brief Extracts a vector of SNES colors from a data buffer.
|
||||
*
|
||||
* @param data The data buffer to extract from.
|
||||
* @param offset The offset in the buffer to start extracting from.
|
||||
* @param palette_size The size of the palette to extract.
|
||||
* @return A vector of SNES colors extracted from the buffer.
|
||||
*/
|
||||
std::vector<snes_color> Extract(const char* data, unsigned int offset,
|
||||
unsigned int palette_size);
|
||||
|
||||
/**
|
||||
* @brief Converts a vector of SNES colors to a vector of characters.
|
||||
*
|
||||
* @param palette The vector of SNES colors to convert.
|
||||
* @return A vector of characters representing the converted SNES colors.
|
||||
*/
|
||||
std::vector<char> Convert(const std::vector<snes_color>& palette);
|
||||
|
||||
struct SNESColor {
|
||||
SNESColor();
|
||||
explicit SNESColor(ImVec4);
|
||||
explicit SNESColor(snes_color);
|
||||
SNESColor() : rgb(0.f, 0.f, 0.f, 0.f), snes(0) {}
|
||||
|
||||
void setRgb(ImVec4);
|
||||
void setSNES(snes_color);
|
||||
void setSNES(uint16_t);
|
||||
void setTransparent(bool t) { transparent = t; }
|
||||
|
||||
auto RGB() {
|
||||
return ImVec4(rgb.x / 255, rgb.y / 255, rgb.z / 255, rgb.w);
|
||||
explicit SNESColor(const ImVec4 val) : rgb(val) {
|
||||
snes_color color;
|
||||
color.red = val.x / 255;
|
||||
color.green = val.y / 255;
|
||||
color.blue = val.z / 255;
|
||||
snes = ConvertRGBtoSNES(color);
|
||||
}
|
||||
|
||||
bool transparent = false;
|
||||
uint16_t snes = 0;
|
||||
explicit SNESColor(const snes_color val)
|
||||
: rgb(val.red, val.green, val.blue, 255.f),
|
||||
snes(ConvertRGBtoSNES(val)),
|
||||
rom_color(val) {}
|
||||
|
||||
ImVec4 GetRGB() const { return rgb; }
|
||||
void SetRGB(const ImVec4 val) {
|
||||
rgb.x = val.x / 255;
|
||||
rgb.y = val.y / 255;
|
||||
rgb.z = val.z / 255;
|
||||
snes_color color;
|
||||
color.red = val.x;
|
||||
color.green = val.y;
|
||||
color.blue = val.z;
|
||||
rom_color = color;
|
||||
snes = ConvertRGBtoSNES(color);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
snes_color GetRomRGB() const { return rom_color; }
|
||||
|
||||
uint16_t GetSNES() const { return snes; }
|
||||
void SetSNES(uint16_t val) {
|
||||
snes = val;
|
||||
snes_color col = ConvertSNEStoRGB(val);
|
||||
rgb = ImVec4(col.red, col.green, col.blue, 0.f);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
bool IsModified() const { return modified; }
|
||||
bool IsTransparent() const { return transparent; }
|
||||
void SetTransparent(bool t) { transparent = t; }
|
||||
void SetModified(bool m) { modified = m; }
|
||||
|
||||
private:
|
||||
ImVec4 rgb;
|
||||
uint16_t snes;
|
||||
snes_color rom_color;
|
||||
bool modified = false;
|
||||
bool transparent = false;
|
||||
};
|
||||
|
||||
gfx::SNESColor ReadColorFromROM(int offset, const uchar* rom);
|
||||
|
||||
SNESColor GetCgxColor(uint16_t color);
|
||||
std::vector<SNESColor> GetColFileData(uchar* data);
|
||||
|
||||
class SNESPalette {
|
||||
public:
|
||||
template <typename T>
|
||||
explicit SNESPalette(const std::vector<T>& data) {
|
||||
for (const auto& item : data) {
|
||||
colors.push_back(SNESColor(item));
|
||||
}
|
||||
}
|
||||
|
||||
SNESPalette() = default;
|
||||
|
||||
explicit SNESPalette(uint8_t mSize);
|
||||
explicit SNESPalette(char* snesPal);
|
||||
explicit SNESPalette(const unsigned char* snes_pal);
|
||||
@@ -66,50 +132,146 @@ class SNESPalette {
|
||||
explicit SNESPalette(const std::vector<snes_color>&);
|
||||
explicit SNESPalette(const std::vector<SNESColor>&);
|
||||
|
||||
void Create(const std::vector<SNESColor>&);
|
||||
void AddColor(SNESColor color) { colors.push_back(color); }
|
||||
auto GetColor(int i) const { return colors[i]; }
|
||||
SDL_Palette* GetSDL_Palette();
|
||||
|
||||
SNESColor operator[](int i) {
|
||||
void Create(const std::vector<SNESColor>& cols) {
|
||||
for (const auto& each : cols) {
|
||||
colors.push_back(each);
|
||||
}
|
||||
size_ = cols.size();
|
||||
}
|
||||
|
||||
void AddColor(SNESColor color) {
|
||||
colors.push_back(color);
|
||||
size_++;
|
||||
}
|
||||
|
||||
void AddColor(snes_color color) {
|
||||
colors.emplace_back(color);
|
||||
size_++;
|
||||
}
|
||||
|
||||
auto GetColor(int i) const {
|
||||
if (i > size_) {
|
||||
std::cout << "SNESPalette: Index out of bounds" << std::endl;
|
||||
return colors[0];
|
||||
throw std::out_of_range("SNESPalette: Index out of bounds");
|
||||
}
|
||||
return colors[i];
|
||||
}
|
||||
|
||||
char* encode();
|
||||
SDL_Palette* GetSDL_Palette();
|
||||
void Clear() {
|
||||
colors.clear();
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
int size_ = 0;
|
||||
std::vector<SNESColor> colors;
|
||||
auto size() const { return colors.size(); }
|
||||
|
||||
SNESColor& operator[](int i) {
|
||||
if (i > size_) {
|
||||
throw std::out_of_range("SNESPalette: Index out of bounds");
|
||||
}
|
||||
return colors[i];
|
||||
}
|
||||
|
||||
void operator()(int i, const SNESColor& color) {
|
||||
if (i >= size_) {
|
||||
throw std::out_of_range("SNESPalette: Index out of bounds");
|
||||
}
|
||||
colors[i] = color;
|
||||
}
|
||||
|
||||
void operator()(int i, const ImVec4& color) {
|
||||
if (i >= size_) {
|
||||
throw std::out_of_range("SNESPalette: Index out of bounds");
|
||||
}
|
||||
colors[i].SetRGB(color);
|
||||
colors[i].SetModified(true);
|
||||
}
|
||||
|
||||
SNESPalette sub_palette(int start, int end) const {
|
||||
SNESPalette pal;
|
||||
for (int i = start; i < end; i++) {
|
||||
pal.AddColor(colors[i]);
|
||||
}
|
||||
return pal;
|
||||
}
|
||||
|
||||
private:
|
||||
int size_ = 0; /**< The size of the palette. */
|
||||
std::vector<SNESColor> colors; /**< The colors in the palette. */
|
||||
};
|
||||
|
||||
SNESPalette ReadPaletteFromROM(int offset, int num_colors, const uchar* rom);
|
||||
uint32_t GetPaletteAddress(const std::string& group_name, size_t palette_index,
|
||||
size_t color_index);
|
||||
std::array<float, 4> ToFloatArray(const SNESColor& color);
|
||||
|
||||
struct PaletteGroup {
|
||||
PaletteGroup() = default;
|
||||
|
||||
explicit PaletteGroup(uint8_t mSize);
|
||||
void AddPalette(SNESPalette pal) {
|
||||
|
||||
absl::Status AddPalette(SNESPalette pal) {
|
||||
palettes.emplace_back(pal);
|
||||
size = palettes.size();
|
||||
size_ = palettes.size();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
void AddColor(SNESColor color) {
|
||||
if (size == 0) {
|
||||
SNESPalette empty_pal;
|
||||
palettes.emplace_back(empty_pal);
|
||||
|
||||
absl::Status AddColor(SNESColor color) {
|
||||
if (size_ == 0) {
|
||||
palettes.emplace_back();
|
||||
}
|
||||
palettes[0].AddColor(color);
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
palettes.clear();
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
auto size() const { return palettes.size(); }
|
||||
|
||||
SNESPalette operator[](int i) {
|
||||
if (i > size) {
|
||||
if (i > size_) {
|
||||
std::cout << "PaletteGroup: Index out of bounds" << std::endl;
|
||||
return palettes[0];
|
||||
}
|
||||
return palettes[i];
|
||||
}
|
||||
int size = 0;
|
||||
|
||||
const SNESPalette& operator[](int i) const {
|
||||
if (i > size_) {
|
||||
std::cout << "PaletteGroup: Index out of bounds" << std::endl;
|
||||
return palettes[0];
|
||||
}
|
||||
return palettes[i];
|
||||
}
|
||||
|
||||
absl::Status operator()(int i, const SNESColor& color) {
|
||||
if (i >= size_) {
|
||||
return absl::InvalidArgumentError("PaletteGroup: Index out of bounds");
|
||||
}
|
||||
palettes[i](0, color);
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status operator()(int i, const ImVec4& color) {
|
||||
if (i >= size_) {
|
||||
return absl::InvalidArgumentError("PaletteGroup: Index out of bounds");
|
||||
}
|
||||
palettes[i](0, color);
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
private:
|
||||
int size_ = 0;
|
||||
std::vector<SNESPalette> palettes;
|
||||
};
|
||||
|
||||
PaletteGroup CreatePaletteGroupFromColFile(std::vector<SNESColor>& colors);
|
||||
|
||||
PaletteGroup CreatePaletteGroupFromLargePalette(SNESPalette& palette);
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
@@ -9,6 +9,327 @@ namespace yaze {
|
||||
namespace app {
|
||||
namespace gfx {
|
||||
|
||||
tile8 UnpackBppTile(const Bytes& data, const uint32_t offset,
|
||||
const uint32_t bpp) {
|
||||
tile8 tile;
|
||||
assert(bpp >= 1 && bpp <= 8);
|
||||
unsigned int bpp_pos[8]; // More for conveniance and readibility
|
||||
for (int col = 0; col < 8; col++) {
|
||||
for (int row = 0; row < 8; row++) {
|
||||
if (bpp == 1) {
|
||||
tile.data[col * 8 + row] = (data[offset + col] >> (7 - row)) & 0x01;
|
||||
continue;
|
||||
}
|
||||
/* SNES bpp format interlace each byte of the first 2 bitplanes.
|
||||
* | byte 1 of first bitplane | byte 1 of second bitplane |
|
||||
* | byte 2 of first bitplane | byte 2 of second bitplane | ..
|
||||
*/
|
||||
bpp_pos[0] = offset + col * 2;
|
||||
bpp_pos[1] = offset + col * 2 + 1;
|
||||
char mask = 1 << (7 - row);
|
||||
tile.data[col * 8 + row] = (data[bpp_pos[0]] & mask) == mask;
|
||||
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[1]] & mask) == mask)
|
||||
<< 1;
|
||||
if (bpp == 3) {
|
||||
// When we have 3 bitplanes, the bytes for the third bitplane are after
|
||||
// the 16 bytes of the 2 bitplanes.
|
||||
bpp_pos[2] = offset + 16 + col;
|
||||
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[2]] & mask) == mask)
|
||||
<< 2;
|
||||
}
|
||||
if (bpp >= 4) {
|
||||
// For 4 bitplanes, the 2 added bitplanes are interlaced like the first
|
||||
// two.
|
||||
bpp_pos[2] = offset + 16 + col * 2;
|
||||
bpp_pos[3] = offset + 16 + col * 2 + 1;
|
||||
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[2]] & mask) == mask)
|
||||
<< 2;
|
||||
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[3]] & mask) == mask)
|
||||
<< 3;
|
||||
}
|
||||
if (bpp == 8) {
|
||||
bpp_pos[4] = offset + 32 + col * 2;
|
||||
bpp_pos[5] = offset + 32 + col * 2 + 1;
|
||||
bpp_pos[6] = offset + 48 + col * 2;
|
||||
bpp_pos[7] = offset + 48 + col * 2 + 1;
|
||||
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[4]] & mask) == mask)
|
||||
<< 4;
|
||||
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[5]] & mask) == mask)
|
||||
<< 5;
|
||||
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[6]] & mask) == mask)
|
||||
<< 6;
|
||||
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[7]] & mask) == mask)
|
||||
<< 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
|
||||
Bytes PackBppTile(const tile8& tile, const uint32_t bpp) {
|
||||
// Allocate memory for output data
|
||||
std::vector<uchar> output(bpp * 8, 0); // initialized with 0
|
||||
unsigned maxcolor = 2 << bpp;
|
||||
|
||||
// Iterate over all columns and rows of the tile
|
||||
for (unsigned int col = 0; col < 8; col++) {
|
||||
for (unsigned int row = 0; row < 8; row++) {
|
||||
uchar color = tile.data[col * 8 + row];
|
||||
if (color > maxcolor) {
|
||||
throw std::invalid_argument("Invalid color value.");
|
||||
}
|
||||
|
||||
// 1bpp format
|
||||
if (bpp == 1) output[col] += (uchar)((color & 1) << (7 - row));
|
||||
|
||||
// 2bpp format
|
||||
if (bpp >= 2) {
|
||||
output[col * 2] += (uchar)((color & 1) << (7 - row));
|
||||
output[col * 2 + 1] += (uchar)((uchar)((color & 2) == 2) << (7 - row));
|
||||
}
|
||||
|
||||
// 3bpp format
|
||||
if (bpp == 3)
|
||||
output[16 + col] += (uchar)(((color & 4) == 4) << (7 - row));
|
||||
|
||||
// 4bpp format
|
||||
if (bpp >= 4) {
|
||||
output[16 + col * 2] += (uchar)(((color & 4) == 4) << (7 - row));
|
||||
output[16 + col * 2 + 1] += (uchar)(((color & 8) == 8) << (7 - row));
|
||||
}
|
||||
|
||||
// 8bpp format
|
||||
if (bpp == 8) {
|
||||
output[32 + col * 2] += (uchar)(((color & 16) == 16) << (7 - row));
|
||||
output[32 + col * 2 + 1] += (uchar)(((color & 32) == 32) << (7 - row));
|
||||
output[48 + col * 2] += (uchar)(((color & 64) == 64) << (7 - row));
|
||||
output[48 + col * 2 + 1] +=
|
||||
(uchar)(((color & 128) == 128) << (7 - row));
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
std::vector<uchar> ConvertBpp(const std::vector<uchar>& tiles,
|
||||
uint32_t from_bpp, uint32_t to_bpp) {
|
||||
unsigned int nb_tile = tiles.size() / (from_bpp * 8);
|
||||
std::vector<uchar> converted(nb_tile * to_bpp * 8);
|
||||
|
||||
for (unsigned int i = 0; i < nb_tile; i++) {
|
||||
tile8 tile = UnpackBppTile(tiles, i * from_bpp * 8, from_bpp);
|
||||
std::vector<uchar> packed_tile = PackBppTile(tile, to_bpp);
|
||||
std::memcpy(converted.data() + i * to_bpp * 8, packed_tile.data(),
|
||||
to_bpp * 8);
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
std::vector<uchar> Convert3bppTo4bpp(const std::vector<uchar>& tiles) {
|
||||
return ConvertBpp(tiles, 3, 4);
|
||||
}
|
||||
|
||||
std::vector<uchar> Convert4bppTo3bpp(const std::vector<uchar>& tiles) {
|
||||
return ConvertBpp(tiles, 4, 3);
|
||||
}
|
||||
|
||||
Bytes SnesTo8bppSheet(Bytes sheet, int bpp) {
|
||||
int xx = 0; // positions where we are at on the sheet
|
||||
int yy = 0;
|
||||
int pos = 0;
|
||||
int ypos = 0;
|
||||
int num_tiles = 64;
|
||||
int buffer_size = 0x1000;
|
||||
if (bpp == 2) {
|
||||
bpp = 16;
|
||||
num_tiles = 128;
|
||||
buffer_size = 0x2000;
|
||||
} else if (bpp == 3) {
|
||||
bpp = 24;
|
||||
}
|
||||
Bytes sheet_buffer_out(buffer_size);
|
||||
|
||||
for (int i = 0; i < num_tiles; i++) { // for each tiles, 16 per line
|
||||
for (int y = 0; y < 8; y++) { // for each line
|
||||
for (int x = 0; x < 8; x++) { //[0] + [1] + [16]
|
||||
auto b1 = (sheet[(y * 2) + (bpp * pos)] & (kGraphicsBitmap[x]));
|
||||
auto b2 = (sheet[((y * 2) + (bpp * pos)) + 1] & (kGraphicsBitmap[x]));
|
||||
auto b3 = (sheet[(16 + y) + (bpp * pos)] & (kGraphicsBitmap[x]));
|
||||
unsigned char b = 0;
|
||||
if (b1 != 0) {
|
||||
b |= 1;
|
||||
}
|
||||
if (b2 != 0) {
|
||||
b |= 2;
|
||||
}
|
||||
if (b3 != 0 && bpp != 16) {
|
||||
b |= 4;
|
||||
}
|
||||
sheet_buffer_out[x + xx + (y * 128) + (yy * 1024)] = b;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
ypos++;
|
||||
xx += 8;
|
||||
if (ypos >= 16) {
|
||||
yy++;
|
||||
xx = 0;
|
||||
ypos = 0;
|
||||
}
|
||||
}
|
||||
return sheet_buffer_out;
|
||||
}
|
||||
|
||||
Bytes Bpp8SnesToIndexed(Bytes data, uint64_t bpp) {
|
||||
// 3BPP
|
||||
// [r0,bp1],[r0,bp2],[r1,bp1],[r1,bp2],[r2,bp1],[r2,bp2],[r3,bp1],[r3,bp2]
|
||||
// [r4,bp1],[r4,bp2],[r5,bp1],[r5,bp2],[r6,bp1],[r6,bp2],[r7,bp1],[r7,bp2]
|
||||
// [r0,bp3],[r0,bp4],[r1,bp3],[r1,bp4],[r2,bp3],[r2,bp4],[r3,bp3],[r3,bp4]
|
||||
// [r4,bp3],[r4,bp4],[r5,bp3],[r5,bp4],[r6,bp3],[r6,bp4],[r7,bp3],[r7,bp4]
|
||||
// [r0,bp5],[r0,bp6],[r1,bp5],[r1,bp6],[r2,bp5],[r2,bp6],[r3,bp5],[r3,bp6]
|
||||
// [r4,bp5],[r4,bp6],[r5,bp5],[r5,bp6],[r6,bp5],[r6,bp6],[r7,bp5],[r7,bp6]
|
||||
// [r0,bp7],[r0,bp8],[r1,bp7],[r1,bp8],[r2,bp7],[r2,bp8],[r3,bp7],[r3,bp8]
|
||||
// [r4,bp7],[r4,bp8],[r5,bp7],[r5,bp8],[r6,bp7],[r6,bp8],[r7,bp7],[r7,bp8]
|
||||
|
||||
// 16 tiles = 1024 bytes
|
||||
auto buffer = Bytes(data.size());
|
||||
std::vector<std::vector<uint8_t>> bitmap_data;
|
||||
bitmap_data.resize(0x80);
|
||||
for (auto& each : bitmap_data) {
|
||||
each.reserve(0x800);
|
||||
}
|
||||
int yy = 0;
|
||||
int xx = 0;
|
||||
int pos = 0;
|
||||
|
||||
const uint16_t sheet_width = 128;
|
||||
|
||||
// 64 = 4096 bytes
|
||||
// 16 = 1024?
|
||||
int ypos = 0;
|
||||
// for each tiles //16 per lines
|
||||
for (int i = 0; i < 4096; i++) {
|
||||
// for each lines
|
||||
for (int y = 0; y < 8; y++) {
|
||||
//[0] + [1] + [16]
|
||||
for (int x = 0; x < 8; x++) {
|
||||
const uint16_t bitmask[] = {0x80, 0x40, 0x20, 0x10,
|
||||
0x08, 0x04, 0x02, 0x01};
|
||||
auto b1 = (data[(y * 2) + ((bpp * 8) * pos)] & (bitmask[x]));
|
||||
auto b2 = (data[((y * 2) + ((bpp * 8) * pos)) + 1] & (bitmask[x]));
|
||||
auto b3 = (data[(y * 2) + ((bpp * 8) * pos) + 16] & (bitmask[x]));
|
||||
auto b4 = (data[(y * 2) + ((bpp * 8) * pos) + 17] & (bitmask[x]));
|
||||
auto b5 = (data[(y * 2) + ((bpp * 8) * pos) + 32] & (bitmask[x]));
|
||||
auto b6 = (data[(y * 2) + ((bpp * 8) * pos) + 33] & (bitmask[x]));
|
||||
auto b7 = (data[(y * 2) + ((bpp * 8) * pos) + 48] & (bitmask[x]));
|
||||
auto b8 = (data[(y * 2) + ((bpp * 8) * pos) + 49] & (bitmask[x]));
|
||||
|
||||
auto b = 0;
|
||||
if (b1 != 0) {
|
||||
b |= 1;
|
||||
}
|
||||
if (b2 != 0) {
|
||||
b |= 2;
|
||||
}
|
||||
if (bpp >= 4) {
|
||||
if (b3 != 0) {
|
||||
b |= 4;
|
||||
}
|
||||
if (b4 != 0) {
|
||||
b |= 8;
|
||||
}
|
||||
}
|
||||
if (bpp >= 8) {
|
||||
if (b5 != 0) {
|
||||
b |= 0x10;
|
||||
}
|
||||
if (b6 != 0) {
|
||||
b |= 0x20;
|
||||
}
|
||||
if (b7 != 0) {
|
||||
b |= 0x40;
|
||||
}
|
||||
if (b8 != 0) {
|
||||
b |= 0x80;
|
||||
}
|
||||
}
|
||||
// bitmap_data[((x + xx) * sheet_width) + y + (yy * 8)] = b;
|
||||
bitmap_data[x + xx][y + (yy * 8)] = b;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
ypos++;
|
||||
xx += 8;
|
||||
if (ypos >= 16) {
|
||||
yy++;
|
||||
xx = 0;
|
||||
ypos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int n = 0;
|
||||
|
||||
for (int y = 0; y < (data.size() / 64); y++) {
|
||||
for (int x = 0; x < sheet_width; x++) { // 128 assumption
|
||||
if (n < data.size()) {
|
||||
// buffer[n] = bitmap_data[(x * sheet_width) + y];
|
||||
buffer[n] = bitmap_data[x][y];
|
||||
n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
uint16_t TileInfoToWord(TileInfo tile_info) {
|
||||
uint16_t result = 0;
|
||||
|
||||
// Copy the id_ value
|
||||
result |= tile_info.id_ & 0x3FF; // ids are 10 bits
|
||||
|
||||
// Set the vertical_mirror_, horizontal_mirror_, and over_ flags
|
||||
result |= (tile_info.vertical_mirror_ ? 1 : 0) << 15;
|
||||
result |= (tile_info.horizontal_mirror_ ? 1 : 0) << 14;
|
||||
result |= (tile_info.over_ ? 1 : 0) << 13;
|
||||
|
||||
// Set the palette_
|
||||
result |= (tile_info.palette_ & 0x07) << 10; // palettes are 3 bits
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TileInfo WordToTileInfo(uint16_t word) {
|
||||
// Extract the id_ value
|
||||
uint16_t id = word & 0x3FF; // ids are 10 bits
|
||||
|
||||
// Extract the vertical_mirror_, horizontal_mirror_, and over_ flags
|
||||
bool vertical_mirror = (word >> 15) & 0x01;
|
||||
bool horizontal_mirror = (word >> 14) & 0x01;
|
||||
bool over = (word >> 13) & 0x01;
|
||||
|
||||
// Extract the palette_
|
||||
uint8_t palette = (word >> 10) & 0x07; // palettes are 3 bits
|
||||
|
||||
return TileInfo(id, palette, vertical_mirror, horizontal_mirror, over);
|
||||
}
|
||||
|
||||
ushort TileInfoToShort(TileInfo tile_info) {
|
||||
ushort result = 0;
|
||||
|
||||
// Copy the id_ value
|
||||
result |= tile_info.id_ & 0x3FF; // ids are 10 bits
|
||||
|
||||
// Set the vertical_mirror_, horizontal_mirror_, and over_ flags
|
||||
result |= (tile_info.vertical_mirror_ ? 1 : 0) << 10;
|
||||
result |= (tile_info.horizontal_mirror_ ? 1 : 0) << 11;
|
||||
result |= (tile_info.over_ ? 1 : 0) << 12;
|
||||
|
||||
// Set the palette_
|
||||
result |= (tile_info.palette_ & 0x07) << 13; // palettes are 3 bits
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TileInfo GetTilesInfo(ushort tile) {
|
||||
// vhopppcc cccccccc
|
||||
bool o = false;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define YAZE_APP_GFX_SNES_TILE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/constants.h"
|
||||
@@ -10,13 +11,30 @@ namespace yaze {
|
||||
namespace app {
|
||||
namespace gfx {
|
||||
|
||||
constexpr uchar kGraphicsBitmap[8] = {0x80, 0x40, 0x20, 0x10,
|
||||
0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
Bytes SnesTo8bppSheet(Bytes sheet, int bpp);
|
||||
Bytes Bpp8SnesToIndexed(Bytes data, uint64_t bpp = 0);
|
||||
|
||||
struct tile8 {
|
||||
unsigned int id;
|
||||
uint32_t id;
|
||||
char data[64];
|
||||
unsigned int palette_id;
|
||||
uint32_t palette_id;
|
||||
};
|
||||
using tile8 = struct tile8;
|
||||
|
||||
tile8 UnpackBppTile(const Bytes& data, const uint32_t offset,
|
||||
const uint32_t bpp);
|
||||
|
||||
Bytes PackBppTile(const tile8& tile, const uint32_t bpp);
|
||||
|
||||
std::vector<uchar> ConvertBpp(const std::vector<uchar>& tiles,
|
||||
uint32_t from_bpp, uint32_t to_bpp);
|
||||
|
||||
std::vector<uchar> Convert3bppTo4bpp(const std::vector<uchar>& tiles);
|
||||
std::vector<uchar> Convert4bppTo3bpp(const std::vector<uchar>& tiles);
|
||||
|
||||
// vhopppcc cccccccc
|
||||
// [0, 1]
|
||||
// [2, 3]
|
||||
@@ -34,19 +52,66 @@ class TileInfo {
|
||||
vertical_mirror_(v),
|
||||
horizontal_mirror_(h),
|
||||
palette_(palette) {}
|
||||
|
||||
bool operator==(const TileInfo& other) const {
|
||||
return id_ == other.id_ && over_ == other.over_ &&
|
||||
vertical_mirror_ == other.vertical_mirror_ &&
|
||||
horizontal_mirror_ == other.horizontal_mirror_ &&
|
||||
palette_ == other.palette_;
|
||||
}
|
||||
};
|
||||
|
||||
uint16_t TileInfoToWord(TileInfo tile_info);
|
||||
TileInfo WordToTileInfo(uint16_t word);
|
||||
ushort TileInfoToShort(TileInfo tile_info);
|
||||
|
||||
TileInfo GetTilesInfo(ushort tile);
|
||||
|
||||
class Tile32 {
|
||||
public:
|
||||
ushort tile0_;
|
||||
ushort tile1_;
|
||||
ushort tile2_;
|
||||
ushort tile3_;
|
||||
uint16_t tile0_;
|
||||
uint16_t tile1_;
|
||||
uint16_t tile2_;
|
||||
uint16_t tile3_;
|
||||
|
||||
Tile32(ushort t0, ushort t1, ushort t2, ushort t3)
|
||||
// Default constructor
|
||||
Tile32() : tile0_(0), tile1_(0), tile2_(0), tile3_(0) {}
|
||||
|
||||
// Parameterized constructor
|
||||
Tile32(uint16_t t0, uint16_t t1, uint16_t t2, uint16_t t3)
|
||||
: tile0_(t0), tile1_(t1), tile2_(t2), tile3_(t3) {}
|
||||
|
||||
// Copy constructor
|
||||
Tile32(const Tile32& other)
|
||||
: tile0_(other.tile0_),
|
||||
tile1_(other.tile1_),
|
||||
tile2_(other.tile2_),
|
||||
tile3_(other.tile3_) {}
|
||||
|
||||
// Constructor from packed value
|
||||
Tile32(uint64_t packedVal) {
|
||||
tile0_ = (packedVal >> 48) & 0xFFFF;
|
||||
tile1_ = (packedVal >> 32) & 0xFFFF;
|
||||
tile2_ = (packedVal >> 16) & 0xFFFF;
|
||||
tile3_ = packedVal & 0xFFFF;
|
||||
}
|
||||
|
||||
// Equality operator
|
||||
bool operator==(const Tile32& other) const {
|
||||
return tile0_ == other.tile0_ && tile1_ == other.tile1_ &&
|
||||
tile2_ == other.tile2_ && tile3_ == other.tile3_;
|
||||
}
|
||||
|
||||
// Inequality operator
|
||||
bool operator!=(const Tile32& other) const { return !(*this == other); }
|
||||
|
||||
// Get packed uint64_t representation
|
||||
uint64_t GetPackedValue() const {
|
||||
return (static_cast<uint64_t>(tile0_) << 48) |
|
||||
(static_cast<uint64_t>(tile1_) << 32) |
|
||||
(static_cast<uint64_t>(tile2_) << 16) |
|
||||
static_cast<uint64_t>(tile3_);
|
||||
}
|
||||
};
|
||||
|
||||
class Tile16 {
|
||||
@@ -65,6 +130,13 @@ class Tile16 {
|
||||
tiles_info.push_back(tile2_);
|
||||
tiles_info.push_back(tile3_);
|
||||
}
|
||||
|
||||
bool operator==(const Tile16& other) const {
|
||||
return tile0_ == other.tile0_ && tile1_ == other.tile1_ &&
|
||||
tile2_ == other.tile2_ && tile3_ == other.tile3_;
|
||||
}
|
||||
|
||||
bool operator!=(const Tile16& other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
class OAMTile {
|
||||
|
||||
487
src/app/gui/canvas.cc
Normal file
487
src/app/gui/canvas.cc
Normal file
@@ -0,0 +1,487 @@
|
||||
#include "canvas.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gui {
|
||||
|
||||
constexpr uint32_t kRectangleColor = IM_COL32(32, 32, 32, 255);
|
||||
constexpr uint32_t kRectangleBorder = IM_COL32(255, 255, 255, 255);
|
||||
constexpr ImGuiButtonFlags kMouseFlags =
|
||||
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight;
|
||||
|
||||
void Canvas::Update(const gfx::Bitmap &bitmap, ImVec2 bg_size, int tile_size,
|
||||
float scale, float grid_size) {
|
||||
if (scale != 1.0f) {
|
||||
bg_size.x *= scale / 2;
|
||||
bg_size.y *= scale / 2;
|
||||
}
|
||||
DrawBackground(bg_size);
|
||||
DrawContextMenu();
|
||||
DrawTileSelector(tile_size);
|
||||
DrawBitmap(bitmap, 2, scale);
|
||||
DrawGrid(grid_size);
|
||||
DrawOverlay();
|
||||
}
|
||||
|
||||
void Canvas::UpdateColorPainter(const gfx::Bitmap &bitmap, const ImVec4 &color,
|
||||
const std::function<void()> &event,
|
||||
ImVec2 bg_size, int tile_size, float scale,
|
||||
float grid_size) {
|
||||
global_scale_ = scale;
|
||||
DrawBackground(bg_size);
|
||||
DrawContextMenu();
|
||||
DrawBitmap(bitmap, 2, scale);
|
||||
if (DrawSolidTilePainter(color, tile_size)) {
|
||||
event();
|
||||
}
|
||||
DrawGrid(grid_size);
|
||||
DrawOverlay();
|
||||
}
|
||||
|
||||
void Canvas::UpdateEvent(const std::function<void()> &event, ImVec2 bg_size,
|
||||
int tile_size, float scale, float grid_size) {
|
||||
DrawBackground(bg_size);
|
||||
DrawContextMenu();
|
||||
event();
|
||||
DrawGrid(grid_size);
|
||||
DrawOverlay();
|
||||
}
|
||||
|
||||
void Canvas::DrawBackground(ImVec2 canvas_size) {
|
||||
canvas_p0_ = ImGui::GetCursorScreenPos();
|
||||
if (!custom_canvas_size_) canvas_sz_ = ImGui::GetContentRegionAvail();
|
||||
if (canvas_size.x != 0) canvas_sz_ = canvas_size;
|
||||
canvas_p1_ = ImVec2(canvas_p0_.x + (canvas_sz_.x * global_scale_),
|
||||
canvas_p0_.y + (canvas_sz_.y * global_scale_));
|
||||
draw_list_ = ImGui::GetWindowDrawList(); // Draw border and background color
|
||||
draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor);
|
||||
draw_list_->AddRect(canvas_p0_, canvas_p1_, kRectangleBorder);
|
||||
}
|
||||
|
||||
void Canvas::DrawContextMenu() {
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
auto scaled_sz =
|
||||
ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_);
|
||||
ImGui::InvisibleButton("canvas", scaled_sz, kMouseFlags);
|
||||
const bool is_active = ImGui::IsItemActive(); // Held
|
||||
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
|
||||
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
|
||||
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
||||
|
||||
// Pan (we use a zero mouse threshold when there's no context menu)
|
||||
if (const float mouse_threshold_for_pan = enable_context_menu_ ? -1.0f : 0.0f;
|
||||
is_active &&
|
||||
ImGui::IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) {
|
||||
scrolling_.x += io.MouseDelta.x;
|
||||
scrolling_.y += io.MouseDelta.y;
|
||||
}
|
||||
|
||||
// Context menu (under default mouse threshold)
|
||||
if (ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right);
|
||||
enable_context_menu_ && drag_delta.x == 0.0f && drag_delta.y == 0.0f)
|
||||
ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
|
||||
|
||||
// Contents of the Context Menu
|
||||
if (ImGui::BeginPopup("context")) {
|
||||
ImGui::MenuItem("Show Grid", nullptr, &enable_grid_);
|
||||
ImGui::Selectable("Show Labels", &enable_hex_tile_labels_);
|
||||
if (ImGui::MenuItem("Reset Position", nullptr, false)) {
|
||||
scrolling_.x = 0;
|
||||
scrolling_.y = 0;
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("8x8", nullptr, custom_step_ == 8.0f)) {
|
||||
custom_step_ = 8.0f;
|
||||
}
|
||||
if (ImGui::MenuItem("16x16", nullptr, custom_step_ == 16.0f)) {
|
||||
custom_step_ = 16.0f;
|
||||
}
|
||||
if (ImGui::MenuItem("32x32", nullptr, custom_step_ == 32.0f)) {
|
||||
custom_step_ = 32.0f;
|
||||
}
|
||||
if (ImGui::MenuItem("64x64", nullptr, custom_step_ == 64.0f)) {
|
||||
custom_step_ = 64.0f;
|
||||
}
|
||||
// Display bitmap metadata such as canvas size and global scale
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Canvas Size: %.0f x %.0f", canvas_sz_.x, canvas_sz_.y);
|
||||
ImGui::Text("Global Scale: %.1f", global_scale_);
|
||||
ImGui::Text("Mouse Position: %.0f x %.0f", mouse_pos.x, mouse_pos.y);
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) {
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
const bool is_hovered = ImGui::IsItemHovered();
|
||||
is_hovered_ = is_hovered;
|
||||
// Lock scrolled origin
|
||||
const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
|
||||
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
||||
|
||||
if (is_hovered) {
|
||||
// Reset the previous tile hover
|
||||
if (!points_.empty()) {
|
||||
points_.clear();
|
||||
}
|
||||
|
||||
// Calculate the coordinates of the mouse
|
||||
ImVec2 painter_pos;
|
||||
painter_pos.x = std::floor((double)mouse_pos.x / size) * size;
|
||||
painter_pos.y = std::floor((double)mouse_pos.y / size) * size;
|
||||
|
||||
auto painter_pos_end = ImVec2(painter_pos.x + size, painter_pos.y + size);
|
||||
points_.push_back(painter_pos);
|
||||
points_.push_back(painter_pos_end);
|
||||
|
||||
if (bitmap.IsActive()) {
|
||||
draw_list_->AddImage(
|
||||
(void *)bitmap.texture(),
|
||||
ImVec2(origin.x + painter_pos.x, origin.y + painter_pos.y),
|
||||
ImVec2(origin.x + painter_pos.x + bitmap.width() * scale,
|
||||
origin.y + painter_pos.y + bitmap.height() * scale));
|
||||
}
|
||||
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
// Draw the currently selected tile on the overworld here
|
||||
// Save the coordinates of the selected tile.
|
||||
drawn_tile_pos_ = io.MousePos;
|
||||
SDL_Log("Drawn tile position: %.0f, %.0f", drawn_tile_pos_.x,
|
||||
drawn_tile_pos_.y);
|
||||
return true;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Erase the hover when the mouse is not in the canvas window.
|
||||
points_.clear();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Canvas::DrawSolidTilePainter(const ImVec4 &color, int tile_size) {
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
const bool is_hovered = ImGui::IsItemHovered();
|
||||
is_hovered_ = is_hovered;
|
||||
// Lock scrolled origin
|
||||
const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
|
||||
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
||||
auto scaled_tile_size = tile_size * global_scale_;
|
||||
|
||||
static bool is_dragging = false;
|
||||
static ImVec2 start_drag_pos;
|
||||
|
||||
if (is_hovered) {
|
||||
// Reset the previous tile hover
|
||||
if (!points_.empty()) {
|
||||
points_.clear();
|
||||
}
|
||||
|
||||
// Calculate the coordinates of the mouse
|
||||
ImVec2 painter_pos;
|
||||
painter_pos.x =
|
||||
std::floor((double)mouse_pos.x / scaled_tile_size) * scaled_tile_size;
|
||||
painter_pos.y =
|
||||
std::floor((double)mouse_pos.y / scaled_tile_size) * scaled_tile_size;
|
||||
|
||||
// Clamp the size to a grid
|
||||
painter_pos.x =
|
||||
std::clamp(painter_pos.x, 0.0f, canvas_sz_.x * global_scale_);
|
||||
painter_pos.y =
|
||||
std::clamp(painter_pos.y, 0.0f, canvas_sz_.y * global_scale_);
|
||||
|
||||
auto painter_pos_end = ImVec2(painter_pos.x + scaled_tile_size,
|
||||
painter_pos.y + scaled_tile_size);
|
||||
points_.push_back(painter_pos);
|
||||
points_.push_back(painter_pos_end);
|
||||
|
||||
draw_list_->AddRectFilled(
|
||||
ImVec2(origin.x + painter_pos.x + 1, origin.y + painter_pos.y + 1),
|
||||
ImVec2(origin.x + painter_pos.x + scaled_tile_size,
|
||||
origin.y + painter_pos.y + scaled_tile_size),
|
||||
IM_COL32(color.x * 255, color.y * 255, color.z * 255, 255));
|
||||
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
is_dragging = true;
|
||||
start_drag_pos = painter_pos;
|
||||
}
|
||||
|
||||
if (is_dragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
||||
is_dragging = false;
|
||||
drawn_tile_pos_ = start_drag_pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Erase the hover when the mouse is not in the canvas window.
|
||||
points_.clear();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap &bitmap,
|
||||
ImVec4 color) {
|
||||
const ImVec2 position = drawn_tile_pos_;
|
||||
int tile_index_x = static_cast<int>(position.x / global_scale_) / tile_size;
|
||||
int tile_index_y = static_cast<int>(position.y / global_scale_) / tile_size;
|
||||
|
||||
ImVec2 start_position(tile_index_x * tile_size, tile_index_y * tile_size);
|
||||
|
||||
// Update the bitmap's pixel data based on the start_position and color
|
||||
for (int y = 0; y < tile_size; ++y) {
|
||||
for (int x = 0; x < tile_size; ++x) {
|
||||
// Calculate the actual pixel index in the bitmap
|
||||
int pixel_index =
|
||||
(start_position.y + y) * bitmap.width() + (start_position.x + x);
|
||||
|
||||
// Write the color to the pixel
|
||||
bitmap.WriteColor(pixel_index, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawTileSelector(int size) {
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
const bool is_hovered = ImGui::IsItemHovered();
|
||||
const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
|
||||
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
||||
|
||||
if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
if (!points_.empty()) {
|
||||
points_.clear();
|
||||
}
|
||||
ImVec2 painter_pos;
|
||||
painter_pos.x = std::floor((double)mouse_pos.x / size) * size;
|
||||
painter_pos.y = std::floor((double)mouse_pos.y / size) * size;
|
||||
|
||||
points_.push_back(painter_pos);
|
||||
points_.push_back(ImVec2(painter_pos.x + size, painter_pos.y + size));
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::HandleTileEdits(Canvas &blockset_canvas,
|
||||
std::vector<gfx::Bitmap> &source_blockset,
|
||||
gfx::Bitmap &destination, int ¤t_tile,
|
||||
float scale, int tile_painter_size,
|
||||
int tiles_per_row) {
|
||||
if (!blockset_canvas.Points().empty()) {
|
||||
uint16_t x = blockset_canvas.Points().front().x / 32;
|
||||
uint16_t y = blockset_canvas.Points().front().y / 32;
|
||||
current_tile = x + (y * tiles_per_row);
|
||||
if (DrawTilePainter(source_blockset[current_tile], tile_painter_size,
|
||||
scale)) {
|
||||
RenderUpdatedBitmap(drawn_tile_position(),
|
||||
source_blockset[current_tile].mutable_data(),
|
||||
destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::RenderUpdatedBitmap(const ImVec2 &click_position,
|
||||
const Bytes &tile_data,
|
||||
gfx::Bitmap &destination) {
|
||||
// Calculate the tile position relative to the current active map
|
||||
constexpr int tile_size = 16; // Tile size is 16x16 pixels
|
||||
|
||||
// Calculate the tile index for x and y based on the click_position
|
||||
int tile_index_x = (static_cast<int>(click_position.x) % 512) / tile_size;
|
||||
int tile_index_y = (static_cast<int>(click_position.y) % 512) / tile_size;
|
||||
|
||||
// Calculate the pixel start position based on tile index and tile size
|
||||
ImVec2 start_position;
|
||||
start_position.x = tile_index_x * tile_size;
|
||||
start_position.y = tile_index_y * tile_size;
|
||||
|
||||
// Update the bitmap's pixel data based on the start_position and tile_data
|
||||
for (int y = 0; y < tile_size; ++y) {
|
||||
for (int x = 0; x < tile_size; ++x) {
|
||||
int pixel_index =
|
||||
(start_position.y + y) * destination.width() + (start_position.x + x);
|
||||
destination.WriteToPixel(pixel_index, tile_data[y * tile_size + x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawBitmap(const Bitmap &bitmap, int border_offset, bool ready) {
|
||||
if (ready) {
|
||||
draw_list_->AddImage(
|
||||
(void *)bitmap.texture(),
|
||||
ImVec2(canvas_p0_.x + border_offset, canvas_p0_.y + border_offset),
|
||||
ImVec2(canvas_p0_.x + (bitmap.width() * 2),
|
||||
canvas_p0_.y + (bitmap.height() * 2)));
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawBitmap(const Bitmap &bitmap, int border_offset, float scale) {
|
||||
draw_list_->AddImage((void *)bitmap.texture(),
|
||||
ImVec2(canvas_p0_.x, canvas_p0_.y),
|
||||
ImVec2(canvas_p0_.x + (bitmap.width() * scale),
|
||||
canvas_p0_.y + (bitmap.height() * scale)));
|
||||
draw_list_->AddRect(canvas_p0_, canvas_p1_, kRectangleBorder);
|
||||
}
|
||||
|
||||
void Canvas::DrawBitmap(const Bitmap &bitmap, int x_offset, int y_offset,
|
||||
float scale) {
|
||||
draw_list_->AddImage(
|
||||
(void *)bitmap.texture(),
|
||||
ImVec2(canvas_p0_.x + x_offset + scrolling_.x,
|
||||
canvas_p0_.y + y_offset + scrolling_.y),
|
||||
ImVec2(
|
||||
canvas_p0_.x + x_offset + scrolling_.x + (bitmap.width() * scale),
|
||||
canvas_p0_.y + y_offset + scrolling_.y + (bitmap.height() * scale)));
|
||||
}
|
||||
|
||||
// TODO: Add parameters for sizing and positioning
|
||||
void Canvas::DrawBitmapTable(const BitmapTable &gfx_bin) {
|
||||
for (const auto &[key, value] : gfx_bin) {
|
||||
int offset = 0x40 * (key + 1);
|
||||
int top_left_y = canvas_p0_.y + 2;
|
||||
if (key >= 1) {
|
||||
top_left_y = canvas_p0_.y + 0x40 * key;
|
||||
}
|
||||
draw_list_->AddImage((void *)value.texture(),
|
||||
ImVec2(canvas_p0_.x + 2, top_left_y),
|
||||
ImVec2(canvas_p0_.x + 0x100, canvas_p0_.y + offset));
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawOutline(int x, int y, int w, int h) {
|
||||
ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
|
||||
canvas_p0_.y + scrolling_.y + y);
|
||||
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
|
||||
canvas_p0_.y + scrolling_.y + y + h);
|
||||
draw_list_->AddRect(origin, size, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
|
||||
void Canvas::DrawSelectRect(int tile_size, float scale) {
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
static ImVec2 drag_start_pos;
|
||||
static bool dragging = false;
|
||||
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
if (!points_.empty()) {
|
||||
points_.clear();
|
||||
}
|
||||
// Snap the start position to the nearest grid point with scaling
|
||||
// consideration
|
||||
drag_start_pos.x =
|
||||
std::floor(io.MousePos.x / (tile_size * scale)) * tile_size * scale;
|
||||
drag_start_pos.y =
|
||||
std::floor(io.MousePos.y / (tile_size * scale)) * tile_size * scale;
|
||||
dragging = true;
|
||||
}
|
||||
|
||||
if (dragging) {
|
||||
ImVec2 current_pos = io.MousePos;
|
||||
ImVec2 grid_pos;
|
||||
grid_pos.x =
|
||||
std::floor(current_pos.x / (tile_size * scale)) * tile_size * scale;
|
||||
grid_pos.y =
|
||||
std::floor(current_pos.y / (tile_size * scale)) * tile_size * scale;
|
||||
|
||||
// Calculate rect_min and rect_max considering the drag direction
|
||||
ImVec2 rect_min, rect_max;
|
||||
rect_min.x =
|
||||
(grid_pos.x < drag_start_pos.x) ? grid_pos.x : drag_start_pos.x;
|
||||
rect_min.y =
|
||||
(grid_pos.y < drag_start_pos.y) ? grid_pos.y : drag_start_pos.y;
|
||||
rect_max.x = (grid_pos.x >= drag_start_pos.x)
|
||||
? grid_pos.x + tile_size * scale
|
||||
: drag_start_pos.x + tile_size * scale;
|
||||
rect_max.y = (grid_pos.y >= drag_start_pos.y)
|
||||
? grid_pos.y + tile_size * scale
|
||||
: drag_start_pos.y + tile_size * scale;
|
||||
|
||||
draw_list_->AddRect(rect_min, rect_max, kRectangleBorder);
|
||||
|
||||
if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||
dragging = false;
|
||||
// Convert the coordinates to scale-independent form
|
||||
ImVec2 scaled_rect_min, scaled_rect_max;
|
||||
scaled_rect_min.x = rect_min.x * scale;
|
||||
scaled_rect_min.y = rect_min.y * scale;
|
||||
scaled_rect_max.x = rect_max.x * scale;
|
||||
scaled_rect_max.y = rect_max.y * scale;
|
||||
|
||||
points_.push_back(scaled_rect_min);
|
||||
points_.push_back(scaled_rect_max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
|
||||
ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
|
||||
canvas_p0_.y + scrolling_.y + y);
|
||||
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
|
||||
canvas_p0_.y + scrolling_.y + y + h);
|
||||
draw_list_->AddRectFilled(origin, size,
|
||||
IM_COL32(color.x, color.y, color.z, color.w));
|
||||
}
|
||||
|
||||
void Canvas::DrawText(std::string text, int x, int y) {
|
||||
draw_list_->AddText(
|
||||
ImVec2(canvas_p0_.x + scrolling_.x + x, canvas_p0_.y + scrolling_.y + y),
|
||||
IM_COL32(255, 255, 255, 255), text.data());
|
||||
}
|
||||
|
||||
void Canvas::DrawGrid(float grid_step) {
|
||||
// Draw grid + all lines in the canvas
|
||||
draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
|
||||
if (enable_grid_) {
|
||||
if (custom_step_ != 0.f) grid_step = custom_step_;
|
||||
|
||||
grid_step *= global_scale_; // Apply global scale to grid step
|
||||
for (float x = fmodf(scrolling_.x, grid_step);
|
||||
x < canvas_sz_.x * global_scale_; x += grid_step)
|
||||
draw_list_->AddLine(ImVec2(canvas_p0_.x + x, canvas_p0_.y),
|
||||
ImVec2(canvas_p0_.x + x, canvas_p1_.y),
|
||||
IM_COL32(200, 200, 200, 50), 0.5f);
|
||||
for (float y = fmodf(scrolling_.y, grid_step);
|
||||
y < canvas_sz_.y * global_scale_; y += grid_step)
|
||||
draw_list_->AddLine(ImVec2(canvas_p0_.x, canvas_p0_.y + y),
|
||||
ImVec2(canvas_p1_.x, canvas_p0_.y + y),
|
||||
IM_COL32(200, 200, 200, 50), 0.5f);
|
||||
|
||||
if (enable_hex_tile_labels_) {
|
||||
// Draw the hex ID of the tile in the center of the tile square
|
||||
for (float x = fmodf(scrolling_.x, grid_step);
|
||||
x < canvas_sz_.x * global_scale_; x += grid_step) {
|
||||
for (float y = fmodf(scrolling_.y, grid_step);
|
||||
y < canvas_sz_.y * global_scale_; y += grid_step) {
|
||||
int tile_x = (x - scrolling_.x) / grid_step;
|
||||
int tile_y = (y - scrolling_.y) / grid_step;
|
||||
int tile_id = tile_x + (tile_y * 16);
|
||||
std::string hex_id = absl::StrFormat("%02X", tile_id);
|
||||
draw_list_->AddText(ImVec2(canvas_p0_.x + x + (grid_step / 2) - 4,
|
||||
canvas_p0_.y + y + (grid_step / 2) - 4),
|
||||
IM_COL32(255, 255, 255, 255), hex_id.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawOverlay() {
|
||||
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
|
||||
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
|
||||
for (int n = 0; n < points_.Size; n += 2) {
|
||||
draw_list_->AddRect(
|
||||
ImVec2(origin.x + points_[n].x, origin.y + points_[n].y),
|
||||
ImVec2(origin.x + points_[n + 1].x, origin.y + points_[n + 1].y),
|
||||
IM_COL32(255, 255, 255, 255), 1.0f);
|
||||
}
|
||||
|
||||
draw_list_->PopClipRect();
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
122
src/app/gui/canvas.h
Normal file
122
src/app/gui/canvas.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#ifndef YAZE_GUI_CANVAS_H
|
||||
#define YAZE_GUI_CANVAS_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gui {
|
||||
|
||||
using app::gfx::Bitmap;
|
||||
using app::gfx::BitmapTable;
|
||||
|
||||
class Canvas {
|
||||
public:
|
||||
Canvas() = default;
|
||||
explicit Canvas(ImVec2 canvas_size)
|
||||
: custom_canvas_size_(true), canvas_sz_(canvas_size) {}
|
||||
|
||||
void Update(const gfx::Bitmap& bitmap, ImVec2 bg_size, int tile_size,
|
||||
float scale = 1.0f, float grid_size = 64.0f);
|
||||
|
||||
void UpdateColorPainter(const gfx::Bitmap& bitmap, const ImVec4& color,
|
||||
const std::function<void()>& event, ImVec2 bg_size,
|
||||
int tile_size, float scale = 1.0f,
|
||||
float grid_size = 64.0f);
|
||||
|
||||
void UpdateEvent(const std::function<void()>& event, ImVec2 bg_size,
|
||||
int tile_size, float scale = 1.0f, float grid_size = 64.0f);
|
||||
|
||||
// Background for the Canvas represents region without any content drawn to
|
||||
// it, but can be controlled by the user.
|
||||
void DrawBackground(ImVec2 canvas_size = ImVec2(0, 0));
|
||||
|
||||
// Context Menu refers to what happens when the right mouse button is pressed
|
||||
// This routine also handles the scrolling for the canvas.
|
||||
void DrawContextMenu();
|
||||
|
||||
// Tile painter shows a preview of the currently selected tile
|
||||
// and allows the user to left click to paint the tile or right
|
||||
// click to select a new tile to paint with.
|
||||
bool DrawTilePainter(const Bitmap& bitmap, int size, float scale = 1.0f);
|
||||
bool DrawSolidTilePainter(const ImVec4& color, int size);
|
||||
|
||||
// Draws a tile on the canvas at the specified position
|
||||
void DrawTileOnBitmap(int tile_size, gfx::Bitmap& bitmap, ImVec4 color);
|
||||
|
||||
// Dictates which tile is currently selected based on what the user clicks
|
||||
// in the canvas window. Represented and split apart into a grid of tiles.
|
||||
void DrawTileSelector(int size);
|
||||
|
||||
void HandleTileEdits(Canvas& blockset_canvas,
|
||||
std::vector<gfx::Bitmap>& source_blockset,
|
||||
gfx::Bitmap& destination, int& current_tile,
|
||||
float scale = 1.0f, int tile_painter_size = 16,
|
||||
int tiles_per_row = 8);
|
||||
|
||||
void RenderUpdatedBitmap(const ImVec2& click_position, const Bytes& tile_data,
|
||||
gfx::Bitmap& destination);
|
||||
|
||||
// Draws the contents of the Bitmap image to the Canvas
|
||||
void DrawBitmap(const Bitmap& bitmap, int border_offset = 0,
|
||||
bool ready = true);
|
||||
void DrawBitmap(const Bitmap& bitmap, int border_offset, float scale);
|
||||
void DrawBitmap(const Bitmap& bitmap, int x_offset = 0, int y_offset = 0,
|
||||
float scale = 1.0f);
|
||||
|
||||
void DrawBitmapTable(const BitmapTable& gfx_bin);
|
||||
void DrawOutline(int x, int y, int w, int h);
|
||||
void DrawSelectRect(int tile_size, float scale = 1.0f);
|
||||
void DrawRect(int x, int y, int w, int h, ImVec4 color);
|
||||
void DrawText(std::string text, int x, int y);
|
||||
void DrawGrid(float grid_step = 64.0f);
|
||||
void DrawOverlay(); // last
|
||||
|
||||
auto Points() const { return points_; }
|
||||
auto GetDrawList() const { return draw_list_; }
|
||||
auto zero_point() const { return canvas_p0_; }
|
||||
auto Scrolling() const { return scrolling_; }
|
||||
auto drawn_tile_position() const { return drawn_tile_pos_; }
|
||||
auto canvas_size() const { return canvas_sz_; }
|
||||
void SetCanvasSize(ImVec2 canvas_size) {
|
||||
canvas_sz_ = canvas_size;
|
||||
custom_canvas_size_ = true;
|
||||
}
|
||||
auto IsMouseHovering() const { return is_hovered_; }
|
||||
void ZoomIn() { global_scale_ += 0.1f; }
|
||||
void ZoomOut() { global_scale_ -= 0.1f; }
|
||||
|
||||
void set_global_scale(float scale) { global_scale_ = scale; }
|
||||
auto global_scale() const { return global_scale_; }
|
||||
|
||||
private:
|
||||
bool enable_grid_ = true;
|
||||
bool enable_hex_tile_labels_ = false;
|
||||
bool enable_context_menu_ = true;
|
||||
bool custom_canvas_size_ = false;
|
||||
bool is_hovered_ = false;
|
||||
|
||||
float custom_step_ = 0.0f;
|
||||
float global_scale_ = 1.0f;
|
||||
|
||||
ImDrawList* draw_list_;
|
||||
ImVector<ImVec2> points_;
|
||||
ImVec2 scrolling_;
|
||||
ImVec2 canvas_sz_;
|
||||
ImVec2 canvas_p0_;
|
||||
ImVec2 canvas_p1_;
|
||||
ImVec2 mouse_pos_in_canvas_;
|
||||
ImVec2 drawn_tile_pos_;
|
||||
};
|
||||
|
||||
} // namespace gui
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
110
src/app/gui/color.cc
Normal file
110
src/app/gui/color.cc
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "color.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gui {
|
||||
|
||||
ImVec4 ConvertSNESColorToImVec4(const SNESColor& color) {
|
||||
return ImVec4(static_cast<float>(color.GetRGB().x) / 255.0f,
|
||||
static_cast<float>(color.GetRGB().y) / 255.0f,
|
||||
static_cast<float>(color.GetRGB().z) / 255.0f,
|
||||
1.0f // Assuming alpha is always fully opaque for SNES colors,
|
||||
// adjust if necessary
|
||||
);
|
||||
}
|
||||
|
||||
IMGUI_API bool SNESColorButton(absl::string_view id, SNESColor& color,
|
||||
ImGuiColorEditFlags flags,
|
||||
const ImVec2& size_arg) {
|
||||
// Convert the SNES color values to ImGui color values (normalized to 0-1
|
||||
// range)
|
||||
ImVec4 displayColor = ConvertSNESColorToImVec4(color);
|
||||
|
||||
// Call the original ImGui::ColorButton with the converted color
|
||||
bool pressed = ImGui::ColorButton(id.data(), displayColor, flags, size_arg);
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
void DisplayPalette(app::gfx::SNESPalette& palette, bool loaded) {
|
||||
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
|
||||
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
|
||||
ImGuiColorEditFlags_NoDragDrop |
|
||||
ImGuiColorEditFlags_NoOptions;
|
||||
|
||||
// Generate a default palette. The palette will persist and can be edited.
|
||||
static bool init = false;
|
||||
static ImVec4 saved_palette[32] = {};
|
||||
if (loaded && !init) {
|
||||
for (int n = 0; n < palette.size(); n++) {
|
||||
saved_palette[n].x = palette.GetColor(n).GetRGB().x / 255;
|
||||
saved_palette[n].y = palette.GetColor(n).GetRGB().y / 255;
|
||||
saved_palette[n].z = palette.GetColor(n).GetRGB().z / 255;
|
||||
saved_palette[n].w = 255; // Alpha
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
static ImVec4 backup_color;
|
||||
ImGui::Text("Current ==>");
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Previous");
|
||||
|
||||
ImGui::ColorButton(
|
||||
"##current", color,
|
||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||
ImVec2(60, 40));
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::ColorButton(
|
||||
"##previous", backup_color,
|
||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||
ImVec2(60, 40)))
|
||||
color = backup_color;
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::BeginGroup(); // Lock X position
|
||||
ImGui::Text("Palette");
|
||||
for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) {
|
||||
ImGui::PushID(n);
|
||||
if ((n % 4) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha |
|
||||
ImGuiColorEditFlags_NoPicker |
|
||||
ImGuiColorEditFlags_NoTooltip;
|
||||
if (ImGui::ColorButton("##palette", saved_palette[n], palette_button_flags,
|
||||
ImVec2(20, 20)))
|
||||
color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z,
|
||||
color.w); // Preserve alpha!
|
||||
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
|
||||
memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3);
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
|
||||
memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4);
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::ColorPicker4("##picker", (float*)&color,
|
||||
misc_flags | ImGuiColorEditFlags_NoSidePreview |
|
||||
ImGuiColorEditFlags_NoSmallPreview);
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
33
src/app/gui/color.h
Normal file
33
src/app/gui/color.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef YAZE_GUI_COLOR_H
|
||||
#define YAZE_GUI_COLOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gui {
|
||||
|
||||
using gfx::SNESColor;
|
||||
|
||||
// A utility function to convert an SNESColor object to an ImVec4 with
|
||||
// normalized color values
|
||||
ImVec4 ConvertSNESColorToImVec4(const SNESColor& color);
|
||||
|
||||
// The wrapper function for ImGui::ColorButton that takes a SNESColor reference
|
||||
IMGUI_API bool SNESColorButton(absl::string_view id, SNESColor& color,
|
||||
ImGuiColorEditFlags flags = 0,
|
||||
const ImVec2& size_arg = ImVec2(0, 0));
|
||||
|
||||
void DisplayPalette(app::gfx::SNESPalette& palette, bool loaded);
|
||||
|
||||
} // namespace gui
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
2192
src/app/gui/icons.h
Normal file
2192
src/app/gui/icons.h
Normal file
File diff suppressed because it is too large
Load Diff
181
src/app/gui/input.cc
Normal file
181
src/app/gui/input.cc
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "input.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(
|
||||
ImGuiDataType data_type, const char* format) {
|
||||
if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
|
||||
return ImGuiInputTextFlags_CharsScientific;
|
||||
const char format_last_char = format[0] ? format[strlen(format) - 1] : 0;
|
||||
return (format_last_char == 'x' || format_last_char == 'X')
|
||||
? ImGuiInputTextFlags_CharsHexadecimal
|
||||
: ImGuiInputTextFlags_CharsDecimal;
|
||||
}
|
||||
|
||||
bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data,
|
||||
const void* p_step, const void* p_step_fast,
|
||||
const char* format, float input_width,
|
||||
ImGuiInputTextFlags flags) {
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
if (window->SkipItems) return false;
|
||||
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiStyle& style = g.Style;
|
||||
|
||||
if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt;
|
||||
|
||||
char buf[64];
|
||||
DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
|
||||
|
||||
if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal |
|
||||
ImGuiInputTextFlags_CharsHexadecimal |
|
||||
ImGuiInputTextFlags_CharsScientific)) == 0)
|
||||
flags |= InputScalar_DefaultCharsFilter(data_type, format);
|
||||
flags |= ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited;
|
||||
|
||||
bool value_changed = false;
|
||||
if (p_step == NULL) {
|
||||
ImGui::SetNextItemWidth(input_width);
|
||||
if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
|
||||
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
|
||||
} else {
|
||||
const float button_size = GetFrameHeight();
|
||||
|
||||
BeginGroup(); // The only purpose of the group here is to allow the caller
|
||||
// to query item data e.g. IsItemActive()
|
||||
PushID(label);
|
||||
SetNextItemWidth(ImMax(
|
||||
1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
|
||||
|
||||
// Place the label on the left of the input field
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
|
||||
ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
|
||||
ImVec2{style.FramePadding.x, style.FramePadding.y});
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("%s", label);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(input_width);
|
||||
if (InputText("", buf, IM_ARRAYSIZE(buf),
|
||||
flags)) // PushId(label) + "" gives us the expected ID
|
||||
// from outside point of view
|
||||
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(
|
||||
g.LastItemData.ID, label,
|
||||
g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
|
||||
|
||||
// Step buttons
|
||||
const ImVec2 backup_frame_padding = style.FramePadding;
|
||||
style.FramePadding.x = style.FramePadding.y;
|
||||
ImGuiButtonFlags button_flags =
|
||||
ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups;
|
||||
if (flags & ImGuiInputTextFlags_ReadOnly) BeginDisabled();
|
||||
SameLine(0, style.ItemInnerSpacing.x);
|
||||
if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) {
|
||||
DataTypeApplyOp(data_type, '-', p_data, p_data,
|
||||
g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
|
||||
value_changed = true;
|
||||
}
|
||||
SameLine(0, style.ItemInnerSpacing.x);
|
||||
if (ButtonEx("+", ImVec2(button_size, button_size), button_flags)) {
|
||||
DataTypeApplyOp(data_type, '+', p_data, p_data,
|
||||
g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
|
||||
value_changed = true;
|
||||
}
|
||||
if (flags & ImGuiInputTextFlags_ReadOnly) EndDisabled();
|
||||
|
||||
style.FramePadding = backup_frame_padding;
|
||||
|
||||
PopID();
|
||||
EndGroup();
|
||||
ImGui::PopStyleVar(2);
|
||||
}
|
||||
if (value_changed) MarkItemEdited(g.LastItemData.ID);
|
||||
|
||||
return value_changed;
|
||||
}
|
||||
} // namespace ImGui
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gui {
|
||||
|
||||
const int kStepOneHex = 0x01;
|
||||
const int kStepFastHex = 0x0F;
|
||||
|
||||
bool InputHex(const char* label, uint64_t* data) {
|
||||
return ImGui::InputScalar(label, ImGuiDataType_U64, data, &kStepOneHex,
|
||||
&kStepFastHex, "%06X",
|
||||
ImGuiInputTextFlags_CharsHexadecimal);
|
||||
}
|
||||
|
||||
bool InputHexShort(const char* label, uint32_t* data) {
|
||||
return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex,
|
||||
&kStepFastHex, "%06X",
|
||||
ImGuiInputTextFlags_CharsHexadecimal);
|
||||
}
|
||||
|
||||
bool InputHexWord(const char* label, uint16_t* data, float input_width) {
|
||||
return ImGui::InputScalarLeft(label, ImGuiDataType_U16, data, &kStepOneHex,
|
||||
&kStepFastHex, "%04X", input_width,
|
||||
ImGuiInputTextFlags_CharsHexadecimal);
|
||||
}
|
||||
|
||||
bool InputHexByte(const char* label, uint8_t* data, uint8_t step,
|
||||
float input_width) {
|
||||
return ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &step,
|
||||
&kStepFastHex, "%02X", input_width,
|
||||
ImGuiInputTextFlags_CharsHexadecimal);
|
||||
}
|
||||
|
||||
void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
const ImVec2 lineStart = ImGui::GetCursorScreenPos();
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
float fullWidth = ImGui::GetContentRegionAvail().x;
|
||||
float itemWidth = ImGui::CalcItemWidth() + style.ItemSpacing.x;
|
||||
ImVec2 textSize = ImGui::CalcTextSize(title.begin(), title.end());
|
||||
ImRect textRect;
|
||||
textRect.Min = ImGui::GetCursorScreenPos();
|
||||
if (flags & ItemLabelFlag::Right) textRect.Min.x = textRect.Min.x + itemWidth;
|
||||
textRect.Max = textRect.Min;
|
||||
textRect.Max.x += fullWidth - itemWidth;
|
||||
textRect.Max.y += textSize.y;
|
||||
|
||||
ImGui::SetCursorScreenPos(textRect.Min);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
// Adjust text rect manually because we render it directly into a drawlist
|
||||
// instead of using public functions.
|
||||
textRect.Min.y += window->DC.CurrLineTextBaseOffset;
|
||||
textRect.Max.y += window->DC.CurrLineTextBaseOffset;
|
||||
|
||||
ImGui::ItemSize(textRect);
|
||||
if (ImGui::ItemAdd(
|
||||
textRect, window->GetID(title.data(), title.data() + title.size()))) {
|
||||
ImGui::RenderTextEllipsis(
|
||||
ImGui::GetWindowDrawList(), textRect.Min, textRect.Max, textRect.Max.x,
|
||||
textRect.Max.x, title.data(), title.data() + title.size(), &textSize);
|
||||
|
||||
if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%.*s", (int)title.size(), title.data());
|
||||
}
|
||||
if (flags & ItemLabelFlag::Left) {
|
||||
ImVec2 result;
|
||||
auto other = ImVec2{0, textSize.y + window->DC.CurrLineTextBaseOffset};
|
||||
result.x = textRect.Max.x - other.x;
|
||||
result.y = textRect.Max.y - other.y;
|
||||
ImGui::SetCursorScreenPos(result);
|
||||
ImGui::SameLine();
|
||||
} else if (flags & ItemLabelFlag::Right)
|
||||
ImGui::SetCursorScreenPos(lineStart);
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
37
src/app/gui/input.h
Normal file
37
src/app/gui/input.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef YAZE_APP_CORE_INPUT_H
|
||||
#define YAZE_APP_CORE_INPUT_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gui {
|
||||
|
||||
constexpr ImVec2 kDefaultModalSize = ImVec2(200, 0);
|
||||
constexpr ImVec2 kZeroPos = ImVec2(0, 0);
|
||||
|
||||
IMGUI_API bool InputHex(const char* label, uint64_t* data);
|
||||
IMGUI_API bool InputHexShort(const char* label, uint32_t* data);
|
||||
IMGUI_API bool InputHexWord(const char* label, uint16_t* data,
|
||||
float input_width = 50.f);
|
||||
IMGUI_API bool InputHexByte(const char* label, uint8_t* data, uint8_t step = 0x01,
|
||||
float input_width = 50.f);
|
||||
|
||||
using ItemLabelFlags = enum ItemLabelFlag {
|
||||
Left = 1u << 0u,
|
||||
Right = 1u << 1u,
|
||||
Default = Left,
|
||||
};
|
||||
|
||||
IMGUI_API void ItemLabel(absl::string_view title, ItemLabelFlags flags);
|
||||
|
||||
} // namespace gui
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
189
src/app/gui/pipeline.cc
Normal file
189
src/app/gui/pipeline.cc
Normal file
@@ -0,0 +1,189 @@
|
||||
#include "pipeline.h"
|
||||
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/color.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gui {
|
||||
|
||||
void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics,
|
||||
gfx::SNESPalette& palette) {
|
||||
const auto palette_row_size = 7;
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)100);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
ImGui::BeginGroup(); // Lock X position
|
||||
ImGui::Text("Palette");
|
||||
for (int n = 0; n < palette.size(); n++) {
|
||||
ImGui::PushID(n);
|
||||
if ((n % palette_row_size) != 0)
|
||||
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
// Check if the current row is selected
|
||||
bool is_selected = (palette_id == n / palette_row_size);
|
||||
|
||||
// Add outline rectangle to the selected row
|
||||
if (is_selected) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
|
||||
}
|
||||
|
||||
if (gui::SNESColorButton("##palette", palette[n],
|
||||
ImGuiColorEditFlags_NoAlpha |
|
||||
ImGuiColorEditFlags_NoPicker |
|
||||
ImGuiColorEditFlags_NoTooltip,
|
||||
ImVec2(20, 20))) {
|
||||
palette_id = n / palette_row_size;
|
||||
refresh_graphics = true;
|
||||
}
|
||||
|
||||
if (is_selected) {
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void GraphicsBinCanvasPipeline(int width, int height, int tile_size,
|
||||
int num_sheets_to_load, int canvas_id,
|
||||
bool is_loaded, gfx::BitmapTable& graphics_bin) {
|
||||
gui::Canvas canvas;
|
||||
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)canvas_id);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
canvas.DrawBackground(ImVec2(width + 1, num_sheets_to_load * height + 1));
|
||||
canvas.DrawContextMenu();
|
||||
if (is_loaded) {
|
||||
for (const auto& [key, value] : graphics_bin) {
|
||||
int offset = height * (key + 1);
|
||||
int top_left_y = canvas.zero_point().y + 2;
|
||||
if (key >= 1) {
|
||||
top_left_y = canvas.zero_point().y + height * key;
|
||||
}
|
||||
canvas.GetDrawList()->AddImage(
|
||||
(void*)value.texture(),
|
||||
ImVec2(canvas.zero_point().x + 2, top_left_y),
|
||||
ImVec2(canvas.zero_point().x + 0x100,
|
||||
canvas.zero_point().y + offset));
|
||||
}
|
||||
}
|
||||
canvas.DrawTileSelector(tile_size);
|
||||
canvas.DrawGrid(tile_size);
|
||||
canvas.DrawOverlay();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void GraphicsManagerCanvasPipeline(int width, int height, int tile_size,
|
||||
int num_sheets, int canvas_id,
|
||||
bool is_loaded,
|
||||
const gfx::BitmapManager& graphics_manager) {
|
||||
gui::Canvas canvas;
|
||||
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)canvas_id);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
canvas.DrawBackground(ImVec2(width + 1, num_sheets * height + 1));
|
||||
canvas.DrawContextMenu();
|
||||
if (is_loaded) {
|
||||
for (const auto& [key, value] : graphics_manager) {
|
||||
int offset = height * (key + 1);
|
||||
int top_left_y = canvas.zero_point().y + 2;
|
||||
if (key >= 1) {
|
||||
top_left_y = canvas.zero_point().y + height * key;
|
||||
}
|
||||
canvas.GetDrawList()->AddImage(
|
||||
(void*)value->texture(),
|
||||
ImVec2(canvas.zero_point().x + 2, top_left_y),
|
||||
ImVec2(canvas.zero_point().x + 0x100,
|
||||
canvas.zero_point().y + offset));
|
||||
}
|
||||
}
|
||||
canvas.DrawTileSelector(tile_size);
|
||||
canvas.DrawGrid(tile_size);
|
||||
canvas.DrawOverlay();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void ButtonPipe(absl::string_view button_text, std::function<void()> callback) {
|
||||
if (ImGui::Button(button_text.data())) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapCanvasPipeline(gui::Canvas& canvas, const gfx::Bitmap& bitmap,
|
||||
int width, int height, int tile_size, bool is_loaded,
|
||||
bool scrollbar, int canvas_id) {
|
||||
auto draw_canvas = [](gui::Canvas& canvas, const gfx::Bitmap& bitmap,
|
||||
int width, int height, int tile_size, bool is_loaded) {
|
||||
canvas.DrawBackground(ImVec2(width + 1, height + 1));
|
||||
canvas.DrawContextMenu();
|
||||
canvas.DrawBitmap(bitmap, 2, is_loaded);
|
||||
canvas.DrawTileSelector(tile_size);
|
||||
canvas.DrawGrid(tile_size);
|
||||
canvas.DrawOverlay();
|
||||
};
|
||||
|
||||
if (scrollbar) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)canvas_id);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
} else {
|
||||
draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildAndRenderBitmapPipeline(int width, int height, int depth, Bytes data,
|
||||
ROM& z3_rom, gfx::Bitmap& bitmap,
|
||||
gfx::SNESPalette& palette) {
|
||||
bitmap.Create(width, height, depth, data);
|
||||
bitmap.ApplyPalette(palette);
|
||||
z3_rom.RenderBitmap(&bitmap);
|
||||
}
|
||||
|
||||
void FileDialogPipeline(absl::string_view display_key,
|
||||
absl::string_view file_extensions,
|
||||
std::optional<absl::string_view> button_text,
|
||||
std::function<void()> callback) {
|
||||
if (button_text.has_value() && ImGui::Button(button_text->data())) {
|
||||
ImGuiFileDialog::Instance()->OpenDialog(display_key.data(), "Choose File",
|
||||
file_extensions.data(), ".");
|
||||
}
|
||||
|
||||
if (ImGuiFileDialog::Instance()->Display(
|
||||
display_key.data(), ImGuiWindowFlags_NoCollapse, ImVec2(600, 400))) {
|
||||
if (ImGuiFileDialog::Instance()->IsOk()) {
|
||||
callback();
|
||||
}
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
54
src/app/gui/pipeline.h
Normal file
54
src/app/gui/pipeline.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef YAZE_APP_CORE_PIPELINE_H
|
||||
#define YAZE_APP_CORE_PIPELINE_H
|
||||
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gui {
|
||||
|
||||
void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics,
|
||||
gfx::SNESPalette& palette);
|
||||
|
||||
void GraphicsBinCanvasPipeline(int width, int height, int tile_size,
|
||||
int num_sheets_to_load, int canvas_id,
|
||||
bool is_loaded, gfx::BitmapTable& graphics_bin);
|
||||
|
||||
void ButtonPipe(absl::string_view button_text, std::function<void()> callback);
|
||||
|
||||
void BitmapCanvasPipeline(gui::Canvas& canvas, const gfx::Bitmap& bitmap,
|
||||
int width, int height, int tile_size, bool is_loaded,
|
||||
bool scrollbar, int canvas_id);
|
||||
|
||||
void GraphicsManagerCanvasPipeline(int width, int height, int tile_size,
|
||||
int num_sheets, int canvas_id,
|
||||
bool is_loaded,
|
||||
const gfx::BitmapManager& graphics_manager);
|
||||
|
||||
void BuildAndRenderBitmapPipeline(int width, int height, int depth, Bytes data,
|
||||
ROM& z3_rom, gfx::Bitmap& bitmap,
|
||||
gfx::SNESPalette& palette);
|
||||
|
||||
void FileDialogPipeline(absl::string_view display_key,
|
||||
absl::string_view file_extensions,
|
||||
std::optional<absl::string_view> button_text,
|
||||
std::function<void()> callback);
|
||||
|
||||
} // namespace core
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user