2 Commits

Author SHA1 Message Date
scawful
92cc574e15 backend-infra-engineer: Pre-0.2.2 2024 Q2 snapshot 2024-04-14 15:49:57 -05:00
scawful
546093360f backend-infra-engineer: Pre-0.2.2 2024 Q1 snapshot 2024-02-09 21:44:12 -05:00
136 changed files with 14493 additions and 5129 deletions

45
.github/workflows/doxy.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Doxygen Action
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ master ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
# Delete the html directory if it exists
- name: Delete html directory
run: rm -rf html
# Installs graphviz for DOT graphs
- name: Install graphviz
run: sudo apt-get install graphviz
- name: Doxygen Action
uses: mattnotmitt/doxygen-action@v1.1.0
with:
# Path to Doxyfile
doxyfile-path: "./Doxyfile" # default is ./Doxyfile
# Working directory
working-directory: "." # default is .
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Default Doxyfile build documentation to html directory.
# Change the directory if changes in Doxyfile
publish_dir: ./html

2857
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,6 @@
# Build Instructions
## Windows
For VSCode users, use the following CMake extensions with MinGW-w64 For VSCode users, use the following CMake extensions with MinGW-w64
@@ -15,3 +18,10 @@ Install the following packages using `pacman -S <package-name>`
`mingw-w64-x86_64-cmake` `mingw-w64-x86_64-cmake`
`mingw-w64-x86_64-glew` `mingw-w64-x86_64-glew`
`mingw-w64-x86_64-lib-png` `mingw-w64-x86_64-lib-png`
# macOS
- Clang 15.0.1 x86_64-apple-darrwin22.5.0
- SDL2 Source v2.26.5
- Removed snes_spc
- Removed asar_static

View File

@@ -2,6 +2,8 @@
The compression algorithm has multiple implementations with varying levels of quality, based primarily on the implementations made in skarsnik/sneshacking, Zarby89/ZScreamDungeon and ZCompress with optimizations made for C++. The compression algorithm has multiple implementations with varying levels of quality, based primarily on the implementations made in skarsnik/sneshacking, Zarby89/ZScreamDungeon and ZCompress with optimizations made for C++.
Currently, the Compress and Uncompress methods from Hyrule Magic are used and all other compression methods are considered deprecated.
## Key Definitions ## Key Definitions
### Constants and Macros: ### Constants and Macros:
@@ -62,6 +64,3 @@ Using `CompressionContext` to handle compression.
- **Compression String Creation**: `CreateCompressionString` - **Compression String Creation**: `CreateCompressionString`
- **Compression Result Validation**: Such as `ValidateCompressionResult` and its V3 variant. - **Compression Result Validation**: Such as `ValidateCompressionResult` and its V3 variant.
- **Compression Piece Manipulation**: Like `SplitCompressionPiece` and its V3 variant. - **Compression Piece Manipulation**: Like `SplitCompressionPiece` and its V3 variant.
## Final Notes
The YAZE's LC_LZ2 compression scheme provides three versions of compression methodologies with comprehensive support for various commands and modes. It ensures versatility and adaptability for different compression needs.

View File

@@ -10,19 +10,10 @@ Before you start using YAZE, make sure you have the following:
- A copy of "The Legend of Zelda: A Link to the Past" ROM file (US or JP) - A copy of "The Legend of Zelda: A Link to the Past" ROM file (US or JP)
- Basic knowledge of hexadecimal and binary data - Basic knowledge of hexadecimal and binary data
## Installation
To install YAZE, follow these steps based on your platform:
### Windows
### MacOS
### GNU/Linux
## Usage ## Usage
To use the Link to the Past ROM Editor, follow these steps: To use the Link to the Past ROM Editor, follow these steps:
Open the "ALTTP.sfc" ROM file using the "File" menu. Open the ROM file using the "File" menu.
... ...

View File

@@ -23,59 +23,6 @@ For developers to reference.
- SDL2 - SDL2
- **test**: Contains testing interface `yaze_test` - **test**: Contains testing interface `yaze_test`
## App Organization
- **Core Namespace**:
- Contains fundamental functionalities.
- [Common](../src/app/core/common.h)
- [Constants](../src/app/core/constants.h)
- [Controller](../src/app/core/controller.h)
- [Editor](../src/app/core/editor.h)
- [Pipeline](../src/app/gui/pipeline.h)
- **Editor Namespace**:
- Editors are responsible for representing the GUI view and handling user input.
- These classes are all controlled by [MasterEditor](../src/app/editor/master_editor.h)
- [DungeonEditor](../src/app/editor/dungeon_editor.h)
- [GraphicsEditor](../src/app/editor/graphics_editor.h)
- [OverworldEditor](../src/app/editor/overworld_editor.h)
- [ScreenEditor](../src/app/editor/screen_editor.h)
- [SpriteEditor](../src/app/editor/sprite_editor.h)
- **Modules**
- [AssemblyEditor](../src/app/editor/modules/assembly_editor.h)
- [MusicEditor](../src/app/editor/modules/music_editor.h)
- [GfxGroupEditor](../src/app/editor/modules/gfx_group_editor.h)
- [Tile16Editor](../src/app/editor/modules/tile16_editor.h)
- **Emu Namespace**:
- Contains business logic for `core::emulator`
- [Audio](../src/app/emu/audio/)
- [Debug](../src/app/emu/debug/)
- [Memory](../src/app/emu/memory/)
- [Video](../src/app/emu/video/)
- [Emulator](../src/app/emu/emulator.h)
- **Gfx Namespace**:
- Handles graphics related tasks.
- [Bitmap](../src/app/gfx/bitmap.h)
- [Compression](../src/app/gfx/compression.h)
- [SCAD Format](../src/app/gfx/scad_format.h)
- [SNES Palette](../src/app/gfx/snes_palette.h)
- [SNES Tile](../src/app/gfx/snes_tile.h)
- **Gui Namespace**:
- Manages GUI elements.
- [Canvas](../src/app/gui/canvas.h)
- [Color](../src/app/gui/color.h)
- [Icons](../src/app/gui/icons.h)
- [Input](../src/app/gui/input.h)
- [Style](../src/app/gui/style.h)
- [Widgets](../src/app/gui/widgets.h)
- **Zelda3 Namespace**:
- Holds business logic specific to Zelda3.
- [Dungeon](../src/app/zelda3/dungeon/)
- [Music](../src/app/zelda3/music/)
- [Screen](../src/app/zelda3/screen/)
- [Sprite](../src/app/zelda3/sprite/)
- [OverworldMap](../src/app/zelda3/overworld_map.h)
- [Overworld](../src/app/zelda3/overworld.h)
### Flow of Control ### Flow of Control
- [app/yaze.cc](../src/app/yaze.cc) - [app/yaze.cc](../src/app/yaze.cc)
@@ -142,22 +89,6 @@ This `ROM` class provides methods to manipulate and access data from a ROM.
- Specific Zelda 3 data can be loaded if specified. - Specific Zelda 3 data can be loaded if specified.
- Palettes are categorized into multiple groups (e.g., `ow_main`, `ow_aux`, `hud`, etc.) and loaded accordingly. - Palettes are categorized into multiple groups (e.g., `ow_main`, `ow_aux`, `hud`, etc.) and loaded accordingly.
## Overworld
- [app/zelda3/overworld.cc](../src/app/zelda3/overworld.cc)
- [app/zelda3/overworld.h](../src/app/zelda3/overworld.h)
- [app/zelda3/overworld_map.cc](../src/app/zelda3/overworld_map.cc)
- [app/zelda3/overworld_map.h](../src/app/zelda3/overworld_map.h)
---
- **Construction of Tile16 and Tile32**
- **Save and Load Resources**
- Sprites
- Entrances
- Tilemaps
## Bitmap ## Bitmap
- [app/gfx/bitmap.cc](../src/app/gfx/bitmap.cc) - [app/gfx/bitmap.cc](../src/app/gfx/bitmap.cc)

View File

@@ -1,6 +0,0 @@
# macOS Build Settings
- Clang 15.0.1 x86_64-apple-darrwin22.5.0
- SDL2 Source v2.26.5
- Removed snes_spc
- Removed asar_static

View File

@@ -2,6 +2,7 @@ set(
YAZE_APP_CORE_SRC YAZE_APP_CORE_SRC
app/core/common.cc app/core/common.cc
app/core/controller.cc app/core/controller.cc
app/core/labeling.cc
) )
set( set(
@@ -27,18 +28,21 @@ set(
app/gfx/scad_format.cc app/gfx/scad_format.cc
app/gfx/snes_palette.cc app/gfx/snes_palette.cc
app/gfx/snes_tile.cc app/gfx/snes_tile.cc
app/gfx/snes_color.cc
app/gfx/tilesheet.cc
) )
set( set(
YAZE_APP_ZELDA3_SRC YAZE_APP_ZELDA3_SRC
app/zelda3/overworld_map.cc app/zelda3/overworld/overworld_map.cc
app/zelda3/overworld.cc app/zelda3/overworld/overworld.cc
app/zelda3/screen/inventory.cc app/zelda3/screen/inventory.cc
app/zelda3/screen/title_screen.cc app/zelda3/screen/title_screen.cc
app/zelda3/sprite/sprite.cc app/zelda3/sprite/sprite.cc
app/zelda3/music/tracker.cc app/zelda3/music/tracker.cc
app/zelda3/dungeon/room.cc app/zelda3/dungeon/room.cc
app/zelda3/dungeon/room_object.cc app/zelda3/dungeon/room_object.cc
app/zelda3/dungeon/object_renderer.cc
) )
set( set(
@@ -75,9 +79,10 @@ if(WIN32 OR MINGW)
add_definitions(-DSDL_MAIN_HANDLED) add_definitions(-DSDL_MAIN_HANDLED)
endif() endif()
if (WIN32 OR MINGW OR UNIX) if (WIN32 OR MINGW OR UNIX AND NOT APPLE)
list(APPEND YAZE_APP_CORE_SRC list(APPEND YAZE_APP_CORE_SRC
app/core/platform/font_loader.cc app/core/platform/font_loader.cc
app/core/platform/clipboard.cc
) )
endif() endif()

View File

@@ -9,12 +9,31 @@
#include <stack> #include <stack>
#include <string> #include <string>
#include "absl/strings/str_format.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace core { namespace core {
std::shared_ptr<ExperimentFlags::Flags> ExperimentFlags::flags_; std::shared_ptr<ExperimentFlags::Flags> ExperimentFlags::flags_;
std::string UppercaseHexByte(uint8_t byte, bool leading) {
if (leading) {
std::string result = absl::StrFormat("0x%02X", byte);
return result;
}
std::string result = absl::StrFormat("%02X", byte);
return result;
}
std::string UppercaseHexWord(uint16_t word) {
std::string result = absl::StrFormat("0x%04x", word);
return result;
}
std::string UppercaseHexLong(uint32_t dword) {
std::string result = absl::StrFormat("0x%08x", dword);
return result;
}
uint32_t SnesToPc(uint32_t addr) { uint32_t SnesToPc(uint32_t addr) {
if (addr >= 0x808000) { if (addr >= 0x808000) {
addr -= 0x808000; addr -= 0x808000;
@@ -24,8 +43,15 @@ uint32_t SnesToPc(uint32_t addr) {
} }
uint32_t PcToSnes(uint32_t addr) { uint32_t PcToSnes(uint32_t addr) {
if (addr >= 0x400000) return -1; uint8_t *b = reinterpret_cast<uint8_t *>(&addr);
addr = ((addr << 1) & 0x7F0000) | (addr & 0x7FFF) | 0x8000; b[2] = static_cast<uint8_t>(b[2] * 2);
if (b[1] >= 0x80) {
b[2] += 1;
} else {
b[1] += 0x80;
}
return addr; return addr;
} }

View File

@@ -5,22 +5,30 @@
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <fstream>
#include <functional> #include <functional>
#include <iostream>
#include <memory> #include <memory>
#include <stack> #include <stack>
#include <string> #include <string>
namespace yaze { namespace yaze {
namespace app { namespace app {
/**
* @namespace yaze::app::core
* @brief Core application logic and utilities.
*/
namespace core { namespace core {
/**
* @class ExperimentFlags
* @brief A class to manage experimental feature flags.
*/
class ExperimentFlags { class ExperimentFlags {
public: public:
struct Flags { struct Flags {
// Load and render overworld sprites to the screen. Unstable. // Bitmap manager abstraction to manage graphics bin of Rom.
bool kDrawOverworldSprites = false;
// Bitmap manager abstraction to manage graphics bin of ROM.
bool kUseBitmapManager = true; bool kUseBitmapManager = true;
// Log instructions to the GUI debugger. // Log instructions to the GUI debugger.
@@ -31,11 +39,11 @@ class ExperimentFlags {
// ported away from that eventually. // ported away from that eventually.
bool kUseNewImGuiInput = false; bool kUseNewImGuiInput = false;
// Flag to enable the saving of all palettes to the ROM. // Flag to enable the saving of all palettes to the Rom.
bool kSaveAllPalettes = false; bool kSaveAllPalettes = false;
// Flag to enable the change queue, which could have any anonymous // Flag to enable the change queue, which could have any anonymous
// save routine for the ROM. In practice, just the overworld tilemap // save routine for the Rom. In practice, just the overworld tilemap
// and tile32 save. // and tile32 save.
bool kSaveWithChangeQueue = false; bool kSaveWithChangeQueue = false;
@@ -49,7 +57,35 @@ class ExperimentFlags {
// only supports macOS. // only supports macOS.
bool kLoadSystemFonts = true; bool kLoadSystemFonts = true;
bool kLoadTexturesAsStreaming = false; // Uses texture streaming from SDL for my dynamic updates.
bool kLoadTexturesAsStreaming = true;
// Save dungeon map edits to the Rom.
bool kSaveDungeonMaps = false;
// Log to the console.
bool kLogToConsole = false;
// Overworld flags
struct Overworld {
// Load and render overworld sprites to the screen. Unstable.
bool kDrawOverworldSprites = false;
// Save overworld map edits to the Rom.
bool kSaveOverworldMaps = true;
// Save overworld entrances to the Rom.
bool kSaveOverworldEntrances = true;
// Save overworld exits to the Rom.
bool kSaveOverworldExits = true;
// Save overworld items to the Rom.
bool kSaveOverworldItems = true;
// Save overworld properties to the Rom.
bool kSaveOverworldProperties = true;
} overworld;
}; };
ExperimentFlags() = default; ExperimentFlags() = default;
@@ -72,6 +108,11 @@ class ExperimentFlags {
static std::shared_ptr<Flags> flags_; static std::shared_ptr<Flags> flags_;
}; };
/**
* @class NotifyValue
* @brief A class to manage a value that can be modified and notify when it
* changes.
*/
template <typename T> template <typename T>
class NotifyValue { class NotifyValue {
public: public:
@@ -113,81 +154,6 @@ class NotifyValue {
T temp_value_; 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 { class ImGuiIdIssuer {
private: private:
static std::stack<ImGuiID> idStack; static std::stack<ImGuiID> idStack;
@@ -209,6 +175,18 @@ class ImGuiIdIssuer {
} }
}; };
class Logger {
public:
static void log(std::string message) {
static std::ofstream fout("log.txt", std::ios::out | std::ios::app);
fout << message << std::endl;
}
};
std::string UppercaseHexByte(uint8_t byte, bool leading = false);
std::string UppercaseHexWord(uint16_t word);
std::string UppercaseHexLong(uint32_t dword);
uint32_t SnesToPc(uint32_t addr); uint32_t SnesToPc(uint32_t addr);
uint32_t PcToSnes(uint32_t addr); uint32_t PcToSnes(uint32_t addr);
@@ -224,6 +202,8 @@ void stle16b_i(uint8_t *const p_arr, size_t const p_index,
uint16_t const p_val); uint16_t const p_val);
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index); uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
uint16_t ldle16b(uint8_t const *const p_arr);
void stle16b(uint8_t *const p_arr, uint16_t const p_val); 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(uint8_t *const p_arr, uint32_t const p_val);

View File

@@ -68,6 +68,15 @@
} \ } \
} }
#define RETURN_VOID_IF_ERROR(expression) \
{ \
auto error = expression; \
if (!error.ok()) { \
std::cout << error.ToString() << std::endl; \
return; \
} \
}
#define RETURN_IF_ERROR(expression) \ #define RETURN_IF_ERROR(expression) \
{ \ { \
auto error = expression; \ auto error = expression; \
@@ -121,9 +130,9 @@
using ushort = unsigned short; using ushort = unsigned short;
using uint = unsigned int; using uint = unsigned int;
using uchar = unsigned char; using uchar = unsigned char;
using Bytes = std::vector<uchar>; using Bytes = std::vector<uint8_t>;
using OWBlockset = std::vector<std::vector<ushort>>; using OWBlockset = std::vector<std::vector<uint16_t>>;
struct OWMapTiles { struct OWMapTiles {
OWBlockset light_world; // 64 maps OWBlockset light_world; // 64 maps
OWBlockset dark_world; // 64 maps OWBlockset dark_world; // 64 maps
@@ -135,7 +144,8 @@ namespace yaze {
namespace app { namespace app {
namespace core { namespace core {
constexpr float kYazeVersion = 0.05; constexpr uint32_t kRedPen = 0xFF0000FF;
constexpr float kYazeVersion = 0.07;
// ============================================================================ // ============================================================================
// Magic numbers // Magic numbers
@@ -185,96 +195,7 @@ constexpr int text_data2 = 0x75F40;
constexpr int pointers_dictionaries = 0x74703; constexpr int pointers_dictionaries = 0x74703;
constexpr int characters_width = 0x74ADF; constexpr int characters_width = 0x74ADF;
// ============================================================================
// Dungeon Entrances Related Variables
// ============================================================================
// 0x14577 word value for each room
constexpr int entrance_room = 0x14813;
// 8 bytes per room, HU, FU, HD, FD, HL, FL, HR, FR
constexpr int entrance_scrolledge = 0x1491D; // 0x14681
constexpr int entrance_yscroll = 0x14D45; // 0x14AA9 2 bytes each room
constexpr int entrance_xscroll = 0x14E4F; // 0x14BB3 2 bytes
constexpr int entrance_yposition = 0x14F59; // 0x14CBD 2bytes
constexpr int entrance_xposition = 0x15063; // 0x14DC7 2bytes
constexpr int entrance_camerayposition = 0x1516D; // 0x14ED1 2bytes
constexpr int entrance_cameraxposition = 0x15277; // 0x14FDB 2bytes
constexpr int entrance_gfx_group = 0x5D97; constexpr int entrance_gfx_group = 0x5D97;
constexpr int entrance_blockset = 0x15381; // 0x150E5 1byte
constexpr int entrance_floor = 0x15406; // 0x1516A 1byte
constexpr int entrance_dungeon = 0x1548B; // 0x151EF 1byte (dungeon id)
constexpr int entrance_door = 0x15510; // 0x15274 1byte
// 1 byte, ---b ---a b = bg2, a = need to check
constexpr int entrance_ladderbg = 0x15595; // 0x152F9
constexpr int entrance_scrolling = 0x1561A; // 0x1537E 1byte --h- --v-
constexpr int entrance_scrollquadrant = 0x1569F; // 0x15403 1byte
constexpr int entrance_exit = 0x15724; // 0x15488 2byte word
constexpr int entrance_music = 0x1582E; // 0x15592
// word value for each room
constexpr int startingentrance_room = 0x15B6E; // 0x158D2
// 8 bytes per room, HU, FU, HD, FD, HL, FL, HR, FR
constexpr int startingentrance_scrolledge = 0x15B7C; // 0x158E0
constexpr int startingentrance_yscroll = 0x15BB4; // 0x14AA9 //2bytes each room
constexpr int startingentrance_xscroll = 0x15BC2; // 0x14BB3 //2bytes
constexpr int startingentrance_yposition = 0x15BD0; // 0x14CBD 2bytes
constexpr int startingentrance_xposition = 0x15BDE; // 0x14DC7 2bytes
constexpr int startingentrance_camerayposition = 0x15BEC; // 0x14ED1 2bytes
constexpr int startingentrance_cameraxposition = 0x15BFA; // 0x14FDB 2bytes
constexpr int startingentrance_blockset = 0x15C08; // 0x150E5 1byte
constexpr int startingentrance_floor = 0x15C0F; // 0x1516A 1byte
constexpr int startingentrance_dungeon = 0x15C16; // 0x151EF 1byte (dungeon id)
constexpr int startingentrance_door = 0x15C2B; // 0x15274 1byte
// 1 byte, ---b ---a b = bg2, a = need to check
constexpr int startingentrance_ladderbg = 0x15C1D; // 0x152F9
// 1byte --h- --v-
constexpr int startingentrance_scrolling = 0x15C24; // 0x1537E
constexpr int startingentrance_scrollquadrant = 0x15C2B; // 0x15403 1byte
constexpr int startingentrance_exit = 0x15C32; // 0x15488 //2byte word
constexpr int startingentrance_music = 0x15C4E; // 0x15592
constexpr int startingentrance_entrance = 0x15C40;
constexpr int items_data_start = 0xDDE9; // save purpose
constexpr int items_data_end = 0xE6B2; // save purpose
constexpr int initial_equipement = 0x271A6;
constexpr int messages_id_dungeon = 0x3F61D;
// item id you get instead if you already have that item
constexpr int chests_backupitems = 0x3B528;
constexpr int chests_yoffset = 0x4836C;
constexpr int chests_xoffset = 0x4836C + (76 * 1);
constexpr int chests_itemsgfx = 0x4836C + (76 * 2);
constexpr int chests_itemswide = 0x4836C + (76 * 3);
constexpr int chests_itemsproperties = 0x4836C + (76 * 4);
constexpr int chests_sramaddress = 0x4836C + (76 * 5);
constexpr int chests_sramvalue = 0x4836C + (76 * 7);
constexpr int chests_msgid = 0x442DD;
constexpr int dungeons_startrooms = 0x7939;
constexpr int dungeons_endrooms = 0x792D;
constexpr int dungeons_bossrooms = 0x10954; // short value
// Bed Related Values (Starting location)
constexpr int bedPositionX = 0x039A37; // short value
constexpr int bedPositionY = 0x039A32; // short value
// short value (on 2 different bytes)
constexpr int bedPositionResetXLow = 0x02DE53;
constexpr int bedPositionResetXHigh = 0x02DE58;
// short value (on 2 different bytes)
constexpr int bedPositionResetYLow = 0x02DE5D;
constexpr int bedPositionResetYHigh = 0x02DE62;
constexpr int bedSheetPositionX = 0x0480BD; // short value
constexpr int bedSheetPositionY = 0x0480B8; // short value
// ============================================================================ // ============================================================================
// Gravestones related variables // Gravestones related variables
@@ -324,30 +245,6 @@ constexpr int customAreaSpecificBGASM = 0x140150;
constexpr int customAreaSpecificBGEnabled = constexpr int customAreaSpecificBGEnabled =
0x140140; // 1 byte, not 0 if enabled 0x140140; // 1 byte, not 0 if enabled
// ============================================================================
// Dungeon Map Related Variables
// ============================================================================
constexpr int dungeonMap_rooms_ptr = 0x57605; // 14 pointers of map data
constexpr int dungeonMap_floors = 0x575D9; // 14 words values
constexpr int dungeonMap_gfx_ptr = 0x57BE4; // 14 pointers of gfx data
// data start for floors/gfx MUST skip 575D9 to 57621 (pointers)
constexpr int dungeonMap_datastart = 0x57039;
// IF Byte = 0xB9 dungeon maps are not expanded
constexpr int dungeonMap_expCheck = 0x56652;
constexpr int dungeonMap_tile16 = 0x57009;
constexpr int dungeonMap_tile16Exp = 0x109010;
// 14 words values 0x000F = no boss
constexpr int dungeonMap_bossrooms = 0x56807;
constexpr int triforceVertices = 0x04FFD2; // group of 3, X, Y ,Z
constexpr int TriforceFaces = 0x04FFE4; // group of 5
constexpr int crystalVertices = 0x04FF98;
// ============================================================================ // ============================================================================
// Names // Names
// ============================================================================ // ============================================================================

View File

@@ -29,8 +29,26 @@ void InitializeKeymap() {
io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN); io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN);
io.KeyMap[ImGuiKey_UpArrow] = SDL_GetScancodeFromKey(SDLK_UP); io.KeyMap[ImGuiKey_UpArrow] = SDL_GetScancodeFromKey(SDLK_UP);
io.KeyMap[ImGuiKey_DownArrow] = SDL_GetScancodeFromKey(SDLK_DOWN); io.KeyMap[ImGuiKey_DownArrow] = SDL_GetScancodeFromKey(SDLK_DOWN);
io.KeyMap[ImGuiKey_LeftArrow] = SDL_GetScancodeFromKey(SDLK_LEFT);
io.KeyMap[ImGuiKey_RightArrow] = SDL_GetScancodeFromKey(SDLK_RIGHT);
io.KeyMap[ImGuiKey_Delete] = SDL_GetScancodeFromKey(SDLK_DELETE);
io.KeyMap[ImGuiKey_Escape] = SDL_GetScancodeFromKey(SDLK_ESCAPE);
io.KeyMap[ImGuiKey_Tab] = SDL_GetScancodeFromKey(SDLK_TAB); io.KeyMap[ImGuiKey_Tab] = SDL_GetScancodeFromKey(SDLK_TAB);
io.KeyMap[ImGuiKey_LeftCtrl] = SDL_GetScancodeFromKey(SDLK_LCTRL); io.KeyMap[ImGuiKey_LeftCtrl] = SDL_GetScancodeFromKey(SDLK_LCTRL);
io.KeyMap[ImGuiKey_PageUp] = SDL_GetScancodeFromKey(SDLK_PAGEUP);
io.KeyMap[ImGuiKey_PageDown] = SDL_GetScancodeFromKey(SDLK_PAGEDOWN);
io.KeyMap[ImGuiKey_Home] = SDL_GetScancodeFromKey(SDLK_HOME);
io.KeyMap[ImGuiKey_Space] = SDL_GetScancodeFromKey(SDLK_SPACE);
io.KeyMap[ImGuiKey_1] = SDL_GetScancodeFromKey(SDLK_1);
io.KeyMap[ImGuiKey_2] = SDL_GetScancodeFromKey(SDLK_2);
io.KeyMap[ImGuiKey_3] = SDL_GetScancodeFromKey(SDLK_3);
io.KeyMap[ImGuiKey_4] = SDL_GetScancodeFromKey(SDLK_4);
io.KeyMap[ImGuiKey_5] = SDL_GetScancodeFromKey(SDLK_5);
io.KeyMap[ImGuiKey_6] = SDL_GetScancodeFromKey(SDLK_6);
io.KeyMap[ImGuiKey_7] = SDL_GetScancodeFromKey(SDLK_7);
io.KeyMap[ImGuiKey_8] = SDL_GetScancodeFromKey(SDLK_8);
io.KeyMap[ImGuiKey_9] = SDL_GetScancodeFromKey(SDLK_9);
io.KeyMap[ImGuiKey_0] = SDL_GetScancodeFromKey(SDLK_0);
} }
void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) { void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) {
@@ -50,6 +68,7 @@ void InitializeClipboard() {
void HandleKeyDown(SDL_Event &event) { void HandleKeyDown(SDL_Event &event) {
ImGuiIO &io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
switch (event.key.keysym.sym) { switch (event.key.keysym.sym) {
case SDLK_UP: case SDLK_UP:
case SDLK_DOWN: case SDLK_DOWN:
@@ -92,6 +111,7 @@ void HandleMouseMovement(int &wheel) {
io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY)); io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT); io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT); io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
io.MouseDown[2] = buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE);
io.MouseWheel = static_cast<float>(wheel); io.MouseWheel = static_cast<float>(wheel);
} }
@@ -215,8 +235,9 @@ absl::Status Controller::CreateGuiContext() {
ImGui::CreateContext(); ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
if (flags()->kUseNewImGuiInput) { if (flags()->kUseNewImGuiInput) {
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
} }
@@ -235,7 +256,7 @@ absl::Status Controller::CreateGuiContext() {
// Build a new ImGui frame // Build a new ImGui frame
ImGui_ImplSDLRenderer2_NewFrame(); ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame(window_.get()); ImGui_ImplSDL2_NewFrame();
return absl::OkStatus(); return absl::OkStatus();
} }

View File

@@ -11,8 +11,8 @@
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/core/common.h" #include "app/core/common.h"
#include "app/core/editor.h"
#include "app/editor/master_editor.h" #include "app/editor/master_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/style.h" #include "app/gui/style.h"
@@ -22,6 +22,12 @@ namespace yaze {
namespace app { namespace app {
namespace core { namespace core {
/**
* @brief Main controller for the application.
*
* This class is responsible for managing the main window and the
* main editor. It is the main entry point for the application.
*/
class Controller : public ExperimentFlags { class Controller : public ExperimentFlags {
public: public:
bool IsActive() const { return active_; } bool IsActive() const { return active_; }

137
src/app/core/labeling.cc Normal file
View File

@@ -0,0 +1,137 @@
#include "app/core/labeling.h"
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <cstdint>
#include <fstream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include "app/core/common.h"
#include "app/core/constants.h"
namespace yaze {
namespace app {
namespace core {
bool ResourceLabelManager::LoadLabels(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
// Create the file if it does not exist
std::ofstream create_file(filename);
if (!create_file.is_open()) {
return false;
}
create_file.close();
file.open(filename);
if (!file.is_open()) {
return false;
}
}
filename_ = filename;
std::string line;
while (std::getline(file, line)) {
std::istringstream iss(line);
std::string type, key, value;
if (std::getline(iss, type, ',') && std::getline(iss, key, ',') &&
std::getline(iss, value)) {
labels_[type][key] = value;
}
}
labels_loaded_ = true;
return true;
}
bool ResourceLabelManager::SaveLabels() {
if (!labels_loaded_) {
return false;
}
std::ofstream file(filename_);
if (!file.is_open()) {
return false;
}
for (const auto& type_pair : labels_) {
for (const auto& label_pair : type_pair.second) {
file << type_pair.first << "," << label_pair.first << ","
<< label_pair.second << std::endl;
}
}
file.close();
return true;
}
void ResourceLabelManager::DisplayLabels(bool* p_open) {
if (!labels_loaded_) {
ImGui::Text("No labels loaded.");
return;
}
if (ImGui::Begin("Resource Labels", p_open)) {
for (const auto& type_pair : labels_) {
if (ImGui::TreeNode(type_pair.first.c_str())) {
for (const auto& label_pair : type_pair.second) {
std::string label_id = type_pair.first + "_" + label_pair.first;
ImGui::Text("%s: %s", label_pair.first.c_str(),
label_pair.second.c_str());
}
ImGui::TreePop();
}
}
if (ImGui::Button("Update Labels")) {
if (SaveLabels()) {
ImGui::Text("Labels updated successfully!");
} else {
ImGui::Text("Failed to update labels.");
}
}
}
ImGui::End();
}
void ResourceLabelManager::EditLabel(const std::string& type,
const std::string& key,
const std::string& newValue) {
labels_[type][key] = newValue;
}
void ResourceLabelManager::SelectableLabelWithNameEdit(
bool selected, const std::string& type, const std::string& key,
const std::string& defaultValue) {
std::string label = CreateOrGetLabel(type, key, defaultValue);
ImGui::Selectable(label.c_str(), selected,
ImGuiSelectableFlags_AllowDoubleClick);
std::string label_id = type + "_" + key;
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
ImGui::OpenPopup(label_id.c_str());
}
if (ImGui::BeginPopupContextItem(label_id.c_str())) {
char* new_label = labels_[type][key].data();
if (ImGui::InputText("##Label", new_label, labels_[type][key].size() + 1,
ImGuiInputTextFlags_EnterReturnsTrue)) {
labels_[type][key] = new_label;
}
ImGui::EndPopup();
}
}
std::string ResourceLabelManager::CreateOrGetLabel(
const std::string& type, const std::string& key,
const std::string& defaultValue) {
if (labels_.find(type) == labels_.end()) {
labels_[type] = std::unordered_map<std::string, std::string>();
}
if (labels_[type].find(key) == labels_[type].end()) {
labels_[type][key] = defaultValue;
}
return labels_[type][key];
}
} // namespace core
} // namespace app
} // namespace yaze

57
src/app/core/labeling.h Normal file
View File

@@ -0,0 +1,57 @@
#ifndef YAZE_APP_CORE_LABELING_H_
#define YAZE_APP_CORE_LABELING_H_
#include <cstdint>
#include <fstream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/core/common.h"
#include "app/core/constants.h"
namespace yaze {
namespace app {
namespace core {
// Default types
static constexpr absl::string_view kDefaultTypes[] = {
"Dungeon Names", "Dungeon Room Names", "Overworld Map Names"};
class ResourceLabelManager {
public:
ResourceLabelManager() = default;
bool LoadLabels(const std::string& filename);
bool SaveLabels();
void DisplayLabels(bool* p_open);
void EditLabel(const std::string& type, const std::string& key,
const std::string& newValue);
void SelectableLabelWithNameEdit(bool selected, const std::string& type,
const std::string& key,
const std::string& defaultValue);
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
const std::string& defaultValue);
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
const absl::string_view& defaultValue);
private:
bool labels_loaded_ = false;
std::string filename_;
struct ResourceType {
std::string key_name;
std::string display_description;
};
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
labels_;
};
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_LABELING_H_

View File

@@ -2,7 +2,7 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import "app/core/controller.h" #import "app/core/controller.h"
#import "app/core/editor.h" #import "app/editor/utils/editor.h"
#import "app/core/platform/app_delegate.h" #import "app/core/platform/app_delegate.h"
#import "app/core/platform/file_dialog.h" #import "app/core/platform/file_dialog.h"
#import "app/rom.h" #import "app/rom.h"
@@ -195,7 +195,7 @@
} }
- (void)openFileAction:(id)sender { - (void)openFileAction:(id)sender {
yaze::app::SharedROM::shared_rom_->LoadFromFile(FileDialogWrapper::ShowOpenFileDialog()); yaze::app::SharedRom::shared_rom_->LoadFromFile(FileDialogWrapper::ShowOpenFileDialog());
} }
- (void)cutAction:(id)sender { - (void)cutAction:(id)sender {

View File

@@ -0,0 +1,8 @@
#include "app/core/platform/clipboard.h"
#include <cstdint>
#include <vector>
void CopyImageToClipboard(const std::vector<uint8_t>& data) {}
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width,
int& height) {}

View File

@@ -8,6 +8,7 @@ void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
#elif defined(__APPLE__) #elif defined(__APPLE__)
#include <cstdint>
#include <vector> #include <vector>
void CopyImageToClipboard(const std::vector<uint8_t>& data); void CopyImageToClipboard(const std::vector<uint8_t>& data);
@@ -15,17 +16,11 @@ void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
#elif defined(__linux__) #elif defined(__linux__)
#include <cstdint>
#include <vector> #include <vector>
void CopyImageToClipboard(const std::vector<uint8_t>& data) { void CopyImageToClipboard(const std::vector<uint8_t>& data);
std::cout << "CopyImageToClipboard() is not implemented on Linux." void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
<< 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

View File

@@ -0,0 +1,42 @@
#ifndef YAZE_APP_EDITOR_CONTEXT_ENTRANCE_CONTEXT_H_
#define YAZE_APP_EDITOR_CONTEXT_ENTRANCE_CONTEXT_H_
#include <cstdint>
#include <vector>
#include "absl/status/status.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
namespace context {
class EntranceContext {
public:
absl::Status LoadEntranceTileTypes(Rom& rom) {
int offset_low = 0xDB8BF;
int offset_high = 0xDB917;
for (int i = 0; i < 0x2C; i++) {
// Load entrance tile types
ASSIGN_OR_RETURN(auto value_low, rom.ReadWord(offset_low + i));
entrance_tile_types_low_.push_back(value_low);
ASSIGN_OR_RETURN(auto value_high, rom.ReadWord(offset_high + i));
entrance_tile_types_low_.push_back(value_high);
}
return absl::OkStatus();
}
private:
std::vector<uint16_t> entrance_tile_types_low_;
std::vector<uint16_t> entrance_tile_types_high_;
};
} // namespace context
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_CONTEXT_ENTRANCE_CONTEXT_H_

View File

@@ -4,32 +4,24 @@
#include <cmath> #include <cmath>
#include "absl/status/status.h" #include "app/editor/utils/editor.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/editor/modules/palette_editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/pipeline.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
namespace context {
absl::Status GfxContext::Update() { return absl::OkStatus(); } std::unordered_map<uint8_t, gfx::Paletteset> GfxContext::palettesets_;
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 editor
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -6,49 +6,34 @@
#include <cmath> #include <cmath>
#include <vector> #include <vector>
#include "absl/status/status.h" #include "app/editor/utils/editor.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/editor/modules/palette_editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/pipeline.h"
#include "app/rom.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 yaze {
namespace app { namespace app {
namespace editor { namespace editor {
namespace context {
/**
* @brief Shared graphical context across editors.
*/
class GfxContext { class GfxContext {
public: public:
absl::Status Update(); absl::Status Update();
protected: protected:
static gfx::Bitmap current_ow_gfx_bmp_; // Palettesets for the tile16 individual tiles
static std::unordered_map<uint8_t, gfx::Paletteset> palettesets_;
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 context
} // namespace editor } // namespace editor
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -3,6 +3,7 @@
#include <imgui/imgui.h> #include <imgui/imgui.h>
#include "app/core/common.h" #include "app/core/common.h"
#include "app/core/labeling.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
@@ -28,44 +29,140 @@ using ImGui::TableNextRow;
using ImGui::TableSetupColumn; using ImGui::TableSetupColumn;
absl::Status DungeonEditor::Update() { absl::Status DungeonEditor::Update() {
if (!is_loaded_ && rom()->isLoaded()) { if (!is_loaded_ && rom()->is_loaded()) {
for (int i = 0; i < 0x100; i++) { RETURN_IF_ERROR(Initialize());
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; is_loaded_ = true;
} }
if (refresh_graphics_) { if (refresh_graphics_) {
for (int block : rooms_[current_room_id_].blocks()) { RETURN_IF_ERROR(RefreshGraphics());
graphics_bin_[block].ApplyPalette(
current_palette_group_[current_palette_id_]);
rom()->UpdateBitmap(&graphics_bin_[block]);
}
refresh_graphics_ = false; refresh_graphics_ = false;
} }
TAB_BAR("##DungeonEditorTabBar")
TAB_ITEM("Room Editor")
UpdateDungeonRoomView();
END_TAB_ITEM()
TAB_ITEM("Usage Statistics")
if (is_loaded_) {
static bool calc_stats = false;
if (!calc_stats) {
CalculateUsageStats();
calc_stats = true;
}
DrawUsageStats();
}
END_TAB_ITEM()
END_TAB_BAR()
return absl::OkStatus();
}
absl::Status DungeonEditor::Initialize() {
auto dungeon_man_pal_group = rom()->palette_group().dungeon_main;
for (int i = 0; i < 0x100 + 40; i++) {
rooms_.emplace_back(zelda3::dungeon::Room(i));
rooms_[i].LoadHeader();
rooms_[i].LoadRoomFromROM();
if (flags()->kDrawDungeonRoomGraphics) {
rooms_[i].LoadRoomGraphics();
}
room_size_pointers_.push_back(rooms_[i].room_size_ptr());
if (rooms_[i].room_size_ptr() != 0x0A8000) {
room_size_addresses_[i] = rooms_[i].room_size_ptr();
}
auto dungeon_palette_ptr = rom()->paletteset_ids[rooms_[i].palette][0];
ASSIGN_OR_RETURN(auto palette_id,
rom()->ReadWord(0xDEC4B + dungeon_palette_ptr));
int p_id = palette_id / 180;
auto color = dungeon_man_pal_group[p_id][3];
room_palette_[rooms_[i].palette] = color.rgb();
}
LoadDungeonRoomSize();
LoadRoomEntrances();
// Load the palette group and palette for the dungeon
full_palette_ = dungeon_man_pal_group[current_palette_group_id_];
ASSIGN_OR_RETURN(current_palette_group_,
gfx::CreatePaletteGroupFromLargePalette(full_palette_));
graphics_bin_ = *rom()->mutable_bitmap_manager();
// 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].get());
}
return absl::OkStatus();
}
absl::Status DungeonEditor::RefreshGraphics() {
for (int i = 0; i < 8; i++) {
int block = rooms_[current_room_id_].blocks()[i];
RETURN_IF_ERROR(graphics_bin_[block].get()->ApplyPaletteWithTransparent(
current_palette_group_[current_palette_id_], 0));
rom()->UpdateBitmap(graphics_bin_[block].get(), true);
}
auto sprites_aux1_pal_group = rom()->palette_group().sprites_aux1;
for (int i = 9; i < 16; i++) {
int block = rooms_[current_room_id_].blocks()[i];
graphics_bin_[block].get()->ApplyPaletteWithTransparent(
sprites_aux1_pal_group[current_palette_id_], 0);
rom()->UpdateBitmap(graphics_bin_[block].get(), true);
}
return absl::OkStatus();
}
void DungeonEditor::LoadDungeonRoomSize() {
std::map<int, std::vector<int>> rooms_by_bank;
for (const auto& room : room_size_addresses_) {
int bank = room.second >> 16;
rooms_by_bank[bank].push_back(room.second);
}
// Process and calculate room sizes within each bank
for (auto& bank_rooms : rooms_by_bank) {
// Sort the rooms within this bank
std::sort(bank_rooms.second.begin(), bank_rooms.second.end());
for (size_t i = 0; i < bank_rooms.second.size(); ++i) {
int room_ptr = bank_rooms.second[i];
// Identify the room ID for the current room pointer
int room_id =
std::find_if(room_size_addresses_.begin(), room_size_addresses_.end(),
[room_ptr](const auto& entry) {
return entry.second == room_ptr;
})
->first;
if (room_ptr != 0x0A8000) {
if (i < bank_rooms.second.size() - 1) {
// Calculate size as difference between current room and next room
// in the same bank
rooms_[room_id].set_room_size(bank_rooms.second[i + 1] - room_ptr);
} else {
// Calculate size for the last room in this bank
int bank_end_address = (bank_rooms.first << 16) | 0xFFFF;
rooms_[room_id].set_room_size(bank_end_address - room_ptr + 1);
}
total_room_size_ += rooms_[room_id].room_size();
} else {
// Room with address 0x0A8000
rooms_[room_id].set_room_size(0x00);
}
}
}
}
absl::Status DungeonEditor::UpdateDungeonRoomView() {
DrawToolset(); DrawToolset();
if (palette_showing_) { if (palette_showing_) {
ImGui::Begin("Palette Editor", &palette_showing_, 0); ImGui::Begin("Palette Editor", &palette_showing_, 0);
current_palette_ = auto dungeon_main_pal_group = rom()->palette_group().dungeon_main;
rom()->palette_group("dungeon_main")[current_palette_group_id_]; current_palette_ = dungeon_main_pal_group[current_palette_group_id_];
gui::SelectablePalettePipeline(current_palette_id_, refresh_graphics_, gui::SelectablePalettePipeline(current_palette_id_, refresh_graphics_,
current_palette_); current_palette_);
ImGui::End(); ImGui::End();
@@ -81,7 +178,14 @@ absl::Status DungeonEditor::Update() {
TableNextRow(); TableNextRow();
TableNextColumn(); TableNextColumn();
TAB_BAR("##DungeonRoomTabBar");
TAB_ITEM("Rooms");
DrawRoomSelector(); DrawRoomSelector();
END_TAB_ITEM();
TAB_ITEM("Entrances");
DrawEntranceSelector();
END_TAB_ITEM();
END_TAB_BAR();
TableNextColumn(); TableNextColumn();
DrawDungeonTabView(); DrawDungeonTabView();
@@ -110,47 +214,47 @@ void DungeonEditor::DrawToolset() {
TableSetupColumn("#doorTool"); TableSetupColumn("#doorTool");
TableSetupColumn("#blockTool"); TableSetupColumn("#blockTool");
ImGui::TableNextColumn(); TableNextColumn();
if (ImGui::Button(ICON_MD_UNDO)) { if (ImGui::Button(ICON_MD_UNDO)) {
PRINT_IF_ERROR(Undo()); PRINT_IF_ERROR(Undo());
} }
ImGui::TableNextColumn(); TableNextColumn();
if (ImGui::Button(ICON_MD_REDO)) { if (ImGui::Button(ICON_MD_REDO)) {
PRINT_IF_ERROR(Redo()); PRINT_IF_ERROR(Redo());
} }
ImGui::TableNextColumn(); TableNextColumn();
ImGui::Text(ICON_MD_MORE_VERT); ImGui::Text(ICON_MD_MORE_VERT);
ImGui::TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_FILTER_NONE, if (ImGui::RadioButton(ICON_MD_FILTER_NONE,
background_type_ == kBackgroundAny)) { background_type_ == kBackgroundAny)) {
background_type_ = kBackgroundAny; background_type_ = kBackgroundAny;
} }
ImGui::TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_FILTER_1, if (ImGui::RadioButton(ICON_MD_FILTER_1,
background_type_ == kBackground1)) { background_type_ == kBackground1)) {
background_type_ = kBackground1; background_type_ = kBackground1;
} }
ImGui::TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_FILTER_2, if (ImGui::RadioButton(ICON_MD_FILTER_2,
background_type_ == kBackground2)) { background_type_ == kBackground2)) {
background_type_ = kBackground2; background_type_ = kBackground2;
} }
ImGui::TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_FILTER_3, if (ImGui::RadioButton(ICON_MD_FILTER_3,
background_type_ == kBackground3)) { background_type_ == kBackground3)) {
background_type_ = kBackground3; background_type_ = kBackground3;
} }
ImGui::TableNextColumn(); TableNextColumn();
ImGui::Text(ICON_MD_MORE_VERT); ImGui::Text(ICON_MD_MORE_VERT);
ImGui::TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) { if (ImGui::RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) {
placement_type_ = kSprite; placement_type_ = kSprite;
} }
@@ -158,7 +262,7 @@ void DungeonEditor::DrawToolset() {
ImGui::SetTooltip("Sprites"); ImGui::SetTooltip("Sprites");
} }
ImGui::TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) { if (ImGui::RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) {
placement_type_ = kItem; placement_type_ = kItem;
} }
@@ -166,7 +270,7 @@ void DungeonEditor::DrawToolset() {
ImGui::SetTooltip("Items"); ImGui::SetTooltip("Items");
} }
ImGui::TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) { if (ImGui::RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) {
placement_type_ = kDoor; placement_type_ = kDoor;
} }
@@ -174,7 +278,7 @@ void DungeonEditor::DrawToolset() {
ImGui::SetTooltip("Doors"); ImGui::SetTooltip("Doors");
} }
ImGui::TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_SQUARE, placement_type_ == kBlock)) { if (ImGui::RadioButton(ICON_MD_SQUARE, placement_type_ == kBlock)) {
placement_type_ = kBlock; placement_type_ = kBlock;
} }
@@ -182,7 +286,7 @@ void DungeonEditor::DrawToolset() {
ImGui::SetTooltip("Blocks"); ImGui::SetTooltip("Blocks");
} }
ImGui::TableNextColumn(); TableNextColumn();
if (ImGui::Button(ICON_MD_PALETTE)) { if (ImGui::Button(ICON_MD_PALETTE)) {
palette_showing_ = !palette_showing_; palette_showing_ = !palette_showing_;
} }
@@ -192,7 +296,7 @@ void DungeonEditor::DrawToolset() {
} }
void DungeonEditor::DrawRoomSelector() { void DungeonEditor::DrawRoomSelector() {
if (rom()->isLoaded()) { if (rom()->is_loaded()) {
gui::InputHexWord("Room ID", &current_room_id_); gui::InputHexWord("Room ID", &current_room_id_);
gui::InputHex("Palette ID", &current_palette_id_); gui::InputHex("Palette ID", &current_palette_id_);
@@ -201,10 +305,15 @@ void DungeonEditor::DrawRoomSelector() {
ImGuiWindowFlags_AlwaysVerticalScrollbar)) { ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
int i = 0; int i = 0;
for (const auto each_room_name : zelda3::dungeon::kRoomNames) { for (const auto each_room_name : zelda3::dungeon::kRoomNames) {
ImGui::Selectable(each_room_name.data(), current_room_id_ == i, rom()->resource_label()->SelectableLabelWithNameEdit(
ImGuiSelectableFlags_AllowDoubleClick); current_room_id_ == i, "Dungeon Room Names",
core::UppercaseHexByte(i), zelda3::dungeon::kRoomNames[i].data());
if (ImGui::IsItemClicked()) { if (ImGui::IsItemClicked()) {
active_rooms_.push_back(i); // TODO: Jump to tab if room is already open
current_room_id_ = i;
if (!active_rooms_.contains(i)) {
active_rooms_.push_back(i);
}
} }
i += 1; i += 1;
} }
@@ -213,12 +322,110 @@ void DungeonEditor::DrawRoomSelector() {
} }
} }
void DungeonEditor::DrawEntranceSelector() {
if (rom()->is_loaded()) {
gui::InputHexWord("Entrance ID",
&entrances_[current_entrance_id_].entrance_id_);
gui::InputHexWord("Room ID", &entrances_[current_entrance_id_].room_, 50.f,
true);
ImGui::SameLine();
gui::InputHexByte("Dungeon ID",
&entrances_[current_entrance_id_].dungeon_id_, 50.f,
true);
gui::InputHexByte("Blockset", &entrances_[current_entrance_id_].blockset_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("Music", &entrances_[current_entrance_id_].music_, 50.f,
true);
ImGui::SameLine();
gui::InputHexByte("Floor", &entrances_[current_entrance_id_].floor_);
ImGui::Separator();
gui::InputHexWord("Player X ",
&entrances_[current_entrance_id_].x_position_);
ImGui::SameLine();
gui::InputHexWord("Player Y ",
&entrances_[current_entrance_id_].y_position_);
gui::InputHexWord("Camera X",
&entrances_[current_entrance_id_].camera_trigger_x_);
ImGui::SameLine();
gui::InputHexWord("Camera Y",
&entrances_[current_entrance_id_].camera_trigger_y_);
gui::InputHexWord("Scroll X ",
&entrances_[current_entrance_id_].camera_x_);
ImGui::SameLine();
gui::InputHexWord("Scroll Y ",
&entrances_[current_entrance_id_].camera_y_);
gui::InputHexWord("Exit", &entrances_[current_entrance_id_].exit_, 50.f,
true);
ImGui::Separator();
ImGui::Text("Camera Boundaries");
ImGui::Separator();
ImGui::Text("\t\t\t\t\tNorth East South West");
gui::InputHexByte("Quadrant",
&entrances_[current_entrance_id_].camera_boundary_qn_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_qe_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_qs_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_qw_,
50.f, true);
gui::InputHexByte("Full room",
&entrances_[current_entrance_id_].camera_boundary_fn_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_fe_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_fs_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_fw_,
50.f, true);
if (ImGui::BeginChild("EntranceSelector", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
for (int i = 0; i < 0x85 + 7; i++) {
rom()->resource_label()->SelectableLabelWithNameEdit(
current_entrance_id_ == i, "Dungeon Entrance Names",
core::UppercaseHexByte(i),
zelda3::dungeon::kEntranceNames[i].data());
if (ImGui::IsItemClicked()) {
current_entrance_id_ = i;
if (!active_rooms_.contains(i)) {
active_rooms_.push_back(entrances_[i].room_);
}
}
}
}
ImGui::EndChild();
}
}
void DungeonEditor::DrawDungeonTabView() { void DungeonEditor::DrawDungeonTabView() {
static int next_tab_id = 0; static int next_tab_id = 0;
if (ImGui::BeginTabBar("MyTabBar", kDungeonTabBarFlags)) { if (ImGui::BeginTabBar("MyTabBar", kDungeonTabBarFlags)) {
// TODO: Manage the room that is being added to the tab bar.
if (ImGui::TabItemButton("+", kDungeonTabFlags)) { if (ImGui::TabItemButton("+", kDungeonTabFlags)) {
if (std::find(active_rooms_.begin(), active_rooms_.end(),
current_room_id_) != active_rooms_.end()) {
// Room is already open
next_tab_id++;
}
active_rooms_.push_back(next_tab_id++); // Add new tab active_rooms_.push_back(next_tab_id++); // Add new tab
} }
@@ -226,6 +433,11 @@ void DungeonEditor::DrawDungeonTabView() {
for (int n = 0; n < active_rooms_.Size;) { for (int n = 0; n < active_rooms_.Size;) {
bool open = true; bool open = true;
if (active_rooms_[n] > sizeof(zelda3::dungeon::kRoomNames) / 4) {
active_rooms_.erase(active_rooms_.Data + n);
continue;
}
if (ImGui::BeginTabItem( if (ImGui::BeginTabItem(
zelda3::dungeon::kRoomNames[active_rooms_[n]].data(), &open, zelda3::dungeon::kRoomNames[active_rooms_[n]].data(), &open,
ImGuiTabItemFlags_None)) { ImGuiTabItemFlags_None)) {
@@ -269,15 +481,18 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
ImGui::EndGroup(); ImGui::EndGroup();
canvas_.DrawBackground(); canvas_.DrawBackground(ImVec2(0x200, 0x200));
canvas_.DrawContextMenu(); canvas_.DrawContextMenu();
if (is_loaded_) {
canvas_.DrawBitmap(rooms_[room_id].layer1(), 0, 0);
}
canvas_.DrawGrid(); canvas_.DrawGrid();
canvas_.DrawOverlay(); canvas_.DrawOverlay();
} }
void DungeonEditor::DrawRoomGraphics() { void DungeonEditor::DrawRoomGraphics() {
const auto height = 0x40; const auto height = 0x40;
room_gfx_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1)); room_gfx_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x10 * 0x40 + 1));
room_gfx_canvas_.DrawContextMenu(); room_gfx_canvas_.DrawContextMenu();
room_gfx_canvas_.DrawTileSelector(32); room_gfx_canvas_.DrawTileSelector(32);
if (is_loaded_) { if (is_loaded_) {
@@ -289,8 +504,8 @@ void DungeonEditor::DrawRoomGraphics() {
if (current_block >= 1) { if (current_block >= 1) {
top_left_y = room_gfx_canvas_.zero_point().y + height * current_block; top_left_y = room_gfx_canvas_.zero_point().y + height * current_block;
} }
room_gfx_canvas_.GetDrawList()->AddImage( room_gfx_canvas_.draw_list()->AddImage(
(void*)graphics_bin_[block].texture(), (void*)graphics_bin_[block].get()->texture(),
ImVec2(room_gfx_canvas_.zero_point().x + 2, top_left_y), ImVec2(room_gfx_canvas_.zero_point().x + 2, top_left_y),
ImVec2(room_gfx_canvas_.zero_point().x + 0x100, ImVec2(room_gfx_canvas_.zero_point().x + 0x100,
room_gfx_canvas_.zero_point().y + offset)); room_gfx_canvas_.zero_point().y + offset));
@@ -328,7 +543,7 @@ void DungeonEditor::DrawObjectRenderer() {
ImGui::GetContentRegionAvail().x); ImGui::GetContentRegionAvail().x);
TableSetupColumn("Canvas"); TableSetupColumn("Canvas");
ImGui::TableNextColumn(); TableNextColumn();
ImGui::BeginChild("DungeonObjectButtons", ImVec2(250, 0), true); ImGui::BeginChild("DungeonObjectButtons", ImVec2(250, 0), true);
int selected_object = 0; int selected_object = 0;
@@ -348,7 +563,7 @@ void DungeonEditor::DrawObjectRenderer() {
ImGui::EndChild(); ImGui::EndChild();
// Right side of the table - Canvas // Right side of the table - Canvas
ImGui::TableNextColumn(); TableNextColumn();
ImGui::BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1), ImGui::BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1),
true); true);
@@ -365,14 +580,268 @@ void DungeonEditor::DrawObjectRenderer() {
ImGui::EndTable(); ImGui::EndTable();
} }
// if (object_loaded_) { if (object_loaded_) {
// ImGui::Begin("Memory Viewer", &object_loaded_, 0); ImGui::Begin("Memory Viewer", &object_loaded_, 0);
// auto memory = object_renderer_.memory(); static MemoryEditor mem_edit;
// static MemoryEditor mem_edit; mem_edit.DrawContents((void*)object_renderer_.mutable_memory(),
// mem_edit.DrawContents((void*)object_renderer_.memory_ptr(), object_renderer_.mutable_memory()->size());
// memory.size()); ImGui::End();
// ImGui::End(); }
// } }
void DungeonEditor::LoadRoomEntrances() {
for (int i = 0; i < 0x07; ++i) {
entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, true));
}
for (int i = 0; i < 0x85; ++i) {
entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, false));
}
}
// ============================================================================
void DungeonEditor::CalculateUsageStats() {
for (const auto& room : rooms_) {
if (blockset_usage_.find(room.blockset) == blockset_usage_.end()) {
blockset_usage_[room.blockset] = 1;
} else {
blockset_usage_[room.blockset] += 1;
}
if (spriteset_usage_.find(room.spriteset) == spriteset_usage_.end()) {
spriteset_usage_[room.spriteset] = 1;
} else {
spriteset_usage_[room.spriteset] += 1;
}
if (palette_usage_.find(room.palette) == palette_usage_.end()) {
palette_usage_[room.palette] = 1;
} else {
palette_usage_[room.palette] += 1;
}
}
}
void DungeonEditor::RenderSetUsage(
const absl::flat_hash_map<uint16_t, int>& usage_map, uint16_t& selected_set,
int spriteset_offset) {
// Sort the usage map by set number
std::vector<std::pair<uint16_t, int>> sorted_usage(usage_map.begin(),
usage_map.end());
std::sort(sorted_usage.begin(), sorted_usage.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
for (const auto& [set, count] : sorted_usage) {
std::string display_str;
if (spriteset_offset != 0x00) {
display_str = absl::StrFormat("%#02x, %#02x: %d", set,
(set + spriteset_offset), count);
} else {
display_str =
absl::StrFormat("%#02x: %d", (set + spriteset_offset), count);
}
if (ImGui::Selectable(display_str.c_str(), selected_set == set)) {
selected_set = set; // Update the selected set when clicked
}
}
}
namespace {
// Calculate the unused sets in a usage map
// Range for blocksets 0-0x24
// Range for spritesets 0-0x8F
// Range for palettes 0-0x47
template <typename T>
void RenderUnusedSets(const absl::flat_hash_map<T, int>& usage_map, int max_set,
int spriteset_offset = 0x00) {
std::vector<int> unused_sets;
for (int i = 0; i < max_set; i++) {
if (usage_map.find(i) == usage_map.end()) {
unused_sets.push_back(i);
}
}
for (const auto& set : unused_sets) {
if (spriteset_offset != 0x00) {
ImGui::Text("%#02x, %#02x", set, (set + spriteset_offset));
} else {
ImGui::Text("%#02x", set);
}
}
}
} // namespace
void DungeonEditor::DrawUsageStats() {
if (ImGui::Button("Refresh")) {
selected_blockset_ = 0xFFFF;
selected_spriteset_ = 0xFFFF;
selected_palette_ = 0xFFFF;
spriteset_usage_.clear();
blockset_usage_.clear();
palette_usage_.clear();
CalculateUsageStats();
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
if (ImGui::BeginTable("DungeonUsageStatsTable", 8,
kDungeonTableFlags | ImGuiTableFlags_SizingFixedFit,
ImGui::GetContentRegionAvail())) {
TableSetupColumn("Blockset Usage");
TableSetupColumn("Unused Blockset");
TableSetupColumn("Palette Usage");
TableSetupColumn("Unused Palette");
TableSetupColumn("Spriteset Usage");
TableSetupColumn("Unused Spriteset");
TableSetupColumn("Usage Grid");
TableSetupColumn("Group Preview");
TableHeadersRow();
ImGui::PopStyleVar(2);
TableNextColumn();
ImGui::BeginChild("BlocksetUsageScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar);
RenderSetUsage(blockset_usage_, selected_blockset_);
ImGui::EndChild();
TableNextColumn();
ImGui::BeginChild("UnusedBlocksetScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar);
RenderUnusedSets(blockset_usage_, 0x25);
ImGui::EndChild();
TableNextColumn();
ImGui::BeginChild("PaletteUsageScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar);
RenderSetUsage(palette_usage_, selected_palette_);
ImGui::EndChild();
TableNextColumn();
ImGui::BeginChild("UnusedPaletteScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar);
RenderUnusedSets(palette_usage_, 0x48);
ImGui::EndChild();
TableNextColumn();
ImGui::BeginChild("SpritesetUsageScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar);
RenderSetUsage(spriteset_usage_, selected_spriteset_, 0x40);
ImGui::EndChild();
TableNextColumn();
ImGui::BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar);
RenderUnusedSets(spriteset_usage_, 0x90, 0x40);
ImGui::EndChild();
TableNextColumn();
ImGui::BeginChild("UsageGrid", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar);
ImGui::Text("%s",
absl::StrFormat("Total size of all rooms: %d hex format: %#06x",
total_room_size_, total_room_size_)
.c_str());
DrawUsageGrid();
ImGui::EndChild();
TableNextColumn();
if (selected_blockset_ < 0x25) {
gfx_group_editor_.SetSelectedBlockset(selected_blockset_);
gfx_group_editor_.DrawBlocksetViewer(true);
} else if (selected_spriteset_ < 0x90) {
gfx_group_editor_.SetSelectedSpriteset(selected_spriteset_ + 0x40);
gfx_group_editor_.DrawSpritesetViewer(true);
}
}
ImGui::EndTable();
}
void DungeonEditor::DrawUsageGrid() {
int totalSquares = 296;
int squaresWide = 16;
int squaresTall = (totalSquares + squaresWide - 1) /
squaresWide; // Ceiling of totalSquares/squaresWide
for (int row = 0; row < squaresTall; ++row) {
ImGui::NewLine();
for (int col = 0; col < squaresWide; ++col) {
// Check if we have reached 295 squares
if (row * squaresWide + col >= totalSquares) {
break;
}
// Determine if this square should be highlighted
const auto& room = rooms_[row * squaresWide + col];
// Create a button or selectable for each square
ImGui::BeginGroup();
ImVec4 color = room_palette_[room.palette];
color.x = color.x / 255;
color.y = color.y / 255;
color.z = color.z / 255;
color.w = 1.0f;
if (rooms_[row * squaresWide + col].room_size() > 0xFFFF) {
color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); // Or any highlight color
}
if (rooms_[row * squaresWide + col].room_size() == 0) {
color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); // Or any highlight color
}
ImGui::PushStyleColor(ImGuiCol_Button, color);
// Make the button text darker
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
bool highlight = room.blockset == selected_blockset_ ||
room.spriteset == selected_spriteset_ ||
room.palette == selected_palette_;
// Set highlight color if needed
if (highlight) {
ImGui::PushStyleColor(
ImGuiCol_Button,
ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Or any highlight color
}
if (ImGui::Button(absl::StrFormat(
"%#x", rooms_[row * squaresWide + col].room_size())
.c_str(),
ImVec2(55, 30))) {
// Switch over to the room editor tab
// and add a room tab by the ID of the square
// that was clicked
}
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
ImGui::OpenPopup(
absl::StrFormat("RoomContextMenu%d", row * squaresWide + col)
.c_str());
}
ImGui::PopStyleColor(2);
ImGui::EndGroup();
// Reset style if it was highlighted
if (highlight) {
ImGui::PopStyleColor();
}
// Check if the square is hovered
if (ImGui::IsItemHovered()) {
// Display a tooltip with all the room properties
ImGui::BeginTooltip();
ImGui::Text("Room ID: %d", row * squaresWide + col);
ImGui::Text("Blockset: %#02x", room.blockset);
ImGui::Text("Spriteset: %#02x", room.spriteset);
ImGui::Text("Palette: %#02x", room.palette);
ImGui::Text("Floor1: %#02x", room.floor1);
ImGui::Text("Floor2: %#02x", room.floor2);
ImGui::Text("Message ID: %#04x", room.message_id_);
ImGui::Text("Size: %#06x", room.room_size());
ImGui::Text("Size Pointer: %#06x", room.room_size_ptr());
ImGui::EndTooltip();
}
// Keep squares in the same line
ImGui::SameLine();
}
}
} }
} // namespace editor } // namespace editor

View File

@@ -4,12 +4,15 @@
#include <imgui/imgui.h> #include <imgui/imgui.h>
#include "app/core/common.h" #include "app/core/common.h"
#include "app/core/editor.h" #include "app/editor/utils/editor.h"
#include "app/core/labeling.h"
#include "app/editor/modules/gfx_group_editor.h"
#include "app/editor/modules/palette_editor.h" #include "app/editor/modules/palette_editor.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/rom.h" #include "app/rom.h"
#include "zelda3/dungeon/room.h" #include "zelda3/dungeon/room.h"
#include "zelda3/dungeon/room_entrance.h"
#include "zelda3/dungeon/room_object.h" #include "zelda3/dungeon/room_object.h"
namespace yaze { namespace yaze {
@@ -29,8 +32,18 @@ constexpr ImGuiTableFlags kDungeonTableFlags =
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV; ImGuiTableFlags_BordersV;
/**
* @brief DungeonEditor class for editing dungeons.
*
* This class is currently a work in progress and is used for editing dungeons.
* It provides various functions for updating, cutting, copying, pasting,
* undoing, and redoing. It also includes methods for drawing the toolset, room
* selector, entrance selector, dungeon tab view, dungeon canvas, room graphics,
* tile selector, and object renderer. Additionally, it handles loading room
* entrances, calculating usage statistics, and rendering set usage.
*/
class DungeonEditor : public Editor, class DungeonEditor : public Editor,
public SharedROM, public SharedRom,
public core::ExperimentFlags { public core::ExperimentFlags {
public: public:
absl::Status Update() override; absl::Status Update() override;
@@ -40,9 +53,19 @@ class DungeonEditor : public Editor,
absl::Status Undo() override { return absl::OkStatus(); } absl::Status Undo() override { return absl::OkStatus(); }
absl::Status Redo() override { return absl::OkStatus(); } absl::Status Redo() override { return absl::OkStatus(); }
void add_room(int i) { active_rooms_.push_back(i); }
private: private:
absl::Status Initialize();
absl::Status RefreshGraphics();
void LoadDungeonRoomSize();
absl::Status UpdateDungeonRoomView();
void DrawToolset(); void DrawToolset();
void DrawRoomSelector(); void DrawRoomSelector();
void DrawEntranceSelector();
void DrawDungeonTabView(); void DrawDungeonTabView();
void DrawDungeonCanvas(int room_id); void DrawDungeonCanvas(int room_id);
@@ -51,6 +74,14 @@ class DungeonEditor : public Editor,
void DrawTileSelector(); void DrawTileSelector();
void DrawObjectRenderer(); void DrawObjectRenderer();
void LoadRoomEntrances();
void CalculateUsageStats();
void DrawUsageStats();
void DrawUsageGrid();
void RenderSetUsage(const absl::flat_hash_map<uint16_t, int>& usage_map,
uint16_t& selected_set, int spriteset_offset = 0x00);
enum BackgroundType { enum BackgroundType {
kNoBackground, kNoBackground,
kBackground1, kBackground1,
@@ -70,15 +101,17 @@ class DungeonEditor : public Editor,
bool refresh_graphics_ = false; bool refresh_graphics_ = false;
bool show_object_render_ = false; bool show_object_render_ = false;
uint16_t current_entrance_id_ = 0;
uint16_t current_room_id_ = 0; uint16_t current_room_id_ = 0;
uint64_t current_palette_id_ = 0; uint64_t current_palette_id_ = 0;
uint64_t current_palette_group_id_ = 0; uint64_t current_palette_group_id_ = 0;
ImVector<int> active_rooms_; ImVector<int> active_rooms_;
GfxGroupEditor gfx_group_editor_;
PaletteEditor palette_editor_; PaletteEditor palette_editor_;
gfx::SNESPalette current_palette_; gfx::SnesPalette current_palette_;
gfx::SNESPalette full_palette_; gfx::SnesPalette full_palette_;
gfx::PaletteGroup current_palette_group_; gfx::PaletteGroup current_palette_group_;
gui::Canvas canvas_; gui::Canvas canvas_;
@@ -86,12 +119,28 @@ class DungeonEditor : public Editor,
gui::Canvas object_canvas_; gui::Canvas object_canvas_;
gfx::Bitmap room_gfx_bmp_; gfx::Bitmap room_gfx_bmp_;
gfx::BitmapTable graphics_bin_; gfx::BitmapManager graphics_bin_;
std::vector<gfx::Bitmap*> room_gfx_sheets_; std::vector<gfx::Bitmap*> room_gfx_sheets_;
std::vector<zelda3::dungeon::Room> rooms_; std::vector<zelda3::dungeon::Room> rooms_;
std::vector<zelda3::dungeon::RoomEntrance> entrances_;
std::vector<gfx::BitmapManager> room_graphics_; std::vector<gfx::BitmapManager> room_graphics_;
zelda3::dungeon::DungeonObjectRenderer object_renderer_; zelda3::dungeon::DungeonObjectRenderer object_renderer_;
absl::flat_hash_map<uint16_t, int> spriteset_usage_;
absl::flat_hash_map<uint16_t, int> blockset_usage_;
absl::flat_hash_map<uint16_t, int> palette_usage_;
std::vector<int64_t> room_size_pointers_;
uint16_t selected_blockset_ = 0xFFFF; // 0xFFFF indicates no selection
uint16_t selected_spriteset_ = 0xFFFF;
uint16_t selected_palette_ = 0xFFFF;
uint64_t total_room_size_ = 0;
std::unordered_map<int, int> room_size_addresses_;
std::unordered_map<int, ImVec4> room_palette_;
}; };
} // namespace editor } // namespace editor

View File

@@ -28,6 +28,7 @@ using ImGui::Button;
using ImGui::InputInt; using ImGui::InputInt;
using ImGui::InputText; using ImGui::InputText;
using ImGui::SameLine; using ImGui::SameLine;
using ImGui::TableNextColumn;
constexpr ImGuiTableFlags kGfxEditTableFlags = constexpr ImGuiTableFlags kGfxEditTableFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
@@ -50,12 +51,13 @@ absl::Status GraphicsEditor::Update() {
} }
absl::Status GraphicsEditor::UpdateGfxEdit() { absl::Status GraphicsEditor::UpdateGfxEdit() {
TAB_ITEM("Graphics Editor") TAB_ITEM("Sheet Editor")
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags, if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
ImVec2(0, 0))) { ImVec2(0, 0))) {
for (const auto& name : kGfxEditColumnNames) for (const auto& name :
ImGui::TableSetupColumn(name.data()); {"Tilesheets", "Current Graphics", "Palette Controls"})
ImGui::TableSetupColumn(name);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
@@ -63,13 +65,13 @@ absl::Status GraphicsEditor::UpdateGfxEdit() {
status_ = UpdateGfxSheetList(); status_ = UpdateGfxSheetList();
NEXT_COLUMN(); NEXT_COLUMN();
if (rom()->isLoaded()) { if (rom()->is_loaded()) {
DrawGfxEditToolset(); DrawGfxEditToolset();
status_ = UpdateGfxTabView(); status_ = UpdateGfxTabView();
} }
NEXT_COLUMN(); NEXT_COLUMN();
if (rom()->isLoaded()) { if (rom()->is_loaded()) {
status_ = UpdatePaletteColumn(); status_ = UpdatePaletteColumn();
} }
} }
@@ -87,38 +89,41 @@ void GraphicsEditor::DrawGfxEditToolset() {
"Zoom In", "Current Color", "Tile Size"}) "Zoom In", "Current Color", "Tile Size"})
ImGui::TableSetupColumn(name); ImGui::TableSetupColumn(name);
ImGui::TableNextColumn(); TableNextColumn();
if (Button(ICON_MD_SELECT_ALL)) { if (Button(ICON_MD_SELECT_ALL)) {
gfx_edit_mode_ = GfxEditMode::kSelect; gfx_edit_mode_ = GfxEditMode::kSelect;
} }
ImGui::TableNextColumn(); TableNextColumn();
if (Button(ICON_MD_DRAW)) { if (Button(ICON_MD_DRAW)) {
gfx_edit_mode_ = GfxEditMode::kPencil; gfx_edit_mode_ = GfxEditMode::kPencil;
} }
HOVER_HINT("Draw with current color");
ImGui::TableNextColumn(); TableNextColumn();
if (Button(ICON_MD_FORMAT_COLOR_FILL)) { if (Button(ICON_MD_FORMAT_COLOR_FILL)) {
gfx_edit_mode_ = GfxEditMode::kFill; gfx_edit_mode_ = GfxEditMode::kFill;
} }
HOVER_HINT("Fill with current color");
ImGui::TableNextColumn(); TableNextColumn();
if (Button(ICON_MD_CONTENT_COPY)) { if (Button(ICON_MD_CONTENT_COPY)) {
std::vector<uint8_t> png_data = std::vector<uint8_t> png_data =
rom()->bitmap_manager().GetBitmap(current_sheet_)->GetPngData(); rom()->bitmap_manager().shared_bitmap(current_sheet_)->GetPngData();
CopyImageToClipboard(png_data); CopyImageToClipboard(png_data);
} }
HOVER_HINT("Copy to Clipboard");
ImGui::TableNextColumn(); TableNextColumn();
if (Button(ICON_MD_CONTENT_PASTE)) { if (Button(ICON_MD_CONTENT_PASTE)) {
std::vector<uint8_t> png_data; std::vector<uint8_t> png_data;
int width, height; int width, height;
GetImageFromClipboard(png_data, width, height); GetImageFromClipboard(png_data, width, height);
if (png_data.size() > 0) { if (png_data.size() > 0) {
rom() rom()
->bitmap_manager() ->mutable_bitmap_manager()
.GetBitmap(current_sheet_) ->mutable_bitmap(current_sheet_)
->LoadFromPngData(png_data, width, height); ->Create(width, height, 8, png_data);
rom()->UpdateBitmap(rom() rom()->UpdateBitmap(rom()
->mutable_bitmap_manager() ->mutable_bitmap_manager()
->mutable_bitmap(current_sheet_) ->mutable_bitmap(current_sheet_)
@@ -127,35 +132,35 @@ void GraphicsEditor::DrawGfxEditToolset() {
} }
HOVER_HINT("Paste from Clipboard"); HOVER_HINT("Paste from Clipboard");
ImGui::TableNextColumn(); TableNextColumn();
if (Button(ICON_MD_ZOOM_OUT)) { if (Button(ICON_MD_ZOOM_OUT)) {
if (current_scale_ >= 0.0f) { if (current_scale_ >= 0.0f) {
current_scale_ -= 1.0f; current_scale_ -= 1.0f;
} }
} }
ImGui::TableNextColumn(); TableNextColumn();
if (Button(ICON_MD_ZOOM_IN)) { if (Button(ICON_MD_ZOOM_IN)) {
if (current_scale_ <= 16.0f) { if (current_scale_ <= 16.0f) {
current_scale_ += 1.0f; current_scale_ += 1.0f;
} }
} }
ImGui::TableNextColumn(); TableNextColumn();
auto bitmap = rom()->bitmap_manager()[current_sheet_]; auto bitmap = rom()->bitmap_manager()[current_sheet_];
auto palette = bitmap->palette(); auto palette = bitmap->palette();
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
ImGui::SameLine(); ImGui::SameLine();
auto color = auto color =
ImVec4(palette[i].GetRGB().x / 255.0f, palette[i].GetRGB().y / 255.0f, ImVec4(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
palette[i].GetRGB().z / 255.0f, 255.0f); palette[i].rgb().z / 255.0f, 255.0f);
if (ImGui::ColorButton(absl::StrFormat("Palette Color %d", i).c_str(), if (ImGui::ColorButton(absl::StrFormat("Palette Color %d", i).c_str(),
color)) { color)) {
current_color_ = color; current_color_ = color;
} }
} }
ImGui::TableNextColumn(); TableNextColumn();
gui::InputHexByte("Tile Size", &tile_size_, 0x01); gui::InputHexByte("Tile Size", &tile_size_, 0x01);
ImGui::EndTable(); ImGui::EndTable();
@@ -173,43 +178,48 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
ImGuiWindowFlags_NoDecoration); ImGuiWindowFlags_NoDecoration);
ImGui::PopStyleVar(); ImGui::PopStyleVar();
gui::Canvas graphics_bin_canvas_; gui::Canvas graphics_bin_canvas_;
auto select_tile_event = [&]() { // auto select_tile_event = [&]() {
if (value.get()->IsActive()) { // };
auto texture = value.get()->texture(); // graphics_bin_canvas_.UpdateEvent(
graphics_bin_canvas_.GetDrawList()->AddImage( // select_tile_event, ImVec2(0x100 + 1, 0x40 + 1), 0x20, sheet_scale_,
(void*)texture, // /*grid_size=*/16.0f);
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)) { graphics_bin_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x40 + 1));
current_sheet_ = key; graphics_bin_canvas_.DrawContextMenu();
open_sheets_.insert(key); if (value.get()->is_active()) {
} auto texture = value.get()->texture();
graphics_bin_canvas_.draw_list()->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_));
// Add a slightly transparent rectangle behind the text if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
ImVec2 text_pos(graphics_bin_canvas_.zero_point().x + 2, current_sheet_ = key;
graphics_bin_canvas_.zero_point().y + 2); open_sheets_.insert(key);
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( // Add a slightly transparent rectangle behind the text
select_tile_event, ImVec2(0x100 + 1, 0x40 + 1), 0x20, sheet_scale_, ImVec2 text_pos(graphics_bin_canvas_.zero_point().x + 2,
/*grid_size=*/16.0f); 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_.draw_list()->AddRectFilled(rent_min, rent_max,
IM_COL32(0, 125, 0, 128));
graphics_bin_canvas_.draw_list()->AddText(
text_pos, IM_COL32(125, 255, 125, 255),
absl::StrFormat("%02X", key).c_str());
}
graphics_bin_canvas_.DrawGrid(16.0f);
graphics_bin_canvas_.DrawOverlay();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::EndChild(); ImGui::EndChild();
} }
@@ -249,18 +259,19 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysVerticalScrollbar |
ImGuiWindowFlags_AlwaysHorizontalScrollbar); ImGuiWindowFlags_AlwaysHorizontalScrollbar);
gfx::Bitmap& current_bitmap =
*rom()->mutable_bitmap_manager()->mutable_bitmap(sheet_id);
auto draw_tile_event = [&]() { auto draw_tile_event = [&]() {
gfx::Bitmap& current_bitmap = current_sheet_canvas_.DrawTileOnBitmap(tile_size_, &current_bitmap,
*rom()->mutable_bitmap_manager()->mutable_bitmap(sheet_id);
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, current_bitmap,
current_color_); current_color_);
rom()->UpdateBitmap(&current_bitmap); rom()->UpdateBitmap(&current_bitmap, true);
}; };
auto size = ImVec2(0x80, 0x20);
current_sheet_canvas_.UpdateColorPainter( current_sheet_canvas_.UpdateColorPainter(
*rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event, *rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event,
size, tile_size_, current_scale_, 8.0f); tile_size_, current_scale_);
ImGui::EndChild(); ImGui::EndChild();
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
@@ -289,12 +300,13 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
ImGui::Begin(absl::StrFormat("##GfxEditPaletteChildWindow%d", id).c_str(), ImGui::Begin(absl::StrFormat("##GfxEditPaletteChildWindow%d", id).c_str(),
&active, ImGuiWindowFlags_AlwaysUseWindowPadding); &active, ImGuiWindowFlags_AlwaysUseWindowPadding);
current_sheet_ = id; current_sheet_ = id;
// ImVec2(0x100, 0x40),
current_sheet_canvas_.UpdateColorPainter( current_sheet_canvas_.UpdateColorPainter(
*rom()->bitmap_manager()[id], current_color_, *rom()->bitmap_manager()[id], current_color_,
[&]() { [&]() {
}, },
ImVec2(0x100, 0x40), tile_size_, current_scale_, 8.0f); tile_size_, current_scale_);
ImGui::End(); ImGui::End();
if (active == false) { if (active == false) {
@@ -310,12 +322,12 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
} }
absl::Status GraphicsEditor::UpdatePaletteColumn() { absl::Status GraphicsEditor::UpdatePaletteColumn() {
auto palette_group = rom()->palette_group( auto palette_group = *rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]); kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
auto palette = palette_group[edit_palette_index_]; auto palette = palette_group.palette(edit_palette_index_);
if (rom()->isLoaded()) { if (rom()->is_loaded()) {
gui::TextWithSeparators("ROM Palette"); gui::TextWithSeparators("ROM Palette");
ImGui::SetNextItemWidth(100.f); ImGui::SetNextItemWidth(100.f);
ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_, ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_,
@@ -328,11 +340,13 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() {
gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_, gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_,
palette); palette);
if (refresh_graphics_) { if (refresh_graphics_ && !open_sheets_.empty()) {
rom()->bitmap_manager()[current_sheet_]->ApplyPaletteWithTransparent( RETURN_IF_ERROR(
palette, edit_palette_sub_index_); rom()->bitmap_manager()[current_sheet_]->ApplyPaletteWithTransparent(
palette, edit_palette_sub_index_));
rom()->UpdateBitmap( rom()->UpdateBitmap(
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_).get()); rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_).get(),
true);
refresh_graphics_ = false; refresh_graphics_ = false;
} }
@@ -342,17 +356,44 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() {
absl::Status GraphicsEditor::UpdateLinkGfxView() { absl::Status GraphicsEditor::UpdateLinkGfxView() {
TAB_ITEM("Player Animations") TAB_ITEM("Player Animations")
const auto link_gfx_offset = 0x80000; if (ImGui::BeginTable("##PlayerAnimationTable", 3, kGfxEditTableFlags,
const auto link_gfx_length = 0x7000; ImVec2(0, 0))) {
for (const auto& name : {"Canvas", "Animation Steps", "Properties"})
ImGui::TableSetupColumn(name);
// Load Links graphics from the ROM ImGui::TableHeadersRow();
RETURN_IF_ERROR(rom()->LoadLinkGraphics());
// Split it into the pose data frames NEXT_COLUMN();
// Create an animation step display for the poses link_canvas_.DrawBackground();
// Allow the user to modify the frames used in an anim step link_canvas_.DrawGrid(16.0f);
// LinkOAM_AnimationSteps: int i = 0;
// #_0D85FB for (auto [key, link_sheet] : rom()->link_graphics()) {
int x_offset = 0;
int y_offset = core::kTilesheetHeight * i * 4;
link_canvas_.DrawBitmap(link_sheet, x_offset, y_offset, 4);
i++;
}
link_canvas_.DrawOverlay();
link_canvas_.DrawGrid();
NEXT_COLUMN();
ImGui::Text("Placeholder");
NEXT_COLUMN();
if (ImGui::Button("Load Link Graphics (Experimental)")) {
if (rom()->is_loaded()) {
// 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
}
}
}
ImGui::EndTable();
END_TAB_ITEM() END_TAB_ITEM()
return absl::OkStatus(); return absl::OkStatus();
@@ -424,7 +465,7 @@ absl::Status GraphicsEditor::DrawToolset() {
for (const auto& name : kGfxToolsetColumnNames) for (const auto& name : kGfxToolsetColumnNames)
ImGui::TableSetupColumn(name.data()); ImGui::TableSetupColumn(name.data());
ImGui::TableNextColumn(); TableNextColumn();
if (Button(ICON_MD_MEMORY)) { if (Button(ICON_MD_MEMORY)) {
if (!open_memory_editor_) { if (!open_memory_editor_) {
open_memory_editor_ = true; open_memory_editor_ = true;
@@ -461,8 +502,8 @@ absl::Status GraphicsEditor::DrawCgxImport() {
[this]() { ImGui::SetClipboardText(cgx_file_path_); }); [this]() { ImGui::SetClipboardText(cgx_file_path_); });
gui::ButtonPipe("Load CGX Data", [this]() { gui::ButtonPipe("Load CGX Data", [this]() {
status_ = gfx::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_, status_ = gfx::scad_format::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_,
decoded_cgx_, extra_cgx_data_); decoded_cgx_, extra_cgx_data_);
cgx_bitmap_.InitializeFromData(0x80, 0x200, 8, decoded_cgx_); cgx_bitmap_.InitializeFromData(0x80, 0x200, 8, decoded_cgx_);
if (col_file_) { if (col_file_) {
@@ -492,11 +533,12 @@ absl::Status GraphicsEditor::DrawScrImport() {
InputInt("SCR Mod", &scr_mod_value_); InputInt("SCR Mod", &scr_mod_value_);
gui::ButtonPipe("Load Scr Data", [this]() { gui::ButtonPipe("Load Scr Data", [this]() {
status_ = gfx::LoadScr(scr_file_path_, scr_mod_value_, scr_data_); status_ =
gfx::scad_format::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
decoded_scr_data_.resize(0x100 * 0x100); decoded_scr_data_.resize(0x100 * 0x100);
status_ = gfx::DrawScrWithCgx(current_bpp_, scr_data_, decoded_scr_data_, status_ = gfx::scad_format::DrawScrWithCgx(current_bpp_, scr_data_,
decoded_cgx_); decoded_scr_data_, decoded_cgx_);
scr_bitmap_.InitializeFromData(0x100, 0x100, 8, decoded_scr_data_); scr_bitmap_.InitializeFromData(0x100, 0x100, 8, decoded_scr_data_);
if (scr_loaded_) { if (scr_loaded_) {
@@ -527,11 +569,15 @@ absl::Status GraphicsEditor::DrawPaletteControls() {
if (col_file_palette_group_.size() != 0) { if (col_file_palette_group_.size() != 0) {
col_file_palette_group_.Clear(); col_file_palette_group_.Clear();
} }
col_file_palette_group_ = gfx::CreatePaletteGroupFromColFile(col_data_); auto col_file_palette_group_status =
col_file_palette_ = gfx::SNESPalette(col_data_); gfx::CreatePaletteGroupFromColFile(col_data_);
if (col_file_palette_group_status.ok()) {
col_file_palette_group_ = col_file_palette_group_status.value();
}
col_file_palette_ = gfx::SnesPalette(col_data_);
// gigaleak dev format based code // gigaleak dev format based code
decoded_col_ = gfx::DecodeColFile(col_file_path_); decoded_col_ = gfx::scad_format::DecodeColFile(col_file_path_);
col_file_ = true; col_file_ = true;
is_open_ = true; is_open_ = true;
}); });
@@ -539,7 +585,7 @@ absl::Status GraphicsEditor::DrawPaletteControls() {
gui::ButtonPipe("Copy COL Path", gui::ButtonPipe("Copy COL Path",
[this]() { ImGui::SetClipboardText(col_file_path_); }); [this]() { ImGui::SetClipboardText(col_file_path_); });
if (rom()->isLoaded()) { if (rom()->is_loaded()) {
gui::TextWithSeparators("ROM Palette"); gui::TextWithSeparators("ROM Palette");
gui::InputHex("Palette Index", &current_palette_index_); gui::InputHex("Palette Index", &current_palette_index_);
ImGui::Combo("Palette", &current_palette_, kPaletteGroupAddressesKeys, ImGui::Combo("Palette", &current_palette_, kPaletteGroupAddressesKeys,
@@ -644,7 +690,7 @@ absl::Status GraphicsEditor::DrawClipboardImport() {
gui::InputHex("Num Sheets", &num_sheets_to_load_); gui::InputHex("Num Sheets", &num_sheets_to_load_);
gui::ButtonPipe("Decompress Clipboard Data", [this]() { gui::ButtonPipe("Decompress Clipboard Data", [this]() {
if (temp_rom_.isLoaded()) { if (temp_rom_.is_loaded()) {
status_ = DecompressImportData(0x40000); status_ = DecompressImportData(0x40000);
} else { } else {
status_ = absl::InvalidArgumentError( status_ = absl::InvalidArgumentError(
@@ -690,8 +736,8 @@ absl::Status GraphicsEditor::DecompressImportData(int size) {
bin_bitmap_.Create(core::kTilesheetWidth, 0x2000, core::kTilesheetDepth, bin_bitmap_.Create(core::kTilesheetWidth, 0x2000, core::kTilesheetDepth,
converted_sheet); converted_sheet);
if (rom()->isLoaded()) { if (rom()->is_loaded()) {
auto palette_group = rom()->palette_group("ow_main"); auto palette_group = rom()->palette_group().overworld_animated;
z3_rom_palette_ = palette_group[current_palette_]; z3_rom_palette_ = palette_group[current_palette_];
if (col_file_) { if (col_file_) {
bin_bitmap_.ApplyPalette(col_file_palette_); bin_bitmap_.ApplyPalette(col_file_palette_);
@@ -723,9 +769,10 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
col_file_palette_group_[current_palette_index_]); col_file_palette_group_[current_palette_index_]);
} else { } else {
// ROM palette // ROM palette
auto palette_group =
rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]); auto palette_group = rom()->palette_group().get_group(
z3_rom_palette_ = palette_group[current_palette_index_]; kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
graphics_bin_[i].ApplyPalette(z3_rom_palette_); graphics_bin_[i].ApplyPalette(z3_rom_palette_);
} }
@@ -748,9 +795,9 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
col_file_palette_group_[current_palette_index_]); col_file_palette_group_[current_palette_index_]);
} else { } else {
// ROM palette // ROM palette
auto palette_group = auto palette_group = rom()->palette_group().get_group(
rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]); kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = palette_group[current_palette_index_]; z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
graphics_bin_[i].ApplyPalette(z3_rom_palette_); graphics_bin_[i].ApplyPalette(z3_rom_palette_);
} }

View File

@@ -15,7 +15,7 @@
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/gui/pipeline.h" #include "app/gui/pipeline.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld.h" #include "app/zelda3/overworld/overworld.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
@@ -50,9 +50,6 @@ constexpr const char* kPaletteGroupAddressesKeys[] = {
"grass", "3d_object", "ow_mini_map", "grass", "3d_object", "ow_mini_map",
}; };
static constexpr std::string_view kGfxEditColumnNames[] = {
"Tilesheets", "Current Graphics", "Palette Controls"};
static constexpr absl::string_view kGfxToolsetColumnNames[] = { static constexpr absl::string_view kGfxToolsetColumnNames[] = {
"#memoryEditor", "#memoryEditor",
"##separator_gfx1", "##separator_gfx1",
@@ -62,7 +59,21 @@ constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable | ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame; ImGuiTableFlags_SizingStretchSame;
class GraphicsEditor : public SharedROM { /**
* @class GraphicsEditor
* @brief Allows the user to edit graphics sheets from the game or view
* prototype graphics.
*
* The GraphicsEditor class is responsible for providing functionality to edit
* graphics sheets from the game or view prototype graphics of Link to the Past
* from the CGX, SCR, and OBJ formats. It provides various methods to update
* different components of the graphics editor, such as the graphics edit tab,
* link graphics view, and prototype graphics viewer. It also includes import
* functions for different file formats, as well as other utility functions for
* drawing toolsets, palette controls, clipboard imports, experimental features,
* and memory editor.
*/
class GraphicsEditor : public SharedRom {
public: public:
absl::Status Update(); absl::Status Update();
@@ -154,9 +165,9 @@ class GraphicsEditor : public SharedROM {
GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect; GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect;
ROM temp_rom_; Rom temp_rom_;
ROM tilemap_rom_; Rom tilemap_rom_;
zelda3::Overworld overworld_; zelda3::overworld::Overworld overworld_;
MemoryEditor cgx_memory_editor_; MemoryEditor cgx_memory_editor_;
MemoryEditor col_memory_editor_; MemoryEditor col_memory_editor_;
PaletteEditor palette_editor_; PaletteEditor palette_editor_;
@@ -176,13 +187,17 @@ class GraphicsEditor : public SharedROM {
gfx::BitmapTable clipboard_graphics_bin_; gfx::BitmapTable clipboard_graphics_bin_;
gfx::BitmapTable link_graphics_; gfx::BitmapTable link_graphics_;
gfx::PaletteGroup col_file_palette_group_; gfx::PaletteGroup col_file_palette_group_;
gfx::SNESPalette z3_rom_palette_; gfx::SnesPalette z3_rom_palette_;
gfx::SNESPalette col_file_palette_; gfx::SnesPalette col_file_palette_;
gfx::SNESPalette link_palette_; gfx::SnesPalette link_palette_;
gui::Canvas import_canvas_; gui::Canvas import_canvas_;
gui::Canvas scr_canvas_; gui::Canvas scr_canvas_;
gui::Canvas super_donkey_canvas_; gui::Canvas super_donkey_canvas_;
gui::Canvas current_sheet_canvas_; gui::Canvas current_sheet_canvas_{ImVec2(0x80, 0x20),
gui::CanvasGridSize::k8x8};
gui::Canvas link_canvas_{
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k16x16};
absl::Status status_; absl::Status status_;
}; };

View File

@@ -4,6 +4,7 @@
#include <ImGuiFileDialog/ImGuiFileDialog.h> #include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h> #include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h> #include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_internal.h>
#include <imgui_memory_editor.h> #include <imgui_memory_editor.h>
#include "absl/status/status.h" #include "absl/status/status.h"
@@ -113,6 +114,7 @@ class RecentFilesManager {
} // namespace } // namespace
using ImGui::BeginMenu; using ImGui::BeginMenu;
using ImGui::Checkbox;
using ImGui::MenuItem; using ImGui::MenuItem;
using ImGui::Text; using ImGui::Text;
@@ -121,6 +123,24 @@ void MasterEditor::SetupScreen(std::shared_ptr<SDL_Renderer> renderer) {
rom()->SetupRenderer(renderer); rom()->SetupRenderer(renderer);
} }
namespace {
// Function to switch the active tab in a tab bar
void SetTabBarTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) {
if (tab_bar == NULL) return;
// Find the tab item with the specified tab_id
ImGuiTabItem* tab_item = &tab_bar->Tabs[tab_id];
tab_item->LastFrameVisible = -1;
tab_item->LastFrameSelected = -1;
tab_bar->VisibleTabId = tab_id;
tab_bar->VisibleTabWasSubmitted = true;
tab_bar->SelectedTabId = tab_id;
tab_bar->NextSelectedTabId = tab_id;
tab_bar->ReorderRequestTabId = tab_id;
tab_bar->CurrFrameVisible = -1;
}
} // namespace
absl::Status MasterEditor::Update() { absl::Status MasterEditor::Update() {
NewMasterFrame(); NewMasterFrame();
@@ -130,22 +150,31 @@ absl::Status MasterEditor::Update() {
DrawAboutPopup(); DrawAboutPopup();
DrawInfoPopup(); DrawInfoPopup();
if (rom()->isLoaded() && !rom_assets_loaded_) { if (rom()->is_loaded() && !rom_assets_loaded_) {
// Load all of the graphics data from the game.
RETURN_IF_ERROR(rom()->LoadAllGraphicsData())
// Initialize overworld graphics, maps, and palettes // Initialize overworld graphics, maps, and palettes
RETURN_IF_ERROR(overworld_editor_.LoadGraphics()); RETURN_IF_ERROR(overworld_editor_.LoadGraphics());
rom_assets_loaded_ = true; rom_assets_loaded_ = true;
} }
TAB_BAR("##TabBar") TAB_BAR("##TabBar")
auto current_tab_bar = ImGui::GetCurrentContext()->CurrentTabBar;
gui::RenderTabItem("Overworld", [&]() { if (overworld_editor_.jump_to_tab() == -1) {
current_editor_ = &overworld_editor_; gui::RenderTabItem("Overworld", [&]() {
status_ = overworld_editor_.Update(); current_editor_ = &overworld_editor_;
}); status_ = overworld_editor_.Update();
});
}
gui::RenderTabItem("Dungeon", [&]() { gui::RenderTabItem("Dungeon", [&]() {
current_editor_ = &dungeon_editor_; current_editor_ = &dungeon_editor_;
status_ = dungeon_editor_.Update(); status_ = dungeon_editor_.Update();
if (overworld_editor_.jump_to_tab() != -1) {
dungeon_editor_.add_room(overworld_editor_.jump_to_tab());
overworld_editor_.jump_to_tab_ = -1;
}
}); });
gui::RenderTabItem("Graphics", gui::RenderTabItem("Graphics",
@@ -195,6 +224,10 @@ void MasterEditor::DrawStatusPopup() {
if (ImGui::Button("OK", gui::kDefaultModalSize)) { if (ImGui::Button("OK", gui::kDefaultModalSize)) {
show_status_ = false; show_status_ = false;
} }
ImGui::SameLine();
if (ImGui::Button(ICON_MD_CONTENT_COPY, ImVec2(50, 0))) {
ImGui::SetClipboardText(prev_status_.ToString().c_str());
}
ImGui::End(); ImGui::End();
} }
} }
@@ -203,7 +236,7 @@ void MasterEditor::DrawAboutPopup() {
if (about_) ImGui::OpenPopup("About"); if (about_) ImGui::OpenPopup("About");
if (ImGui::BeginPopupModal("About", nullptr, if (ImGui::BeginPopupModal("About", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) { ImGuiWindowFlags_AlwaysAutoResize)) {
Text("Yet Another Zelda3 Editor - v0.05"); Text("Yet Another Zelda3 Editor - v%.2f", core::kYazeVersion);
Text("Written by: scawful"); Text("Written by: scawful");
ImGui::Spacing(); ImGui::Spacing();
Text("Special Thanks: Zarby89, JaredBrian"); Text("Special Thanks: Zarby89, JaredBrian");
@@ -307,35 +340,66 @@ void MasterEditor::DrawFileMenu() {
ImGui::EndMenu(); ImGui::EndMenu();
} }
MENU_ITEM2("Save", "Ctrl+S") { status_ = rom()->SaveToFile(backup_rom_); } MENU_ITEM2("Save", "Ctrl+S") {
if (rom()->is_loaded()) {
SaveRom();
}
}
MENU_ITEM("Save As..") { save_as_menu = true; } MENU_ITEM("Save As..") { save_as_menu = true; }
if (rom()->isLoaded()) { if (rom()->is_loaded()) {
MENU_ITEM("Reload") { status_ = rom()->Reload(); } MENU_ITEM("Reload") { status_ = rom()->Reload(); }
MENU_ITEM("Close") { status_ = rom()->Close(); } MENU_ITEM("Close") {
status_ = rom()->Close();
rom_assets_loaded_ = false;
}
} }
ImGui::Separator(); ImGui::Separator();
if (BeginMenu("Options")) { if (BeginMenu("Options")) {
MenuItem("Backup ROM", "", &backup_rom_); MenuItem("Backup ROM", "", &backup_rom_);
MenuItem("Save New Auto", "", &save_new_auto_);
ImGui::Separator(); ImGui::Separator();
Text("Experiment Flags"); if (BeginMenu("Experiment Flags")) {
ImGui::Checkbox("Enable Texture Streaming", if (BeginMenu("Overworld Flags")) {
&mutable_flags()->kLoadTexturesAsStreaming); Checkbox("Enable Overworld Sprites",
ImGui::Checkbox("Enable Overworld Sprites", &mutable_flags()->overworld.kDrawOverworldSprites);
&mutable_flags()->kDrawOverworldSprites); ImGui::Separator();
ImGui::Checkbox("Use Bitmap Manager", Checkbox("Save Overworld Maps",
&mutable_flags()->kUseBitmapManager); &mutable_flags()->overworld.kSaveOverworldMaps);
ImGui::Checkbox("Log Instructions to Debugger", Checkbox("Save Overworld Entrances",
&mutable_flags()->kLogInstructions); &mutable_flags()->overworld.kSaveOverworldEntrances);
ImGui::Checkbox("Use New ImGui Input", Checkbox("Save Overworld Exits",
&mutable_flags()->kUseNewImGuiInput); &mutable_flags()->overworld.kSaveOverworldExits);
ImGui::Checkbox("Save All Palettes", &mutable_flags()->kSaveAllPalettes); Checkbox("Save Overworld Items",
ImGui::Checkbox("Save With Change Queue", &mutable_flags()->overworld.kSaveOverworldItems);
&mutable_flags()->kSaveWithChangeQueue); Checkbox("Save Overworld Properties",
ImGui::Checkbox("Draw Dungeon Room Graphics", &mutable_flags()->overworld.kSaveOverworldProperties);
&mutable_flags()->kDrawDungeonRoomGraphics); ImGui::EndMenu();
}
if (BeginMenu("Dungeon Flags")) {
Checkbox("Draw Dungeon Room Graphics",
&mutable_flags()->kDrawDungeonRoomGraphics);
ImGui::Separator();
Checkbox("Save Dungeon Maps", &mutable_flags()->kSaveDungeonMaps);
ImGui::EndMenu();
}
Checkbox("Enable Console Logging", &mutable_flags()->kLogToConsole);
Checkbox("Enable Texture Streaming",
&mutable_flags()->kLoadTexturesAsStreaming);
Checkbox("Use Bitmap Manager", &mutable_flags()->kUseBitmapManager);
Checkbox("Log Instructions to Debugger",
&mutable_flags()->kLogInstructions);
Checkbox("Save All Palettes", &mutable_flags()->kSaveAllPalettes);
Checkbox("Save With Change Queue",
&mutable_flags()->kSaveWithChangeQueue);
Checkbox("Use New ImGui Input", &mutable_flags()->kUseNewImGuiInput);
ImGui::EndMenu();
}
ImGui::EndMenu(); ImGui::EndMenu();
} }
@@ -353,7 +417,7 @@ void MasterEditor::DrawFileMenu() {
ImGui::Begin("Save As..", &save_as_menu, ImGuiWindowFlags_AlwaysAutoResize); ImGui::Begin("Save As..", &save_as_menu, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::InputText("Filename", &save_as_filename); ImGui::InputText("Filename", &save_as_filename);
if (ImGui::Button("Save", gui::kDefaultModalSize)) { if (ImGui::Button("Save", gui::kDefaultModalSize)) {
status_ = rom()->SaveToFile(backup_rom_, save_as_filename); SaveRom();
save_as_menu = false; save_as_menu = false;
} }
ImGui::SameLine(); ImGui::SameLine();
@@ -388,6 +452,7 @@ void MasterEditor::DrawViewMenu() {
static bool show_memory_viewer = false; static bool show_memory_viewer = false;
static bool show_palette_editor = false; static bool show_palette_editor = false;
static bool show_emulator = false; static bool show_emulator = false;
static bool show_resource_label_manager = false;
if (show_emulator) { if (show_emulator) {
ImGui::Begin("Emulator", &show_emulator, ImGuiWindowFlags_MenuBar); ImGui::Begin("Emulator", &show_emulator, ImGuiWindowFlags_MenuBar);
@@ -401,7 +466,43 @@ void MasterEditor::DrawViewMenu() {
if (show_memory_editor) { if (show_memory_editor) {
static MemoryEditor mem_edit; static MemoryEditor mem_edit;
mem_edit.DrawWindow("Memory Editor", (void*)&(*rom()), rom()->size()); static MemoryEditor comp_edit;
static bool show_compare_rom = false;
static Rom comparison_rom;
ImGui::Begin("Hex Editor", &show_memory_editor);
if (ImGui::Button("Compare Rom")) {
auto file_name = FileDialogWrapper::ShowOpenFileDialog();
PRINT_IF_ERROR(comparison_rom.LoadFromFile(file_name));
show_compare_rom = true;
}
static uint64_t convert_address = 0;
gui::InputHex("SNES to PC", (int*)&convert_address, 6, 200.f);
ImGui::SameLine();
ImGui::Text("%x", core::SnesToPc(convert_address));
// mem_edit.DrawWindow("Memory Editor", (void*)&(*rom()), rom()->size());
BEGIN_TABLE("Memory Comparison", 2, ImGuiTableFlags_Resizable);
SETUP_COLUMN("Source")
SETUP_COLUMN("Dest")
NEXT_COLUMN()
ImGui::Text("%s", rom()->filename().data());
mem_edit.DrawContents((void*)&(*rom()), rom()->size());
NEXT_COLUMN()
if (show_compare_rom) {
comp_edit.SetComparisonData((void*)&(*rom()));
ImGui::BeginGroup();
ImGui::BeginChild("Comparison ROM");
ImGui::Text("%s", comparison_rom.filename().data());
comp_edit.DrawContents((void*)&(comparison_rom), comparison_rom.size());
ImGui::EndChild();
ImGui::EndGroup();
}
END_TABLE()
ImGui::End();
} }
if (show_imgui_demo) { if (show_imgui_demo) {
@@ -441,12 +542,20 @@ void MasterEditor::DrawViewMenu() {
ImGui::End(); ImGui::End();
} }
if (show_resource_label_manager) {
rom()->resource_label()->DisplayLabels(&show_resource_label_manager);
}
if (BeginMenu("View")) { if (BeginMenu("View")) {
MenuItem("Emulator", nullptr, &show_emulator); 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("Memory Viewer", nullptr, &show_memory_viewer);
ImGui::Separator();
MenuItem("Resource Label Manager", nullptr, &show_resource_label_manager);
ImGui::Separator();
MenuItem("Hex Editor", nullptr, &show_memory_editor);
MenuItem("Assembly Editor", nullptr, &show_asm_editor);
MenuItem("Palette Editor", nullptr, &show_palette_editor);
ImGui::Separator();
MenuItem("ImGui Demo", nullptr, &show_imgui_demo); MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics); MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics);
ImGui::EndMenu(); ImGui::EndMenu();
@@ -455,12 +564,32 @@ void MasterEditor::DrawViewMenu() {
void MasterEditor::DrawHelpMenu() { void MasterEditor::DrawHelpMenu() {
static bool open_rom_help = false; static bool open_rom_help = false;
static bool open_supported_features = false;
if (BeginMenu("Help")) { if (BeginMenu("Help")) {
if (MenuItem("How to open a ROM")) open_rom_help = true; if (MenuItem("How to open a ROM")) open_rom_help = true;
if (MenuItem("Supported Features")) open_supported_features = true;
if (MenuItem("About")) about_ = true; if (MenuItem("About")) about_ = true;
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (open_supported_features) ImGui::OpenPopup("Supported Features");
if (ImGui::BeginPopupModal("Supported Features", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
// TODO: Expand on details of what is currently implemented.
ImGui::BulletText("Overworld Editing");
ImGui::BulletText("Dungeon Editing");
ImGui::BulletText("Sprite Editing");
ImGui::BulletText("Palette Editing");
ImGui::BulletText("Screen Editing");
if (ImGui::Button("Close", gui::kDefaultModalSize)) {
open_supported_features = false;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (open_rom_help) ImGui::OpenPopup("Open a ROM"); if (open_rom_help) ImGui::OpenPopup("Open a ROM");
if (ImGui::BeginPopupModal("Open a ROM", nullptr, if (ImGui::BeginPopupModal("Open a ROM", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) { ImGuiWindowFlags_AlwaysAutoResize)) {
@@ -479,6 +608,41 @@ void MasterEditor::DrawHelpMenu() {
} }
} }
void MasterEditor::SaveRom() {
if (flags()->kSaveDungeonMaps) {
status_ = screen_editor_.SaveDungeonMaps();
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldMaps) {
RETURN_VOID_IF_ERROR(
status_ = overworld_editor_.overworld()->CreateTile32Tilemap());
status_ = overworld_editor_.overworld()->SaveMap32Tiles();
RETURN_VOID_IF_ERROR(status_);
status_ = overworld_editor_.overworld()->SaveMap16Tiles();
RETURN_VOID_IF_ERROR(status_);
status_ = overworld_editor_.overworld()->SaveOverworldMaps();
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldEntrances) {
status_ = overworld_editor_.overworld()->SaveEntrances();
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldExits) {
status_ = overworld_editor_.overworld()->SaveExits();
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldItems) {
status_ = overworld_editor_.overworld()->SaveItems();
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldProperties) {
status_ = overworld_editor_.overworld()->SaveMapProperties();
RETURN_VOID_IF_ERROR(status_);
}
status_ = rom()->SaveToFile(backup_rom_, save_new_auto_);
}
} // namespace editor } // namespace editor
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,6 +1,8 @@
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H #ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
#define YAZE_APP_EDITOR_MASTER_EDITOR_H #define YAZE_APP_EDITOR_MASTER_EDITOR_H
#define IMGUI_DEFINE_MATH_OPERATORS 1
#include <ImGuiColorTextEdit/TextEditor.h> #include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h> #include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h> #include <imgui/imgui.h>
@@ -10,7 +12,6 @@
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/core/common.h" #include "app/core/common.h"
#include "app/core/constants.h" #include "app/core/constants.h"
#include "app/gui/pipeline.h"
#include "app/editor/context/gfx_context.h" #include "app/editor/context/gfx_context.h"
#include "app/editor/dungeon_editor.h" #include "app/editor/dungeon_editor.h"
#include "app/editor/graphics_editor.h" #include "app/editor/graphics_editor.h"
@@ -26,14 +27,33 @@
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/gui/pipeline.h"
#include "app/rom.h" #include "app/rom.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
class MasterEditor : public SharedROM, /**
public GfxContext, * @class MasterEditor
* @brief The MasterEditor class represents the main editor for a Rom in the
* Yaze application.
*
* This class inherits from SharedRom, GfxContext, and ExperimentFlags, and
* provides functionality for setting up the screen, updating the editor, and
* shutting down the editor. It also includes methods for drawing various menus
* and popups, saving the Rom, and managing editor-specific flags.
*
* The MasterEditor class contains instances of various editor classes such as
* AssemblyEditor, DungeonEditor, GraphicsEditor, MusicEditor, OverworldEditor,
* PaletteEditor, ScreenEditor, and SpriteEditor. The current_editor_ member
* variable points to the currently active editor in the tab view.
*
* @note This class assumes the presence of an SDL_Renderer object for rendering
* graphics.
*/
class MasterEditor : public SharedRom,
public context::GfxContext,
public core::ExperimentFlags { public core::ExperimentFlags {
public: public:
MasterEditor() { current_editor_ = &overworld_editor_; } MasterEditor() { current_editor_ = &overworld_editor_; }
@@ -55,9 +75,12 @@ class MasterEditor : public SharedROM,
void DrawViewMenu(); void DrawViewMenu();
void DrawHelpMenu(); void DrawHelpMenu();
void SaveRom();
bool about_ = false; bool about_ = false;
bool rom_info_ = false; bool rom_info_ = false;
bool backup_rom_ = true; bool backup_rom_ = false;
bool save_new_auto_ = true;
bool show_status_ = false; bool show_status_ = false;
bool rom_assets_loaded_ = false; bool rom_assets_loaded_ = false;

View File

@@ -12,6 +12,10 @@ namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
/**
* @class AssemblyEditor
* @brief Text editor for modifying assembly code.
*/
class AssemblyEditor { class AssemblyEditor {
public: public:
AssemblyEditor(); AssemblyEditor();

View File

@@ -6,18 +6,19 @@
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h" #include "absl/status/statusor.h"
#include "app/core/editor.h"
#include "app/editor/modules/palette_editor.h" #include "app/editor/modules/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/gui/pipeline.h" #include "app/gui/pipeline.h"
#include "app/gui/widgets.h" #include "app/gui/widgets.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld.h" #include "app/zelda3/overworld/overworld.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
@@ -33,89 +34,15 @@ absl::Status GfxGroupEditor::Update() {
if (ImGui::BeginTabBar("GfxGroupEditor")) { if (ImGui::BeginTabBar("GfxGroupEditor")) {
if (ImGui::BeginTabItem("Main")) { if (ImGui::BeginTabItem("Main")) {
gui::InputHexByte("Selected Blockset", &selected_blockset_); 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();
}
DrawBlocksetViewer();
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
if (ImGui::BeginTabItem("Rooms")) { if (ImGui::BeginTabItem("Rooms")) {
gui::InputHexByte("Selected Blockset", &selected_roomset_); gui::InputHexByte("Selected Blockset", &selected_roomset_);
ImGui::Text("Values - Overwrites 4 of main blockset"); DrawRoomsetViewer();
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(); ImGui::EndTabItem();
} }
@@ -123,43 +50,12 @@ absl::Status GfxGroupEditor::Update() {
gui::InputHexByte("Selected Spriteset", &selected_spriteset_); gui::InputHexByte("Selected Spriteset", &selected_spriteset_);
ImGui::Text("Values"); ImGui::Text("Values");
if (ImGui::BeginTable("##SpritesTable", 2, ImGuiTableFlags_Borders, DrawSpritesetViewer();
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(); ImGui::EndTabItem();
} }
if (ImGui::BeginTabItem("Palettes")) { if (ImGui::BeginTabItem("Palettes")) {
DrawPaletteViewer();
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
@@ -173,6 +69,174 @@ absl::Status GfxGroupEditor::Update() {
return absl::OkStatus(); return absl::OkStatus();
} }
void GfxGroupEditor::DrawBlocksetViewer(bool sheet_only) {
if (ImGui::BeginTable("##BlocksetTable", sheet_only ? 1 : 2,
ImGuiTableFlags_Borders, ImVec2(0, 0))) {
if (!sheet_only) {
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
}
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
if (!sheet_only) {
TableNextColumn();
{
ImGui::BeginGroup();
for (int i = 0; i < 8; i++) {
ImGui::SetNextItemWidth(100.f);
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
&rom()->main_blockset_ids[selected_blockset_][i]);
}
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();
}
}
void GfxGroupEditor::DrawRoomsetViewer() {
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(("0x" + std::to_string(i)).c_str(),
&rom()->room_blockset_ids[selected_roomset_][i]);
}
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();
}
}
void GfxGroupEditor::DrawSpritesetViewer(bool sheet_only) {
if (ImGui::BeginTable("##SpritesTable", sheet_only ? 1 : 2,
ImGuiTableFlags_Borders, ImVec2(0, 0))) {
if (!sheet_only) {
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
}
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
if (!sheet_only) {
TableNextColumn();
{
ImGui::BeginGroup();
for (int i = 0; i < 4; i++) {
ImGui::SetNextItemWidth(100.f);
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
&rom()->spriteset_ids[selected_spriteset_][i]);
}
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()[115 + sheet_id];
gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 24);
}
ImGui::EndGroup();
}
ImGui::EndTable();
}
}
namespace {
void DrawPaletteFromPaletteGroup(gfx::SnesPalette &palette) {
for (int n = 0; n < palette.size(); n++) {
ImGui::PushID(n);
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
auto popup_id = absl::StrCat("Palette", n);
// Small icon of the color in the palette
if (gui::SnesColorButton(popup_id, palette[n],
ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip)) {
}
ImGui::PopID();
}
}
} // namespace
void GfxGroupEditor::DrawPaletteViewer() {
static uint8_t selected_paletteset = 0;
gui::InputHexByte("Selected Paletteset", &selected_paletteset);
auto dungeon_main_palette_val = rom()->paletteset_ids[selected_paletteset][0];
auto dungeon_spr_pal_1_val = rom()->paletteset_ids[selected_paletteset][1];
auto dungeon_spr_pal_2_val = rom()->paletteset_ids[selected_paletteset][2];
auto dungeon_spr_pal_3_val = rom()->paletteset_ids[selected_paletteset][3];
gui::InputHexByte("Dungeon Main", &dungeon_main_palette_val);
gui::InputHexByte("Dungeon Spr Pal 1", &dungeon_spr_pal_1_val);
gui::InputHexByte("Dungeon Spr Pal 2", &dungeon_spr_pal_2_val);
gui::InputHexByte("Dungeon Spr Pal 3", &dungeon_spr_pal_3_val);
auto &palette = *rom()->mutable_palette_group()->dungeon_main.mutable_palette(
rom()->paletteset_ids[selected_paletteset][0]);
DrawPaletteFromPaletteGroup(palette);
auto &spr_aux_pal1 =
*rom()->mutable_palette_group()->sprites_aux1.mutable_palette(
rom()->paletteset_ids[selected_paletteset][1]);
DrawPaletteFromPaletteGroup(spr_aux_pal1);
auto &spr_aux_pal2 =
*rom()->mutable_palette_group()->sprites_aux2.mutable_palette(
rom()->paletteset_ids[selected_paletteset][2]);
DrawPaletteFromPaletteGroup(spr_aux_pal2);
auto &spr_aux_pal3 =
*rom()->mutable_palette_group()->sprites_aux3.mutable_palette(
rom()->paletteset_ids[selected_paletteset][3]);
DrawPaletteFromPaletteGroup(spr_aux_pal3);
}
void GfxGroupEditor::InitBlockset(gfx::Bitmap tile16_blockset) { void GfxGroupEditor::InitBlockset(gfx::Bitmap tile16_blockset) {
tile16_blockset_bmp_ = tile16_blockset; tile16_blockset_bmp_ = tile16_blockset;
} }

View File

@@ -7,26 +7,41 @@
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h" #include "absl/status/statusor.h"
#include "app/core/editor.h" #include "app/editor/utils/editor.h"
#include "app/gui/pipeline.h"
#include "app/editor/modules/palette_editor.h" #include "app/editor/modules/palette_editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/pipeline.h"
#include "app/gui/widgets.h" #include "app/gui/widgets.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld.h" #include "app/zelda3/overworld/overworld.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
class GfxGroupEditor : public SharedROM { /**
* @class GfxGroupEditor
* @brief Manage graphics group configurations in a Rom.
*/
class GfxGroupEditor : public SharedRom {
public: public:
absl::Status Update(); absl::Status Update();
void DrawBlocksetViewer(bool sheet_only = false);
void DrawRoomsetViewer();
void DrawSpritesetViewer(bool sheet_only = false);
void DrawPaletteViewer();
void SetSelectedBlockset(uint8_t blockset) { selected_blockset_ = blockset; }
void SetSelectedRoomset(uint8_t roomset) { selected_roomset_ = roomset; }
void SetSelectedSpriteset(uint8_t spriteset) {
selected_spriteset_ = spriteset;
}
void InitBlockset(gfx::Bitmap tile16_blockset); void InitBlockset(gfx::Bitmap tile16_blockset);
private: private:
@@ -36,11 +51,13 @@ class GfxGroupEditor : public SharedROM {
uint8_t selected_roomset_ = 0; uint8_t selected_roomset_ = 0;
uint8_t selected_spriteset_ = 0; uint8_t selected_spriteset_ = 0;
PaletteEditor palette_editor_;
gui::Canvas blockset_canvas_; gui::Canvas blockset_canvas_;
gui::Canvas roomset_canvas_; gui::Canvas roomset_canvas_;
gui::Canvas spriteset_canvas_; gui::Canvas spriteset_canvas_;
gfx::SNESPalette palette_; gfx::SnesPalette palette_;
gfx::PaletteGroup palette_group_; gfx::PaletteGroup palette_group_;
gfx::Bitmap tile16_blockset_bmp_; gfx::Bitmap tile16_blockset_bmp_;
@@ -48,7 +65,7 @@ class GfxGroupEditor : public SharedROM {
std::vector<gfx::Bitmap> tile16_individual_; std::vector<gfx::Bitmap> tile16_individual_;
gui::BitmapViewer gfx_group_viewer_; gui::BitmapViewer gfx_group_viewer_;
zelda3::Overworld overworld_; zelda3::overworld::Overworld overworld_;
}; };
} // namespace editor } // namespace editor

View File

@@ -54,7 +54,12 @@ static const char* kGameSongs[] = {"Title",
static constexpr absl::string_view kSongNotes[] = { static constexpr absl::string_view kSongNotes[] = {
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C", "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"}; "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C"};
class MusicEditor : public SharedROM {
/**
* @class MusicEditor
* @brief A class for editing music data in a Rom.
*/
class MusicEditor : public SharedRom {
public: public:
void Update(); void Update();
@@ -65,7 +70,7 @@ class MusicEditor : public SharedROM {
void DrawSongToolset(); void DrawSongToolset();
void DrawToolset(); void DrawToolset();
zelda3::Tracker music_tracker_; zelda3::music::Tracker music_tracker_;
// Mix_Music* current_song_ = NULL; // Mix_Music* current_song_ = NULL;

View File

@@ -35,6 +35,15 @@ namespace app {
namespace editor { namespace editor {
absl::Status PaletteEditor::Update() { absl::Status PaletteEditor::Update() {
if (rom()->is_loaded()) {
// Initialize the labels
for (int i = 0; i < kNumPalettes; i++) {
rom()->resource_label()->CreateOrGetLabel(
"Palette Group Name", std::to_string(i),
std::string(kPaletteGroupNames[i]));
}
}
if (ImGui::BeginTable("paletteEditorTable", 2, if (ImGui::BeginTable("paletteEditorTable", 2,
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable | ImGuiTableFlags_Resizable |
@@ -55,7 +64,9 @@ absl::Status PaletteEditor::Update() {
} }
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Test Column"); if (gui::SnesColorEdit4("Color Picker", current_color_,
ImGuiColorEditFlags_NoAlpha)) {
}
ImGui::EndTable(); ImGui::EndTable();
} }
@@ -64,71 +75,74 @@ absl::Status PaletteEditor::Update() {
return absl::OkStatus(); return absl::OkStatus();
} }
void PaletteEditor::EditColorInPalette(gfx::SNESPalette& palette, int index) { absl::Status PaletteEditor::EditColorInPalette(gfx::SnesPalette& palette,
int index) {
if (index >= palette.size()) { if (index >= palette.size()) {
// Handle error: the index is out of bounds return absl::InvalidArgumentError("Index out of bounds");
return;
} }
// Get the current color // Get the current color
auto currentColor = palette.GetColor(index).GetRGB(); ASSIGN_OR_RETURN(auto color, palette.GetColor(index));
auto currentColor = color.rgb();
if (ImGui::ColorPicker4("Color Picker", (float*)&palette[index])) { if (ImGui::ColorPicker4("Color Picker", (float*)&palette[index])) {
// The color was modified, update it in the palette // The color was modified, update it in the palette
palette(index, currentColor); palette(index, currentColor);
} }
return absl::OkStatus();
} }
void PaletteEditor::ResetColorToOriginal( absl::Status PaletteEditor::ResetColorToOriginal(
gfx::SNESPalette& palette, int index, gfx::SnesPalette& palette, int index,
const gfx::SNESPalette& originalPalette) { const gfx::SnesPalette& originalPalette) {
if (index >= palette.size() || index >= originalPalette.size()) { if (index >= palette.size() || index >= originalPalette.size()) {
// Handle error: the index is out of bounds return absl::InvalidArgumentError("Index out of bounds");
return;
} }
ASSIGN_OR_RETURN(auto color, originalPalette.GetColor(index));
auto originalColor = originalPalette.GetColor(index).GetRGB(); auto originalColor = color.rgb();
palette(index, originalColor); palette(index, originalColor);
return absl::OkStatus();
} }
absl::Status PaletteEditor::DrawPaletteGroup(int category) { absl::Status PaletteEditor::DrawPaletteGroup(int category) {
if (!rom()->isLoaded()) { if (!rom()->is_loaded()) {
return absl::NotFoundError("ROM not open, no palettes to display"); return absl::NotFoundError("ROM not open, no palettes to display");
} }
const auto size = std::string group_name = kPaletteGroupNames[category].data();
rom()->palette_group(kPaletteGroupNames[category].data()).size(); auto palette_group = *rom()->palette_group().get_group(group_name);
auto palettes = rom()->palette_group(kPaletteGroupNames[category].data()); const auto size = palette_group.size();
static bool edit_color = false; static bool edit_color = false;
for (int j = 0; j < size; j++) { for (int j = 0; j < size; j++) {
ImGui::Text("%d", j); // ImGui::Text("%d", j);
rom()->resource_label()->SelectableLabelWithNameEdit(
auto palette = palettes[j]; false, "Palette Group Name", std::to_string(j),
auto pal_size = palette.size(); std::string(kPaletteGroupNames[category]));
auto palette = palette_group.mutable_palette(j);
auto pal_size = palette->size();
for (int n = 0; n < pal_size; n++) { for (int n = 0; n < pal_size; n++) {
ImGui::PushID(n); ImGui::PushID(n);
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); if ((n % 7) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
auto popup_id = auto popup_id =
absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n); absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
// Small icon of the color in the palette // Small icon of the color in the palette
if (gui::SNESColorButton(popup_id, palette[n], palette_button_flags)) { if (gui::SnesColorButton(popup_id, *palette->mutable_color(n),
edit_color = true; palette_button_flags)) {
ASSIGN_OR_RETURN(current_color_, palette->GetColor(n));
// EditColorInPalette(*palette, n);
} }
if (ImGui::BeginPopupContextItem(popup_id.c_str())) { if (ImGui::BeginPopupContextItem(popup_id.c_str())) {
RETURN_IF_ERROR(HandleColorPopup(palette, category, j, n)) RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
} }
if (edit_color) { // if (gui::SnesColorEdit4(popup_id.c_str(), (*palette)[n],
// The color button was clicked, open the popup // palette_button_flags)) {
if (ImGui::ColorEdit4(popup_id.c_str(), // EditColorInPalette(*palette, n);
gfx::ToFloatArray(palette[n]).data(), // }
palette_button_flags)) {
EditColorInPalette(palette, n);
}
}
ImGui::PopID(); ImGui::PopID();
} }
@@ -136,16 +150,14 @@ absl::Status PaletteEditor::DrawPaletteGroup(int category) {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status PaletteEditor::HandleColorPopup(gfx::SNESPalette& palette, int i, absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i,
int j, int n) { int j, int n) {
auto col = gfx::ToFloatArray(palette[n]); auto col = gfx::ToFloatArray(palette[n]);
if (ImGui::ColorEdit4("Edit Color", col.data(), color_popup_flags)) { if (gui::SnesColorEdit4("Edit Color", palette[n], color_popup_flags)) {
RETURN_IF_ERROR(rom()->UpdatePaletteColor(kPaletteGroupNames[i].data(), j, // TODO: Implement new update color function
n, palette[n]))
} }
if (ImGui::Button("Copy as..", ImVec2(-1, 0))) ImGui::OpenPopup("Copy"); if (ImGui::Button("Copy as..", ImVec2(-1, 0))) ImGui::OpenPopup("Copy");
if (ImGui::BeginPopup("Copy")) { if (ImGui::BeginPopup("Copy")) {
int cr = IM_F32_TO_INT8_SAT(col[0]); int cr = IM_F32_TO_INT8_SAT(col[0]);
int cg = IM_F32_TO_INT8_SAT(col[1]); int cg = IM_F32_TO_INT8_SAT(col[1]);
@@ -167,7 +179,7 @@ absl::Status PaletteEditor::HandleColorPopup(gfx::SNESPalette& palette, int i,
return absl::OkStatus(); return absl::OkStatus();
} }
void PaletteEditor::DisplayPalette(gfx::SNESPalette& palette, bool loaded) { void PaletteEditor::DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
static ImVec4 color = ImVec4(0, 0, 0, 255.f); static ImVec4 color = ImVec4(0, 0, 0, 255.f);
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
ImGuiColorEditFlags_NoDragDrop | ImGuiColorEditFlags_NoDragDrop |
@@ -245,7 +257,7 @@ void PaletteEditor::DisplayPalette(gfx::SNESPalette& palette, bool loaded) {
} }
} }
void PaletteEditor::DrawPortablePalette(gfx::SNESPalette& palette) { void PaletteEditor::DrawPortablePalette(gfx::SnesPalette& palette) {
static bool init = false; static bool init = false;
if (!init) { if (!init) {
InitializeSavedPalette(palette); InitializeSavedPalette(palette);

View File

@@ -25,20 +25,21 @@ static constexpr absl::string_view kPaletteGroupNames[] = {
"ow_aux", "global_sprites", "dungeon_main", "ow_mini_map", "ow_aux", "global_sprites", "dungeon_main", "ow_mini_map",
"ow_mini_map", "3d_object", "3d_object"}; "ow_mini_map", "3d_object", "3d_object"};
namespace palette_internal {
struct PaletteChange { struct PaletteChange {
std::string groupName; std::string group_name;
size_t paletteIndex; size_t palette_index;
size_t colorIndex; size_t color_index;
gfx::SNESColor originalColor; gfx::SnesColor original_color;
gfx::SNESColor newColor; gfx::SnesColor new_color;
}; };
class PaletteEditorHistory { class PaletteEditorHistory {
public: public:
// Record a change in the palette editor // Record a change in the palette editor
void RecordChange(const std::string& groupName, size_t paletteIndex, void RecordChange(const std::string& groupName, size_t paletteIndex,
size_t colorIndex, const gfx::SNESColor& originalColor, size_t colorIndex, const gfx::SnesColor& originalColor,
const gfx::SNESColor& newColor) { const gfx::SnesColor& newColor) {
// Check size and remove the oldest if necessary // Check size and remove the oldest if necessary
if (recentChanges.size() >= maxHistorySize) { if (recentChanges.size() >= maxHistorySize) {
recentChanges.pop_front(); recentChanges.pop_front();
@@ -55,61 +56,67 @@ class PaletteEditorHistory {
} }
// Restore the original color // Restore the original color
gfx::SNESColor GetOriginalColor(const std::string& groupName, gfx::SnesColor GetOriginalColor(const std::string& groupName,
size_t paletteIndex, size_t paletteIndex,
size_t colorIndex) const { size_t colorIndex) const {
for (const auto& change : recentChanges) { for (const auto& change : recentChanges) {
if (change.groupName == groupName && if (change.group_name == groupName &&
change.paletteIndex == paletteIndex && change.palette_index == paletteIndex &&
change.colorIndex == colorIndex) { change.color_index == colorIndex) {
return change.originalColor; return change.original_color;
} }
} }
// Handle error or return default (this is just an example, // Handle error or return default (this is just an example,
// handle as appropriate for your application) // handle as appropriate for your application)
return gfx::SNESColor(); return gfx::SnesColor();
} }
private: private:
std::deque<PaletteChange> recentChanges; std::deque<PaletteChange> recentChanges;
static const size_t maxHistorySize = 50; // or any other number you deem fit static const size_t maxHistorySize = 50; // or any other number you deem fit
}; };
} // namespace palette_internal
class PaletteEditor : public SharedROM { /**
* @class PaletteEditor
* @brief Allows the user to view and edit in game palettes.
*/
class PaletteEditor : public SharedRom {
public: public:
absl::Status Update(); absl::Status Update();
absl::Status DrawPaletteGroups(); absl::Status DrawPaletteGroups();
void EditColorInPalette(gfx::SNESPalette& palette, int index); absl::Status EditColorInPalette(gfx::SnesPalette& palette, int index);
void ResetColorToOriginal(gfx::SNESPalette& palette, int index, absl::Status ResetColorToOriginal(gfx::SnesPalette& palette, int index,
const gfx::SNESPalette& originalPalette); const gfx::SnesPalette& originalPalette);
void DisplayPalette(gfx::SNESPalette& palette, bool loaded); void DisplayPalette(gfx::SnesPalette& palette, bool loaded);
void DrawPortablePalette(gfx::SNESPalette& palette); void DrawPortablePalette(gfx::SnesPalette& palette);
absl::Status DrawPaletteGroup(int category);
private: private:
absl::Status DrawPaletteGroup(int category); absl::Status HandleColorPopup(gfx::SnesPalette& palette, int i, int j, int n);
absl::Status HandleColorPopup(gfx::SNESPalette& palette, int i, int j, int n);
void InitializeSavedPalette(const gfx::SNESPalette& palette) { absl::Status InitializeSavedPalette(const gfx::SnesPalette& palette) {
for (int n = 0; n < palette.size(); n++) { for (int n = 0; n < palette.size(); n++) {
saved_palette_[n].x = palette.GetColor(n).GetRGB().x / 255; ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
saved_palette_[n].y = palette.GetColor(n).GetRGB().y / 255; saved_palette_[n].x = color.rgb().x / 255;
saved_palette_[n].z = palette.GetColor(n).GetRGB().z / 255; saved_palette_[n].y = color.rgb().y / 255;
saved_palette_[n].z = color.rgb().z / 255;
saved_palette_[n].w = 255; // Alpha saved_palette_[n].w = 255; // Alpha
} }
return absl::OkStatus();
} }
absl::Status status_; absl::Status status_;
PaletteEditorHistory history_; palette_internal::PaletteEditorHistory history_;
ImVec4 saved_palette_[256] = {}; ImVec4 saved_palette_[256] = {};
ImVec4 current_color_; gfx::SnesColor current_color_;
ImGuiColorEditFlags color_popup_flags = ImGuiColorEditFlags color_popup_flags =
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha; ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha;
ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha;
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoTooltip;
ImGuiColorEditFlags palette_button_flags_2 = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags palette_button_flags_2 = ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip; ImGuiColorEditFlags_NoTooltip;

View File

@@ -6,17 +6,20 @@
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h" #include "absl/status/statusor.h"
#include "app/core/editor.h"
#include "app/editor/modules/palette_editor.h" #include "app/editor/modules/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/gui/pipeline.h" #include "app/gui/pipeline.h"
#include "app/gui/style.h"
#include "app/gui/widgets.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld.h" #include "app/zelda3/overworld/overworld.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
@@ -36,83 +39,94 @@ using ImGui::TableNextRow;
using ImGui::TableSetupColumn; using ImGui::TableSetupColumn;
absl::Status Tile16Editor::Update() { absl::Status Tile16Editor::Update() {
// Create a tab for Tile16 Editing if (rom()->is_loaded() && !map_blockset_loaded_) {
static bool start_task = false; RETURN_IF_ERROR(LoadTile8());
if (ImGui::Button("Test")) { ImVector<std::string> tile16_names;
start_task = true; for (int i = 0; i < 0x200; ++i) {
std::string str = core::UppercaseHexByte(all_tiles_types_[i]);
tile16_names.push_back(str);
}
*tile8_source_canvas_.mutable_labels(0) = tile16_names;
*tile8_source_canvas_.custom_labels_enabled() = true;
} }
if (start_task && !map_blockset_loaded_) { RETURN_IF_ERROR(DrawMenu());
LoadTile8();
}
// Create a tab bar for Tile16 Editing and Tile16 Transfer
if (BeginTabBar("Tile16 Editor Tabs")) { if (BeginTabBar("Tile16 Editor Tabs")) {
if (BeginTabItem("Tile16 Editing")) { RETURN_IF_ERROR(DrawTile16Editor());
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE, RETURN_IF_ERROR(UpdateTile16Transfer());
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(); ImGui::EndTabBar();
} }
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Tile16Editor::DrawMenu() {
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("View")) {
ImGui::Checkbox("Show Collision Types",
tile8_source_canvas_.custom_labels_enabled());
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
return absl::OkStatus();
}
absl::Status Tile16Editor::DrawTile16Editor() {
if (BeginTabItem("Tile16 Editing")) {
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) {
TableSetupColumn("Blockset", ImGuiTableColumnFlags_WidthFixed,
ImGui::GetContentRegionAvail().x);
TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
TableHeadersRow();
TableNextRow();
TableNextColumn();
RETURN_IF_ERROR(UpdateBlockset());
TableNextColumn();
RETURN_IF_ERROR(UpdateTile16Edit());
RETURN_IF_ERROR(DrawTileEditControls());
ImGui::EndTable();
}
ImGui::EndTabItem();
}
return absl::OkStatus();
}
absl::Status Tile16Editor::UpdateBlockset() { absl::Status Tile16Editor::UpdateBlockset() {
gui::BitmapCanvasPipeline(blockset_canvas_, tile16_blockset_bmp_, 0x100, gui::BeginPadding(2);
(8192 * 2), 0x20, map_blockset_loaded_, true, 55); gui::BeginChildWithScrollbar("##Tile16EditorBlocksetScrollRegion");
blockset_canvas_.DrawBackground();
gui::EndPadding();
{
blockset_canvas_.DrawContextMenu();
blockset_canvas_.DrawTileSelector(32);
blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 0, map_blockset_loaded_);
blockset_canvas_.DrawGrid();
blockset_canvas_.DrawOverlay();
ImGui::EndChild();
}
if (!blockset_canvas_.Points().empty()) { if (!blockset_canvas_.points().empty()) {
uint16_t x = blockset_canvas_.Points().front().x / 32; uint16_t x = blockset_canvas_.points().front().x / 32;
uint16_t y = blockset_canvas_.Points().front().y / 32; uint16_t y = blockset_canvas_.points().front().y / 32;
notify_tile16.mutable_get() = x + (y * 8); // notify_tile16.mutable_get() = x + (y * 8);
notify_tile16.mutable_get() = blockset_canvas_.GetTileIdFromMousePos();
notify_tile16.apply_changes(); notify_tile16.apply_changes();
if (notify_tile16.modified()) { if (notify_tile16.modified()) {
current_tile16_ = notify_tile16.get();
current_tile16_bmp_ = tile16_individual_[notify_tile16]; current_tile16_bmp_ = tile16_individual_[notify_tile16];
current_tile16_bmp_.ApplyPalette( auto ow_main_pal_group = rom()->palette_group().overworld_main;
rom()->palette_group("ow_main")[current_palette_]); RETURN_IF_ERROR(current_tile16_bmp_.ApplyPalette(
ow_main_pal_group[current_palette_]));
rom()->RenderBitmap(&current_tile16_bmp_); rom()->RenderBitmap(&current_tile16_bmp_);
} }
} }
@@ -120,58 +134,198 @@ absl::Status Tile16Editor::UpdateBlockset() {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Tile16Editor::UpdateTile16Edit() { absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
if (ImGui::BeginChild("Tile8 Selector", constexpr int tile8_size = 8;
ImVec2(ImGui::GetContentRegionAvail().x, 0x100), constexpr int tile16_size = 16;
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", // Calculate the tile index for x and y based on the click_position
ImVec2(ImGui::GetContentRegionAvail().x, 0x50), true)) { // Adjusting for Tile16 (16x16) which contains 4 Tile8 (8x8)
tile16_edit_canvas_.DrawBackground(ImVec2(0x40, 0x40)); int tile_index_x = static_cast<int>(click_position.x) / tile8_size;
tile16_edit_canvas_.DrawContextMenu(); int tile_index_y = static_cast<int>(click_position.y) / tile8_size;
// if (current_tile8_bmp_.modified()) { std::cout << "Tile Index X: " << tile_index_x << std::endl;
// rom()->UpdateBitmap(&current_tile8_bmp_); std::cout << "Tile Index Y: " << tile_index_y << std::endl;
// 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); // Calculate the pixel start position within the Tile16
tile16_edit_canvas_.DrawOverlay(); ImVec2 start_position;
start_position.x = ((tile_index_x) / 4) * 0x40;
start_position.y = ((tile_index_y) / 4) * 0x40;
std::cout << "Start Position X: " << start_position.x << std::endl;
std::cout << "Start Position Y: " << start_position.y << std::endl;
// Draw the Tile8 to the correct position within the Tile16
for (int y = 0; y < tile8_size; ++y) {
for (int x = 0; x < tile8_size; ++x) {
int pixel_index =
(start_position.y + y) * tile16_size + ((start_position.x) + x);
int gfx_pixel_index = y * tile8_size + x;
current_tile16_bmp_.WriteToPixel(
pixel_index,
current_gfx_individual_[current_tile8_].data()[gfx_pixel_index]);
}
} }
ImGui::EndChild();
DrawTileEditControls();
return absl::OkStatus(); return absl::OkStatus();
} }
void Tile16Editor::DrawTileEditControls() { absl::Status Tile16Editor::UpdateTile16Edit() {
auto ow_main_pal_group = rom()->palette_group().overworld_main;
if (ImGui::BeginChild("Tile8 Selector",
ImVec2(ImGui::GetContentRegionAvail().x, 0x175),
true)) {
tile8_source_canvas_.DrawBackground();
tile8_source_canvas_.DrawContextMenu(&current_gfx_bmp_);
if (tile8_source_canvas_.DrawTileSelector(32)) {
RETURN_IF_ERROR(
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
ow_main_pal_group[0], current_palette_));
rom()->UpdateBitmap(&current_gfx_individual_[current_tile8_]);
}
tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 4.0f);
tile8_source_canvas_.DrawGrid();
tile8_source_canvas_.DrawOverlay();
}
ImGui::EndChild();
// The user selected a tile8
if (!tile8_source_canvas_.points().empty()) {
uint16_t x = tile8_source_canvas_.points().front().x / 16;
uint16_t y = tile8_source_canvas_.points().front().y / 16;
current_tile8_ = x + (y * 8);
RETURN_IF_ERROR(
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
ow_main_pal_group[0], current_palette_));
rom()->UpdateBitmap(&current_gfx_individual_[current_tile8_]);
}
if (ImGui::BeginChild("Tile16 Editor Options",
ImVec2(ImGui::GetContentRegionAvail().x, 0x50), true)) {
tile16_edit_canvas_.DrawBackground();
tile16_edit_canvas_.DrawContextMenu(&current_tile16_bmp_);
tile16_edit_canvas_.DrawBitmap(current_tile16_bmp_, 0, 0, 4.0f);
if (!tile8_source_canvas_.points().empty()) {
if (tile16_edit_canvas_.DrawTilePainter(
current_gfx_individual_[current_tile8_], 16, 2.0f)) {
RETURN_IF_ERROR(
DrawToCurrentTile16(tile16_edit_canvas_.drawn_tile_position()));
rom()->UpdateBitmap(&current_tile16_bmp_);
}
}
tile16_edit_canvas_.DrawGrid();
tile16_edit_canvas_.DrawOverlay();
}
ImGui::EndChild();
return absl::OkStatus();
}
absl::Status Tile16Editor::DrawTileEditControls() {
ImGui::Separator(); ImGui::Separator();
ImGui::Text("Tile16 ID: %d", current_tile16_);
ImGui::Text("Tile8 ID: %d", current_tile8_);
ImGui::Text("Options:"); ImGui::Text("Options:");
gui::InputHexByte("Palette", &notify_palette.mutable_get()); gui::InputHexByte("Palette", &notify_palette.mutable_get());
notify_palette.apply_changes(); notify_palette.apply_changes();
if (notify_palette.modified()) { if (notify_palette.modified()) {
current_gfx_bmp_.ApplyPalette( auto palette = palettesets_[current_palette_].main;
rom()->palette_group("ow_main")[notify_palette.get()]); auto value = notify_palette.get();
current_tile16_bmp_.ApplyPalette( if (notify_palette.get() > 0x04 && notify_palette.get() < 0x06) {
rom()->palette_group("ow_main")[notify_palette.get()]); palette = palettesets_[current_palette_].aux1;
rom()->UpdateBitmap(&current_gfx_bmp_); value -= 0x04;
} else if (notify_palette.get() > 0x06) {
palette = palettesets_[current_palette_].aux2;
value -= 0x06;
}
if (value > 0x00) {
RETURN_IF_ERROR(
current_gfx_bmp_.ApplyPaletteWithTransparent(palette, value));
RETURN_IF_ERROR(
current_tile16_bmp_.ApplyPaletteWithTransparent(palette, value));
rom()->UpdateBitmap(&current_gfx_bmp_);
rom()->UpdateBitmap(&current_tile16_bmp_);
}
} }
ImGui::Checkbox("X Flip", &x_flip); ImGui::Checkbox("X Flip", &x_flip);
ImGui::Checkbox("Y Flip", &y_flip); ImGui::Checkbox("Y Flip", &y_flip);
ImGui::Checkbox("Priority Tile", &priority_tile); ImGui::Checkbox("Priority Tile", &priority_tile);
return absl::OkStatus();
}
absl::Status Tile16Editor::LoadTile8() {
auto ow_main_pal_group = rom()->palette_group().overworld_main;
current_gfx_individual_.reserve(1024);
for (int index = 0; index < 1024; index++) {
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++) {
// Current Gfx Data is 16 sheets of 8x8 tiles ordered 16 wide by 4 tall
// Calculate the position in the tile data vector
int position = tx + (ty * 0x08);
// Calculate the position in the current gfx data
int num_columns = current_gfx_bmp_.width() / 8;
int num_rows = current_gfx_bmp_.height() / 8;
int x = (index % num_columns) * 8 + tx;
int y = (index / num_columns) * 8 + ty;
int gfx_position = x + (y * 0x100);
// Get the pixel value from the current gfx data
uint8_t value = current_gfx_bmp_.data()[gfx_position];
if (value & 0x80) {
value -= 0x88;
}
tile_data[position] = value;
}
}
current_gfx_individual_.emplace_back();
current_gfx_individual_[index].Create(0x08, 0x08, 0x08, tile_data);
RETURN_IF_ERROR(current_gfx_individual_[index].ApplyPaletteWithTransparent(
ow_main_pal_group[0], current_palette_));
rom()->RenderBitmap(&current_gfx_individual_[index]);
}
map_blockset_loaded_ = true;
return absl::OkStatus();
}
// ============================================================================
// Tile16 Transfer
absl::Status Tile16Editor::UpdateTile16Transfer() {
if (BeginTabItem("Tile16 Transfer")) {
if (BeginTable("#Tile16TransferTable", 2, TABLE_BORDERS_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();
}
return absl::OkStatus();
} }
absl::Status Tile16Editor::UpdateTransferTileCanvas() { absl::Status Tile16Editor::UpdateTransferTileCanvas() {
@@ -188,19 +342,20 @@ absl::Status Tile16Editor::UpdateTransferTileCanvas() {
transfer_started_ = true; transfer_started_ = true;
}); });
// TODO: Implement tile16 transfer
if (transfer_started_ && !transfer_blockset_loaded_) { if (transfer_started_ && !transfer_blockset_loaded_) {
PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData()) PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData())
graphics_bin_ = transfer_rom_.graphics_bin(); graphics_bin_ = transfer_rom_.graphics_bin();
// Load the Link to the Past overworld. // Load the Link to the Past overworld.
PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_)) PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_))
transfer_overworld_.SetCurrentMap(0); transfer_overworld_.set_current_map(0);
palette_ = transfer_overworld_.AreaPalette(); palette_ = transfer_overworld_.AreaPalette();
// Create the tile16 blockset image // Create the tile16 blockset image
gui::BuildAndRenderBitmapPipeline(0x80, 0x2000, 0x80, RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(0x80, 0x2000, 0x80,
transfer_overworld_.Tile16Blockset(), transfer_overworld_.Tile16Blockset(),
*rom(), transfer_blockset_bmp_, palette_); transfer_blockset_bmp_, palette_));
transfer_blockset_loaded_ = true; transfer_blockset_loaded_ = true;
} }
@@ -212,87 +367,6 @@ absl::Status Tile16Editor::UpdateTransferTileCanvas() {
return absl::OkStatus(); 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(&current_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(&current_gfx_individual_[i]);
// }
return absl::OkStatus();
}
} // namespace editor } // namespace editor
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -7,38 +7,66 @@
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h" #include "absl/status/statusor.h"
#include "app/core/editor.h" #include "app/editor/context/gfx_context.h"
#include "app/gui/pipeline.h"
#include "app/editor/modules/palette_editor.h" #include "app/editor/modules/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/pipeline.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld.h" #include "app/zelda3/overworld/overworld.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
class Tile16Editor : public SharedROM { /**
* @brief Popup window to edit Tile16 data
*/
class Tile16Editor : public context::GfxContext, public SharedRom {
public: public:
absl::Status Update(); absl::Status Update();
absl::Status DrawMenu();
absl::Status DrawTile16Editor();
absl::Status UpdateTile16Transfer();
absl::Status UpdateBlockset(); absl::Status UpdateBlockset();
absl::Status DrawToCurrentTile16(ImVec2 pos);
absl::Status UpdateTile16Edit(); absl::Status UpdateTile16Edit();
void DrawTileEditControls(); absl::Status DrawTileEditControls();
absl::Status UpdateTransferTileCanvas(); absl::Status UpdateTransferTileCanvas();
absl::Status InitBlockset(const gfx::Bitmap& tile16_blockset_bmp, void InitBlockset(const gfx::Bitmap& tile16_blockset_bmp,
gfx::Bitmap current_gfx_bmp, gfx::Bitmap current_gfx_bmp,
const std::vector<gfx::Bitmap>& tile16_individual); const std::vector<gfx::Bitmap>& tile16_individual,
uint8_t all_tiles_types[0x200]) {
all_tiles_types_ = all_tiles_types;
tile16_blockset_bmp_ = tile16_blockset_bmp;
tile16_individual_ = tile16_individual;
current_gfx_bmp_ = current_gfx_bmp;
tile8_gfx_data_ = current_gfx_bmp_.vector();
}
absl::Status LoadTile8(); absl::Status LoadTile8();
absl::Status set_tile16(int id) {
current_tile16_ = id;
current_tile16_bmp_ = tile16_individual_[id];
auto ow_main_pal_group = rom()->palette_group().overworld_main;
RETURN_IF_ERROR(
current_tile16_bmp_.ApplyPalette(ow_main_pal_group[current_palette_]));
rom()->RenderBitmap(&current_tile16_bmp_);
return absl::OkStatus();
}
private: private:
bool map_blockset_loaded_ = false; bool map_blockset_loaded_ = false;
bool transfer_started_ = false; bool transfer_started_ = false;
@@ -48,7 +76,7 @@ class Tile16Editor : public SharedROM {
int current_tile8_ = 0; int current_tile8_ = 0;
uint8_t current_palette_ = 0; uint8_t current_palette_ = 0;
core::NotifyValue<uint8_t> notify_tile16; core::NotifyValue<uint32_t> notify_tile16;
core::NotifyValue<uint8_t> notify_palette; core::NotifyValue<uint8_t> notify_palette;
// Canvas dimensions // Canvas dimensions
@@ -64,18 +92,25 @@ class Tile16Editor : public SharedROM {
bool priority_tile; bool priority_tile;
int tile_size; int tile_size;
uint8_t* all_tiles_types_;
// Tile16 blockset for selecting the tile to edit // Tile16 blockset for selecting the tile to edit
gui::Canvas blockset_canvas_; gui::Canvas blockset_canvas_{ImVec2(0x100, 0x4000),
gui::CanvasGridSize::k32x32};
gfx::Bitmap tile16_blockset_bmp_; gfx::Bitmap tile16_blockset_bmp_;
// Canvas for editing the selected tile // Canvas for editing the selected tile
gui::Canvas tile16_edit_canvas_; gui::Canvas tile16_edit_canvas_{ImVec2(0x40, 0x40),
gui::CanvasGridSize::k64x64};
gfx::Bitmap current_tile16_bmp_; gfx::Bitmap current_tile16_bmp_;
gfx::Bitmap current_tile8_bmp_; gfx::Bitmap current_tile8_bmp_;
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_ // Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
gui::Canvas tile8_source_canvas_; gui::Canvas tile8_source_canvas_{
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k32x32};
gfx::Bitmap current_gfx_bmp_; gfx::Bitmap current_gfx_bmp_;
std::vector<gfx::Tilesheet> current_tilesheets_;
gui::Canvas transfer_canvas_; gui::Canvas transfer_canvas_;
gfx::Bitmap transfer_blockset_bmp_; gfx::Bitmap transfer_blockset_bmp_;
@@ -88,17 +123,17 @@ class Tile16Editor : public SharedROM {
std::vector<uint8_t> current_tile16_data_; std::vector<uint8_t> current_tile16_data_;
std::vector<uint8_t> tile8_gfx_data_;
PaletteEditor palette_editor_; PaletteEditor palette_editor_;
gfx::SNESPalette palette_; gfx::SnesPalette palette_;
zelda3::Overworld transfer_overworld_; zelda3::overworld::Overworld transfer_overworld_;
gfx::BitmapTable graphics_bin_; gfx::BitmapTable graphics_bin_;
ROM transfer_rom_; Rom transfer_rom_;
absl::Status transfer_status_; absl::Status transfer_status_;
core::TaskManager<std::function<void(int)>> task_manager_;
}; };
} // namespace editor } // namespace editor

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H #define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
#include <imgui/imgui.h> #include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include <cmath> #include <cmath>
#include <unordered_map> #include <unordered_map>
@@ -11,10 +12,12 @@
#include "absl/status/statusor.h" #include "absl/status/statusor.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/core/common.h" #include "app/core/common.h"
#include "app/core/editor.h" #include "app/editor/context/entrance_context.h"
#include "app/editor/context/gfx_context.h"
#include "app/editor/modules/gfx_group_editor.h" #include "app/editor/modules/gfx_group_editor.h"
#include "app/editor/modules/palette_editor.h" #include "app/editor/modules/palette_editor.h"
#include "app/editor/modules/tile16_editor.h" #include "app/editor/modules/tile16_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
@@ -22,7 +25,7 @@
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/pipeline.h" #include "app/gui/pipeline.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld.h" #include "app/zelda3/overworld/overworld.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
@@ -36,12 +39,14 @@ static constexpr uint kTile8DisplayHeight = 64;
static constexpr float kInputFieldSize = 30.f; static constexpr float kInputFieldSize = 30.f;
static constexpr absl::string_view kToolsetColumnNames[] = { static constexpr absl::string_view kToolsetColumnNames[] = {
"#undoTool", "#redoTool", "#drawTool", "#separator2", "#undoTool", "#redoTool", "#separator2", "#zoomOutTool",
"#zoomOutTool", "#zoomInTool", "#separator", "#history", "#zoomInTool", "#separator", "#drawTool", "#history",
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool", "#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
"#transportTool", "#musicTool", "#separator3", "#tilemapTool"}; "#transportTool", "#musicTool", "#separator3", "#tilemapTool",
"propertiesTool"};
constexpr ImGuiTableFlags kOWMapFlags = ImGuiTableFlags_Borders; constexpr ImGuiTableFlags kOWMapFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
constexpr ImGuiTableFlags kToolsetTableFlags = ImGuiTableFlags_SizingFixedFit; constexpr ImGuiTableFlags kToolsetTableFlags = ImGuiTableFlags_SizingFixedFit;
constexpr ImGuiTableFlags kOWEditFlags = constexpr ImGuiTableFlags kOWEditFlags =
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
@@ -57,8 +62,26 @@ constexpr absl::string_view kTileSelectorTab = "##TileSelectorTabBar";
constexpr absl::string_view kOWEditTable = "##OWEditTable"; constexpr absl::string_view kOWEditTable = "##OWEditTable";
constexpr absl::string_view kOWMapTable = "#MapSettingsTable"; constexpr absl::string_view kOWMapTable = "#MapSettingsTable";
/**
* @class OverworldEditor
* @brief Manipulates the Overworld and OverworldMap data in a Rom.
*
* The `OverworldEditor` class is responsible for managing the editing and
* manipulation of the overworld in a game. The user can drag and drop tiles,
* modify OverworldEntrance, OverworldExit, Sprite, and OverworldItem
* as well as change the gfx and palettes used in each overworld map.
*
* The Overworld itself is a series of bitmap images which exist inside each
* OverworldMap object. The drawing of the overworld is done using the Canvas
* class in conjunction with these underlying Bitmap objects.
*
* Provides access to the GfxGroupEditor and Tile16Editor through popup windows.
*
*/
class OverworldEditor : public Editor, class OverworldEditor : public Editor,
public SharedROM, public SharedRom,
public context::GfxContext,
public context::EntranceContext,
public core::ExperimentFlags { public core::ExperimentFlags {
public: public:
absl::Status Update() final; absl::Status Update() final;
@@ -70,47 +93,93 @@ class OverworldEditor : public Editor,
auto overworld() { return &overworld_; } auto overworld() { return &overworld_; }
/**
* @brief
*/
int jump_to_tab() { return jump_to_tab_; }
int jump_to_tab_ = -1;
void Shutdown() { void Shutdown() {
for (auto &bmp : tile16_individual_) { for (auto& bmp : tile16_individual_) {
bmp.Cleanup(); bmp.Cleanup();
} }
for (auto &[i, bmp] : maps_bmp_) { for (auto& [i, bmp] : maps_bmp_) {
bmp.Cleanup(); bmp.Cleanup();
} }
for (auto &[i, bmp] : graphics_bin_) { for (auto& [i, bmp] : graphics_bin_) {
bmp.Cleanup(); bmp.Cleanup();
} }
for (auto &[i, bmp] : current_graphics_set_) { for (auto& [i, bmp] : current_graphics_set_) {
bmp.Cleanup(); bmp.Cleanup();
} }
maps_bmp_.clear();
overworld_.Destroy();
all_gfx_loaded_ = false;
map_blockset_loaded_ = false;
} }
/**
* @brief Load the Bitmap objects for each OverworldMap.
*
* Calls the Overworld class to load the image data and palettes from the Rom,
* then renders the area graphics and tile16 blockset Bitmap objects before
* assembling the OverworldMap Bitmap objects.
*/
absl::Status LoadGraphics(); absl::Status LoadGraphics();
private: private:
absl::Status UpdateOverworldEdit();
absl::Status UpdateFullscreenCanvas();
absl::Status DrawToolset(); absl::Status DrawToolset();
void DrawOverworldMapSettings(); void DrawOverworldMapSettings();
void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling); void RefreshChildMap(int i);
void DrawOverworldMaps(); void RefreshOverworldMap();
absl::Status RefreshMapPalette();
void RefreshMapProperties();
absl::Status RefreshTile16Blockset();
void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling,
bool holes = false);
void DrawOverworldExits(ImVec2 zero, ImVec2 scrolling);
void DrawOverworldItems();
void DrawOverworldSprites(); void DrawOverworldSprites();
void DrawOverworldMaps();
void DrawOverworldEdits(); void DrawOverworldEdits();
void RenderUpdatedMapBitmap(const ImVec2 &click_position, void RenderUpdatedMapBitmap(const ImVec2& click_position,
const Bytes &tile_data); const Bytes& tile_data);
void SaveOverworldChanges();
void DetermineActiveMap(const ImVec2 &mouse_position);
void CheckForOverworldEdits(); void CheckForOverworldEdits();
void CheckForCurrentMap(); void CheckForSelectRectangle();
absl::Status CheckForCurrentMap();
void CheckForMousePan();
/**
* @brief Allows the user to make changes to the overworld map.
*/
void DrawOverworldCanvas(); void DrawOverworldCanvas();
absl::Status DrawTile16Selector();
void DrawTile8Selector(); void DrawTile8Selector();
void DrawTileSelector(); absl::Status DrawAreaGraphics();
absl::Status DrawTileSelector();
absl::Status LoadSpriteGraphics(); absl::Status LoadSpriteGraphics();
void DrawOverworldProperties();
absl::Status DrawExperimentalModal(); absl::Status DrawExperimentalModal();
absl::Status UpdateUsageStats();
void DrawUsageGrid();
void CalculateUsageStats();
absl::Status LoadAnimatedMaps();
void DrawDebugWindow();
auto gfx_group_editor() const { return gfx_group_editor_; }
enum class EditingMode { enum class EditingMode {
DRAW_TILE, DRAW_TILE,
ENTRANCES, ENTRANCES,
@@ -118,16 +187,25 @@ class OverworldEditor : public Editor,
ITEMS, ITEMS,
SPRITES, SPRITES,
TRANSPORTS, TRANSPORTS,
MUSIC MUSIC,
PAN
}; };
EditingMode current_mode = EditingMode::DRAW_TILE; EditingMode current_mode = EditingMode::DRAW_TILE;
EditingMode previous_mode = EditingMode::DRAW_TILE;
int current_world_ = 0; int current_world_ = 0;
int current_map_ = 0; int current_map_ = 0;
int current_parent_ = 0;
int game_state_ = 1;
int current_tile16_ = 0; int current_tile16_ = 0;
int selected_tile_ = 0; int selected_tile_ = 0;
int game_state_ = 0;
int current_blockset_ = 0;
int selected_entrance_ = 0;
int selected_usage_map_ = 0xFFFF;
char map_gfx_[3] = ""; char map_gfx_[3] = "";
char map_palette_[3] = ""; char map_palette_[3] = "";
char spr_gfx_[3] = ""; char spr_gfx_[3] = "";
@@ -149,10 +227,21 @@ class OverworldEditor : public Editor,
bool is_dragging_entrance_ = false; bool is_dragging_entrance_ = false;
bool show_tile16_editor_ = false; bool show_tile16_editor_ = false;
bool show_gfx_group_editor_ = false; bool show_gfx_group_editor_ = false;
bool overworld_canvas_fullscreen_ = false;
bool middle_mouse_dragging_ = false;
bool IsMouseHoveringOverEntrance(const zelda3::OverworldEntrance &entrance, bool is_dragging_entity_ = false;
ImVec2 canvas_p, ImVec2 scrolling); zelda3::OverworldEntity* dragged_entity_;
zelda3::OverworldEntrance *dragged_entrance_; zelda3::OverworldEntity* current_entity_;
int current_entrance_id_ = 0;
zelda3::overworld::OverworldEntrance current_entrance_;
int current_exit_id_ = 0;
zelda3::overworld::OverworldExit current_exit_;
int current_item_id_ = 0;
zelda3::overworld::OverworldItem current_item_;
int current_sprite_id_ = 0;
zelda3::Sprite current_sprite_;
bool show_experimental = false; bool show_experimental = false;
std::string ow_tilemap_filename_ = ""; std::string ow_tilemap_filename_ = "";
@@ -168,14 +257,20 @@ class OverworldEditor : public Editor,
Tile16Editor tile16_editor_; Tile16Editor tile16_editor_;
GfxGroupEditor gfx_group_editor_; GfxGroupEditor gfx_group_editor_;
PaletteEditor palette_editor_; PaletteEditor palette_editor_;
zelda3::Overworld overworld_; zelda3::overworld::Overworld overworld_;
gui::Canvas ow_map_canvas_; gui::Canvas ow_map_canvas_{ImVec2(0x200 * 8, 0x200 * 8),
gui::Canvas current_gfx_canvas_; gui::CanvasGridSize::k64x64};
gui::Canvas blockset_canvas_; gui::Canvas current_gfx_canvas_{ImVec2(0x100 + 1, 0x10 * 0x40 + 1),
gui::Canvas graphics_bin_canvas_; gui::CanvasGridSize::k32x32};
gui::Canvas blockset_canvas_{ImVec2(0x100 + 1, 0x2000 + 1),
gui::CanvasGridSize::k32x32};
gui::Canvas graphics_bin_canvas_{
ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1),
gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_;
gfx::SNESPalette palette_; gfx::SnesPalette palette_;
gfx::Bitmap selected_tile_bmp_; gfx::Bitmap selected_tile_bmp_;
gfx::Bitmap tile16_blockset_bmp_; gfx::Bitmap tile16_blockset_bmp_;
gfx::Bitmap current_gfx_bmp_; gfx::Bitmap current_gfx_bmp_;
@@ -186,6 +281,8 @@ class OverworldEditor : public Editor,
gfx::BitmapTable current_graphics_set_; gfx::BitmapTable current_graphics_set_;
gfx::BitmapTable sprite_previews_; gfx::BitmapTable sprite_previews_;
gfx::BitmapTable animated_maps_;
absl::Status status_; absl::Status status_;
}; };
} // namespace editor } // namespace editor

View File

@@ -15,9 +15,11 @@
#include "app/core/constants.h" #include "app/core/constants.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/zelda3/dungeon/room.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
@@ -27,11 +29,15 @@ ScreenEditor::ScreenEditor() { screen_canvas_.SetCanvasSize(ImVec2(512, 512)); }
void ScreenEditor::Update() { void ScreenEditor::Update() {
TAB_BAR("##TabBar") TAB_BAR("##TabBar")
TAB_ITEM("Dungeon Maps")
if (rom()->is_loaded()) {
DrawDungeonMapsEditor();
}
END_TAB_ITEM()
DrawInventoryMenuEditor(); DrawInventoryMenuEditor();
DrawOverworldMapEditor();
DrawTitleScreenEditor(); DrawTitleScreenEditor();
DrawNamingScreenEditor(); DrawNamingScreenEditor();
DrawOverworldMapEditor();
DrawDungeonMapsEditor();
END_TAB_BAR() END_TAB_BAR()
} }
@@ -39,7 +45,7 @@ void ScreenEditor::DrawInventoryMenuEditor() {
TAB_ITEM("Inventory Menu") TAB_ITEM("Inventory Menu")
static bool create = false; static bool create = false;
if (!create && rom()->isLoaded()) { if (!create && rom()->is_loaded()) {
inventory_.Create(); inventory_.Create();
palette_ = inventory_.Palette(); palette_ = inventory_.Palette();
create = true; create = true;
@@ -76,43 +82,6 @@ void ScreenEditor::DrawInventoryMenuEditor() {
END_TAB_ITEM() END_TAB_ITEM()
} }
void ScreenEditor::DrawTitleScreenEditor() {
TAB_ITEM("Title Screen")
END_TAB_ITEM()
}
void ScreenEditor::DrawNamingScreenEditor() {
TAB_ITEM("Naming Screen")
END_TAB_ITEM()
}
void ScreenEditor::DrawOverworldMapEditor() {
TAB_ITEM("Overworld Map")
END_TAB_ITEM()
}
void ScreenEditor::DrawDungeonMapsEditor() {
TAB_ITEM("Dungeon Maps")
END_TAB_ITEM()
}
void ScreenEditor::DrawToolset() {
static bool show_bg1 = true;
static bool show_bg2 = true;
static bool show_bg3 = true;
static bool drawing_bg1 = true;
static bool drawing_bg2 = false;
static bool drawing_bg3 = false;
ImGui::Checkbox("Show BG1", &show_bg1);
ImGui::SameLine();
ImGui::Checkbox("Show BG2", &show_bg2);
ImGui::Checkbox("Draw BG1", &drawing_bg1);
ImGui::SameLine();
ImGui::Checkbox("Draw BG2", &drawing_bg2);
ImGui::SameLine();
ImGui::Checkbox("Draw BG3", &drawing_bg3);
}
void ScreenEditor::DrawInventoryToolset() { void ScreenEditor::DrawInventoryToolset() {
if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit, if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
ImVec2(0, 0))) { ImVec2(0, 0))) {
@@ -138,6 +107,334 @@ void ScreenEditor::DrawInventoryToolset() {
} }
} }
absl::Status ScreenEditor::LoadDungeonMaps() {
std::vector<std::array<uint8_t, 25>> current_floor_rooms_d;
std::vector<std::array<uint8_t, 25>> current_floor_gfx_d;
int total_floors_d;
uint8_t nbr_floor_d;
uint8_t nbr_basement_d;
for (int d = 0; d < 14; d++) {
current_floor_rooms_d.clear();
current_floor_gfx_d.clear();
ASSIGN_OR_RETURN(
int ptr,
rom()->ReadWord(zelda3::screen::kDungeonMapRoomsPtr + (d * 2)));
ASSIGN_OR_RETURN(
int ptrGFX,
rom()->ReadWord(zelda3::screen::kDungeonMapRoomsPtr + (d * 2)));
ptr |= 0x0A0000; // Add bank to the short ptr
ptrGFX |= 0x0A0000; // Add bank to the short ptr
int pcPtr = core::SnesToPc(ptr); // Contains data for the next 25 rooms
int pcPtrGFX =
core::SnesToPc(ptrGFX); // Contains data for the next 25 rooms
ASSIGN_OR_RETURN(
ushort bossRoomD,
rom()->ReadWord(zelda3::screen::kDungeonMapBossRooms + (d * 2)));
ASSIGN_OR_RETURN(
nbr_basement_d,
rom()->ReadByte(zelda3::screen::kDungeonMapFloors + (d * 2)));
nbr_basement_d &= 0x0F;
ASSIGN_OR_RETURN(
nbr_floor_d,
rom()->ReadByte(zelda3::screen::kDungeonMapFloors + (d * 2)));
nbr_floor_d &= 0xF0;
nbr_floor_d = nbr_floor_d >> 4;
total_floors_d = nbr_basement_d + nbr_floor_d;
dungeon_map_labels_.emplace_back();
// for each floor in the dungeon
for (int i = 0; i < total_floors_d; i++) {
dungeon_map_labels_[d].emplace_back();
std::array<uint8_t, 25> rdata;
std::array<uint8_t, 25> gdata;
// for each room on the floor
for (int j = 0; j < 25; j++) {
// rdata[j] = 0x0F;
gdata[j] = 0xFF;
rdata[j] = rom()->data()[pcPtr + j + (i * 25)]; // Set the rooms
if (rdata[j] == 0x0F) {
gdata[j] = 0xFF;
} else {
gdata[j] = rom()->data()[pcPtrGFX++];
}
std::string label = core::UppercaseHexByte(rdata[j]);
dungeon_map_labels_[d][i][j] = label;
}
current_floor_gfx_d.push_back(gdata); // Add new floor gfx data
current_floor_rooms_d.push_back(rdata); // Add new floor data
}
dungeon_maps_.emplace_back(bossRoomD, nbr_floor_d, nbr_basement_d,
current_floor_rooms_d, current_floor_gfx_d);
}
return absl::OkStatus();
}
absl::Status ScreenEditor::SaveDungeonMaps() {
for (int d = 0; d < 14; d++) {
int ptr = zelda3::screen::kDungeonMapRoomsPtr + (d * 2);
int ptrGFX = zelda3::screen::kDungeonMapGfxPtr + (d * 2);
int pcPtr = core::SnesToPc(ptr);
int pcPtrGFX = core::SnesToPc(ptrGFX);
const int nbr_floors = dungeon_maps_[d].nbr_of_floor;
const int nbr_basements = dungeon_maps_[d].nbr_of_basement;
for (int i = 0; i < nbr_floors + nbr_basements; i++) {
for (int j = 0; j < 25; j++) {
// rom()->data()[pcPtr + j + (i * 25)] =
// dungeon_maps_[d].floor_rooms[i][j];
// rom()->data()[pcPtrGFX++] = dungeon_maps_[d].floor_gfx[i][j];
RETURN_IF_ERROR(rom()->WriteByte(ptr + j + (i * 25),
dungeon_maps_[d].floor_rooms[i][j]));
RETURN_IF_ERROR(rom()->WriteByte(ptrGFX + j + (i * 25),
dungeon_maps_[d].floor_gfx[i][j]));
pcPtrGFX++;
}
}
}
return absl::OkStatus();
}
absl::Status ScreenEditor::LoadDungeonMapTile16() {
tile16_sheet_.Init(256, 192, gfx::TileType::Tile16);
for (int i = 0; i < 186; i++) {
int addr = zelda3::screen::kDungeonMapTile16;
if (rom()->data()[zelda3::screen::kDungeonMapExpCheck] != 0xB9) {
addr = zelda3::screen::kDungeonMapTile16Expanded;
}
ASSIGN_OR_RETURN(auto tl, rom()->ReadWord(addr + (i * 8)));
gfx::TileInfo t1 = gfx::WordToTileInfo(tl); // Top left
ASSIGN_OR_RETURN(auto tr, rom()->ReadWord(addr + 2 + (i * 8)));
gfx::TileInfo t2 = gfx::WordToTileInfo(tr); // Top right
ASSIGN_OR_RETURN(auto bl, rom()->ReadWord(addr + 4 + (i * 8)));
gfx::TileInfo t3 = gfx::WordToTileInfo(bl); // Bottom left
ASSIGN_OR_RETURN(auto br, rom()->ReadWord(addr + 6 + (i * 8)));
gfx::TileInfo t4 = gfx::WordToTileInfo(br); // Bottom right
tile16_sheet_.ComposeTile16(rom()->graphics_buffer(), t1, t2, t3, t4);
}
tile16_sheet_.mutable_bitmap()->ApplyPalette(
*rom()->mutable_dungeon_palette(3));
rom()->RenderBitmap(&*tile16_sheet_.mutable_bitmap().get());
for (int i = 0; i < tile16_sheet_.num_tiles(); ++i) {
if (tile16_individual_.count(i) == 0) {
auto tile = tile16_sheet_.GetTile16(i);
tile16_individual_[i] = tile;
rom()->RenderBitmap(&tile16_individual_[i]);
}
}
return absl::OkStatus();
}
void ScreenEditor::DrawDungeonMapsTabs() {
auto current_dungeon = dungeon_maps_[selected_dungeon];
if (ImGui::BeginTabBar("##DungeonMapTabs")) {
auto nbr_floors =
current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
for (int i = 0; i < nbr_floors; i++) {
std::string tab_name = absl::StrFormat("Floor %d", i + 1);
if (i >= current_dungeon.nbr_of_floor) {
tab_name = absl::StrFormat("Basement %d",
i - current_dungeon.nbr_of_floor + 1);
}
if (ImGui::BeginTabItem(tab_name.c_str())) {
floor_number = i;
// screen_canvas_.LoadCustomLabels(dungeon_map_labels_[selected_dungeon]);
// screen_canvas_.set_current_labels(floor_number);
screen_canvas_.DrawBackground(ImVec2(325, 325));
screen_canvas_.DrawTileSelector(64.f);
auto boss_room = current_dungeon.boss_room;
for (int j = 0; j < 25; j++) {
if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
int tile16_id = current_dungeon.floor_rooms[floor_number][j];
int tile_x = (tile16_id % 16) * 16;
int tile_y = (tile16_id / 16) * 16;
int posX = ((j % 5) * 32);
int posY = ((j / 5) * 32);
if (tile16_individual_.count(tile16_id) == 0) {
auto tile = tile16_sheet_.GetTile16(tile16_id);
std::cout << "Tile16: " << tile16_id << std::endl;
rom()->RenderBitmap(&tile);
tile16_individual_[tile16_id] = tile;
}
screen_canvas_.DrawBitmap(tile16_individual_[tile16_id], (posX * 2),
(posY * 2), 4.0f);
if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64,
64, core::kRedPen);
}
std::string label =
dungeon_map_labels_[selected_dungeon][floor_number][j];
screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
// GFX.drawText(
// e.Graphics, 16 + ((i % 5) * 32), 20 + ((i / 5) * 32),
}
// if (dungmapSelectedTile == i)
// Constants.AzurePen2,
// 10 + ((i % 5) * 32), 12 + ((i / 5) * 32), 32, 32));
}
screen_canvas_.DrawGrid(64.f, 5);
screen_canvas_.DrawOverlay();
if (!screen_canvas_.points().empty()) {
int x = screen_canvas_.points().front().x / 64;
int y = screen_canvas_.points().front().y / 64;
selected_room = x + (y * 5);
}
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
}
gui::InputHexByte(
"Selected Room",
&current_dungeon.floor_rooms[floor_number].at(selected_room));
gui::InputHexWord("Boss Room", &current_dungeon.boss_room);
if (ImGui::Button("Copy Floor", ImVec2(100, 0))) {
copy_button_pressed = true;
}
ImGui::SameLine();
if (ImGui::Button("Paste Floor", ImVec2(100, 0))) {
paste_button_pressed = true;
}
}
void ScreenEditor::DrawDungeonMapsEditor() {
if (!dungeon_maps_loaded_) {
if (LoadDungeonMaps().ok()) {
if (LoadDungeonMapTile16().ok()) {
auto bitmap_manager = rom()->mutable_bitmap_manager();
sheets_.emplace(0, *bitmap_manager->mutable_bitmap(212));
sheets_.emplace(1, *bitmap_manager->mutable_bitmap(213));
sheets_.emplace(2, *bitmap_manager->mutable_bitmap(214));
sheets_.emplace(3, *bitmap_manager->mutable_bitmap(215));
dungeon_maps_loaded_ = true;
} else {
ImGui::Text("Failed to load dungeon map tile16");
}
} else {
ImGui::Text("Failed to load dungeon maps");
}
}
static std::vector<std::string> dungeon_names = {
"Sewers/Sanctuary", "Hyrule Castle", "Eastern Palace",
"Desert Palace", "Tower of Hera", "Agahnim's Tower",
"Palace of Darkness", "Swamp Palace", "Skull Woods",
"Thieves' Town", "Ice Palace", "Misery Mire",
"Turtle Rock", "Ganon's Tower"};
if (ImGui::BeginTable("DungeonMapsTable", 4, ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("Dungeon");
ImGui::TableSetupColumn("Map");
ImGui::TableSetupColumn("Rooms Gfx");
ImGui::TableSetupColumn("Tiles Gfx");
ImGui::TableHeadersRow();
// Dungeon column
ImGui::TableNextColumn();
for (int i = 0; i < dungeon_names.size(); i++) {
rom()->resource_label()->SelectableLabelWithNameEdit(
selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
dungeon_names[i]);
}
// Map column
ImGui::TableNextColumn();
DrawDungeonMapsTabs();
ImGui::TableNextColumn();
if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
tilesheet_canvas_.DrawBackground(ImVec2((256 * 2) + 2, (192 * 2) + 4));
tilesheet_canvas_.DrawContextMenu();
tilesheet_canvas_.DrawTileSelector(32.f);
tilesheet_canvas_.DrawBitmap(*tile16_sheet_.bitmap(), 2, true);
tilesheet_canvas_.DrawGrid(32.f);
tilesheet_canvas_.DrawOverlay();
if (!tilesheet_canvas_.points().empty()) {
selected_tile16_ = tilesheet_canvas_.points().front().x / 32 +
(tilesheet_canvas_.points().front().y / 32) * 16;
}
}
ImGui::EndChild();
ImGui::TableNextColumn();
tilemap_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
tilemap_canvas_.DrawContextMenu();
tilemap_canvas_.DrawBitmapTable(sheets_);
tilemap_canvas_.DrawGrid();
tilemap_canvas_.DrawOverlay();
ImGui::EndTable();
}
}
void ScreenEditor::DrawTitleScreenEditor() {
TAB_ITEM("Title Screen")
END_TAB_ITEM()
}
void ScreenEditor::DrawNamingScreenEditor() {
TAB_ITEM("Naming Screen")
END_TAB_ITEM()
}
void ScreenEditor::DrawOverworldMapEditor() {
TAB_ITEM("Overworld Map")
END_TAB_ITEM()
}
void ScreenEditor::DrawToolset() {
static bool show_bg1 = true;
static bool show_bg2 = true;
static bool show_bg3 = true;
static bool drawing_bg1 = true;
static bool drawing_bg2 = false;
static bool drawing_bg3 = false;
ImGui::Checkbox("Show BG1", &show_bg1);
ImGui::SameLine();
ImGui::Checkbox("Show BG2", &show_bg2);
ImGui::Checkbox("Draw BG1", &drawing_bg1);
ImGui::SameLine();
ImGui::Checkbox("Draw BG2", &drawing_bg2);
ImGui::SameLine();
ImGui::Checkbox("Draw BG3", &drawing_bg3);
}
} // namespace editor } // namespace editor
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -5,40 +5,84 @@
#include <array> #include <array>
#include "absl/status/status.h"
#include "app/core/constants.h" #include "app/core/constants.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/color.h" #include "app/gui/color.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/screen/dungeon_map.h"
#include "app/zelda3/screen/inventory.h" #include "app/zelda3/screen/inventory.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
class ScreenEditor : public SharedROM { /**
* @brief The ScreenEditor class allows the user to edit a variety of screens in
* the game or create a custom menu.
*
* This class is currently a work in progress (WIP) and provides functionality
* for updating the screens, saving dungeon maps, drawing different types of
* screens, loading dungeon maps, and managing various properties related to the
* editor.
*
* The screens that can be edited include the title screen, naming screen,
* overworld map, inventory menu, and more.
*
* The class inherits from the SharedRom class.
*/
class ScreenEditor : public SharedRom {
public: public:
ScreenEditor(); ScreenEditor();
void Update(); void Update();
absl::Status SaveDungeonMaps();
private: private:
void DrawTitleScreenEditor(); void DrawTitleScreenEditor();
void DrawNamingScreenEditor(); void DrawNamingScreenEditor();
void DrawOverworldMapEditor(); void DrawOverworldMapEditor();
void DrawDungeonMapsEditor();
void DrawInventoryMenuEditor();
void DrawInventoryMenuEditor();
void DrawToolset(); void DrawToolset();
void DrawInventoryToolset(); void DrawInventoryToolset();
absl::Status LoadDungeonMaps();
absl::Status LoadDungeonMapTile16();
void DrawDungeonMapsTabs();
void DrawDungeonMapsEditor();
std::vector<zelda3::screen::DungeonMap> dungeon_maps_;
std::vector<std::vector<std::array<std::string, 25>>> dungeon_map_labels_;
std::unordered_map<int, gfx::Bitmap> tile16_individual_;
bool dungeon_maps_loaded_ = false;
int selected_tile16_ = 0;
int selected_dungeon = 0;
uint8_t selected_room = 0;
uint8_t boss_room = 0;
int floor_number = 1;
bool copy_button_pressed = false;
bool paste_button_pressed = false;
Bytes all_gfx_; Bytes all_gfx_;
zelda3::Inventory inventory_; zelda3::screen::Inventory inventory_;
gfx::SNESPalette palette_; gfx::SnesPalette palette_;
gui::Canvas screen_canvas_; gui::Canvas screen_canvas_;
gui::Canvas tilesheet_canvas_; gui::Canvas tilesheet_canvas_;
gui::Canvas tilemap_canvas_;
gfx::BitmapTable sheets_;
gfx::Tilesheet tile16_sheet_;
}; };
} // namespace editor } // namespace editor

View File

@@ -4,7 +4,65 @@ namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
absl::Status SpriteEditor::Update() { return absl::OkStatus(); } using ImGui::Button;
using ImGui::Separator;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
using ImGui::Text;
absl::Status SpriteEditor::Update() {
if (rom()->is_loaded() && !sheets_loaded_) {
// Load the values for current_sheets_ array
sheets_loaded_ = true;
}
// if (ImGui::BeginTable({"Canvas", "Graphics"}, 2, nullptr, ImVec2(0, 0))) {
// TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
// ImGui::GetContentRegionAvail().x);
// TableSetupColumn("Tile Selector", ImGuiTableColumnFlags_WidthFixed, 256);
// TableHeadersRow();
// TableNextRow();
// TableNextColumn();
// DrawSpriteCanvas();
// TableNextColumn();
// if (sheets_loaded_) {
// DrawCurrentSheets();
// }
// ImGui::EndTable();
// }
return absl::OkStatus();
}
void SpriteEditor::DrawEditorTable() {}
void SpriteEditor::DrawSpriteCanvas() {}
void SpriteEditor::DrawCurrentSheets() {
static gui::Canvas graphics_sheet_canvas;
for (int i = 0; i < 8; i++) {
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)) {
graphics_sheet_canvas.DrawBackground(ImVec2(0x200 * 8, 0x200 * 8));
ImGui::PopStyleVar(2);
graphics_sheet_canvas.DrawContextMenu();
graphics_sheet_canvas.DrawBitmap(
*rom()->bitmap_manager()[current_sheets_[i]], 2, 2);
graphics_sheet_canvas.DrawGrid(64.0f);
graphics_sheet_canvas.DrawOverlay();
}
ImGui::EndChild();
}
}
} // namespace editor } // namespace editor
} // namespace app } // namespace app

View File

@@ -2,14 +2,48 @@
#define YAZE_APP_EDITOR_SPRITE_EDITOR_H #define YAZE_APP_EDITOR_SPRITE_EDITOR_H
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/gui/canvas.h"
#include "app/rom.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
class SpriteEditor { /**
public: * @class SpriteEditor
absl::Status Update(); * @brief Allows the user to edit sprites.
*
* This class provides functionality for updating the sprite editor, drawing the
* editor table, drawing the sprite canvas, and drawing the current sheets.
*/
class SpriteEditor : public SharedRom {
public:
/**
* @brief Updates the sprite editor.
*
* @return An absl::Status indicating the success or failure of the update.
*/
absl::Status Update();
private:
/**
* @brief Draws the editor table.
*/
void DrawEditorTable();
/**
* @brief Draws the sprite canvas.
*/
void DrawSpriteCanvas();
/**
* @brief Draws the current sheets.
*/
void DrawCurrentSheets();
uint8_t current_sheets_[8]; /**< Array to store the current sheets. */
bool sheets_loaded_ =
false; /**< Flag indicating whether the sheets are loaded or not. */
}; };
} // namespace editor } // namespace editor

View File

@@ -3,6 +3,21 @@
#include "absl/status/status.h" #include "absl/status/status.h"
namespace yaze {
namespace app {
/**
* @namespace yaze::app::editor
* @brief Editors are the view controllers for the application.
*/
namespace editor {
/**
* @class Editor
* @brief Interface for editor classes.
*
* Provides basic editing operations that each editor should implement.
*/
class Editor { class Editor {
public: public:
Editor() = default; Editor() = default;
@@ -18,4 +33,8 @@ class Editor {
virtual absl::Status Update() = 0; virtual absl::Status Update() = 0;
}; };
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_EDITOR_H #endif // YAZE_APP_CORE_EDITOR_H

View File

@@ -13,8 +13,9 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace audio {
void APU::Init() { void Apu::Init() {
// Set the clock frequency // Set the clock frequency
clock_.SetFrequency(kApuClockSpeed); clock_.SetFrequency(kApuClockSpeed);
@@ -27,17 +28,17 @@ void APU::Init() {
[this](int16_t sample) { this->PushToAudioBuffer(sample); }); [this](int16_t sample) { this->PushToAudioBuffer(sample); });
} }
void APU::Reset() { void Apu::Reset() {
clock_.ResetAccumulatedTime(); clock_.ResetAccumulatedTime();
spc700_.Reset(); spc700_.Reset();
dsp_.Reset(); dsp_.Reset();
} }
void APU::Update() { void Apu::Update() {
auto cycles_to_run = clock_.GetCycleCount(); auto cycles_to_run = clock_.GetCycleCount();
for (auto i = 0; i < cycles_to_run; ++i) { for (auto i = 0; i < cycles_to_run; ++i) {
// Update the APU // Update the Apu
UpdateChannelSettings(); UpdateChannelSettings();
// Update the SPC700 // Update the SPC700
@@ -49,14 +50,14 @@ void APU::Update() {
ProcessSamples(); ProcessSamples();
} }
void APU::Notify(uint32_t address, uint8_t data) { void Apu::Notify(uint32_t address, uint8_t data) {
if (address < 0x2140 || address > 0x2143) { if (address < 0x2140 || address > 0x2143) {
return; return;
} }
auto offset = address - 0x2140; auto offset = address - 0x2140;
spc700_.write(offset, data); spc700_.write(offset, data);
// HACK - This is a temporary solution to get the APU to play audio // HACK - This is a temporary solution to get the Apu to play audio
ports_[address - 0x2140] = data; ports_[address - 0x2140] = data;
switch (address) { switch (address) {
case 0x2140: case 0x2140:
@@ -76,7 +77,7 @@ void APU::Notify(uint32_t address, uint8_t data) {
} }
} }
void APU::ProcessSamples() { void Apu::ProcessSamples() {
// Fetch sample data from AudioRam // Fetch sample data from AudioRam
// Iterate over all voices // Iterate over all voices
for (uint8_t voice_num = 0; voice_num < 8; voice_num++) { for (uint8_t voice_num = 0; voice_num < 8; voice_num++) {
@@ -91,17 +92,17 @@ void APU::ProcessSamples() {
} }
} }
uint8_t APU::FetchSampleForVoice(uint8_t voice_num) { uint8_t Apu::FetchSampleForVoice(uint8_t voice_num) {
uint16_t address = CalculateAddressForVoice(voice_num); uint16_t address = CalculateAddressForVoice(voice_num);
return aram_.read(address); return aram_.read(address);
} }
uint16_t APU::CalculateAddressForVoice(uint8_t voice_num) { uint16_t Apu::CalculateAddressForVoice(uint8_t voice_num) {
// TODO: Calculate the address for the specified voice // TODO: Calculate the address for the specified voice
return voice_num; return voice_num;
} }
int16_t APU::GetNextSample() { int16_t Apu::GetNextSample() {
if (!audio_samples_.empty()) { if (!audio_samples_.empty()) {
int16_t sample = audio_samples_.front(); int16_t sample = audio_samples_.front();
audio_samples_.erase(audio_samples_.begin()); audio_samples_.erase(audio_samples_.begin());
@@ -110,30 +111,31 @@ int16_t APU::GetNextSample() {
return 0; // TODO: Return the last sample instead of 0. return 0; // TODO: Return the last sample instead of 0.
} }
const std::vector<int16_t>& APU::GetAudioSamples() const { const std::vector<int16_t>& Apu::GetAudioSamples() const {
return audio_samples_; return audio_samples_;
} }
void APU::UpdateChannelSettings() { void Apu::UpdateChannelSettings() {
// TODO: Implement this method to update the channel settings. // TODO: Implement this method to update the channel settings.
} }
int16_t APU::GenerateSample(int channel) { int16_t Apu::GenerateSample(int channel) {
// TODO: Implement this method to generate a sample for the specified channel. // TODO: Implement this method to generate a sample for the specified channel.
} }
void APU::ApplyEnvelope(int channel) { void Apu::ApplyEnvelope(int channel) {
// TODO: Implement this method to apply an envelope to the specified channel. // TODO: Implement this method to apply an envelope to the specified channel.
} }
uint8_t APU::ReadDspMemory(uint16_t address) { uint8_t Apu::ReadDspMemory(uint16_t address) {
return dsp_.ReadGlobalReg(address); return dsp_.ReadGlobalReg(address);
} }
void APU::WriteDspMemory(uint16_t address, uint8_t value) { void Apu::WriteDspMemory(uint16_t address, uint8_t value) {
dsp_.WriteGlobalReg(address, value); dsp_.WriteGlobalReg(address, value);
} }
} // namespace audio
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -13,13 +13,34 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace audio {
using namespace memory;
/** /**
* *
*
*/
const int kApuClockSpeed = 1024000; // 1.024 MHz
const int apuSampleRate = 32000; // 32 KHz
const int apuClocksPerSample = 64; // 64 clocks per sample
/**
* @class Apu
* @brief The Apu class represents the Audio Processing Unit (APU) of a system.
*
* The Apu class is responsible for generating audio samples and managing the
* APU state. It interacts with the Memory, AudioRam, and Clock classes to
* read/write data and update the clock. The class also implements the Observer
* interface to receive notifications from the system.
*
* @par IPL ROM Info
* 64 kilobytes of RAM are mapped across the 16-bit memory space of the SPC-700. * 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. * Some regions of this space are overlaid with special hardware functions.
* *
* Range Note * @par Range Note
* $0000-00EF Zero Page RAM * $0000-00EF Zero Page RAM
* $00F0-00FF Sound CPU Registers * $00F0-00FF Sound CPU Registers
* $0100-01FF Stack Page RAM * $0100-01FF Stack Page RAM
@@ -30,16 +51,10 @@ namespace emu {
* underlying RAM can always be written to, and the high bit of the Control * 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 * register $F1 can be cleared to unmap the IPL ROM and allow read access to
* this RAM. * this RAM.
*
*/ */
class Apu : public Observer {
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: public:
APU(MemoryImpl &memory, AudioRam &aram, Clock &clock) Apu(MemoryImpl &memory, AudioRam &aram, Clock &clock)
: aram_(aram), clock_(clock), memory_(memory) {} : aram_(aram), clock_(clock), memory_(memory) {}
void Init(); void Init();
@@ -130,6 +145,7 @@ class APU : public Observer {
std::function<void()> ready_callback_; std::function<void()> ready_callback_;
}; };
} // namespace audio
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -5,6 +5,7 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace audio {
void DigitalSignalProcessor::Reset() {} void DigitalSignalProcessor::Reset() {}
@@ -37,7 +38,8 @@ uint8_t DigitalSignalProcessor::ReadVoiceReg(uint8_t voice, uint8_t reg) const {
} }
} }
void DigitalSignalProcessor::WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value) { void DigitalSignalProcessor::WriteVoiceReg(uint8_t voice, uint8_t reg,
uint8_t value) {
voice %= kNumVoices; voice %= kNumVoices;
switch (reg % kNumVoiceRegs) { switch (reg % kNumVoiceRegs) {
case 0: case 0:
@@ -69,9 +71,13 @@ void DigitalSignalProcessor::WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t v
} }
// Set the callbacks // Set the callbacks
void DigitalSignalProcessor::SetSampleFetcher(SampleFetcher fetcher) { sample_fetcher_ = fetcher; } void DigitalSignalProcessor::SetSampleFetcher(SampleFetcher fetcher) {
sample_fetcher_ = fetcher;
}
void DigitalSignalProcessor::SetSamplePusher(SamplePusher pusher) { sample_pusher_ = pusher; } void DigitalSignalProcessor::SetSamplePusher(SamplePusher pusher) {
sample_pusher_ = pusher;
}
int16_t DigitalSignalProcessor::DecodeSample(uint8_t voice_num) { int16_t DigitalSignalProcessor::DecodeSample(uint8_t voice_num) {
Voice const& voice = voices_[voice_num]; Voice const& voice = voices_[voice_num];
@@ -82,7 +88,8 @@ int16_t DigitalSignalProcessor::DecodeSample(uint8_t voice_num) {
return sample; return sample;
} }
int16_t DigitalSignalProcessor::ProcessSample(uint8_t voice_num, int16_t sample) { int16_t DigitalSignalProcessor::ProcessSample(uint8_t voice_num,
int16_t sample) {
Voice const& voice = voices_[voice_num]; Voice const& voice = voices_[voice_num];
// Adjust the pitch (for simplicity, we're just adjusting the sample value) // Adjust the pitch (for simplicity, we're just adjusting the sample value)
@@ -276,6 +283,7 @@ void DigitalSignalProcessor::process_envelope(uint8_t voice_num) {
apply_envelope_to_output(voice_num); apply_envelope_to_output(voice_num);
} }
} // namespace audio
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -10,12 +10,12 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace audio {
using SampleFetcher = std::function<uint8_t(uint16_t)>; using SampleFetcher = std::function<uint8_t(uint16_t)>;
using SamplePusher = std::function<void(int16_t)>; using SamplePusher = std::function<void(int16_t)>;
/** /**
*
* The S-DSP is a digital signal processor generating the sound data. * 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 * A DSP register can be selected with $F2, after which it can be read or
@@ -34,21 +34,19 @@ using SamplePusher = std::function<void(int16_t)>;
* There are 8 voices, numbered 0 to 7. * There are 8 voices, numbered 0 to 7.
* Each voice X has 10 registers in the range $X0-$X9. * Each voice X has 10 registers in the range $X0-$X9.
* *
* Name Address Bits Notes * | Name | Address | Bits | Notes |
* VOL (L) $X0 SVVV VVVV Left channel volume, signed. * |---------|---------|-----------|--------------------------------------------------------|
* VOL (R) $X1 SVVV VVVV Right channel volume, signed. * | VOL (L) | $X0 | SVVV VVVV | Left channel volume, signed. |
* P (L) $X2 LLLL LLLL Low 8 bits of sample pitch. * | VOL (R) | $X1 | SVVV VVVV | Right channel volume, signed. |
* P (H) $X3 --HH HHHH High 6 bits of sample pitch. * | P (L) | $X2 | LLLL LLLL | Low 8 bits of sample pitch. |
* SCRN $X4 SSSS SSSS Selects a sample source entry from the * | P (H) | $X3 | --HH HHHH | High 6 bits of sample pitch. |
* directory ADSR (1) $X5 EDDD AAAA ADSR enable (E), decay rate (D), * | SCRN | $X4 | SSSS SSSS | Selects a sample source entry from the directory. |
* attack rate (A). * | 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). * | ADSR (2)| $X6 | SSSR RRRR | Sustain level (S), release rate (R). |
* GAIN $X7 0VVV VVVV 1MMV VVVV Mode (M), value (V). * | GAIN | $X7 | 0VVV VVVV 1MMV VVVV | Mode (M), value (V). |
* ENVX $X8 0VVV VVVV Reads current 7-bit value of ADSR/GAIN * | ENVX | $X8 | 0VVV VVVV | Reads current 7-bit value of ADSR/GAIN envelope. |
* envelope. * | OUTX | $X9 | SVVV VVVV | Reads signed 8-bit value of current sample wave |
* OUTX $X9 SVVV VVVV Reads signed 8-bit value of current * | | | | multiplied by ENVX, before applying VOL. |
* sample wave multiplied by ENVX, before applying VOL.
*
*/ */
class DigitalSignalProcessor { class DigitalSignalProcessor {
@@ -308,6 +306,8 @@ class DigitalSignalProcessor {
// and apply the envelope to the audio output. // and apply the envelope to the audio output.
void process_envelope(uint8_t voice_num); void process_envelope(uint8_t voice_num);
}; };
} // namespace audio
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -3,6 +3,7 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace audio {
// Immediate // Immediate
uint8_t Spc700::imm() { uint8_t Spc700::imm() {
@@ -89,6 +90,7 @@ uint16_t Spc700::addr_plus_i_indexed() {
return read(addr) | (read(addr + 1) << 8); return read(addr) | (read(addr + 1) << 8);
} }
} // namespace audio
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -3,6 +3,7 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace audio {
void Spc700::MOV(uint8_t& dest, uint8_t operand) { void Spc700::MOV(uint8_t& dest, uint8_t operand) {
dest = operand; dest = operand;
@@ -356,6 +357,7 @@ void Spc700::SLEEP() {}
void Spc700::STOP() {} void Spc700::STOP() {}
} // namespace audio
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -11,6 +11,7 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace audio {
void Spc700::Reset() { void Spc700::Reset() {
PC = 0; PC = 0;
@@ -952,6 +953,7 @@ void Spc700::LogInstruction(uint16_t initial_pc, uint8_t opcode) {
log_.push_back(log_entry); log_.push_back(log_entry);
} }
} // namespace audio
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -9,7 +9,11 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace audio {
/**
* @brief AudioRam is an interface for the Audio RAM used by the SPC700.
*/
class AudioRam { class AudioRam {
public: public:
virtual ~AudioRam() = default; virtual ~AudioRam() = default;
@@ -19,6 +23,9 @@ class AudioRam {
virtual void write(uint16_t address, uint8_t value) = 0; virtual void write(uint16_t address, uint8_t value) = 0;
}; };
/**
* @brief AudioRamImpl is an implementation of the AudioRam interface.
*/
class AudioRamImpl : public AudioRam { class AudioRamImpl : public AudioRam {
static const int ARAM_SIZE = 0x10000; static const int ARAM_SIZE = 0x10000;
std::vector<uint8_t> ram = std::vector<uint8_t>(ARAM_SIZE, 0); std::vector<uint8_t> ram = std::vector<uint8_t>(ARAM_SIZE, 0);
@@ -40,6 +47,17 @@ class AudioRamImpl : public AudioRam {
} }
}; };
/**
* @class Spc700
* @brief The Spc700 class represents the SPC700 processor.
*
* The Spc700 class provides the functionality to execute instructions, read and
* write memory, and handle various addressing modes. It also contains registers
* and flags specific to the SPC700.
*
* @note This class assumes the existence of an `AudioRam` object for memory
* access.
*/
class Spc700 { class Spc700 {
private: private:
AudioRam& aram_; AudioRam& aram_;
@@ -261,6 +279,7 @@ class Spc700 {
// CBNE DBNZ // CBNE DBNZ
}; };
} // namespace audio
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -10,7 +10,7 @@ namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
void CPU::Update(UpdateMode mode, int stepCount) { void Cpu::Update(UpdateMode mode, int stepCount) {
int cycles = (mode == UpdateMode::Run) ? clock.GetCycleCount() : stepCount; int cycles = (mode == UpdateMode::Run) ? clock.GetCycleCount() : stepCount;
// Execute the calculated number of cycles // Execute the calculated number of cycles
@@ -31,7 +31,7 @@ void CPU::Update(UpdateMode mode, int stepCount) {
} }
} }
void CPU::ExecuteInstruction(uint8_t opcode) { void Cpu::ExecuteInstruction(uint8_t opcode) {
uint8_t cycles = 0; uint8_t cycles = 0;
uint8_t instruction_length = 0; uint8_t instruction_length = 0;
uint32_t operand = 0; uint32_t operand = 0;
@@ -1475,7 +1475,7 @@ void CPU::ExecuteInstruction(uint8_t opcode) {
UpdatePC(instruction_length); UpdatePC(instruction_length);
} }
void CPU::LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand, void Cpu::LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand,
bool immediate, bool accumulator_mode) { bool immediate, bool accumulator_mode) {
if (flags()->kLogInstructions) { if (flags()->kLogInstructions) {
std::ostringstream oss; std::ostringstream oss;
@@ -1576,12 +1576,14 @@ void CPU::LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand,
<< static_cast<int>(DB); << static_cast<int>(DB);
std::cout << " D:" << std::hex << std::setw(2) << std::setfill('0') std::cout << " D:" << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(D); << static_cast<int>(D);
std::cout << " SP:" << std::hex << std::setw(4) << std::setfill('0')
<< SP();
std::cout << std::endl; std::cout << std::endl;
} }
} }
uint8_t CPU::GetInstructionLength(uint8_t opcode) { uint8_t Cpu::GetInstructionLength(uint8_t opcode) {
switch (opcode) { switch (opcode) {
case 0x00: // BRK case 0x00: // BRK
case 0x02: // COP case 0x02: // COP
@@ -1926,7 +1928,7 @@ uint8_t CPU::GetInstructionLength(uint8_t opcode) {
} }
// TODO: Implement 65816 interrupts. // TODO: Implement 65816 interrupts.
void CPU::HandleInterrupts() { void Cpu::HandleInterrupts() {
if (GetInterruptFlag()) { if (GetInterruptFlag()) {
return; return;
} }

View File

@@ -38,9 +38,11 @@ class InstructionEntry {
const int kCpuClockSpeed = 21477272; // 21.477272 MHz const int kCpuClockSpeed = 21477272; // 21.477272 MHz
class CPU : public Memory, public Loggable, public core::ExperimentFlags { class Cpu : public memory::Memory,
public Loggable,
public core::ExperimentFlags {
public: public:
explicit CPU(Memory& mem, Clock& vclock) : memory(mem), clock(vclock) {} explicit Cpu(Memory& mem, Clock& vclock) : memory(mem), clock(vclock) {}
enum class UpdateMode { Run, Step, Pause }; enum class UpdateMode { Run, Step, Pause };
void Init(bool verbose = false) { clock.SetFrequency(kCpuClockSpeed); } void Init(bool verbose = false) { clock.SetFrequency(kCpuClockSpeed); }
@@ -394,6 +396,13 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags {
} }
} }
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(); }
// ====================================================== // ======================================================
// Instructions // Instructions
@@ -702,12 +711,6 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags {
} }
bool GetFlag(uint8_t mask) const { return (status & mask) != 0; } 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(); } void ClearMemory() override { memory.ClearMemory(); }
uint8_t operator[](int i) const override { return 0; } uint8_t operator[](int i) const override { return 0; }
uint8_t at(int i) const override { return 0; } uint8_t at(int i) const override { return 0; }

View File

@@ -4,92 +4,92 @@ namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
uint32_t CPU::Absolute(CPU::AccessType access_type) { uint32_t Cpu::Absolute(Cpu::AccessType access_type) {
auto operand = FetchWord(); auto operand = FetchWord();
uint32_t bank = uint32_t bank =
(access_type == CPU::AccessType::Data) ? (DB << 16) : (PB << 16); (access_type == Cpu::AccessType::Data) ? (DB << 16) : (PB << 16);
return bank | (operand & 0xFFFF); return bank | (operand & 0xFFFF);
} }
uint32_t CPU::AbsoluteIndexedX() { uint32_t Cpu::AbsoluteIndexedX() {
uint16_t address = memory.ReadWord((PB << 16) | (PC + 1)); uint16_t address = memory.ReadWord((PB << 16) | (PC + 1));
uint32_t effective_address = (DB << 16) | ((address + X) & 0xFFFF); uint32_t effective_address = (DB << 16) | ((address + X) & 0xFFFF);
return effective_address; return effective_address;
} }
uint32_t CPU::AbsoluteIndexedY() { uint32_t Cpu::AbsoluteIndexedY() {
uint16_t address = memory.ReadWord((PB << 16) | (PC + 1)); uint16_t address = memory.ReadWord((PB << 16) | (PC + 1));
uint32_t effective_address = (DB << 16) | address + Y; uint32_t effective_address = (DB << 16) | address + Y;
return effective_address; return effective_address;
} }
uint16_t CPU::AbsoluteIndexedIndirect() { uint16_t Cpu::AbsoluteIndexedIndirect() {
uint16_t address = FetchWord() + X; uint16_t address = FetchWord() + X;
return memory.ReadWord((DB << 16) | address & 0xFFFF); return memory.ReadWord((DB << 16) | address & 0xFFFF);
} }
uint16_t CPU::AbsoluteIndirect() { uint16_t Cpu::AbsoluteIndirect() {
uint16_t address = FetchWord(); uint16_t address = FetchWord();
return memory.ReadWord((PB << 16) | address); return memory.ReadWord((PB << 16) | address);
} }
uint32_t CPU::AbsoluteIndirectLong() { uint32_t Cpu::AbsoluteIndirectLong() {
uint16_t address = FetchWord(); uint16_t address = FetchWord();
return memory.ReadWordLong((PB << 16) | address); return memory.ReadWordLong((PB << 16) | address);
} }
uint32_t CPU::AbsoluteLong() { return FetchLong(); } uint32_t Cpu::AbsoluteLong() { return FetchLong(); }
uint32_t CPU::AbsoluteLongIndexedX() { return FetchLong() + X; } uint32_t Cpu::AbsoluteLongIndexedX() { return FetchLong() + X; }
void CPU::BlockMove(uint16_t source, uint16_t dest, uint16_t length) { void Cpu::BlockMove(uint16_t source, uint16_t dest, uint16_t length) {
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
memory.WriteByte(dest + i, memory.ReadByte(source + i)); memory.WriteByte(dest + i, memory.ReadByte(source + i));
} }
} }
uint16_t CPU::DirectPage() { uint16_t Cpu::DirectPage() {
uint8_t dp = FetchByte(); uint8_t dp = FetchByte();
return D + dp; return D + dp;
} }
uint16_t CPU::DirectPageIndexedX() { uint16_t Cpu::DirectPageIndexedX() {
uint8_t operand = FetchByte(); uint8_t operand = FetchByte();
uint16_t x_by_mode = GetAccumulatorSize() ? X : X & 0xFF; uint16_t x_by_mode = GetAccumulatorSize() ? X : X & 0xFF;
return D + operand + x_by_mode; return D + operand + x_by_mode;
} }
uint16_t CPU::DirectPageIndexedY() { uint16_t Cpu::DirectPageIndexedY() {
uint8_t operand = FetchByte(); uint8_t operand = FetchByte();
return (operand + Y) & 0xFF; return (operand + Y) & 0xFF;
} }
uint16_t CPU::DirectPageIndexedIndirectX() { uint16_t Cpu::DirectPageIndexedIndirectX() {
uint8_t operand = FetchByte(); uint8_t operand = FetchByte();
uint16_t indirect_address = D + operand + X; uint16_t indirect_address = D + operand + X;
uint16_t effective_address = memory.ReadWord(indirect_address & 0xFFFF); uint16_t effective_address = memory.ReadWord(indirect_address & 0xFFFF);
return effective_address; return effective_address;
} }
uint16_t CPU::DirectPageIndirect() { uint16_t Cpu::DirectPageIndirect() {
uint8_t dp = FetchByte(); uint8_t dp = FetchByte();
uint16_t effective_address = D + dp; uint16_t effective_address = D + dp;
return memory.ReadWord(effective_address); return memory.ReadWord(effective_address);
} }
uint32_t CPU::DirectPageIndirectLong() { uint32_t Cpu::DirectPageIndirectLong() {
uint8_t dp = FetchByte(); uint8_t dp = FetchByte();
uint16_t effective_address = D + dp; uint16_t effective_address = D + dp;
return memory.ReadWordLong((0x00 << 0x10) | effective_address); return memory.ReadWordLong((0x00 << 0x10) | effective_address);
} }
uint16_t CPU::DirectPageIndirectIndexedY() { uint16_t Cpu::DirectPageIndirectIndexedY() {
uint8_t operand = FetchByte(); uint8_t operand = FetchByte();
uint16_t indirect_address = D + operand; uint16_t indirect_address = D + operand;
return memory.ReadWord(indirect_address) + Y; return memory.ReadWord(indirect_address) + Y;
} }
uint32_t CPU::DirectPageIndirectLongIndexedY() { uint32_t Cpu::DirectPageIndirectLongIndexedY() {
uint8_t operand = FetchByte(); uint8_t operand = FetchByte();
uint16_t indirect_address = D + operand; uint16_t indirect_address = D + operand;
uint16_t y_by_mode = GetAccumulatorSize() ? Y : Y & 0xFF; uint16_t y_by_mode = GetAccumulatorSize() ? Y : Y & 0xFF;
@@ -98,7 +98,7 @@ uint32_t CPU::DirectPageIndirectLongIndexedY() {
return effective_address; return effective_address;
} }
uint16_t CPU::Immediate(bool index_size) { uint16_t Cpu::Immediate(bool index_size) {
bool bit_mode = index_size ? GetIndexSize() : GetAccumulatorSize(); bool bit_mode = index_size ? GetIndexSize() : GetAccumulatorSize();
if (bit_mode) { if (bit_mode) {
return memory.ReadByte((PB << 16) | PC + 1); return memory.ReadByte((PB << 16) | PC + 1);
@@ -107,13 +107,13 @@ uint16_t CPU::Immediate(bool index_size) {
} }
} }
uint16_t CPU::StackRelative() { uint16_t Cpu::StackRelative() {
uint8_t sr = FetchByte(); uint8_t sr = FetchByte();
uint16_t effective_address = SP() + sr; uint16_t effective_address = SP() + sr;
return effective_address; return effective_address;
} }
uint32_t CPU::StackRelativeIndirectIndexedY() { uint32_t Cpu::StackRelativeIndirectIndexedY() {
uint8_t sr = FetchByte(); uint8_t sr = FetchByte();
return (DB << 0x10) | (memory.ReadWord(SP() + sr) + Y); return (DB << 0x10) | (memory.ReadWord(SP() + sr) + Y);
} }

View File

@@ -14,7 +14,7 @@ namespace emu {
* TODO: STP, WDM * TODO: STP, WDM
*/ */
void CPU::ADC(uint16_t operand) { void Cpu::ADC(uint16_t operand) {
bool C = GetCarryFlag(); bool C = GetCarryFlag();
if (GetAccumulatorSize()) { // 8-bit mode if (GetAccumulatorSize()) { // 8-bit mode
uint16_t result = static_cast<uint16_t>(A & 0xFF) + uint16_t result = static_cast<uint16_t>(A & 0xFF) +
@@ -47,7 +47,7 @@ void CPU::ADC(uint16_t operand) {
} }
} }
void CPU::AND(uint32_t value, bool isImmediate) { void Cpu::AND(uint32_t value, bool isImmediate) {
uint16_t operand; uint16_t operand;
if (GetAccumulatorSize()) { // 8-bit mode if (GetAccumulatorSize()) { // 8-bit mode
operand = isImmediate ? value : memory.ReadByte(value); operand = isImmediate ? value : memory.ReadByte(value);
@@ -63,14 +63,14 @@ void CPU::AND(uint32_t value, bool isImmediate) {
} }
// New function for absolute long addressing mode // New function for absolute long addressing mode
void CPU::ANDAbsoluteLong(uint32_t address) { void Cpu::ANDAbsoluteLong(uint32_t address) {
uint32_t operand32 = memory.ReadWordLong(address); uint32_t operand32 = memory.ReadWordLong(address);
A &= operand32; A &= operand32;
SetZeroFlag(A == 0); SetZeroFlag(A == 0);
SetNegativeFlag(A & 0x8000); SetNegativeFlag(A & 0x8000);
} }
void CPU::ASL(uint16_t address) { void Cpu::ASL(uint16_t address) {
uint8_t value = memory.ReadByte(address); uint8_t value = memory.ReadByte(address);
SetCarryFlag(!(value & 0x80)); // Set carry flag if bit 7 is set SetCarryFlag(!(value & 0x80)); // Set carry flag if bit 7 is set
value <<= 1; // Shift left value <<= 1; // Shift left
@@ -80,53 +80,53 @@ void CPU::ASL(uint16_t address) {
SetZeroFlag(value); SetZeroFlag(value);
} }
void CPU::BCC(int8_t offset) { void Cpu::BCC(int8_t offset) {
if (!GetCarryFlag()) { // If the carry flag is clear if (!GetCarryFlag()) { // If the carry flag is clear
next_pc_ = offset; next_pc_ = offset;
} }
} }
void CPU::BCS(int8_t offset) { void Cpu::BCS(int8_t offset) {
if (GetCarryFlag()) { // If the carry flag is set if (GetCarryFlag()) { // If the carry flag is set
next_pc_ = offset; next_pc_ = offset;
} }
} }
void CPU::BEQ(int8_t offset) { void Cpu::BEQ(int8_t offset) {
if (GetZeroFlag()) { // If the zero flag is set if (GetZeroFlag()) { // If the zero flag is set
next_pc_ = offset; next_pc_ = offset;
} }
} }
void CPU::BIT(uint16_t address) { void Cpu::BIT(uint16_t address) {
uint8_t value = memory.ReadByte(address); uint8_t value = memory.ReadByte(address);
SetNegativeFlag(value & 0x80); SetNegativeFlag(value & 0x80);
SetOverflowFlag(value & 0x40); SetOverflowFlag(value & 0x40);
SetZeroFlag((A & value) == 0); SetZeroFlag((A & value) == 0);
} }
void CPU::BMI(int8_t offset) { void Cpu::BMI(int8_t offset) {
if (GetNegativeFlag()) { // If the negative flag is set if (GetNegativeFlag()) { // If the negative flag is set
next_pc_ = offset; next_pc_ = offset;
} }
} }
void CPU::BNE(int8_t offset) { void Cpu::BNE(int8_t offset) {
if (!GetZeroFlag()) { // If the zero flag is clear if (!GetZeroFlag()) { // If the zero flag is clear
// PC += offset; // PC += offset;
next_pc_ = offset; next_pc_ = offset;
} }
} }
void CPU::BPL(int8_t offset) { void Cpu::BPL(int8_t offset) {
if (!GetNegativeFlag()) { // If the negative flag is clear if (!GetNegativeFlag()) { // If the negative flag is clear
next_pc_ = offset; next_pc_ = offset;
} }
} }
void CPU::BRA(int8_t offset) { next_pc_ = offset; } void Cpu::BRA(int8_t offset) { next_pc_ = offset; }
void CPU::BRK() { void Cpu::BRK() {
next_pc_ = PC + 2; // Increment the program counter by 2 next_pc_ = PC + 2; // Increment the program counter by 2
memory.PushWord(next_pc_); memory.PushWord(next_pc_);
memory.PushByte(status); memory.PushByte(status);
@@ -138,32 +138,32 @@ void CPU::BRK() {
} }
} }
void CPU::BRL(int16_t offset) { next_pc_ = offset; } void Cpu::BRL(int16_t offset) { next_pc_ = offset; }
void CPU::BVC(int8_t offset) { void Cpu::BVC(int8_t offset) {
if (!GetOverflowFlag()) { // If the overflow flag is clear if (!GetOverflowFlag()) { // If the overflow flag is clear
next_pc_ = offset; next_pc_ = offset;
} }
} }
void CPU::BVS(int8_t offset) { void Cpu::BVS(int8_t offset) {
if (GetOverflowFlag()) { // If the overflow flag is set if (GetOverflowFlag()) { // If the overflow flag is set
next_pc_ = offset; next_pc_ = offset;
} }
} }
void CPU::CLC() { status &= ~0x01; } void Cpu::CLC() { status &= ~0x01; }
void CPU::CLD() { status &= ~0x08; } void Cpu::CLD() { status &= ~0x08; }
void CPU::CLI() { status &= ~0x04; } void Cpu::CLI() { status &= ~0x04; }
void CPU::CLV() { status &= ~0x40; } void Cpu::CLV() { status &= ~0x40; }
// n Set if MSB of result is set; else cleared // n Set if MSB of result is set; else cleared
// z Set if result is zero; else cleared // z Set if result is zero; else cleared
// c Set if no borrow; else cleared // c Set if no borrow; else cleared
void CPU::CMP(uint32_t value, bool isImmediate) { void Cpu::CMP(uint32_t value, bool isImmediate) {
if (GetAccumulatorSize()) { // 8-bit if (GetAccumulatorSize()) { // 8-bit
uint8_t result; uint8_t result;
if (isImmediate) { if (isImmediate) {
@@ -189,7 +189,7 @@ void CPU::CMP(uint32_t value, bool isImmediate) {
} }
} }
void CPU::COP() { void Cpu::COP() {
next_pc_ += 2; // Increment the program counter by 2 next_pc_ += 2; // Increment the program counter by 2
memory.PushWord(next_pc_); memory.PushWord(next_pc_);
memory.PushByte(status); memory.PushByte(status);
@@ -202,7 +202,7 @@ void CPU::COP() {
SetDecimalFlag(false); SetDecimalFlag(false);
} }
void CPU::CPX(uint32_t value, bool isImmediate) { void Cpu::CPX(uint32_t value, bool isImmediate) {
if (GetIndexSize()) { // 8-bit if (GetIndexSize()) { // 8-bit
uint8_t memory_value = isImmediate ? value : memory.ReadByte(value); uint8_t memory_value = isImmediate ? value : memory.ReadByte(value);
compare(X, memory_value); compare(X, memory_value);
@@ -212,7 +212,7 @@ void CPU::CPX(uint32_t value, bool isImmediate) {
} }
} }
void CPU::CPY(uint32_t value, bool isImmediate) { void Cpu::CPY(uint32_t value, bool isImmediate) {
if (GetIndexSize()) { // 8-bit if (GetIndexSize()) { // 8-bit
uint8_t memory_value = isImmediate ? value : memory.ReadByte(value); uint8_t memory_value = isImmediate ? value : memory.ReadByte(value);
compare(Y, memory_value); compare(Y, memory_value);
@@ -222,7 +222,7 @@ void CPU::CPY(uint32_t value, bool isImmediate) {
} }
} }
void CPU::DEC(uint32_t address, bool accumulator) { void Cpu::DEC(uint32_t address, bool accumulator) {
if (accumulator) { if (accumulator) {
if (GetAccumulatorSize()) { // 8-bit if (GetAccumulatorSize()) { // 8-bit
A = (A - 1) & 0xFF; A = (A - 1) & 0xFF;
@@ -251,7 +251,7 @@ void CPU::DEC(uint32_t address, bool accumulator) {
} }
} }
void CPU::DEX() { void Cpu::DEX() {
if (GetIndexSize()) { // 8-bit if (GetIndexSize()) { // 8-bit
X = static_cast<uint8_t>(X - 1); X = static_cast<uint8_t>(X - 1);
SetZeroFlag(X == 0); SetZeroFlag(X == 0);
@@ -263,7 +263,7 @@ void CPU::DEX() {
} }
} }
void CPU::DEY() { void Cpu::DEY() {
if (GetIndexSize()) { // 8-bit if (GetIndexSize()) { // 8-bit
Y = static_cast<uint8_t>(Y - 1); Y = static_cast<uint8_t>(Y - 1);
SetZeroFlag(Y == 0); SetZeroFlag(Y == 0);
@@ -275,7 +275,7 @@ void CPU::DEY() {
} }
} }
void CPU::EOR(uint32_t address, bool isImmediate) { void Cpu::EOR(uint32_t address, bool isImmediate) {
if (GetAccumulatorSize()) { if (GetAccumulatorSize()) {
A ^= isImmediate ? address : memory.ReadByte(address); A ^= isImmediate ? address : memory.ReadByte(address);
SetZeroFlag(A == 0); SetZeroFlag(A == 0);
@@ -287,7 +287,7 @@ void CPU::EOR(uint32_t address, bool isImmediate) {
} }
} }
void CPU::INC(uint32_t address, bool accumulator) { void Cpu::INC(uint32_t address, bool accumulator) {
if (accumulator) { if (accumulator) {
if (GetAccumulatorSize()) { // 8-bit if (GetAccumulatorSize()) { // 8-bit
A = (A + 1) & 0xFF; A = (A + 1) & 0xFF;
@@ -316,7 +316,7 @@ void CPU::INC(uint32_t address, bool accumulator) {
} }
} }
void CPU::INX() { void Cpu::INX() {
if (GetIndexSize()) { // 8-bit if (GetIndexSize()) { // 8-bit
X = static_cast<uint8_t>(X + 1); X = static_cast<uint8_t>(X + 1);
SetZeroFlag(X == 0); SetZeroFlag(X == 0);
@@ -328,7 +328,7 @@ void CPU::INX() {
} }
} }
void CPU::INY() { void Cpu::INY() {
if (GetIndexSize()) { // 8-bit if (GetIndexSize()) { // 8-bit
Y = static_cast<uint8_t>(Y + 1); Y = static_cast<uint8_t>(Y + 1);
SetZeroFlag(Y == 0); SetZeroFlag(Y == 0);
@@ -340,28 +340,28 @@ void CPU::INY() {
} }
} }
void CPU::JMP(uint16_t address) { void Cpu::JMP(uint16_t address) {
next_pc_ = address; // Set program counter to the new address next_pc_ = address; // Set program counter to the new address
} }
void CPU::JML(uint32_t address) { void Cpu::JML(uint32_t address) {
next_pc_ = static_cast<uint16_t>(address & 0xFFFF); next_pc_ = static_cast<uint16_t>(address & 0xFFFF);
// Set the PBR to the upper 8 bits of the address // Set the PBR to the upper 8 bits of the address
PB = static_cast<uint8_t>((address >> 16) & 0xFF); PB = static_cast<uint8_t>((address >> 16) & 0xFF);
} }
void CPU::JSR(uint16_t address) { void Cpu::JSR(uint16_t address) {
memory.PushWord(PC); // Push the program counter onto the stack memory.PushWord(PC); // Push the program counter onto the stack
next_pc_ = address; // Set program counter to the new address next_pc_ = address; // Set program counter to the new address
} }
void CPU::JSL(uint32_t address) { void Cpu::JSL(uint32_t address) {
memory.PushLong(PC); // Push the program counter onto the stack as a long memory.PushLong(PC); // Push the program counter onto the stack as a long
// value (24 bits) // value (24 bits)
next_pc_ = address; // Set program counter to the new address next_pc_ = address; // Set program counter to the new address
} }
void CPU::LDA(uint16_t address, bool isImmediate, bool direct_page, bool data_bank) { void Cpu::LDA(uint16_t address, bool isImmediate, bool direct_page, bool data_bank) {
uint8_t bank = PB; uint8_t bank = PB;
if (direct_page) { if (direct_page) {
bank = 0; bank = 0;
@@ -377,7 +377,7 @@ void CPU::LDA(uint16_t address, bool isImmediate, bool direct_page, bool data_ba
} }
} }
void CPU::LDX(uint16_t address, bool isImmediate) { void Cpu::LDX(uint16_t address, bool isImmediate) {
if (GetIndexSize()) { if (GetIndexSize()) {
X = isImmediate ? address : memory.ReadByte(address); X = isImmediate ? address : memory.ReadByte(address);
SetZeroFlag(X == 0); SetZeroFlag(X == 0);
@@ -389,7 +389,7 @@ void CPU::LDX(uint16_t address, bool isImmediate) {
} }
} }
void CPU::LDY(uint16_t address, bool isImmediate) { void Cpu::LDY(uint16_t address, bool isImmediate) {
if (GetIndexSize()) { if (GetIndexSize()) {
Y = isImmediate ? address : memory.ReadByte(address); Y = isImmediate ? address : memory.ReadByte(address);
SetZeroFlag(Y == 0); SetZeroFlag(Y == 0);
@@ -401,7 +401,7 @@ void CPU::LDY(uint16_t address, bool isImmediate) {
} }
} }
void CPU::LSR(uint16_t address, bool accumulator) { void Cpu::LSR(uint16_t address, bool accumulator) {
if (accumulator) { if (accumulator) {
if (GetAccumulatorSize()) { // 8-bit if (GetAccumulatorSize()) { // 8-bit
SetCarryFlag(A & 0x01); SetCarryFlag(A & 0x01);
@@ -424,7 +424,7 @@ void CPU::LSR(uint16_t address, bool accumulator) {
SetZeroFlag(value == 0); SetZeroFlag(value == 0);
} }
void CPU::MVN(uint16_t source, uint16_t dest, uint16_t length) { void Cpu::MVN(uint16_t source, uint16_t dest, uint16_t length) {
for (uint16_t i = 0; i < length; i++) { for (uint16_t i = 0; i < length; i++) {
memory.WriteByte(dest, memory.ReadByte(source)); memory.WriteByte(dest, memory.ReadByte(source));
source++; source++;
@@ -432,7 +432,7 @@ void CPU::MVN(uint16_t source, uint16_t dest, uint16_t length) {
} }
} }
void CPU::MVP(uint16_t source, uint16_t dest, uint16_t length) { void Cpu::MVP(uint16_t source, uint16_t dest, uint16_t length) {
for (uint16_t i = 0; i < length; i++) { for (uint16_t i = 0; i < length; i++) {
memory.WriteByte(dest, memory.ReadByte(source)); memory.WriteByte(dest, memory.ReadByte(source));
source--; source--;
@@ -440,11 +440,11 @@ void CPU::MVP(uint16_t source, uint16_t dest, uint16_t length) {
} }
} }
void CPU::NOP() { void Cpu::NOP() {
// Do nothing // Do nothing
} }
void CPU::ORA(uint16_t address, bool isImmediate) { void Cpu::ORA(uint16_t address, bool isImmediate) {
if (GetAccumulatorSize()) { if (GetAccumulatorSize()) {
A |= isImmediate ? address : memory.ReadByte(address); A |= isImmediate ? address : memory.ReadByte(address);
SetZeroFlag(A == 0); SetZeroFlag(A == 0);
@@ -456,22 +456,22 @@ void CPU::ORA(uint16_t address, bool isImmediate) {
} }
} }
void CPU::PEA() { void Cpu::PEA() {
uint16_t address = FetchWord(); uint16_t address = FetchWord();
memory.PushWord(address); memory.PushWord(address);
} }
void CPU::PEI() { void Cpu::PEI() {
uint16_t address = FetchWord(); uint16_t address = FetchWord();
memory.PushWord(memory.ReadWord(address)); memory.PushWord(memory.ReadWord(address));
} }
void CPU::PER() { void Cpu::PER() {
uint16_t address = FetchWord(); uint16_t address = FetchWord();
memory.PushWord(PC + address); memory.PushWord(PC + address);
} }
void CPU::PHA() { void Cpu::PHA() {
if (GetAccumulatorSize()) { if (GetAccumulatorSize()) {
memory.PushByte(static_cast<uint8_t>(A)); memory.PushByte(static_cast<uint8_t>(A));
} else { } else {
@@ -479,15 +479,15 @@ void CPU::PHA() {
} }
} }
void CPU::PHB() { memory.PushByte(DB); } void Cpu::PHB() { memory.PushByte(DB); }
void CPU::PHD() { memory.PushWord(D); } void Cpu::PHD() { memory.PushWord(D); }
void CPU::PHK() { memory.PushByte(PB); } void Cpu::PHK() { memory.PushByte(PB); }
void CPU::PHP() { memory.PushByte(status); } void Cpu::PHP() { memory.PushByte(status); }
void CPU::PHX() { void Cpu::PHX() {
if (GetIndexSize()) { if (GetIndexSize()) {
memory.PushByte(static_cast<uint8_t>(X)); memory.PushByte(static_cast<uint8_t>(X));
} else { } else {
@@ -495,7 +495,7 @@ void CPU::PHX() {
} }
} }
void CPU::PHY() { void Cpu::PHY() {
if (GetIndexSize()) { if (GetIndexSize()) {
memory.PushByte(static_cast<uint8_t>(Y)); memory.PushByte(static_cast<uint8_t>(Y));
} else { } else {
@@ -503,7 +503,7 @@ void CPU::PHY() {
} }
} }
void CPU::PLA() { void Cpu::PLA() {
if (GetAccumulatorSize()) { if (GetAccumulatorSize()) {
A = memory.PopByte(); A = memory.PopByte();
SetNegativeFlag((A & 0x80) != 0); SetNegativeFlag((A & 0x80) != 0);
@@ -514,23 +514,23 @@ void CPU::PLA() {
SetZeroFlag(A == 0); SetZeroFlag(A == 0);
} }
void CPU::PLB() { void Cpu::PLB() {
DB = memory.PopByte(); DB = memory.PopByte();
SetNegativeFlag((DB & 0x80) != 0); SetNegativeFlag((DB & 0x80) != 0);
SetZeroFlag(DB == 0); SetZeroFlag(DB == 0);
} }
// Pull Direct Page Register from Stack // Pull Direct Page Register from Stack
void CPU::PLD() { void Cpu::PLD() {
D = memory.PopWord(); D = memory.PopWord();
SetNegativeFlag((D & 0x8000) != 0); SetNegativeFlag((D & 0x8000) != 0);
SetZeroFlag(D == 0); SetZeroFlag(D == 0);
} }
// Pull Processor Status Register from Stack // Pull Processor Status Register from Stack
void CPU::PLP() { status = memory.PopByte(); } void Cpu::PLP() { status = memory.PopByte(); }
void CPU::PLX() { void Cpu::PLX() {
if (GetIndexSize()) { if (GetIndexSize()) {
X = memory.PopByte(); X = memory.PopByte();
SetNegativeFlag((A & 0x80) != 0); SetNegativeFlag((A & 0x80) != 0);
@@ -542,7 +542,7 @@ void CPU::PLX() {
SetZeroFlag(X == 0); SetZeroFlag(X == 0);
} }
void CPU::PLY() { void Cpu::PLY() {
if (GetIndexSize()) { if (GetIndexSize()) {
Y = memory.PopByte(); Y = memory.PopByte();
SetNegativeFlag((A & 0x80) != 0); SetNegativeFlag((A & 0x80) != 0);
@@ -553,12 +553,12 @@ void CPU::PLY() {
SetZeroFlag(Y == 0); SetZeroFlag(Y == 0);
} }
void CPU::REP() { void Cpu::REP() {
auto byte = FetchByte(); auto byte = FetchByte();
status &= ~byte; status &= ~byte;
} }
void CPU::ROL(uint32_t address, bool accumulator) { void Cpu::ROL(uint32_t address, bool accumulator) {
if (accumulator) { if (accumulator) {
if (GetAccumulatorSize()) { // 8-bit if (GetAccumulatorSize()) { // 8-bit
uint8_t carry = GetCarryFlag() ? 0x01 : 0x00; uint8_t carry = GetCarryFlag() ? 0x01 : 0x00;
@@ -588,7 +588,7 @@ void CPU::ROL(uint32_t address, bool accumulator) {
SetZeroFlag(value == 0); SetZeroFlag(value == 0);
} }
void CPU::ROR(uint32_t address, bool accumulator) { void Cpu::ROR(uint32_t address, bool accumulator) {
if (accumulator) { if (accumulator) {
if (GetAccumulatorSize()) { // 8-bit if (GetAccumulatorSize()) { // 8-bit
uint8_t carry = GetCarryFlag() ? 0x80 : 0x00; uint8_t carry = GetCarryFlag() ? 0x80 : 0x00;
@@ -618,21 +618,21 @@ void CPU::ROR(uint32_t address, bool accumulator) {
SetZeroFlag(value == 0); SetZeroFlag(value == 0);
} }
void CPU::RTI() { void Cpu::RTI() {
status = memory.PopByte(); status = memory.PopByte();
PC = memory.PopWord(); PC = memory.PopWord();
} }
void CPU::RTL() { void Cpu::RTL() {
next_pc_ = memory.PopWord(); next_pc_ = memory.PopWord();
PB = memory.PopByte(); PB = memory.PopByte();
} }
void CPU::RTS() { void Cpu::RTS() {
last_call_frame_ = memory.PopWord(); last_call_frame_ = memory.PopWord();
} }
void CPU::SBC(uint32_t value, bool isImmediate) { void Cpu::SBC(uint32_t value, bool isImmediate) {
uint16_t operand; uint16_t operand;
if (!GetAccumulatorSize()) { // 16-bit mode if (!GetAccumulatorSize()) { // 16-bit mode
operand = isImmediate ? value : memory.ReadWord(value); operand = isImmediate ? value : memory.ReadWord(value);
@@ -665,18 +665,18 @@ void CPU::SBC(uint32_t value, bool isImmediate) {
} }
} }
void CPU::SEC() { status |= 0x01; } void Cpu::SEC() { status |= 0x01; }
void CPU::SED() { status |= 0x08; } void Cpu::SED() { status |= 0x08; }
void CPU::SEI() { status |= 0x04; } void Cpu::SEI() { status |= 0x04; }
void CPU::SEP() { void Cpu::SEP() {
auto byte = FetchByte(); auto byte = FetchByte();
status |= byte; status |= byte;
} }
void CPU::STA(uint32_t address) { void Cpu::STA(uint32_t address) {
if (GetAccumulatorSize()) { if (GetAccumulatorSize()) {
memory.WriteByte(address, static_cast<uint8_t>(A)); memory.WriteByte(address, static_cast<uint8_t>(A));
} else { } else {
@@ -686,12 +686,12 @@ void CPU::STA(uint32_t address) {
// TODO: Make this work with the Clock class of the CPU // TODO: Make this work with the Clock class of the CPU
void CPU::STP() { void Cpu::STP() {
// During the next phase 2 clock cycle, stop the processors oscillator input // During the next phase 2 clock cycle, stop the processors oscillator input
// The processor is effectively shut down until a reset occurs (RES` pin). // The processor is effectively shut down until a reset occurs (RES` pin).
} }
void CPU::STX(uint16_t address) { void Cpu::STX(uint16_t address) {
if (GetIndexSize()) { if (GetIndexSize()) {
memory.WriteByte(address, static_cast<uint8_t>(X)); memory.WriteByte(address, static_cast<uint8_t>(X));
} else { } else {
@@ -699,7 +699,7 @@ void CPU::STX(uint16_t address) {
} }
} }
void CPU::STY(uint16_t address) { void Cpu::STY(uint16_t address) {
if (GetIndexSize()) { if (GetIndexSize()) {
memory.WriteByte(address, static_cast<uint8_t>(Y)); memory.WriteByte(address, static_cast<uint8_t>(Y));
} else { } else {
@@ -707,7 +707,7 @@ void CPU::STY(uint16_t address) {
} }
} }
void CPU::STZ(uint16_t address) { void Cpu::STZ(uint16_t address) {
if (GetAccumulatorSize()) { if (GetAccumulatorSize()) {
memory.WriteByte(address, 0x00); memory.WriteByte(address, 0x00);
} else { } else {
@@ -715,79 +715,79 @@ void CPU::STZ(uint16_t address) {
} }
} }
void CPU::TAX() { void Cpu::TAX() {
X = A; X = A;
SetZeroFlag(X == 0); SetZeroFlag(X == 0);
SetNegativeFlag(X & 0x80); SetNegativeFlag(X & 0x80);
} }
void CPU::TAY() { void Cpu::TAY() {
Y = A; Y = A;
SetZeroFlag(Y == 0); SetZeroFlag(Y == 0);
SetNegativeFlag(Y & 0x80); SetNegativeFlag(Y & 0x80);
} }
void CPU::TCD() { void Cpu::TCD() {
D = A; D = A;
SetZeroFlag(D == 0); SetZeroFlag(D == 0);
SetNegativeFlag(D & 0x80); SetNegativeFlag(D & 0x80);
} }
void CPU::TCS() { memory.SetSP(A); } void Cpu::TCS() { memory.SetSP(A); }
void CPU::TDC() { void Cpu::TDC() {
A = D; A = D;
SetZeroFlag(A == 0); SetZeroFlag(A == 0);
SetNegativeFlag(A & 0x80); SetNegativeFlag(A & 0x80);
} }
void CPU::TRB(uint16_t address) { void Cpu::TRB(uint16_t address) {
uint8_t value = memory.ReadByte(address); uint8_t value = memory.ReadByte(address);
SetZeroFlag((A & value) == 0); SetZeroFlag((A & value) == 0);
value &= ~A; value &= ~A;
memory.WriteByte(address, value); memory.WriteByte(address, value);
} }
void CPU::TSB(uint16_t address) { void Cpu::TSB(uint16_t address) {
uint8_t value = memory.ReadByte(address); uint8_t value = memory.ReadByte(address);
SetZeroFlag((A & value) == 0); SetZeroFlag((A & value) == 0);
value |= A; value |= A;
memory.WriteByte(address, value); memory.WriteByte(address, value);
} }
void CPU::TSC() { void Cpu::TSC() {
A = SP(); A = SP();
SetZeroFlag(A == 0); SetZeroFlag(A == 0);
SetNegativeFlag(A & 0x80); SetNegativeFlag(A & 0x80);
} }
void CPU::TSX() { void Cpu::TSX() {
X = SP(); X = SP();
SetZeroFlag(X == 0); SetZeroFlag(X == 0);
SetNegativeFlag(X & 0x80); SetNegativeFlag(X & 0x80);
} }
void CPU::TXA() { void Cpu::TXA() {
A = X; A = X;
SetZeroFlag(A == 0); SetZeroFlag(A == 0);
SetNegativeFlag(A & 0x80); SetNegativeFlag(A & 0x80);
} }
void CPU::TXS() { memory.SetSP(X); } void Cpu::TXS() { memory.SetSP(X); }
void CPU::TXY() { void Cpu::TXY() {
Y = X; Y = X;
SetZeroFlag(X == 0); SetZeroFlag(X == 0);
SetNegativeFlag(X & 0x80); SetNegativeFlag(X & 0x80);
} }
void CPU::TYA() { void Cpu::TYA() {
A = Y; A = Y;
SetZeroFlag(A == 0); SetZeroFlag(A == 0);
SetNegativeFlag(A & 0x80); SetNegativeFlag(A & 0x80);
} }
void CPU::TYX() { void Cpu::TYX() {
X = Y; X = Y;
SetZeroFlag(Y == 0); SetZeroFlag(Y == 0);
SetNegativeFlag(Y & 0x80); SetNegativeFlag(Y & 0x80);
@@ -795,20 +795,20 @@ void CPU::TYX() {
// TODO: Make this communicate with the SNES class // TODO: Make this communicate with the SNES class
void CPU::WAI() { void Cpu::WAI() {
// Pull the RDY pin low // Pull the RDY pin low
// Power consumption is reduced(?) // Power consumption is reduced(?)
// RDY remains low until an external hardware interupt // RDY remains low until an external hardware interupt
// (NMI, IRQ, ABORT, or RESET) is received from the SNES class // (NMI, IRQ, ABORT, or RESET) is received from the SNES class
} }
void CPU::XBA() { void Cpu::XBA() {
uint8_t lowByte = A & 0xFF; uint8_t lowByte = A & 0xFF;
uint8_t highByte = (A >> 8) & 0xFF; uint8_t highByte = (A >> 8) & 0xFF;
A = (lowByte << 8) | highByte; A = (lowByte << 8) | highByte;
} }
void CPU::XCE() { void Cpu::XCE() {
uint8_t carry = status & 0x01; uint8_t carry = status & 0x01;
status &= ~0x01; status &= ~0x01;
status |= E; status |= E;

View File

@@ -13,7 +13,7 @@ class Debugger {
public: public:
Debugger() = default; Debugger() = default;
// Attach the debugger to the emulator // Attach the debugger to the emulator
// Debugger(CPU &cpu, PPU &ppu, APU &apu); // Debugger(CPU &cpu, PPU &ppu, Apu &apu);
// Set a breakpoint // Set a breakpoint
void SetBreakpoint(uint16_t address); void SetBreakpoint(uint16_t address);
@@ -43,7 +43,7 @@ class Debugger {
// References to the emulator's components // References to the emulator's components
// CPU &cpu; // CPU &cpu;
// PPU &ppu; // PPU &ppu;
// APU &apu; // Apu &apu;
// Breakpoints, watchpoints, etc. // Breakpoints, watchpoints, etc.
// ... // ...

View File

@@ -33,7 +33,7 @@ using ImGui::TableNextColumn;
using ImGui::Text; using ImGui::Text;
void Emulator::Run() { void Emulator::Run() {
if (!snes_.running() && rom()->isLoaded()) { if (!snes_.running() && rom()->is_loaded()) {
snes_.SetupMemory(*rom()); snes_.SetupMemory(*rom());
snes_.Init(*rom()); snes_.Init(*rom());
} }
@@ -259,7 +259,7 @@ void Emulator::RenderBreakpointList() {
} }
} }
void Emulator::RenderCpuState(CPU& cpu) { void Emulator::RenderCpuState(Cpu& cpu) {
if (ImGui::CollapsingHeader("Register Values", if (ImGui::CollapsingHeader("Register Values",
ImGuiTreeNodeFlags_DefaultOpen)) { ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Columns(2, "RegistersColumns"); ImGui::Columns(2, "RegistersColumns");

View File

@@ -11,9 +11,18 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
/**
* @namespace yaze::app::emu
* @brief SNES Emulation and debugging tools.
*/
namespace emu { namespace emu {
class Emulator : public SharedROM { /**
* @class Emulator
* @brief A class for emulating and debugging SNES games.
*/
class Emulator : public SharedRom {
public: public:
void Run(); void Run();
@@ -24,7 +33,7 @@ class Emulator : public SharedROM {
void RenderEmulator(); void RenderEmulator();
void RenderSnesPpu(); void RenderSnesPpu();
void RenderBreakpointList(); void RenderBreakpointList();
void RenderCpuState(CPU& cpu); void RenderCpuState(Cpu& cpu);
void RenderMemoryViewer(); void RenderMemoryViewer();
struct Bookmark { struct Bookmark {

View File

@@ -5,8 +5,9 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace memory {
void DMA::StartDMATransfer(uint8_t channelMask) { void DirectMemoryAccess::StartDMATransfer(uint8_t channelMask) {
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
if ((channelMask & (1 << i)) != 0) { if ((channelMask & (1 << i)) != 0) {
Channel& ch = channels[i]; Channel& ch = channels[i];
@@ -20,8 +21,9 @@ void DMA::StartDMATransfer(uint8_t channelMask) {
// Determine the transfer size based on the DMAPn register // Determine the transfer size based on the DMAPn register
bool transferTwoBytes = (ch.DMAPn & 0x40) != 0; bool transferTwoBytes = (ch.DMAPn & 0x40) != 0;
// Perform the DMA transfer based on the channel parameters // Perform the DirectMemoryAccess transfer based on the channel parameters
std::cout << "Starting DMA transfer for channel " << i << std::endl; std::cout << "Starting DirectMemoryAccess transfer for channel " << i
<< std::endl;
for (uint16_t j = 0; j < ch.DASn; ++j) { for (uint16_t j = 0; j < ch.DASn; ++j) {
// Read a byte or two bytes from memory based on the transfer size // Read a byte or two bytes from memory based on the transfer size
@@ -46,7 +48,7 @@ void DMA::StartDMATransfer(uint8_t channelMask) {
MDMAEN = channelMask; // Set the MDMAEN register to the channel mask MDMAEN = channelMask; // Set the MDMAEN register to the channel mask
} }
void DMA::EnableHDMATransfers(uint8_t channelMask) { void DirectMemoryAccess::EnableHDMATransfers(uint8_t channelMask) {
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
if ((channelMask & (1 << i)) != 0) { if ((channelMask & (1 << i)) != 0) {
Channel& ch = channels[i]; Channel& ch = channels[i];
@@ -70,6 +72,7 @@ void DMA::EnableHDMATransfers(uint8_t channelMask) {
HDMAEN = channelMask; // Set the HDMAEN register to the channel mask HDMAEN = channelMask; // Set the HDMAEN register to the channel mask
} }
} // namespace memory
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -6,11 +6,11 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace memory {
// Direct Memory Address class DirectMemoryAccess {
class DMA {
public: public:
DMA() { DirectMemoryAccess() {
// Initialize DMA and HDMA channels // Initialize DMA and HDMA channels
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
channels[i].DMAPn = 0; channels[i].DMAPn = 0;
@@ -52,6 +52,8 @@ class DMA {
uint8_t MDMAEN = 0; // Start DMA transfer uint8_t MDMAEN = 0; // Start DMA transfer
uint8_t HDMAEN = 0; // Enable HDMA transfers uint8_t HDMAEN = 0; // Enable HDMA transfers
}; };
} // namespace memory
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -12,6 +12,7 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace memory {
void DrawSnesMemoryMapping(const MemoryImpl& memory) { void DrawSnesMemoryMapping(const MemoryImpl& memory) {
// Using those as a base value to create width/height that are factor of the // Using those as a base value to create width/height that are factor of the
@@ -77,6 +78,7 @@ void DrawSnesMemoryMapping(const MemoryImpl& memory) {
} }
} }
} // namespace memory
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -28,12 +28,13 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace memory {
enum ROMSpeed { SLOW_ROM = 0x00, FAST_ROM = 0x07 }; enum RomSpeed { SLOW_ROM = 0x00, FAST_ROM = 0x07 };
enum BankSize { LOW_ROM = 0x00, HI_ROM = 0x01 }; enum BankSize { LOW_ROM = 0x00, HI_ROM = 0x01 };
enum ROMType { enum RomType {
ROM_DEFAULT = 0x00, ROM_DEFAULT = 0x00,
ROM_RAM = 0x01, ROM_RAM = 0x01,
ROM_SRAM = 0x02, ROM_SRAM = 0x02,
@@ -43,7 +44,7 @@ enum ROMType {
FX = 0x06 FX = 0x06
}; };
enum ROMSize { enum RomSize {
SIZE_2_MBIT = 0x08, SIZE_2_MBIT = 0x08,
SIZE_4_MBIT = 0x09, SIZE_4_MBIT = 0x09,
SIZE_8_MBIT = 0x0A, SIZE_8_MBIT = 0x0A,
@@ -51,7 +52,7 @@ enum ROMSize {
SIZE_32_MBIT = 0x0C SIZE_32_MBIT = 0x0C
}; };
enum SRAMSize { enum SramSize {
NO_SRAM = 0x00, NO_SRAM = 0x00,
SRAM_16_KBIT = 0x01, SRAM_16_KBIT = 0x01,
SRAM_32_KBIT = 0x02, SRAM_32_KBIT = 0x02,
@@ -73,14 +74,14 @@ enum License {
// ... and other licenses // ... and other licenses
}; };
class ROMInfo { class RomInfo {
public: public:
std::string title; std::string title;
ROMSpeed romSpeed; RomSpeed romSpeed;
BankSize bankSize; BankSize bankSize;
ROMType romType; RomType romType;
ROMSize romSize; RomSize romSize;
SRAMSize sramSize; SramSize sramSize;
CountryCode countryCode; CountryCode countryCode;
License license; License license;
uint8_t version; uint8_t version;
@@ -105,7 +106,9 @@ constexpr uint32_t kVRAMSize = 0x10000;
constexpr uint32_t kOAMStart = 0x218000; constexpr uint32_t kOAMStart = 0x218000;
constexpr uint32_t kOAMSize = 0x220; constexpr uint32_t kOAMSize = 0x220;
// memory.h /**
* @brief Memory interface
*/
class Memory { class Memory {
public: public:
virtual ~Memory() = default; virtual ~Memory() = default;
@@ -137,6 +140,29 @@ class Memory {
enum class MemoryMapping { SNES_LOROM = 0, PC_ADDRESS = 1 }; enum class MemoryMapping { SNES_LOROM = 0, PC_ADDRESS = 1 };
/**
* @class MemoryImpl
* @brief Implementation of the Memory interface for emulating memory in a SNES
* system.
*
* The MemoryImpl class provides methods for initializing and accessing memory
* in a SNES system. It implements the Memory interface and inherits from the
* Loggable class.
*
* The class supports different memory mappings, including LoROM and PC_ADDRESS
* mappings. It provides methods for reading and writing bytes, words, and longs
* from/to memory. It also supports stack operations for pushing and popping
* values.
*
* The class maintains separate vectors for ROM, RAM, VRAM, and OAM memory
* regions. It provides methods for accessing these memory regions and
* retrieving their sizes.
*
* The class also allows adding observers to be notified when memory is read or
* written.
*
* @note This class assumes a 16-bit address space.
*/
class MemoryImpl : public Memory, public Loggable { class MemoryImpl : public Memory, public Loggable {
public: public:
void Initialize(const std::vector<uint8_t>& romData, bool verbose = false, void Initialize(const std::vector<uint8_t>& romData, bool verbose = false,
@@ -396,6 +422,7 @@ class MemoryImpl : public Memory, public Loggable {
void DrawSnesMemoryMapping(const MemoryImpl& memory); void DrawSnesMemoryMapping(const MemoryImpl& memory);
} // namespace memory
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -8,10 +8,14 @@
#include "app/emu/cpu/cpu.h" #include "app/emu/cpu/cpu.h"
#include "app/emu/memory/memory.h" #include "app/emu/memory/memory.h"
using yaze::app::emu::Clock; namespace yaze {
using yaze::app::emu::CPU; namespace app {
using yaze::app::emu::Memory; namespace emu {
namespace memory {
/**
* @brief Mock CPU class for testing
*/
class MockClock : public Clock { class MockClock : public Clock {
public: public:
MOCK_METHOD(void, UpdateClock, (double delta), (override)); MOCK_METHOD(void, UpdateClock, (double delta), (override));
@@ -21,9 +25,23 @@ class MockClock : public Clock {
MOCK_METHOD(float, GetFrequency, (), (const, override)); MOCK_METHOD(float, GetFrequency, (), (const, override));
}; };
// 0x1000000 is 16 MB, simplifying the memory layout for testing /**
// 2 MB is = 0x200000 * @class MockMemory
* @brief A mock implementation of the Memory class.
*
* This class is used for testing purposes and provides a mock implementation of
* the Memory class. It allows for reading and writing bytes, words, and longs
* to memory, as well as pushing and popping values onto and from the stack. It
* also provides methods for setting and getting the stack pointer, initializing
* memory with ROM data, and clearing memory.
*
* The mock memory is represented by a vector of uint8_t values, where each
* element represents a byte of memory. The memory can be accessed using the []
* operator, and its contents can be set using the SetMemoryContents() method.
*
* @note This class is intended for testing purposes only and should not be used
* in production code.
*/
class MockMemory : public Memory { class MockMemory : public Memory {
public: public:
MOCK_CONST_METHOD1(ReadByte, uint8_t(uint32_t address)); MOCK_CONST_METHOD1(ReadByte, uint8_t(uint32_t address));
@@ -85,6 +103,8 @@ class MockMemory : public Memory {
} }
} }
// 16MB = 0x1000000
// 02MB = 0x200000
void Initialize(const std::vector<uint8_t>& romData) { void Initialize(const std::vector<uint8_t>& romData) {
// 16 MB, simplifying the memory layout for testing // 16 MB, simplifying the memory layout for testing
memory_.resize(0x1000000); memory_.resize(0x1000000);
@@ -186,4 +206,9 @@ class MockMemory : public Memory {
uint16_t SP_ = 0x01FF; uint16_t SP_ = 0x01FF;
}; };
} // namespace memory
} // namespace emu
} // namespace app
} // namespace yaze
#endif // YAZE_TEST_MOCK_MOCK_MEMORY_H #endif // YAZE_TEST_MOCK_MOCK_MEMORY_H

View File

@@ -46,7 +46,7 @@ uint16_t GetHeaderOffset(const Memory& memory) {
} }
void audio_callback(void* userdata, uint8_t* stream, int len) { void audio_callback(void* userdata, uint8_t* stream, int len) {
auto* apu = static_cast<APU*>(userdata); auto* apu = static_cast<audio::Apu*>(userdata);
auto* buffer = reinterpret_cast<int16_t*>(stream); auto* buffer = reinterpret_cast<int16_t*>(stream);
for (int i = 0; i < len / 2; i++) { // Assuming 16-bit samples for (int i = 0; i < len / 2; i++) { // Assuming 16-bit samples
@@ -57,8 +57,8 @@ void audio_callback(void* userdata, uint8_t* stream, int len) {
} // namespace } // namespace
ROMInfo SNES::ReadRomHeader(uint32_t offset) { RomInfo SNES::ReadRomHeader(uint32_t offset) {
ROMInfo romInfo; RomInfo romInfo;
// Read cartridge title // Read cartridge title
char title[22]; char title[22];
@@ -70,17 +70,17 @@ ROMInfo SNES::ReadRomHeader(uint32_t offset) {
// Read ROM speed and memory map mode // Read ROM speed and memory map mode
uint8_t romSpeedAndMapMode = cpu_.ReadByte(offset + 0x15); uint8_t romSpeedAndMapMode = cpu_.ReadByte(offset + 0x15);
romInfo.romSpeed = (ROMSpeed)(romSpeedAndMapMode & 0x07); romInfo.romSpeed = (RomSpeed)(romSpeedAndMapMode & 0x07);
romInfo.bankSize = (BankSize)((romSpeedAndMapMode >> 5) & 0x01); romInfo.bankSize = (BankSize)((romSpeedAndMapMode >> 5) & 0x01);
// Read ROM type // Read ROM type
romInfo.romType = (ROMType)cpu_.ReadByte(offset + 0x16); romInfo.romType = (RomType)cpu_.ReadByte(offset + 0x16);
// Read ROM size // Read ROM size
romInfo.romSize = (ROMSize)cpu_.ReadByte(offset + 0x17); romInfo.romSize = (RomSize)cpu_.ReadByte(offset + 0x17);
// Read RAM size // Read RAM size
romInfo.sramSize = (SRAMSize)cpu_.ReadByte(offset + 0x18); romInfo.sramSize = (SramSize)cpu_.ReadByte(offset + 0x18);
// Read country code // Read country code
romInfo.countryCode = (CountryCode)cpu_.ReadByte(offset + 0x19); romInfo.countryCode = (CountryCode)cpu_.ReadByte(offset + 0x19);
@@ -106,7 +106,7 @@ ROMInfo SNES::ReadRomHeader(uint32_t offset) {
return romInfo; return romInfo;
} }
void SNES::Init(ROM& rom) { void SNES::Init(Rom& rom) {
// Perform a long jump into a FastROM bank (if the ROM speed is FastROM) // Perform a long jump into a FastROM bank (if the ROM speed is FastROM)
// Disable the emulation flag (switch to 65816 native mode) // Disable the emulation flag (switch to 65816 native mode)
cpu_.E = 0; cpu_.E = 0;
@@ -257,7 +257,7 @@ void SNES::Run() {
void SNES::StepRun() { void SNES::StepRun() {
// Update the CPU // Update the CPU
cpu_.UpdateClock(0.0); cpu_.UpdateClock(0.0);
cpu_.Update(CPU::UpdateMode::Step); cpu_.Update(Cpu::UpdateMode::Step);
// Update the PPU // Update the PPU
ppu_.UpdateClock(0.0); ppu_.UpdateClock(0.0);

View File

@@ -20,15 +20,17 @@ namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
class SNES : public DMA { using namespace memory;
class SNES : public DirectMemoryAccess {
public: public:
SNES() = default; SNES() = default;
~SNES() = default; ~SNES() = default;
ROMInfo ReadRomHeader(uint32_t offset); RomInfo ReadRomHeader(uint32_t offset);
// Initialization // Initialization
void Init(ROM& rom); void Init(Rom& rom);
// Main emulation loop // Main emulation loop
void Run(); void Run();
@@ -61,16 +63,16 @@ class SNES : public DMA {
bool running() const { return running_; } bool running() const { return running_; }
auto cpu() -> CPU& { return cpu_; } auto cpu() -> Cpu& { return cpu_; }
auto ppu() -> Ppu& { return ppu_; } auto ppu() -> video::Ppu& { return ppu_; }
auto Memory() -> MemoryImpl* { return &memory_; } auto Memory() -> MemoryImpl* { return &memory_; }
void SetCpuMode(int mode) { cpu_mode_ = mode; } void SetCpuMode(int mode) { cpu_mode_ = mode; }
CPU::UpdateMode GetCpuMode() const { Cpu::UpdateMode GetCpuMode() const {
return static_cast<CPU::UpdateMode>(cpu_mode_); return static_cast<Cpu::UpdateMode>(cpu_mode_);
} }
void SetupMemory(ROM& rom) { void SetupMemory(Rom& rom) {
// Setup observers for the memory space // Setup observers for the memory space
memory_.AddObserver(&apu_); memory_.AddObserver(&apu_);
memory_.AddObserver(&ppu_); memory_.AddObserver(&ppu_);
@@ -88,14 +90,14 @@ class SNES : public DMA {
// Components of the SNES // Components of the SNES
MemoryImpl memory_; MemoryImpl memory_;
ClockImpl clock_; ClockImpl clock_;
AudioRamImpl audio_ram_; audio::AudioRamImpl audio_ram_;
CPU cpu_{memory_, clock_}; Cpu cpu_{memory_, clock_};
Ppu ppu_{memory_, clock_}; video::Ppu ppu_{memory_, clock_};
APU apu_{memory_, audio_ram_, clock_}; audio::Apu apu_{memory_, audio_ram_, clock_};
// Helper classes // Helper classes
ROMInfo rom_info_; RomInfo rom_info_;
Debugger debugger; Debugger debugger;
// Currently loaded ROM // Currently loaded ROM

View File

@@ -9,6 +9,7 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace video {
using namespace PpuRegisters; using namespace PpuRegisters;
@@ -56,8 +57,8 @@ void Ppu::RenderScanline() {
// Fetch the tile data from VRAM, tile map data from memory, and palette data // Fetch the tile data from VRAM, tile map data from memory, and palette data
// from CGRAM // from CGRAM
// UpdateTileData(); // Fetches the tile data from VRAM and stores it in an // UpdateTileData(); // Fetches the tile data from VRAM and stores it in
// internal buffer // an internal buffer
UpdateTileMapData(); // Fetches the tile map data from memory and stores it UpdateTileMapData(); // Fetches the tile map data from memory and stores it
// in an internal buffer // in an internal buffer
UpdatePaletteData(); // Fetches the palette data from CGRAM and stores it in UpdatePaletteData(); // Fetches the palette data from CGRAM and stores it in
@@ -424,12 +425,13 @@ void Ppu::ApplyEffects() {}
void Ppu::ComposeLayers() {} void Ppu::ComposeLayers() {}
void Ppu::DisplayFrameBuffer() { void Ppu::DisplayFrameBuffer() {
if (!screen_->IsActive()) { if (!screen_->is_active()) {
screen_->Create(256, 240, 24, frame_buffer_); screen_->Create(256, 240, 24, frame_buffer_);
rom()->RenderBitmap(screen_.get()); rom()->RenderBitmap(screen_.get());
} }
} }
} // namespace video
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -13,8 +13,10 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace video {
using namespace yaze::app::emu::PpuRegisters; using namespace PpuRegisters;
using namespace memory;
class PpuInterface { class PpuInterface {
public: public:
@@ -263,17 +265,17 @@ struct BackgroundLayer {
const int kPpuClockSpeed = 5369318; // 5.369318 MHz const int kPpuClockSpeed = 5369318; // 5.369318 MHz
class Ppu : public Observer, public SharedROM { class Ppu : public Observer, public SharedRom {
public: public:
// Initializes the PPU with the necessary resources and dependencies // Initializes the PPU with the necessary resources and dependencies
Ppu(Memory& memory, Clock& clock) : memory_(memory), clock_(clock) {} Ppu(memory::Memory& memory, Clock& clock) : memory_(memory), clock_(clock) {}
// Initialize the frame buffer // Initialize the frame buffer
void Init() { void Init() {
clock_.SetFrequency(kPpuClockSpeed); clock_.SetFrequency(kPpuClockSpeed);
frame_buffer_.resize(256 * 240, 0); frame_buffer_.resize(256 * 240, 0);
screen_ = std::make_shared<gfx::Bitmap>(256, 240, 8, 0x100); screen_ = std::make_shared<gfx::Bitmap>(256, 240, 8, 0x100);
screen_->SetActive(false); screen_->set_active(false);
} }
// Resets the PPU to its initial state // Resets the PPU to its initial state
@@ -386,6 +388,7 @@ class Ppu : public Observer, public SharedROM {
const int visibleScanlines = 224; // SNES PPU renders 224 visible scanlines const int visibleScanlines = 224; // SNES PPU renders 224 visible scanlines
}; };
} // namespace video
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -8,7 +8,7 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace video {
namespace PpuRegisters { namespace PpuRegisters {
constexpr uint16_t INIDISP = 0x2100; constexpr uint16_t INIDISP = 0x2100;
@@ -414,7 +414,7 @@ struct STAT78 {
}; };
} // namespace PpuRegisters } // namespace PpuRegisters
} // namespace video
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -30,6 +30,24 @@ void PngWriteCallback(png_structp png_ptr, png_bytep data, png_size_t length) {
p->insert(p->end(), data, data + length); p->insert(p->end(), data, data + length);
} }
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);
static size_t pos = 0; // Position to read from
if (pos + byteCountToRead <= png_data->size()) {
memcpy(outBytes, png_data->data() + pos, byteCountToRead);
pos += byteCountToRead;
} else {
png_error(png_ptr, "Read error in PngReadCallback");
}
}
} // namespace
bool ConvertSurfaceToPNG(SDL_Surface *surface, std::vector<uint8_t> &buffer) { bool ConvertSurfaceToPNG(SDL_Surface *surface, std::vector<uint8_t> &buffer) {
png_structp png_ptr = png_create_write_struct("1.6.40", NULL, NULL, NULL); png_structp png_ptr = png_create_write_struct("1.6.40", NULL, NULL, NULL);
if (!png_ptr) { if (!png_ptr) {
@@ -72,6 +90,10 @@ bool ConvertSurfaceToPNG(SDL_Surface *surface, std::vector<uint8_t> &buffer) {
free(pal_ptr); free(pal_ptr);
} }
if (surface->format->Amask) { // Check for alpha channel
colortype |= PNG_COLOR_MASK_ALPHA;
}
auto depth = surface->format->BitsPerPixel; auto depth = surface->format->BitsPerPixel;
// Set image attributes. // Set image attributes.
@@ -98,18 +120,6 @@ bool ConvertSurfaceToPNG(SDL_Surface *surface, std::vector<uint8_t> &buffer) {
return true; 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, void ConvertPngToSurface(const std::vector<uint8_t> &png_data,
SDL_Surface **outSurface) { SDL_Surface **outSurface) {
std::vector<uint8_t> data(png_data); std::vector<uint8_t> data(png_data);
@@ -140,59 +150,38 @@ void ConvertPngToSurface(const std::vector<uint8_t> &png_data,
png_byte color_type = png_get_color_type(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); 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 // Apply necessary transformations...
// palettes, etc. // (Same as in your existing code)
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 // Update info structure with transformations
png_read_update_info(png_ptr, info_ptr); png_read_update_info(png_ptr, info_ptr);
// Read the file // Read the file
std::vector<png_bytep> row_pointers(height);
std::vector<uint8_t> raw_data(width * height * std::vector<uint8_t> raw_data(width * height *
4); // Assuming 4 bytes per pixel (RGBA) 4); // Assuming 4 bytes per pixel (RGBA)
std::vector<png_bytep> row_pointers(height);
for (size_t y = 0; y < height; y++) { for (size_t y = 0; y < height; y++) {
row_pointers[y] = &raw_data[y * width * 4]; row_pointers[y] = raw_data.data() + y * width * 4;
} }
png_read_image(png_ptr, row_pointers.data()); png_read_image(png_ptr, row_pointers.data());
png_destroy_read_struct(&png_ptr, &info_ptr, NULL); png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
// Create SDL_Surface from raw pixel data // Create an SDL_Surface
*outSurface = SDL_CreateRGBSurfaceWithFormatFrom( *outSurface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32,
raw_data.data(), width, height, 32, width * 4, SDL_PIXELFORMAT_RGBA32); SDL_PIXELFORMAT_RGBA32);
if (*outSurface == nullptr) { if (*outSurface == nullptr) {
SDL_Log("SDL_CreateRGBSurfaceWithFormatFrom failed: %s\n", SDL_GetError()); SDL_Log("SDL_CreateRGBSurfaceWithFormat failed: %s\n", SDL_GetError());
} else { return;
SDL_Log("Successfully created SDL_Surface from PNG data");
} }
// Copy the raw data into the SDL_Surface
SDL_LockSurface(*outSurface);
memcpy((*outSurface)->pixels, raw_data.data(), raw_data.size());
SDL_UnlockSurface(*outSurface);
SDL_Log("Successfully created SDL_Surface from PNG data");
} }
} // namespace
Bitmap::Bitmap(int width, int height, int depth, int data_size) { Bitmap::Bitmap(int width, int height, int depth, int data_size) {
Create(width, height, depth, data_size); Create(width, height, depth, data_size);
@@ -267,7 +256,7 @@ void Bitmap::CreateTexture(SDL_Renderer *renderer) {
SDL_UnlockTexture(texture_.get()); SDL_UnlockTexture(texture_.get());
} }
void Bitmap::UpdateTexture(SDL_Renderer *renderer) { void Bitmap::UpdateTexture(SDL_Renderer *renderer, bool use_sdl_update) {
SDL_Surface *converted_surface = SDL_Surface *converted_surface =
SDL_ConvertSurfaceFormat(surface_.get(), SDL_PIXELFORMAT_ARGB8888, 0); SDL_ConvertSurfaceFormat(surface_.get(), SDL_PIXELFORMAT_ARGB8888, 0);
if (converted_surface) { if (converted_surface) {
@@ -282,8 +271,17 @@ void Bitmap::UpdateTexture(SDL_Renderer *renderer) {
SDL_LockTexture(texture_.get(), nullptr, (void **)&texture_pixels, SDL_LockTexture(texture_.get(), nullptr, (void **)&texture_pixels,
&converted_surface_->pitch); &converted_surface_->pitch);
memcpy(texture_pixels, converted_surface_->pixels, try {
converted_surface_->h * converted_surface_->pitch); if (use_sdl_update) {
SDL_UpdateTexture(texture_.get(), nullptr, converted_surface_->pixels,
converted_surface_->pitch);
} else {
memcpy(texture_pixels, converted_surface_->pixels,
converted_surface_->h * converted_surface_->pitch);
}
} catch (const std::exception &e) {
SDL_Log("Exception: %s\n", e.what());
}
SDL_UnlockTexture(texture_.get()); SDL_UnlockTexture(texture_.get());
} }
@@ -327,33 +325,76 @@ void Bitmap::LoadFromPngData(const std::vector<uint8_t> &png_data, int width,
} }
// Convert SNESPalette to SDL_Palette for surface. // Convert SNESPalette to SDL_Palette for surface.
void Bitmap::ApplyPalette(const SNESPalette &palette) { absl::Status Bitmap::ApplyPalette(const SnesPalette &palette) {
if (surface_ == nullptr) {
return absl::FailedPreconditionError("Surface is null");
}
if (surface_->format == nullptr || surface_->format->palette == nullptr) {
return absl::FailedPreconditionError("Surface format or palette is null");
}
palette_ = palette; palette_ = palette;
SDL_Palette *sdlPalette = surface_->format->palette;
if (sdlPalette == nullptr) {
return absl::InternalError("Failed to get SDL palette");
}
SDL_UnlockSurface(surface_.get()); SDL_UnlockSurface(surface_.get());
for (int i = 0; i < palette.size(); ++i) { for (int i = 0; i < palette.size(); ++i) {
if (palette.GetColor(i).IsTransparent()) { ASSIGN_OR_RETURN(gfx::SnesColor pal_color, palette.GetColor(i));
if (pal_color.is_transparent()) {
sdlPalette->colors[i].r = 0;
sdlPalette->colors[i].g = 0;
sdlPalette->colors[i].b = 0;
sdlPalette->colors[i].a = 0;
} else {
sdlPalette->colors[i].r = pal_color.rgb().x;
sdlPalette->colors[i].g = pal_color.rgb().y;
sdlPalette->colors[i].b = pal_color.rgb().z;
sdlPalette->colors[i].a = pal_color.rgb().w;
}
}
SDL_LockSurface(surface_.get());
return absl::OkStatus();
}
absl::Status Bitmap::ApplyPaletteFromPaletteGroup(const SnesPalette &palette,
int palette_id) {
auto start_index = palette_id * 8;
palette_ = palette.sub_palette(start_index, start_index + 8);
SDL_UnlockSurface(surface_.get());
for (int i = 0; i < palette_.size(); ++i) {
ASSIGN_OR_RETURN(auto pal_color, palette_.GetColor(i));
if (pal_color.is_transparent()) {
surface_->format->palette->colors[i].r = 0; surface_->format->palette->colors[i].r = 0;
surface_->format->palette->colors[i].g = 0; surface_->format->palette->colors[i].g = 0;
surface_->format->palette->colors[i].b = 0; surface_->format->palette->colors[i].b = 0;
surface_->format->palette->colors[i].a = 0; surface_->format->palette->colors[i].a = 0;
} else { } else {
surface_->format->palette->colors[i].r = palette.GetColor(i).GetRGB().x; surface_->format->palette->colors[i].r = pal_color.rgb().x;
surface_->format->palette->colors[i].g = palette.GetColor(i).GetRGB().y; surface_->format->palette->colors[i].g = pal_color.rgb().y;
surface_->format->palette->colors[i].b = palette.GetColor(i).GetRGB().z; surface_->format->palette->colors[i].b = pal_color.rgb().z;
surface_->format->palette->colors[i].a = palette.GetColor(i).GetRGB().w; surface_->format->palette->colors[i].a = pal_color.rgb().w;
} }
} }
SDL_LockSurface(surface_.get()); SDL_LockSurface(surface_.get());
return absl::OkStatus();
} }
void Bitmap::ApplyPaletteWithTransparent(const SNESPalette &palette, absl::Status Bitmap::ApplyPaletteWithTransparent(const SnesPalette &palette,
int index) { int index, int length) {
auto start_index = index * 7; auto start_index = index * 7;
palette_ = palette.sub_palette(start_index, start_index + 7); palette_ = palette.sub_palette(start_index, start_index + 7);
std::vector<ImVec4> colors; std::vector<ImVec4> colors;
colors.push_back(ImVec4(0, 0, 0, 0)); colors.push_back(ImVec4(0, 0, 0, 0));
for (int i = start_index; i < start_index + 7; ++i) { for (int i = start_index; i < start_index + 7; ++i) {
colors.push_back(palette.GetColor(i).GetRGB()); ASSIGN_OR_RETURN(auto pal_color, palette.GetColor(i));
colors.push_back(pal_color.rgb());
} }
SDL_UnlockSurface(surface_.get()); SDL_UnlockSurface(surface_.get());
@@ -366,6 +407,7 @@ void Bitmap::ApplyPaletteWithTransparent(const SNESPalette &palette,
i++; i++;
} }
SDL_LockSurface(surface_.get()); SDL_LockSurface(surface_.get());
return absl::OkStatus();
} }
void Bitmap::ApplyPalette(const std::vector<SDL_Color> &palette) { void Bitmap::ApplyPalette(const std::vector<SDL_Color> &palette) {

View File

@@ -15,8 +15,32 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
/**
* @namespace yaze::app::gfx
* @brief Contains classes for handling graphical data.
*/
namespace gfx { namespace gfx {
/**
* @brief Convert SDL_Surface to PNG image data.
*/
bool ConvertSurfaceToPNG(SDL_Surface *surface, std::vector<uint8_t> &buffer);
/**
* @brief Convert PNG image data to SDL_Surface.
*/
void ConvertPngToSurface(const std::vector<uint8_t> &png_data,
SDL_Surface **outSurface);
/**
* @brief Represents a bitmap image.
*
* The `Bitmap` class provides functionality to create, manipulate, and display
* bitmap images. It supports various operations such as creating a bitmap
* object, creating and updating textures, applying palettes, and accessing
* pixel data.
*/
class Bitmap { class Bitmap {
public: public:
Bitmap() = default; Bitmap() = default;
@@ -26,17 +50,44 @@ class Bitmap {
: width_(width), height_(height), depth_(depth), data_(data) { : width_(width), height_(height), depth_(depth), data_(data) {
InitializeFromData(width, height, depth, data); InitializeFromData(width, height, depth, data);
} }
Bitmap(int width, int height, int depth, const Bytes &data,
const SnesPalette &palette)
: width_(width),
height_(height),
depth_(depth),
data_(data),
palette_(palette) {
InitializeFromData(width, height, depth, data);
ApplyPalette(palette);
}
/**
* @brief Creates a bitmap object and reserves space for graphical data.
*/
void Create(int width, int height, int depth, int data_size); void Create(int width, int height, int depth, int data_size);
/**
* @brief Creates a bitmap object with the provided graphical data.
*/
void Create(int width, int height, int depth, const Bytes &data); void Create(int width, int height, int depth, const Bytes &data);
void InitializeFromData(uint32_t width, uint32_t height, uint32_t depth, void InitializeFromData(uint32_t width, uint32_t height, uint32_t depth,
const Bytes &data); const Bytes &data);
/**
* @brief Creates the underlying SDL_Texture to be displayed.
*
* Converts the surface from a RGB to ARGB format.
* Uses SDL_TEXTUREACCESS_STREAMING to allow for live updates.
*/
void CreateTexture(std::shared_ptr<SDL_Renderer> renderer); void CreateTexture(std::shared_ptr<SDL_Renderer> renderer);
/**
* @brief Updates the underlying SDL_Texture when it already exists.
*/
void UpdateTexture(std::shared_ptr<SDL_Renderer> renderer); void UpdateTexture(std::shared_ptr<SDL_Renderer> renderer);
void CreateTexture(SDL_Renderer *renderer); void CreateTexture(SDL_Renderer *renderer);
void UpdateTexture(SDL_Renderer *renderer); void UpdateTexture(SDL_Renderer *renderer, bool use_sdl_update = false);
void SaveSurfaceToFile(std::string_view filename); void SaveSurfaceToFile(std::string_view filename);
void SetSurface(SDL_Surface *surface); void SetSurface(SDL_Surface *surface);
@@ -44,9 +95,15 @@ class Bitmap {
void LoadFromPngData(const std::vector<uint8_t> &png_data, int width, void LoadFromPngData(const std::vector<uint8_t> &png_data, int width,
int height); int height);
void ApplyPalette(const SNESPalette &palette); /**
void ApplyPaletteWithTransparent(const SNESPalette &palette, int index); * @brief Copy color data from the SnesPalette into the SDL_Palette
*/
absl::Status ApplyPalette(const SnesPalette &palette);
absl::Status ApplyPaletteWithTransparent(const SnesPalette &palette,
int index, int length = 7);
void ApplyPalette(const std::vector<SDL_Color> &palette); void ApplyPalette(const std::vector<SDL_Color> &palette);
absl::Status ApplyPaletteFromPaletteGroup(const SnesPalette &palette,
int palette_id);
void WriteToPixel(int position, uchar value) { void WriteToPixel(int position, uchar value) {
if (pixel_data_ == nullptr) { if (pixel_data_ == nullptr) {
@@ -67,13 +124,13 @@ class Bitmap {
void Get8x8Tile(int tile_index, int x, int y, std::vector<uint8_t> &tile_data, void Get8x8Tile(int tile_index, int x, int y, std::vector<uint8_t> &tile_data,
int &tile_data_offset) { int &tile_data_offset) {
int tile_offset = tile_index * 64; int tile_offset = tile_index * (width_ * height_);
int tile_x = x * 8; int tile_x = (x * 8) % width_;
int tile_y = y * 8; int tile_y = (y * 8) % height_;
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
int row_offset = tile_offset + (i * 8); int row_offset = tile_offset + ((tile_y + i) * width_);
for (int j = 0; j < 8; j++) { for (int j = 0; j < 8; j++) {
int pixel_offset = row_offset + j; int pixel_offset = row_offset + (tile_x + j);
int pixel_value = data_[pixel_offset]; int pixel_value = data_[pixel_offset];
tile_data[tile_data_offset] = pixel_value; tile_data[tile_data_offset] = pixel_value;
tile_data_offset++; tile_data_offset++;
@@ -81,6 +138,41 @@ class Bitmap {
} }
} }
void Get16x16Tile(int tile_index, int x, int y,
std::vector<uint8_t> &tile_data, int &tile_data_offset) {
int tile_offset = tile_index * (width_ * height_);
int tile_x = x * 16;
int tile_y = y * 16;
for (int i = 0; i < 16; i++) {
int row_offset = tile_offset + ((i / 8) * (width_ * 8));
for (int j = 0; j < 16; j++) {
int pixel_offset =
row_offset + ((j / 8) * 8) + ((i % 8) * width_) + (j % 8);
int pixel_value = data_[pixel_offset];
tile_data[tile_data_offset] = pixel_value;
tile_data_offset++;
}
}
}
void Get16x16Tile(int tile_x, int tile_y, std::vector<uint8_t> &tile_data,
int &tile_data_offset) {
// Assuming 'width_' and 'height_' are the dimensions of the bitmap
// and 'data_' is the bitmap data.
for (int ty = 0; ty < 16; ty++) {
for (int tx = 0; tx < 16; tx++) {
// Calculate the pixel position in the bitmap
int pixel_x = tile_x + tx;
int pixel_y = tile_y + ty;
int pixel_offset = pixel_y * width_ + pixel_x;
int pixel_value = data_[pixel_offset];
// Store the pixel value in the tile data
tile_data[tile_data_offset++] = pixel_value;
}
}
}
void WriteColor(int position, const ImVec4 &color) { void WriteColor(int position, const ImVec4 &color) {
// Convert ImVec4 (RGBA) to SDL_Color (RGBA) // Convert ImVec4 (RGBA) to SDL_Color (RGBA)
SDL_Color sdl_color; SDL_Color sdl_color;
@@ -141,6 +233,8 @@ class Bitmap {
auto mutable_pixel_data() { return pixel_data_; } auto mutable_pixel_data() { return pixel_data_; }
auto surface() const { return surface_.get(); } auto surface() const { return surface_.get(); }
auto mutable_surface() { return surface_.get(); } auto mutable_surface() { return surface_.get(); }
auto converted_surface() const { return converted_surface_.get(); }
auto mutable_converted_surface() { return converted_surface_.get(); }
void set_data(const Bytes &data) { data_ = data; } void set_data(const Bytes &data) { data_ = data; }
auto vector() const { return data_; } auto vector() const { return data_; }
@@ -148,8 +242,8 @@ class Bitmap {
auto texture() const { return texture_.get(); } auto texture() const { return texture_.get(); }
auto modified() const { return modified_; } auto modified() const { return modified_; }
void set_modified(bool modified) { modified_ = modified; } void set_modified(bool modified) { modified_ = modified; }
auto IsActive() const { return active_; } auto is_active() const { return active_; }
auto SetActive(bool active) { active_ = active; } auto set_active(bool active) { active_ = active; }
private: private:
struct SDL_Texture_Deleter { struct SDL_Texture_Deleter {
@@ -186,7 +280,7 @@ class Bitmap {
std::vector<uint8_t> png_data_; std::vector<uint8_t> png_data_;
gfx::SNESPalette palette_; gfx::SnesPalette palette_;
std::shared_ptr<SDL_Texture> texture_ = nullptr; std::shared_ptr<SDL_Texture> texture_ = nullptr;
std::shared_ptr<SDL_Surface> surface_ = nullptr; std::shared_ptr<SDL_Surface> surface_ = nullptr;
std::shared_ptr<SDL_Surface> converted_surface_ = nullptr; std::shared_ptr<SDL_Surface> converted_surface_ = nullptr;
@@ -194,6 +288,9 @@ class Bitmap {
using BitmapTable = std::unordered_map<int, gfx::Bitmap>; using BitmapTable = std::unordered_map<int, gfx::Bitmap>;
/**
* @brief Hash map container of shared pointers to Bitmaps.
*/
class BitmapManager { class BitmapManager {
private: private:
std::unordered_map<int, std::shared_ptr<gfx::Bitmap>> bitmap_cache_; std::unordered_map<int, std::shared_ptr<gfx::Bitmap>> bitmap_cache_;
@@ -204,23 +301,24 @@ class BitmapManager {
std::make_shared<gfx::Bitmap>(width, height, depth, data); 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) { std::shared_ptr<gfx::Bitmap> const &operator[](int id) {
auto it = bitmap_cache_.find(id); auto it = bitmap_cache_.find(id);
if (it != bitmap_cache_.end()) { if (it != bitmap_cache_.end()) {
return it->second; return it->second;
} }
return nullptr; throw std::runtime_error(
absl::StrCat("Bitmap with id ", id, " not found."));
}
std::shared_ptr<gfx::Bitmap> const &shared_bitmap(int id) {
auto it = bitmap_cache_.find(id);
if (it != bitmap_cache_.end()) {
return it->second;
}
throw std::runtime_error(
absl::StrCat("Bitmap with id ", id, " not found."));
} }
auto mutable_bitmap(int id) { return bitmap_cache_[id]; } auto mutable_bitmap(int id) { return bitmap_cache_[id]; }
void clear_cache() { bitmap_cache_.clear(); }
using value_type = std::pair<const int, std::shared_ptr<gfx::Bitmap>>; using value_type = std::pair<const int, std::shared_ptr<gfx::Bitmap>>;
using iterator = using iterator =
@@ -234,16 +332,6 @@ class BitmapManager {
const_iterator end() const noexcept { return bitmap_cache_.end(); } const_iterator end() const noexcept { return bitmap_cache_.end(); }
const_iterator cbegin() const noexcept { return bitmap_cache_.cbegin(); } const_iterator cbegin() const noexcept { return bitmap_cache_.cbegin(); }
const_iterator cend() const noexcept { return bitmap_cache_.cend(); } 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 } // namespace gfx

View File

@@ -474,7 +474,7 @@ Bytes CreateCompressionString(CompressionPiecePointer& start, int mode) {
absl::Status ValidateCompressionResult(CompressionPiecePointer& chain_head, absl::Status ValidateCompressionResult(CompressionPiecePointer& chain_head,
int mode, int start, int src_data_pos) { int mode, int start, int src_data_pos) {
if (chain_head->next != nullptr) { if (chain_head->next != nullptr) {
ROM temp_rom; Rom temp_rom;
RETURN_IF_ERROR( RETURN_IF_ERROR(
temp_rom.LoadFromBytes(CreateCompressionString(chain_head->next, mode))) temp_rom.LoadFromBytes(CreateCompressionString(chain_head->next, mode)))
ASSIGN_OR_RETURN(auto decomp_data, ASSIGN_OR_RETURN(auto decomp_data,
@@ -588,6 +588,286 @@ absl::StatusOr<Bytes> CompressV2(const uchar* data, const int start,
return CreateCompressionString(compressed_chain_start->next, mode); return CreateCompressionString(compressed_chain_start->next, mode);
} }
// Hyrule Magic
uint8_t* Compress(uint8_t const* const src, int const oldsize, int* const size,
int const flag) {
unsigned char* b2 =
(unsigned char*)malloc(0x1000); // allocate a 2^12 sized buffer
int i, j, k, l, m = 0, n, o = 0, bd = 0, p, q = 0, r;
for (i = 0; i < oldsize;) {
l = src[i]; // grab a char from the buffer.
k = 0;
r = !!q; // r = the same logical value (0 or 1) as q, but not the same
// value necesarily.
for (j = 0; j < i - 1; j++) {
if (src[j] == l) {
m = oldsize - j;
for (n = 0; n < m; n++)
if (src[n + j] != src[n + i]) break;
if (n > k) k = n, o = j;
}
}
for (n = i + 1; n < oldsize; n++) {
if (src[n] != l) {
// look for chars identical to the first one.
// stop if we can't find one.
// n will reach i+k+1 for some k >= 0.
break;
}
}
n -= i; // offset back by i. i.e. n = k+1 as above.
if (n > 1 + r)
p = 1;
else {
m = src[i + 1];
for (n = i + 2; n < oldsize; n++) {
if (src[n] != l) break;
n++;
if (src[n] != m) break;
}
n -= i;
if (n > 2 + r)
p = 2;
else {
m = oldsize - i;
for (n = 1; n < m; n++)
if (src[i + n] != l + n) break;
if (n > 1 + r)
p = 3;
else
p = 0;
}
}
if (k > 3 + r && k > n + (p & 1)) p = 4, n = k;
if (!p)
q++, i++;
else {
if (q) {
q--;
if (q > 31) {
b2[bd++] = (unsigned char)(224 + (q >> 8));
}
b2[bd++] = (unsigned char)q;
q++;
memcpy(b2 + bd, src + i - q, q);
bd += q;
q = 0;
}
i += n;
n--;
if (n > 31) {
b2[bd++] = (unsigned char)(224 + (n >> 8) + (p << 2));
b2[bd++] = (unsigned char)n;
} else
b2[bd++] = (unsigned char)((p << 5) + n);
switch (p) {
case 1:
case 3:
b2[bd++] = (unsigned char)l;
break;
case 2:
b2[bd++] = (unsigned char)l;
b2[bd++] = (unsigned char)m;
break;
case 4:
if (flag) {
b2[bd++] = (unsigned char)(o >> 8);
b2[bd++] = (unsigned char)o;
} else {
b2[bd++] = (unsigned char)o;
b2[bd++] = (unsigned char)(o >> 8);
}
}
continue;
}
}
if (q) {
q--;
if (q > 31) {
b2[bd++] = (unsigned char)(224 + (q >> 8));
}
b2[bd++] = (unsigned char)q;
q++;
memcpy(b2 + bd, src + i - q, q);
bd += q;
}
b2[bd++] = 255;
b2 = (unsigned char*)realloc(b2, bd);
*size = bd;
return b2;
}
uint8_t* Uncompress(uint8_t const* src, int* const size,
int const p_big_endian) {
unsigned char* b2 = (unsigned char*)malloc(1024);
int bd = 0, bs = 1024;
unsigned char a;
unsigned char b;
unsigned short c, d;
for (;;) {
// retrieve a uchar from the buffer.
a = *(src++);
// end the decompression routine if we encounter 0xff.
if (a == 0xff) break;
// examine the top 3 bits of a.
b = (a >> 5);
if (b == 7) // i.e. 0b 111
{
// get bits 0b 0001 1100
b = ((a >> 2) & 7);
// get bits 0b 0000 0011, multiply by 256, OR with the next byte.
c = ((a & 0x0003) << 8);
c |= *(src++);
} else
// or get bits 0b 0001 1111
c = (uint16_t)(a & 31);
c++;
if ((bd + c) > (bs - 512)) {
// need to increase the buffer size.
bs += 1024;
b2 = (uint8_t*)realloc(b2, bs);
}
// 7 was handled, here we handle other decompression codes.
switch (b) {
case 0: // 0b 000
// raw copy
// copy info from the src buffer to our new buffer,
// at offset bd (which we'll be increasing;
memcpy(b2 + bd, src, c);
// increment the src pointer accordingly.
src += c;
bd += c;
break;
case 1: // 0b 001
// rle copy
// make c duplicates of one byte, inc the src pointer.
memset(b2 + bd, *(src++), c);
// increase the b2 offset.
bd += c;
break;
case 2: // 0b 010
// rle 16-bit alternating copy
d = core::ldle16b(src);
src += 2;
while (c > 1) {
// copy that 16-bit number c/2 times into the b2 buffer.
core::stle16b(b2 + bd, d);
bd += 2;
c -= 2; // hence c/2
}
if (c) // if there's a remainder of c/2, this handles it.
b2[bd++] = (char)d;
break;
case 3: // 0b 011
// incrementing copy
// get the current src byte.
a = *(src++);
while (c--) {
// increment that byte and copy to b2 in c iterations.
// e.g. a = 4, b2 will have 4,5,6,7,8... written to it.
b2[bd++] = a++;
}
break;
default: // 0b 100, 101, 110
// lz copy
if (p_big_endian) {
d = (*src << 8) + src[1];
} else {
d = core::ldle16b(src);
}
while (c--) {
// copy from a different location in the buffer.
b2[bd++] = b2[d++];
}
src += 2;
}
}
b2 = (unsigned char*)realloc(b2, bd);
if (size) (*size) = bd;
// return the unsigned char* buffer b2, which contains the uncompressed data.
return b2;
}
absl::StatusOr<Bytes> CompressGraphics(const uchar* data, const int pos, absl::StatusOr<Bytes> CompressGraphics(const uchar* data, const int pos,
const int length) { const int length) {
return CompressV2(data, pos, length, kNintendoMode2); return CompressV2(data, pos, length, kNintendoMode2);
@@ -598,6 +878,11 @@ absl::StatusOr<Bytes> CompressOverworld(const uchar* data, const int pos,
return CompressV2(data, pos, length, kNintendoMode1); return CompressV2(data, pos, length, kNintendoMode1);
} }
absl::StatusOr<Bytes> CompressOverworld(const std::vector<uint8_t> data,
const int pos, const int length) {
return CompressV3(data, pos, length, kNintendoMode1);
}
// ============================================================================ // ============================================================================
// Compression V3 // Compression V3
@@ -888,7 +1173,7 @@ void AddCompressionToChain(CompressionContext& context) {
absl::Status ValidateCompressionResultV3(const CompressionContext& context) { absl::Status ValidateCompressionResultV3(const CompressionContext& context) {
if (!context.compressed_data.empty()) { if (!context.compressed_data.empty()) {
ROM temp_rom; Rom temp_rom;
RETURN_IF_ERROR(temp_rom.LoadFromBytes(context.compressed_data)); RETURN_IF_ERROR(temp_rom.LoadFromBytes(context.compressed_data));
ASSIGN_OR_RETURN(auto decomp_data, ASSIGN_OR_RETURN(auto decomp_data,
DecompressV2(temp_rom.data(), 0, temp_rom.size())) DecompressV2(temp_rom.data(), 0, temp_rom.size()))
@@ -1019,7 +1304,7 @@ void FinalizeCompression(CompressionContext& context) {
<< context.compressed_data.size()); << context.compressed_data.size());
} }
absl::StatusOr<Bytes> CompressV3(const std::vector<uint8_t> data, absl::StatusOr<Bytes> CompressV3(const std::vector<uint8_t>& data,
const int start, const int length, int mode, const int start, const int length, int mode,
bool check) { bool check) {
if (length == 0) { if (length == 0) {

View File

@@ -15,8 +15,26 @@ namespace yaze {
namespace app { namespace app {
namespace gfx { namespace gfx {
/**
* @namespace yaze::app::gfx::lc_lz2
* @brief Contains the LC_LZ2 compression algorithm.
*/
namespace lc_lz2 { namespace lc_lz2 {
const int D_NINTENDO_C_MODE1 = 0;
const int D_NINTENDO_C_MODE2 = 1;
const int D_CMD_COPY = 0;
const int D_CMD_BYTE_REPEAT = 1;
const int D_CMD_WORD_REPEAT = 2;
const int D_CMD_BYTE_INC = 3;
const int D_CMD_COPY_EXISTING = 4;
const int D_MAX_NORMAL_LENGTH = 32;
const int D_MAX_LENGTH = 1024;
const int INITIAL_ALLOC_SIZE = 1024;
constexpr int kCommandDirectCopy = 0; constexpr int kCommandDirectCopy = 0;
constexpr int kCommandByteFill = 1; constexpr int kCommandByteFill = 1;
constexpr int kCommandWordFill = 2; constexpr int kCommandWordFill = 2;
@@ -120,6 +138,10 @@ void CompressionCommandAlternativeV2(const uchar* data,
uint& src_pos, uint& comp_accumulator, uint& src_pos, uint& comp_accumulator,
uint& cmd_with_max, uint& max_win); uint& cmd_with_max, uint& max_win);
/**
* @brief Compresses a buffer of data using the LC_LZ2 algorithm.
* \deprecated Use Compress and Uncompress instead.
*/
absl::StatusOr<Bytes> CompressV2(const uchar* data, const int start, absl::StatusOr<Bytes> CompressV2(const uchar* data, const int start,
const int length, int mode = 1, const int length, int mode = 1,
bool check = false); bool check = false);
@@ -128,6 +150,8 @@ absl::StatusOr<Bytes> CompressGraphics(const uchar* data, const int pos,
const int length); const int length);
absl::StatusOr<Bytes> CompressOverworld(const uchar* data, const int pos, absl::StatusOr<Bytes> CompressOverworld(const uchar* data, const int pos,
const int length); const int length);
absl::StatusOr<Bytes> CompressOverworld(const std::vector<uint8_t> data,
const int pos, const int length);
absl::StatusOr<CompressionPiecePointer> SplitCompressionPiece( absl::StatusOr<CompressionPiecePointer> SplitCompressionPiece(
CompressionPiecePointer& piece, int mode); CompressionPiecePointer& piece, int mode);
@@ -185,10 +209,21 @@ absl::StatusOr<CompressionPiece> SplitCompressionPieceV3(
CompressionPiece& piece, int mode); CompressionPiece& piece, int mode);
void FinalizeCompression(CompressionContext& context); void FinalizeCompression(CompressionContext& context);
absl::StatusOr<Bytes> CompressV3(const std::vector<uint8_t> data, /**
* @brief Compresses a buffer of data using the LC_LZ2 algorithm.
* \deprecated Use Compress and Uncompress instead.
*/
absl::StatusOr<Bytes> CompressV3(const std::vector<uint8_t>& data,
const int start, const int length, const int start, const int length,
int mode = 1, bool check = false); int mode = 1, bool check = false);
// Hyrule Magic
uint8_t* Compress(uint8_t const* const src, int const oldsize, int* const size,
int const flag);
uint8_t* Uncompress(uint8_t const* src, int* const size,
int const p_big_endian);
// Decompression // Decompression
std::string SetBuffer(const std::vector<uint8_t>& data, int src_pos, std::string SetBuffer(const std::vector<uint8_t>& data, int src_pos,
@@ -197,6 +232,10 @@ std::string SetBuffer(const uchar* data, int src_pos, int comp_accumulator);
void memfill(const uchar* data, Bytes& buffer, int buffer_pos, int offset, void memfill(const uchar* data, Bytes& buffer, int buffer_pos, int offset,
int length); int length);
/**
* @brief Decompresses a buffer of data using the LC_LZ2 algorithm.
* \deprecated Use Compress and Uncompress instead.
*/
absl::StatusOr<Bytes> DecompressV2(const uchar* data, int offset, absl::StatusOr<Bytes> DecompressV2(const uchar* data, int offset,
int size = 0x800, int mode = 1); int size = 0x800, int mode = 1);
absl::StatusOr<Bytes> DecompressGraphics(const uchar* data, int pos, int size); absl::StatusOr<Bytes> DecompressGraphics(const uchar* data, int pos, int size);

View File

@@ -15,6 +15,7 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace gfx { namespace gfx {
namespace scad_format {
void FindMetastamp() { void FindMetastamp() {
int matching_position = -1; int matching_position = -1;
@@ -276,6 +277,7 @@ absl::Status DecodeObjFile(
return absl::OkStatus(); return absl::OkStatus();
} }
} // namespace scad_format
} // namespace gfx } // namespace gfx
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -22,18 +22,27 @@ namespace yaze {
namespace app { namespace app {
namespace gfx { namespace gfx {
// キャラクタ(.SCH)ファイル /**
// ヘッダー情報 * @namespace yaze::app::gfx::scad_format
// アドレス 説明 * @brief Loading from prototype SCAD format
// 00000 - 00003 ファイルタイプ "SCH" */
// 00004 - 00008 ビットモード "?BIT" namespace scad_format {
// 00009 - 00013 バージョンナンバー "Ver-????\n"
// 00014 - 00017 ヘッダーサイズ /**
// 00018 - 0001B ハード名 "SFC" or "CGB" or "GB" * @brief Cgx file header
// 0001C - 0001C BG/OBJフラグ(AGBの時) * キャラクタ(.SCH)ファイル
// 0001D - 0001D Color Pallette Number * ヘッダー情報
// 0001D - 000FF 予約 * アドレス 説明
// 00100 - 001FF Color Path * 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 { struct CgxHeader {
char file_type[4]; char file_type[4];
char bit_mode[5]; char bit_mode[5];
@@ -49,28 +58,47 @@ struct CgxHeader {
constexpr uint16_t kMatchedBytes[] = {0x4E, 0x41, 0x4B, 0x31, 0x39, 0x38, 0x39}; constexpr uint16_t kMatchedBytes[] = {0x4E, 0x41, 0x4B, 0x31, 0x39, 0x38, 0x39};
constexpr uint16_t kOffsetFromMatchedBytesEnd = 0x1D; constexpr uint16_t kOffsetFromMatchedBytesEnd = 0x1D;
/**
* @brief Find metastamp in CGX file
*/
void FindMetastamp(); void FindMetastamp();
/**
* @brief Load Scr file (screen data)
*/
absl::Status LoadScr(std::string_view filename, uint8_t input_value, absl::Status LoadScr(std::string_view filename, uint8_t input_value,
std::vector<uint8_t>& map_data); std::vector<uint8_t>& map_data);
/**
* @brief Load Cgx file (graphical content)
*/
absl::Status LoadCgx(uint8_t bpp, std::string_view filename, absl::Status LoadCgx(uint8_t bpp, std::string_view filename,
std::vector<uint8_t>& cgx_data, std::vector<uint8_t>& cgx_data,
std::vector<uint8_t>& cgx_loaded, std::vector<uint8_t>& cgx_loaded,
std::vector<uint8_t>& cgx_header); std::vector<uint8_t>& cgx_header);
/**
* @brief Draw screen tilemap with graphical data
*/
absl::Status DrawScrWithCgx(uint8_t bpp, std::vector<uint8_t>& map_bitmap_data, absl::Status DrawScrWithCgx(uint8_t bpp, std::vector<uint8_t>& map_bitmap_data,
std::vector<uint8_t>& map_data, std::vector<uint8_t>& map_data,
std::vector<uint8_t>& cgx_loaded); std::vector<uint8_t>& cgx_loaded);
/**
* @brief Decode color file
*/
std::vector<SDL_Color> DecodeColFile(const std::string_view filename); std::vector<SDL_Color> DecodeColFile(const std::string_view filename);
/**
* @brief Decode obj file
*/
absl::Status DecodeObjFile( absl::Status DecodeObjFile(
std::string_view filename, std::vector<uint8_t>& obj_data, std::string_view filename, std::vector<uint8_t>& obj_data,
std::vector<uint8_t> actual_obj_data, std::vector<uint8_t> actual_obj_data,
std::unordered_map<std::string, std::vector<uint8_t>> decoded_obj, 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>& decoded_extra_obj, int& obj_loaded);
} // namespace scad_format
} // namespace gfx } // namespace gfx
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

104
src/app/gfx/snes_color.cc Normal file
View File

@@ -0,0 +1,104 @@
#include "app/gfx/snes_color.h"
#include <imgui/imgui.h>
#include <cstdint>
#include <vector>
namespace yaze {
namespace app {
namespace gfx {
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;
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;
}
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;
}
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);
}
SnesColor ReadColorFromRom(int offset, const uint8_t* rom) {
short color = (uint16_t)((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;
}
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 palette;
}
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 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.set_rgb(rgb);
return toret;
}
std::vector<SnesColor> GetColFileData(uint8_t* data) {
std::vector<SnesColor> colors;
colors.reserve(256);
colors.resize(256);
for (int i = 0; i < 512; i += 2) {
colors[i / 2] = GetCgxColor((uint16_t)((data[i + 1] << 8) + data[i]));
}
return colors;
}
} // namespace gfx
} // namespace app
} // namespace yaze

114
src/app/gfx/snes_color.h Normal file
View File

@@ -0,0 +1,114 @@
#ifndef YAZE_APP_GFX_SNES_COLOR_H_
#define YAZE_APP_GFX_SNES_COLOR_H_
#include <imgui/imgui.h>
#include <cstdint>
#include <vector>
namespace yaze {
namespace app {
namespace gfx {
/**
* @brief Primitive of 16-bit RGB SNES color.
*/
struct snes_color {
uint16_t red; /**< Red component of the color. */
uint16_t blue; /**< Blue component of the color. */
uint16_t green; /**< Green component of the color. */
};
typedef struct snes_color snes_color;
snes_color ConvertSNEStoRGB(uint16_t snes_color);
uint16_t ConvertRGBtoSNES(const snes_color& color);
uint16_t ConvertRGBtoSNES(const ImVec4& color);
std::vector<snes_color> Extract(const char* data, unsigned int offset,
unsigned int palette_size);
std::vector<char> Convert(const std::vector<snes_color>& palette);
/**
* @brief SNES Color container
*
* Used for displaying the color to the screen and writing
* the color to the Rom file in the correct format.
*
* SNES colors may be represented in one of three formats:
* - Color data from the rom in a snes_color struct
* - Color data for displaying to the UI via ImVec4
*/
class SnesColor {
public:
SnesColor() : rgb_(0.f, 0.f, 0.f, 0.f), snes_(0) {}
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);
}
explicit SnesColor(const snes_color val)
: rgb_(val.red, val.green, val.blue, 255.f),
snes_(ConvertRGBtoSNES(val)),
rom_color_(val) {}
SnesColor(uint8_t r, uint8_t g, uint8_t b) {
rgb_ = ImVec4(r, g, b, 255.f);
snes_color color;
color.red = r;
color.green = g;
color.blue = b;
snes_ = ConvertRGBtoSNES(color);
rom_color_ = color;
}
ImVec4 rgb() const { return rgb_; }
void set_rgb(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;
}
void set_snes(uint16_t val) {
snes_ = val;
snes_color col = ConvertSNEStoRGB(val);
rgb_ = ImVec4(col.red, col.green, col.blue, 0.f);
modified = true;
}
snes_color rom_color() const { return rom_color_; }
uint16_t snes() const { return snes_; }
bool is_modified() const { return modified; }
bool is_transparent() const { return transparent; }
void set_transparent(bool t) { transparent = t; }
void set_modified(bool m) { modified = m; }
private:
ImVec4 rgb_;
uint16_t snes_;
snes_color rom_color_;
bool modified = false;
bool transparent = false;
};
SnesColor ReadColorFromRom(int offset, const uint8_t* rom);
SnesColor GetCgxColor(uint16_t color);
std::vector<SnesColor> GetColFileData(uint8_t* data);
} // namespace gfx
} // namespace app
} // namespace yaze
#endif // YAZE_APP_GFX_SNES_COLOR_H_

View File

@@ -12,14 +12,178 @@
#include "absl/container/flat_hash_map.h" // for flat_hash_map #include "absl/container/flat_hash_map.h" // for flat_hash_map
#include "absl/status/status.h" // for Status #include "absl/status/status.h" // for Status
#include "absl/status/statusor.h"
#include "app/core/constants.h" #include "app/core/constants.h"
#include "app/gfx/snes_color.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace gfx { namespace gfx {
// Define a hash map to hold the addresses of different palette groups /**
const absl::flat_hash_map<std::string, uint32_t> paletteGroupAddresses = { * @namespace yaze::app::gfx::palette_group_internal
* @brief Internal functions for loading palettes by group.
*/
namespace palette_group_internal {
absl::Status LoadOverworldMainPalettes(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 6; i++) {
RETURN_IF_ERROR(palette_groups.overworld_main.AddPalette(
gfx::ReadPaletteFromRom(core::overworldPaletteMain + (i * (35 * 2)),
/*num_colors*/ 35, data)))
}
return absl::OkStatus();
}
absl::Status LoadOverworldAuxiliaryPalettes(
const Bytes& rom_data, gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 20; i++) {
RETURN_IF_ERROR(
palette_groups.overworld_aux.AddPalette(gfx::ReadPaletteFromRom(
core::overworldPaletteAuxialiary + (i * (21 * 2)),
/*num_colors*/ 21, data)))
}
return absl::OkStatus();
}
absl::Status LoadOverworldAnimatedPalettes(
const Bytes& rom_data, gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 14; i++) {
RETURN_IF_ERROR(
palette_groups.overworld_animated.AddPalette(gfx::ReadPaletteFromRom(
core::overworldPaletteAnimated + (i * (7 * 2)), 7, data)))
}
return absl::OkStatus();
}
absl::Status LoadHUDPalettes(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 2; i++) {
RETURN_IF_ERROR(palette_groups.hud.AddPalette(
gfx::ReadPaletteFromRom(core::hudPalettes + (i * 64), 32, data)))
}
return absl::OkStatus();
}
absl::Status LoadGlobalSpritePalettes(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
RETURN_IF_ERROR(palette_groups.global_sprites.AddPalette(
gfx::ReadPaletteFromRom(core::globalSpritePalettesLW, 60, data)))
RETURN_IF_ERROR(palette_groups.global_sprites.AddPalette(
gfx::ReadPaletteFromRom(core::globalSpritePalettesDW, 60, data)))
return absl::OkStatus();
}
absl::Status LoadArmorPalettes(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 5; i++) {
RETURN_IF_ERROR(palette_groups.armors.AddPalette(
gfx::ReadPaletteFromRom(core::armorPalettes + (i * 30), 15, data)))
}
return absl::OkStatus();
}
absl::Status LoadSwordPalettes(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 4; i++) {
RETURN_IF_ERROR(palette_groups.swords.AddPalette(
gfx::ReadPaletteFromRom(core::swordPalettes + (i * 6), 3, data)))
}
return absl::OkStatus();
}
absl::Status LoadShieldPalettes(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 3; i++) {
RETURN_IF_ERROR(palette_groups.shields.AddPalette(
gfx::ReadPaletteFromRom(core::shieldPalettes + (i * 8), 4, data)))
}
return absl::OkStatus();
}
absl::Status LoadSpriteAux1Palettes(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 12; i++) {
RETURN_IF_ERROR(palette_groups.sprites_aux1.AddPalette(
gfx::ReadPaletteFromRom(core::spritePalettesAux1 + (i * 14), 7, data)))
}
return absl::OkStatus();
}
absl::Status LoadSpriteAux2Palettes(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 11; i++) {
RETURN_IF_ERROR(palette_groups.sprites_aux2.AddPalette(
gfx::ReadPaletteFromRom(core::spritePalettesAux2 + (i * 14), 7, data)))
}
return absl::OkStatus();
}
absl::Status LoadSpriteAux3Palettes(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 24; i++) {
RETURN_IF_ERROR(palette_groups.sprites_aux3.AddPalette(
gfx::ReadPaletteFromRom(core::spritePalettesAux3 + (i * 14), 7, data)))
}
return absl::OkStatus();
}
absl::Status LoadDungeonMainPalettes(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 20; i++) {
RETURN_IF_ERROR(
palette_groups.dungeon_main.AddPalette(gfx::ReadPaletteFromRom(
core::dungeonMainPalettes + (i * 180), 90, data)))
}
return absl::OkStatus();
}
absl::Status LoadGrassColors(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
RETURN_IF_ERROR(palette_groups.grass.AddColor(
gfx::ReadColorFromRom(core::hardcodedGrassLW, rom_data.data())))
RETURN_IF_ERROR(palette_groups.grass.AddColor(
gfx::ReadColorFromRom(core::hardcodedGrassDW, rom_data.data())))
RETURN_IF_ERROR(palette_groups.grass.AddColor(
gfx::ReadColorFromRom(core::hardcodedGrassSpecial, rom_data.data())))
return absl::OkStatus();
}
absl::Status Load3DObjectPalettes(const Bytes& rom_data,
gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
RETURN_IF_ERROR(palette_groups.object_3d.AddPalette(
gfx::ReadPaletteFromRom(core::triforcePalette, 8, data)))
RETURN_IF_ERROR(palette_groups.object_3d.AddPalette(
gfx::ReadPaletteFromRom(core::crystalPalette, 8, data)))
return absl::OkStatus();
}
absl::Status LoadOverworldMiniMapPalettes(
const Bytes& rom_data, gfx::PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 2; i++) {
RETURN_IF_ERROR(
palette_groups.overworld_mini_map.AddPalette(gfx::ReadPaletteFromRom(
core::overworldMiniMapPalettes + (i * 256), 128, data)))
}
return absl::OkStatus();
}
} // namespace palette_group_internal
const absl::flat_hash_map<std::string, uint32_t> kPaletteGroupAddressMap = {
{"ow_main", core::overworldPaletteMain}, {"ow_main", core::overworldPaletteMain},
{"ow_aux", core::overworldPaletteAuxialiary}, {"ow_aux", core::overworldPaletteAuxialiary},
{"ow_animated", core::overworldPaletteAnimated}, {"ow_animated", core::overworldPaletteAnimated},
@@ -37,8 +201,7 @@ const absl::flat_hash_map<std::string, uint32_t> paletteGroupAddresses = {
{"ow_mini_map", core::overworldMiniMapPalettes}, {"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> kPaletteGroupColorCounts = {
const absl::flat_hash_map<std::string, uint32_t> paletteGroupColorCounts = {
{"ow_main", 35}, {"ow_aux", 21}, {"ow_animated", 7}, {"ow_main", 35}, {"ow_aux", 21}, {"ow_animated", 7},
{"hud", 32}, {"global_sprites", 60}, {"armors", 15}, {"hud", 32}, {"global_sprites", 60}, {"armors", 15},
{"swords", 3}, {"shields", 4}, {"sprites_aux1", 7}, {"swords", 3}, {"shields", 4}, {"sprites_aux1", 7},
@@ -46,200 +209,13 @@ const absl::flat_hash_map<std::string, uint32_t> paletteGroupColorCounts = {
{"grass", 1}, {"3d_object", 8}, {"ow_mini_map", 128}, {"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;
}
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_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 palette;
}
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);
for (int i = 0; i < 512; i += 2) {
colors[i / 2] = GetCgxColor((uint16_t)((data[i + 1] << 8) + data[i]));
}
return colors;
}
// ============================================================================
SNESPalette::SNESPalette(uint8_t mSize) : size_(mSize) {
for (unsigned int i = 0; i < mSize; i++) {
SNESColor col;
colors.push_back(col);
}
}
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.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);
}
}
SNESPalette::SNESPalette(const unsigned char* snes_pal)
: size_(sizeof(snes_pal) / 2) {
assert((sizeof(snes_pal) % 4 == 0) && (sizeof(snes_pal) <= 32));
for (unsigned i = 0; i < sizeof(snes_pal); i += 2) {
SNESColor col;
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);
}
}
SNESPalette::SNESPalette(const std::vector<ImVec4>& cols) {
for (const auto& each : cols) {
SNESColor scol;
scol.SetRGB(each);
colors.push_back(scol);
}
size_ = cols.size();
}
SNESPalette::SNESPalette(const std::vector<snes_color>& cols) {
for (const auto& each : cols) {
SNESColor scol;
scol.SetSNES(ConvertRGBtoSNES(each));
colors.push_back(scol);
}
size_ = cols.size();
}
SNESPalette::SNESPalette(const std::vector<SNESColor>& cols) {
for (const auto& each : cols) {
colors.push_back(each);
}
size_ = cols.size();
}
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].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;
}
sdl_palette->colors = color.data();
return sdl_palette.get();
}
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, uint32_t GetPaletteAddress(const std::string& group_name, size_t palette_index,
size_t color_index) { size_t color_index) {
// Retrieve the base address for the palette group // Retrieve the base address for the palette group
uint32_t base_address = paletteGroupAddresses.at(group_name); uint32_t base_address = kPaletteGroupAddressMap.at(group_name);
// Retrieve the number of colors for each palette in the group // Retrieve the number of colors for each palette in the group
uint32_t colors_per_palette = paletteGroupColorCounts.at(group_name); uint32_t colors_per_palette = kPaletteGroupColorCounts.at(group_name);
// Calculate the address for thes specified color in the ROM // Calculate the address for thes specified color in the ROM
uint32_t address = base_address + (palette_index * colors_per_palette * 2) + uint32_t address = base_address + (palette_index * colors_per_palette * 2) +
@@ -248,51 +224,171 @@ uint32_t GetPaletteAddress(const std::string& group_name, size_t palette_index,
return address; return address;
} }
std::array<float, 4> ToFloatArray(const SNESColor& color) { // ============================================================================
SnesPalette::SnesPalette(uint8_t mSize) : size_(mSize) {
for (unsigned int i = 0; i < mSize; i++) {
SnesColor col;
colors.push_back(col);
}
size_ = mSize;
}
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.set_snes(static_cast<uchar>(data[i + 1]) << 8);
col.set_snes(col.snes() | static_cast<uchar>(data[i]));
snes_color mColor = ConvertSNEStoRGB(col.snes());
col.set_rgb(ImVec4(mColor.red, mColor.green, mColor.blue, 1.f));
colors.push_back(col);
}
size_ = sizeof(data) / 2;
}
SnesPalette::SnesPalette(const unsigned char* snes_pal)
: size_(sizeof(snes_pal) / 2) {
assert((sizeof(snes_pal) % 4 == 0) && (sizeof(snes_pal) <= 32));
for (unsigned i = 0; i < sizeof(snes_pal); i += 2) {
SnesColor col;
col.set_snes(snes_pal[i + 1] << (uint16_t)8);
col.set_snes(col.snes() | snes_pal[i]);
snes_color mColor = ConvertSNEStoRGB(col.snes());
col.set_rgb(ImVec4(mColor.red, mColor.green, mColor.blue, 1.f));
colors.push_back(col);
}
size_ = sizeof(snes_pal) / 2;
}
SnesPalette::SnesPalette(const std::vector<ImVec4>& cols) {
for (const auto& each : cols) {
SnesColor scol;
scol.set_rgb(each);
colors.push_back(scol);
}
size_ = cols.size();
}
SnesPalette::SnesPalette(const std::vector<snes_color>& cols) {
for (const auto& each : cols) {
SnesColor scol;
scol.set_snes(ConvertRGBtoSNES(each));
colors.push_back(scol);
}
size_ = cols.size();
}
SnesPalette::SnesPalette(const std::vector<SnesColor>& cols) {
for (const auto& each : cols) {
colors.push_back(each);
}
size_ = cols.size();
}
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].a = 0;
std::cout << "Color " << i << " added (R:" << color[i].r
<< " G:" << color[i].g << " B:" << color[i].b << ")" << std::endl;
}
sdl_palette->colors = color.data();
return sdl_palette.get();
}
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].set_snes(ConvertRGBtoSNES(new_color));
if (color_offset == 0) {
colors[color_offset].set_transparent(true);
}
color_offset++;
offset += 2;
}
return gfx::SnesPalette(colors);
}
std::array<float, 4> ToFloatArray(const SnesColor& color) {
std::array<float, 4> colorArray; std::array<float, 4> colorArray;
colorArray[0] = color.GetRGB().x / 255.0f; colorArray[0] = color.rgb().x / 255.0f;
colorArray[1] = color.GetRGB().y / 255.0f; colorArray[1] = color.rgb().y / 255.0f;
colorArray[2] = color.GetRGB().z / 255.0f; colorArray[2] = color.rgb().z / 255.0f;
colorArray[3] = color.GetRGB().w; colorArray[3] = color.rgb().w;
return colorArray; return colorArray;
} }
PaletteGroup::PaletteGroup(uint8_t mSize) : size_(mSize) {} PaletteGroup::PaletteGroup(uint8_t mSize) : size_(mSize) {}
PaletteGroup CreatePaletteGroupFromColFile( absl::StatusOr<PaletteGroup> CreatePaletteGroupFromColFile(
std::vector<SNESColor>& palette_rows) { std::vector<SnesColor>& palette_rows) {
PaletteGroup toret; PaletteGroup toret;
for (int i = 0; i < palette_rows.size(); i += 8) { for (int i = 0; i < palette_rows.size(); i += 8) {
SNESPalette palette; SnesPalette palette;
for (int j = 0; j < 8; j++) { for (int j = 0; j < 8; j++) {
palette.AddColor(palette_rows[i + j].GetRomRGB()); palette.AddColor(palette_rows[i + j].rom_color());
} }
toret.AddPalette(palette); RETURN_IF_ERROR(toret.AddPalette(palette));
} }
return toret; return toret;
} }
// Take a SNESPalette with N many colors and divide it into palettes of 8 colors // Take a SNESPalette with N many colors and divide it into palettes of 8 colors
// each absl::StatusOr<PaletteGroup> CreatePaletteGroupFromLargePalette(
PaletteGroup CreatePaletteGroupFromLargePalette(SNESPalette& palette) { SnesPalette& palette) {
PaletteGroup toret; PaletteGroup toret;
std::cout << "Palette size is " << palette.size() << std::endl;
for (int i = 0; i < palette.size(); i += 8) { for (int i = 0; i < palette.size(); i += 8) {
SNESPalette new_palette; SnesPalette new_palette;
if (i + 8 < palette.size()) { if (i + 8 < palette.size()) {
// new_palette.AddColor(SnesColor(ImVec4(0,0,0,0)));
for (int j = 0; j < 8; j++) { for (int j = 0; j < 8; j++) {
new_palette.AddColor(palette[i + j]); new_palette.AddColor(palette[i + j]);
} }
} }
toret.AddPalette(new_palette); RETURN_IF_ERROR(toret.AddPalette(new_palette));
} }
return toret; return toret;
} }
using namespace palette_group_internal;
absl::Status LoadAllPalettes(const Bytes& rom_data, PaletteGroupMap& groups) {
RETURN_IF_ERROR(LoadOverworldMainPalettes(rom_data, groups))
RETURN_IF_ERROR(LoadOverworldAuxiliaryPalettes(rom_data, groups))
RETURN_IF_ERROR(LoadOverworldAnimatedPalettes(rom_data, groups))
RETURN_IF_ERROR(LoadHUDPalettes(rom_data, groups))
RETURN_IF_ERROR(LoadGlobalSpritePalettes(rom_data, groups))
RETURN_IF_ERROR(LoadArmorPalettes(rom_data, groups))
RETURN_IF_ERROR(LoadSwordPalettes(rom_data, groups))
RETURN_IF_ERROR(LoadShieldPalettes(rom_data, groups))
RETURN_IF_ERROR(LoadSpriteAux1Palettes(rom_data, groups))
RETURN_IF_ERROR(LoadSpriteAux2Palettes(rom_data, groups))
RETURN_IF_ERROR(LoadSpriteAux3Palettes(rom_data, groups))
RETURN_IF_ERROR(LoadDungeonMainPalettes(rom_data, groups))
RETURN_IF_ERROR(LoadGrassColors(rom_data, groups))
RETURN_IF_ERROR(Load3DObjectPalettes(rom_data, groups))
RETURN_IF_ERROR(LoadOverworldMiniMapPalettes(rom_data, groups))
return absl::OkStatus();
}
} // namespace gfx } // namespace gfx
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -13,19 +13,17 @@
#include "absl/base/casts.h" #include "absl/base/casts.h"
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/core/constants.h" #include "app/core/constants.h"
#include "app/gfx/snes_color.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace gfx { namespace gfx {
struct snes_color { /**
uint16_t red; /**< Red component of the color. */ * @brief Primitive of a SNES color palette.
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 { struct snes_palette {
uint id; /**< ID of the palette. */ uint id; /**< ID of the palette. */
uint size; /**< Size of the palette. */ uint size; /**< Size of the palette. */
@@ -33,115 +31,50 @@ struct snes_palette {
}; };
using snes_palette = struct snes_palette; using snes_palette = struct snes_palette;
uint16_t ConvertRGBtoSNES(const snes_color& color); uint32_t GetPaletteAddress(const std::string& group_name, size_t palette_index,
uint16_t ConvertRGBtoSNES(const ImVec4& color); size_t color_index);
snes_color ConvertSNEStoRGB(uint16_t snes_color);
/** /**
* @brief Extracts a vector of SNES colors from a data buffer. * @brief Represents a palette of colors for the Super Nintendo Entertainment
* System (SNES).
* *
* @param data The data buffer to extract from. * The `SnesPalette` class provides functionality to create, modify, and access
* @param offset The offset in the buffer to start extracting from. * colors in an SNES palette. It supports various constructors to initialize the
* @param palette_size The size of the palette to extract. * palette with different types of data. The palette can be modified by adding
* @return A vector of SNES colors extracted from the buffer. * or changing colors, and it can be cleared to remove all colors. Colors in the
* palette can be accessed using index-based access or through the `GetColor`
* method. The class also provides a method to create a sub-palette by selecting
* a range of colors from the original palette.
*/ */
std::vector<snes_color> Extract(const char* data, unsigned int offset, class SnesPalette {
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() : rgb(0.f, 0.f, 0.f, 0.f), snes(0) {}
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);
}
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: public:
template <typename T> template <typename T>
explicit SNESPalette(const std::vector<T>& data) { explicit SnesPalette(const std::vector<T>& data) {
for (const auto& item : data) { for (const auto& item : data) {
colors.push_back(SNESColor(item)); colors.push_back(SnesColor(item));
} }
size_ = data.size();
} }
SNESPalette() = default; SnesPalette() = default;
explicit SNESPalette(uint8_t mSize); explicit SnesPalette(uint8_t mSize);
explicit SNESPalette(char* snesPal); explicit SnesPalette(char* snesPal);
explicit SNESPalette(const unsigned char* snes_pal); explicit SnesPalette(const unsigned char* snes_pal);
explicit SNESPalette(const std::vector<ImVec4>&); explicit SnesPalette(const std::vector<ImVec4>&);
explicit SNESPalette(const std::vector<snes_color>&); explicit SnesPalette(const std::vector<snes_color>&);
explicit SNESPalette(const std::vector<SNESColor>&); explicit SnesPalette(const std::vector<SnesColor>&);
SDL_Palette* GetSDL_Palette(); SDL_Palette* GetSDL_Palette();
void Create(const std::vector<SNESColor>& cols) { void Create(const std::vector<SnesColor>& cols) {
for (const auto& each : cols) { for (const auto& each : cols) {
colors.push_back(each); colors.push_back(each);
} }
size_ = cols.size(); size_ = cols.size();
} }
void AddColor(SNESColor color) { void AddColor(SnesColor color) {
colors.push_back(color); colors.push_back(color);
size_++; size_++;
} }
@@ -151,13 +84,15 @@ class SNESPalette {
size_++; size_++;
} }
auto GetColor(int i) const { absl::StatusOr<SnesColor> GetColor(int i) const {
if (i > size_) { if (i > size_) {
throw std::out_of_range("SNESPalette: Index out of bounds"); return absl::InvalidArgumentError("SnesPalette: Index out of bounds");
} }
return colors[i]; return colors[i];
} }
auto mutable_color(int i) { return &colors[i]; }
void Clear() { void Clear() {
colors.clear(); colors.clear();
size_ = 0; size_ = 0;
@@ -165,14 +100,15 @@ class SNESPalette {
auto size() const { return colors.size(); } auto size() const { return colors.size(); }
SNESColor& operator[](int i) { SnesColor& operator[](int i) {
if (i > size_) { if (i > size_) {
throw std::out_of_range("SNESPalette: Index out of bounds"); std::cout << "SNESPalette: Index out of bounds" << std::endl;
return colors[0];
} }
return colors[i]; return colors[i];
} }
void operator()(int i, const SNESColor& color) { void operator()(int i, const SnesColor& color) {
if (i >= size_) { if (i >= size_) {
throw std::out_of_range("SNESPalette: Index out of bounds"); throw std::out_of_range("SNESPalette: Index out of bounds");
} }
@@ -183,12 +119,12 @@ class SNESPalette {
if (i >= size_) { if (i >= size_) {
throw std::out_of_range("SNESPalette: Index out of bounds"); throw std::out_of_range("SNESPalette: Index out of bounds");
} }
colors[i].SetRGB(color); colors[i].set_rgb(color);
colors[i].SetModified(true); colors[i].set_modified(true);
} }
SNESPalette sub_palette(int start, int end) const { SnesPalette sub_palette(int start, int end) const {
SNESPalette pal; SnesPalette pal;
for (int i = start; i < end; i++) { for (int i = start; i < end; i++) {
pal.AddColor(colors[i]); pal.AddColor(colors[i]);
} }
@@ -197,26 +133,31 @@ class SNESPalette {
private: private:
int size_ = 0; /**< The size of the palette. */ int size_ = 0; /**< The size of the palette. */
std::vector<SNESColor> colors; /**< The colors in the palette. */ std::vector<SnesColor> colors; /**< The colors in the palette. */
}; };
SNESPalette ReadPaletteFromROM(int offset, int num_colors, const uchar* rom); SnesPalette ReadPaletteFromRom(int offset, int num_colors, const uint8_t* 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);
std::array<float, 4> ToFloatArray(const SnesColor& color);
/**
* @brief Represents a group of palettes.
*
* Supports adding palettes and colors, clearing the group, and accessing
* palettes and colors by index.
*/
struct PaletteGroup { struct PaletteGroup {
PaletteGroup() = default; PaletteGroup() = default;
explicit PaletteGroup(uint8_t mSize); explicit PaletteGroup(uint8_t mSize);
absl::Status AddPalette(SNESPalette pal) { absl::Status AddPalette(SnesPalette pal) {
palettes.emplace_back(pal); palettes.emplace_back(pal);
size_ = palettes.size(); size_ = palettes.size();
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status AddColor(SNESColor color) { absl::Status AddColor(SnesColor color) {
if (size_ == 0) { if (size_ == 0) {
palettes.emplace_back(); palettes.emplace_back();
} }
@@ -229,9 +170,12 @@ struct PaletteGroup {
size_ = 0; size_ = 0;
} }
auto name() const { return name_; }
auto size() const { return palettes.size(); } auto size() const { return palettes.size(); }
auto mutable_palette(int i) { return &palettes[i]; }
auto palette(int i) const { return palettes[i]; }
SNESPalette operator[](int i) { SnesPalette operator[](int i) {
if (i > size_) { if (i > size_) {
std::cout << "PaletteGroup: Index out of bounds" << std::endl; std::cout << "PaletteGroup: Index out of bounds" << std::endl;
return palettes[0]; return palettes[0];
@@ -239,7 +183,7 @@ struct PaletteGroup {
return palettes[i]; return palettes[i];
} }
const SNESPalette& operator[](int i) const { const SnesPalette& operator[](int i) const {
if (i > size_) { if (i > size_) {
std::cout << "PaletteGroup: Index out of bounds" << std::endl; std::cout << "PaletteGroup: Index out of bounds" << std::endl;
return palettes[0]; return palettes[0];
@@ -247,7 +191,7 @@ struct PaletteGroup {
return palettes[i]; return palettes[i];
} }
absl::Status operator()(int i, const SNESColor& color) { absl::Status operator()(int i, const SnesColor& color) {
if (i >= size_) { if (i >= size_) {
return absl::InvalidArgumentError("PaletteGroup: Index out of bounds"); return absl::InvalidArgumentError("PaletteGroup: Index out of bounds");
} }
@@ -265,12 +209,152 @@ struct PaletteGroup {
private: private:
int size_ = 0; int size_ = 0;
std::vector<SNESPalette> palettes; std::string name_;
std::vector<SnesPalette> palettes;
}; };
PaletteGroup CreatePaletteGroupFromColFile(std::vector<SNESColor>& colors); /**
* @brief Represents a mapping of palette groups.
*
* Originally, this was an actual std::unordered_map but since the palette
* groups supported never change, it was changed to a struct with a method to
* get the group by name.
*/
struct PaletteGroupMap {
PaletteGroup overworld_main;
PaletteGroup overworld_aux;
PaletteGroup overworld_animated;
PaletteGroup hud;
PaletteGroup global_sprites;
PaletteGroup armors;
PaletteGroup swords;
PaletteGroup shields;
PaletteGroup sprites_aux1;
PaletteGroup sprites_aux2;
PaletteGroup sprites_aux3;
PaletteGroup dungeon_main;
PaletteGroup grass;
PaletteGroup object_3d;
PaletteGroup overworld_mini_map;
PaletteGroup CreatePaletteGroupFromLargePalette(SNESPalette& palette); auto get_group(const std::string& group_name) {
if (group_name == "ow_main") {
return &overworld_main;
} else if (group_name == "ow_aux") {
return &overworld_aux;
} else if (group_name == "ow_animated") {
return &overworld_animated;
} else if (group_name == "hud") {
return &hud;
} else if (group_name == "global_sprites") {
return &global_sprites;
} else if (group_name == "armors") {
return &armors;
} else if (group_name == "swords") {
return &swords;
} else if (group_name == "shields") {
return &shields;
} else if (group_name == "sprites_aux1") {
return &sprites_aux1;
} else if (group_name == "sprites_aux2") {
return &sprites_aux2;
} else if (group_name == "sprites_aux3") {
return &sprites_aux3;
} else if (group_name == "dungeon_main") {
return &dungeon_main;
} else if (group_name == "grass") {
return &grass;
} else if (group_name == "3d_object") {
return &object_3d;
} else if (group_name == "ow_mini_map") {
return &overworld_mini_map;
} else {
throw std::out_of_range("PaletteGroupMap: Group not found");
}
}
template <typename Func>
void for_each(Func&& func) {
func(overworld_main);
func(overworld_aux);
func(overworld_animated);
func(hud);
func(global_sprites);
func(armors);
func(swords);
func(shields);
func(sprites_aux1);
func(sprites_aux2);
func(sprites_aux3);
func(dungeon_main);
func(grass);
func(object_3d);
func(overworld_mini_map);
}
};
absl::StatusOr<PaletteGroup> CreatePaletteGroupFromColFile(
std::vector<SnesColor>& colors);
absl::StatusOr<PaletteGroup> CreatePaletteGroupFromLargePalette(
SnesPalette& palette);
/**
* @brief Loads all the palettes for the game.
*
* This function loads all the palettes for the game, including overworld,
* HUD, armor, swords, shields, sprites, dungeon, grass, and 3D object
* palettes. It also adds the loaded palettes to their respective palette
* groups.
*
*/
absl::Status LoadAllPalettes(const Bytes& rom_data, PaletteGroupMap& groups);
/**
* @brief Represents a set of palettes used in a SNES graphics system.
*/
struct Paletteset {
/**
* @brief Default constructor for Paletteset.
*/
Paletteset() = default;
/**
* @brief Constructor for Paletteset.
* @param main The main palette.
* @param animated The animated palette.
* @param aux1 The first auxiliary palette.
* @param aux2 The second auxiliary palette.
* @param background The background color.
* @param hud The HUD palette.
* @param spr The sprite palette.
* @param spr2 The second sprite palette.
* @param comp The composite palette.
*/
Paletteset(gfx::SnesPalette main, gfx::SnesPalette animated,
gfx::SnesPalette aux1, gfx::SnesPalette aux2,
gfx::SnesColor background, gfx::SnesPalette hud,
gfx::SnesPalette spr, gfx::SnesPalette spr2, gfx::SnesPalette comp)
: main(main),
animated(animated),
aux1(aux1),
aux2(aux2),
background(background),
hud(hud),
spr(spr),
spr2(spr2),
composite(comp) {}
gfx::SnesPalette main; /**< The main palette. */
gfx::SnesPalette animated; /**< The animated palette. */
gfx::SnesPalette aux1; /**< The first auxiliary palette. */
gfx::SnesPalette aux2; /**< The second auxiliary palette. */
gfx::SnesColor background; /**< The background color. */
gfx::SnesPalette hud; /**< The HUD palette. */
gfx::SnesPalette spr; /**< The sprite palette. */
gfx::SnesPalette spr2; /**< The second sprite palette. */
gfx::SnesPalette composite; /**< The composite palette. */
};
} // namespace gfx } // namespace gfx
} // namespace app } // namespace app

View File

@@ -28,13 +28,13 @@ tile8 UnpackBppTile(const Bytes& data, const uint32_t offset,
bpp_pos[1] = offset + col * 2 + 1; bpp_pos[1] = offset + col * 2 + 1;
char mask = 1 << (7 - row); char mask = 1 << (7 - row);
tile.data[col * 8 + row] = (data[bpp_pos[0]] & mask) == mask; tile.data[col * 8 + row] = (data[bpp_pos[0]] & mask) == mask;
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[1]] & mask) == mask) tile.data[col * 8 + row] |= (uint8_t)((data[bpp_pos[1]] & mask) == mask)
<< 1; << 1;
if (bpp == 3) { if (bpp == 3) {
// When we have 3 bitplanes, the bytes for the third bitplane are after // When we have 3 bitplanes, the bytes for the third bitplane are after
// the 16 bytes of the 2 bitplanes. // the 16 bytes of the 2 bitplanes.
bpp_pos[2] = offset + 16 + col; bpp_pos[2] = offset + 16 + col;
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[2]] & mask) == mask) tile.data[col * 8 + row] |= (uint8_t)((data[bpp_pos[2]] & mask) == mask)
<< 2; << 2;
} }
if (bpp >= 4) { if (bpp >= 4) {
@@ -42,9 +42,9 @@ tile8 UnpackBppTile(const Bytes& data, const uint32_t offset,
// two. // two.
bpp_pos[2] = offset + 16 + col * 2; bpp_pos[2] = offset + 16 + col * 2;
bpp_pos[3] = offset + 16 + col * 2 + 1; bpp_pos[3] = offset + 16 + col * 2 + 1;
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[2]] & mask) == mask) tile.data[col * 8 + row] |= (uint8_t)((data[bpp_pos[2]] & mask) == mask)
<< 2; << 2;
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[3]] & mask) == mask) tile.data[col * 8 + row] |= (uint8_t)((data[bpp_pos[3]] & mask) == mask)
<< 3; << 3;
} }
if (bpp == 8) { if (bpp == 8) {
@@ -52,13 +52,13 @@ tile8 UnpackBppTile(const Bytes& data, const uint32_t offset,
bpp_pos[5] = offset + 32 + col * 2 + 1; bpp_pos[5] = offset + 32 + col * 2 + 1;
bpp_pos[6] = offset + 48 + col * 2; bpp_pos[6] = offset + 48 + col * 2;
bpp_pos[7] = offset + 48 + col * 2 + 1; bpp_pos[7] = offset + 48 + col * 2 + 1;
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[4]] & mask) == mask) tile.data[col * 8 + row] |= (uint8_t)((data[bpp_pos[4]] & mask) == mask)
<< 4; << 4;
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[5]] & mask) == mask) tile.data[col * 8 + row] |= (uint8_t)((data[bpp_pos[5]] & mask) == mask)
<< 5; << 5;
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[6]] & mask) == mask) tile.data[col * 8 + row] |= (uint8_t)((data[bpp_pos[6]] & mask) == mask)
<< 6; << 6;
tile.data[col * 8 + row] |= (uchar)((data[bpp_pos[7]] & mask) == mask) tile.data[col * 8 + row] |= (uint8_t)((data[bpp_pos[7]] & mask) == mask)
<< 7; << 7;
} }
} }
@@ -68,72 +68,74 @@ tile8 UnpackBppTile(const Bytes& data, const uint32_t offset,
Bytes PackBppTile(const tile8& tile, const uint32_t bpp) { Bytes PackBppTile(const tile8& tile, const uint32_t bpp) {
// Allocate memory for output data // Allocate memory for output data
std::vector<uchar> output(bpp * 8, 0); // initialized with 0 std::vector<uint8_t> output(bpp * 8, 0); // initialized with 0
unsigned maxcolor = 2 << bpp; unsigned maxcolor = 2 << bpp;
// Iterate over all columns and rows of the tile // Iterate over all columns and rows of the tile
for (unsigned int col = 0; col < 8; col++) { for (unsigned int col = 0; col < 8; col++) {
for (unsigned int row = 0; row < 8; row++) { for (unsigned int row = 0; row < 8; row++) {
uchar color = tile.data[col * 8 + row]; uint8_t color = tile.data[col * 8 + row];
if (color > maxcolor) { if (color > maxcolor) {
throw std::invalid_argument("Invalid color value."); throw std::invalid_argument("Invalid color value.");
} }
// 1bpp format // 1bpp format
if (bpp == 1) output[col] += (uchar)((color & 1) << (7 - row)); if (bpp == 1) output[col] += (uint8_t)((color & 1) << (7 - row));
// 2bpp format // 2bpp format
if (bpp >= 2) { if (bpp >= 2) {
output[col * 2] += (uchar)((color & 1) << (7 - row)); output[col * 2] += (uint8_t)((color & 1) << (7 - row));
output[col * 2 + 1] += (uchar)((uchar)((color & 2) == 2) << (7 - row)); output[col * 2 + 1] +=
(uint8_t)((uint8_t)((color & 2) == 2) << (7 - row));
} }
// 3bpp format // 3bpp format
if (bpp == 3) if (bpp == 3)
output[16 + col] += (uchar)(((color & 4) == 4) << (7 - row)); output[16 + col] += (uint8_t)(((color & 4) == 4) << (7 - row));
// 4bpp format // 4bpp format
if (bpp >= 4) { if (bpp >= 4) {
output[16 + col * 2] += (uchar)(((color & 4) == 4) << (7 - row)); output[16 + col * 2] += (uint8_t)(((color & 4) == 4) << (7 - row));
output[16 + col * 2 + 1] += (uchar)(((color & 8) == 8) << (7 - row)); output[16 + col * 2 + 1] += (uint8_t)(((color & 8) == 8) << (7 - row));
} }
// 8bpp format // 8bpp format
if (bpp == 8) { if (bpp == 8) {
output[32 + col * 2] += (uchar)(((color & 16) == 16) << (7 - row)); output[32 + col * 2] += (uint8_t)(((color & 16) == 16) << (7 - row));
output[32 + col * 2 + 1] += (uchar)(((color & 32) == 32) << (7 - row)); output[32 + col * 2 + 1] +=
output[48 + col * 2] += (uchar)(((color & 64) == 64) << (7 - row)); (uint8_t)(((color & 32) == 32) << (7 - row));
output[48 + col * 2] += (uint8_t)(((color & 64) == 64) << (7 - row));
output[48 + col * 2 + 1] += output[48 + col * 2 + 1] +=
(uchar)(((color & 128) == 128) << (7 - row)); (uint8_t)(((color & 128) == 128) << (7 - row));
} }
} }
} }
return output; return output;
} }
std::vector<uchar> ConvertBpp(const std::vector<uchar>& tiles, std::vector<uint8_t> ConvertBpp(const std::vector<uint8_t>& tiles,
uint32_t from_bpp, uint32_t to_bpp) { uint32_t from_bpp, uint32_t to_bpp) {
unsigned int nb_tile = tiles.size() / (from_bpp * 8); unsigned int nb_tile = tiles.size() / (from_bpp * 8);
std::vector<uchar> converted(nb_tile * to_bpp * 8); std::vector<uint8_t> converted(nb_tile * to_bpp * 8);
for (unsigned int i = 0; i < nb_tile; i++) { for (unsigned int i = 0; i < nb_tile; i++) {
tile8 tile = UnpackBppTile(tiles, i * from_bpp * 8, from_bpp); tile8 tile = UnpackBppTile(tiles, i * from_bpp * 8, from_bpp);
std::vector<uchar> packed_tile = PackBppTile(tile, to_bpp); std::vector<uint8_t> packed_tile = PackBppTile(tile, to_bpp);
std::memcpy(converted.data() + i * to_bpp * 8, packed_tile.data(), std::memcpy(converted.data() + i * to_bpp * 8, packed_tile.data(),
to_bpp * 8); to_bpp * 8);
} }
return converted; return converted;
} }
std::vector<uchar> Convert3bppTo4bpp(const std::vector<uchar>& tiles) { std::vector<uint8_t> Convert3bppTo4bpp(const std::vector<uint8_t>& tiles) {
return ConvertBpp(tiles, 3, 4); return ConvertBpp(tiles, 3, 4);
} }
std::vector<uchar> Convert4bppTo3bpp(const std::vector<uchar>& tiles) { std::vector<uint8_t> Convert4bppTo3bpp(const std::vector<uint8_t>& tiles) {
return ConvertBpp(tiles, 4, 3); return ConvertBpp(tiles, 4, 3);
} }
Bytes SnesTo8bppSheet(Bytes sheet, int bpp) { Bytes SnesTo8bppSheet(const Bytes& sheet, int bpp) {
int xx = 0; // positions where we are at on the sheet int xx = 0; // positions where we are at on the sheet
int yy = 0; int yy = 0;
int pos = 0; int pos = 0;
@@ -146,6 +148,11 @@ Bytes SnesTo8bppSheet(Bytes sheet, int bpp) {
buffer_size = 0x2000; buffer_size = 0x2000;
} else if (bpp == 3) { } else if (bpp == 3) {
bpp = 24; bpp = 24;
} else if (bpp == 4) {
bpp = 32;
buffer_size = 0x4000;
} else if (bpp == 8) {
bpp = 64;
} }
Bytes sheet_buffer_out(buffer_size); Bytes sheet_buffer_out(buffer_size);
@@ -313,34 +320,45 @@ TileInfo WordToTileInfo(uint16_t word) {
return TileInfo(id, palette, vertical_mirror, horizontal_mirror, over); return TileInfo(id, palette, vertical_mirror, horizontal_mirror, over);
} }
ushort TileInfoToShort(TileInfo tile_info) { uint16_t TileInfoToShort(TileInfo tile_info) {
ushort result = 0; // uint16_t result = 0;
// Copy the id_ value // // Copy the id_ value
result |= tile_info.id_ & 0x3FF; // ids are 10 bits // result |= tile_info.id_ & 0x3FF; // ids are 10 bits
// Set the vertical_mirror_, horizontal_mirror_, and over_ flags // // Set the vertical_mirror_, horizontal_mirror_, and over_ flags
result |= (tile_info.vertical_mirror_ ? 1 : 0) << 10; // result |= (tile_info.vertical_mirror_ ? 1 : 0) << 10;
result |= (tile_info.horizontal_mirror_ ? 1 : 0) << 11; // result |= (tile_info.horizontal_mirror_ ? 1 : 0) << 11;
result |= (tile_info.over_ ? 1 : 0) << 12; // result |= (tile_info.over_ ? 1 : 0) << 12;
// Set the palette_ // // Set the palette_
result |= (tile_info.palette_ & 0x07) << 13; // palettes are 3 bits // result |= (tile_info.palette_ & 0x07) << 13; // palettes are 3 bits
return result; uint16_t value = 0;
// vhopppcc cccccccc
if (tile_info.over_) {
value |= core::TilePriorityBit;
}
if (tile_info.horizontal_mirror_) {
value |= core::TileHFlipBit;
}
if (tile_info.vertical_mirror_) {
value |= core::TileVFlipBit;
}
value |= (uint16_t)((tile_info.palette_ << 10) & 0x1C00);
value |= (uint16_t)(tile_info.id_ & core::TileNameMask);
return value;
} }
TileInfo GetTilesInfo(ushort tile) { TileInfo GetTilesInfo(uint16_t tile) {
// vhopppcc cccccccc // vhopppcc cccccccc
bool o = false; uint16_t tid = (uint16_t)(tile & core::TileNameMask);
bool v = false; uint8_t p = (uint8_t)((tile >> 10) & 0x07);
bool h = false;
auto tid = (ushort)(tile & core::TileNameMask);
auto p = (uchar)((tile >> 10) & 0x07);
o = ((tile & core::TilePriorityBit) == core::TilePriorityBit); bool o = ((tile & core::TilePriorityBit) == core::TilePriorityBit);
h = ((tile & core::TileHFlipBit) == core::TileHFlipBit); bool h = ((tile & core::TileHFlipBit) == core::TileHFlipBit);
v = ((tile & core::TileVFlipBit) == core::TileVFlipBit); bool v = ((tile & core::TileVFlipBit) == core::TileVFlipBit);
return TileInfo(tid, p, v, h, o); return TileInfo(tid, p, v, h, o);
} }

View File

@@ -11,10 +11,10 @@ namespace yaze {
namespace app { namespace app {
namespace gfx { namespace gfx {
constexpr uchar kGraphicsBitmap[8] = {0x80, 0x40, 0x20, 0x10, constexpr uint8_t kGraphicsBitmap[8] = {0x80, 0x40, 0x20, 0x10,
0x08, 0x04, 0x02, 0x01}; 0x08, 0x04, 0x02, 0x01};
Bytes SnesTo8bppSheet(Bytes sheet, int bpp); Bytes SnesTo8bppSheet(const Bytes& sheet, int bpp);
Bytes Bpp8SnesToIndexed(Bytes data, uint64_t bpp = 0); Bytes Bpp8SnesToIndexed(Bytes data, uint64_t bpp = 0);
struct tile8 { struct tile8 {
@@ -29,24 +29,29 @@ tile8 UnpackBppTile(const Bytes& data, const uint32_t offset,
Bytes PackBppTile(const tile8& tile, const uint32_t bpp); Bytes PackBppTile(const tile8& tile, const uint32_t bpp);
std::vector<uchar> ConvertBpp(const std::vector<uchar>& tiles, std::vector<uint8_t> ConvertBpp(const std::vector<uint8_t>& tiles,
uint32_t from_bpp, uint32_t to_bpp); uint32_t from_bpp, uint32_t to_bpp);
std::vector<uchar> Convert3bppTo4bpp(const std::vector<uchar>& tiles); std::vector<uint8_t> Convert3bppTo4bpp(const std::vector<uint8_t>& tiles);
std::vector<uchar> Convert4bppTo3bpp(const std::vector<uchar>& tiles); std::vector<uint8_t> Convert4bppTo3bpp(const std::vector<uint8_t>& tiles);
// vhopppcc cccccccc /**
// [0, 1] * @brief SNES 16-bit tile metadata container
// [2, 3] *
* Format:
* vhopppcc cccccccc
* [0, 1]
* [2, 3]
*/
class TileInfo { class TileInfo {
public: public:
ushort id_; uint16_t id_;
uint8_t palette_;
bool over_; bool over_;
bool vertical_mirror_; bool vertical_mirror_;
bool horizontal_mirror_; bool horizontal_mirror_;
uchar palette_;
TileInfo() = default; TileInfo() = default;
TileInfo(ushort id, uchar palette, bool v, bool h, bool o) TileInfo(uint16_t id, uint8_t palette, bool v, bool h, bool o)
: id_(id), : id_(id),
over_(o), over_(o),
vertical_mirror_(v), vertical_mirror_(v),
@@ -63,10 +68,13 @@ class TileInfo {
uint16_t TileInfoToWord(TileInfo tile_info); uint16_t TileInfoToWord(TileInfo tile_info);
TileInfo WordToTileInfo(uint16_t word); TileInfo WordToTileInfo(uint16_t word);
ushort TileInfoToShort(TileInfo tile_info); uint16_t TileInfoToShort(TileInfo tile_info);
TileInfo GetTilesInfo(ushort tile); TileInfo GetTilesInfo(uint16_t tile);
/**
* @brief Tile composition of four 16x16 tiles.
*/
class Tile32 { class Tile32 {
public: public:
uint16_t tile0_; uint16_t tile0_;
@@ -90,10 +98,17 @@ class Tile32 {
// Constructor from packed value // Constructor from packed value
Tile32(uint64_t packedVal) { Tile32(uint64_t packedVal) {
tile0_ = (packedVal >> 48) & 0xFFFF; tile0_ = (uint16_t)packedVal;
tile1_ = (packedVal >> 32) & 0xFFFF; tile1_ = (uint16_t)(packedVal >> 16);
tile2_ = (packedVal >> 16) & 0xFFFF; tile2_ = (uint16_t)(packedVal >> 32);
tile3_ = packedVal & 0xFFFF; tile3_ = (uint16_t)(packedVal >> 48);
}
// Get packed uint64_t representation
uint64_t GetPackedValue() const {
return static_cast<uint64_t>(tile3_) << 48 |
(static_cast<uint64_t>(tile2_) << 32) |
(static_cast<uint64_t>(tile1_) << 16) | tile0_;
} }
// Equality operator // Equality operator
@@ -104,16 +119,11 @@ class Tile32 {
// Inequality operator // Inequality operator
bool operator!=(const Tile32& other) const { return !(*this == other); } 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_);
}
}; };
/**
* @brief Tile composition of four 8x8 tiles.
*/
class Tile16 { class Tile16 {
public: public:
TileInfo tile0_; TileInfo tile0_;
@@ -139,22 +149,25 @@ class Tile16 {
bool operator!=(const Tile16& other) const { return !(*this == other); } bool operator!=(const Tile16& other) const { return !(*this == other); }
}; };
class OAMTile { /**
* @brief Object Attribute Memory tile abstraction container
*/
class OamTile {
public: public:
int x_; int x_;
int y_; int y_;
int mx_; int mx_;
int my_; int my_;
int pal_; int pal_;
ushort tile_; uint16_t tile_;
OAMTile() = default; OamTile() = default;
OAMTile(int x, int y, ushort tile, int pal, bool upper = false, int mx = 0, OamTile(int x, int y, uint16_t tile, int pal, bool upper = false, int mx = 0,
int my = 0) int my = 0)
: x_(x), y_(y), mx_(mx), my_(my), pal_(pal) { : x_(x), y_(y), mx_(mx), my_(my), pal_(pal) {
if (upper) { if (upper) {
tile_ = (ushort)(tile + 512); tile_ = (uint16_t)(tile + 512);
} else { } else {
tile_ = (ushort)(tile + 256 + 512); tile_ = (uint16_t)(tile + 256 + 512);
} }
} }
}; };

114
src/app/gfx/tilesheet.cc Normal file
View File

@@ -0,0 +1,114 @@
#include "app/gfx/tilesheet.h"
#include <memory>
#include <vector>
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_color.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
namespace yaze {
namespace app {
namespace gfx {
absl::StatusOr<Tilesheet> CreateTilesheetFromGraphicsBuffer(
const uint8_t* graphics_buffer, int width, int height, TileType tile_type,
int sheet_id) {
Tilesheet tilesheet;
// Calculate the offset in the graphics buffer based on the sheet ID
int sheet_offset = sheet_id * width * height;
// Initialize the tilesheet with the specified width, height, and tile type
tilesheet.Init(width, height, tile_type);
// Iterate over the tiles in the sheet and copy them into the tilesheet
for (int row = 0; row < height; ++row) {
for (int col = 0; col < width; ++col) {
// Calculate the index of the current tile in the graphics buffer
int tile_index = sheet_offset + (row * width + col) * 64;
// Copy the tile data into the tilesheet
for (int y = 0; y < 8; ++y) {
for (int x = 0; x < 8; ++x) {
int srcIndex = tile_index + (y * 8 + x);
int destX = col * 8 + x;
int destY = row * 8 + y;
int destIndex = (destY * width * 8) + destX;
tilesheet.mutable_bitmap()->mutable_data()[destIndex] =
graphics_buffer[srcIndex];
}
}
}
}
return tilesheet;
}
void Tilesheet::Init(int width, int height, TileType tile_type) {
bitmap_ = std::make_shared<Bitmap>(width, height, 8, 0x20000);
internal_data_.resize(0x20000);
tile_type_ = tile_type;
if (tile_type_ == TileType::Tile8) {
tile_width_ = 8;
tile_height_ = 8;
} else {
tile_width_ = 16;
tile_height_ = 16;
}
}
void Tilesheet::ComposeTile16(const std::vector<uint8_t>& graphics_buffer,
const TileInfo& top_left,
const TileInfo& top_right,
const TileInfo& bottom_left,
const TileInfo& bottom_right) {
// Calculate the base position for this Tile16 in the full-size bitmap
int tiles_per_row = bitmap_->width() / tile_width_;
int tile16_row = num_tiles_ / tiles_per_row;
int tile16_column = num_tiles_ % tiles_per_row;
int baseX = tile16_column * tile_width_;
int baseY = tile16_row * tile_height_;
// Compose and place each part of the Tile16
ComposeAndPlaceTilePart(graphics_buffer, top_left, baseX, baseY);
ComposeAndPlaceTilePart(graphics_buffer, top_right, baseX + 8, baseY);
ComposeAndPlaceTilePart(graphics_buffer, bottom_left, baseX, baseY + 8);
ComposeAndPlaceTilePart(graphics_buffer, bottom_right, baseX + 8, baseY + 8);
tile_info_.push_back({top_left, top_right, bottom_left, bottom_right});
num_tiles_++;
}
void Tilesheet::ComposeAndPlaceTilePart(
const std::vector<uint8_t>& graphics_buffer, const TileInfo& tile_info,
int baseX, int baseY) {
std::vector<uint8_t> tile_data =
FetchTileDataFromGraphicsBuffer(graphics_buffer, tile_info.id_);
if (tile_info.vertical_mirror_) {
MirrorTileDataVertically(tile_data);
}
if (tile_info.horizontal_mirror_) {
MirrorTileDataHorizontally(tile_data);
}
// Place the tile data into the full-size bitmap at the calculated position
for (int y = 0; y < 8; ++y) {
for (int x = 0; x < 8; ++x) {
int srcIndex = y * 8 + x;
int destX = baseX + x;
int destY = baseY + y;
int destIndex = (destY * bitmap_->width()) + destX;
internal_data_[destIndex] = tile_data[srcIndex];
}
}
bitmap_->set_data(internal_data_);
}
} // namespace gfx
} // namespace app
} // namespace yaze

198
src/app/gfx/tilesheet.h Normal file
View File

@@ -0,0 +1,198 @@
#ifndef YAZE_APP_GFX_TILESHEET_H
#define YAZE_APP_GFX_TILESHEET_H
#include <memory>
#include <vector>
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_color.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
namespace yaze {
namespace app {
namespace gfx {
enum class TileType { Tile8, Tile16 };
/**
* @class Tilesheet
* @brief Represents a tilesheet, which is a collection of tiles stored in a
* bitmap.
*
* The Tilesheet class provides methods to manipulate and extract tiles from the
* tilesheet. It also supports copying and mirroring tiles within the tilesheet.
*/
class Tilesheet {
public:
Tilesheet() = default;
Tilesheet(std::shared_ptr<Bitmap> bitmap, int tileWidth, int tileHeight,
TileType tile_type)
: bitmap_(std::move(bitmap)),
tile_width_(tileWidth),
tile_height_(tileHeight),
tile_type_(tile_type) {}
void Init(int width, int height, TileType tile_type);
void ComposeTile16(const std::vector<uint8_t>& graphics_buffer,
const TileInfo& top_left, const TileInfo& top_right,
const TileInfo& bottom_left, const TileInfo& bottom_right);
void ComposeAndPlaceTilePart(const std::vector<uint8_t>& graphics_buffer,
const TileInfo& tile_info, int baseX, int baseY);
// Extracts a tile from the tilesheet
Bitmap GetTile(int tileX, int tileY, int bmp_width, int bmp_height) {
std::vector<uint8_t> tileData(tile_width_ * tile_height_);
int tileDataOffset = 0;
bitmap_->Get8x8Tile(CalculateTileIndex(tileX, tileY), tileX, tileY,
tileData, tileDataOffset);
return Bitmap(bmp_width, bmp_height, bitmap_->depth(), tileData);
}
Bitmap GetTile16(int tile_x, int tile_y) {
std::vector<uint8_t> tile_data(tile_width_ * tile_height_, 0x00);
int tileDataOffset = 0;
bitmap_->Get16x16Tile(tile_x, tile_y, tile_data, tileDataOffset);
return Bitmap(16, 16, bitmap_->depth(), tile_data);
}
Bitmap GetTile16(int tile_id) {
int tiles_per_row = bitmap_->width() / tile_width_;
int tile_x = (tile_id % tiles_per_row) * tile_width_;
int tile_y = (tile_id / tiles_per_row) * tile_height_;
return GetTile16(tile_x, tile_y);
}
// Copy a tile within the tilesheet
void CopyTile(int srcX, int srcY, int destX, int destY, bool mirrorX = false,
bool mirrorY = false) {
auto srcTile = GetTile(srcX, srcY, tile_width_, tile_height_);
auto destTileData = srcTile.vector();
MirrorTileData(destTileData, mirrorX, mirrorY);
WriteTile(destX, destY, destTileData);
}
// Other methods and properties
auto bitmap() const { return bitmap_; }
auto mutable_bitmap() { return bitmap_; }
auto num_tiles() const { return num_tiles_; }
auto tile_width() const { return tile_width_; }
auto tile_height() const { return tile_height_; }
auto set_palette(gfx::SnesPalette& palette) { palette_ = palette; }
auto palette() const { return palette_; }
auto tile_type() const { return tile_type_; }
auto tile_info() const { return tile_info_; }
auto mutable_tile_info() { return tile_info_; }
private:
int CalculateTileIndex(int x, int y) {
return y * (bitmap_->width() / tile_width_) + x;
}
std::vector<uint8_t> FetchTileDataFromGraphicsBuffer(
const std::vector<uint8_t>& graphics_buffer, int tile_id) {
const int tileWidth = 8;
const int tileHeight = 8;
const int bufferWidth = 128;
const int sheetHeight = 32;
const int tilesPerRow = bufferWidth / tileWidth;
const int rowsPerSheet = sheetHeight / tileHeight;
const int tilesPerSheet = tilesPerRow * rowsPerSheet;
// Calculate the position in the graphics_buffer_ based on tile_id
std::vector<uint8_t> tile_data(0x40, 0x00);
int sheet = (tile_id / tilesPerSheet) % 4 + 212;
int positionInSheet = tile_id % tilesPerSheet;
int rowInSheet = positionInSheet / tilesPerRow;
int columnInSheet = positionInSheet % tilesPerRow;
// Ensure that the sheet ID is between 212 and 215
assert(sheet >= 212 && sheet <= 215);
// Copy the tile data from the graphics_buffer_ to tile_data
for (int y = 0; y < 8; ++y) {
for (int x = 0; x < 8; ++x) {
// Calculate the position in the graphics_buffer_ based on tile_id
int srcX = columnInSheet * tileWidth + x;
int srcY = (sheet * sheetHeight) + (rowInSheet * tileHeight) + y;
int src_index = (srcY * bufferWidth) + srcX;
int dest_index = y * tileWidth + x;
tile_data[dest_index] = graphics_buffer[src_index];
}
}
return tile_data;
}
void MirrorTileDataVertically(std::vector<uint8_t>& tileData) {
std::vector<uint8_t> tile_data_copy = tileData;
for (int i = 0; i < 8; ++i) { // For each row
for (int j = 0; j < 8; ++j) { // For each column
int src_index = i * 8 + j;
int dest_index = (7 - i) * 8 + j; // Calculate the mirrored row
tile_data_copy[dest_index] = tileData[src_index];
}
}
tileData = tile_data_copy;
}
void MirrorTileDataHorizontally(std::vector<uint8_t>& tileData) {
std::vector<uint8_t> tile_data_copy = tileData;
for (int i = 0; i < 8; ++i) { // For each row
for (int j = 0; j < 8; ++j) { // For each column
int src_index = i * 8 + j;
int dest_index = i * 8 + (7 - j); // Calculate the mirrored column
tile_data_copy[dest_index] = tileData[src_index];
}
}
tileData = tile_data_copy;
}
void MirrorTileData(std::vector<uint8_t>& tileData, bool mirrorX,
bool mirrorY) {
// Implement logic to mirror tile data horizontally and/or vertically
std::vector tile_data_copy = tileData;
if (mirrorX) {
MirrorTileDataHorizontally(tile_data_copy);
}
if (mirrorY) {
MirrorTileDataVertically(tile_data_copy);
}
tileData = tile_data_copy;
}
void WriteTile(int x, int y, const std::vector<uint8_t>& tileData) {
int tileDataOffset = 0;
bitmap_->Get8x8Tile(CalculateTileIndex(x, y), x, y,
const_cast<std::vector<uint8_t>&>(tileData),
tileDataOffset);
}
gfx::SnesPalette palette_;
std::vector<uint8_t> internal_data_;
std::shared_ptr<Bitmap> bitmap_;
struct InternalTile16 {
std::array<TileInfo, 4> tiles;
};
std::vector<InternalTile16> tile_info_;
int num_tiles_ = 0;
int tile_width_ = 0;
int tile_height_ = 0;
TileType tile_type_;
};
absl::StatusOr<Tilesheet> CreateTilesheetFromGraphicsBuffer(
const uint8_t* graphics_buffer, int width, int height, TileType tile_type,
int sheet_id);
} // namespace gfx
} // namespace app
} // namespace yaze
#endif // YAZE_APP_GFX_TILESHEET_H

View File

@@ -5,6 +5,7 @@
#include <cmath> #include <cmath>
#include <string> #include <string>
#include "app/editor/graphics_editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/rom.h" #include "app/rom.h"
@@ -31,18 +32,17 @@ void Canvas::Update(const gfx::Bitmap &bitmap, ImVec2 bg_size, int tile_size,
DrawOverlay(); DrawOverlay();
} }
void Canvas::UpdateColorPainter(const gfx::Bitmap &bitmap, const ImVec4 &color, void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
const std::function<void()> &event, const std::function<void()> &event,
ImVec2 bg_size, int tile_size, float scale, int tile_size, float scale) {
float grid_size) {
global_scale_ = scale; global_scale_ = scale;
DrawBackground(bg_size); DrawBackground();
DrawContextMenu(); DrawContextMenu();
DrawBitmap(bitmap, 2, scale); DrawBitmap(bitmap, 2, scale);
if (DrawSolidTilePainter(color, tile_size)) { if (DrawSolidTilePainter(color, tile_size)) {
event(); event();
} }
DrawGrid(grid_size); DrawGrid();
DrawOverlay(); DrawOverlay();
} }
@@ -55,7 +55,15 @@ void Canvas::UpdateEvent(const std::function<void()> &event, ImVec2 bg_size,
DrawOverlay(); DrawOverlay();
} }
void Canvas::DrawBackground(ImVec2 canvas_size) { void Canvas::UpdateInfoGrid(ImVec2 bg_size, int tile_size, float scale,
float grid_size) {
enable_custom_labels_ = true;
DrawBackground(bg_size);
DrawGrid(grid_size);
DrawOverlay();
}
void Canvas::DrawBackground(ImVec2 canvas_size, bool can_drag) {
canvas_p0_ = ImGui::GetCursorScreenPos(); canvas_p0_ = ImGui::GetCursorScreenPos();
if (!custom_canvas_size_) canvas_sz_ = ImGui::GetContentRegionAvail(); if (!custom_canvas_size_) canvas_sz_ = ImGui::GetContentRegionAvail();
if (canvas_size.x != 0) canvas_sz_ = canvas_size; if (canvas_size.x != 0) canvas_sz_ = canvas_size;
@@ -64,26 +72,37 @@ void Canvas::DrawBackground(ImVec2 canvas_size) {
draw_list_ = ImGui::GetWindowDrawList(); // Draw border and background color draw_list_ = ImGui::GetWindowDrawList(); // Draw border and background color
draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor); draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor);
draw_list_->AddRect(canvas_p0_, canvas_p1_, kRectangleBorder); draw_list_->AddRect(canvas_p0_, canvas_p1_, kRectangleBorder);
}
void Canvas::DrawContextMenu() {
const ImGuiIO &io = ImGui::GetIO(); const ImGuiIO &io = ImGui::GetIO();
auto scaled_sz = auto scaled_sz =
ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_); ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_);
ImGui::InvisibleButton("canvas", scaled_sz, kMouseFlags); ImGui::InvisibleButton("canvas", scaled_sz, kMouseFlags);
const bool is_active = ImGui::IsItemActive(); // Held
if (draggable_ && ImGui::IsItemHovered()) {
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;
}
}
}
void Canvas::DrawContextMenu(gfx::Bitmap *bitmap) {
const ImGuiIO &io = ImGui::GetIO();
auto scaled_sz =
ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_);
const ImVec2 origin(canvas_p0_.x + scrolling_.x, const ImVec2 origin(canvas_p0_.x + scrolling_.x,
canvas_p0_.y + scrolling_.y); // Lock scrolled origin canvas_p0_.y + scrolling_.y); // Lock scrolled origin
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); 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) // Context menu (under default mouse threshold)
if (ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right); if (ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right);
enable_context_menu_ && drag_delta.x == 0.0f && drag_delta.y == 0.0f) enable_context_menu_ && drag_delta.x == 0.0f && drag_delta.y == 0.0f)
@@ -91,30 +110,47 @@ void Canvas::DrawContextMenu() {
// Contents of the Context Menu // Contents of the Context Menu
if (ImGui::BeginPopup("context")) { 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)) { if (ImGui::MenuItem("Reset Position", nullptr, false)) {
scrolling_.x = 0; scrolling_.x = 0;
scrolling_.y = 0; scrolling_.y = 0;
} }
ImGui::MenuItem("Show Grid", nullptr, &enable_grid_);
ImGui::Selectable("Show Position Labels", &enable_hex_tile_labels_);
if (ImGui::BeginMenu("Canvas Properties")) {
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::EndMenu();
}
if (bitmap != nullptr) {
if (ImGui::BeginMenu("Bitmap Properties")) {
ImGui::Text("Size: %.0f x %.0f", scaled_sz.x, scaled_sz.y);
ImGui::Text("Pitch: %s",
absl::StrFormat("%d", bitmap->surface()->pitch).c_str());
ImGui::Text("BitsPerPixel: %d",
bitmap->surface()->format->BitsPerPixel);
ImGui::Text("BytesPerPixel: %d",
bitmap->surface()->format->BytesPerPixel);
ImGui::EndMenu();
}
}
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("8x8", nullptr, custom_step_ == 8.0f)) { if (ImGui::BeginMenu("Grid Tile Size")) {
custom_step_ = 8.0f; 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;
}
ImGui::EndMenu();
} }
if (ImGui::MenuItem("16x16", nullptr, custom_step_ == 16.0f)) { // TODO: Add a menu item for selecting the palette
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(); ImGui::EndPopup();
} }
@@ -136,30 +172,37 @@ bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) {
// Calculate the coordinates of the mouse // Calculate the coordinates of the mouse
ImVec2 painter_pos; ImVec2 painter_pos;
painter_pos.x = std::floor((double)mouse_pos.x / size) * size; painter_pos.x =
painter_pos.y = std::floor((double)mouse_pos.y / size) * size; std::floor((double)mouse_pos.x / (size * scale)) * (size * scale);
painter_pos.y =
std::floor((double)mouse_pos.y / (size * scale)) * (size * scale);
auto painter_pos_end = ImVec2(painter_pos.x + size, painter_pos.y + size); mouse_pos_in_canvas_ = painter_pos;
auto painter_pos_end =
ImVec2(painter_pos.x + (size * scale), painter_pos.y + (size * scale));
points_.push_back(painter_pos); points_.push_back(painter_pos);
points_.push_back(painter_pos_end); points_.push_back(painter_pos_end);
if (bitmap.IsActive()) { if (bitmap.is_active()) {
draw_list_->AddImage( draw_list_->AddImage(
(void *)bitmap.texture(), (void *)bitmap.texture(),
ImVec2(origin.x + painter_pos.x, origin.y + painter_pos.y), ImVec2(origin.x + painter_pos.x, origin.y + painter_pos.y),
ImVec2(origin.x + painter_pos.x + bitmap.width() * scale, ImVec2(origin.x + painter_pos.x + (size)*scale,
origin.y + painter_pos.y + bitmap.height() * scale)); origin.y + painter_pos.y + size * scale));
} }
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
// Draw the currently selected tile on the overworld here // Draw the currently selected tile on the overworld here
// Save the coordinates of the selected tile. // Save the coordinates of the selected tile.
drawn_tile_pos_ = io.MousePos; drawn_tile_pos_ = painter_pos;
SDL_Log("Drawn tile position: %.0f, %.0f", drawn_tile_pos_.x, return true;
drawn_tile_pos_.y); } else if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
// Draw the currently selected tile on the overworld here
// Save the coordinates of the selected tile.
drawn_tile_pos_ = painter_pos;
return true; return true;
} }
} else { } else {
// Erase the hover when the mouse is not in the canvas window. // Erase the hover when the mouse is not in the canvas window.
points_.clear(); points_.clear();
@@ -227,7 +270,7 @@ bool Canvas::DrawSolidTilePainter(const ImVec4 &color, int tile_size) {
return false; return false;
} }
void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap &bitmap, void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap,
ImVec4 color) { ImVec4 color) {
const ImVec2 position = drawn_tile_pos_; const ImVec2 position = drawn_tile_pos_;
int tile_index_x = static_cast<int>(position.x / global_scale_) / tile_size; int tile_index_x = static_cast<int>(position.x / global_scale_) / tile_size;
@@ -240,15 +283,15 @@ void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap &bitmap,
for (int x = 0; x < tile_size; ++x) { for (int x = 0; x < tile_size; ++x) {
// Calculate the actual pixel index in the bitmap // Calculate the actual pixel index in the bitmap
int pixel_index = int pixel_index =
(start_position.y + y) * bitmap.width() + (start_position.x + x); (start_position.y + y) * bitmap->width() + (start_position.x + x);
// Write the color to the pixel // Write the color to the pixel
bitmap.WriteColor(pixel_index, color); bitmap->WriteColor(pixel_index, color);
} }
} }
} }
void Canvas::DrawTileSelector(int size) { bool Canvas::DrawTileSelector(int size) {
const ImGuiIO &io = ImGui::GetIO(); const ImGuiIO &io = ImGui::GetIO();
const bool is_hovered = ImGui::IsItemHovered(); const bool is_hovered = ImGui::IsItemHovered();
const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
@@ -264,50 +307,14 @@ void Canvas::DrawTileSelector(int size) {
points_.push_back(painter_pos); points_.push_back(painter_pos);
points_.push_back(ImVec2(painter_pos.x + size, painter_pos.y + size)); points_.push_back(ImVec2(painter_pos.x + size, painter_pos.y + size));
mouse_pos_in_canvas_ = painter_pos;
} }
}
void Canvas::HandleTileEdits(Canvas &blockset_canvas, if (is_hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
std::vector<gfx::Bitmap> &source_blockset, return true;
gfx::Bitmap &destination, int &current_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, return false;
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) { void Canvas::DrawBitmap(const Bitmap &bitmap, int border_offset, bool ready) {
@@ -329,14 +336,15 @@ void Canvas::DrawBitmap(const Bitmap &bitmap, int border_offset, float scale) {
} }
void Canvas::DrawBitmap(const Bitmap &bitmap, int x_offset, int y_offset, void Canvas::DrawBitmap(const Bitmap &bitmap, int x_offset, int y_offset,
float scale) { float scale, int alpha) {
draw_list_->AddImage( draw_list_->AddImage(
(void *)bitmap.texture(), (void *)bitmap.texture(),
ImVec2(canvas_p0_.x + x_offset + scrolling_.x, ImVec2(canvas_p0_.x + x_offset + scrolling_.x,
canvas_p0_.y + y_offset + scrolling_.y), canvas_p0_.y + y_offset + scrolling_.y),
ImVec2( ImVec2(
canvas_p0_.x + x_offset + scrolling_.x + (bitmap.width() * scale), canvas_p0_.x + x_offset + scrolling_.x + (bitmap.width() * scale),
canvas_p0_.y + y_offset + scrolling_.y + (bitmap.height() * scale))); canvas_p0_.y + y_offset + scrolling_.y + (bitmap.height() * scale)),
ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha));
} }
// TODO: Add parameters for sizing and positioning // TODO: Add parameters for sizing and positioning
@@ -358,65 +366,215 @@ void Canvas::DrawOutline(int x, int y, int w, int h) {
canvas_p0_.y + scrolling_.y + y); canvas_p0_.y + scrolling_.y + y);
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w, ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
canvas_p0_.y + scrolling_.y + y + h); canvas_p0_.y + scrolling_.y + y + h);
draw_list_->AddRect(origin, size, IM_COL32(255, 255, 255, 255)); draw_list_->AddRect(origin, size, IM_COL32(255, 255, 255, 200), 0, 0, 1.5f);
} }
void Canvas::DrawSelectRect(int tile_size, float scale) { void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) {
const ImGuiIO &io = ImGui::GetIO(); ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
static ImVec2 drag_start_pos; canvas_p0_.y + scrolling_.y + y);
static bool dragging = false; ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
canvas_p0_.y + scrolling_.y + y + h);
draw_list_->AddRect(origin, size,
IM_COL32(color.x, color.y, color.z, color.w));
}
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) {
if (!points_.empty()) { ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
points_.clear(); canvas_p0_.y + scrolling_.y + y);
} ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
// Snap the start position to the nearest grid point with scaling canvas_p0_.y + scrolling_.y + y + h);
// consideration draw_list_->AddRect(origin, size, color);
drag_start_pos.x = }
std::floor(io.MousePos.x / (tile_size * scale)) * tile_size * scale;
drag_start_pos.y = void Canvas::DrawSelectRectTile16(int current_map) {
std::floor(io.MousePos.y / (tile_size * scale)) * tile_size * scale; 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);
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
// Calculate the coordinates of the mouse
ImVec2 painter_pos;
painter_pos.x = std::floor((double)mouse_pos.x / 16) * 16;
painter_pos.y = std::floor((double)mouse_pos.y / 16) * 16;
int painter_x = painter_pos.x;
int painter_y = painter_pos.y;
constexpr int small_map_size = 0x200;
auto tile16_x = (painter_x % small_map_size) / (small_map_size / 0x20);
auto tile16_y = (painter_y % small_map_size) / (small_map_size / 0x20);
int superY = current_map / 8;
int superX = current_map % 8;
int index_x = superX * 0x20 + tile16_x;
int index_y = superY * 0x20 + tile16_y;
selected_tiles_.push_back(ImVec2(index_x, index_y));
}
}
namespace {
ImVec2 AlignPosToGrid(ImVec2 pos, float scale) {
return ImVec2(std::floor((double)pos.x / scale) * scale,
std::floor((double)pos.y / scale) * scale);
}
} // namespace
void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) {
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);
static ImVec2 drag_start_pos;
const float scaled_size = tile_size * scale;
static bool dragging = false;
constexpr int small_map_size = 0x200;
int superY = current_map / 8;
int superX = current_map % 8;
// Handle right click for single tile selection
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
ImVec2 painter_pos = AlignPosToGrid(mouse_pos, scaled_size);
int painter_x = painter_pos.x;
int painter_y = painter_pos.y;
auto tile16_x = (painter_x % small_map_size) / (small_map_size / 0x20);
auto tile16_y = (painter_y % small_map_size) / (small_map_size / 0x20);
int index_x = superX * 0x20 + tile16_x;
int index_y = superY * 0x20 + tile16_y;
selected_tile_pos_ = ImVec2(index_x, index_y);
selected_points_.clear();
select_rect_active_ = false;
// Start drag position for rectangle selection
drag_start_pos = {std::floor(mouse_pos.x / scaled_size) * scaled_size,
std::floor(mouse_pos.y / scaled_size) * scaled_size};
}
// Calculate the rectangle's top-left and bottom-right corners
ImVec2 drag_end_pos = AlignPosToGrid(mouse_pos, scaled_size);
if (ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
auto start = ImVec2(canvas_p0_.x + drag_start_pos.x,
canvas_p0_.y + drag_start_pos.y);
auto end = ImVec2(canvas_p0_.x + drag_end_pos.x + tile_size,
canvas_p0_.y + drag_end_pos.y + tile_size);
draw_list_->AddRect(start, end, kRectangleBorder);
dragging = true; dragging = true;
} }
if (dragging) { if (dragging) {
ImVec2 current_pos = io.MousePos; // Release dragging mode
ImVec2 grid_pos; if (!ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
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; 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); // Calculate the bounds of the rectangle in terms of 16x16 tile indices
points_.push_back(scaled_rect_max); constexpr int tile16_size = 16;
int start_x = std::floor(drag_start_pos.x / scaled_size) * tile16_size;
int start_y = std::floor(drag_start_pos.y / scaled_size) * tile16_size;
int end_x = std::floor(drag_end_pos.x / scaled_size) * tile16_size;
int end_y = std::floor(drag_end_pos.y / scaled_size) * tile16_size;
// Swap the start and end positions if they are in the wrong order
if (start_x > end_x) std::swap(start_x, end_x);
if (start_y > end_y) std::swap(start_y, end_y);
selected_tiles_.clear();
// Number of tiles per local map (since each tile is 16x16)
constexpr int tiles_per_local_map = small_map_size / 16;
// Loop through the tiles in the rectangle and store their positions
for (int y = start_y; y <= end_y; y += tile16_size) {
for (int x = start_x; x <= end_x; x += tile16_size) {
// Determine which local map (512x512) the tile is in
int local_map_x = x / small_map_size;
int local_map_y = y / small_map_size;
// Calculate the tile's position within its local map
int tile16_x = (x % small_map_size) / tile16_size;
int tile16_y = (y % small_map_size) / tile16_size;
// Calculate the index within the overall map structure
int index_x = local_map_x * tiles_per_local_map + tile16_x;
int index_y = local_map_y * tiles_per_local_map + tile16_y;
selected_tiles_.push_back(ImVec2(index_x, index_y));
}
}
// Clear and add the calculated rectangle points
selected_points_.clear();
selected_points_.push_back(drag_start_pos);
selected_points_.push_back(drag_end_pos);
select_rect_active_ = true;
} }
} }
} }
void Canvas::DrawBitmapGroup(std::vector<int> &group,
std::vector<gfx::Bitmap> &tile16_individual_,
int tile_size, float scale) {
if (selected_points_.size() != 2) {
// points_ should contain exactly two points
return;
}
if (group.empty()) {
// group should not be empty
return;
}
// Top-left and bottom-right corners of the rectangle
ImVec2 rect_top_left = selected_points_[0];
ImVec2 rect_bottom_right = selected_points_[1];
// Calculate the start and end tiles in the grid
int start_tile_x =
static_cast<int>(std::floor(rect_top_left.x / (tile_size * scale)));
int start_tile_y =
static_cast<int>(std::floor(rect_top_left.y / (tile_size * scale)));
int end_tile_x =
static_cast<int>(std::floor(rect_bottom_right.x / (tile_size * scale)));
int end_tile_y =
static_cast<int>(std::floor(rect_bottom_right.y / (tile_size * scale)));
if (start_tile_x > end_tile_x) std::swap(start_tile_x, end_tile_x);
if (start_tile_y > end_tile_y) std::swap(start_tile_y, end_tile_y);
// Calculate the size of the rectangle in 16x16 grid form
int rect_width = (end_tile_x - start_tile_x) * tile_size;
int rect_height = (end_tile_y - start_tile_y) * tile_size;
int tiles_per_row = rect_width / tile_size;
int tiles_per_col = rect_height / tile_size;
int i = 0;
for (int y = 0; y < tiles_per_col + 1; ++y) {
for (int x = 0; x < tiles_per_row + 1; ++x) {
int tile_id = group[i];
// Check if tile_id is within the range of tile16_individual_
if (tile_id >= 0 && tile_id < tile16_individual_.size()) {
// Calculate the position of the tile within the rectangle
int tile_pos_x = (x + start_tile_x) * tile_size * scale;
int tile_pos_y = (y + start_tile_y) * tile_size * scale;
// Draw the tile bitmap at the calculated position
DrawBitmap(tile16_individual_[tile_id], tile_pos_x, tile_pos_y, scale,
150.0f);
i++;
}
}
}
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);
auto new_start_pos = AlignPosToGrid(mouse_pos, tile_size * scale);
auto new_end_pos =
ImVec2(new_start_pos.x + rect_width, new_start_pos.y + rect_height);
selected_points_.clear();
selected_points_.push_back(new_start_pos);
selected_points_.push_back(new_end_pos);
select_rect_active_ = true;
}
void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) { void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
ImVec2 origin(canvas_p0_.x + scrolling_.x + x, ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
canvas_p0_.y + scrolling_.y + y); canvas_p0_.y + scrolling_.y + y);
@@ -424,31 +582,53 @@ void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
canvas_p0_.y + scrolling_.y + y + h); canvas_p0_.y + scrolling_.y + y + h);
draw_list_->AddRectFilled(origin, size, draw_list_->AddRectFilled(origin, size,
IM_COL32(color.x, color.y, color.z, color.w)); IM_COL32(color.x, color.y, color.z, color.w));
// Add a black outline
ImVec2 outline_origin(origin.x - 1, origin.y - 1);
ImVec2 outline_size(size.x + 1, size.y + 1);
draw_list_->AddRect(outline_origin, outline_size, IM_COL32(0, 0, 0, 255));
} }
void Canvas::DrawText(std::string text, int x, int y) { void Canvas::DrawText(std::string text, int x, int y) {
draw_list_->AddText(ImVec2(canvas_p0_.x + scrolling_.x + x + 1,
canvas_p0_.y + scrolling_.y + y + 1),
IM_COL32(0, 0, 0, 255), text.data());
draw_list_->AddText( draw_list_->AddText(
ImVec2(canvas_p0_.x + scrolling_.x + x, canvas_p0_.y + scrolling_.y + y), ImVec2(canvas_p0_.x + scrolling_.x + x, canvas_p0_.y + scrolling_.y + y),
IM_COL32(255, 255, 255, 255), text.data()); IM_COL32(255, 255, 255, 255), text.data());
} }
void Canvas::DrawGrid(float grid_step) { void Canvas::DrawGridLines(float 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);
}
void Canvas::DrawGrid(float grid_step, int tile_id_offset) {
// Draw grid + all lines in the canvas // Draw grid + all lines in the canvas
draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true); draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
if (enable_grid_) { if (enable_grid_) {
if (custom_step_ != 0.f) grid_step = custom_step_; if (custom_step_ != 0.f) grid_step = custom_step_;
grid_step *= global_scale_; // Apply global scale to grid 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) DrawGridLines(grid_step);
draw_list_->AddLine(ImVec2(canvas_p0_.x + x, canvas_p0_.y),
ImVec2(canvas_p0_.x + x, canvas_p1_.y), if (highlight_tile_id != -1) {
IM_COL32(200, 200, 200, 50), 0.5f); int tile_x = highlight_tile_id % 8;
for (float y = fmodf(scrolling_.y, grid_step); int tile_y = highlight_tile_id / 8;
y < canvas_sz_.y * global_scale_; y += grid_step) ImVec2 tile_pos(canvas_p0_.x + scrolling_.x + tile_x * grid_step,
draw_list_->AddLine(ImVec2(canvas_p0_.x, canvas_p0_.y + y), canvas_p0_.y + scrolling_.y + tile_y * grid_step);
ImVec2(canvas_p1_.x, canvas_p0_.y + y), ImVec2 tile_pos_end(tile_pos.x + grid_step, tile_pos.y + grid_step);
IM_COL32(200, 200, 200, 50), 0.5f);
draw_list_->AddRectFilled(tile_pos, tile_pos_end,
IM_COL32(255, 0, 255, 255));
}
if (enable_hex_tile_labels_) { if (enable_hex_tile_labels_) {
// Draw the hex ID of the tile in the center of the tile square // Draw the hex ID of the tile in the center of the tile square
@@ -466,6 +646,28 @@ void Canvas::DrawGrid(float grid_step) {
} }
} }
} }
if (enable_custom_labels_) {
// Draw the contents of labels on the grid
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 * tile_id_offset);
if (tile_id >= labels_[current_labels_].size()) {
break;
}
std::string label = labels_[current_labels_][tile_id];
draw_list_->AddText(
ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
IM_COL32(255, 255, 255, 255), label.data());
}
}
}
} }
} }
@@ -479,6 +681,16 @@ void Canvas::DrawOverlay() {
IM_COL32(255, 255, 255, 255), 1.0f); IM_COL32(255, 255, 255, 255), 1.0f);
} }
if (!selected_points_.empty()) {
for (int n = 0; n < selected_points_.size(); n += 2) {
draw_list_->AddRect(ImVec2(origin.x + selected_points_[n].x,
origin.y + selected_points_[n].y),
ImVec2(origin.x + selected_points_[n + 1].x + 0x10,
origin.y + selected_points_[n + 1].y + 0x10),
IM_COL32(255, 255, 255, 255), 1.0f);
}
}
draw_list_->PopClipRect(); draw_list_->PopClipRect();
} }

View File

@@ -11,35 +11,71 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
/**
* @namespace yaze::app::gui
* @brief Graphical User Interface (GUI) components for the application.
*/
namespace gui { namespace gui {
using app::gfx::Bitmap; using app::gfx::Bitmap;
using app::gfx::BitmapTable; using app::gfx::BitmapTable;
enum class CanvasType { kTile, kBlock, kMap };
enum class CanvasMode { kPaint, kSelect };
enum class CanvasGridSize { k8x8, k16x16, k32x32, k64x64 };
/**
* @class Canvas
* @brief Represents a canvas for drawing and manipulating graphics.
*
* The Canvas class provides various functions for updating and drawing graphics
* on a canvas. It supports features such as bitmap drawing, context menu
* handling, tile painting, custom grid, and more.
*/
class Canvas { class Canvas {
public: public:
Canvas() = default; Canvas() = default;
explicit Canvas(ImVec2 canvas_size) explicit Canvas(ImVec2 canvas_size)
: custom_canvas_size_(true), canvas_sz_(canvas_size) {} : custom_canvas_size_(true), canvas_sz_(canvas_size) {}
explicit Canvas(ImVec2 canvas_size, CanvasGridSize grid_size)
: custom_canvas_size_(true), canvas_sz_(canvas_size) {
switch (grid_size) {
case CanvasGridSize::k8x8:
custom_step_ = 8.0f;
break;
case CanvasGridSize::k16x16:
custom_step_ = 16.0f;
break;
case CanvasGridSize::k32x32:
custom_step_ = 32.0f;
break;
case CanvasGridSize::k64x64:
custom_step_ = 64.0f;
break;
}
}
void Update(const gfx::Bitmap& bitmap, ImVec2 bg_size, int tile_size, void Update(const gfx::Bitmap& bitmap, ImVec2 bg_size, int tile_size,
float scale = 1.0f, float grid_size = 64.0f); float scale = 1.0f, float grid_size = 64.0f);
void UpdateColorPainter(const gfx::Bitmap& bitmap, const ImVec4& color, void UpdateColorPainter(gfx::Bitmap& bitmap, const ImVec4& color,
const std::function<void()>& event, ImVec2 bg_size, const std::function<void()>& event, int tile_size,
int tile_size, float scale = 1.0f, float scale = 1.0f);
float grid_size = 64.0f);
void UpdateEvent(const std::function<void()>& event, ImVec2 bg_size, void UpdateEvent(const std::function<void()>& event, ImVec2 bg_size,
int tile_size, float scale = 1.0f, float grid_size = 64.0f); int tile_size, float scale = 1.0f, float grid_size = 64.0f);
void UpdateInfoGrid(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 // Background for the Canvas represents region without any content drawn to
// it, but can be controlled by the user. // it, but can be controlled by the user.
void DrawBackground(ImVec2 canvas_size = ImVec2(0, 0)); void DrawBackground(ImVec2 canvas_size = ImVec2(0, 0), bool drag = false);
// Context Menu refers to what happens when the right mouse button is pressed // Context Menu refers to what happens when the right mouse button is pressed
// This routine also handles the scrolling for the canvas. // This routine also handles the scrolling for the canvas.
void DrawContextMenu(); void DrawContextMenu(gfx::Bitmap* bitmap = nullptr);
// Tile painter shows a preview of the currently selected tile // Tile painter shows a preview of the currently selected tile
// and allows the user to left click to paint the tile or right // and allows the user to left click to paint the tile or right
@@ -48,56 +84,105 @@ class Canvas {
bool DrawSolidTilePainter(const ImVec4& color, int size); bool DrawSolidTilePainter(const ImVec4& color, int size);
// Draws a tile on the canvas at the specified position // Draws a tile on the canvas at the specified position
void DrawTileOnBitmap(int tile_size, gfx::Bitmap& bitmap, ImVec4 color); void DrawTileOnBitmap(int tile_size, gfx::Bitmap* bitmap, ImVec4 color);
// Dictates which tile is currently selected based on what the user clicks // 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. // in the canvas window. Represented and split apart into a grid of tiles.
void DrawTileSelector(int size); bool 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 // Draws the contents of the Bitmap image to the Canvas
void DrawBitmap(const Bitmap& bitmap, int border_offset = 0, void DrawBitmap(const Bitmap& bitmap, int border_offset = 0,
bool ready = true); bool ready = true);
void DrawBitmap(const Bitmap& bitmap, int border_offset, float scale); void DrawBitmap(const Bitmap& bitmap, int border_offset, float scale);
void DrawBitmap(const Bitmap& bitmap, int x_offset = 0, int y_offset = 0, void DrawBitmap(const Bitmap& bitmap, int x_offset = 0, int y_offset = 0,
float scale = 1.0f); float scale = 1.0f, int alpha = 255);
void DrawBitmapTable(const BitmapTable& gfx_bin); 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_; } void DrawBitmapGroup(std::vector<int>& group,
auto GetDrawList() const { return draw_list_; } std::vector<gfx::Bitmap>& tile16_individual_,
auto zero_point() const { return canvas_p0_; } int tile_size, float scale = 1.0f);
auto Scrolling() const { return scrolling_; }
auto drawn_tile_position() const { return drawn_tile_pos_; } void DrawOutline(int x, int y, int w, int h);
auto canvas_size() const { return canvas_sz_; } void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color);
void DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color);
void DrawSelectRect(int current_map, int tile_size = 0x10,
float scale = 1.0f);
void DrawSelectRectTile16(int current_map);
void DrawRect(int x, int y, int w, int h, ImVec4 color);
void DrawText(std::string text, int x, int y);
void DrawGridLines(float grid_step);
void DrawGrid(float grid_step = 64.0f, int tile_id_offset = 8);
void DrawOverlay(); // last
void SetCanvasSize(ImVec2 canvas_size) { void SetCanvasSize(ImVec2 canvas_size) {
canvas_sz_ = canvas_size; canvas_sz_ = canvas_size;
custom_canvas_size_ = true; custom_canvas_size_ = true;
} }
auto IsMouseHovering() const { return is_hovered_; } bool IsMouseHovering() const { return is_hovered_; }
void ZoomIn() { global_scale_ += 0.1f; } void ZoomIn() { global_scale_ += 0.25f; }
void ZoomOut() { global_scale_ -= 0.1f; } void ZoomOut() { global_scale_ -= 0.25f; }
auto points() const { return points_; }
auto mutable_points() { return &points_; }
auto push_back(ImVec2 pos) { points_.push_back(pos); }
auto draw_list() 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 set_global_scale(float scale) { global_scale_ = scale; } void set_global_scale(float scale) { global_scale_ = scale; }
auto global_scale() const { return global_scale_; } auto global_scale() const { return global_scale_; }
auto custom_labels_enabled() { return &enable_custom_labels_; }
auto custom_step() const { return custom_step_; }
auto width() const { return canvas_sz_.x; }
auto height() const { return canvas_sz_.y; }
auto set_draggable(bool value) { draggable_ = value; }
auto labels(int i) {
if (i >= labels_.size()) {
labels_.push_back(ImVector<std::string>());
}
return labels_[i];
}
auto mutable_labels(int i) {
if (i >= labels_.size()) {
labels_.push_back(ImVector<std::string>());
}
return &labels_[i];
}
int GetTileIdFromMousePos() {
int x = mouse_pos_in_canvas_.x;
int y = mouse_pos_in_canvas_.y;
int num_columns = width() / custom_step_;
int num_rows = height() / custom_step_;
int tile_id = (x / custom_step_) + (y / custom_step_) * num_columns;
if (tile_id >= num_columns * num_rows) {
tile_id = -1; // Invalid tile ID
}
return tile_id;
}
auto set_current_labels(int i) { current_labels_ = i; }
auto set_highlight_tile_id(int i) { highlight_tile_id = i; }
auto selected_tiles() const { return selected_tiles_; }
auto mutable_selected_tiles() { return &selected_tiles_; }
auto selected_tile_pos() const { return selected_tile_pos_; }
auto set_selected_tile_pos(ImVec2 pos) { selected_tile_pos_ = pos; }
bool select_rect_active() const { return select_rect_active_; }
auto selected_points() const { return selected_points_; }
auto hover_mouse_pos() const { return mouse_pos_in_canvas_; }
private: private:
bool draggable_ = false;
bool enable_grid_ = true; bool enable_grid_ = true;
bool enable_hex_tile_labels_ = false; bool enable_hex_tile_labels_ = false;
bool enable_custom_labels_ = false;
bool enable_context_menu_ = true; bool enable_context_menu_ = true;
bool custom_canvas_size_ = false; bool custom_canvas_size_ = false;
bool is_hovered_ = false; bool is_hovered_ = false;
@@ -105,14 +190,23 @@ class Canvas {
float custom_step_ = 0.0f; float custom_step_ = 0.0f;
float global_scale_ = 1.0f; float global_scale_ = 1.0f;
int current_labels_ = 0;
int highlight_tile_id = -1;
ImDrawList* draw_list_; ImDrawList* draw_list_;
ImVector<ImVec2> points_; ImVector<ImVec2> points_;
ImVector<ImVector<std::string>> labels_;
ImVec2 scrolling_; ImVec2 scrolling_;
ImVec2 canvas_sz_; ImVec2 canvas_sz_;
ImVec2 canvas_p0_; ImVec2 canvas_p0_;
ImVec2 canvas_p1_; ImVec2 canvas_p1_;
ImVec2 mouse_pos_in_canvas_; ImVec2 mouse_pos_in_canvas_;
ImVec2 drawn_tile_pos_; ImVec2 drawn_tile_pos_;
bool select_rect_active_ = false;
ImVec2 selected_tile_pos_ = ImVec2(-1, -1);
ImVector<ImVec2> selected_points_;
std::vector<ImVec2> selected_tiles_;
}; };
} // namespace gui } // namespace gui

View File

@@ -12,16 +12,16 @@ namespace yaze {
namespace app { namespace app {
namespace gui { namespace gui {
ImVec4 ConvertSNESColorToImVec4(const SNESColor& color) { ImVec4 ConvertSNESColorToImVec4(const SnesColor& color) {
return ImVec4(static_cast<float>(color.GetRGB().x) / 255.0f, return ImVec4(static_cast<float>(color.rgb().x) / 255.0f,
static_cast<float>(color.GetRGB().y) / 255.0f, static_cast<float>(color.rgb().y) / 255.0f,
static_cast<float>(color.GetRGB().z) / 255.0f, static_cast<float>(color.rgb().z) / 255.0f,
1.0f // Assuming alpha is always fully opaque for SNES colors, 1.0f // Assuming alpha is always fully opaque for SNES colors,
// adjust if necessary // adjust if necessary
); );
} }
IMGUI_API bool SNESColorButton(absl::string_view id, SNESColor& color, IMGUI_API bool SnesColorButton(absl::string_view id, SnesColor& color,
ImGuiColorEditFlags flags, ImGuiColorEditFlags flags,
const ImVec2& size_arg) { const ImVec2& size_arg) {
// Convert the SNES color values to ImGui color values (normalized to 0-1 // Convert the SNES color values to ImGui color values (normalized to 0-1
@@ -34,7 +34,25 @@ IMGUI_API bool SNESColorButton(absl::string_view id, SNESColor& color,
return pressed; return pressed;
} }
void DisplayPalette(app::gfx::SNESPalette& palette, bool loaded) { IMGUI_API bool SnesColorEdit4(absl::string_view label, SnesColor& color,
ImGuiColorEditFlags flags) {
// Convert the SNES color values to ImGui color values (normalized to 0-1
// range)
ImVec4 displayColor = ConvertSNESColorToImVec4(color);
// Call the original ImGui::ColorEdit4 with the converted color
bool pressed = ImGui::ColorEdit4(label.data(), (float*)&displayColor, flags);
// Convert the ImGui color values back to SNES color values (normalized to
// 0-255 range)
color = SnesColor(static_cast<uint8_t>(displayColor.x * 255.0f),
static_cast<uint8_t>(displayColor.y * 255.0f),
static_cast<uint8_t>(displayColor.z * 255.0f));
return pressed;
}
absl::Status DisplayPalette(app::gfx::SnesPalette& palette, bool loaded) {
static ImVec4 color = ImVec4(0, 0, 0, 255.f); static ImVec4 color = ImVec4(0, 0, 0, 255.f);
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
ImGuiColorEditFlags_NoDragDrop | ImGuiColorEditFlags_NoDragDrop |
@@ -45,9 +63,10 @@ void DisplayPalette(app::gfx::SNESPalette& palette, bool loaded) {
static ImVec4 saved_palette[32] = {}; static ImVec4 saved_palette[32] = {};
if (loaded && !init) { if (loaded && !init) {
for (int n = 0; n < palette.size(); n++) { for (int n = 0; n < palette.size(); n++) {
saved_palette[n].x = palette.GetColor(n).GetRGB().x / 255; ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
saved_palette[n].y = palette.GetColor(n).GetRGB().y / 255; saved_palette[n].x = color.rgb().x / 255;
saved_palette[n].z = palette.GetColor(n).GetRGB().z / 255; saved_palette[n].y = color.rgb().y / 255;
saved_palette[n].z = color.rgb().z / 255;
saved_palette[n].w = 255; // Alpha saved_palette[n].w = 255; // Alpha
} }
init = true; init = true;

View File

@@ -6,6 +6,7 @@
#include <cmath> #include <cmath>
#include <string> #include <string>
#include "absl/status/status.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
@@ -13,18 +14,21 @@ namespace yaze {
namespace app { namespace app {
namespace gui { namespace gui {
using gfx::SNESColor; using gfx::SnesColor;
// A utility function to convert an SNESColor object to an ImVec4 with // A utility function to convert an SnesColor object to an ImVec4 with
// normalized color values // normalized color values
ImVec4 ConvertSNESColorToImVec4(const SNESColor& color); ImVec4 ConvertSNESColorToImVec4(const SnesColor& color);
// The wrapper function for ImGui::ColorButton that takes a SNESColor reference // The wrapper function for ImGui::ColorButton that takes a SnesColor reference
IMGUI_API bool SNESColorButton(absl::string_view id, SNESColor& color, IMGUI_API bool SnesColorButton(absl::string_view id, SnesColor& color,
ImGuiColorEditFlags flags = 0, ImGuiColorEditFlags flags = 0,
const ImVec2& size_arg = ImVec2(0, 0)); const ImVec2& size_arg = ImVec2(0, 0));
void DisplayPalette(app::gfx::SNESPalette& palette, bool loaded); IMGUI_API bool SnesColorEdit4(absl::string_view label, SnesColor& color,
ImGuiColorEditFlags flags = 0);
absl::Status DisplayPalette(app::gfx::SnesPalette& palette, bool loaded);
} // namespace gui } // namespace gui
} // namespace app } // namespace app

View File

@@ -2,6 +2,7 @@
#include <imgui/imgui.h> #include <imgui/imgui.h>
#include <imgui/imgui_internal.h> #include <imgui/imgui_internal.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
@@ -16,11 +17,10 @@ static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(
? ImGuiInputTextFlags_CharsHexadecimal ? ImGuiInputTextFlags_CharsHexadecimal
: ImGuiInputTextFlags_CharsDecimal; : ImGuiInputTextFlags_CharsDecimal;
} }
bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data, bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data,
const void* p_step, const void* p_step_fast, const void* p_step, const void* p_step_fast,
const char* format, float input_width, const char* format, float input_width,
ImGuiInputTextFlags flags) { ImGuiInputTextFlags flags, bool no_step = false) {
ImGuiWindow* window = ImGui::GetCurrentWindow(); ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems) return false; if (window->SkipItems) return false;
@@ -39,37 +39,59 @@ bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data,
flags |= ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited; flags |= ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited;
bool value_changed = false; bool value_changed = false;
if (p_step == NULL) { // if (p_step == NULL) {
ImGui::SetNextItemWidth(input_width); // ImGui::SetNextItemWidth(input_width);
if (InputText(label, buf, IM_ARRAYSIZE(buf), flags)) // if (InputText("", buf, IM_ARRAYSIZE(buf), flags))
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format); // value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
} else { // } else {
const float button_size = GetFrameHeight(); const float button_size = GetFrameHeight();
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", label);
ImGui::SameLine();
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));
BeginGroup(); // The only purpose of the group here is to allow the caller // Place the label on the left of the input field
// to query item data e.g. IsItemActive() ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
PushID(label); ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
SetNextItemWidth(ImMax( ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); ImVec2{style.FramePadding.x, style.FramePadding.y});
// Place the label on the left of the input field ImGui::SetNextItemWidth(input_width);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, if (InputText("", buf, IM_ARRAYSIZE(buf),
ImVec2{style.ItemSpacing.x, style.ItemSpacing.y}); flags)) // PushId(label) + "" gives us the expected ID
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, // from outside point of view
ImVec2{style.FramePadding.x, style.FramePadding.y}); value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
ImGui::AlignTextToFramePadding(); IMGUI_TEST_ENGINE_ITEM_INFO(
ImGui::Text("%s", label); g.LastItemData.ID, label,
ImGui::SameLine(); g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
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 // Mouse wheel support
if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
float scroll_amount = g.IO.MouseWheel;
float scroll_speed = 0.25f; // Adjust the scroll speed as needed
if (g.IO.KeyCtrl && p_step_fast)
scroll_amount *= *(const float*)p_step_fast;
else
scroll_amount *= *(const float*)p_step;
if (scroll_amount > 0.0f) {
scroll_amount *= scroll_speed; // Adjust the scroll speed as needed
DataTypeApplyOp(data_type, '+', p_data, p_data, &scroll_amount);
value_changed = true;
} else if (scroll_amount < 0.0f) {
scroll_amount *= -scroll_speed; // Adjust the scroll speed as needed
DataTypeApplyOp(data_type, '-', p_data, p_data, &scroll_amount);
value_changed = true;
}
}
// Step buttons
if (!no_step) {
const ImVec2 backup_frame_padding = style.FramePadding; const ImVec2 backup_frame_padding = style.FramePadding;
style.FramePadding.x = style.FramePadding.y; style.FramePadding.x = style.FramePadding.y;
ImGuiButtonFlags button_flags = ImGuiButtonFlags button_flags =
@@ -87,14 +109,15 @@ bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data,
g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
value_changed = true; value_changed = true;
} }
if (flags & ImGuiInputTextFlags_ReadOnly) EndDisabled(); if (flags & ImGuiInputTextFlags_ReadOnly) EndDisabled();
style.FramePadding = backup_frame_padding; style.FramePadding = backup_frame_padding;
PopID();
EndGroup();
ImGui::PopStyleVar(2);
} }
PopID();
EndGroup();
ImGui::PopStyleVar(2);
if (value_changed) MarkItemEdited(g.LastItemData.ID); if (value_changed) MarkItemEdited(g.LastItemData.ID);
return value_changed; return value_changed;
@@ -114,23 +137,38 @@ bool InputHex(const char* label, uint64_t* data) {
ImGuiInputTextFlags_CharsHexadecimal); ImGuiInputTextFlags_CharsHexadecimal);
} }
bool InputHex(const char* label, int* data, int num_digits, float input_width) {
const std::string format = "%0" + std::to_string(num_digits) + "X";
return ImGui::InputScalarLeft(label, ImGuiDataType_S32, data, &kStepOneHex,
&kStepFastHex, format.c_str(), input_width,
ImGuiInputTextFlags_CharsHexadecimal);
}
bool InputHexShort(const char* label, uint32_t* data) { bool InputHexShort(const char* label, uint32_t* data) {
return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex, return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex,
&kStepFastHex, "%06X", &kStepFastHex, "%06X",
ImGuiInputTextFlags_CharsHexadecimal); ImGuiInputTextFlags_CharsHexadecimal);
} }
bool InputHexWord(const char* label, uint16_t* data, float input_width) { bool InputHexWord(const char* label, uint16_t* data, float input_width,
bool no_step) {
return ImGui::InputScalarLeft(label, ImGuiDataType_U16, data, &kStepOneHex, return ImGui::InputScalarLeft(label, ImGuiDataType_U16, data, &kStepOneHex,
&kStepFastHex, "%04X", input_width, &kStepFastHex, "%04X", input_width,
ImGuiInputTextFlags_CharsHexadecimal); ImGuiInputTextFlags_CharsHexadecimal, no_step);
} }
bool InputHexByte(const char* label, uint8_t* data, uint8_t step, bool InputHexWord(const char* label, int16_t* data, float input_width,
float input_width) { bool no_step) {
return ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &step, return ImGui::InputScalarLeft(label, ImGuiDataType_S16, data, &kStepOneHex,
&kStepFastHex, "%04X", input_width,
ImGuiInputTextFlags_CharsHexadecimal, no_step);
}
bool InputHexByte(const char* label, uint8_t* data, float input_width,
bool no_step) {
return ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &kStepOneHex,
&kStepFastHex, "%02X", input_width, &kStepFastHex, "%02X", input_width,
ImGuiInputTextFlags_CharsHexadecimal); ImGuiInputTextFlags_CharsHexadecimal, no_step);
} }
void ItemLabel(absl::string_view title, ItemLabelFlags flags) { void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
@@ -176,6 +214,18 @@ void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
ImGui::SetCursorScreenPos(lineStart); ImGui::SetCursorScreenPos(lineStart);
} }
bool ListBox(const char* label, int* current_item,
const std::vector<std::string>& items, int height_in_items) {
std::vector<const char*> items_ptr;
items_ptr.reserve(items.size());
for (const auto& item : items) {
items_ptr.push_back(item.c_str());
}
int items_count = static_cast<int>(items.size());
return ImGui::ListBox(label, current_item, items_ptr.data(), items_count,
height_in_items);
}
} // namespace gui } // namespace gui
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -5,6 +5,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <vector>
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
@@ -15,12 +16,24 @@ namespace gui {
constexpr ImVec2 kDefaultModalSize = ImVec2(200, 0); constexpr ImVec2 kDefaultModalSize = ImVec2(200, 0);
constexpr ImVec2 kZeroPos = ImVec2(0, 0); constexpr ImVec2 kZeroPos = ImVec2(0, 0);
IMGUI_API bool InputHexWithScrollwheel(const char* label, uint32_t* data,
uint32_t step = 0x01,
float input_width = 50.f);
IMGUI_API bool InputHex(const char* label, uint64_t* data); IMGUI_API bool InputHex(const char* label, uint64_t* data);
IMGUI_API bool InputHex(const char* label, int* data, int num_digits = 4,
float input_width = 50.f);
IMGUI_API bool InputHexShort(const char* label, uint32_t* data); IMGUI_API bool InputHexShort(const char* label, uint32_t* data);
IMGUI_API bool InputHexWord(const char* label, uint16_t* data, IMGUI_API bool InputHexWord(const char* label, uint16_t* data,
float input_width = 50.f); float input_width = 50.f, bool no_step = false);
IMGUI_API bool InputHexByte(const char* label, uint8_t* data, uint8_t step = 0x01, IMGUI_API bool InputHexWord(const char* label, int16_t* data,
float input_width = 50.f); float input_width = 50.f, bool no_step = false);
IMGUI_API bool InputHexByte(const char* label, uint8_t* data,
float input_width = 50.f, bool no_step = false);
IMGUI_API bool ListBox(const char* label, int* current_item,
const std::vector<std::string>& items,
int height_in_items = -1);
using ItemLabelFlags = enum ItemLabelFlag { using ItemLabelFlags = enum ItemLabelFlag {
Left = 1u << 0u, Left = 1u << 0u,

View File

@@ -15,14 +15,13 @@
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/color.h" #include "app/gui/color.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/rom.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace gui { namespace gui {
void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics, void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics,
gfx::SNESPalette& palette) { gfx::SnesPalette& palette) {
const auto palette_row_size = 7; const auto palette_row_size = 7;
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)100); if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)100);
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
@@ -43,7 +42,7 @@ void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics,
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
} }
if (gui::SNESColorButton("##palette", palette[n], if (gui::SnesColorButton("##palette", palette[n],
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip, ImGuiColorEditFlags_NoTooltip,
@@ -81,7 +80,7 @@ void GraphicsBinCanvasPipeline(int width, int height, int tile_size,
if (key >= 1) { if (key >= 1) {
top_left_y = canvas.zero_point().y + height * key; top_left_y = canvas.zero_point().y + height * key;
} }
canvas.GetDrawList()->AddImage( canvas.draw_list()->AddImage(
(void*)value.texture(), (void*)value.texture(),
ImVec2(canvas.zero_point().x + 2, top_left_y), ImVec2(canvas.zero_point().x + 2, top_left_y),
ImVec2(canvas.zero_point().x + 0x100, ImVec2(canvas.zero_point().x + 0x100,
@@ -113,7 +112,7 @@ void GraphicsManagerCanvasPipeline(int width, int height, int tile_size,
if (key >= 1) { if (key >= 1) {
top_left_y = canvas.zero_point().y + height * key; top_left_y = canvas.zero_point().y + height * key;
} }
canvas.GetDrawList()->AddImage( canvas.draw_list()->AddImage(
(void*)value->texture(), (void*)value->texture(),
ImVec2(canvas.zero_point().x + 2, top_left_y), ImVec2(canvas.zero_point().x + 2, top_left_y),
ImVec2(canvas.zero_point().x + 0x100, ImVec2(canvas.zero_point().x + 0x100,
@@ -158,14 +157,6 @@ void BitmapCanvasPipeline(gui::Canvas& canvas, const gfx::Bitmap& bitmap,
} }
} }
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, void FileDialogPipeline(absl::string_view display_key,
absl::string_view file_extensions, absl::string_view file_extensions,
std::optional<absl::string_view> button_text, std::optional<absl::string_view> button_text,

View File

@@ -14,14 +14,13 @@
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/rom.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace gui { namespace gui {
void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics, void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics,
gfx::SNESPalette& palette); gfx::SnesPalette& palette);
void GraphicsBinCanvasPipeline(int width, int height, int tile_size, void GraphicsBinCanvasPipeline(int width, int height, int tile_size,
int num_sheets_to_load, int canvas_id, int num_sheets_to_load, int canvas_id,
@@ -38,10 +37,6 @@ void GraphicsManagerCanvasPipeline(int width, int height, int tile_size,
bool is_loaded, bool is_loaded,
const gfx::BitmapManager& graphics_manager); 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, void FileDialogPipeline(absl::string_view display_key,
absl::string_view file_extensions, absl::string_view file_extensions,
std::optional<absl::string_view> button_text, std::optional<absl::string_view> button_text,

View File

@@ -5,9 +5,57 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace gui { namespace gui {
void BeginWindowWithDisplaySettings(const char* id, bool* active,
const ImVec2& size,
ImGuiWindowFlags flags) {
ImGuiStyle* ref = &ImGui::GetStyle();
static float childBgOpacity = 0.75f;
auto color = ImVec4(0.f, 0.f, 0.f, childBgOpacity);
ImGui::PushStyleColor(ImGuiCol_WindowBg, color);
ImGui::PushStyleColor(ImGuiCol_ChildBg, color);
ImGui::PushStyleColor(ImGuiCol_Border, color);
ImGui::Begin(id, active, flags | ImGuiWindowFlags_MenuBar);
ImGui::BeginMenuBar();
if (ImGui::BeginMenu("Display Settings")) {
ImGui::SliderFloat("Child Background Opacity", &childBgOpacity, 0.0f, 1.0f);
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
void EndWindowWithDisplaySettings() {
ImGui::End();
ImGui::PopStyleColor(3);
}
void BeginPadding(int i) {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(i, i));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(i, i));
}
void EndPadding() { EndNoPadding(); }
void BeginNoPadding() {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
}
void EndNoPadding() { ImGui::PopStyleVar(2); }
void BeginChildWithScrollbar(const char* str_id) {
ImGui::BeginChild(str_id, ImGui::GetContentRegionAvail(), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
}
void BeginChildBothScrollbars(int id) {
ImGuiID child_id = ImGui::GetID((void*)(intptr_t)id);
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar |
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
}
void DrawDisplaySettings(ImGuiStyle* ref) { void DrawDisplaySettings(ImGuiStyle* ref) {
// You can pass in a reference ImGuiStyle structure to compare to, revert to // You can pass in a reference ImGuiStyle structure to compare to, revert to
// and save to (without a reference style pointer, we will use one compared // and save to (without a reference style pointer, we will use one compared
@@ -473,6 +521,7 @@ void ColorsYaze() {
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
} }
} // namespace gui } // namespace gui
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -9,6 +9,22 @@ namespace yaze {
namespace app { namespace app {
namespace gui { namespace gui {
void BeginWindowWithDisplaySettings(const char* id, bool* active,
const ImVec2& size = ImVec2(0, 0),
ImGuiWindowFlags flags = 0);
void EndWindowWithDisplaySettings();
void BeginPadding(int i);
void EndPadding();
void BeginNoPadding();
void EndNoPadding();
void BeginChildWithScrollbar(const char *str_id);
void BeginChildBothScrollbars(int id);
void DrawDisplaySettings(ImGuiStyle* ref = nullptr); void DrawDisplaySettings(ImGuiStyle* ref = nullptr);
void TextWithSeparators(const absl::string_view& text); void TextWithSeparators(const absl::string_view& text);

View File

@@ -16,9 +16,7 @@ namespace yaze {
namespace app { namespace app {
namespace gui { namespace gui {
class DynamicLayout { class DynamicLayout {};
};
TextEditor::LanguageDefinition GetAssemblyLanguageDef(); TextEditor::LanguageDefinition GetAssemblyLanguageDef();
@@ -29,7 +27,7 @@ class BitmapViewer {
public: public:
BitmapViewer() : current_bitmap_index_(0) {} BitmapViewer() : current_bitmap_index_(0) {}
void Display(const std::vector<gfx::Bitmap>& bitmaps) { void Display(const std::vector<gfx::Bitmap>& bitmaps, float scale = 1.0f) {
if (bitmaps.empty()) { if (bitmaps.empty()) {
ImGui::Text("No bitmaps available."); ImGui::Text("No bitmaps available.");
return; return;
@@ -57,8 +55,9 @@ class BitmapViewer {
// Assuming Bitmap has a function to get its texture ID, and width and // Assuming Bitmap has a function to get its texture ID, and width and
// height. // height.
ImTextureID tex_id = current_bitmap.texture(); ImTextureID tex_id = current_bitmap.texture();
ImVec2 size(current_bitmap.width(), current_bitmap.height()); ImVec2 size(current_bitmap.width() * scale,
ImGui::Image(tex_id, size); current_bitmap.height() * scale);
// ImGui::Image(tex_id, size);
// Scroll if the image is larger than the display area. // Scroll if the image is larger than the display area.
if (ImGui::BeginChild("BitmapScrollArea", ImVec2(0, 0), false, if (ImGui::BeginChild("BitmapScrollArea", ImVec2(0, 0), false,

View File

@@ -22,171 +22,14 @@
#include "app/core/constants.h" // for Bytes, ASSIGN_OR_RETURN #include "app/core/constants.h" // for Bytes, ASSIGN_OR_RETURN
#include "app/gfx/bitmap.h" // for Bitmap, BitmapTable #include "app/gfx/bitmap.h" // for Bitmap, BitmapTable
#include "app/gfx/compression.h" // for DecompressV2 #include "app/gfx/compression.h" // for DecompressV2
#include "app/gfx/snes_palette.h" // for PaletteGroup, SNESColor #include "app/gfx/snes_color.h" // for SNESColor
#include "app/gfx/snes_palette.h" // for PaletteGroup
#include "app/gfx/snes_tile.h" // for SnesTo8bppSheet #include "app/gfx/snes_tile.h" // for SnesTo8bppSheet
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace { absl::StatusOr<Bytes> Rom::Load2BppGraphics() {
absl::Status LoadOverworldMainPalettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 6; i++) {
RETURN_IF_ERROR(palette_groups["ow_main"].AddPalette(
gfx::ReadPaletteFromROM(core::overworldPaletteMain + (i * (35 * 2)),
/*num_colors*/ 35, data)))
}
return absl::OkStatus();
}
absl::Status LoadOverworldAuxiliaryPalettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 20; i++) {
RETURN_IF_ERROR(palette_groups["ow_aux"].AddPalette(gfx::ReadPaletteFromROM(
core::overworldPaletteAuxialiary + (i * (21 * 2)),
/*num_colors*/ 21, data)))
}
return absl::OkStatus();
}
absl::Status LoadOverworldAnimatedPalettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 14; i++) {
RETURN_IF_ERROR(
palette_groups["ow_animated"].AddPalette(gfx::ReadPaletteFromROM(
core::overworldPaletteAnimated + (i * (7 * 2)), 7, data)))
}
return absl::OkStatus();
}
absl::Status LoadHUDPalettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 2; i++) {
RETURN_IF_ERROR(palette_groups["hud"].AddPalette(
gfx::ReadPaletteFromROM(core::hudPalettes + (i * 64), 32, data)))
}
return absl::OkStatus();
}
absl::Status LoadGlobalSpritePalettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
RETURN_IF_ERROR(palette_groups["global_sprites"].AddPalette(
gfx::ReadPaletteFromROM(core::globalSpritePalettesLW, 60, data)))
RETURN_IF_ERROR(palette_groups["global_sprites"].AddPalette(
gfx::ReadPaletteFromROM(core::globalSpritePalettesDW, 60, data)))
return absl::OkStatus();
}
absl::Status LoadArmorPalettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 5; i++) {
RETURN_IF_ERROR(palette_groups["armors"].AddPalette(
gfx::ReadPaletteFromROM(core::armorPalettes + (i * 30), 15, data)))
}
return absl::OkStatus();
}
absl::Status LoadSwordPalettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 4; i++) {
RETURN_IF_ERROR(palette_groups["swords"].AddPalette(
gfx::ReadPaletteFromROM(core::swordPalettes + (i * 6), 3, data)))
}
return absl::OkStatus();
}
absl::Status LoadShieldPalettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 3; i++) {
RETURN_IF_ERROR(palette_groups["shields"].AddPalette(
gfx::ReadPaletteFromROM(core::shieldPalettes + (i * 8), 4, data)))
}
return absl::OkStatus();
}
absl::Status LoadSpriteAux1Palettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 12; i++) {
RETURN_IF_ERROR(palette_groups["sprites_aux1"].AddPalette(
gfx::ReadPaletteFromROM(core::spritePalettesAux1 + (i * 14), 7, data)))
}
return absl::OkStatus();
}
absl::Status LoadSpriteAux2Palettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 11; i++) {
RETURN_IF_ERROR(palette_groups["sprites_aux2"].AddPalette(
gfx::ReadPaletteFromROM(core::spritePalettesAux2 + (i * 14), 7, data)))
}
return absl::OkStatus();
}
absl::Status LoadSpriteAux3Palettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 24; i++) {
RETURN_IF_ERROR(palette_groups["sprites_aux3"].AddPalette(
gfx::ReadPaletteFromROM(core::spritePalettesAux3 + (i * 14), 7, data)))
}
return absl::OkStatus();
}
absl::Status LoadDungeonMainPalettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 20; i++) {
RETURN_IF_ERROR(
palette_groups["dungeon_main"].AddPalette(gfx::ReadPaletteFromROM(
core::dungeonMainPalettes + (i * 180), 90, data)))
}
return absl::OkStatus();
}
absl::Status LoadGrassColors(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
RETURN_IF_ERROR(palette_groups["grass"].AddColor(
gfx::ReadColorFromROM(core::hardcodedGrassLW, rom_data.data())))
RETURN_IF_ERROR(palette_groups["grass"].AddColor(
gfx::ReadColorFromROM(core::hardcodedGrassDW, rom_data.data())))
RETURN_IF_ERROR(palette_groups["grass"].AddColor(
gfx::ReadColorFromROM(core::hardcodedGrassSpecial, rom_data.data())))
return absl::OkStatus();
}
absl::Status Load3DObjectPalettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
RETURN_IF_ERROR(palette_groups["3d_object"].AddPalette(
gfx::ReadPaletteFromROM(core::triforcePalette, 8, data)))
RETURN_IF_ERROR(palette_groups["3d_object"].AddPalette(
gfx::ReadPaletteFromROM(core::crystalPalette, 8, data)))
return absl::OkStatus();
}
absl::Status LoadOverworldMiniMapPalettes(const Bytes& rom_data,
PaletteGroupMap& palette_groups) {
auto data = rom_data.data();
for (int i = 0; i < 2; i++) {
RETURN_IF_ERROR(
palette_groups["ow_mini_map"].AddPalette(gfx::ReadPaletteFromROM(
core::overworldMiniMapPalettes + (i * 256), 128, data)))
}
return absl::OkStatus();
}
} // namespace
absl::StatusOr<Bytes> ROM::Load2BppGraphics() {
Bytes sheet; Bytes sheet;
const uint8_t sheets[] = {113, 114, 218, 219, 220, 221}; const uint8_t sheets[] = {113, 114, 218, 219, 220, 221};
@@ -202,10 +45,10 @@ absl::StatusOr<Bytes> ROM::Load2BppGraphics() {
return sheet; return sheet;
} }
// TODO: Load Links graphics from the ROM absl::Status Rom::LoadLinkGraphics() {
absl::Status ROM::LoadLinkGraphics() { const auto link_gfx_offset = 0x80000; // $10:8000
const auto link_gfx_offset = 81920; // $10:8000 const auto link_gfx_length = 0x800; // 0x4000 or 0x7000?
const auto link_gfx_length = 0x800; link_palette_ = palette_groups_.armors[0];
// Load Links graphics from the ROM // Load Links graphics from the ROM
for (int i = 0; i < 14; i++) { for (int i = 0; i < 14; i++) {
@@ -213,17 +56,22 @@ absl::Status ROM::LoadLinkGraphics() {
auto link_sheet_data, auto link_sheet_data,
ReadByteVector(/*offset=*/link_gfx_offset + (i * link_gfx_length), ReadByteVector(/*offset=*/link_gfx_offset + (i * link_gfx_length),
/*length=*/link_gfx_length)) /*length=*/link_gfx_length))
auto link_sheet_8bpp = gfx::SnesTo8bppSheet(link_sheet_data, /*bpp=*/4); // auto link_sheet_8bpp = gfx::SnesTo8bppSheet(link_sheet_data, /*bpp=*/4);
// Convert to 3bpp, then from 3bpp to 8bpp before creating bitmap.
auto link_sheet_3bpp = gfx::Convert4bppTo3bpp(link_sheet_data);
auto link_sheet_8bpp = gfx::SnesTo8bppSheet(link_sheet_3bpp, /*bpp=*/3);
link_graphics_[i].Create(core::kTilesheetWidth, core::kTilesheetHeight, link_graphics_[i].Create(core::kTilesheetWidth, core::kTilesheetHeight,
core::kTilesheetDepth, link_sheet_8bpp); core::kTilesheetDepth, link_sheet_8bpp);
link_graphics_[i].ApplyPalette(link_palette_); RETURN_IF_ERROR(
link_graphics_[i].ApplyPaletteWithTransparent(link_palette_, 0));
RenderBitmap(&link_graphics_[i]); RenderBitmap(&link_graphics_[i]);
} }
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status ROM::LoadAllGraphicsData() { absl::Status Rom::LoadAllGraphicsData() {
constexpr uint32_t kNumGfxSheets = 223;
Bytes sheet; Bytes sheet;
bool bpp3 = false; bool bpp3 = false;
@@ -249,8 +97,14 @@ absl::Status ROM::LoadAllGraphicsData() {
graphics_manager_.LoadBitmap(i, converted_sheet, core::kTilesheetWidth, graphics_manager_.LoadBitmap(i, converted_sheet, core::kTilesheetWidth,
core::kTilesheetHeight, core::kTilesheetHeight,
core::kTilesheetDepth); core::kTilesheetDepth);
graphics_manager_[i]->ApplyPaletteWithTransparent( if (i > 115) {
palette_groups_["dungeon_main"][0], 0); // Apply sprites palette
RETURN_IF_ERROR(graphics_manager_[i]->ApplyPaletteWithTransparent(
palette_groups_.global_sprites[0], 0));
} else {
RETURN_IF_ERROR(graphics_manager_[i]->ApplyPaletteWithTransparent(
palette_groups_.dungeon_main[0], 0));
}
graphics_manager_[i]->CreateTexture(renderer_); graphics_manager_[i]->CreateTexture(renderer_);
} }
graphics_bin_[i] = graphics_bin_[i] =
@@ -272,26 +126,7 @@ absl::Status ROM::LoadAllGraphicsData() {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status ROM::LoadAllPalettes() { absl::Status Rom::LoadFromFile(const absl::string_view& filename,
RETURN_IF_ERROR(LoadOverworldMainPalettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadOverworldAuxiliaryPalettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadOverworldAnimatedPalettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadHUDPalettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadGlobalSpritePalettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadArmorPalettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadSwordPalettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadShieldPalettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadSpriteAux1Palettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadSpriteAux2Palettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadSpriteAux3Palettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadDungeonMainPalettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadGrassColors(rom_data_, palette_groups_))
RETURN_IF_ERROR(Load3DObjectPalettes(rom_data_, palette_groups_))
RETURN_IF_ERROR(LoadOverworldMiniMapPalettes(rom_data_, palette_groups_))
return absl::OkStatus();
}
absl::Status ROM::LoadFromFile(const absl::string_view& filename,
bool z3_load) { bool z3_load) {
// Set filename // Set filename
filename_ = filename; filename_ = filename;
@@ -331,22 +166,32 @@ absl::Status ROM::LoadFromFile(const absl::string_view& filename,
// Load Zelda 3 specific data if requested // Load Zelda 3 specific data if requested
if (z3_load) { if (z3_load) {
// Copy ROM title // Copy ROM title
constexpr uint32_t kTitleStringOffset = 0x7FC0;
constexpr uint32_t kTitleStringLength = 20;
memcpy(title_, rom_data_.data() + kTitleStringOffset, kTitleStringLength); memcpy(title_, rom_data_.data() + kTitleStringOffset, kTitleStringLength);
if (rom_data_[kTitleStringOffset + 0x19] == 0) { if (rom_data_[kTitleStringOffset + 0x19] == 0) {
version_ = Z3_Version::JP; version_ = Z3_Version::JP;
} else { } else {
version_ = Z3_Version::US; version_ = Z3_Version::US;
} }
RETURN_IF_ERROR(LoadAllPalettes()) RETURN_IF_ERROR(gfx::LoadAllPalettes(rom_data_, palette_groups_));
LoadGfxGroups(); LoadGfxGroups();
} }
// Expand the ROM data to 2MB without changing the data in the first 1MB
rom_data_.resize(baseROMSize * 2);
size_ = baseROMSize * 2;
// Set up the resource labels
std::string resource_label_filename = absl::StrFormat("%s.labels", filename);
resource_label_manager_.LoadLabels(resource_label_filename);
// Set is_loaded_ flag and return success // Set is_loaded_ flag and return success
is_loaded_ = true; is_loaded_ = true;
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status ROM::LoadFromPointer(uchar* data, size_t length) { absl::Status Rom::LoadFromPointer(uchar* data, size_t length) {
if (!data) if (!data)
return absl::InvalidArgumentError( return absl::InvalidArgumentError(
"Could not load ROM: parameter `data` is empty."); "Could not load ROM: parameter `data` is empty.");
@@ -356,7 +201,7 @@ absl::Status ROM::LoadFromPointer(uchar* data, size_t length) {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status ROM::LoadFromBytes(const Bytes& data) { absl::Status Rom::LoadFromBytes(const Bytes& data) {
if (data.empty()) { if (data.empty()) {
return absl::InvalidArgumentError( return absl::InvalidArgumentError(
"Could not load ROM: parameter `data` is empty."); "Could not load ROM: parameter `data` is empty.");
@@ -367,7 +212,8 @@ absl::Status ROM::LoadFromBytes(const Bytes& data) {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status ROM::SaveToFile(bool backup, absl::string_view filename) { absl::Status Rom::SaveToFile(bool backup, bool save_new, std::string filename) {
absl::Status non_firing_status;
if (rom_data_.empty()) { if (rom_data_.empty()) {
return absl::InternalError("ROM data is empty."); return absl::InternalError("ROM data is empty.");
} }
@@ -394,8 +240,13 @@ absl::Status ROM::SaveToFile(bool backup, absl::string_view filename) {
std::replace(backup_filename.begin(), backup_filename.end(), ' ', '_'); std::replace(backup_filename.begin(), backup_filename.end(), ' ', '_');
// Now, copy the original file to the backup file // Now, copy the original file to the backup file
std::filesystem::copy(filename, backup_filename, try {
std::filesystem::copy_options::overwrite_existing); std::filesystem::copy(filename, backup_filename,
std::filesystem::copy_options::overwrite_existing);
} catch (const std::filesystem::filesystem_error& e) {
non_firing_status = absl::InternalError(absl::StrCat(
"Could not create backup file: ", backup_filename, " - ", e.what()));
}
} }
// Run the other save functions // Run the other save functions
@@ -403,19 +254,34 @@ absl::Status ROM::SaveToFile(bool backup, absl::string_view filename) {
SaveAllPalettes(); SaveAllPalettes();
} }
if (flags()->kSaveWithChangeQueue) { if (save_new) {
while (!changes_.empty()) { // Create a file of the same name and append the date between the filename
auto change = changes_.top(); // and file extension
change(); auto now = std::chrono::system_clock::now();
changes_.pop(); auto now_c = std::chrono::system_clock::to_time_t(now);
} auto filename_no_ext = filename.substr(0, filename.find_last_of("."));
std::cout << filename_no_ext << std::endl;
filename = absl::StrCat(filename_no_ext, "_", std::ctime(&now_c));
// Remove spaces from new_filename and replace with _
filename.erase(std::remove(filename.begin(), filename.end(), ' '),
filename.end());
// Remove newline character from ctime()
filename.erase(std::remove(filename.begin(), filename.end(), '\n'),
filename.end());
// Add the file extension back to the new_filename
filename = filename + ".sfc";
std::cout << filename << std::endl;
} }
// Open the file that we know exists for writing // Open the file that we know exists for writing
std::ofstream file(filename.data(), std::ios::binary); std::ofstream file(filename.data(), std::ios::binary | std::ios::app);
if (!file) { if (!file) {
return absl::InternalError( // Create the file if it does not exist
absl::StrCat("Could not open ROM file: ", filename)); file.open(filename.data(), std::ios::binary);
if (!file) {
return absl::InternalError(
absl::StrCat("Could not open or create ROM file: ", filename));
}
} }
// Save the data to the file // Save the data to the file
@@ -434,63 +300,39 @@ absl::Status ROM::SaveToFile(bool backup, absl::string_view filename) {
absl::StrCat("Error while writing to ROM file: ", filename)); absl::StrCat("Error while writing to ROM file: ", filename));
} }
if (!non_firing_status.ok()) {
return non_firing_status;
}
return absl::OkStatus(); return absl::OkStatus();
} }
void ROM::SavePalette(int index, const std::string& group_name, absl::Status Rom::SavePalette(int index, const std::string& group_name,
gfx::SNESPalette& palette) { gfx::SnesPalette& palette) {
// Iterate through all colors in the palette // Iterate through all colors in the palette
for (size_t j = 0; j < palette.size(); ++j) { for (size_t j = 0; j < palette.size(); ++j) {
gfx::SNESColor color = palette[j]; gfx::SnesColor color = palette[j];
// If the color is modified, save the color to the ROM // If the color is modified, save the color to the ROM
if (color.IsModified()) { if (color.is_modified()) {
WriteColor(gfx::GetPaletteAddress(group_name, index, j), color); RETURN_IF_ERROR(
color.SetModified(false); // Reset the modified flag after saving WriteColor(gfx::GetPaletteAddress(group_name, index, j), color));
color.set_modified(false); // Reset the modified flag after saving
} }
} }
}
void ROM::SaveAllPalettes() {
// Iterate through all palette_groups_
for (auto& [group_name, palettes] : palette_groups_) {
// Iterate through all palettes in the group
for (size_t i = 0; i < palettes.size(); ++i) {
auto palette = palettes[i];
SavePalette(i, group_name, palette);
}
}
}
absl::Status ROM::UpdatePaletteColor(const std::string& groupName,
size_t paletteIndex, size_t colorIndex,
const gfx::SNESColor& newColor) {
// Check if the groupName exists in the palette_groups_ map
if (palette_groups_.find(groupName) != palette_groups_.end()) {
// Check if the paletteIndex is within the range of available palettes in
// the group
if (paletteIndex < palette_groups_[groupName].size()) {
// Check if the colorIndex is within the range of available colors in the
// palette
if (colorIndex < palette_groups_[groupName][paletteIndex].size()) {
// Update the color value in the palette
palette_groups_[groupName][paletteIndex][colorIndex] = newColor;
palette_groups_[groupName][paletteIndex][colorIndex].SetModified(true);
} else {
return absl::AbortedError(
"Error: Invalid color index in UpdatePaletteColor.");
}
} else {
return absl::AbortedError(
"Error: Invalid palette index in UpdatePaletteColor.");
}
} else {
return absl::AbortedError(
"Error: Invalid group name in UpdatePaletteColor");
}
return absl::OkStatus(); return absl::OkStatus();
} }
std::shared_ptr<ROM> SharedROM::shared_rom_ = nullptr; absl::Status Rom::SaveAllPalettes() {
palette_groups_.for_each([&](gfx::PaletteGroup& group) {
for (size_t i = 0; i < group.size(); ++i) {
SavePalette(i, group.name(), *group.mutable_palette(i));
}
});
return absl::OkStatus();
}
std::shared_ptr<Rom> SharedRom::shared_rom_ = nullptr;
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -2,7 +2,7 @@
#define YAZE_APP_ROM_H #define YAZE_APP_ROM_H
#include <SDL.h> #include <SDL.h>
#include <asar/src/asar/interface-lib.h> #include <asar/src/asar/interface-shared.h>
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
@@ -31,7 +31,8 @@
#include "absl/strings/string_view.h" // for string_view #include "absl/strings/string_view.h" // for string_view
#include "app/core/common.h" #include "app/core/common.h"
#include "app/core/constants.h" // for Bytes, uchar, armorPalettes #include "app/core/constants.h" // for Bytes, uchar, armorPalettes
#include "app/gfx/bitmap.h" // for Bitmap, BitmapTable #include "app/core/labeling.h"
#include "app/gfx/bitmap.h" // for Bitmap, BitmapTable
#include "app/gfx/compression.h" #include "app/gfx/compression.h"
#include "app/gfx/snes_palette.h" // for PaletteGroup, SNESColor #include "app/gfx/snes_palette.h" // for PaletteGroup, SNESColor
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
@@ -39,17 +40,17 @@
namespace yaze { namespace yaze {
namespace app { namespace app {
using PaletteGroupMap = std::unordered_map<std::string, gfx::PaletteGroup>;
// Define an enum class for the different versions of the game // Define an enum class for the different versions of the game
enum class Z3_Version { enum class Z3_Version {
US = 1, US = 1, // US version
JP = 2, JP = 2, // JP version
SD = 3, SD = 3, // Super Donkey Proto (Experimental)
RANDO = 4, RANDO = 4, // Randomizer (Unimplemented)
}; };
// Define a struct to hold the version-specific constants /**
* @brief A struct to hold version constants for each version of the game.
*/
struct VersionConstants { struct VersionConstants {
uint32_t kGfxAnimatedPointer; uint32_t kGfxAnimatedPointer;
uint32_t kOverworldGfxGroups1; uint32_t kOverworldGfxGroups1;
@@ -71,7 +72,9 @@ struct VersionConstants {
uint32_t kDungeonPalettesGroups; uint32_t kDungeonPalettesGroups;
}; };
// Define a map to hold the version constants for each version /**
* @brief A map of version constants for each version of the game.
*/
static const std::map<Z3_Version, VersionConstants> kVersionConstantsMap = { static const std::map<Z3_Version, VersionConstants> kVersionConstantsMap = {
{Z3_Version::US, {Z3_Version::US,
{ {
@@ -116,93 +119,53 @@ static const std::map<Z3_Version, VersionConstants> kVersionConstantsMap = {
0x67DD0, // kDungeonPalettesGroups 0x67DD0, // kDungeonPalettesGroups
}}}; }}};
// Define some constants used throughout the ROM class
constexpr uint32_t kOverworldGraphicsPos1 = 0x4F80;
constexpr uint32_t kOverworldGraphicsPos2 = 0x505F;
constexpr uint32_t kOverworldGraphicsPos3 = 0x513E;
constexpr uint32_t kTile32Num = 4432;
constexpr uint32_t kTitleStringOffset = 0x7FC0;
constexpr uint32_t kTitleStringLength = 20;
constexpr uint32_t kNumGfxSheets = 223;
constexpr uint32_t kNormalGfxSpaceStart = 0x87000; constexpr uint32_t kNormalGfxSpaceStart = 0x87000;
constexpr uint32_t kNormalGfxSpaceEnd = 0xC4200; constexpr uint32_t kNormalGfxSpaceEnd = 0xC4200;
constexpr uint32_t kLinkSpriteLocation = 0x80000;
constexpr uint32_t kFontSpriteLocation = 0x70000; constexpr uint32_t kFontSpriteLocation = 0x70000;
constexpr uint32_t gfx_groups_pointer = 0x6237; constexpr uint32_t kGfxGroupsPointer = 0x6237;
struct WriteAction { /**
int address; * @brief The Rom class is used to load, save, and modify Rom data.
std::variant<int, uint8_t, uint16_t, std::vector<uint8_t>, gfx::SNESColor> */
value; class Rom : public core::ExperimentFlags {
};
class ROM : public core::ExperimentFlags {
public: public:
template <typename... Args>
absl::Status RunTransaction(Args... args) {
absl::Status status;
// Fold expression to apply the Write function on each argument
((status = WriteHelper(args)), ...);
return status;
}
absl::Status WriteHelper(const WriteAction& action) {
if (std::holds_alternative<uint8_t>(action.value) ||
std::holds_alternative<int>(action.value)) {
return Write(action.address, std::get<uint8_t>(action.value));
} else if (std::holds_alternative<uint16_t>(action.value)) {
return WriteShort(action.address, std::get<uint16_t>(action.value));
} else if (std::holds_alternative<std::vector<uint8_t>>(action.value)) {
return WriteVector(action.address,
std::get<std::vector<uint8_t>>(action.value));
} else if (std::holds_alternative<gfx::SNESColor>(action.value)) {
return WriteColor(action.address, std::get<gfx::SNESColor>(action.value));
}
return absl::InvalidArgumentError("Invalid write argument type");
}
/** /**
* Loads 2bpp graphics from ROM data. * @brief Loads 2bpp graphics from Rom data.
* *
* This function loads 2bpp graphics from ROM data by iterating over a list of * This function loads 2bpp graphics from Rom data by iterating over a list of
* sheet IDs, decompressing the sheet data, converting it to 8bpp format, and * sheet IDs, decompressing the sheet data, converting it to 8bpp format, and
* appending the converted sheet data to a byte vector. * appending the converted sheet data to a byte vector.
* *
*/ */
absl::StatusOr<Bytes> Load2BppGraphics(); absl::StatusOr<Bytes> Load2BppGraphics();
/**
* @brief Loads the players 4bpp graphics sheet from Rom data.
*/
absl::Status LoadLinkGraphics(); absl::Status LoadLinkGraphics();
/** /**
* This function iterates over all graphics sheets in the ROM and loads them * @brief This function iterates over all graphics sheets in the Rom and loads
* into memory. Depending on the sheet's index, it may be uncompressed or * them into memory. Depending on the sheet's index, it may be uncompressed or
* compressed using the LC-LZ2 algorithm. The uncompressed sheets are 3 bits * compressed using the LC-LZ2 algorithm. The uncompressed sheets are 3 bits
* per pixel (BPP), while the compressed sheets are 4 BPP. The loaded graphics * per pixel (BPP), while the compressed sheets are 4 BPP. The loaded graphics
* data is converted to 8 BPP and stored in a bitmap. * data is converted to 8 BPP and stored in a bitmap.
* *
* The graphics sheets are divided into the following ranges: * The graphics sheets are divided into the following ranges:
* 0-112 -> compressed 3bpp bgr -> (decompressed each) 0x600 chars *
* 113-114 -> compressed 2bpp -> (decompressed each) 0x800 chars * | Range | Compression Type | Decompressed Size | Number of Chars |
* 115-126 -> uncompressed 3bpp sprites -> (each) 0x600 chars * |---------|------------------|------------------|-----------------|
* 127-217 -> compressed 3bpp sprites -> (decompressed each) 0x600 chars * | 0-112 | Compressed 3bpp BGR | 0x600 chars | Decompressed each |
* 218-222 -> compressed 2bpp -> (decompressed each) 0x800 chars * | 113-114 | Compressed 2bpp | 0x800 chars | Decompressed each |
* | 115-126 | Uncompressed 3bpp sprites | 0x600 chars | Each |
* | 127-217 | Compressed 3bpp sprites | 0x600 chars | Decompressed each |
* | 218-222 | Compressed 2bpp | 0x800 chars | Decompressed each |
* *
*/ */
absl::Status LoadAllGraphicsData(); absl::Status LoadAllGraphicsData();
/** /**
* @brief Loads all the palettes for the game. * Load Rom data from a file.
*
* This function loads all the palettes for the game, including overworld,
* HUD, armor, swords, shields, sprites, dungeon, grass, and 3D object
* palettes. It also adds the loaded palettes to their respective palette
* groups.
*
*/
absl::Status LoadAllPalettes();
/**
* Load ROM data from a file.
* *
* @param filename The name of the file to load. * @param filename The name of the file to load.
* @param z3_load Whether to load data specific to Zelda 3. * @param z3_load Whether to load data specific to Zelda 3.
@@ -214,33 +177,34 @@ class ROM : public core::ExperimentFlags {
absl::Status LoadFromBytes(const Bytes& data); absl::Status LoadFromBytes(const Bytes& data);
/** /**
* @brief Saves the ROM data to a file * @brief Saves the Rom data to a file
* *
* @param backup If true, creates a backup file with timestamp in its name * @param backup If true, creates a backup file with timestamp in its name
* @param filename The name of the file to save the ROM data to * @param filename The name of the file to save the Rom data to
* *
* @return absl::Status Returns an OK status if the save was successful, * @return absl::Status Returns an OK status if the save was successful,
* otherwise returns an error status * otherwise returns an error status
*/ */
absl::Status SaveToFile(bool backup, absl::string_view filename = ""); absl::Status SaveToFile(bool backup, bool save_new = false,
std::string filename = "");
/** /**
* Saves the given palette to the ROM if any of its colors have been modified. * Saves the given palette to the Rom if any of its colors have been modified.
* *
* @param index The index of the palette to save. * @param index The index of the palette to save.
* @param group_name The name of the group containing the palette. * @param group_name The name of the group containing the palette.
* @param palette The palette to save. * @param palette The palette to save.
*/ */
void SavePalette(int index, const std::string& group_name, absl::Status SavePalette(int index, const std::string& group_name,
gfx::SNESPalette& palette); gfx::SnesPalette& palette);
/** /**
* @brief Saves all palettes in the ROM. * @brief Saves all palettes in the Rom.
* *
* This function iterates through all palette groups and all palettes in each * This function iterates through all palette groups and all palettes in each
* group, and saves each palette using the SavePalette() function. * group, and saves each palette using the SavePalette() function.
*/ */
void SaveAllPalettes(); absl::Status SaveAllPalettes();
/** /**
* @brief Updates a color in a specified palette group. * @brief Updates a color in a specified palette group.
@@ -261,7 +225,7 @@ class ROM : public core::ExperimentFlags {
*/ */
absl::Status UpdatePaletteColor(const std::string& group_name, absl::Status UpdatePaletteColor(const std::string& group_name,
size_t palette_index, size_t colorIndex, size_t palette_index, size_t colorIndex,
const gfx::SNESColor& newColor); const gfx::SnesColor& newColor);
// Read functions // Read functions
absl::StatusOr<uint8_t> ReadByte(int offset) { absl::StatusOr<uint8_t> ReadByte(int offset) {
@@ -279,6 +243,10 @@ class ROM : public core::ExperimentFlags {
return result; return result;
} }
uint16_t toint16(int offset) {
return (uint16_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8));
}
absl::StatusOr<uint32_t> ReadLong(int offset) { absl::StatusOr<uint32_t> ReadLong(int offset) {
if (offset + 2 >= rom_data_.size()) { if (offset + 2 >= rom_data_.size()) {
return absl::InvalidArgumentError("Offset out of range"); return absl::InvalidArgumentError("Offset out of range");
@@ -294,7 +262,7 @@ class ROM : public core::ExperimentFlags {
return absl::InvalidArgumentError("Offset and length out of range"); return absl::InvalidArgumentError("Offset and length out of range");
} }
std::vector<uint8_t> result; std::vector<uint8_t> result;
for (int i = offset; i < length; i++) { for (int i = offset; i < offset + length; i++) {
result.push_back(rom_data_[i]); result.push_back(rom_data_[i]);
} }
return result; return result;
@@ -334,40 +302,113 @@ class ROM : public core::ExperimentFlags {
// Write functions // Write functions
absl::Status Write(int addr, int value) { absl::Status Write(int addr, int value) {
if (addr >= rom_data_.size()) { if (addr >= rom_data_.size()) {
return absl::InvalidArgumentError("Address out of range"); return absl::InvalidArgumentError(absl::StrFormat(
"Attempt to write %d value failed, address %d out of range", value,
addr));
} }
rom_data_[addr] = value; rom_data_[addr] = value;
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status WriteShort(uint32_t addr, uint16_t value) { absl::Status WriteByte(int addr, uint8_t value) {
if (addr >= rom_data_.size()) {
return absl::InvalidArgumentError(absl::StrFormat(
"Attempt to write byte %#02x value failed, address %d out of range",
value, addr));
}
rom_data_[addr] = value;
std::string log_str = absl::StrFormat("WriteByte: %#06X: %s", addr,
core::UppercaseHexByte(value).data());
core::Logger::log(log_str);
return absl::OkStatus();
}
absl::Status WriteWord(int addr, uint16_t value) {
if (addr + 1 >= rom_data_.size()) { if (addr + 1 >= rom_data_.size()) {
return absl::InvalidArgumentError("Address out of range"); return absl::InvalidArgumentError(absl::StrFormat(
"Attempt to write word %#04x value failed, address %d out of range",
value, addr));
} }
rom_data_[addr] = (uint8_t)(value & 0xFF); rom_data_[addr] = (uint8_t)(value & 0xFF);
rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF); rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
core::Logger::log(absl::StrFormat("WriteWord: %#06X: %s", addr,
core::UppercaseHexWord(value)));
return absl::OkStatus();
}
absl::Status WriteShort(int addr, uint16_t value) {
if (addr + 1 >= rom_data_.size()) {
return absl::InvalidArgumentError(absl::StrFormat(
"Attempt to write short %#04x value failed, address %d out of range",
value, addr));
}
rom_data_[addr] = (uint8_t)(value & 0xFF);
rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
core::Logger::log(absl::StrFormat("WriteShort: %#06X: %s", addr,
core::UppercaseHexWord(value)));
return absl::OkStatus();
}
absl::Status WriteLong(uint32_t addr, uint32_t value) {
if (addr + 2 >= rom_data_.size()) {
return absl::InvalidArgumentError(absl::StrFormat(
"Attempt to write long %#06x value failed, address %d out of range",
value, addr));
}
rom_data_[addr] = (uint8_t)(value & 0xFF);
rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
rom_data_[addr + 2] = (uint8_t)((value >> 16) & 0xFF);
core::Logger::log(absl::StrFormat("WriteLong: %#06X: %s", addr,
core::UppercaseHexLong(value)));
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status WriteVector(int addr, std::vector<uint8_t> data) { absl::Status WriteVector(int addr, std::vector<uint8_t> data) {
if (addr + data.size() > rom_data_.size()) { if (addr + data.size() > rom_data_.size()) {
return absl::InvalidArgumentError("Address and data size out of range"); return absl::InvalidArgumentError(absl::StrFormat(
"Attempt to write vector value failed, address %d out of range",
addr));
} }
for (int i = 0; i < data.size(); i++) { for (int i = 0; i < data.size(); i++) {
rom_data_[addr + i] = data[i]; rom_data_[addr + i] = data[i];
} }
core::Logger::log(absl::StrFormat("WriteVector: %#06X: %s", addr,
core::UppercaseHexByte(data[0])));
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status WriteColor(uint32_t address, const gfx::SNESColor& color) { absl::Status WriteColor(uint32_t address, const gfx::SnesColor& color) {
uint16_t bgr = ((color.GetSNES() >> 10) & 0x1F) | uint16_t bgr = ((color.snes() >> 10) & 0x1F) |
((color.GetSNES() & 0x1F) << 10) | ((color.snes() & 0x1F) << 10) | (color.snes() & 0x7C00);
(color.GetSNES() & 0x7C00);
// Write the 16-bit color value to the ROM at the specified address // Write the 16-bit color value to the ROM at the specified address
core::Logger::log(absl::StrFormat("WriteColor: %#06X: %s", address,
core::UppercaseHexWord(bgr)));
return WriteShort(address, bgr); return WriteShort(address, bgr);
} }
template <typename... Args>
absl::Status WriteTransaction(Args... args) {
absl::Status status;
// Fold expression to apply the Write function on each argument
((status = WriteHelper(args)), ...);
return status;
}
template <typename T, typename... Args>
absl::Status ReadTransaction(T& var, int address, Args&&... args) {
absl::Status status = ReadHelper<T>(var, address);
if (!status.ok()) {
return status;
}
if constexpr (sizeof...(args) > 0) {
status = ReadTransaction(std::forward<Args>(args)...);
}
return status;
}
void Expand(int size) { void Expand(int size) {
rom_data_.resize(size); rom_data_.resize(size);
size_ = size; size_ = size;
@@ -403,13 +444,14 @@ class ROM : public core::ExperimentFlags {
return core::SnesToPc(snes_addr); return core::SnesToPc(snes_addr);
} }
gfx::PaletteGroup palette_group(const std::string& group) { auto palette_group() { return palette_groups_; }
return palette_groups_[group]; auto mutable_palette_group() { return &palette_groups_; }
} auto dungeon_palette(int i) { return palette_groups_.dungeon_main[i]; }
auto mutable_palette_group(const std::string& group) { auto mutable_dungeon_palette(int i) {
return &palette_groups_[group]; return palette_groups_.dungeon_main.mutable_palette(i);
} }
// Full graphical data for the game
Bytes graphics_buffer() const { return graphics_buffer_; } Bytes graphics_buffer() const { return graphics_buffer_; }
gfx::BitmapTable graphics_bin() const { return graphics_bin_; } gfx::BitmapTable graphics_bin() const { return graphics_bin_; }
@@ -419,6 +461,7 @@ class ROM : public core::ExperimentFlags {
} }
auto bitmap_manager() { return graphics_manager_; } auto bitmap_manager() { return graphics_manager_; }
auto mutable_bitmap_manager() { return &graphics_manager_; } auto mutable_bitmap_manager() { return &graphics_manager_; }
auto link_graphics() { return link_graphics_; }
auto title() const { return title_; } auto title() const { return title_; }
auto size() const { return size_; } auto size() const { return size_; }
@@ -428,10 +471,10 @@ class ROM : public core::ExperimentFlags {
auto push_back(uint8_t byte) { rom_data_.push_back(byte); } auto push_back(uint8_t byte) { rom_data_.push_back(byte); }
auto vector() const { return rom_data_; } auto vector() const { return rom_data_; }
auto filename() const { return filename_; } auto filename() const { return filename_; }
auto isLoaded() const { return is_loaded_; } auto is_loaded() const { return is_loaded_; }
auto version() const { return version_; } auto version() const { return version_; }
uchar& operator[](int i) { uint8_t& operator[](int i) {
if (i > size_) { if (i > size_) {
std::cout << "ROM: Index " << i << " out of bounds, size: " << size_ std::cout << "ROM: Index " << i << " out of bounds, size: " << size_
<< std::endl; << std::endl;
@@ -439,7 +482,7 @@ class ROM : public core::ExperimentFlags {
} }
return rom_data_[i]; return rom_data_[i];
} }
uchar& operator+(int i) { uint8_t& operator+(int i) {
if (i > size_) { if (i > size_) {
std::cout << "ROM: Index " << i << " out of bounds, size: " << size_ std::cout << "ROM: Index " << i << " out of bounds, size: " << size_
<< std::endl; << std::endl;
@@ -447,16 +490,24 @@ class ROM : public core::ExperimentFlags {
} }
return rom_data_[i]; return rom_data_[i];
} }
const uchar* operator&() { return rom_data_.data(); } const uint8_t* operator&() { return rom_data_.data(); }
ushort toint16(int offset) {
return (ushort)((rom_data_[offset + 1]) << 8) | rom_data_[offset];
}
void SetupRenderer(std::shared_ptr<SDL_Renderer> renderer) { void SetupRenderer(std::shared_ptr<SDL_Renderer> renderer) {
renderer_ = renderer; renderer_ = renderer;
} }
absl::Status CreateAndRenderBitmap(int width, int height, int depth,
const Bytes& data, gfx::Bitmap& bitmap,
gfx::SnesPalette& palette) {
bitmap.Create(width, height, depth, data);
RETURN_IF_ERROR(bitmap.ApplyPalette(palette));
RenderBitmap(&bitmap);
return absl::OkStatus();
}
/**
* @brief Used to render a bitmap to the screen.
*/
void RenderBitmap(gfx::Bitmap* bitmap) { void RenderBitmap(gfx::Bitmap* bitmap) {
if (flags()->kLoadTexturesAsStreaming) { if (flags()->kLoadTexturesAsStreaming) {
bitmap->CreateTexture(renderer_.get()); bitmap->CreateTexture(renderer_.get());
@@ -465,9 +516,12 @@ class ROM : public core::ExperimentFlags {
} }
} }
void UpdateBitmap(gfx::Bitmap* bitmap) { /**
* @brief Used to update a bitmap on the screen.
*/
void UpdateBitmap(gfx::Bitmap* bitmap, bool use_sdl_update = false) {
if (flags()->kLoadTexturesAsStreaming) { if (flags()->kLoadTexturesAsStreaming) {
bitmap->UpdateTexture(renderer_.get()); bitmap->UpdateTexture(renderer_.get(), use_sdl_update);
} else { } else {
bitmap->UpdateTexture(renderer_); bitmap->UpdateTexture(renderer_);
} }
@@ -484,8 +538,8 @@ class ROM : public core::ExperimentFlags {
spriteset_ids.resize(144, std::vector<uint8_t>(4)); spriteset_ids.resize(144, std::vector<uint8_t>(4));
paletteset_ids.resize(72, std::vector<uint8_t>(4)); paletteset_ids.resize(72, std::vector<uint8_t>(4));
int gfxPointer = (rom_data_[gfx_groups_pointer + 1] << 8) + int gfxPointer =
rom_data_[gfx_groups_pointer]; (rom_data_[kGfxGroupsPointer + 1] << 8) + rom_data_[kGfxGroupsPointer];
gfxPointer = core::SnesToPc(gfxPointer); gfxPointer = core::SnesToPc(gfxPointer);
for (int i = 0; i < 37; i++) { for (int i = 0; i < 37; i++) {
@@ -516,9 +570,9 @@ class ROM : public core::ExperimentFlags {
} }
} }
bool SaveGroupsToROM() { bool SaveGroupsToRom() {
int gfxPointer = (rom_data_[gfx_groups_pointer + 1] << 8) + int gfxPointer =
rom_data_[gfx_groups_pointer]; (rom_data_[kGfxGroupsPointer + 1] << 8) + rom_data_[kGfxGroupsPointer];
gfxPointer = core::SnesToPc(gfxPointer); gfxPointer = core::SnesToPc(gfxPointer);
for (int i = 0; i < 37; i++) { for (int i = 0; i < 37; i++) {
@@ -551,7 +605,53 @@ class ROM : public core::ExperimentFlags {
return false; return false;
} }
auto resource_label() { return &resource_label_manager_; }
private: private:
struct WriteAction {
int address;
std::variant<int, uint8_t, uint16_t, short, std::vector<uint8_t>,
gfx::SnesColor, std::vector<gfx::SnesColor>>
value;
};
absl::Status WriteHelper(const WriteAction& action) {
if (std::holds_alternative<uint8_t>(action.value)) {
return Write(action.address, std::get<uint8_t>(action.value));
} else if (std::holds_alternative<uint16_t>(action.value) ||
std::holds_alternative<short>(action.value)) {
return WriteShort(action.address, std::get<uint16_t>(action.value));
} else if (std::holds_alternative<std::vector<uint8_t>>(action.value)) {
return WriteVector(action.address,
std::get<std::vector<uint8_t>>(action.value));
} else if (std::holds_alternative<gfx::SnesColor>(action.value)) {
return WriteColor(action.address, std::get<gfx::SnesColor>(action.value));
} else if (std::holds_alternative<std::vector<gfx::SnesColor>>(
action.value)) {
return absl::UnimplementedError(
"WriteHelper: std::vector<gfx::SnesColor>");
}
auto error_message = absl::StrFormat("Invalid write argument type: %s",
typeid(action.value).name());
throw std::runtime_error(error_message);
return absl::InvalidArgumentError(error_message);
}
template <typename T>
absl::Status ReadHelper(T& var, int address) {
if constexpr (std::is_same_v<T, uint8_t>) {
ASSIGN_OR_RETURN(auto result, ReadByte(address));
var = result;
} else if constexpr (std::is_same_v<T, uint16_t>) {
ASSIGN_OR_RETURN(auto result, ReadWord(address));
var = result;
} else if constexpr (std::is_same_v<T, std::vector<uint8_t>>) {
ASSIGN_OR_RETURN(auto result, ReadByteVector(address, var.size()));
var = result;
}
return absl::OkStatus();
}
long size_ = 0; long size_ = 0;
bool is_loaded_ = false; bool is_loaded_ = false;
bool has_header_ = false; bool has_header_ = false;
@@ -565,35 +665,39 @@ class ROM : public core::ExperimentFlags {
gfx::BitmapTable graphics_bin_; gfx::BitmapTable graphics_bin_;
gfx::BitmapManager graphics_manager_; gfx::BitmapManager graphics_manager_;
gfx::BitmapTable link_graphics_; gfx::BitmapTable link_graphics_;
gfx::SNESPalette link_palette_; gfx::SnesPalette link_palette_;
PaletteGroupMap palette_groups_; gfx::PaletteGroupMap palette_groups_;
core::ResourceLabelManager resource_label_manager_;
std::stack<std::function<void()>> changes_; std::stack<std::function<void()>> changes_;
std::shared_ptr<SDL_Renderer> renderer_; std::shared_ptr<SDL_Renderer> renderer_;
}; };
class SharedROM { /**
* @brief A class to hold a shared pointer to a Rom object.
*/
class SharedRom {
public: public:
SharedROM() = default; SharedRom() = default;
virtual ~SharedROM() = default; virtual ~SharedRom() = default;
std::shared_ptr<ROM> shared_rom() { std::shared_ptr<Rom> shared_rom() {
if (!shared_rom_) { if (!shared_rom_) {
shared_rom_ = std::make_shared<ROM>(); shared_rom_ = std::make_shared<Rom>();
} }
return shared_rom_; return shared_rom_;
} }
auto rom() { auto rom() {
if (!shared_rom_) { if (!shared_rom_) {
shared_rom_ = std::make_shared<ROM>(); shared_rom_ = std::make_shared<Rom>();
} }
ROM* rom = shared_rom_.get(); Rom* rom = shared_rom_.get();
return rom; return rom;
} }
// private: // private:
static std::shared_ptr<ROM> shared_rom_; static std::shared_ptr<Rom> shared_rom_;
}; };
} // namespace app } // namespace app

View File

@@ -8,6 +8,15 @@
#include "absl/debugging/symbolize.h" #include "absl/debugging/symbolize.h"
#include "app/core/controller.h" #include "app/core/controller.h"
/**
* @namespace yaze::app
* @brief Main namespace for the ImGui application.
*/
using namespace yaze::app;
/**
* @brief Main entry point for the application.
*/
int main(int argc, char** argv) { int main(int argc, char** argv) {
absl::InitializeSymbolizer(argv[0]); absl::InitializeSymbolizer(argv[0]);
@@ -16,7 +25,7 @@ int main(int argc, char** argv) {
options.alarm_on_failure_secs = true; options.alarm_on_failure_secs = true;
absl::InstallFailureSignalHandler(options); absl::InstallFailureSignalHandler(options);
yaze::app::core::Controller controller; core::Controller controller;
EXIT_IF_ERROR(controller.OnEntry()) EXIT_IF_ERROR(controller.OnEntry())
#ifdef __APPLE__ #ifdef __APPLE__

46
src/app/zelda3/common.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef YAZE_APP_ZELDA3_COMMON_H
#define YAZE_APP_ZELDA3_COMMON_H
namespace yaze {
namespace app {
/**
* @namespace yaze::app::zelda3
* @brief Zelda 3 specific classes and functions.
*/
namespace zelda3 {
/**
* @class OverworldEntity
* @brief Base class for all overworld entities.
*/
class OverworldEntity {
public:
enum EntityType {
kEntrance = 0,
kExit = 1,
kItem = 2,
kSprite = 3,
kTransport = 4,
kMusic = 5,
kTilemap = 6,
kProperties = 7
} type_;
int x_;
int y_;
int game_x_;
int game_y_;
int entity_id_;
int map_id_;
auto set_x(int x) { x_ = x; }
auto set_y(int y) { y_ = y; }
OverworldEntity() = default;
virtual void UpdateMapProperties(short map_id) = 0;
};
} // namespace zelda3
} // namespace app
} // namespace yaze
#endif // YAZE_APP_ZELDA3_COMMON_H

View File

@@ -0,0 +1,161 @@
#include "app/zelda3/dungeon/object_renderer.h"
namespace yaze {
namespace app {
namespace zelda3 {
namespace dungeon {
void DungeonObjectRenderer::LoadObject(uint16_t objectId,
std::array<uint8_t, 16>& sheet_ids) {
vram_.sheets = sheet_ids;
rom_data_ = rom()->vector();
// Prepare the CPU and memory environment
memory_.Initialize(rom_data_);
// Fetch the subtype pointers for the given object ID
auto subtypeInfo = FetchSubtypeInfo(objectId);
// Configure the object based on the fetched information
ConfigureObject(subtypeInfo);
// Run the CPU emulation for the object's draw routines
RenderObject(subtypeInfo);
}
SubtypeInfo DungeonObjectRenderer::FetchSubtypeInfo(uint16_t object_id) {
SubtypeInfo info;
// Determine the subtype based on objectId
uint8_t subtype = 1;
// Based on the subtype, fetch the correct pointers
switch (subtype) {
case 1: // Subtype 1
info.subtype_ptr = core::subtype1_tiles + (object_id & 0xFF) * 2;
info.routine_ptr = core::subtype1_tiles + 0x200 + (object_id & 0xFF) * 2;
std::cout << "Subtype 1 " << std::hex << info.subtype_ptr << std::endl;
std::cout << "Subtype 1 " << std::hex << info.routine_ptr << std::endl;
break;
case 2: // Subtype 2
info.subtype_ptr = core::subtype2_tiles + (object_id & 0x7F) * 2;
info.routine_ptr = core::subtype2_tiles + 0x80 + (object_id & 0x7F) * 2;
break;
case 3: // Subtype 3
info.subtype_ptr = core::subtype3_tiles + (object_id & 0xFF) * 2;
info.routine_ptr = core::subtype3_tiles + 0x100 + (object_id & 0xFF) * 2;
break;
default:
// Handle unknown subtype
throw std::runtime_error("Unknown subtype for object ID: " +
std::to_string(object_id));
}
return info;
}
void DungeonObjectRenderer::ConfigureObject(const SubtypeInfo& info) {
cpu.A = 0x03D8;
cpu.X = 0x03D8;
cpu.DB = 0x7E;
// VRAM target destinations
cpu.WriteLong(0xBF, 0x7E2000);
cpu.WriteLong(0xCB, 0x7E2080);
cpu.WriteLong(0xC2, 0x7E2002);
cpu.WriteLong(0xCE, 0x7E2082);
cpu.SetAccumulatorSize(false);
cpu.SetIndexSize(false);
}
/**
* Example:
* the STA $BF, $CD, $C2, $CE are the location of the object in the room
* $B2 is used for size loop
* so if object size is setted on 07 that draw code will be repeated 7 times
* and since Y is increasing by 4 it makes the object draw from left to right
RoomDraw_Rightwards2x2_1to15or32:
#_018B89: JSR RoomDraw_GetSize_1to15or32
.next
#_018B8C: JSR RoomDraw_Rightwards2x2
#_018B8F: DEC.b $B2
#_018B91: BNE .next
#_018B93: RTS
RoomDraw_Rightwards2x2:
#_019895: LDA.w RoomDrawObjectData+0,X
#_019898: STA.b [$BF],Y
#_01989A: LDA.w RoomDrawObjectData+2,X
#_01989D: STA.b [$CB],Y
#_01989F: LDA.w RoomDrawObjectData+4,X
#_0198A2: STA.b [$C2],Y
#_0198A4: LDA.w RoomDrawObjectData+6,X
#_0198A7: STA.b [$CE],Y
#_0198A9: INY #4
#_0198AD: RTS
*/
void DungeonObjectRenderer::RenderObject(const SubtypeInfo& info) {
cpu.PB = 0x01;
cpu.PC = cpu.ReadWord(0x01 << 16 | info.routine_ptr);
// Push an initial value to the stack we can read later to confirm we are
// done
cpu.PushLong(0x01 << 16 | info.routine_ptr);
int i = 0;
while (true) {
uint8_t opcode = cpu.ReadByte(cpu.PB << 16 | cpu.PC);
cpu.ExecuteInstruction(opcode);
cpu.HandleInterrupts();
if ((i != 0 &&
(cpu.ReadWord((0x00 << 16 | cpu.SP() + 2)) == info.routine_ptr) ||
0x8b93 == cpu.PC)) {
std::cout << std::hex << cpu.ReadWord((0x00 << 16 | cpu.SP() + 3))
<< std::endl;
break;
}
i++;
}
UpdateObjectBitmap();
}
// In the underworld, this holds a copy of the entire BG tilemap for
// Layer 1 (BG2) in TILEMAPA
// Layer 2 (BG1) in TILEMAPB
void DungeonObjectRenderer::UpdateObjectBitmap() {
tilemap_.reserve(0x2000);
for (int i = 0; i < 0x2000; ++i) {
tilemap_.push_back(0);
}
int tilemap_offset = 0;
// Iterate over tilemap in memory to read tile IDs
for (int tile_index = 0; tile_index < 512; tile_index++) {
// Read the tile ID from memory
int tile_id = memory_.ReadWord(0x7E2000 + tile_index);
std::cout << "Tile ID: " << std::hex << tile_id << std::endl;
int sheet_number = tile_id / 32;
std::cout << "Sheet number: " << std::hex << sheet_number << std::endl;
int row = tile_id / 8;
int column = tile_id % 8;
int x = column * 8;
int y = row * 8;
auto sheet = rom()->mutable_graphics_sheet(vram_.sheets[sheet_number]);
// Copy the tile from VRAM using the read tile_id
sheet->Get8x8Tile(tile_id, x, y, tilemap_, tilemap_offset);
}
bitmap_.Create(256, 256, 8, tilemap_);
}
} // namespace dungeon
} // namespace zelda3
} // namespace app
} // namespace yaze

View File

@@ -18,188 +18,38 @@ namespace app {
namespace zelda3 { namespace zelda3 {
namespace dungeon { namespace dungeon {
class DungeonObjectRenderer : public SharedROM { struct PseudoVram {
public: std::array<uint8_t, 16> sheets;
struct PseudoVram { std::vector<gfx::SnesPalette> palettes;
std::array<uint8_t, 16> sheets; };
std::vector<gfx::SNESPalette> palettes;
};
struct SubtypeInfo {
uint32_t subtype_ptr;
uint32_t routine_ptr;
};
class DungeonObjectRenderer : public SharedRom {
public:
DungeonObjectRenderer() = default; DungeonObjectRenderer() = default;
void LoadObject(uint16_t objectId, std::array<uint8_t, 16>& sheet_ids) { void LoadObject(uint16_t objectId, std::array<uint8_t, 16>& sheet_ids);
vram_.sheets = sheet_ids;
rom_data_ = rom()->vector();
// Prepare the CPU and memory environment
memory_.Initialize(rom_data_);
// Fetch the subtype pointers for the given object ID
auto subtypeInfo = FetchSubtypeInfo(objectId);
// Configure the object based on the fetched information
ConfigureObject(subtypeInfo);
// Run the CPU emulation for the object's draw routines
RenderObject(subtypeInfo);
}
gfx::Bitmap* bitmap() { return &bitmap_; } gfx::Bitmap* bitmap() { return &bitmap_; }
auto memory() { return memory_; } auto memory() { return memory_; }
auto* memory_ptr() { return &memory_; } auto mutable_memory() { return &memory_; }
auto mutable_memory() { return memory_.data(); }
private: private:
struct SubtypeInfo { SubtypeInfo FetchSubtypeInfo(uint16_t object_id);
uint32_t subtype_ptr; void ConfigureObject(const SubtypeInfo& info);
uint32_t routine_ptr; void RenderObject(const SubtypeInfo& info);
}; void UpdateObjectBitmap();
SubtypeInfo FetchSubtypeInfo(uint16_t object_id) {
SubtypeInfo info;
// Determine the subtype based on objectId
uint8_t subtype = 1;
// Based on the subtype, fetch the correct pointers
switch (subtype) {
case 1: // Subtype 1
info.subtype_ptr = core::subtype1_tiles + (object_id & 0xFF) * 2;
info.routine_ptr =
core::subtype1_tiles + 0x200 + (object_id & 0xFF) * 2;
std::cout << "Subtype 1 " << std::hex << info.subtype_ptr << std::endl;
std::cout << "Subtype 1 " << std::hex << info.routine_ptr << std::endl;
break;
case 2: // Subtype 2
info.subtype_ptr = core::subtype2_tiles + (object_id & 0x7F) * 2;
info.routine_ptr = core::subtype2_tiles + 0x80 + (object_id & 0x7F) * 2;
break;
case 3: // Subtype 3
info.subtype_ptr = core::subtype3_tiles + (object_id & 0xFF) * 2;
info.routine_ptr =
core::subtype3_tiles + 0x100 + (object_id & 0xFF) * 2;
break;
default:
// Handle unknown subtype
throw std::runtime_error("Unknown subtype for object ID: " +
std::to_string(object_id));
}
// Find the RTS of the subtype routine
while (true) {
uint8_t opcode = memory_.ReadByte(info.routine_ptr);
if (opcode == 0x60) {
break;
}
info.routine_ptr++;
}
return info;
}
void ConfigureObject(const SubtypeInfo& info) {
cpu.A = 0x03D8;
cpu.X = 0x03D8;
cpu.DB = 0x7E;
// VRAM target destinations
cpu.WriteLong(0xBF, 0x7E2000);
cpu.WriteLong(0xCB, 0x7E2080);
cpu.WriteLong(0xC2, 0x7E2002);
cpu.WriteLong(0xCE, 0x7E2082);
cpu.SetAccumulatorSize(false);
cpu.SetIndexSize(false);
}
/**
* Example:
* the STA $BF, $CD, $C2, $CE are the location of the object in the room
* $B2 is used for size loop
* so if object size is setted on 07 that draw code will be repeated 7 times
* and since Y is increasing by 4 it makes the object draw from left to right
RoomDraw_Rightwards2x2_1to15or32:
#_018B89: JSR RoomDraw_GetSize_1to15or32
.next
#_018B8C: JSR RoomDraw_Rightwards2x2
#_018B8F: DEC.b $B2
#_018B91: BNE .next
#_018B93: RTS
RoomDraw_Rightwards2x2:
#_019895: LDA.w RoomDrawObjectData+0,X
#_019898: STA.b [$BF],Y
#_01989A: LDA.w RoomDrawObjectData+2,X
#_01989D: STA.b [$CB],Y
#_01989F: LDA.w RoomDrawObjectData+4,X
#_0198A2: STA.b [$C2],Y
#_0198A4: LDA.w RoomDrawObjectData+6,X
#_0198A7: STA.b [$CE],Y
#_0198A9: INY #4
#_0198AD: RTS
*/
void RenderObject(const SubtypeInfo& info) {
cpu.PB = 0x01;
cpu.PC = cpu.ReadWord(0x01 << 16 | info.routine_ptr);
int i = 0;
while (true) {
uint8_t opcode = cpu.ReadByte(cpu.PB << 16 | cpu.PC);
cpu.ExecuteInstruction(opcode);
cpu.HandleInterrupts();
if (i > 50) {
break;
}
i++;
}
UpdateObjectBitmap();
}
// In the underworld, this holds a copy of the entire BG tilemap for
// Layer 1 (BG2) in TILEMAPA
// Layer 2 (BG1) in TILEMAPB
//
// In the overworld, this holds the entire map16 space, using both blocks as a
// single array TILEMAPA = $7E2000 TILEMAPB = $7E4000
void UpdateObjectBitmap() {
tilemap_.reserve(0x2000);
for (int i = 0; i < 0x2000; ++i) {
tilemap_.push_back(0);
}
int tilemap_offset = 0;
// Iterate over tilemap in memory to read tile IDs
for (int tile_index = 0; tile_index < 512; tile_index++) {
// Read the tile ID from memory
int tile_id = memory_.ReadWord(0x7E4000 + tile_index);
int sheet_number = tile_id / 32;
int local_id = tile_id % 32;
int row = local_id / 8;
int column = local_id % 8;
int x = column * 8;
int y = row * 8;
auto sheet = rom()->mutable_graphics_sheet(vram_.sheets[sheet_number]);
// Copy the tile from VRAM using the read tile_id
sheet->Get8x8Tile(tile_id, x, y, tilemap_, tilemap_offset);
}
bitmap_.Create(256, 256, 8, tilemap_);
}
std::vector<uint8_t> tilemap_; std::vector<uint8_t> tilemap_;
uint16_t pc_with_rts_; uint16_t pc_with_rts_;
std::vector<uint8_t> rom_data_; std::vector<uint8_t> rom_data_;
emu::MemoryImpl memory_; emu::memory::MemoryImpl memory_;
emu::ClockImpl clock_; emu::ClockImpl clock_;
emu::CPU cpu{memory_, clock_}; emu::Cpu cpu{memory_, clock_};
emu::Ppu ppu{memory_, clock_}; emu::video::Ppu ppu{memory_, clock_};
gfx::Bitmap bitmap_; gfx::Bitmap bitmap_;
PseudoVram vram_; PseudoVram vram_;
}; };

View File

@@ -31,12 +31,12 @@ void Room::LoadHeader() {
auto header_location = core::SnesToPc(address); auto header_location = core::SnesToPc(address);
bg2 = (Background2)((rom()->data()[header_location] >> 5) & 0x07); bg2_ = (Background2)((rom()->data()[header_location] >> 5) & 0x07);
// collision = (CollisionKey)((rom()->data()[header_location] >> 2) & 0x07); // collision = (CollisionKey)((rom()->data()[header_location] >> 2) & 0x07);
light = ((rom()->data()[header_location]) & 0x01) == 1; is_light_ = ((rom()->data()[header_location]) & 0x01) == 1;
if (light) { if (is_light_) {
bg2 = Background2::DarkRoom; bg2_ = Background2::DarkRoom;
} }
palette = ((rom()->data()[header_location + 1] & 0x3F)); palette = ((rom()->data()[header_location + 1] & 0x3F));
@@ -46,16 +46,158 @@ void Room::LoadHeader() {
// tag1 = (TagKey)((rom()->data()[header_location + 5])); // tag1 = (TagKey)((rom()->data()[header_location + 5]));
// tag2 = (TagKey)((rom()->data()[header_location + 6])); // tag2 = (TagKey)((rom()->data()[header_location + 6]));
staircase_plane[0] = ((rom()->data()[header_location + 7] >> 2) & 0x03); staircase_plane_[0] = ((rom()->data()[header_location + 7] >> 2) & 0x03);
staircase_plane[1] = ((rom()->data()[header_location + 7] >> 4) & 0x03); staircase_plane_[1] = ((rom()->data()[header_location + 7] >> 4) & 0x03);
staircase_plane[2] = ((rom()->data()[header_location + 7] >> 6) & 0x03); staircase_plane_[2] = ((rom()->data()[header_location + 7] >> 6) & 0x03);
staircase_plane[3] = ((rom()->data()[header_location + 8]) & 0x03); staircase_plane_[3] = ((rom()->data()[header_location + 8]) & 0x03);
holewarp = (rom()->data()[header_location + 9]); holewarp = (rom()->data()[header_location + 9]);
staircase_rooms[0] = (rom()->data()[header_location + 10]); staircase_rooms_[0] = (rom()->data()[header_location + 10]);
staircase_rooms[1] = (rom()->data()[header_location + 11]); staircase_rooms_[1] = (rom()->data()[header_location + 11]);
staircase_rooms[2] = (rom()->data()[header_location + 12]); staircase_rooms_[2] = (rom()->data()[header_location + 12]);
staircase_rooms[3] = (rom()->data()[header_location + 13]); staircase_rooms_[3] = (rom()->data()[header_location + 13]);
// Calculate the size of the room based on how many objects are used per room
// Some notes from hacker Zarby89
// vanilla rooms are using in average ~0x80 bytes
// a "normal" person who wants more details than vanilla will use around 0x100
// bytes per rooms you could fit 128 rooms like that in 1 bank
// F8000 I don't remember if that's PC or snes tho
// Check last rooms
// F8000+(roomid*3)
// So we want to search the rom() object at this addressed based on the room
// ID since it's the roomid * 3 we will by pulling 3 bytes at a time We can do
// this with the rom()->ReadByteVector(addr, size)
try {
// Existing room size address calculation...
auto room_size_address = 0xF8000 + (room_id_ * 3);
if (flags()->kLogToConsole)
std::cout << "Room #" << room_id_ << " Address: " << std::hex
<< room_size_address << std::endl;
// Reading bytes for long address construction
uint8_t low = rom()->data()[room_size_address];
uint8_t high = rom()->data()[room_size_address + 1];
uint8_t bank = rom()->data()[room_size_address + 2];
// Constructing the long address
int long_address = (bank << 16) | (high << 8) | low;
if (flags()->kLogToConsole)
std::cout << std::hex << std::setfill('0') << std::setw(6) << long_address
<< std::endl;
room_size_pointer_ = long_address;
if (long_address == 0x0A8000) {
// Blank room disregard in size calculation
if (flags()->kLogToConsole)
std::cout << "Size of Room #" << room_id_ << ": 0 bytes" << std::endl;
room_size_ = 0;
} else {
// use the long address to calculate the size of the room
// we will use the room_id_ to calculate the next room's address
// and subtract the two to get the size of the room
int next_room_address = 0xF8000 + ((room_id_ + 1) * 3);
if (flags()->kLogToConsole)
std::cout << "Next Room Address: " << std::hex << next_room_address
<< std::endl;
// Reading bytes for long address construction
uint8_t next_low = rom()->data()[next_room_address];
uint8_t next_high = rom()->data()[next_room_address + 1];
uint8_t next_bank = rom()->data()[next_room_address + 2];
// Constructing the long address
int next_long_address = (next_bank << 16) | (next_high << 8) | next_low;
if (flags()->kLogToConsole)
std::cout << std::hex << std::setfill('0') << std::setw(6)
<< next_long_address << std::endl;
// Calculate the size of the room
int room_size = next_long_address - long_address;
room_size_ = room_size;
if (flags()->kLogToConsole)
std::cout << "Size of Room #" << room_id_ << ": " << std::dec
<< room_size << " bytes" << std::endl;
}
} catch (const std::exception& e) {
if (flags()->kLogToConsole) std::cout << "Error: " << e.what() << std::endl;
}
}
void Room::LoadRoomFromROM() {
// Load dungeon header
auto rom_data = rom()->vector();
int header_pointer = core::SnesToPc(kRoomHeaderPointer);
message_id_ = messages_id_dungeon + (room_id_ * 2);
int address = (rom()->data()[kRoomHeaderPointerBank] << 16) +
(rom()->data()[(header_pointer + 1) + (room_id_ * 2)] << 8) +
rom()->data()[(header_pointer) + (room_id_ * 2)];
int hpos = core::SnesToPc(address);
hpos++;
uint8_t b = rom_data[hpos];
layer2_mode_ = (b >> 5);
// TODO(@scawful): Make LayerMerging object.
// LayerMerging = LayerMergeType.ListOf[(b & 0x0C) >> 2];
is_dark_ = (b & 0x01) == 0x01;
hpos++;
palette_ = rom_data[hpos];
hpos++;
background_tileset_ = rom_data[hpos];
hpos++;
sprite_tileset_ = rom_data[hpos];
hpos++;
layer2_behavior_ = rom_data[hpos];
hpos++;
tag1_ = rom_data[hpos];
hpos++;
tag2_ = rom_data[hpos];
hpos++;
b = rom_data[hpos];
pits_.TargetLayer = (uchar)(b & 0x03);
stair1_.TargetLayer = (uchar)((b >> 2) & 0x03);
stair2_.TargetLayer = (uchar)((b >> 4) & 0x03);
stair3_.TargetLayer = (uchar)((b >> 6) & 0x03);
hpos++;
stair4_.TargetLayer = (uchar)(rom_data[hpos] & 0x03);
hpos++;
pits_.Target = rom_data[hpos];
hpos++;
stair1_.Target = rom_data[hpos];
hpos++;
stair2_.Target = rom_data[hpos];
hpos++;
stair3_.Target = rom_data[hpos];
hpos++;
stair4_.Target = rom_data[hpos];
hpos++;
// Load room objects
int objectPointer = core::SnesToPc(room_object_pointer);
int room_address = objectPointer + (room_id_ * 3);
int objects_location = core::SnesToPc(room_address);
// Load sprites
// int spr_ptr = 0x040000 | rooms_sprite_pointer;
// int sprite_address =
// core::SnesToPc(dungeon_spr_ptrs | spr_ptr + (room_id_ * 2));
} }
void Room::LoadRoomGraphics(uchar entrance_blockset) { void Room::LoadRoomGraphics(uchar entrance_blockset) {
@@ -125,89 +267,19 @@ void Room::LoadAnimatedGraphics() {
auto rom_data = rom()->vector(); auto rom_data = rom()->vector();
int data = 0; int data = 0;
while (data < 512) { while (data < 512) {
uchar mapByte = uchar map_byte =
gfx_buffer_data[data + (92 * 2048) + (512 * animated_frame)]; gfx_buffer_data[data + (92 * 2048) + (512 * animated_frame_)];
current_gfx16_[data + (7 * 2048)] = mapByte; current_gfx16_[data + (7 * 2048)] = map_byte;
mapByte = map_byte =
gfx_buffer_data[data + (rom_data[gfx_ptr + BackgroundTileset] * 2048) + gfx_buffer_data[data +
(512 * animated_frame)]; (rom_data[gfx_ptr + background_tileset_] * 2048) +
current_gfx16_[data + (7 * 2048) - 512] = mapByte; (512 * animated_frame_)];
current_gfx16_[data + (7 * 2048) - 512] = map_byte;
data++; data++;
} }
} }
void Room::LoadSprites() {
auto rom_data = rom()->vector();
int spritePointer = (0x04 << 16) + (rom_data[rooms_sprite_pointer + 1] << 8) +
(rom_data[rooms_sprite_pointer]);
int sprite_address_snes =
(0x09 << 16) + (rom_data[spritePointer + (room_id_ * 2) + 1] << 8) +
rom_data[spritePointer + (room_id_ * 2)];
int sprite_address = core::SnesToPc(sprite_address_snes);
bool sortsprites = rom_data[sprite_address] == 1;
sprite_address += 1;
while (true) {
uint8_t b1 = rom_data[sprite_address];
uint8_t b2 = rom_data[sprite_address + 1];
uint8_t b3 = rom_data[sprite_address + 2];
if (b1 == 0xFF) {
break;
}
// sprites_.emplace_back(this, b3, (b2 & 0x1F), (b1 & 0x1F),
// ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
// (b1 & 0x80) >> 7);
if (sprites_.size() > 1) {
Sprite& spr = sprites_.back();
Sprite& prevSprite = sprites_[sprites_.size() - 2];
if (spr.id() == 0xE4 && spr.x() == 0x00 && spr.y() == 0x1E &&
spr.layer() == 1 && spr.subtype() == 0x18) {
// prevSprite.keyDrop() = 1;
sprites_.pop_back();
}
if (spr.id() == 0xE4 && spr.x() == 0x00 && spr.y() == 0x1D &&
spr.layer() == 1 && spr.subtype() == 0x18) {
// prevSprite.keyDrop() = 2;
sprites_.pop_back();
}
}
sprite_address += 3;
}
}
void Room::LoadChests() {
auto rom_data = rom()->vector();
int cpos = (rom_data[chests_data_pointer1 + 2] << 16) +
(rom_data[chests_data_pointer1 + 1] << 8) +
(rom_data[chests_data_pointer1]);
cpos = core::SnesToPc(cpos);
int clength = (rom_data[chests_length_pointer + 1] << 8) +
(rom_data[chests_length_pointer]);
for (int i = 0; i < clength; i++) {
if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
0x7FFF) == room_id_) {
// There's a chest in that room !
bool big = false;
if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
0x8000) == 0x8000) // ?????
{
big = true;
}
chests_in_room.emplace_back(ChestData(rom_data[cpos + (i * 3) + 2], big));
}
}
}
void Room::LoadObjects() { void Room::LoadObjects() {
auto rom_data = rom()->vector(); auto rom_data = rom()->vector();
int objectPointer = (rom_data[room_object_pointer + 2] << 16) + int objectPointer = (rom_data[room_object_pointer + 2] << 16) +
@@ -225,16 +297,17 @@ void Room::LoadObjects() {
std::cout << "Room ID : " << room_id_ << std::endl; std::cout << "Room ID : " << room_id_ << std::endl;
} }
if (floor) { if (is_floor_) {
floor1 = static_cast<uint8_t>(rom_data[objects_location] & 0x0F); floor1_graphics_ = static_cast<uint8_t>(rom_data[objects_location] & 0x0F);
floor2 = static_cast<uint8_t>((rom_data[objects_location] >> 4) & 0x0F); floor2_graphics_ =
static_cast<uint8_t>((rom_data[objects_location] >> 4) & 0x0F);
} }
layout = static_cast<uint8_t>((rom_data[objects_location + 1] >> 2) & 0x07); layout = static_cast<uint8_t>((rom_data[objects_location + 1] >> 2) & 0x07);
LoadChests(); LoadChests();
staircaseRooms.clear(); staircase_rooms_vec_.clear();
int nbr_of_staircase = 0; int nbr_of_staircase = 0;
int pos = objects_location + 2; int pos = objects_location + 2;
@@ -314,7 +387,7 @@ void Room::LoadObjects() {
if (nbr_of_staircase < 4) { if (nbr_of_staircase < 4) {
tilesObjects.back().options |= ObjectOption::Stairs; tilesObjects.back().options |= ObjectOption::Stairs;
staircaseRooms.push_back(StaircaseRoom( staircaseRooms.push_back(StaircaseRoom(
posX, posY, "To " + staircase_rooms[nbr_of_staircase])); posX, posY, "To " + staircase_rooms_[nbr_of_staircase]));
nbr_of_staircase++; nbr_of_staircase++;
} else { } else {
tilesObjects.back().options |= ObjectOption::Stairs; tilesObjects.back().options |= ObjectOption::Stairs;
@@ -348,76 +421,77 @@ void Room::LoadObjects() {
} }
} }
void Room::LoadRoomFromROM() { void Room::LoadSprites() {
// Load dungeon header
auto rom_data = rom()->vector(); auto rom_data = rom()->vector();
int header_pointer = core::SnesToPc(kRoomHeaderPointer); int sprite_pointer = (0x04 << 16) +
(rom_data[rooms_sprite_pointer + 1] << 8) +
(rom_data[rooms_sprite_pointer]);
int sprite_address_snes =
(0x09 << 16) + (rom_data[sprite_pointer + (room_id_ * 2) + 1] << 8) +
rom_data[sprite_pointer + (room_id_ * 2)];
message_id_ = messages_id_dungeon + (room_id_ * 2); int sprite_address = core::SnesToPc(sprite_address_snes);
bool sortsprites = rom_data[sprite_address] == 1;
sprite_address += 1;
int address = (rom()->data()[kRoomHeaderPointerBank] << 16) + while (true) {
(rom()->data()[(header_pointer + 1) + (room_id_ * 2)] << 8) + uint8_t b1 = rom_data[sprite_address];
rom()->data()[(header_pointer) + (room_id_ * 2)]; uint8_t b2 = rom_data[sprite_address + 1];
uint8_t b3 = rom_data[sprite_address + 2];
int hpos = core::SnesToPc(address); if (b1 == 0xFF) {
hpos++; break;
uint8_t b = rom_data[hpos]; }
Layer2Mode = (b >> 5); // sprites_.emplace_back(this, b3, (b2 & 0x1F), (b1 & 0x1F),
// TODO(@scawful): Make LayerMerging object. // ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
// LayerMerging = LayerMergeType.ListOf[(b & 0x0C) >> 2]; // (b1 & 0x80) >> 7);
IsDark = (b & 0x01) == 0x01; if (sprites_.size() > 1) {
hpos++; Sprite& spr = sprites_.back();
Sprite& prevSprite = sprites_[sprites_.size() - 2];
Palette = rom_data[hpos]; if (spr.id() == 0xE4 && spr.x() == 0x00 && spr.y() == 0x1E &&
hpos++; spr.layer() == 1 && spr.subtype() == 0x18) {
// prevSprite.keyDrop() = 1;
sprites_.pop_back();
}
BackgroundTileset = rom_data[hpos]; if (spr.id() == 0xE4 && spr.x() == 0x00 && spr.y() == 0x1D &&
hpos++; spr.layer() == 1 && spr.subtype() == 0x18) {
// prevSprite.keyDrop() = 2;
sprites_.pop_back();
}
}
SpriteTileset = rom_data[hpos]; sprite_address += 3;
hpos++; }
}
Layer2Behavior = rom_data[hpos]; void Room::LoadChests() {
hpos++; auto rom_data = rom()->vector();
int cpos = (rom_data[chests_data_pointer1 + 2] << 16) +
(rom_data[chests_data_pointer1 + 1] << 8) +
(rom_data[chests_data_pointer1]);
cpos = core::SnesToPc(cpos);
int clength = (rom_data[chests_length_pointer + 1] << 8) +
(rom_data[chests_length_pointer]);
Tag1 = rom_data[hpos]; for (int i = 0; i < clength; i++) {
hpos++; if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
0x7FFF) == room_id_) {
// There's a chest in that room !
bool big = false;
if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
0x8000) == 0x8000) // ?????
{
big = true;
}
Tag2 = rom_data[hpos]; chests_in_room_.emplace_back(
hpos++; ChestData(rom_data[cpos + (i * 3) + 2], big));
}
b = rom_data[hpos]; }
Pits.TargetLayer = (uchar)(b & 0x03);
Stair1.TargetLayer = (uchar)((b >> 2) & 0x03);
Stair2.TargetLayer = (uchar)((b >> 4) & 0x03);
Stair3.TargetLayer = (uchar)((b >> 6) & 0x03);
hpos++;
Stair4.TargetLayer = (uchar)(rom_data[hpos] & 0x03);
hpos++;
Pits.Target = rom_data[hpos];
hpos++;
Stair1.Target = rom_data[hpos];
hpos++;
Stair2.Target = rom_data[hpos];
hpos++;
Stair3.Target = rom_data[hpos];
hpos++;
Stair4.Target = rom_data[hpos];
hpos++;
// Load room objects
// int objectPointer = core::SnesToPc(room_object_pointer);
// int room_address = objectPointer + (room_id_ * 3);
// int objects_location = core::SnesToPc(room_address);
// Load sprites
// int spr_ptr = 0x040000 | rooms_sprite_pointer;
// int sprite_address =
// core::SnesToPc(dungeon_spr_ptrs | spr_ptr + (room_id_ * 2));
} }
} // namespace dungeon } // namespace dungeon

View File

@@ -39,7 +39,6 @@ namespace dungeon {
constexpr int room_object_layout_pointer = 0x882D; constexpr int room_object_layout_pointer = 0x882D;
constexpr int room_object_pointer = 0x874C; // Long pointer constexpr int room_object_pointer = 0x874C; // Long pointer
constexpr int entrance_gfx_group = 0x5D97;
constexpr int dungeons_main_bg_palette_pointers = 0xDEC4B; // JP Same constexpr int dungeons_main_bg_palette_pointers = 0xDEC4B; // JP Same
constexpr int dungeons_palettes = 0xDD734; constexpr int dungeons_palettes = 0xDD734;
constexpr int room_items_pointers = 0xDB69; // JP 0xDB67 constexpr int room_items_pointers = 0xDB69; // JP 0xDB67
@@ -92,7 +91,6 @@ class DungeonDestination {
uint8_t Index; uint8_t Index;
uint8_t Target = 0; uint8_t Target = 0;
uint8_t TargetLayer = 0; uint8_t TargetLayer = 0;
// RoomObject* AssociatedObject = nullptr;
}; };
struct object_door { struct object_door {
@@ -118,37 +116,43 @@ struct ChestData {
struct StaircaseRooms {}; struct StaircaseRooms {};
class Room : public SharedROM { class Room : public SharedRom, public core::ExperimentFlags {
public: public:
Room() = default; Room() = default;
Room(int room_id) : room_id_(room_id) {} Room(int room_id) : room_id_(room_id) {}
~Room() = default; ~Room() = default;
void LoadHeader(); void LoadHeader();
void LoadRoomFromROM();
void LoadRoomGraphics(uchar entrance_blockset = 0xFF); void LoadRoomGraphics(uchar entrance_blockset = 0xFF);
void CopyRoomGraphicsToBuffer(); void CopyRoomGraphicsToBuffer();
void LoadAnimatedGraphics(); void LoadAnimatedGraphics();
void LoadObjects();
void LoadSprites(); void LoadSprites();
void LoadChests(); void LoadChests();
void LoadObjects();
void LoadRoomFromROM();
auto blocks() const { return blocks_; } auto blocks() const { return blocks_; }
auto& mutable_blocks() { return blocks_; } auto& mutable_blocks() { return blocks_; }
auto layer1() const { return background_bmps_[0]; }
auto layer2() const { return background_bmps_[1]; }
auto layer3() const { return background_bmps_[2]; }
auto room_size() const { return room_size_; }
auto room_size_ptr() const { return room_size_pointer_; }
auto set_room_size(uint64_t size) { room_size_ = size; }
RoomObject AddObject(short oid, uint8_t x, uint8_t y, uint8_t size, RoomObject AddObject(short oid, uint8_t x, uint8_t y, uint8_t size,
uint8_t layer) { uint8_t layer) {
return RoomObject(oid, x, y, size, layer); return RoomObject(oid, x, y, size, layer);
} }
uint8_t floor1 = 0;
uint8_t floor2 = 0;
uint8_t blockset = 0; uint8_t blockset = 0;
uint8_t spriteset = 0; uint8_t spriteset = 0;
uint8_t palette = 0; uint8_t palette = 0;
uint8_t layout = 0; uint8_t layout = 0;
uint8_t holewarp = 0; uint8_t holewarp = 0;
uint8_t floor1 = 0;
uint8_t floor2 = 0;
uint16_t message_id_ = 0; uint16_t message_id_ = 0;
@@ -158,44 +162,49 @@ class Room : public SharedROM {
std::vector<uint8_t> current_gfx16_; std::vector<uint8_t> current_gfx16_;
private: private:
bool light = false; bool is_light_;
bool is_loaded_ = false; bool is_loaded_;
bool IsDark = false; bool is_dark_;
bool floor = false; bool is_floor_;
int room_id_ = 0; int room_id_;
int animated_frame = 0; int animated_frame_;
uchar Tag1; uchar tag1_;
uchar Tag2; uchar tag2_;
uint8_t staircase_plane[4]; uint8_t staircase_plane_[4];
uint8_t staircase_rooms[4]; uint8_t staircase_rooms_[4];
uint8_t BackgroundTileset; uint8_t background_tileset_;
uint8_t SpriteTileset; uint8_t sprite_tileset_;
uint8_t Layer2Behavior; uint8_t layer2_behavior_;
uint8_t Palette; uint8_t palette_;
uint8_t Floor1Graphics; uint8_t floor1_graphics_;
uint8_t Floor2Graphics; uint8_t floor2_graphics_;
uint8_t Layer2Mode; uint8_t layer2_mode_;
uint64_t room_size_;
int64_t room_size_pointer_;
std::array<uint8_t, 16> blocks_; std::array<uint8_t, 16> blocks_;
std::array<uchar, 16> ChestList; std::array<uchar, 16> chest_list_;
std::array<gfx::Bitmap, 3> background_bmps_; std::array<gfx::Bitmap, 3> background_bmps_;
std::vector<zelda3::Sprite> sprites_; std::vector<zelda3::Sprite> sprites_;
std::vector<StaircaseRooms> staircaseRooms; std::vector<StaircaseRooms> staircase_rooms_vec_;
Background2 bg2; Background2 bg2_;
DungeonDestination Pits; DungeonDestination pits_;
DungeonDestination Stair1; DungeonDestination stair1_;
DungeonDestination Stair2; DungeonDestination stair2_;
DungeonDestination Stair3; DungeonDestination stair3_;
DungeonDestination Stair4; DungeonDestination stair4_;
std::vector<ChestData> chests_in_room; std::vector<ChestData> chests_in_room_;
std::vector<RoomObject> tilesObjects; std::vector<RoomObject> tile_objects_;
std::vector<int> room_addresses_;
}; };
} // namespace dungeon } // namespace dungeon

Some files were not shown because too many files have changed in this diff Show More