backend-infra-engineer: Pre-0.2.2 2024 Q4 snapshot

This commit is contained in:
scawful
2024-11-28 11:50:47 -05:00
parent 75bf38fa71
commit 18b7fb9abf
238 changed files with 22057 additions and 8538 deletions

View File

@@ -1,40 +0,0 @@
include(app/editor/CMakeLists.txt)
include(app/zelda3/CMakeLists.txt)
add_executable(
yaze
app/yaze.cc
app/rom.cc
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
${IMGUI_TEST_ENGINE_SOURCES}
)
target_include_directories(
yaze PUBLIC
lib/
app/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
)
target_link_libraries(
yaze PUBLIC
${ABSL_TARGETS}
${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS}
ImGuiTestEngine
ImGui
)
if (APPLE)
target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY})
endif()

73
src/app/app.cmake Normal file
View File

@@ -0,0 +1,73 @@
include(app/core/core.cmake)
include(app/editor/editor.cmake)
include(app/gfx/gfx.cmake)
include(app/gui/gui.cmake)
include(app/zelda3/zelda3.cmake)
if (APPLE)
add_executable(
yaze
MACOSX_BUNDLE
app/main.cc
app/rom.cc
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
# Bundled Resources
${YAZE_RESOURCE_FILES}
)
else()
add_executable(
yaze
app/main.cc
app/rom.cc
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
)
endif()
target_include_directories(
yaze PUBLIC
lib/
app/
${ASAR_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/incl/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
)
target_link_libraries(
yaze PUBLIC
asar-static
${ABSL_TARGETS}
${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS}
ImGui
ImGuiTestEngine
)
if (APPLE)
target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY})
endif()
if (WIN32 OR MINGW)
target_link_libraries(
yaze PUBLIC
${CMAKE_SOURCE_DIR}/build/build-windows/bin/libpng16.dll
zlib
mingw32
ws2_32)
endif()

View File

@@ -1,107 +1,60 @@
#include "common.h"
#include "imgui/imgui.h"
#include <zlib.h>
#include <chrono>
#include <cstdint>
#include <functional>
#include <cstring>
#include <memory>
#include <stack>
#include <string>
#include <vector>
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
namespace yaze {
namespace app {
namespace core {
std::shared_ptr<ExperimentFlags::Flags> ExperimentFlags::flags_;
namespace {
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%06X", dword);
return result;
}
uint32_t SnesToPc(uint32_t addr) {
if (addr >= 0x808000) {
addr -= 0x808000;
}
uint32_t temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
return (temp + 0x0);
}
uint32_t PcToSnes(uint32_t addr) {
uint8_t *b = reinterpret_cast<uint8_t *>(&addr);
b[2] = static_cast<uint8_t>(b[2] * 2);
if (b[1] >= 0x80) {
b[2] += 1;
} else {
b[1] += 0x80;
}
return addr;
}
uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr) {
uint32_t result = 0;
result = (bank << 16) | addr;
return result;
}
int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3) {
return (addr1 << 16) | (addr2 << 8) | addr3;
}
// hextodec has been imported from SNESDisasm to parse hex numbers
int HexToDec(char *input, int length) {
int result = 0;
int value;
int ceiling = length - 1;
int power16 = 16;
int j = ceiling;
for (; j >= 0; j--) {
if (input[j] >= 'A' && input[j] <= 'F') {
value = input[j] - 'F';
value += 15;
} else {
value = input[j] - '9';
value += 9;
void encode(uint64_t data, std::vector<uint8_t> &output) {
while (true) {
uint8_t x = data & 0x7f;
data >>= 7;
if (data == 0) {
output.push_back(0x80 | x);
break;
}
if (j == ceiling) {
result += value;
continue;
}
result += (value * power16);
power16 *= 16;
output.push_back(x);
data--;
}
return result;
}
bool StringReplace(std::string &str, const std::string &from,
const std::string &to) {
size_t start = str.find(from);
if (start == std::string::npos) return false;
uint64_t decode(const std::vector<uint8_t> &input, size_t &offset) {
uint64_t data = 0;
uint64_t shift = 1;
while (true) {
uint8_t x = input[offset++];
data += (x & 0x7f) * shift;
if (x & 0x80) break;
shift <<= 7;
data += shift;
}
return data;
}
str.replace(start, from.length(), to);
return true;
uint32_t crc32(const std::vector<uint8_t> &data) {
uint32_t crc = ::crc32(0L, Z_NULL, 0);
return ::crc32(crc, data.data(), data.size());
}
// "load little endian value at the given byte offset and shift to get its
// value relative to the base offset (powers of 256, essentially)"
unsigned ldle(uint8_t const *const p_arr, unsigned const p_index) {
uint32_t v = p_arr[p_index];
v <<= (8 * p_index);
return v;
}
void stle(uint8_t *const p_arr, size_t const p_index, unsigned const p_val) {
@@ -125,25 +78,6 @@ void stle2(uint8_t *const p_arr, unsigned const p_val) {
void stle3(uint8_t *const p_arr, unsigned const p_val) {
stle(p_arr, 3, p_val);
}
void stle16b(uint8_t *const p_arr, uint16_t const p_val) {
stle0(p_arr, p_val);
stle1(p_arr, p_val);
}
// "Store little endian 16-bit value using a byte pointer, offset by an
// index before dereferencing"
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
uint16_t const p_val) {
stle16b(p_arr + (p_index * 2), p_val);
}
// "load little endian value at the given byte offset and shift to get its
// value relative to the base offset (powers of 256, essentially)"
unsigned ldle(uint8_t const *const p_arr, unsigned const p_index) {
uint32_t v = p_arr[p_index];
v <<= (8 * p_index);
return v;
}
// Helper function to get the first byte in a little endian number
uint32_t ldle0(uint8_t const *const p_arr) { return ldle(p_arr, 0); }
@@ -156,32 +90,244 @@ uint32_t ldle2(uint8_t const *const p_arr) { return ldle(p_arr, 2); }
// Helper function to get the third byte in a little endian number
uint32_t ldle3(uint8_t const *const p_arr) { return ldle(p_arr, 3); }
// Load little endian halfword (16-bit) dereferenced from
uint16_t ldle16b(uint8_t const *const p_arr) {
uint16_t v = 0;
v |= (ldle0(p_arr) | ldle1(p_arr));
} // namespace
return v;
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;
}
// Load little endian halfword (16-bit) dereferenced from an arrays of bytes.
// This version provides an index that will be multiplied by 2 and added to the
// base address.
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) {
return ldle16b(p_arr + (2 * p_index));
std::string UppercaseHexWord(uint16_t word, bool leading) {
if (leading) {
std::string result = absl::StrFormat("0x%04X", word);
return result;
}
std::string result = absl::StrFormat("%04X", word);
return result;
}
std::string UppercaseHexLong(uint32_t dword) {
std::string result = absl::StrFormat("0x%06X", dword);
return result;
}
std::string UppercaseHexLongLong(uint64_t qword) {
std::string result = absl::StrFormat("0x%08X", qword);
return result;
}
// Initialize the static member
std::stack<ImGuiID> ImGuiIdIssuer::idStack;
bool StringReplace(std::string &str, const std::string &from,
const std::string &to) {
size_t start = str.find(from);
if (start == std::string::npos) return false;
str.replace(start, from.length(), to);
return true;
}
std::shared_ptr<ExperimentFlags::Flags> ExperimentFlags::flags_;
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc) {
uint32_t ret = (PcToSnes(addr) & 0xFF0000) | (data[addr + 1] << 8) | data[addr];
uint32_t ret =
(PcToSnes(addr) & 0xFF0000) | (data[addr + 1] << 8) | data[addr];
if (pc) {
return SnesToPc(ret);
}
return ret;
}
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
uint16_t const p_val) {
stle16b(p_arr + (p_index * 2), p_val);
}
void stle16b(uint8_t *const p_arr, uint16_t const p_val) {
stle0(p_arr, p_val);
stle1(p_arr, p_val);
}
uint16_t ldle16b(uint8_t const *const p_arr) {
uint16_t v = 0;
v |= (ldle0(p_arr) | ldle1(p_arr));
return v;
}
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) {
return ldle16b(p_arr + (2 * p_index));
}
void CreateBpsPatch(const std::vector<uint8_t> &source,
const std::vector<uint8_t> &target,
std::vector<uint8_t> &patch) {
patch.clear();
patch.insert(patch.end(), {'B', 'P', 'S', '1'});
encode(source.size(), patch);
encode(target.size(), patch);
encode(0, patch); // No metadata
size_t source_offset = 0;
size_t target_offset = 0;
int64_t source_rel_offset = 0;
int64_t target_rel_offset = 0;
while (target_offset < target.size()) {
if (source_offset < source.size() &&
source[source_offset] == target[target_offset]) {
size_t length = 0;
while (source_offset + length < source.size() &&
target_offset + length < target.size() &&
source[source_offset + length] == target[target_offset + length]) {
length++;
}
encode((length - 1) << 2 | 0, patch); // SourceRead
source_offset += length;
target_offset += length;
} else {
size_t length = 0;
while (
target_offset + length < target.size() &&
(source_offset + length >= source.size() ||
source[source_offset + length] != target[target_offset + length])) {
length++;
}
if (length > 0) {
encode((length - 1) << 2 | 1, patch); // TargetRead
for (size_t i = 0; i < length; i++) {
patch.push_back(target[target_offset + i]);
}
target_offset += length;
}
}
// SourceCopy
if (source_offset < source.size()) {
size_t length = 0;
int64_t offset = source_offset - source_rel_offset;
while (source_offset + length < source.size() &&
target_offset + length < target.size() &&
source[source_offset + length] == target[target_offset + length]) {
length++;
}
if (length > 0) {
encode((length - 1) << 2 | 2, patch);
encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch);
source_offset += length;
target_offset += length;
source_rel_offset = source_offset;
}
}
// TargetCopy
if (target_offset > 0) {
size_t length = 0;
int64_t offset = target_offset - target_rel_offset;
while (target_offset + length < target.size() &&
target[target_offset - 1] == target[target_offset + length]) {
length++;
}
if (length > 0) {
encode((length - 1) << 2 | 3, patch);
encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch);
target_offset += length;
target_rel_offset = target_offset;
}
}
}
patch.resize(patch.size() + 12); // Make space for the checksums
uint32_t source_checksum = crc32(source);
uint32_t target_checksum = crc32(target);
uint32_t patch_checksum = crc32(patch);
memcpy(patch.data() + patch.size() - 12, &source_checksum, sizeof(uint32_t));
memcpy(patch.data() + patch.size() - 8, &target_checksum, sizeof(uint32_t));
memcpy(patch.data() + patch.size() - 4, &patch_checksum, sizeof(uint32_t));
}
void ApplyBpsPatch(const std::vector<uint8_t> &source,
const std::vector<uint8_t> &patch,
std::vector<uint8_t> &target) {
if (patch.size() < 4 || patch[0] != 'B' || patch[1] != 'P' ||
patch[2] != 'S' || patch[3] != '1') {
throw std::runtime_error("Invalid patch format");
}
size_t patch_offset = 4;
uint64_t target_size = decode(patch, patch_offset);
uint64_t metadata_size = decode(patch, patch_offset);
patch_offset += metadata_size;
target.resize(target_size);
size_t source_offset = 0;
size_t target_offset = 0;
int64_t source_rel_offset = 0;
int64_t target_rel_offset = 0;
while (patch_offset < patch.size() - 12) {
uint64_t data = decode(patch, patch_offset);
uint64_t command = data & 3;
uint64_t length = (data >> 2) + 1;
switch (command) {
case 0: // SourceRead
while (length--) {
target[target_offset++] = source[source_offset++];
}
break;
case 1: // TargetRead
while (length--) {
target[target_offset++] = patch[patch_offset++];
}
break;
case 2: // SourceCopy
{
int64_t offsetData = decode(patch, patch_offset);
source_rel_offset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1);
while (length--) {
target[target_offset++] = source[source_rel_offset++];
}
} break;
case 3: // TargetCopy
{
uint64_t offsetData = decode(patch, patch_offset);
target_rel_offset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1);
while (length--) {
target[target_offset++] = target[target_rel_offset++];
}
}
default:
throw std::runtime_error("Invalid patch command");
}
}
uint32_t source_checksum;
uint32_t target_checksum;
uint32_t patch_checksum;
memcpy(&source_checksum, patch.data() + patch.size() - 12, sizeof(uint32_t));
memcpy(&target_checksum, patch.data() + patch.size() - 8, sizeof(uint32_t));
memcpy(&patch_checksum, patch.data() + patch.size() - 4, sizeof(uint32_t));
if (source_checksum != crc32(source) || target_checksum != crc32(target) ||
patch_checksum !=
crc32(std::vector<uint8_t>(patch.begin(), patch.end() - 4))) {
throw std::runtime_error("Checksum mismatch");
}
}
absl::StatusOr<std::string> CheckVersion(const char *version) {
std::string version_string = version;
if (version_string != kYazeVersion) {
std::string message =
absl::StrFormat("Yaze version mismatch: expected %s, got %s",
kYazeVersion.data(), version_string.c_str());
return absl::InvalidArgumentError(message);
}
return version_string;
}
} // namespace core
} // namespace app
} // namespace yaze

View File

@@ -1,17 +1,16 @@
#ifndef YAZE_CORE_COMMON_H
#define YAZE_CORE_COMMON_H
#include "imgui/imgui.h"
#include <chrono>
#include <cstdint>
#include <fstream>
#include <functional>
#include <iostream>
#include <memory>
#include <stack>
#include <string>
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "absl/container/flat_hash_map.h"
namespace yaze {
namespace app {
@@ -21,16 +20,21 @@ namespace app {
*/
namespace core {
std::string UppercaseHexByte(uint8_t byte, bool leading = false);
std::string UppercaseHexWord(uint16_t word, bool leading = false);
std::string UppercaseHexLong(uint32_t dword);
std::string UppercaseHexLongLong(uint64_t qword);
bool StringReplace(std::string &str, const std::string &from,
const std::string &to);
/**
* @class ExperimentFlags
* @brief A class to manage experimental feature flags.
*/
class ExperimentFlags {
public:
public:
struct Flags {
// Bitmap manager abstraction to manage graphics bin of Rom.
bool kUseBitmapManager = true;
// Log instructions to the GUI debugger.
bool kLogInstructions = true;
@@ -56,22 +60,18 @@ class ExperimentFlags {
// Use the new platform specific file dialog wrappers.
bool kNewFileDialogWrapper = true;
// Platform specific loading of fonts from the system. Currently
// only supports macOS.
bool kLoadSystemFonts = true;
// Uses texture streaming from SDL for my dynamic updates.
bool kLoadTexturesAsStreaming = true;
// Save dungeon map edits to the Rom.
bool kSaveDungeonMaps = false;
// Save graphics sheet to the Rom.
bool kSaveGraphicsSheet = false;
// Log to the console.
bool kLogToConsole = false;
// Load audio device for emulator
bool kLoadAudioDevice = false;
// Overworld flags
struct Overworld {
// Load and render overworld sprites to the screen. Unstable.
@@ -91,6 +91,9 @@ class ExperimentFlags {
// Save overworld properties to the Rom.
bool kSaveOverworldProperties = true;
// Load custom overworld data from the ROM and enable UI.
bool kLoadCustomOverworld = false;
} overworld;
};
@@ -109,8 +112,44 @@ class ExperimentFlags {
}
return flags_.get();
}
std::string Serialize() const {
std::string result;
result +=
"kLogInstructions: " + std::to_string(flags_->kLogInstructions) + "\n";
result +=
"kUseNewImGuiInput: " + std::to_string(flags_->kUseNewImGuiInput) +
"\n";
result +=
"kSaveAllPalettes: " + std::to_string(flags_->kSaveAllPalettes) + "\n";
result +=
"kSaveGfxGroups: " + std::to_string(flags_->kSaveGfxGroups) + "\n";
result += "kSaveWithChangeQueue: " +
std::to_string(flags_->kSaveWithChangeQueue) + "\n";
result += "kDrawDungeonRoomGraphics: " +
std::to_string(flags_->kDrawDungeonRoomGraphics) + "\n";
result += "kNewFileDialogWrapper: " +
std::to_string(flags_->kNewFileDialogWrapper) + "\n";
result += "kLoadTexturesAsStreaming: " +
std::to_string(flags_->kLoadTexturesAsStreaming) + "\n";
result +=
"kSaveDungeonMaps: " + std::to_string(flags_->kSaveDungeonMaps) + "\n";
result += "kLogToConsole: " + std::to_string(flags_->kLogToConsole) + "\n";
result += "kDrawOverworldSprites: " +
std::to_string(flags_->overworld.kDrawOverworldSprites) + "\n";
result += "kSaveOverworldMaps: " +
std::to_string(flags_->overworld.kSaveOverworldMaps) + "\n";
result += "kSaveOverworldEntrances: " +
std::to_string(flags_->overworld.kSaveOverworldEntrances) + "\n";
result += "kSaveOverworldExits: " +
std::to_string(flags_->overworld.kSaveOverworldExits) + "\n";
result += "kSaveOverworldItems: " +
std::to_string(flags_->overworld.kSaveOverworldItems) + "\n";
result += "kSaveOverworldProperties: " +
std::to_string(flags_->overworld.kSaveOverworldProperties) + "\n";
return result;
}
private:
private:
static std::shared_ptr<Flags> flags_;
};
@@ -119,9 +158,8 @@ class ExperimentFlags {
* @brief A class to manage a value that can be modified and notify when it
* changes.
*/
template <typename T>
class NotifyValue {
public:
template <typename T> class NotifyValue {
public:
NotifyValue() : value_(), modified_(false), temp_value_() {}
NotifyValue(const T &value)
: value_(value), modified_(false), temp_value_() {}
@@ -154,69 +192,108 @@ class NotifyValue {
bool modified() const { return modified_; }
private:
private:
T value_;
bool modified_;
T temp_value_;
};
class ImGuiIdIssuer {
private:
static std::stack<ImGuiID> idStack;
static bool log_to_console = false;
static std::string log_file_out = "log.txt";
public:
// Generate and push a new ID onto the stack
static ImGuiID GetNewID() {
static int counter = 1; // Start from 1 to ensure uniqueness
ImGuiID child_id = ImGui::GetID((void *)(intptr_t)counter++);
idStack.push(child_id);
return child_id;
template <typename... Args>
static void logf(const absl::FormatSpec<Args...> &format, const Args &...args) {
std::string message = absl::StrFormat(format, args...);
if (log_to_console) {
std::cout << message << std::endl;
}
static std::ofstream fout(log_file_out, std::ios::out | std::ios::app);
fout << message << std::endl;
}
// Pop all IDs from the stack (can be called explicitly or upon program exit)
static void Cleanup() {
while (!idStack.empty()) {
idStack.pop();
}
}
struct StructuredLog {
std::string raw_message;
std::string category;
};
static absl::flat_hash_map<std::string, std::vector<std::string>> log_categories;
template <typename... Args>
static void logm(const std::string &category,
const absl::FormatSpec<Args...> &format, const Args &...args) {
std::string message = absl::StrFormat(format, args...);
if (log_to_console) {
std::cout << category << ": " << message << std::endl;
}
if (log_categories.contains(category)) {
log_categories[category].push_back(message);
} else {
log_categories[category] = {message};
}
}
class Logger {
public:
public:
static void log(std::string message) {
static std::ofstream fout("log.txt", std::ios::out | std::ios::app);
static std::ofstream fout(log_file_out, std::ios::out | std::ios::app);
fout << message << std::endl;
}
// log to console
static void logc(std::string message) { logs.emplace_back(message); }
static std::vector<std::string> logs;
};
std::string UppercaseHexByte(uint8_t byte, bool leading = false);
std::string UppercaseHexWord(uint16_t word);
std::string UppercaseHexLong(uint32_t dword);
constexpr uint32_t kFastRomRegion = 0x808000;
uint32_t SnesToPc(uint32_t addr);
uint32_t PcToSnes(uint32_t addr);
inline uint32_t SnesToPc(uint32_t addr) noexcept {
if (addr >= kFastRomRegion) {
addr -= kFastRomRegion;
}
uint32_t temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
return (temp + 0x0);
}
uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr);
inline uint32_t PcToSnes(uint32_t addr) {
uint8_t *b = reinterpret_cast<uint8_t *>(&addr);
b[2] = static_cast<uint8_t>(b[2] * 2);
int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3);
int HexToDec(char *input, int length);
if (b[1] >= 0x80) {
b[2] += 1;
} else {
b[1] += 0x80;
}
bool StringReplace(std::string &str, const std::string &from,
const std::string &to);
return addr;
}
inline int AddressFromBytes(uint8_t bank, uint8_t high, uint8_t low) noexcept {
return (bank << 16) | (high << 8) | low;
}
inline uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr) noexcept {
uint32_t result = 0;
result = (bank << 16) | addr;
return result;
}
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc = true);
/**
* @brief Store little endian 16-bit value using a byte pointer, offset by an
* index before dereferencing
*/
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
uint16_t const p_val);
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);
/**
* @brief Load little endian halfword (16-bit) dereferenced from an arrays of
* bytes. This version provides an index that will be multiplied by 2 and added
* to the base address.
*/
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
// Load little endian halfword (16-bit) dereferenced from
uint16_t ldle16b(uint8_t const *const p_arr);
struct FolderItem {
std::string name;
std::vector<FolderItem> subfolders;
@@ -225,10 +302,20 @@ struct FolderItem {
typedef struct FolderItem FolderItem;
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc = true);
void CreateBpsPatch(const std::vector<uint8_t> &source,
const std::vector<uint8_t> &target,
std::vector<uint8_t> &patch);
} // namespace core
} // namespace app
} // namespace yaze
void ApplyBpsPatch(const std::vector<uint8_t> &source,
const std::vector<uint8_t> &patch,
std::vector<uint8_t> &target);
constexpr std::string_view kYazeVersion = "0.2.1";
absl::StatusOr<std::string> CheckVersion(const char *version);
} // namespace core
} // namespace app
} // namespace yaze
#endif

View File

@@ -1,15 +1,6 @@
#ifndef YAZE_APP_CORE_CONSTANTS_H
#define YAZE_APP_CORE_CONSTANTS_H
#include <vector>
#include "absl/strings/string_view.h"
#define TAB_BAR(w) if (ImGui::BeginTabBar(w)) {
#define END_TAB_BAR() \
ImGui::EndTabBar(); \
}
#define TAB_ITEM(w) if (ImGui::BeginTabItem(w)) {
#define END_TAB_ITEM() \
ImGui::EndTabItem(); \
@@ -121,732 +112,5 @@
using ushort = unsigned short;
using uint = unsigned int;
using uchar = unsigned char;
using Bytes = std::vector<uint8_t>;
using OWBlockset = std::vector<std::vector<uint16_t>>;
struct OWMapTiles {
OWBlockset light_world; // 64 maps
OWBlockset dark_world; // 64 maps
OWBlockset special_world; // 32 maps
};
using OWMapTiles = struct OWMapTiles;
namespace yaze {
namespace app {
namespace core {
constexpr uint32_t kRedPen = 0xFF0000FF;
constexpr float kYazeVersion = 0.2;
// ============================================================================
// Magic numbers
// ============================================================================
/// Bit set for object priority
constexpr ushort TilePriorityBit = 0x2000;
/// Bit set for object hflip
constexpr ushort TileHFlipBit = 0x4000;
/// Bit set for object vflip
constexpr ushort TileVFlipBit = 0x8000;
/// Bits used for tile name
constexpr ushort TileNameMask = 0x03FF;
constexpr int Uncompressed3BPPSize = 0x0600;
constexpr int UncompressedSheetSize = 0x0800;
constexpr int NumberOfRooms = 296;
constexpr int NumberOfColors = 3143;
// ============================================================================
// Game Graphics
// ============================================================================
constexpr int tile_address = 0x1B52; // JP = Same
constexpr int tile_address_floor = 0x1B5A; // JP = Same
constexpr int subtype1_tiles = 0x8000; // JP = Same
constexpr int subtype2_tiles = 0x83F0; // JP = Same
constexpr int subtype3_tiles = 0x84F0; // JP = Same
constexpr int gfx_animated_pointer = 0x10275; // JP 0x10624 //long pointer
constexpr int hud_palettes = 0xDD660;
constexpr int maxGfx = 0xC3FB5;
constexpr int kTilesheetWidth = 128;
constexpr int kTilesheetHeight = 32;
constexpr int kTilesheetDepth = 8;
// TEXT EDITOR RELATED CONSTANTS
constexpr int gfx_font = 0x70000; // 2bpp format
constexpr int text_data = 0xE0000;
constexpr int text_data2 = 0x75F40;
constexpr int pointers_dictionaries = 0x74703;
constexpr int characters_width = 0x74ADF;
constexpr int entrance_gfx_group = 0x5D97;
// ============================================================================
// Gravestones related variables
// ============================================================================
constexpr int GravesYTilePos = 0x49968; // short (0x0F entries)
constexpr int GravesXTilePos = 0x49986; // short (0x0F entries)
constexpr int GravesTilemapPos = 0x499A4; // short (0x0F entries)
constexpr int GravesGFX = 0x499C2; // short (0x0F entries)
constexpr int GravesXPos = 0x4994A; // short (0x0F entries)
constexpr int GravesYLine = 0x4993A; // short (0x08 entries)
constexpr int GravesCountOnY = 0x499E0; // Byte 0x09 entries
constexpr int GraveLinkSpecialHole = 0x46DD9; // short
constexpr int GraveLinkSpecialStairs = 0x46DE0; // short
// ============================================================================
// Names
// ============================================================================
static const std::string RoomEffect[] = {"Nothing",
"Nothing",
"Moving Floor",
"Moving Water",
"Trinexx Shell",
"Red Flashes",
"Light Torch to See Floor",
"Ganon's Darkness"};
static const std::string RoomTag[] = {"Nothing",
"NW Kill Enemy to Open",
"NE Kill Enemy to Open",
"SW Kill Enemy to Open",
"SE Kill Enemy to Open",
"W Kill Enemy to Open",
"E Kill Enemy to Open",
"N Kill Enemy to Open",
"S Kill Enemy to Open",
"Clear Quadrant to Open",
"Clear Full Tile to Open",
"NW Push Block to Open",
"NE Push Block to Open",
"SW Push Block to Open",
"SE Push Block to Open",
"W Push Block to Open",
"E Push Block to Open",
"N Push Block to Open",
"S Push Block to Open",
"Push Block to Open",
"Pull Lever to Open",
"Collect Prize to Open",
"Hold Switch Open Door",
"Toggle Switch to Open Door",
"Turn off Water",
"Turn on Water",
"Water Gate",
"Water Twin",
"Moving Wall Right",
"Moving Wall Left",
"Crash",
"Crash",
"Push Switch Exploding Wall",
"Holes 0",
"Open Chest (Holes 0)",
"Holes 1",
"Holes 2",
"Defeat Boss for Dungeon Prize",
"SE Kill Enemy to Push Block",
"Trigger Switch Chest",
"Pull Lever Exploding Wall",
"NW Kill Enemy for Chest",
"NE Kill Enemy for Chest",
"SW Kill Enemy for Chest",
"SE Kill Enemy for Chest",
"W Kill Enemy for Chest",
"E Kill Enemy for Chest",
"N Kill Enemy for Chest",
"S Kill Enemy for Chest",
"Clear Quadrant for Chest",
"Clear Full Tile for Chest",
"Light Torches to Open",
"Holes 3",
"Holes 4",
"Holes 5",
"Holes 6",
"Agahnim Room",
"Holes 7",
"Holes 8",
"Open Chest for Holes 8",
"Push Block for Chest",
"Clear Room for Triforce Door",
"Light Torches for Chest",
"Kill Boss Again"};
static const std::string SecretItemNames[] = {
"Nothing", "Green Rupee", "Rock hoarder", "Bee", "Health pack",
"Bomb", "Heart ", "Blue Rupee",
"Key", "Arrow", "Bomb", "Heart", "Magic",
"Full Magic", "Cucco", "Green Soldier", "Bush Stal", "Blue Soldier",
"Landmine", "Heart", "Fairy", "Heart",
"Nothing ", // 22
"Hole", "Warp", "Staircase", "Bombable", "Switch"};
static const std::string TileTypeNames[] = {
"$00 Nothing (standard floor)",
"$01 Collision",
"$02 Collision",
"$03 Collision",
"$04 Collision",
"$05 Nothing (unused?)",
"$06 Nothing (unused?)",
"$07 Nothing (unused?)",
"$08 Deep water",
"$09 Shallow water",
"$0A Unknown? Possibly unused",
"$0B Collision (different in Overworld and unknown)",
"$0C Overlay mask",
"$0D Spike floor",
"$0E GT ice",
"$0F Ice palace ice",
"$10 Slope ◤",
"$11 Slope ◥",
"$12 Slope ◣",
"$13 Slope ◢",
"$14 Nothing (unused?)",
"$15 Nothing (unused?)",
"$16 Nothing (unused?)",
"$17 Nothing (unused?)",
"$18 Slope ◤",
"$19 Slope ◥",
"$1A Slope ◣",
"$1B Slope ◢",
"$1C Layer 2 overlay",
"$1D North single-layer auto stairs",
"$1E North layer-swap auto stairs",
"$1F North layer-swap auto stairs",
"$20 Pit",
"$21 Nothing (unused?)",
"$22 Manual stairs",
"$23 Pot switch",
"$24 Pressure switch",
"$25 Nothing (unused but referenced by somaria blocks)",
"$26 Collision (near stairs?)",
"$27 Brazier/Fence/Statue/Block/General hookable things",
"$28 North ledge",
"$29 South ledge",
"$2A East ledge",
"$2B West ledge",
"$2C ◤ ledge",
"$2D ◣ ledge",
"$2E ◥ ledge",
"$2F ◢ ledge",
"$30 Straight inter-room stairs south/up 0",
"$31 Straight inter-room stairs south/up 1",
"$32 Straight inter-room stairs south/up 2",
"$33 Straight inter-room stairs south/up 3",
"$34 Straight inter-room stairs north/down 0",
"$35 Straight inter-room stairs north/down 1",
"$36 Straight inter-room stairs north/down 2",
"$37 Straight inter-room stairs north/down 3",
"$38 Straight inter-room stairs north/down edge",
"$39 Straight inter-room stairs south/up edge",
"$3A Star tile (inactive on load)",
"$3B Star tile (active on load)",
"$3C Nothing (unused?)",
"$3D South single-layer auto stairs",
"$3E South layer-swap auto stairs",
"$3F South layer-swap auto stairs",
"$40 Thick grass",
"$41 Nothing (unused?)",
"$42 Gravestone / Tower of hera ledge shadows??",
"$43 Skull Woods entrance/Hera columns???",
"$44 Spike",
"$45 Nothing (unused?)",
"$46 Desert Tablet",
"$47 Nothing (unused?)",
"$48 Diggable ground",
"$49 Nothing (unused?)",
"$4A Diggable ground",
"$4B Warp tile",
"$4C Nothing (unused?) | Something unknown in overworld",
"$4D Nothing (unused?) | Something unknown in overworld",
"$4E Square corners in EP overworld",
"$4F Square corners in EP overworld",
"$50 Green bush",
"$51 Dark bush",
"$52 Gray rock",
"$53 Black rock",
"$54 Hint tile/Sign",
"$55 Big gray rock",
"$56 Big black rock",
"$57 Bonk rocks",
"$58 Chest 0",
"$59 Chest 1",
"$5A Chest 2",
"$5B Chest 3",
"$5C Chest 4",
"$5D Chest 5",
"$5E Spiral stairs",
"$5F Spiral stairs",
"$60 Rupee tile",
"$61 Nothing (unused?)",
"$62 Bombable floor",
"$63 Minigame chest",
"$64 Nothing (unused?)",
"$65 Nothing (unused?)",
"$66 Crystal peg down",
"$67 Crystal peg up",
"$68 Upwards conveyor",
"$69 Downwards conveyor",
"$6A Leftwards conveyor",
"$6B Rightwards conveyor",
"$6C North vines",
"$6D South vines",
"$6E West vines",
"$6F East vines",
"$70 Pot/Hammer peg/Push block 00",
"$71 Pot/Hammer peg/Push block 01",
"$72 Pot/Hammer peg/Push block 02",
"$73 Pot/Hammer peg/Push block 03",
"$74 Pot/Hammer peg/Push block 04",
"$75 Pot/Hammer peg/Push block 05",
"$76 Pot/Hammer peg/Push block 06",
"$77 Pot/Hammer peg/Push block 07",
"$78 Pot/Hammer peg/Push block 08",
"$79 Pot/Hammer peg/Push block 09",
"$7A Pot/Hammer peg/Push block 0A",
"$7B Pot/Hammer peg/Push block 0B",
"$7C Pot/Hammer peg/Push block 0C",
"$7D Pot/Hammer peg/Push block 0D",
"$7E Pot/Hammer peg/Push block 0E",
"$7F Pot/Hammer peg/Push block 0F",
"$80 North/South door",
"$81 East/West door",
"$82 North/South shutter door",
"$83 East/West shutter door",
"$84 North/South layer 2 door",
"$85 East/West layer 2 door",
"$86 North/South layer 2 shutter door",
"$87 East/West layer 2 shutter door",
"$88 Some type of door (?)",
"$89 East/West transport door",
"$8A Some type of door (?)",
"$8B Some type of door (?)",
"$8C Some type of door (?)",
"$8D Some type of door (?)",
"$8E Entrance door",
"$8F Entrance door",
"$90 Layer toggle shutter door (?)",
"$91 Layer toggle shutter door (?)",
"$92 Layer toggle shutter door (?)",
"$93 Layer toggle shutter door (?)",
"$94 Layer toggle shutter door (?)",
"$95 Layer toggle shutter door (?)",
"$96 Layer toggle shutter door (?)",
"$97 Layer toggle shutter door (?)",
"$98 Layer+Dungeon toggle shutter door (?)",
"$99 Layer+Dungeon toggle shutter door (?)",
"$9A Layer+Dungeon toggle shutter door (?)",
"$9B Layer+Dungeon toggle shutter door (?)",
"$9C Layer+Dungeon toggle shutter door (?)",
"$9D Layer+Dungeon toggle shutter door (?)",
"$9E Layer+Dungeon toggle shutter door (?)",
"$9F Layer+Dungeon toggle shutter door (?)",
"$A0 North/South Dungeon swap door",
"$A1 Dungeon toggle door (?)",
"$A2 Dungeon toggle door (?)",
"$A3 Dungeon toggle door (?)",
"$A4 Dungeon toggle door (?)",
"$A5 Dungeon toggle door (?)",
"$A6 Nothing (unused?)",
"$A7 Nothing (unused?)",
"$A8 Layer+Dungeon toggle shutter door (?)",
"$A9 Layer+Dungeon toggle shutter door (?)",
"$AA Layer+Dungeon toggle shutter door (?)",
"$AB Layer+Dungeon toggle shutter door (?)",
"$AC Layer+Dungeon toggle shutter door (?)",
"$AD Layer+Dungeon toggle shutter door (?)",
"$AE Layer+Dungeon toggle shutter door (?)",
"$AF Layer+Dungeon toggle shutter door (?)",
"$B0 Somaria ─",
"$B1 Somaria │",
"$B2 Somaria ┌",
"$B3 Somaria └",
"$B4 Somaria ┐",
"$B5 Somaria ┘",
"$B6 Somaria ⍰ 1 way",
"$B7 Somaria ┬",
"$B8 Somaria ┴",
"$B9 Somaria ├",
"$BA Somaria ┤",
"$BB Somaria ┼",
"$BC Somaria ⍰ 2 way",
"$BD Somaria ┼ crossover",
"$BE Pipe entrance",
"$BF Nothing (unused?)",
"$C0 Torch 00",
"$C1 Torch 01",
"$C2 Torch 02",
"$C3 Torch 03",
"$C4 Torch 04",
"$C5 Torch 05",
"$C6 Torch 06",
"$C7 Torch 07",
"$C8 Torch 08",
"$C9 Torch 09",
"$CA Torch 0A",
"$CB Torch 0B",
"$CC Torch 0C",
"$CD Torch 0D",
"$CE Torch 0E",
"$CF Torch 0F",
"$D0 Nothing (unused?)",
"$D1 Nothing (unused?)",
"$D2 Nothing (unused?)",
"$D3 Nothing (unused?)",
"$D4 Nothing (unused?)",
"$D5 Nothing (unused?)",
"$D6 Nothing (unused?)",
"$D7 Nothing (unused?)",
"$D8 Nothing (unused?)",
"$D9 Nothing (unused?)",
"$DA Nothing (unused?)",
"$DB Nothing (unused?)",
"$DC Nothing (unused?)",
"$DD Nothing (unused?)",
"$DE Nothing (unused?)",
"$DF Nothing (unused?)",
"$E0 Nothing (unused?)",
"$E1 Nothing (unused?)",
"$E2 Nothing (unused?)",
"$E3 Nothing (unused?)",
"$E4 Nothing (unused?)",
"$E5 Nothing (unused?)",
"$E6 Nothing (unused?)",
"$E7 Nothing (unused?)",
"$E8 Nothing (unused?)",
"$E9 Nothing (unused?)",
"$EA Nothing (unused?)",
"$EB Nothing (unused?)",
"$EC Nothing (unused?)",
"$ED Nothing (unused?)",
"$EE Nothing (unused?)",
"$EF Nothing (unused?)",
"$F0 Door 0 bottom",
"$F1 Door 1 bottom",
"$F2 Door 2 bottom",
"$F3 Door 3 bottom",
"$F4 Door X bottom? (unused?)",
"$F5 Door X bottom? (unused?)",
"$F6 Door X bottom? (unused?)",
"$F7 Door X bottom? (unused?)",
"$F8 Door 0 top",
"$F9 Door 1 top",
"$FA Door 2 top",
"$FB Door 3 top",
"$FC Door X top? (unused?)",
"$FD Door X top? (unused?)",
"$FE Door X top? (unused?)",
"$FF Door X top? (unused?)"};
static const std::string kSpriteDefaultNames[]{
"00 Raven",
"01 Vulture",
"02 Flying Stalfos Head",
"03 No Pointer (Empty",
"04 Pull Switch (good",
"05 Pull Switch (unused",
"06 Pull Switch (bad",
"07 Pull Switch (unused",
"08 Octorock (one way",
"09 Moldorm (Boss",
"0A Octorock (four way",
"0B Chicken",
"0C Octorock (?",
"0D Buzzblock",
"0E Snapdragon",
"0F Octoballoon",
"10 Octoballon Hatchlings",
"11 Hinox",
"12 Moblin",
"13 Mini Helmasaure",
"14 Gargoyle's Domain Gate",
"15 Antifairy",
"16 Sahasrahla / Aginah",
"17 Bush Hoarder",
"18 Mini Moldorm",
"19 Poe",
"1A Dwarves",
"1B Arrow in wall",
"1C Statue",
"1D Weathervane",
"1E Crystal Switch",
"1F Bug-Catching Kid",
"20 Sluggula",
"21 Push Switch",
"22 Ropa",
"23 Red Bari",
"24 Blue Bari",
"25 Talking Tree",
"26 Hardhat Beetle",
"27 Deadrock",
"28 Storytellers",
"29 Blind Hideout attendant",
"2A Sweeping Lady",
"2B Storytellers",
"2C Lumberjacks",
"2D Telepathic Stones",
"2E Multipurpose Sprite",
"2F Race Npc",
"30 Person?",
"31 Fortune Teller",
"32 Angry Brothers",
"33 Pull for items",
"34 Scared Girl",
"35 Innkeeper",
"36 Witch",
"37 Waterfall",
"38 Arrow Target",
"39 Average Middle",
"3A Half Magic Bat",
"3B Dash Item",
"3C Village Kid",
"3D Signs? Chicken lady also showed up / Scared ladies outside houses.",
"3E Rock Hoarder",
"3F Tutorial Soldier",
"40 Lightning Lock",
"41 Blue Sword Soldier / Used by guards to detect player",
"42 Green Sword Soldier",
"43 Red Spear Soldier",
"44 Assault Sword Soldier",
"45 Green Spear Soldier",
"46 Blue Archer",
"47 Green Archer",
"48 Red Javelin Soldier",
"49 Red Javelin Soldier 2",
"4A Red Bomb Soldiers",
"4B Green Soldier Recruits",
"4C Geldman",
"4D Rabbit",
"4E Popo",
"4F Popo 2",
"50 Cannon Balls",
"51 Armos",
"52 Giant Zora",
"53 Armos Knights (Boss",
"54 Lanmolas (Boss",
"55 Fireball Zora",
"56 Walking Zora",
"57 Desert Palace Barriers",
"58 Crab",
"59 Bird",
"5A Squirrel",
"5B Spark (Left to Right",
"5C Spark (Right to Left",
"5D Roller (vertical moving",
"5E Roller (vertical moving",
"5F Roller",
"60 Roller (horizontal moving",
"61 Beamos",
"62 Master Sword",
"63 Devalant (Non",
"64 Devalant (Shooter",
"65 Shooting Gallery Proprietor",
"66 Moving Cannon Ball Shooters (Right",
"67 Moving Cannon Ball Shooters (Left",
"68 Moving Cannon Ball Shooters (Down",
"69 Moving Cannon Ball Shooters (Up",
"6A Ball N' Chain Trooper",
"6B Cannon Soldier",
"6C Mirror Portal",
"6D Rat",
"6E Rope",
"6F Keese",
"70 Helmasaur King Fireball",
"71 Leever",
"72 Activator for the ponds (where you throw in items",
"73 Uncle / Priest",
"74 Running Man",
"75 Bottle Salesman",
"76 Princess Zelda",
"77 Antifairy (Alternate",
"78 Village Elder",
"79 Bee",
"7A Agahnim",
"7B Agahnim Energy Ball",
"7C Hyu",
"7D Big Spike Trap",
"7E Guruguru Bar (Clockwise",
"7F Guruguru Bar (Counter Clockwise",
"80 Winder",
"81 Water Tektite",
"82 Antifairy Circle",
"83 Green Eyegore",
"84 Red Eyegore",
"85 Yellow Stalfos",
"86 Kodongos",
"87 Flames",
"88 Mothula (Boss",
"89 Mothula's Beam",
"8A Spike Trap",
"8B Gibdo",
"8C Arrghus (Boss",
"8D Arrghus spawn",
"8E Terrorpin",
"8F Slime",
"90 Wallmaster",
"91 Stalfos Knight",
"92 Helmasaur King",
"93 Bumper",
"94 Swimmers",
"95 Eye Laser (Right",
"96 Eye Laser (Left",
"97 Eye Laser (Down",
"98 Eye Laser (Up",
"99 Pengator",
"9A Kyameron",
"9B Wizzrobe",
"9C Tadpoles",
"9D Tadpoles",
"9E Ostrich (Haunted Grove",
"9F Flute",
"A0 Birds (Haunted Grove",
"A1 Freezor",
"A2 Kholdstare (Boss",
"A3 Kholdstare's Shell",
"A4 Falling Ice",
"A5 Zazak Fireball",
"A6 Red Zazak",
"A7 Stalfos",
"A8 Bomber Flying Creatures from Darkworld",
"A9 Bomber Flying Creatures from Darkworld",
"AA Pikit",
"AB Maiden",
"AC Apple",
"AD Lost Old Man",
"AE Down Pipe",
"AF Up Pipe",
"B0 Right Pip",
"B1 Left Pipe",
"B2 Good bee again?",
"B3 Hylian Inscription",
"B4 Thief?s chest (not the one that follows you",
"B5 Bomb Salesman",
"B6 Kiki",
"B7 Maiden following you in Blind Dungeon",
"B8 Monologue Testing Sprite",
"B9 Feuding Friends on Death Mountain",
"BA Whirlpool",
"BB Salesman / chestgame guy / 300 rupee giver guy / Chest game thief",
"BC Drunk in the inn",
"BD Vitreous (Large Eyeball",
"BE Vitreous (Small Eyeball",
"BF Vitreous' Lightning",
"C0 Monster in Lake of Ill Omen / Quake Medallion",
"C1 Agahnim teleporting Zelda to dark world",
"C2 Boulders",
"C3 Gibo",
"C4 Thief",
"C5 Medusa",
"C6 Four Way Fireball Spitters (spit when you use your sword",
"C7 Hokku",
"C8 Big Fairy who heals you",
"C9 Tektite",
"CA Chain Chomp",
"CB Trinexx",
"CC Another part of trinexx",
"CD Yet another part of trinexx",
"CE Blind The Thief (Boss)",
"CF Swamola",
"D0 Lynel",
"D1 Bunny Beam",
"D2 Flopping fish",
"D3 Stal",
"D4 Landmine",
"D5 Digging Game Proprietor",
"D6 Ganon",
"D7 Copy of Ganon",
"D8 Heart",
"D9 Green Rupee",
"DA Blue Rupee",
"DB Red Rupee",
"DC Bomb Refill (1)",
"DD Bomb Refill (4)",
"DE Bomb Refill (8)",
"DF Small Magic Refill",
"E0 Full Magic Refill",
"E1 Arrow Refill (5)",
"E2 Arrow Refill (10)",
"E3 Fairy",
"E4 Key",
"E5 Big Key",
"E6 Shield",
"E7 Mushroom",
"E8 Fake Master Sword",
"E9 Magic Shop dude / His items",
"EA Heart Container",
"EB Heart Piece",
"EC Bushes",
"ED Cane Of Somaria Platform",
"EE Mantle",
"EF Cane of Somaria Platform (Unused)",
"F0 Cane of Somaria Platform (Unused)",
"F1 Cane of Somaria Platform (Unused)",
"F2 Medallion Tablet",
"F3",
"F4 Falling Rocks",
"F5",
"F6",
"F7",
"F8",
"F9",
"FA",
"FB",
"FC",
"FD",
"FE",
"FF",
};
static const std::string overlordnames[] = {
"Overlord_SpritePositionTarget",
"Overlord_AllDirectionMetalBallFactory",
"Overlord_CascadeMetalBallFactory",
"Overlord_StalfosFactory",
"Overlord_StalfosTrap",
"Overlord_SnakeTrap",
"Overlord_MovingFloor",
"Overlord_ZolFactory",
"Overlord_WallMasterFactory",
"Overlord_CrumbleTilePath 1",
"Overlord_CrumbleTilePath 2",
"Overlord_CrumbleTilePath 3",
"Overlord_CrumbleTilePath 4",
"Overlord_CrumbleTilePath 5",
"Overlord_CrumbleTilePath 6",
"Overlord_PirogusuFactory 1",
"Overlord_PirogusuFactory 2",
"Overlord_PirogusuFactory 3",
"Overlord_PirogusuFactory 4",
"Overlord_FlyingTileFactory",
"Overlord_WizzrobeFactory",
"Overlord_ZoroFactory",
"Overlord_StalfosTrapTriggerWindow",
"Overlord_RedStalfosTrap",
"Overlord_ArmosCoordinator",
"Overlord_BombTrap",
};
} // namespace core
} // namespace app
} // namespace yaze
#endif

View File

@@ -2,30 +2,18 @@
#include <SDL.h>
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#if defined(__APPLE__) && defined(__MACH__)
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1
#include "imgui/backends/imgui_impl_metal.h"
#elif TARGET_OS_IPHONE == 1
#include "imgui/backends/imgui_impl_metal.h"
#elif TARGET_OS_MAC == 1
#endif
#endif
#include <filesystem>
#include <memory>
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "app/core/platform/font_loader.h"
#include "app/editor/master_editor.h"
#include "app/gui/icons.h"
#include "app/editor/editor_manager.h"
#include "app/gui/style.h"
#include "core/utils/file_util.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
@@ -38,7 +26,6 @@ constexpr ImGuiWindowFlags kMainEditorFlags =
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
using ::ImVec2;
using ImGui::Begin;
using ImGui::End;
using ImGui::GetIO;
@@ -61,205 +48,66 @@ void NewMasterFrame() {
}
}
void InitializeKeymap() {
} // namespace
absl::Status Controller::OnEntry(std::string filename) {
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
platform_ = Platform::kiOS;
#elif TARGET_OS_MAC == 1
platform_ = Platform::kMacOS;
#endif
#elif defined(_WIN32)
platform_ = Platform::kWindows;
#elif defined(__linux__)
platform_ = Platform::kLinux;
#else
platform_ = Platform::kUnknown;
#endif
RETURN_IF_ERROR(CreateWindow())
RETURN_IF_ERROR(CreateRenderer())
RETURN_IF_ERROR(CreateGuiContext())
RETURN_IF_ERROR(LoadAudioDevice())
editor_manager_.SetupScreen(filename);
active_ = true;
return absl::OkStatus();
}
void Controller::OnInput() {
int wheel = 0;
SDL_Event event;
ImGuiIO &io = ImGui::GetIO();
io.KeyMap[ImGuiKey_LeftSuper] = SDL_GetScancodeFromKey(SDLK_LGUI);
io.KeyMap[ImGuiKey_Backspace] = SDL_GetScancodeFromKey(SDLK_BACKSPACE);
io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT);
io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN);
io.KeyMap[ImGuiKey_UpArrow] = SDL_GetScancodeFromKey(SDLK_UP);
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_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);
io.KeyMap[ImGuiKey_A] = SDL_GetScancodeFromKey(SDLK_a);
io.KeyMap[ImGuiKey_B] = SDL_GetScancodeFromKey(SDLK_b);
io.KeyMap[ImGuiKey_C] = SDL_GetScancodeFromKey(SDLK_c);
io.KeyMap[ImGuiKey_D] = SDL_GetScancodeFromKey(SDLK_d);
io.KeyMap[ImGuiKey_E] = SDL_GetScancodeFromKey(SDLK_e);
io.KeyMap[ImGuiKey_F] = SDL_GetScancodeFromKey(SDLK_f);
io.KeyMap[ImGuiKey_G] = SDL_GetScancodeFromKey(SDLK_g);
io.KeyMap[ImGuiKey_H] = SDL_GetScancodeFromKey(SDLK_h);
io.KeyMap[ImGuiKey_I] = SDL_GetScancodeFromKey(SDLK_i);
io.KeyMap[ImGuiKey_J] = SDL_GetScancodeFromKey(SDLK_j);
io.KeyMap[ImGuiKey_K] = SDL_GetScancodeFromKey(SDLK_k);
io.KeyMap[ImGuiKey_L] = SDL_GetScancodeFromKey(SDLK_l);
io.KeyMap[ImGuiKey_M] = SDL_GetScancodeFromKey(SDLK_m);
io.KeyMap[ImGuiKey_N] = SDL_GetScancodeFromKey(SDLK_n);
io.KeyMap[ImGuiKey_O] = SDL_GetScancodeFromKey(SDLK_o);
io.KeyMap[ImGuiKey_P] = SDL_GetScancodeFromKey(SDLK_p);
io.KeyMap[ImGuiKey_Q] = SDL_GetScancodeFromKey(SDLK_q);
io.KeyMap[ImGuiKey_R] = SDL_GetScancodeFromKey(SDLK_r);
io.KeyMap[ImGuiKey_S] = SDL_GetScancodeFromKey(SDLK_s);
io.KeyMap[ImGuiKey_T] = SDL_GetScancodeFromKey(SDLK_t);
io.KeyMap[ImGuiKey_U] = SDL_GetScancodeFromKey(SDLK_u);
io.KeyMap[ImGuiKey_V] = SDL_GetScancodeFromKey(SDLK_v);
io.KeyMap[ImGuiKey_W] = SDL_GetScancodeFromKey(SDLK_w);
io.KeyMap[ImGuiKey_X] = SDL_GetScancodeFromKey(SDLK_x);
io.KeyMap[ImGuiKey_Y] = SDL_GetScancodeFromKey(SDLK_y);
io.KeyMap[ImGuiKey_Z] = SDL_GetScancodeFromKey(SDLK_z);
io.KeyMap[ImGuiKey_F1] = SDL_GetScancodeFromKey(SDLK_F1);
io.KeyMap[ImGuiKey_F2] = SDL_GetScancodeFromKey(SDLK_F2);
io.KeyMap[ImGuiKey_F3] = SDL_GetScancodeFromKey(SDLK_F3);
io.KeyMap[ImGuiKey_F4] = SDL_GetScancodeFromKey(SDLK_F4);
io.KeyMap[ImGuiKey_F5] = SDL_GetScancodeFromKey(SDLK_F5);
io.KeyMap[ImGuiKey_F6] = SDL_GetScancodeFromKey(SDLK_F6);
io.KeyMap[ImGuiKey_F7] = SDL_GetScancodeFromKey(SDLK_F7);
io.KeyMap[ImGuiKey_F8] = SDL_GetScancodeFromKey(SDLK_F8);
io.KeyMap[ImGuiKey_F9] = SDL_GetScancodeFromKey(SDLK_F9);
io.KeyMap[ImGuiKey_F10] = SDL_GetScancodeFromKey(SDLK_F10);
io.KeyMap[ImGuiKey_F11] = SDL_GetScancodeFromKey(SDLK_F11);
io.KeyMap[ImGuiKey_F12] = SDL_GetScancodeFromKey(SDLK_F12);
}
void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) {
SDL_SetClipboardText(text);
}
const char *ImGui_ImplSDL2_GetClipboardText(void *user_data) {
return SDL_GetClipboardText();
}
void InitializeClipboard() {
ImGuiIO &io = ImGui::GetIO();
io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
io.ClipboardUserData = nullptr;
}
void HandleKeyDown(SDL_Event &event, editor::MasterEditor &editor) {
ImGuiIO &io = ImGui::GetIO();
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
switch (event.key.keysym.sym) {
case SDLK_BACKSPACE:
case SDLK_LSHIFT:
case SDLK_LCTRL:
case SDLK_TAB:
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
break;
case SDLK_z:
editor.emulator().snes().SetButtonState(1, 0, true);
break;
case SDLK_a:
editor.emulator().snes().SetButtonState(1, 1, true);
break;
case SDLK_RSHIFT:
editor.emulator().snes().SetButtonState(1, 2, true);
break;
case SDLK_RETURN:
editor.emulator().snes().SetButtonState(1, 3, true);
break;
case SDLK_UP:
editor.emulator().snes().SetButtonState(1, 4, true);
break;
case SDLK_DOWN:
editor.emulator().snes().SetButtonState(1, 5, true);
break;
case SDLK_LEFT:
editor.emulator().snes().SetButtonState(1, 6, true);
break;
case SDLK_RIGHT:
editor.emulator().snes().SetButtonState(1, 7, true);
break;
case SDLK_x:
editor.emulator().snes().SetButtonState(1, 8, true);
break;
case SDLK_s:
editor.emulator().snes().SetButtonState(1, 9, true);
break;
case SDLK_d:
editor.emulator().snes().SetButtonState(1, 10, true);
break;
case SDLK_c:
editor.emulator().snes().SetButtonState(1, 11, true);
break;
default:
break;
while (SDL_PollEvent(&event)) {
ImGui_ImplSDL2_ProcessEvent(&event);
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP: {
ImGuiIO &io = ImGui::GetIO();
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
break;
}
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_CLOSE:
active_ = false;
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
io.DisplaySize.x = static_cast<float>(event.window.data1);
io.DisplaySize.y = static_cast<float>(event.window.data2);
break;
default:
break;
}
break;
default:
break;
}
}
}
void HandleKeyUp(SDL_Event &event, editor::MasterEditor &editor) {
ImGuiIO &io = ImGui::GetIO();
int key = event.key.keysym.scancode;
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
io.KeysDown[key] = (event.type == SDL_KEYDOWN);
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
switch (event.key.keysym.sym) {
case SDLK_z:
editor.emulator().snes().SetButtonState(1, 0, false);
break;
case SDLK_a:
editor.emulator().snes().SetButtonState(1, 1, false);
break;
case SDLK_RSHIFT:
editor.emulator().snes().SetButtonState(1, 2, false);
break;
case SDLK_RETURN:
editor.emulator().snes().SetButtonState(1, 3, false);
break;
case SDLK_UP:
editor.emulator().snes().SetButtonState(1, 4, false);
break;
case SDLK_DOWN:
editor.emulator().snes().SetButtonState(1, 5, false);
break;
case SDLK_LEFT:
editor.emulator().snes().SetButtonState(1, 6, false);
break;
case SDLK_RIGHT:
editor.emulator().snes().SetButtonState(1, 7, false);
break;
case SDLK_x:
editor.emulator().snes().SetButtonState(1, 8, false);
break;
case SDLK_s:
editor.emulator().snes().SetButtonState(1, 9, false);
break;
case SDLK_d:
editor.emulator().snes().SetButtonState(1, 10, false);
break;
case SDLK_c:
editor.emulator().snes().SetButtonState(1, 11, false);
break;
default:
break;
}
}
void ChangeWindowSizeEvent(SDL_Event &event) {
ImGuiIO &io = ImGui::GetIO();
io.DisplaySize.x = static_cast<float>(event.window.data1);
io.DisplaySize.y = static_cast<float>(event.window.data2);
}
void HandleMouseMovement(int &wheel) {
ImGuiIO &io = ImGui::GetIO();
int mouseX;
int mouseY;
const int buttons = SDL_GetMouseState(&mouseX, &mouseY);
@@ -272,169 +120,80 @@ void HandleMouseMovement(int &wheel) {
io.MouseWheel = static_cast<float>(wheel);
}
} // namespace
absl::Status Controller::OnEntry(std::string filename) {
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_IPHONE_SIMULATOR == 1
/* iOS in Xcode simulator */
platform_ = Platform::kiOS;
#elif TARGET_OS_IPHONE == 1
/* iOS */
platform_ = Platform::kiOS;
#elif TARGET_OS_MAC == 1
/* macOS */
platform_ = Platform::kMacOS;
#endif
#elif defined(_WIN32)
platform_ = Platform::kWindows;
#elif defined(__linux__)
platform_ = Platform::kLinux;
#else
platform_ = Platform::kUnknown;
#endif
RETURN_IF_ERROR(CreateSDL_Window())
RETURN_IF_ERROR(CreateRenderer())
RETURN_IF_ERROR(CreateGuiContext())
if (flags()->kLoadAudioDevice) {
RETURN_IF_ERROR(LoadAudioDevice())
master_editor_.emulator().set_audio_buffer(audio_buffer_);
master_editor_.emulator().set_audio_device_id(audio_device_);
absl::Status Controller::OnLoad() {
if (editor_manager_.quit()) {
active_ = false;
}
InitializeKeymap();
master_editor_.SetupScreen(renderer_, filename);
active_ = true;
#if TARGET_OS_IPHONE != 1
if (platform_ != Platform::kiOS) {
NewMasterFrame();
}
#endif
RETURN_IF_ERROR(editor_manager_.Update());
#if TARGET_OS_IPHONE != 1
if (platform_ != Platform::kiOS) {
End();
}
#endif
return absl::OkStatus();
}
void Controller::OnInput() {
int wheel = 0;
SDL_Event event;
ImGuiIO &io = ImGui::GetIO();
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_KEYDOWN:
HandleKeyDown(event, master_editor_);
break;
case SDL_KEYUP:
HandleKeyUp(event, master_editor_);
break;
case SDL_TEXTINPUT:
io.AddInputCharactersUTF8(event.text.text);
break;
case SDL_MOUSEWHEEL:
wheel = event.wheel.y;
break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_CLOSE:
CloseWindow();
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
ChangeWindowSizeEvent(event);
break;
default:
break;
}
break;
default:
break;
}
}
HandleMouseMovement(wheel);
}
absl::Status Controller::OnLoad() {
if (master_editor_.quit()) {
active_ = false;
}
NewMasterFrame();
RETURN_IF_ERROR(master_editor_.Update());
absl::Status Controller::OnTestLoad() {
RETURN_IF_ERROR(test_editor_->Update());
return absl::OkStatus();
}
void Controller::DoRender() const {
ImGui::Render();
SDL_RenderClear(renderer_.get());
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer_.get());
SDL_RenderPresent(renderer_.get());
SDL_RenderClear(Renderer::GetInstance().renderer());
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(),
Renderer::GetInstance().renderer());
SDL_RenderPresent(Renderer::GetInstance().renderer());
}
void Controller::OnExit() {
ImGui::DestroyContext();
if (flags()->kLoadAudioDevice) {
SDL_PauseAudioDevice(audio_device_, 1);
SDL_CloseAudioDevice(audio_device_);
delete audio_buffer_;
}
switch (platform_) {
case Platform::kMacOS:
case Platform::kWindows:
case Platform::kLinux:
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
break;
case Platform::kiOS:
// Deferred
break;
default:
break;
}
SDL_PauseAudioDevice(audio_device_, 1);
SDL_CloseAudioDevice(audio_device_);
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_Quit();
}
absl::Status Controller::CreateSDL_Window() {
auto sdl_flags = SDL_INIT_VIDEO | SDL_INIT_TIMER;
absl::Status Controller::CreateWindow() {
auto sdl_flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
if (flags()->kUseNewImGuiInput) {
sdl_flags |= SDL_INIT_GAMECONTROLLER;
}
if (flags()->kLoadAudioDevice) {
sdl_flags |= SDL_INIT_AUDIO;
}
if (SDL_Init(sdl_flags) != 0) {
return absl::InternalError(
absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
} else {
SDL_DisplayMode displayMode;
SDL_GetCurrentDisplayMode(0, &displayMode);
int screenWidth = displayMode.w * 0.8;
int screenHeight = displayMode.h * 0.8;
window_ = std::unique_ptr<SDL_Window, sdl_deleter>(
SDL_CreateWindow("Yet Another Zelda3 Editor", // window title
SDL_WINDOWPOS_UNDEFINED, // initial x position
SDL_WINDOWPOS_UNDEFINED, // initial y position
screenWidth, // width, in pixels
screenHeight, // height, in pixels
SDL_WINDOW_RESIZABLE),
sdl_deleter());
if (window_ == nullptr) {
return absl::InternalError(
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
}
}
SDL_DisplayMode display_mode;
SDL_GetCurrentDisplayMode(0, &display_mode);
int screen_width = display_mode.w * 0.8;
int screen_height = display_mode.h * 0.8;
window_ = std::unique_ptr<SDL_Window, core::SDL_Deleter>(
SDL_CreateWindow("Yet Another Zelda3 Editor", // window title
SDL_WINDOWPOS_UNDEFINED, // initial x position
SDL_WINDOWPOS_UNDEFINED, // initial y position
screen_width, // width, in pixels
screen_height, // height, in pixels
SDL_WINDOW_RESIZABLE),
core::SDL_Deleter());
if (window_ == nullptr) {
return absl::InternalError(
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
}
return absl::OkStatus();
}
absl::Status Controller::CreateRenderer() {
renderer_ = std::unique_ptr<SDL_Renderer, sdl_deleter>(
SDL_CreateRenderer(window_.get(), -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC),
sdl_deleter());
if (renderer_ == nullptr) {
return absl::InternalError(
absl::StrFormat("SDL_CreateRenderer: %s\n", SDL_GetError()));
} else {
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00);
}
return absl::OkStatus();
return Renderer::GetInstance().CreateRenderer(window_.get());
}
absl::Status Controller::CreateGuiContext() {
@@ -443,20 +202,16 @@ absl::Status Controller::CreateGuiContext() {
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
if (flags()->kUseNewImGuiInput) {
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
}
// Initialize ImGui for SDL
ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), renderer_.get());
ImGui_ImplSDLRenderer2_Init(renderer_.get());
// Initialize ImGui based on the backend
ImGui_ImplSDL2_InitForSDLRenderer(window_.get(),
Renderer::GetInstance().renderer());
ImGui_ImplSDLRenderer2_Init(Renderer::GetInstance().renderer());
if (flags()->kLoadSystemFonts) {
LoadSystemFonts();
} else {
RETURN_IF_ERROR(LoadFontFamilies());
}
RETURN_IF_ERROR(LoadFontFamilies());
// Set the default style
gui::ColorsYaze();
@@ -469,94 +224,8 @@ absl::Status Controller::CreateGuiContext() {
}
absl::Status Controller::LoadFontFamilies() const {
ImGuiIO &io = ImGui::GetIO();
const char *font_path = "assets/font/";
static const char *KARLA_REGULAR = "Karla-Regular.ttf";
static const char *ROBOTO_MEDIUM = "Roboto-Medium.ttf";
static const char *COUSINE_REGULAR = "Cousine-Regular.ttf";
static const char *DROID_SANS = "DroidSans.ttf";
static const char *NOTO_SANS_JP = "NotoSansJP.ttf";
static const char *IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
static const float FONT_SIZE_DEFAULT = 14.0f;
static const float FONT_SIZE_DROID_SANS = 16.0f;
static const float ICON_FONT_SIZE = 18.0f;
// Icon configuration
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
// Japanese font configuration
ImFontConfig japanese_font_config;
japanese_font_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
// List of fonts to be loaded
std::vector<const char *> font_paths = {KARLA_REGULAR, ROBOTO_MEDIUM,
COUSINE_REGULAR, IBM_PLEX_JP};
// Load fonts with associated icon and Japanese merges
for (const auto &font_path : font_paths) {
float font_size =
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT;
std::string actual_font_path;
#ifdef __APPLE__
#if TARGET_OS_IOS == 1
const std::string kBundlePath = GetBundleResourcePath();
actual_font_path = kBundlePath + font_path;
#else
actual_font_path = std::filesystem::absolute(font_path).string();
#endif
#else
actual_font_path = font_path;
#endif
if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(), font_size)) {
return absl::InternalError(
absl::StrFormat("Failed to load font from %s", actual_font_path));
}
// Merge icon set
std::string actual_icon_font_path = "";
const char *icon_font_path = FONT_ICON_FILE_NAME_MD;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS == 1
const std::string kIconBundlePath = GetBundleResourcePath();
actual_icon_font_path = kIconBundlePath + "MaterialIcons-Regular.ttf";
#else
actual_icon_font_path = std::filesystem::absolute(icon_font_path).string();
#endif
#else
#endif
io.Fonts->AddFontFromFileTTF(actual_icon_font_path.data(), ICON_FONT_SIZE,
&icons_config, icons_ranges);
// Merge Japanese font
std::string actual_japanese_font_path = "";
const char *japanese_font_path = NOTO_SANS_JP;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS == 1
const std::string kJapaneseBundlePath = GetBundleResourcePath();
actual_japanese_font_path = kJapaneseBundlePath + japanese_font_path;
#else
actual_japanese_font_path =
std::filesystem::absolute(japanese_font_path).string();
#endif
#else
#endif
io.Fonts->AddFontFromFileTTF(actual_japanese_font_path.data(), 18.0f,
&japanese_font_config,
io.Fonts->GetGlyphRangesJapanese());
}
return absl::OkStatus();
// LoadSystemFonts();
return LoadPackageFonts();
}
absl::Status Controller::LoadAudioDevice() {
@@ -572,9 +241,39 @@ absl::Status Controller::LoadAudioDevice() {
return absl::InternalError(
absl::StrFormat("Failed to open audio: %s\n", SDL_GetError()));
}
audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4];
master_editor_.emulator().set_audio_buffer(audio_buffer_);
// audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4];
audio_buffer_ = std::make_shared<int16_t>(audio_frequency_ / 50 * 4);
SDL_PauseAudioDevice(audio_device_, 0);
editor_manager_.emulator().set_audio_buffer(audio_buffer_.get());
editor_manager_.emulator().set_audio_device_id(audio_device_);
return absl::OkStatus();
}
absl::Status Controller::LoadConfigFiles() {
// Create and load a dotfile for the application
// This will store the user's preferences and settings
std::string config_directory = GetConfigDirectory(platform_);
// Create the directory if it doesn't exist
if (!std::filesystem::exists(config_directory)) {
if (!std::filesystem::create_directory(config_directory)) {
return absl::InternalError(absl::StrFormat(
"Failed to create config directory %s", config_directory));
}
}
// Check if the config file exists
std::string config_file = config_directory + "yaze.cfg";
if (!std::filesystem::exists(config_file)) {
// Create the file if it doesn't exist
std::ofstream file(config_file);
if (!file.is_open()) {
return absl::InternalError(
absl::StrFormat("Failed to create config file %s", config_file));
}
file.close();
}
return absl::OkStatus();
}

View File

@@ -2,20 +2,18 @@
#define YAZE_APP_CORE_CONTROLLER_H
#include <SDL.h>
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imconfig.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#include <memory>
#include "absl/status/status.h"
#include "app/core/common.h"
#include "app/editor/master_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
#include "app/core/platform/renderer.h"
#include "app/core/utils/file_util.h"
#include "app/editor/editor_manager.h"
#include "app/editor/editor.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imconfig.h"
#include "imgui/imgui.h"
int main(int argc, char **argv);
@@ -23,8 +21,6 @@ namespace yaze {
namespace app {
namespace core {
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
/**
* @brief Main controller for the application.
*
@@ -37,45 +33,40 @@ class Controller : public ExperimentFlags {
absl::Status OnEntry(std::string filename = "");
void OnInput();
absl::Status OnLoad();
absl::Status OnTestLoad();
void DoRender() const;
void OnExit();
absl::Status CreateSDL_Window();
absl::Status CreateWindow();
absl::Status CreateRenderer();
absl::Status CreateGuiContext();
absl::Status LoadFontFamilies() const;
absl::Status LoadAudioDevice();
absl::Status LoadConfigFiles();
auto master_editor() -> editor::MasterEditor & { return master_editor_; }
void SetupScreen(std::string filename = "") {
editor_manager_.SetupScreen(filename);
}
auto editor_manager() -> editor::EditorManager & { return editor_manager_; }
auto renderer() -> SDL_Renderer * {
return Renderer::GetInstance().renderer();
}
auto window() -> SDL_Window * { return window_.get(); }
void init_test_editor(editor::Editor *editor) { test_editor_ = editor; }
void set_active(bool active) { active_ = active; }
private:
struct sdl_deleter {
void operator()(SDL_Window *p) const {
if (p) {
SDL_DestroyWindow(p);
}
}
void operator()(SDL_Renderer *p) const {
if (p) {
SDL_DestroyRenderer(p);
}
}
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
};
void CloseWindow() { active_ = false; }
friend int ::main(int argc, char **argv);
bool active_;
Platform platform_;
editor::MasterEditor master_editor_;
editor::Editor *test_editor_;
editor::EditorManager editor_manager_;
int audio_frequency_ = 48000;
int16_t *audio_buffer_;
SDL_AudioDeviceID audio_device_;
std::shared_ptr<int16_t> audio_buffer_;
std::shared_ptr<SDL_Window> window_;
std::shared_ptr<SDL_Renderer> renderer_;
};
} // namespace core

33
src/app/core/core.cmake Normal file
View File

@@ -0,0 +1,33 @@
set(
YAZE_APP_CORE_SRC
app/core/common.cc
app/core/controller.cc
app/emu/emulator.cc
app/core/project.cc
app/core/utils/file_util.cc
)
if (WIN32 OR MINGW OR UNIX AND NOT APPLE)
list(APPEND YAZE_APP_CORE_SRC
app/core/platform/font_loader.cc
app/core/platform/clipboard.cc
app/core/platform/file_dialog.cc
)
endif()
if(APPLE)
list(APPEND YAZE_APP_CORE_SRC
app/core/platform/file_dialog.mm
app/core/platform/app_delegate.mm
app/core/platform/font_loader.cc
app/core/platform/font_loader.mm
app/core/platform/clipboard.mm
app/core/platform/file_path.mm
)
find_library(COCOA_LIBRARY Cocoa)
if(NOT COCOA_LIBRARY)
message(FATAL_ERROR "Cocoa not found")
endif()
set(CMAKE_EXE_LINKER_FLAGS "-framework ServiceManagement -framework Foundation -framework Cocoa")
endif()

View File

@@ -1,53 +0,0 @@
#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"};
struct ResourceLabelManager {
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);
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

@@ -1,13 +1,57 @@
#ifndef YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
#define YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
#ifdef TARGET_OS_MAC
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */
#import <CoreText/CoreText.h>
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
/* iOS in Xcode simulator */
#import <PencilKit/PencilKit.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate,
UIDocumentPickerDelegate,
UITabBarControllerDelegate,
PKCanvasViewDelegate>
@property(strong, nonatomic) UIWindow *window;
@property UIDocumentPickerViewController *documentPicker;
@property(nonatomic, copy) void (^completionHandler)(NSString *selectedFile);
- (void)PresentDocumentPickerWithCompletionHandler:
(void (^)(NSString *selectedFile))completionHandler;
// TODO: Setup a tab bar controller for multiple yaze instances
@property(nonatomic) UITabBarController *tabBarController;
// TODO: Setup a font picker for the text editor and display settings
@property(nonatomic) UIFontPickerViewController *fontPicker;
// TODO: Setup the pencil kit for drawing
@property PKToolPicker *toolPicker;
@property PKCanvasView *canvasView;
// TODO: Setup the file manager for file operations
@property NSFileManager *fileManager;
@end
#elif TARGET_OS_MAC == 1
#ifdef __cplusplus
extern "C" {
#endif
void InitializeCocoa();
/**
* @brief Initialize the Cocoa application.
*/
void yaze_initialize_cocoa();
/**
* @brief Run the Cocoa application delegate.
*/
void yaze_run_cocoa_app_delegate(const char *filename);
#ifdef __cplusplus
} // extern "C"
@@ -15,4 +59,6 @@ void InitializeCocoa();
#endif // TARGET_OS_MAC
#endif // defined(__APPLE__) && defined(__MACH__)
#endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H

View File

@@ -2,7 +2,7 @@
#import "app/core/platform/app_delegate.h"
#import "app/core/controller.h"
#import "app/core/platform/file_dialog.h"
#import "app/editor/utils/editor.h"
#import "app/editor/editor.h"
#import "app/rom.h"
#if defined(__APPLE__) && defined(__MACH__)
@@ -11,12 +11,9 @@
#import <CoreText/CoreText.h>
#if TARGET_IPHONE_SIMULATOR == 1
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
/* iOS in Xcode simulator */
#elif TARGET_OS_IPHONE == 1
/* iOS */
#elif TARGET_OS_MAC == 1
/* macOS */
#import <Cocoa/Cocoa.h>
@@ -209,7 +206,8 @@
}
- (void)openFileAction:(id)sender {
if (!yaze::app::SharedRom::shared_rom_->LoadFromFile(FileDialogWrapper::ShowOpenFileDialog())
if (!yaze::app::SharedRom::shared_rom_
->LoadFromFile(yaze::app::core::FileDialogWrapper::ShowOpenFileDialog())
.ok()) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Error"];
@@ -227,7 +225,9 @@
NSLog(@"Open Recent File action triggered");
}
extern "C" void InitializeCocoa() {
@end
extern "C" void yaze_initialize_cococa() {
@autoreleasepool {
AppDelegate *delegate = [[AppDelegate alloc] init];
[NSApplication sharedApplication];
@@ -236,7 +236,21 @@ extern "C" void InitializeCocoa() {
}
}
@end
extern "C" void yaze_run_cocoa_app_delegate(const char *filename) {
yaze_initialize_cococa();
yaze::app::core::Controller controller;
RETURN_VOID_IF_ERROR(controller.OnEntry(filename));
while (controller.IsActive()) {
@autoreleasepool {
controller.OnInput();
if (auto status = controller.OnLoad(); !status.ok()) {
break;
}
controller.DoRender();
}
}
controller.OnExit();
}
#endif

View File

@@ -3,6 +3,14 @@
#include <cstdint>
#include <vector>
namespace yaze {
namespace app {
namespace core {
void CopyImageToClipboard(const std::vector<uint8_t>& data) {}
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width,
int& height) {}
int& height) {}
} // namespace core
} // namespace app
} // namespace yaze

View File

@@ -1,27 +1,18 @@
#ifndef YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
#define YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
#ifdef _WIN32
void CopyImageToClipboard(const std::vector<uint8_t>& data);
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
#elif defined(__APPLE__)
#include <cstdint>
#include <vector>
void CopyImageToClipboard(const std::vector<uint8_t>& data);
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
namespace yaze {
namespace app {
namespace core {
#elif defined(__linux__)
void CopyImageToClipboard(const std::vector<uint8_t> &data);
void GetImageFromClipboard(std::vector<uint8_t> &data, int &width, int &height);
#include <cstdint>
#include <vector>
void CopyImageToClipboard(const std::vector<uint8_t>& data);
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
#endif
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_PLATFORM_CLIPBOARD_H

View File

@@ -6,7 +6,7 @@
#ifdef TARGET_OS_MAC
#import <Cocoa/Cocoa.h>
void CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
void yaze::app::core::CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
NSData* data = [NSData dataWithBytes:pngData.data() length:pngData.size()];
NSImage* image = [[NSImage alloc] initWithData:data];
@@ -15,7 +15,7 @@ void CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
[pasteboard writeObjects:@[ image ]];
}
void GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& height) {
void yaze::app::core::GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& height) {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSArray* classArray = [NSArray arrayWithObject:[NSImage class]];
NSDictionary* options = [NSDictionary dictionary];

View File

@@ -0,0 +1,145 @@
#include "file_dialog.h"
#ifdef _WIN32
// Include Windows-specific headers
#include <shobjidl.h>
#include <windows.h>
#endif // _WIN32
namespace yaze {
namespace app {
namespace core {
#ifdef _WIN32
std::string FileDialogWrapper::ShowOpenFileDialog() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog *pfd = NULL;
HRESULT hr =
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileDialog,
reinterpret_cast<void **>(&pfd));
std::string file_path_windows;
if (SUCCEEDED(hr)) {
// Show the dialog
hr = pfd->Show(NULL);
if (SUCCEEDED(hr)) {
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr)) {
// Get the file path
PWSTR pszFilePath;
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
char str[128];
wcstombs(str, pszFilePath, 128);
file_path_windows = str;
psiResult->Release();
CoTaskMemFree(pszFilePath);
}
}
pfd->Release();
}
CoUninitialize();
return file_path_windows;
}
std::string FileDialogWrapper::ShowOpenFolderDialog() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog *pfd = NULL;
HRESULT hr =
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileDialog,
reinterpret_cast<void **>(&pfd));
std::string folder_path_windows;
if (SUCCEEDED(hr)) {
// Show the dialog
DWORD dwOptions;
hr = pfd->GetOptions(&dwOptions);
if (SUCCEEDED(hr)) {
hr = pfd->SetOptions(dwOptions | FOS_PICKFOLDERS);
if (SUCCEEDED(hr)) {
hr = pfd->Show(NULL);
if (SUCCEEDED(hr)) {
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr)) {
// Get the folder path
PWSTR pszFolderPath;
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFolderPath);
char str[128];
wcstombs(str, pszFolderPath, 128);
folder_path_windows = str;
psiResult->Release();
CoTaskMemFree(pszFolderPath);
}
}
}
}
pfd->Release();
}
CoUninitialize();
return folder_path_windows;
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string &folder_path) {
std::vector<std::string> subdirectories;
WIN32_FIND_DATA findFileData;
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (strcmp(findFileData.cFileName, ".") != 0 &&
strcmp(findFileData.cFileName, "..") != 0) {
subdirectories.push_back(findFileData.cFileName);
}
}
} while (FindNextFile(hFind, &findFileData) != 0);
FindClose(hFind);
}
return subdirectories;
}
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
const std::string &folder_path) {
std::vector<std::string> files;
WIN32_FIND_DATA findFileData;
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
files.push_back(findFileData.cFileName);
}
} while (FindNextFile(hFind, &findFileData) != 0);
FindClose(hFind);
}
return files;
}
#elif defined(__linux__)
std::string FileDialogWrapper::ShowOpenFileDialog() {
return "Linux: Open file dialog";
}
std::string FileDialogWrapper::ShowOpenFolderDialog() {
return "Linux: Open folder dialog";
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string& folder_path) {
return {"Linux: Subdirectories in folder"};
}
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
const std::string& folder_path) {
return {"Linux: Files in folder"};
}
#endif
} // namespace core
} // namespace app
} // namespace yaze

View File

@@ -1,61 +1,25 @@
#ifndef YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
#define YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
#include <string>
#ifdef _WIN32
// Include Windows-specific headers
#include <shobjidl.h>
#include <windows.h>
class FileDialogWrapper {
public:
static std::string ShowOpenFileDialog() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog *pfd = NULL;
HRESULT hr =
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileDialog, reinterpret_cast<void **>(&pfd));
std::string file_path_windows;
if (SUCCEEDED(hr)) {
// Show the dialog
hr = pfd->Show(NULL);
if (SUCCEEDED(hr)) {
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr)) {
// Get the file path
PWSTR pszFilePath;
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
char str[128];
wcstombs(str, pszFilePath, 128);
file_path_windows = str;
psiResult->Release();
CoTaskMemFree(pszFilePath);
}
}
pfd->Release();
}
CoUninitialize();
return file_path_windows;
}
};
#elif defined(__APPLE__)
#include "TargetConditionals.h"
#include <string>
#include <vector>
#ifdef TARGET_OS_MAC
// Other kinds of Mac OS
namespace yaze {
namespace app {
namespace core {
class FileDialogWrapper {
public:
/**
* @brief ShowOpenFileDialog opens a file dialog and returns the selected
* filepath.
*/
static std::string ShowOpenFileDialog();
/**
* @brief ShowOpenFolderDialog opens a file dialog and returns the selected
* folder path.
*/
static std::string ShowOpenFolderDialog();
static std::vector<std::string> GetSubdirectoriesInFolder(
const std::string& folder_path);
@@ -63,34 +27,8 @@ class FileDialogWrapper {
const std::string& folder_path);
};
#elif TARGET_OS_IPHONE
// iOS
class FileDialogWrapper {
public:
static std::string ShowOpenFileDialog();
static std::string ShowOpenFolderDialog();
static std::vector<std::string> GetSubdirectoriesInFolder(
const std::string& folder_path);
static std::vector<std::string> GetFilesInFolder(
const std::string& folder_path);
};
#endif
#elif defined(__linux__)
class FileDialogWrapper {
public:
static std::string ShowOpenFileDialog() {
// Linux-specific file dialog implementation using GTK
// ...
return "file_path_linux";
}
};
#else
#error "Unsupported platform."
#endif
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H

View File

@@ -1,8 +1,8 @@
#include "app/core/platform/file_dialog.h"
#include <string>
#include <vector>
#include "app/core/platform/file_dialog.h"
#include "imgui/imgui.h"
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */
@@ -10,32 +10,48 @@
#import <CoreText/CoreText.h>
#if TARGET_IPHONE_SIMULATOR == 1
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
/* iOS in Xcode simulator */
std::string FileDialogWrapper::ShowOpenFileDialog() { return ""; }
#import <UIKit/UIKit.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
std::string FileDialogWrapper::ShowOpenFolderDialog() { return ""; }
#include "app/core/platform/app_delegate.h"
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(const std::string& folder) {
namespace {
static std::string selectedFile;
void ShowOpenFileDialogImpl(void (^completionHandler)(std::string)) {
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[appDelegate PresentDocumentPickerWithCompletionHandler:^(NSString *filePath) {
selectedFile = std::string([filePath UTF8String]);
completionHandler(selectedFile);
}];
}
std::string ShowOpenFileDialogSync() {
__block std::string result;
ShowOpenFileDialogImpl(^(std::string filePath) {
result = filePath;
});
return result;
}
}
std::string yaze::app::core::FileDialogWrapper::ShowOpenFileDialog() {
return ShowOpenFileDialogSync();
}
std::string yaze::app::core::FileDialogWrapper::ShowOpenFolderDialog() { return ""; }
std::vector<std::string> yaze::app::core::FileDialogWrapper::GetFilesInFolder(
const std::string &folder) {
return {};
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std::string& folder) {
return {};
}
#elif TARGET_OS_IPHONE == 1
/* iOS */
std::string FileDialogWrapper::ShowOpenFileDialog() { return ""; }
std::string FileDialogWrapper::ShowOpenFolderDialog() { return ""; }
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(const std::string& folder) {
return {};
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std::string& folder) {
std::vector<std::string> yaze::app::core::FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string &folder) {
return {};
}
@@ -44,7 +60,7 @@ std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std:
#import <Cocoa/Cocoa.h>
std::string FileDialogWrapper::ShowOpenFileDialog() {
std::string yaze::app::core::FileDialogWrapper::ShowOpenFileDialog() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:YES];
[openPanel setCanChooseDirectories:NO];
@@ -59,7 +75,7 @@ std::string FileDialogWrapper::ShowOpenFileDialog() {
return "";
}
std::string FileDialogWrapper::ShowOpenFolderDialog() {
std::string yaze::app::core::FileDialogWrapper::ShowOpenFolderDialog() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:NO];
[openPanel setCanChooseDirectories:YES];
@@ -74,7 +90,8 @@ std::string FileDialogWrapper::ShowOpenFolderDialog() {
return "";
}
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(const std::string& folder) {
std::vector<std::string> yaze::app::core::FileDialogWrapper::GetFilesInFolder(
const std::string& folder) {
std::vector<std::string> filenames;
NSFileManager* fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator* enumerator =
@@ -89,7 +106,8 @@ std::vector<std::string> FileDialogWrapper::GetFilesInFolder(const std::string&
return filenames;
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std::string& folder) {
std::vector<std::string> yaze::app::core::FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string& folder) {
std::vector<std::string> subdirectories;
NSFileManager* fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator* enumerator =
@@ -113,4 +131,4 @@ std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std:
// Unsupported platform
#endif // TARGET_OS_MAC
#endif // __APPLE__ && __MACH__
#endif // __APPLE__ && __MACH__

View File

@@ -1,6 +1,20 @@
#ifndef YAZE_APP_CORE_PLATFORM_FILE_PATH_H
#define YAZE_APP_CORE_PLATFORM_FILE_PATH_H
#include <string>
namespace yaze {
namespace app {
namespace core {
/**
* @brief GetBundleResourcePath returns the path to the bundle resource
* directory. Specific to MacOS.
*/
std::string GetBundleResourcePath();
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_PLATFORM_FILE_PATH_H

View File

@@ -1,3 +1,5 @@
#include "file_path.h"
#include <iostream>
#include <string>
@@ -5,17 +7,15 @@
#include <Foundation/Foundation.h>
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1
std::string GetBundleResourcePath() {}
#elif TARGET_OS_IPHONE == 1
std::string GetBundleResourcePath() {
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
std::string yaze::app::core::GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
return [path UTF8String];
}
#elif TARGET_OS_MAC == 1
std::string GetBundleResourcePath() {
std::string yaze::app::core::GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];

View File

@@ -1,17 +1,124 @@
#include "app/core/platform/font_loader.h"
#include <string>
#include <unordered_set>
#include <vector>
#include <filesystem>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "app/core/platform/file_path.h"
#include "app/gui/icons.h"
#include "imgui/imgui.h"
#include <string>
#include <vector>
namespace yaze {
namespace app {
namespace core {
absl::Status LoadPackageFonts() {
ImGuiIO &io = ImGui::GetIO();
static const char *KARLA_REGULAR = "Karla-Regular.ttf";
static const char *ROBOTO_MEDIUM = "Roboto-Medium.ttf";
static const char *COUSINE_REGULAR = "Cousine-Regular.ttf";
static const char *DROID_SANS = "DroidSans.ttf";
static const char *NOTO_SANS_JP = "NotoSansJP.ttf";
static const char *IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
static const float FONT_SIZE_DEFAULT = 16.0f;
static const float FONT_SIZE_DROID_SANS = 18.0f;
static const float ICON_FONT_SIZE = 18.0f;
// Icon configuration
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
// Japanese font configuration
ImFontConfig japanese_font_config;
japanese_font_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
// List of fonts to be loaded
std::vector<const char *> font_paths = {
KARLA_REGULAR, ROBOTO_MEDIUM, COUSINE_REGULAR, IBM_PLEX_JP, DROID_SANS};
// Load fonts with associated icon and Japanese merges
for (const auto &font_path : font_paths) {
float font_size =
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT;
std::string actual_font_path;
#ifdef __APPLE__
#if TARGET_OS_IOS == 1
const std::string kBundlePath = GetBundleResourcePath();
actual_font_path = kBundlePath + font_path;
#else
actual_font_path = absl::StrCat(GetBundleResourcePath(),
"Contents/Resources/font/", font_path);
#endif
#else
actual_font_path = std::filesystem::absolute(font_path).string();
#endif
if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(), font_size)) {
return absl::InternalError(
absl::StrFormat("Failed to load font from %s", actual_font_path));
}
// Merge icon set
std::string actual_icon_font_path = "";
const char *icon_font_path = FONT_ICON_FILE_NAME_MD;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS == 1
const std::string kIconBundlePath = GetBundleResourcePath();
actual_icon_font_path = kIconBundlePath + "MaterialIcons-Regular.ttf";
#else
actual_icon_font_path =
absl::StrCat(GetBundleResourcePath(),
"Contents/Resources/font/MaterialIcons-Regular.ttf");
#endif
#else
actual_icon_font_path = std::filesystem::absolute(icon_font_path).string();
#endif
io.Fonts->AddFontFromFileTTF(actual_icon_font_path.data(), ICON_FONT_SIZE,
&icons_config, icons_ranges);
// Merge Japanese font
std::string actual_japanese_font_path = "";
const char *japanese_font_path = NOTO_SANS_JP;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS == 1
const std::string kJapaneseBundlePath = GetBundleResourcePath();
actual_japanese_font_path = kJapaneseBundlePath + japanese_font_path;
#else
actual_japanese_font_path =
absl::StrCat(GetBundleResourcePath(), "Contents/Resources/font/",
japanese_font_path);
#endif
#else
actual_japanese_font_path =
std::filesystem::absolute(japanese_font_path).string();
#endif
io.Fonts->AddFontFromFileTTF(actual_japanese_font_path.data(), 18.0f,
&japanese_font_config,
io.Fonts->GetGlyphRangesJapanese());
}
return absl::OkStatus();
}
#ifdef _WIN32
#include <Windows.h>
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
int CALLBACK EnumFontFamExProc(const LOGFONT *lpelfe, const TEXTMETRIC *lpntme,
DWORD FontType, LPARAM lParam) {
// Step 3: Load the font into ImGui
ImGuiIO& io = ImGui::GetIO();
ImGuiIO &io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF(lpelfe->lfFaceName, 16.0f);
return 1;
@@ -34,8 +141,8 @@ void LoadSystemFonts() {
RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount,
&maxValueNameSize, &maxValueDataSize, NULL, NULL);
char* valueName = new char[maxValueNameSize + 1]; // +1 for null terminator
BYTE* valueData = new BYTE[maxValueDataSize + 1]; // +1 for null terminator
char *valueName = new char[maxValueNameSize + 1]; // +1 for null terminator
BYTE *valueData = new BYTE[maxValueDataSize + 1]; // +1 for null terminator
// Enumerate all font entries
for (DWORD i = 0; i < valueCount; i++) {
@@ -52,7 +159,9 @@ void LoadSystemFonts() {
valueData, &valueDataSize) == ERROR_SUCCESS) {
if (valueType == REG_SZ) {
// Add the font file path to the vector
std::string fontPath((char*)valueData);
std::string fontPath(reinterpret_cast<char *>(valueData),
valueDataSize);
fontPaths.push_back(fontPath);
}
}
@@ -64,10 +173,57 @@ void LoadSystemFonts() {
RegCloseKey(hKey);
}
ImGuiIO& io = ImGui::GetIO();
ImGuiIO &io = ImGui::GetIO();
for (const auto& fontPath : fontPaths) {
io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 16.0f);
// List of common font face names
static const std::unordered_set<std::string> commonFontFaceNames = {
"arial",
"times",
"cour",
"verdana",
"tahoma",
"comic",
"Impact",
"ariblk",
"Trebuchet MS",
"Georgia",
"Palatino Linotype",
"Lucida Sans Unicode",
"Tahoma",
"Lucida Console"};
for (auto &fontPath : fontPaths) {
// Check if the font path has a "C:\" prefix
if (fontPath.substr(0, 2) != "C:") {
// Add "C:\Windows\Fonts\" prefix to the font path
fontPath = absl::StrFormat("C:\\Windows\\Fonts\\%s", fontPath.c_str());
}
// Check if the font file has a .ttf or .TTF extension
std::string extension = fontPath.substr(fontPath.find_last_of(".") + 1);
if (extension == "ttf" || extension == "TTF") {
// Get the font face name from the font path
std::string fontFaceName =
fontPath.substr(fontPath.find_last_of("\\/") + 1);
fontFaceName = fontFaceName.substr(0, fontFaceName.find_last_of("."));
// Check if the font face name is in the common font face names list
if (commonFontFaceNames.find(fontFaceName) != commonFontFaceNames.end()) {
io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 16.0f);
// Merge icon set
// Icon configuration
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
ImFontConfig icons_config;
static const float ICON_FONT_SIZE = 18.0f;
icons_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE,
&icons_config, icons_ranges);
}
}
}
}
@@ -78,4 +234,8 @@ void LoadSystemFonts() {
// ...
}
#endif
#endif
} // namespace core
} // namespace app
} // namespace yaze

View File

@@ -1,26 +1,17 @@
// FontLoader.h
#ifndef FONTLOADER_H
#define FONTLOADER_H
#ifndef YAZE_APP_CORE_PLATFORM_FONTLOADER_H
#define YAZE_APP_CORE_PLATFORM_FONTLOADER_H
#include "TargetConditionals.h"
#include "absl/status/status.h"
#ifdef _WIN32
#include <Windows.h>
// Windows specific function declaration for loading system fonts into ImGui
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
DWORD FontType, LPARAM lParam);
#elif __APPLE__
#ifdef TARGET_OS_MAC
namespace yaze {
namespace app {
namespace core {
void LoadSystemFonts();
absl::Status LoadPackageFonts();
#elif TARGET_OS_IPHONE
} // namespace core
} // namespace app
} // namespace yaze
void LoadSystemFonts();
#endif
#endif
#endif // FONTLOADER_H
#endif // YAZE_APP_CORE_PLATFORM_FONTLOADER_H

View File

@@ -1,32 +1,20 @@
// FontLoader.mm
#include "app/core/platform/font_loader.h"
#include "imgui/imgui.h"
#include "app/gui/icons.h"
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */
#import <CoreText/CoreText.h>
#include <TargetConditionals.h>
#import <CoreText/CoreText.h>
#include "app/gui/icons.h"
#include "imgui/imgui.h"
#if TARGET_IPHONE_SIMULATOR == 1
/* iOS in Xcode simulator */
void LoadSystemFonts() {}
#elif TARGET_OS_IPHONE == 1
#if TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1
/* iOS */
void LoadSystemFonts() {}
void yaze::app::core::LoadSystemFonts() {}
#elif TARGET_OS_MAC == 1
/* macOS */
#import <Cocoa/Cocoa.h>
// MacOS Implementation
void LoadSystemFonts() {
// List of common macOS system fonts
void yaze::app::core::LoadSystemFonts() {
NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ];
for (NSString *fontName in fontNames) {
@@ -67,8 +55,5 @@ void LoadSystemFonts() {
}
}
}
#else
// Unsupported platform
#endif
#endif

View File

@@ -0,0 +1,83 @@
#ifndef YAZE_APP_CORE_PLATFORM_RENDERER_H
#define YAZE_APP_CORE_PLATFORM_RENDERER_H
#include <SDL.h>
#include <memory>
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "app/core/utils/sdl_deleter.h"
#include "app/gfx/bitmap.h"
namespace yaze {
namespace app {
namespace core {
/**
* @class Renderer
* @brief The Renderer class represents the renderer for the Yaze application.
*
* This class is a singleton that provides functionality for creating and
* rendering bitmaps to the screen. It also includes methods for updating
* bitmaps on the screen.
*/
class Renderer {
public:
static Renderer &GetInstance() {
static Renderer instance;
return instance;
}
absl::Status CreateRenderer(SDL_Window *window) {
renderer_ = std::unique_ptr<SDL_Renderer, SDL_Deleter>(SDL_CreateRenderer(
window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED));
if (renderer_ == nullptr) {
return absl::InternalError(
absl::StrFormat("SDL_CreateRenderer: %s\n", SDL_GetError()));
}
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00);
return absl::OkStatus();
}
auto renderer() -> SDL_Renderer * { return renderer_.get(); }
/**
* @brief Used to render a bitmap to the screen.
*/
void RenderBitmap(gfx::Bitmap *bitmap) {
bitmap->CreateTexture(renderer_.get());
}
/**
* @brief Used to update a bitmap on the screen.
*/
void UpdateBitmap(gfx::Bitmap *bitmap) {
bitmap->UpdateTexture(renderer_.get());
}
absl::Status CreateAndRenderBitmap(int width, int height, int depth,
const std::vector<uint8_t> &data,
gfx::Bitmap &bitmap,
gfx::SnesPalette &palette) {
bitmap.Create(width, height, depth, data);
RETURN_IF_ERROR(bitmap.ApplyPalette(palette));
RenderBitmap(&bitmap);
return absl::OkStatus();
}
private:
Renderer() = default;
std::unique_ptr<SDL_Renderer, SDL_Deleter> renderer_;
Renderer(const Renderer &) = delete;
Renderer &operator=(const Renderer &) = delete;
};
} // namespace core
} // namespace app
} // namespace yaze
#endif

View File

@@ -0,0 +1,29 @@
#ifndef YAZE_APP_CORE_PLATFORM_VIEW_CONTROLLER_H
#define YAZE_APP_CORE_PLATFORM_VIEW_CONTROLLER_H
#ifdef __APPLE__
#include <TargetConditionals.h>
#if TARGET_OS_OSX
#include "imgui_impl_osx.h"
@interface AppViewController : NSViewController <NSWindowDelegate>
@end
#else
@interface AppViewController : UIViewController <MTKViewDelegate>
@property(nonatomic) yaze::app::core::Controller *controller;
@property(nonatomic) UIHoverGestureRecognizer *hoverGestureRecognizer;
@property(nonatomic) UIPinchGestureRecognizer *pinchRecognizer;
@property(nonatomic) UISwipeGestureRecognizer *swipeRecognizer;
@property(nonatomic) UILongPressGestureRecognizer *longPressRecognizer;
@end
#endif
@interface AppViewController () <MTKViewDelegate>
@property(nonatomic, readonly) MTKView *mtkView;
@property(nonatomic, strong) id<MTLDevice> device;
@property(nonatomic, strong) id<MTLCommandQueue> commandQueue;
@end
#endif // __APPLE__
#endif // YAZE_APP_CORE_PLATFORM_APP_VIEW_CONTROLLER_H

View File

@@ -1,22 +1,66 @@
#include "app/core/labeling.h"
#include "project.h"
#include <fstream>
#include <string>
#include "app/core/constants.h"
#include "app/gui/icons.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"
#include "app/gui/icons.h"
namespace yaze {
namespace app {
namespace core {
absl::Status Project::Open(const std::string& project_path) {
filepath = project_path;
name = project_path.substr(project_path.find_last_of("/") + 1);
std::ifstream in(project_path);
if (!in.good()) {
return absl::InternalError("Could not open project file.");
}
std::string line;
std::getline(in, name);
std::getline(in, filepath);
std::getline(in, rom_filename_);
std::getline(in, code_folder_);
std::getline(in, labels_filename_);
std::getline(in, keybindings_file);
while (std::getline(in, line)) {
if (line == kEndOfProjectFile) {
break;
}
}
in.close();
return absl::OkStatus();
}
absl::Status Project::Save() {
RETURN_IF_ERROR(CheckForEmptyFields());
std::ofstream out(filepath + "/" + name + ".yaze");
if (!out.good()) {
return absl::InternalError("Could not open project file.");
}
out << name << std::endl;
out << filepath << std::endl;
out << rom_filename_ << std::endl;
out << code_folder_ << std::endl;
out << labels_filename_ << std::endl;
out << keybindings_file << std::endl;
out << kEndOfProjectFile << std::endl;
out.close();
return absl::OkStatus();
}
bool ResourceLabelManager::LoadLabels(const std::string& filename) {
std::ifstream file(filename);
@@ -124,6 +168,11 @@ void ResourceLabelManager::SelectableLabelWithNameEdit(
}
}
std::string ResourceLabelManager::GetLabel(const std::string& type,
const std::string& key) {
return labels_[type][key];
}
std::string ResourceLabelManager::CreateOrGetLabel(
const std::string& type, const std::string& key,
const std::string& defaultValue) {
@@ -136,6 +185,5 @@ std::string ResourceLabelManager::CreateOrGetLabel(
return labels_[type][key];
}
} // namespace core
} // namespace app
} // namespace yaze

View File

@@ -1,26 +1,20 @@
#ifndef YAZE_APP_CORE_PROJECT_H
#define YAZE_APP_CORE_PROJECT_H
#include "absl/strings/match.h"
#include <algorithm>
#include <fstream>
#include <string>
#include <string_view>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "app/core/common.h"
#include "app/core/utils/file_util.h"
namespace yaze {
namespace app {
constexpr absl::string_view kProjectFileExtension = ".yaze";
constexpr absl::string_view kProjectFileFilter =
"Yaze Project Files (*.yaze)\0*.yaze\0";
constexpr absl::string_view kPreviousRomFilenameDelimiter =
"PreviousRomFilename";
constexpr absl::string_view kEndOfProjectFile = "EndOfProjectFile";
const std::string kRecentFilesFilename = "recent_files.txt";
constexpr char kEndOfProjectFile[] = "EndOfProjectFile";
/**
* @struct Project
@@ -31,81 +25,12 @@ constexpr absl::string_view kEndOfProjectFile = "EndOfProjectFile";
* user can have different rom file names for a single project and keep track of
* backups.
*/
struct Project : public core::ExperimentFlags {
/**
* @brief Creates a new project.
*
* @param project_name The name of the project.
* @param project_path The path to the project.
* @return An absl::Status indicating the success or failure of the project
* creation.
*/
absl::Status Create(const std::string &project_name) {
absl::Status Create(const std::string& project_name) {
name = project_name;
project_opened_ = true;
return absl::OkStatus();
}
absl::Status Open(const std::string &project_path) {
filepath = project_path;
name = project_path.substr(project_path.find_last_of("/") + 1);
std::ifstream in(project_path);
if (!in.good()) {
return absl::InternalError("Could not open project file.");
}
std::string line;
std::getline(in, name);
std::getline(in, filepath);
std::getline(in, rom_filename_);
std::getline(in, code_folder_);
std::getline(in, labels_filename_);
while (std::getline(in, line)) {
if (line == kEndOfProjectFile) {
break;
}
if (absl::StrContains(line, kPreviousRomFilenameDelimiter)) {
previous_rom_filenames_.push_back(
line.substr(line.find(kPreviousRomFilenameDelimiter) +
kPreviousRomFilenameDelimiter.size() + 1));
}
}
in.close();
return absl::OkStatus();
}
absl::Status Save() {
RETURN_IF_ERROR(CheckForEmptyFields());
std::ofstream out(filepath + "/" + name + ".yaze");
if (!out.good()) {
return absl::InternalError("Could not open project file.");
}
out << name << std::endl;
out << filepath << std::endl;
out << rom_filename_ << std::endl;
out << code_folder_ << std::endl;
out << labels_filename_ << std::endl;
for (const auto &filename : previous_rom_filenames_) {
out << kPreviousRomFilenameDelimiter << " " << filename << std::endl;
}
out << kEndOfProjectFile << std::endl;
out.close();
return absl::OkStatus();
}
absl::Status CheckForEmptyFields() {
if (name.empty() || filepath.empty() || rom_filename_.empty() ||
code_folder_.empty() || labels_filename_.empty()) {
@@ -116,20 +41,93 @@ struct Project : public core::ExperimentFlags {
return absl::OkStatus();
}
absl::Status SerializeExperimentFlags() {
auto flags = mutable_flags();
// TODO: Serialize flags
return absl::OkStatus();
}
absl::Status Open(const std::string &project_path);
absl::Status Save();
bool project_opened_ = false;
std::string name;
std::string flags = "";
std::string filepath;
std::string rom_filename_ = "";
std::string code_folder_ = "";
std::string labels_filename_ = "";
std::vector<std::string> previous_rom_filenames_;
std::string keybindings_file = "";
};
// Default types
static constexpr absl::string_view kDefaultTypes[] = {
"Dungeon Names", "Dungeon Room Names", "Overworld Map Names"};
struct ResourceLabelManager {
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 GetLabel(const std::string& type, const std::string& key);
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
const std::string& defaultValue);
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_;
};
class RecentFilesManager {
public:
RecentFilesManager() : RecentFilesManager(kRecentFilesFilename) {}
RecentFilesManager(const std::string& filename) : filename_(filename) {}
void AddFile(const std::string& file_path) {
// Add a file to the list, avoiding duplicates
auto it = std::find(recent_files_.begin(), recent_files_.end(), file_path);
if (it == recent_files_.end()) {
recent_files_.push_back(file_path);
}
}
void Save() {
std::ofstream file(filename_);
if (!file.is_open()) {
return; // Handle the error appropriately
}
for (const auto& file_path : recent_files_) {
file << file_path << std::endl;
}
}
void Load() {
std::ifstream file(filename_);
if (!file.is_open()) {
return;
}
recent_files_.clear();
std::string line;
while (std::getline(file, line)) {
if (!line.empty()) {
recent_files_.push_back(line);
}
}
}
const std::vector<std::string>& GetRecentFiles() const {
return recent_files_;
}
private:
std::string filename_;
std::vector<std::string> recent_files_;
};
} // namespace app

View File

@@ -1,19 +0,0 @@
#ifndef YAZE_APP_CORE_TESTABLE_H
#define YAZE_APP_CORE_TESTABLE_H
#include <imgui_test_engine/imgui_te_context.h>
namespace yaze {
namespace app {
namespace core {
class GuiTestable {
public:
virtual void RegisterTests(ImGuiTestEngine* e) = 0;
ImGuiTestEngine* test_engine;
};
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_TESTABLE_H

View File

@@ -0,0 +1,74 @@
#include "file_util.h"
#if defined(_WIN32)
#include <windows.h>
#else
#include <dirent.h>
#include <sys/stat.h>
#endif
#include <fstream>
#include <sstream>
namespace yaze {
namespace app {
namespace core {
std::string GetFileExtension(const std::string &filename) {
size_t dot = filename.find_last_of(".");
if (dot == std::string::npos) {
return "";
}
return filename.substr(dot + 1);
}
std::string GetFileName(const std::string &filename) {
size_t slash = filename.find_last_of("/");
if (slash == std::string::npos) {
return filename;
}
return filename.substr(slash + 1);
}
std::string LoadFile(const std::string &filename, Platform platform) {
std::string contents;
std::string filepath = GetConfigDirectory(platform) + "/" + filename;
std::ifstream file(filepath);
if (file.is_open()) {
std::stringstream buffer;
buffer << file.rdbuf();
contents = buffer.str();
file.close();
}
return contents;
}
void SaveFile(const std::string &filename, const std::string &contents,
Platform platform) {
std::string filepath = GetConfigDirectory(platform) + "/" + filename;
std::ofstream file(filepath);
if (file.is_open()) {
file << contents;
file.close();
}
}
std::string GetConfigDirectory(Platform platform) {
std::string config_directory = ".yaze";
switch (platform) {
case Platform::kWindows:
config_directory = "~/AppData/Roaming/yaze";
break;
case Platform::kMacOS:
case Platform::kLinux:
config_directory = "~/.config/yaze";
break;
default:
break;
}
return config_directory;
}
} // namespace core
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,24 @@
#ifndef YAZE_APP_CORE_UTILS_FILE_UTIL_H
#define YAZE_APP_CORE_UTILS_FILE_UTIL_H
#include <string>
namespace yaze {
namespace app {
namespace core {
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
std::string GetFileExtension(const std::string &filename);
std::string GetFileName(const std::string &filename);
std::string LoadFile(const std::string &filename, Platform platform);
std::string GetConfigDirectory(Platform platform);
void SaveFile(const std::string &filename, const std::string &data,
Platform platform);
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_UTILS_FILE_UTIL_H

View File

@@ -0,0 +1,40 @@
#ifndef YAZE_APP_CORE_UTILS_SDL_DELETER_H_
#define YAZE_APP_CORE_UTILS_SDL_DELETER_H_
#include <SDL.h>
namespace yaze {
namespace app {
namespace core {
/**
* @brief Deleter for SDL_Window and SDL_Renderer.
*/
struct SDL_Deleter {
void operator()(SDL_Window *p) const { SDL_DestroyWindow(p); }
void operator()(SDL_Renderer *p) const { SDL_DestroyRenderer(p); }
};
/**
* @brief Deleter for SDL_Texture.
*/
struct SDL_Texture_Deleter {
void operator()(SDL_Texture *p) const {
SDL_DestroyTexture(p);
}
};
/**
* @brief Deleter for SDL_Surface.
*/
struct SDL_Surface_Deleter {
void operator()(SDL_Surface *p) const {
SDL_FreeSurface(p);
}
};
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_UTILS_SDL_DELETER_H_

View File

@@ -1,16 +1,17 @@
#include "assembly_editor.h"
#include "ImGuiColorTextEdit/TextEditor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "absl/strings/str_cat.h"
#include "app/core/platform/file_dialog.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "core/constants.h"
namespace yaze {
namespace app {
namespace editor {
using core::FileDialogWrapper;
namespace {
std::vector<std::string> RemoveIgnoredFiles(
@@ -57,11 +58,11 @@ core::FolderItem LoadFolder(const std::string& folder) {
auto root_files = FileDialogWrapper::GetFilesInFolder(current_folder.name);
current_folder.files = RemoveIgnoredFiles(root_files, ignored_files);
for (const auto& folder :
for (const auto& subfolder :
FileDialogWrapper::GetSubdirectoriesInFolder(current_folder.name)) {
core::FolderItem folder_item;
folder_item.name = folder;
std::string full_folder = current_folder.name + "/" + folder;
folder_item.name = subfolder;
std::string full_folder = current_folder.name + "/" + subfolder;
auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder);
for (const auto& files : folder_files) {
// Remove subdirectory files

View File

@@ -1,15 +1,11 @@
#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#include "ImGuiColorTextEdit/TextEditor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include <fstream>
#include <istream>
#include <string>
#include "ImGuiColorTextEdit/TextEditor.h"
#include "app/core/common.h"
#include "app/editor/utils/editor.h"
#include "app/editor/editor.h"
#include "app/gui/style.h"
namespace yaze {

View File

@@ -1,27 +1,20 @@
#ifndef YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
#define YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h"
#include "app/core/common.h"
#include "app/core/constants.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/project.h"
#include "app/editor/code/assembly_editor.h"
#include "app/editor/code/memory_editor.h"
#include "app/editor/dungeon/dungeon_editor.h"
#include "app/editor/editor.h"
#include "app/editor/graphics/graphics_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/screen_editor.h"
#include "app/editor/music/music_editor.h"
#include "app/editor/overworld_editor.h"
#include "app/editor/overworld/overworld_editor.h"
#include "app/editor/sprite/sprite_editor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/utils/gfx_context.h"
#include "app/editor/utils/recent_files.h"
#include "app/emu/emulator.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
@@ -30,6 +23,9 @@
#include "app/gui/input.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
namespace yaze {
namespace app {
@@ -46,7 +42,7 @@ struct MemoryEditorWithDiffChecker : public SharedRom {
static Rom comparison_rom;
ImGui::Begin("Hex Editor", &show_memory_editor);
if (ImGui::Button("Compare Rom")) {
auto file_name = FileDialogWrapper::ShowOpenFileDialog();
auto file_name = core::FileDialogWrapper::ShowOpenFileDialog();
PRINT_IF_ERROR(comparison_rom.LoadFromFile(file_name));
show_compare_rom = true;
}

View File

@@ -1,9 +1,7 @@
#include "dungeon_editor.h"
#include "imgui/imgui.h"
#include "app/core/common.h"
#include "app/core/labeling.h"
#include "absl/container/flat_hash_map.h"
#include "app/core/platform/renderer.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
@@ -11,13 +9,15 @@
#include "app/gui/input.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/object_names.h"
#include "app/zelda3/dungeon/room_names.h"
#include "imgui/imgui.h"
#include "zelda3/dungeon/room.h"
namespace yaze {
namespace app {
namespace editor {
using core::Renderer;
using ImGui::BeginChild;
using ImGui::BeginTabBar;
using ImGui::BeginTabItem;
@@ -50,21 +50,22 @@ absl::Status DungeonEditor::Update() {
refresh_graphics_ = false;
}
TAB_BAR("##DungeonEditorTabBar")
TAB_ITEM("Room Editor")
status_ = UpdateDungeonRoomView();
END_TAB_ITEM()
TAB_ITEM("Usage Statistics")
if (is_loaded_) {
static bool calc_stats = false;
if (!calc_stats) {
CalculateUsageStats();
calc_stats = true;
if (ImGui::BeginTabBar("##DungeonEditorTabBar")) {
TAB_ITEM("Room Editor")
status_ = UpdateDungeonRoomView();
END_TAB_ITEM()
TAB_ITEM("Usage Statistics")
if (is_loaded_) {
static bool calc_stats = false;
if (!calc_stats) {
CalculateUsageStats();
calc_stats = true;
}
DrawUsageStats();
}
DrawUsageStats();
END_TAB_ITEM()
ImGui::EndTabBar();
}
END_TAB_ITEM()
END_TAB_BAR()
return absl::OkStatus();
}
@@ -72,7 +73,7 @@ absl::Status DungeonEditor::Update() {
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_.emplace_back(zelda3::dungeon::Room(/*room_id=*/i));
rooms_[i].LoadHeader();
rooms_[i].LoadRoomFromROM();
if (flags()->kDrawDungeonRoomGraphics) {
@@ -107,7 +108,7 @@ absl::Status DungeonEditor::Initialize() {
ASSIGN_OR_RETURN(current_palette_group_,
gfx::CreatePaletteGroupFromLargePalette(full_palette_));
graphics_bin_ = *rom()->mutable_bitmap_manager();
graphics_bin_ = rom()->gfx_sheets();
// 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]);
@@ -120,14 +121,14 @@ absl::Status DungeonEditor::RefreshGraphics() {
int block = rooms_[current_room_id_].blocks()[i];
RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
current_palette_group_[current_palette_id_], 0));
rom()->UpdateBitmap(&graphics_bin_[block], true);
Renderer::GetInstance().UpdateBitmap(&graphics_bin_[block]);
}
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];
RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
sprites_aux1_pal_group[current_palette_id_], 0));
rom()->UpdateBitmap(&graphics_bin_[block], true);
Renderer::GetInstance().UpdateBitmap(&graphics_bin_[block]);
}
return absl::OkStatus();
}
@@ -195,14 +196,15 @@ absl::Status DungeonEditor::UpdateDungeonRoomView() {
TableNextRow();
TableNextColumn();
TAB_BAR("##DungeonRoomTabBar");
TAB_ITEM("Rooms");
DrawRoomSelector();
END_TAB_ITEM();
TAB_ITEM("Entrances");
DrawEntranceSelector();
END_TAB_ITEM();
END_TAB_BAR();
if (ImGui::BeginTabBar("##DungeonRoomTabBar")) {
TAB_ITEM("Rooms");
DrawRoomSelector();
END_TAB_ITEM();
TAB_ITEM("Entrances");
DrawEntranceSelector();
END_TAB_ITEM();
ImGui::EndTabBar();
}
TableNextColumn();
DrawDungeonTabView();
@@ -320,7 +322,7 @@ void DungeonEditor::DrawRoomSelector() {
for (const auto each_room_name : zelda3::dungeon::kRoomNames) {
rom()->resource_label()->SelectableLabelWithNameEdit(
current_room_id_ == i, "Dungeon Room Names",
core::UppercaseHexByte(i), zelda3::dungeon::kRoomNames[i].data());
core::UppercaseHexByte(i), each_room_name.data());
if (ImGui::IsItemClicked()) {
// TODO: Jump to tab if room is already open
current_room_id_ = i;
@@ -498,7 +500,7 @@ void DungeonEditor::DrawRoomGraphics() {
top_left_y = room_gfx_canvas_.zero_point().y + height * current_block;
}
room_gfx_canvas_.draw_list()->AddImage(
(void*)graphics_bin_[block].texture(),
(ImTextureID)(intptr_t)graphics_bin_[block].texture(),
ImVec2(room_gfx_canvas_.zero_point().x + 2, top_left_y),
ImVec2(room_gfx_canvas_.zero_point().x + 0x100,
room_gfx_canvas_.zero_point().y + offset));
@@ -547,7 +549,7 @@ void DungeonEditor::DrawObjectRenderer() {
current_object_ = i;
object_renderer_.LoadObject(i,
rooms_[current_room_id_].mutable_blocks());
rom()->RenderBitmap(object_renderer_.bitmap());
Renderer::GetInstance().RenderBitmap(object_renderer_.bitmap());
object_loaded_ = true;
}
i += 1;
@@ -827,4 +829,4 @@ void DungeonEditor::DrawUsageGrid() {
} // namespace editor
} // namespace app
} // namespace yaze
} // namespace yaze

View File

@@ -1,16 +1,14 @@
#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
#include "imgui/imgui.h"
#include "app/core/common.h"
#include "app/core/labeling.h"
#include "absl/container/flat_hash_map.h"
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/editor.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/rom.h"
#include "imgui/imgui.h"
#include "zelda3/dungeon/room.h"
#include "zelda3/dungeon/room_entrance.h"
#include "zelda3/dungeon/room_object.h"
@@ -100,7 +98,6 @@ class DungeonEditor : public Editor,
bool object_loaded_ = false;
bool palette_showing_ = false;
bool refresh_graphics_ = false;
bool show_object_render_ = false;
uint16_t current_entrance_id_ = 0;
uint16_t current_room_id_ = 0;
@@ -120,12 +117,11 @@ class DungeonEditor : public Editor,
gui::Canvas object_canvas_;
gfx::Bitmap room_gfx_bmp_;
gfx::BitmapManager graphics_bin_;
std::array<gfx::Bitmap, kNumGfxSheets> graphics_bin_;
std::vector<gfx::Bitmap*> room_gfx_sheets_;
std::vector<zelda3::dungeon::Room> rooms_;
std::vector<zelda3::dungeon::RoomEntrance> entrances_;
std::vector<gfx::BitmapManager> room_graphics_;
zelda3::dungeon::DungeonObjectRenderer object_renderer_;
absl::flat_hash_map<uint16_t, int> spriteset_usage_;

52
src/app/editor/editor.cc Normal file
View File

@@ -0,0 +1,52 @@
#include "editor.h"
#include "app/core/constants.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace editor {
absl::Status DrawEditor(EditorLayoutParams *params) {
if (params->editor == nullptr) {
return absl::InternalError("Editor is not initialized");
}
// Draw the editors based on h_split and v_split in a recursive manner
if (params->v_split) {
ImGui::BeginTable("##VerticalSplitTable", 2);
ImGui::TableNextColumn();
if (params->left)
RETURN_IF_ERROR(DrawEditor(params->left));
ImGui::TableNextColumn();
if (params->right)
RETURN_IF_ERROR(DrawEditor(params->right));
ImGui::EndTable();
} else if (params->h_split) {
ImGui::BeginTable("##HorizontalSplitTable", 1);
ImGui::TableNextColumn();
if (params->top)
RETURN_IF_ERROR(DrawEditor(params->top));
ImGui::TableNextColumn();
if (params->bottom)
RETURN_IF_ERROR(DrawEditor(params->bottom));
ImGui::EndTable();
} else {
// No split, just draw the single editor
ImGui::Text("%s Editor",
kEditorNames[static_cast<int>(params->editor->type())]);
RETURN_IF_ERROR(params->editor->Update());
}
return absl::OkStatus();
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,21 +1,21 @@
set(
YAZE_APP_EDITOR_SRC
app/editor/editor.cc
app/editor/editor_manager.cc
app/editor/dungeon/dungeon_editor.cc
app/editor/master_editor.cc
app/editor/master_editor_test.cc
app/editor/settings_editor.cc
app/editor/overworld_editor.cc
app/editor/overworld/overworld_editor.cc
app/editor/sprite/sprite_editor.cc
app/editor/music/music_editor.cc
app/editor/message/message_editor.cc
app/editor/message/message_editor_test.cc
app/editor/message/message_data.cc
app/editor/code/assembly_editor.cc
app/editor/graphics/screen_editor.cc
app/editor/graphics/graphics_editor.cc
app/editor/graphics/palette_editor.cc
app/editor/graphics/tile16_editor.cc
app/editor/graphics/gfx_group_editor.cc
app/editor/utils/gfx_context.cc
app/editor/overworld/refresh.cc
app/editor/overworld/entity.cc
)
app/editor/system/settings_editor.cc
app/editor/system/command_manager.cc
app/editor/system/extension_manager.cc
)

View File

@@ -1,7 +1,14 @@
#ifndef YAZE_APP_CORE_EDITOR_H
#define YAZE_APP_CORE_EDITOR_H
#include <array>
#include "absl/status/status.h"
#include "app/editor/system/command_manager.h"
#include "app/editor/system/constant_manager.h"
#include "app/editor/system/extension_manager.h"
#include "app/editor/system/history_manager.h"
#include "app/editor/system/resource_manager.h"
namespace yaze {
namespace app {
@@ -12,6 +19,14 @@ namespace app {
*/
namespace editor {
struct EditorContext {
ConstantManager constant_manager;
CommandManager command_manager;
ExtensionManager extension_manager;
HistoryManager history_manager;
ResourceManager resource_manager;
};
enum class EditorType {
kAssembly,
kDungeon,
@@ -25,7 +40,7 @@ enum class EditorType {
kSettings,
};
constexpr std::array<const char*, 10> kEditorNames = {
constexpr std::array<const char *, 10> kEditorNames = {
"Assembly", "Dungeon", "Graphics", "Music", "Overworld",
"Palette", "Screen", "Sprite", "Message", "Settings",
};
@@ -56,10 +71,35 @@ class Editor {
protected:
EditorType type_;
EditorContext context_;
};
/**
* @brief Dynamic Editor Layout Parameters
*/
typedef struct EditorLayoutParams {
bool v_split;
bool h_split;
int v_split_pos;
int h_split_pos;
Editor *editor = nullptr;
EditorLayoutParams *left = nullptr;
EditorLayoutParams *right = nullptr;
EditorLayoutParams *top = nullptr;
EditorLayoutParams *bottom = nullptr;
EditorLayoutParams() {
v_split = false;
h_split = false;
v_split_pos = 0;
h_split_pos = 0;
}
} EditorLayoutParams;
absl::Status DrawEditor(EditorLayoutParams *params);
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_EDITOR_H
#endif // YAZE_APP_CORE_EDITOR_H

View File

@@ -1,48 +1,39 @@
#include "master_editor.h"
#include "ImGuiColorTextEdit/TextEditor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "abseil-cpp/absl/strings/match.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_internal.h"
#include "imgui_memory_editor.h"
#include "editor_manager.h"
#include "absl/status/status.h"
#include "app/core/common.h"
#include "absl/strings/match.h"
#include "app/core/constants.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/project.h"
#include "app/editor/code/assembly_editor.h"
#include "app/editor/dungeon/dungeon_editor.h"
#include "app/editor/graphics/graphics_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/screen_editor.h"
#include "app/editor/music/music_editor.h"
#include "app/editor/overworld_editor.h"
#include "app/editor/overworld/overworld_editor.h"
#include "app/editor/sprite/sprite_editor.h"
#include "app/editor/utils/flags.h"
#include "app/editor/utils/recent_files.h"
#include "app/editor/system/flags.h"
#include "app/emu/emulator.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "editor/editor.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
namespace yaze {
namespace app {
namespace editor {
using namespace ImGui;
using core::FileDialogWrapper;
namespace {
bool BeginCentered(const char *name) {
ImGuiIO const &io = GetIO();
bool BeginCentered(const char* name) {
ImGuiIO const& io = GetIO();
ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGuiWindowFlags flags =
@@ -58,21 +49,18 @@ bool IsEditorActive(Editor* editor, std::vector<Editor*>& active_editors) {
} // namespace
void MasterEditor::SetupScreen(std::shared_ptr<SDL_Renderer> renderer,
std::string filename) {
sdl_renderer_ = renderer;
rom()->SetupRenderer(renderer);
void EditorManager::SetupScreen(std::string filename) {
if (!filename.empty()) {
PRINT_IF_ERROR(rom()->LoadFromFile(filename));
}
overworld_editor_.InitializeZeml();
InitializeCommands();
}
absl::Status MasterEditor::Update() {
absl::Status EditorManager::Update() {
ManageKeyboardShortcuts();
DrawYazeMenu();
DrawFileDialog();
DrawStatusPopup();
DrawAboutPopup();
DrawInfoPopup();
@@ -85,12 +73,15 @@ absl::Status MasterEditor::Update() {
rom_assets_loaded_ = true;
}
ManageActiveEditors();
if (dynamic_layout_)
RETURN_IF_ERROR(DrawDynamicLayout())
else
ManageActiveEditors();
return absl::OkStatus();
}
void MasterEditor::ManageActiveEditors() {
void EditorManager::ManageActiveEditors() {
// Show popup pane to select an editor to add
static bool show_add_editor = false;
if (show_add_editor) OpenPopup("AddEditor");
@@ -253,16 +244,23 @@ void MasterEditor::ManageActiveEditors() {
}
}
void MasterEditor::ManageKeyboardShortcuts() {
absl::Status EditorManager::DrawDynamicLayout() {
// Dynamic layout for multiple editors to be open at once
// Allows for tiling and resizing of editors using ImGui
return DrawEditor(&root_layout_);
}
void EditorManager::ManageKeyboardShortcuts() {
bool ctrl_or_super = (GetIO().KeyCtrl || GetIO().KeySuper);
editor_context_.command_manager.ShowWhichKey();
// If CMD + R is pressed, reload the top result of recent files
if (IsKeyDown(ImGuiKey_R) && ctrl_or_super) {
static RecentFilesManager manager("recent_files.txt");
manager.Load();
if (!manager.GetRecentFiles().empty()) {
auto front = manager.GetRecentFiles().front();
std::cout << "Reloading: " << front << std::endl;
OpenRomOrProject(front);
}
}
@@ -311,55 +309,113 @@ void MasterEditor::ManageKeyboardShortcuts() {
}
}
void MasterEditor::DrawFileDialog() {
gui::FileDialogPipeline("ChooseFileDlgKey", ".sfc,.smc", std::nullopt, [&]() {
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
status_ = rom()->LoadFromFile(filePathName);
static RecentFilesManager manager("recent_files.txt");
void EditorManager::InitializeCommands() {
if (root_layout_.editor == nullptr) {
root_layout_.editor = &overworld_editor_;
}
// Load existing recent files
manager.Load();
// New editor popup for window management commands
static EditorLayoutParams new_layout;
if (ImGui::BeginPopup("NewEditor")) {
ImGui::Text("New Editor");
ImGui::Separator();
if (ImGui::Button("Overworld")) {
new_layout.editor = &overworld_editor_;
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Dungeon")) {
new_layout.editor = &dungeon_editor_;
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Graphics")) {
new_layout.editor = &graphics_editor_;
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Music")) {
new_layout.editor = &music_editor_;
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Palette")) {
new_layout.editor = &palette_editor_;
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Screen")) {
new_layout.editor = &screen_editor_;
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Sprite")) {
new_layout.editor = &sprite_editor_;
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Code")) {
new_layout.editor = &assembly_editor_;
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Settings")) {
new_layout.editor = &settings_editor_;
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Message")) {
new_layout.editor = &message_editor_;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
// Add a new file
manager.AddFile(filePathName);
// Save the updated list
manager.Save();
});
editor_context_.command_manager.RegisterPrefix("window", 'w',
"window management", "");
editor_context_.command_manager.RegisterSubcommand(
"window", "vsplit", '/', "vertical split",
"split windows vertically and place editor in new window", [this]() {
ImGui::OpenPopup("NewEditor");
root_layout_.v_split = true;
});
editor_context_.command_manager.RegisterSubcommand(
"window", "hsplit", '-', "horizontal split",
"split windows horizontally and place editor in new window", [this]() {
ImGui::OpenPopup("NewEditor");
root_layout_.h_split = true;
});
editor_context_.command_manager.RegisterSubcommand(
"window", "close", 'd', "close", "close the current editor", [this]() {
if (root_layout_.editor != nullptr) {
root_layout_.editor = nullptr;
}
});
}
void MasterEditor::DrawStatusPopup() {
void EditorManager::DrawStatusPopup() {
static absl::Status prev_status;
if (!status_.ok()) {
show_status_ = true;
prev_status_ = status_;
prev_status = status_;
}
if (show_status_ && (BeginCentered("StatusWindow"))) {
Text("%s", ICON_MD_ERROR);
Text("%s", prev_status_.ToString().c_str());
Text("%s", prev_status.ToString().c_str());
Spacing();
NextColumn();
Columns(1);
Separator();
NewLine();
SameLine(128);
if (Button("OK", gui::kDefaultModalSize) ||
IsKeyPressed(GetKeyIndex(ImGuiKey_Space))) {
if (Button("OK", gui::kDefaultModalSize) || IsKeyPressed(ImGuiKey_Space)) {
show_status_ = false;
status_ = absl::OkStatus();
}
SameLine();
if (Button(ICON_MD_CONTENT_COPY, ImVec2(50, 0))) {
SetClipboardText(prev_status_.ToString().c_str());
SetClipboardText(prev_status.ToString().c_str());
}
End();
}
}
void MasterEditor::DrawAboutPopup() {
void EditorManager::DrawAboutPopup() {
if (about_) OpenPopup("About");
if (BeginPopupModal("About", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
Text("Yet Another Zelda3 Editor - v%.2f", core::kYazeVersion);
Text("Yet Another Zelda3 Editor - v%s", core::kYazeVersion.data());
Text("Written by: scawful");
Spacing();
Text("Special Thanks: Zarby89, JaredBrian");
@@ -373,15 +429,15 @@ void MasterEditor::DrawAboutPopup() {
}
}
void MasterEditor::DrawInfoPopup() {
void EditorManager::DrawInfoPopup() {
if (rom_info_) OpenPopup("ROM Information");
if (BeginPopupModal("ROM Information", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
Text("Title: %s", rom()->title());
Text("ROM Size: %ld", rom()->size());
Text("Title: %s", rom()->title().c_str());
Text("ROM Size: %s", core::UppercaseHexLongLong(rom()->size()).c_str());
if (Button("Close", gui::kDefaultModalSize) ||
IsKeyPressed(GetKeyIndex(ImGuiKey_Space))) {
IsKeyPressed(ImGuiKey_Escape)) {
rom_info_ = false;
CloseCurrentPopup();
}
@@ -389,33 +445,19 @@ void MasterEditor::DrawInfoPopup() {
}
}
void MasterEditor::DrawYazeMenu() {
void EditorManager::DrawYazeMenu() {
static bool show_display_settings = false;
static bool show_command_line_interface = false;
if (BeginMenuBar()) {
DrawFileMenu();
DrawEditMenu();
DrawViewMenu();
DrawTestMenu();
DrawProjectMenu();
DrawHelpMenu();
DrawYazeMenuBar();
SameLine(GetWindowWidth() - GetStyle().ItemSpacing.x -
CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x - 150);
// Modify the style of the button to have no background color
CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x - 110);
PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
if (Button(ICON_MD_DISPLAY_SETTINGS)) {
show_display_settings = !show_display_settings;
}
if (Button(ICON_MD_TERMINAL)) {
show_command_line_interface = !show_command_line_interface;
}
PopStyleColor();
Text("%s", absl::StrCat("yaze v", core::kYazeVersion).c_str());
Text("yaze v%s", core::kYazeVersion.data());
EndMenuBar();
}
@@ -424,27 +466,9 @@ void MasterEditor::DrawYazeMenu() {
gui::DrawDisplaySettings();
End();
}
if (show_command_line_interface) {
Begin("Command Line Interface", &show_command_line_interface,
ImGuiWindowFlags_None);
Text("Enter a command:");
End();
}
}
void MasterEditor::OpenRomOrProject(const std::string& filename) {
if (absl::StrContains(filename, ".yaze")) {
status_ = current_project_.Open(filename);
if (status_.ok()) {
status_ = OpenProject();
}
} else {
status_ = rom()->LoadFromFile(filename);
}
}
void MasterEditor::DrawFileMenu() {
void EditorManager::DrawYazeMenuBar() {
static bool save_as_menu = false;
static bool new_project_menu = false;
@@ -476,7 +500,6 @@ void MasterEditor::DrawFileMenu() {
MENU_ITEM("Save As..") { save_as_menu = true; }
if (rom()->is_loaded()) {
MENU_ITEM("Reload") { status_ = rom()->Reload(); }
MENU_ITEM("Close") {
status_ = rom()->Close();
rom_assets_loaded_ = false;
@@ -492,8 +515,8 @@ void MasterEditor::DrawFileMenu() {
}
if (MenuItem("Open Project")) {
// Open an existing project
status_ =
current_project_.Open(FileDialogWrapper::ShowOpenFileDialog());
status_ = current_project_.Open(
core::FileDialogWrapper::ShowOpenFileDialog());
if (status_.ok()) {
status_ = OpenProject();
}
@@ -582,9 +605,7 @@ void MasterEditor::DrawFileMenu() {
}
End();
}
}
void MasterEditor::DrawEditMenu() {
if (BeginMenu("Edit")) {
MENU_ITEM2("Undo", "Ctrl+Z") { status_ = current_editor_->Undo(); }
MENU_ITEM2("Redo", "Ctrl+Y") { status_ = current_editor_->Redo(); }
@@ -593,12 +614,10 @@ void MasterEditor::DrawEditMenu() {
MENU_ITEM2("Copy", "Ctrl+C") { status_ = current_editor_->Copy(); }
MENU_ITEM2("Paste", "Ctrl+V") { status_ = current_editor_->Paste(); }
Separator();
MENU_ITEM2("Find", "Ctrl+F") {}
MENU_ITEM2("Find", "Ctrl+F") { status_ = current_editor_->Find(); }
EndMenu();
}
}
void MasterEditor::DrawViewMenu() {
static bool show_imgui_metrics = false;
static bool show_memory_editor = false;
static bool show_asm_editor = false;
@@ -606,28 +625,17 @@ void MasterEditor::DrawViewMenu() {
static bool show_palette_editor = false;
static bool show_emulator = false;
if (show_imgui_demo) ShowDemoWindow();
if (show_imgui_metrics) ShowMetricsWindow(&show_imgui_metrics);
if (show_memory_editor) memory_editor_.Update(show_memory_editor);
if (show_asm_editor) assembly_editor_.Update(show_asm_editor);
if (show_emulator) {
Begin("Emulator", &show_emulator, ImGuiWindowFlags_MenuBar);
emulator_.Run();
End();
}
if (show_imgui_metrics) {
ShowMetricsWindow(&show_imgui_metrics);
}
if (show_memory_editor) {
memory_editor_.Update(show_memory_editor);
}
if (show_imgui_demo) {
ShowDemoWindow();
}
if (show_asm_editor) {
assembly_editor_.Update(show_asm_editor);
}
if (show_palette_editor) {
Begin("Palette Editor", &show_palette_editor);
status_ = palette_editor_.Update();
@@ -635,6 +643,7 @@ void MasterEditor::DrawViewMenu() {
}
if (BeginMenu("View")) {
MenuItem("Dynamic Layout", nullptr, &dynamic_layout_);
MenuItem("Emulator", nullptr, &show_emulator);
Separator();
MenuItem("Memory Editor", nullptr, &show_memory_editor);
@@ -647,23 +656,9 @@ void MasterEditor::DrawViewMenu() {
MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics);
EndMenu();
}
}
void MasterEditor::DrawTestMenu() {
static bool show_tests_ = false;
if (BeginMenu("Tests")) {
MenuItem("Run Tests", nullptr, &show_tests_);
EndMenu();
}
}
void MasterEditor::DrawProjectMenu() {
static bool show_resource_label_manager = false;
if (current_project_.project_opened_) {
// Project Menu
if (BeginMenu("Project")) {
Text("Name: %s", current_project_.name.c_str());
Text("ROM: %s", current_project_.rom_filename_.c_str());
@@ -674,16 +669,7 @@ void MasterEditor::DrawProjectMenu() {
EndMenu();
}
}
if (show_resource_label_manager) {
rom()->resource_label()->DisplayLabels(&show_resource_label_manager);
if (current_project_.project_opened_ &&
!current_project_.labels_filename_.empty()) {
current_project_.labels_filename_ = rom()->resource_label()->filename_;
}
}
}
void MasterEditor::DrawHelpMenu() {
static bool open_rom_help = false;
static bool open_supported_features = false;
static bool open_manage_project = false;
@@ -752,11 +738,10 @@ void MasterEditor::DrawHelpMenu() {
Text("Project Menu");
Text("Create a new project or open an existing one.");
Text("Save the project to save the current state of the project.");
Text(
TextWrapped(
"To save a project, you need to first open a ROM and initialize your "
"code path and labels file. Label resource manager can be found in "
"the "
"View menu. Code path is set in the Code editor after opening a "
"the View menu. Code path is set in the Code editor after opening a "
"folder.");
if (Button("Close", gui::kDefaultModalSize)) {
@@ -765,58 +750,51 @@ void MasterEditor::DrawHelpMenu() {
}
EndPopup();
}
if (show_resource_label_manager) {
rom()->resource_label()->DisplayLabels(&show_resource_label_manager);
if (current_project_.project_opened_ &&
!current_project_.labels_filename_.empty()) {
current_project_.labels_filename_ = rom()->resource_label()->filename_;
}
}
}
void MasterEditor::LoadRom() {
if (flags()->kNewFileDialogWrapper) {
auto file_name = FileDialogWrapper::ShowOpenFileDialog();
PRINT_IF_ERROR(rom()->LoadFromFile(file_name));
void EditorManager::LoadRom() {
auto file_name = FileDialogWrapper::ShowOpenFileDialog();
auto load_rom = rom()->LoadFromFile(file_name);
if (load_rom.ok()) {
static RecentFilesManager manager("recent_files.txt");
manager.Load();
manager.AddFile(file_name);
manager.Save();
} else {
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
".sfc,.smc", ".");
}
}
void MasterEditor::SaveRom() {
void EditorManager::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_ = overworld_editor_.Save();
RETURN_VOID_IF_ERROR(status_);
status_ = rom()->SaveToFile(backup_rom_, save_new_auto_);
}
absl::Status MasterEditor::OpenProject() {
void EditorManager::OpenRomOrProject(const std::string& filename) {
if (absl::StrContains(filename, ".yaze")) {
status_ = current_project_.Open(filename);
if (status_.ok()) {
status_ = OpenProject();
}
} else {
status_ = rom()->LoadFromFile(filename);
}
}
absl::Status EditorManager::OpenProject() {
RETURN_IF_ERROR(rom()->LoadFromFile(current_project_.rom_filename_));
if (!rom()->resource_label()->LoadLabels(current_project_.labels_filename_)) {

View File

@@ -1,17 +1,9 @@
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
#define YAZE_APP_EDITOR_MASTER_EDITOR_H
#ifndef YAZE_APP_EDITOR_EDITOR_MANAGER_H
#define YAZE_APP_EDITOR_EDITOR_MANAGER_H
#define IMGUI_DEFINE_MATH_OPERATORS
#include "ImGuiColorTextEdit/TextEditor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h"
#include "app/core/common.h"
#include "app/core/constants.h"
#include "app/core/project.h"
#include "app/editor/code/assembly_editor.h"
#include "app/editor/code/memory_editor.h"
@@ -21,15 +13,10 @@
#include "app/editor/graphics/screen_editor.h"
#include "app/editor/message/message_editor.h"
#include "app/editor/music/music_editor.h"
#include "app/editor/overworld_editor.h"
#include "app/editor/settings_editor.h"
#include "app/editor/overworld/overworld_editor.h"
#include "app/editor/sprite/sprite_editor.h"
#include "app/editor/utils/gfx_context.h"
#include "app/editor/system/settings_editor.h"
#include "app/emu/emulator.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/rom.h"
@@ -38,28 +25,19 @@ namespace app {
namespace editor {
/**
* @class MasterEditor
* @brief The MasterEditor class represents the main editor for a Rom in the
* Yaze application.
* @class EditorManager
* @brief The EditorManager controls the main editor window and manages the
* various editor classes.
*
* 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
* The EditorManager 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 {
class EditorManager : public SharedRom, public core::ExperimentFlags {
public:
MasterEditor() {
EditorManager() {
current_editor_ = &overworld_editor_;
active_editors_.push_back(&overworld_editor_);
active_editors_.push_back(&dungeon_editor_);
@@ -67,37 +45,33 @@ class MasterEditor : public SharedRom,
active_editors_.push_back(&palette_editor_);
active_editors_.push_back(&sprite_editor_);
active_editors_.push_back(&message_editor_);
active_editors_.push_back(&screen_editor_);
}
void SetupScreen(std::shared_ptr<SDL_Renderer> renderer,
std::string filename = "");
void SetupScreen(std::string filename = "");
absl::Status Update();
auto emulator() -> emu::Emulator& { return emulator_; }
auto emulator() -> emu::Emulator & { return emulator_; }
auto quit() { return quit_; }
auto overworld_editor() -> OverworldEditor& { return overworld_editor_; }
private:
void ManageActiveEditors();
void ManageKeyboardShortcuts();
void OpenRomOrProject(const std::string& filename);
absl::Status DrawDynamicLayout();
void ManageKeyboardShortcuts();
void InitializeCommands();
void DrawFileDialog();
void DrawStatusPopup();
void DrawAboutPopup();
void DrawInfoPopup();
void DrawYazeMenu();
void DrawFileMenu();
void DrawEditMenu();
void DrawViewMenu();
void DrawTestMenu();
void DrawProjectMenu();
void DrawHelpMenu();
void DrawYazeMenuBar();
void LoadRom();
void SaveRom();
void OpenRomOrProject(const std::string &filename);
absl::Status OpenProject();
bool quit_ = false;
@@ -107,16 +81,21 @@ class MasterEditor : public SharedRom,
bool save_new_auto_ = true;
bool show_status_ = false;
bool rom_assets_loaded_ = false;
bool dynamic_layout_ = false;
absl::Status status_;
absl::Status prev_status_;
std::shared_ptr<SDL_Renderer> sdl_renderer_;
emu::Emulator emulator_;
Project current_project_;
std::vector<Editor *> active_editors_;
std::vector<EditorLayoutParams> active_layouts_;
EditorLayoutParams root_layout_;
Project current_project_;
EditorContext editor_context_;
Editor *current_editor_ = nullptr;
AssemblyEditor assembly_editor_;
DungeonEditor dungeon_editor_;
GraphicsEditor graphics_editor_;
@@ -128,14 +107,10 @@ class MasterEditor : public SharedRom,
SettingsEditor settings_editor_;
MessageEditor message_editor_;
MemoryEditorWithDiffChecker memory_editor_;
ImVector<int> active_tabs_;
std::vector<Editor*> active_editors_;
Editor* current_editor_ = nullptr;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H
#endif // YAZE_APP_EDITOR_EDITOR_MANAGER_H

View File

@@ -1,22 +1,14 @@
#include "gfx_group_editor.h"
#include "imgui/imgui.h"
#include <cmath>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "absl/strings/str_cat.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
@@ -27,7 +19,6 @@ using ImGui::BeginGroup;
using ImGui::BeginTabBar;
using ImGui::BeginTabItem;
using ImGui::BeginTable;
using ImGui::ColorButton;
using ImGui::EndChild;
using ImGui::EndGroup;
using ImGui::EndTabBar;
@@ -39,7 +30,6 @@ using ImGui::IsItemClicked;
using ImGui::PopID;
using ImGui::PushID;
using ImGui::SameLine;
using ImGui::Selectable;
using ImGui::Separator;
using ImGui::SetNextItemWidth;
using ImGui::TableHeadersRow;
@@ -123,7 +113,7 @@ void GfxGroupEditor::DrawBlocksetViewer(bool sheet_only) {
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];
auto sheet = rom()->gfx_sheets().at(sheet_id);
gui::BitmapCanvasPipeline(blockset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 22);
}
@@ -176,7 +166,7 @@ void GfxGroupEditor::DrawRoomsetViewer() {
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];
auto sheet = rom()->gfx_sheets().at(sheet_id);
gui::BitmapCanvasPipeline(roomset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 23);
}
@@ -214,7 +204,7 @@ void GfxGroupEditor::DrawSpritesetViewer(bool sheet_only) {
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];
auto sheet = rom()->gfx_sheets().at(115 + sheet_id);
gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 24);
}
@@ -229,7 +219,7 @@ void DrawPaletteFromPaletteGroup(gfx::SnesPalette &palette) {
if (palette.empty()) {
return;
}
for (int n = 0; n < palette.size(); n++) {
for (size_t n = 0; n < palette.size(); n++) {
PushID(n);
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);

View File

@@ -1,21 +1,10 @@
#ifndef YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
#define YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
#include "imgui/imgui.h"
#include <cmath>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace app {
@@ -40,13 +29,7 @@ class GfxGroupEditor : public SharedRom {
selected_spriteset_ = spriteset;
}
void InitBlockset(gfx::Bitmap* tile16_blockset) {
tile16_blockset_bmp_ = tile16_blockset;
}
private:
int preview_palette_id_ = 0;
int last_sheet_id_ = 0;
uint8_t selected_blockset_ = 0;
uint8_t selected_roomset_ = 0;
uint8_t selected_spriteset_ = 0;
@@ -57,17 +40,9 @@ class GfxGroupEditor : public SharedRom {
gui::Canvas spriteset_canvas_;
gfx::SnesPalette palette_;
gfx::PaletteGroup palette_group_;
gfx::Bitmap* tile16_blockset_bmp_;
std::vector<Bytes> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_;
gui::BitmapViewer gfx_group_viewer_;
zelda3::overworld::Overworld overworld_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
#endif // YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H

View File

@@ -1,30 +1,33 @@
#include "graphics_editor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/core/platform/clipboard.h"
#include "app/core/platform/renderer.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/compression.h"
#include "app/gfx/scad_format.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/asset_browser.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/modules/asset_browser.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
namespace yaze {
namespace app {
namespace editor {
using core::Renderer;
using gfx::kPaletteGroupAddressesKeys;
using ImGui::Button;
using ImGui::InputInt;
@@ -32,70 +35,56 @@ using ImGui::InputText;
using ImGui::SameLine;
using ImGui::TableNextColumn;
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
"#memoryEditor",
"##separator_gfx1",
};
constexpr ImGuiTableFlags kGfxEditTableFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
ImGuiTableFlags_SizingFixedFit;
constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
ImGuiTabBarFlags_FittingPolicyResizeDown |
ImGuiTabBarFlags_TabListPopupButton;
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame;
absl::Status GraphicsEditor::Update() {
TAB_BAR("##TabBar")
status_ = UpdateGfxEdit();
TAB_ITEM("Sheet Browser")
if (asset_browser_.Initialized == false) {
asset_browser_.Initialize(rom()->mutable_bitmap_manager());
if (ImGui::BeginTabBar("##TabBar")) {
status_ = UpdateGfxEdit();
TAB_ITEM("Sheet Browser")
if (asset_browser_.Initialized == false) {
asset_browser_.Initialize(rom()->gfx_sheets());
}
asset_browser_.Draw(rom()->gfx_sheets());
END_TAB_ITEM()
status_ = UpdateScadView();
status_ = UpdateLinkGfxView();
ImGui::EndTabBar();
}
asset_browser_.Draw(rom()->mutable_bitmap_manager());
END_TAB_ITEM()
status_ = UpdateScadView();
status_ = UpdateLinkGfxView();
END_TAB_BAR()
CLEAR_AND_RETURN_STATUS(status_)
return absl::OkStatus();
}
absl::Status GraphicsEditor::UpdateGfxEdit() {
TAB_ITEM("Sheet Editor")
if (ImGui::BeginTabItem("Sheet Editor")) {
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
ImVec2(0, 0))) {
for (const auto& name :
{"Tilesheets", "Current Graphics", "Palette Controls"})
ImGui::TableSetupColumn(name);
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
ImVec2(0, 0))) {
for (const auto& name :
{"Tilesheets", "Current Graphics", "Palette Controls"})
ImGui::TableSetupColumn(name);
ImGui::TableHeadersRow();
ImGui::TableHeadersRow();
NEXT_COLUMN();
status_ = UpdateGfxSheetList();
NEXT_COLUMN();
status_ = UpdateGfxSheetList();
NEXT_COLUMN();
if (rom()->is_loaded()) {
DrawGfxEditToolset();
status_ = UpdateGfxTabView();
}
NEXT_COLUMN();
if (rom()->is_loaded()) {
DrawGfxEditToolset();
status_ = UpdateGfxTabView();
NEXT_COLUMN();
if (rom()->is_loaded()) {
status_ = UpdatePaletteColumn();
}
}
ImGui::EndTable();
NEXT_COLUMN();
if (rom()->is_loaded()) {
status_ = UpdatePaletteColumn();
}
ImGui::EndTabItem();
}
ImGui::EndTable();
END_TAB_ITEM()
return absl::OkStatus();
}
@@ -127,8 +116,8 @@ void GraphicsEditor::DrawGfxEditToolset() {
TableNextColumn();
if (Button(ICON_MD_CONTENT_COPY)) {
std::vector<uint8_t> png_data =
rom()->bitmap_manager().shared_bitmap(current_sheet_).GetPngData();
CopyImageToClipboard(png_data);
rom()->gfx_sheets().at(current_sheet_).GetPngData();
core::CopyImageToClipboard(png_data);
}
HOVER_HINT("Copy to Clipboard");
@@ -136,14 +125,14 @@ void GraphicsEditor::DrawGfxEditToolset() {
if (Button(ICON_MD_CONTENT_PASTE)) {
std::vector<uint8_t> png_data;
int width, height;
GetImageFromClipboard(png_data, width, height);
core::GetImageFromClipboard(png_data, width, height);
if (png_data.size() > 0) {
rom()
->mutable_bitmap_manager()
->mutable_bitmap(current_sheet_)
->Create(width, height, 8, png_data);
rom()->UpdateBitmap(
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_));
->mutable_gfx_sheets()
->at(current_sheet_)
.Create(width, height, 8, png_data);
Renderer::GetInstance().UpdateBitmap(
&rom()->mutable_gfx_sheets()->at(current_sheet_));
}
}
HOVER_HINT("Paste from Clipboard");
@@ -163,7 +152,7 @@ void GraphicsEditor::DrawGfxEditToolset() {
}
TableNextColumn();
auto bitmap = rom()->bitmap_manager()[current_sheet_];
auto bitmap = rom()->gfx_sheets()[current_sheet_];
auto palette = bitmap.palette();
for (int i = 0; i < 8; i++) {
ImGui::SameLine();
@@ -192,28 +181,28 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
static ImGuiSelectionBasicStorage selection;
ImGuiMultiSelectFlags flags =
ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(
flags, selection.Size, rom()->bitmap_manager().size());
ImGuiMultiSelectIO* ms_io =
ImGui::BeginMultiSelect(flags, selection.Size, kNumGfxSheets);
selection.ApplyRequests(ms_io);
ImGuiListClipper clipper;
clipper.Begin(rom()->bitmap_manager().size());
clipper.Begin(kNumGfxSheets);
if (ms_io->RangeSrcItem != -1)
clipper.IncludeItemByIndex(
(int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.
for (auto& [key, value] : rom()->bitmap_manager()) {
int key = 0;
for (auto& value : rom()->gfx_sheets()) {
ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(),
ImVec2(0x100 + 1, 0x40 + 1), true,
ImGuiWindowFlags_NoDecoration);
ImGui::PopStyleVar();
gui::Canvas graphics_bin_canvas_;
graphics_bin_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x40 + 1));
graphics_bin_canvas_.DrawContextMenu();
if (value.is_active()) {
auto texture = value.texture();
graphics_bin_canvas_.draw_list()->AddImage(
(void*)texture,
(ImTextureID)(intptr_t)texture,
ImVec2(graphics_bin_canvas_.zero_point().x + 2,
graphics_bin_canvas_.zero_point().y + 2),
ImVec2(graphics_bin_canvas_.zero_point().x +
@@ -240,6 +229,8 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
graphics_bin_canvas_.draw_list()->AddText(
text_pos, IM_COL32(125, 255, 125, 255),
absl::StrFormat("%02X", key).c_str());
key++;
}
graphics_bin_canvas_.DrawGrid(16.0f);
graphics_bin_canvas_.DrawOverlay();
@@ -256,6 +247,10 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
absl::Status GraphicsEditor::UpdateGfxTabView() {
static int next_tab_id = 0;
constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
ImGuiTabBarFlags_FittingPolicyResizeDown |
ImGuiTabBarFlags_TabListPopupButton;
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing |
@@ -285,18 +280,17 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
ImGuiWindowFlags_AlwaysVerticalScrollbar |
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
gfx::Bitmap& current_bitmap =
*rom()->mutable_bitmap_manager()->mutable_bitmap(sheet_id);
gfx::Bitmap& current_bitmap = rom()->mutable_gfx_sheets()->at(sheet_id);
auto draw_tile_event = [&]() {
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, &current_bitmap,
current_color_);
rom()->UpdateBitmap(&current_bitmap, true);
Renderer::GetInstance().UpdateBitmap(&current_bitmap);
};
current_sheet_canvas_.UpdateColorPainter(
rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event,
tile_size_, current_scale_);
rom()->mutable_gfx_sheets()->at(sheet_id), current_color_,
draw_tile_event, tile_size_, current_scale_);
ImGui::EndChild();
ImGui::EndTabItem();
@@ -328,7 +322,7 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
current_sheet_ = id;
// ImVec2(0x100, 0x40),
current_sheet_canvas_.UpdateColorPainter(
rom()->bitmap_manager()[id], current_color_,
rom()->mutable_gfx_sheets()->at(id), current_color_,
[&]() {
},
@@ -365,11 +359,12 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() {
if (refresh_graphics_ && !open_sheets_.empty()) {
RETURN_IF_ERROR(
rom()->bitmap_manager()[current_sheet_].ApplyPaletteWithTransparent(
palette, edit_palette_sub_index_));
rom()->UpdateBitmap(
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_),
true);
rom()
->mutable_gfx_sheets()
->data()[current_sheet_]
.ApplyPaletteWithTransparent(palette, edit_palette_sub_index_));
Renderer::GetInstance().UpdateBitmap(
&rom()->mutable_gfx_sheets()->data()[current_sheet_]);
refresh_graphics_ = false;
}
}
@@ -391,9 +386,9 @@ absl::Status GraphicsEditor::UpdateLinkGfxView() {
link_canvas_.DrawGrid(16.0f);
int i = 0;
for (auto [key, link_sheet] : *rom()->mutable_link_graphics()) {
for (auto link_sheet : *rom()->mutable_link_graphics()) {
int x_offset = 0;
int y_offset = core::kTilesheetHeight * i * 4;
int y_offset = gfx::kTilesheetHeight * i * 4;
link_canvas_.DrawContextMenu(&link_sheet);
link_canvas_.DrawBitmap(link_sheet, x_offset, y_offset, 4);
i++;
@@ -435,6 +430,10 @@ absl::Status GraphicsEditor::UpdateScadView() {
ImGui::End();
}
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame;
BEGIN_TABLE("#gfxEditTable", 4, kGfxEditFlags)
SETUP_COLUMN("File Import (BIN, CGX, ROM)")
SETUP_COLUMN("Palette (COL)")
@@ -458,17 +457,18 @@ absl::Status GraphicsEditor::UpdateScadView() {
NEXT_COLUMN()
if (super_donkey_) {
if (refresh_graphics_) {
for (int i = 0; i < graphics_bin_.size(); i++) {
status_ = graphics_bin_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]);
rom()->UpdateBitmap(&graphics_bin_[i]);
}
refresh_graphics_ = false;
}
// TODO: Implement the Super Donkey 1 graphics decompression
// if (refresh_graphics_) {
// for (int i = 0; i < kNumGfxSheets; i++) {
// status_ = graphics_bin_[i].ApplyPalette(
// col_file_palette_group_[current_palette_index_]);
// Renderer::GetInstance().UpdateBitmap(&graphics_bin_[i]);
// }
// refresh_graphics_ = false;
// }
// Load the full graphics space from `super_donkey_1.bin`
gui::GraphicsBinCanvasPipeline(0x100, 0x40, 0x20, num_sheets_to_load_, 3,
super_donkey_, graphics_bin_);
// gui::GraphicsBinCanvasPipeline(0x100, 0x40, 0x20, num_sheets_to_load_, 3,
// super_donkey_, graphics_bin_);
} else if (cgx_loaded_ && col_file_) {
// Load the CGX graphics
gui::BitmapCanvasPipeline(import_canvas_, cgx_bitmap_, 0x100, 16384, 0x20,
@@ -485,6 +485,11 @@ absl::Status GraphicsEditor::UpdateScadView() {
}
absl::Status GraphicsEditor::DrawToolset() {
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
"#memoryEditor",
"##separator_gfx1",
};
if (ImGui::BeginTable("GraphicsToolset", 2, ImGuiTableFlags_SizingFixedFit,
ImVec2(0, 0))) {
for (const auto& name : kGfxToolsetColumnNames)
@@ -535,7 +540,7 @@ absl::Status GraphicsEditor::DrawCgxImport() {
cgx_bitmap_.Create(0x80, 0x200, 8, decoded_cgx_);
if (col_file_) {
cgx_bitmap_.ApplyPalette(decoded_col_);
rom()->RenderBitmap(&cgx_bitmap_);
Renderer::GetInstance().RenderBitmap(&cgx_bitmap_);
}
}
@@ -570,7 +575,7 @@ absl::Status GraphicsEditor::DrawScrImport() {
scr_bitmap_.Create(0x100, 0x100, 8, decoded_scr_data_);
if (scr_loaded_) {
scr_bitmap_.ApplyPalette(decoded_col_);
rom()->RenderBitmap(&scr_bitmap_);
Renderer::GetInstance().RenderBitmap(&scr_bitmap_);
}
}
@@ -594,7 +599,7 @@ absl::Status GraphicsEditor::DrawPaletteControls() {
/*z3_load=*/false);
auto col_data_ = gfx::GetColFileData(temp_rom_.data());
if (col_file_palette_group_.size() != 0) {
col_file_palette_group_.Clear();
col_file_palette_group_.clear();
}
auto col_file_palette_group_status =
gfx::CreatePaletteGroupFromColFile(col_data_);
@@ -707,7 +712,8 @@ absl::Status GraphicsEditor::DrawClipboardImport() {
if (Button("Paste From Clipboard")) {
const char* text = ImGui::GetClipboardText();
if (text) {
const auto clipboard_data = Bytes(text, text + strlen(text));
const auto clipboard_data =
std::vector<uint8_t>(text, text + strlen(text));
ImGui::MemFree((void*)text);
status_ = temp_rom_.LoadFromBytes(clipboard_data);
is_open_ = true;
@@ -762,7 +768,7 @@ absl::Status GraphicsEditor::DecompressImportData(int size) {
temp_rom_.data(), current_offset_, size))
auto converted_sheet = gfx::SnesTo8bppSheet(import_data_, 3);
bin_bitmap_.Create(core::kTilesheetWidth, 0x2000, core::kTilesheetDepth,
bin_bitmap_.Create(gfx::kTilesheetWidth, 0x2000, gfx::kTilesheetDepth,
converted_sheet);
if (rom()->is_loaded()) {
@@ -775,7 +781,7 @@ absl::Status GraphicsEditor::DecompressImportData(int size) {
}
}
rom()->RenderBitmap(&bin_bitmap_);
Renderer::GetInstance().RenderBitmap(&bin_bitmap_);
gfx_loaded_ = true;
return absl::OkStatus();
@@ -790,11 +796,10 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
auto decompressed_data,
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
graphics_bin_[i] =
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
core::kTilesheetDepth, converted_sheet);
gfx_sheets_[i] = gfx::Bitmap(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
gfx::kTilesheetDepth, converted_sheet);
if (col_file_) {
status_ = graphics_bin_[i].ApplyPalette(
status_ = gfx_sheets_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]);
} else {
// ROM palette
@@ -802,10 +807,10 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_);
status_ = gfx_sheets_[i].ApplyPalette(z3_rom_palette_);
}
rom()->RenderBitmap(&graphics_bin_[i]);
Renderer::GetInstance().RenderBitmap(&gfx_sheets_[i]);
i++;
}
@@ -816,21 +821,20 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
auto decompressed_data,
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
graphics_bin_[i] =
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
core::kTilesheetDepth, converted_sheet);
gfx_sheets_[i] = gfx::Bitmap(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
gfx::kTilesheetDepth, converted_sheet);
if (col_file_) {
status_ = graphics_bin_[i].ApplyPalette(
status_ = gfx_sheets_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]);
} else {
// ROM palette
auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_);
status_ = gfx_sheets_[i].ApplyPalette(z3_rom_palette_);
}
rom()->RenderBitmap(&graphics_bin_[i]);
Renderer::GetInstance().RenderBitmap(&gfx_sheets_[i]);
i++;
}
super_donkey_ = true;

View File

@@ -1,22 +1,19 @@
#ifndef YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
#define YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include <stack>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/asset_browser.h"
#include "app/gui/modules/asset_browser.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
namespace yaze {
namespace app {
@@ -167,8 +164,8 @@ class GraphicsEditor : public SharedRom, public Editor {
MemoryEditor cgx_memory_editor_;
MemoryEditor col_memory_editor_;
PaletteEditor palette_editor_;
Bytes import_data_;
Bytes graphics_buffer_;
std::vector<uint8_t> import_data_;
std::vector<uint8_t> graphics_buffer_;
std::vector<uint8_t> decoded_cgx_;
std::vector<uint8_t> cgx_data_;
std::vector<uint8_t> extra_cgx_data_;
@@ -179,9 +176,8 @@ class GraphicsEditor : public SharedRom, public Editor {
gfx::Bitmap scr_bitmap_;
gfx::Bitmap bin_bitmap_;
gfx::Bitmap link_full_sheet_;
gfx::BitmapTable graphics_bin_;
gfx::BitmapTable clipboard_graphics_bin_;
gfx::BitmapTable link_graphics_;
std::array<gfx::Bitmap, kNumGfxSheets> gfx_sheets_;
gfx::PaletteGroup col_file_palette_group_;
gfx::SnesPalette z3_rom_palette_;
gfx::SnesPalette col_file_palette_;
@@ -189,11 +185,12 @@ class GraphicsEditor : public SharedRom, public Editor {
gui::Canvas import_canvas_;
gui::Canvas scr_canvas_;
gui::Canvas super_donkey_canvas_;
gui::Canvas graphics_bin_canvas_;
gui::Canvas current_sheet_canvas_{"CurrentSheetCanvas", ImVec2(0x80, 0x20),
gui::CanvasGridSize::k8x8};
gui::Canvas link_canvas_{
"LinkCanvas",
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
ImVec2(gfx::kTilesheetWidth * 4, gfx::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k16x16};
absl::Status status_;
};

View File

@@ -1,13 +1,11 @@
#include "palette_editor.h"
#include "imgui/imgui.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
@@ -86,6 +84,93 @@ static inline float color_saturate(float f) {
0.5f)) // Saturated, always output 0..255
} // namespace
absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
static ImVec4 current_palette[256] = {};
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
ImGuiColorEditFlags_NoDragDrop |
ImGuiColorEditFlags_NoOptions;
// Generate a default palette. The palette will persist and can be edited.
static bool init = false;
if (loaded && !init) {
for (int n = 0; n < palette.size(); n++) {
ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
current_palette[n].x = color.rgb().x / 255;
current_palette[n].y = color.rgb().y / 255;
current_palette[n].z = color.rgb().z / 255;
current_palette[n].w = 255; // Alpha
}
init = true;
}
static ImVec4 backup_color;
bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
SameLine(0, GetStyle().ItemInnerSpacing.x);
open_popup |= Button("Palette");
if (open_popup) {
OpenPopup("mypicker");
backup_color = color;
}
if (BeginPopup("mypicker")) {
TEXT_WITH_SEPARATOR("Current Overworld Palette");
ColorPicker4("##picker", (float*)&color,
misc_flags | ImGuiColorEditFlags_NoSidePreview |
ImGuiColorEditFlags_NoSmallPreview);
SameLine();
BeginGroup(); // Lock X position
Text("Current ==>");
SameLine();
Text("Previous");
if (Button("Update Map Palette")) {
}
ColorButton(
"##current", color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40));
SameLine();
if (ColorButton(
"##previous", backup_color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40)))
color = backup_color;
// List of Colors in Overworld Palette
Separator();
Text("Palette");
for (int n = 0; n < IM_ARRAYSIZE(current_palette); n++) {
PushID(n);
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
if (ColorButton("##palette", current_palette[n], kPalButtonFlags2,
ImVec2(20, 20)))
color = ImVec4(current_palette[n].x, current_palette[n].y,
current_palette[n].z, color.w); // Preserve alpha!
if (BeginDragDropTarget()) {
if (const ImGuiPayload* payload =
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 3);
if (const ImGuiPayload* payload =
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 4);
EndDragDropTarget();
}
PopID();
}
EndGroup();
EndPopup();
}
return absl::OkStatus();
}
absl::Status PaletteEditor::Update() {
if (rom()->is_loaded()) {
// Initialize the labels
@@ -348,7 +433,7 @@ absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i,
// SNES Format
CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
ConvertRGBtoSNES(ImVec4(col[0], col[1], col[2], 1.0f)));
ConvertRgbToSnes(ImVec4(col[0], col[1], col[2], 1.0f)));
if (Selectable(buf)) SetClipboardText(buf);
EndPopup();
@@ -358,84 +443,6 @@ absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i,
return absl::OkStatus();
}
void PaletteEditor::DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
ImGuiColorEditFlags_NoDragDrop |
ImGuiColorEditFlags_NoOptions;
// Generate a default palette. The palette will persist and can be edited.
static bool init = false;
if (loaded && !init) {
status_ = InitializeSavedPalette(palette);
init = true;
}
static ImVec4 backup_color;
bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
SameLine(0, GetStyle().ItemInnerSpacing.x);
open_popup |= Button("Palette");
if (open_popup) {
OpenPopup("mypicker");
backup_color = color;
}
if (BeginPopup("mypicker")) {
TEXT_WITH_SEPARATOR("Current Overworld Palette");
ColorPicker4("##picker", (float*)&color,
misc_flags | ImGuiColorEditFlags_NoSidePreview |
ImGuiColorEditFlags_NoSmallPreview);
SameLine();
BeginGroup(); // Lock X position
Text("Current ==>");
SameLine();
Text("Previous");
if (Button("Update Map Palette")) {
}
ColorButton(
"##current", color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40));
SameLine();
if (ColorButton(
"##previous", backup_color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40)))
color = backup_color;
// List of Colors in Overworld Palette
Separator();
Text("Palette");
for (int n = 0; n < IM_ARRAYSIZE(saved_palette_); n++) {
PushID(n);
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
if (ColorButton("##palette", saved_palette_[n], kPalButtonFlags2,
ImVec2(20, 20)))
color = ImVec4(saved_palette_[n].x, saved_palette_[n].y,
saved_palette_[n].z, color.w); // Preserve alpha!
if (BeginDragDropTarget()) {
if (const ImGuiPayload* payload =
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 3);
if (const ImGuiPayload* payload =
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 4);
EndDragDropTarget();
}
PopID();
}
EndGroup();
EndPopup();
}
}
absl::Status PaletteEditor::EditColorInPalette(gfx::SnesPalette& palette,
int index) {
if (index >= palette.size()) {

View File

@@ -1,15 +1,17 @@
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
#include "imgui/imgui.h"
#include <deque>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/editor.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gfx/snes_color.h"
#include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
@@ -26,56 +28,51 @@ struct PaletteChange {
class PaletteEditorHistory {
public:
// Record a change in the palette editor
void RecordChange(const std::string& groupName, size_t paletteIndex,
size_t colorIndex, const gfx::SnesColor& originalColor,
const gfx::SnesColor& newColor) {
// Check size and remove the oldest if necessary
if (recentChanges.size() >= maxHistorySize) {
recentChanges.pop_front();
void RecordChange(const std::string& group_name, size_t palette_index,
size_t color_index, const gfx::SnesColor& original_color,
const gfx::SnesColor& new_color) {
if (recent_changes_.size() >= kMaxHistorySize) {
recent_changes_.pop_front();
}
// Push the new change
recentChanges.push_back(
{groupName, paletteIndex, colorIndex, originalColor, newColor});
recent_changes_.push_back(
{group_name, palette_index, color_index, original_color, new_color});
}
// Get recent changes for display in the palette editor
const std::deque<PaletteChange>& GetRecentChanges() const {
return recentChanges;
}
// Restore the original color
gfx::SnesColor RestoreOriginalColor(const std::string& groupName,
size_t paletteIndex,
size_t colorIndex) const {
for (const auto& change : recentChanges) {
if (change.group_name == groupName &&
change.palette_index == paletteIndex &&
change.color_index == colorIndex) {
gfx::SnesColor RestoreOriginalColor(const std::string& group_name,
size_t palette_index,
size_t color_index) const {
for (const auto& change : recent_changes_) {
if (change.group_name == group_name &&
change.palette_index == palette_index &&
change.color_index == color_index) {
return change.original_color;
}
}
// Handle error or return default (this is just an example,
// handle as appropriate for your application)
return gfx::SnesColor();
}
auto size() const { return recentChanges.size(); }
auto size() const { return recent_changes_.size(); }
gfx::SnesColor& GetModifiedColor(size_t index) {
return recentChanges[index].new_color;
return recent_changes_[index].new_color;
}
gfx::SnesColor& GetOriginalColor(size_t index) {
return recentChanges[index].original_color;
return recent_changes_[index].original_color;
}
const std::deque<PaletteChange>& GetRecentChanges() const {
return recent_changes_;
}
private:
std::deque<PaletteChange> recentChanges;
static const size_t maxHistorySize = 50; // or any other number you deem fit
std::deque<PaletteChange> recent_changes_;
static const size_t kMaxHistorySize = 50;
};
} // namespace palette_internal
absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded);
/**
* @class PaletteEditor
* @brief Allows the user to view and edit in game palettes.
@@ -101,7 +98,6 @@ class PaletteEditor : public SharedRom, public Editor {
absl::Status EditColorInPalette(gfx::SnesPalette& palette, int index);
absl::Status ResetColorToOriginal(gfx::SnesPalette& palette, int index,
const gfx::SnesPalette& originalPalette);
void DisplayPalette(gfx::SnesPalette& palette, bool loaded);
absl::Status DrawPaletteGroup(int category, bool right_side = false);
void DrawCustomPalette();
@@ -110,16 +106,6 @@ class PaletteEditor : public SharedRom, public Editor {
private:
absl::Status HandleColorPopup(gfx::SnesPalette& palette, int i, int j, int n);
absl::Status InitializeSavedPalette(const gfx::SnesPalette& palette) {
for (int n = 0; n < palette.size(); n++) {
ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
saved_palette_[n].x = color.rgb().x / 255;
saved_palette_[n].y = color.rgb().y / 255;
saved_palette_[n].z = color.rgb().z / 255;
saved_palette_[n].w = 255; // Alpha
}
return absl::OkStatus();
}
absl::Status status_;
gfx::SnesColor current_color_;
@@ -137,4 +123,4 @@ class PaletteEditor : public SharedRom, public Editor {
} // namespace app
} // namespace yaze
#endif
#endif

View File

@@ -1,44 +1,45 @@
#include "screen_editor.h"
#include "imgui/imgui.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "app/core/common.h"
#include "app/core/constants.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/platform/renderer.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/zelda3/dungeon/room.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace editor {
absl::Status ScreenEditor::Update() {
TAB_BAR("##TabBar")
TAB_ITEM("Dungeon Maps")
if (rom()->is_loaded()) {
DrawDungeonMapsEditor();
}
END_TAB_ITEM()
DrawInventoryMenuEditor();
DrawOverworldMapEditor();
DrawTitleScreenEditor();
DrawNamingScreenEditor();
END_TAB_BAR()
using core::Renderer;
return absl::OkStatus();
constexpr uint32_t kRedPen = 0xFF0000FF;
absl::Status ScreenEditor::Update() {
if (ImGui::BeginTabBar("##ScreenEditorTabBar")) {
if (ImGui::BeginTabItem("Dungeon Maps")) {
if (rom()->is_loaded()) {
DrawDungeonMapsEditor();
}
ImGui::EndTabItem();
}
DrawInventoryMenuEditor();
DrawOverworldMapEditor();
DrawTitleScreenEditor();
DrawNamingScreenEditor();
ImGui::EndTabBar();
}
return status_;
}
void ScreenEditor::DrawInventoryMenuEditor() {
@@ -121,16 +122,16 @@ absl::Status ScreenEditor::LoadDungeonMaps() {
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
int ptr_gfx,
rom()->ReadWord(zelda3::screen::kDungeonMapGfxPtr + (d * 2)));
ptr |= 0x0A0000; // Add bank to the short ptr
ptr_gfx |= 0x0A0000; // Add bank to the short ptr
int pc_ptr = core::SnesToPc(ptr); // Contains data for the next 25 rooms
int pc_ptr_gfx =
core::SnesToPc(ptr_gfx); // Contains data for the next 25 rooms
ASSIGN_OR_RETURN(
ushort bossRoomD,
ushort boss_room_d,
rom()->ReadWord(zelda3::screen::kDungeonMapBossRooms + (d * 2)));
ASSIGN_OR_RETURN(
@@ -157,14 +158,13 @@ absl::Status ScreenEditor::LoadDungeonMaps() {
// 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
rdata[j] = rom()->data()[pc_ptr + j + (i * 25)]; // Set the rooms
if (rdata[j] == 0x0F) {
gdata[j] = 0xFF;
} else {
gdata[j] = rom()->data()[pcPtrGFX++];
gdata[j] = rom()->data()[pc_ptr_gfx++];
}
std::string label = core::UppercaseHexByte(rdata[j]);
@@ -175,7 +175,7 @@ absl::Status ScreenEditor::LoadDungeonMaps() {
current_floor_rooms_d.push_back(rdata); // Add new floor data
}
dungeon_maps_.emplace_back(bossRoomD, nbr_floor_d, nbr_basement_d,
dungeon_maps_.emplace_back(boss_room_d, nbr_floor_d, nbr_basement_d,
current_floor_rooms_d, current_floor_gfx_d);
}
@@ -185,23 +185,19 @@ absl::Status ScreenEditor::LoadDungeonMaps() {
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);
int ptr_gfx = zelda3::screen::kDungeonMapGfxPtr + (d * 2);
int pc_ptr = core::SnesToPc(ptr);
int pc_ptr_gfx = core::SnesToPc(ptr_gfx);
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),
RETURN_IF_ERROR(rom()->WriteByte(pc_ptr + j + (i * 25),
dungeon_maps_[d].floor_rooms[i][j]));
RETURN_IF_ERROR(rom()->WriteByte(ptrGFX + j + (i * 25),
RETURN_IF_ERROR(rom()->WriteByte(pc_ptr_gfx + j + (i * 25),
dungeon_maps_[d].floor_gfx[i][j]));
pcPtrGFX++;
pc_ptr_gfx++;
}
}
}
@@ -209,7 +205,8 @@ absl::Status ScreenEditor::SaveDungeonMaps() {
return absl::OkStatus();
}
absl::Status ScreenEditor::LoadDungeonMapTile16() {
absl::Status ScreenEditor::LoadDungeonMapTile16(
const std::vector<uint8_t>& gfx_data, bool bin_mode) {
tile16_sheet_.Init(256, 192, gfx::TileType::Tile16);
for (int i = 0; i < 186; i++) {
@@ -230,69 +227,99 @@ absl::Status ScreenEditor::LoadDungeonMapTile16() {
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);
int sheet_offset = 212;
if (bin_mode) {
sheet_offset = 0;
}
tile16_sheet_.ComposeTile16(gfx_data, t1, t2, t3, t4, sheet_offset);
}
RETURN_IF_ERROR(tile16_sheet_.mutable_bitmap()->ApplyPalette(
*rom()->mutable_dungeon_palette(3)));
rom()->RenderBitmap(&*tile16_sheet_.mutable_bitmap().get());
Renderer::GetInstance().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]);
}
auto tile = tile16_sheet_.GetTile16(i);
tile16_individual_[i] = tile;
RETURN_IF_ERROR(
tile16_individual_[i].ApplyPalette(*rom()->mutable_dungeon_palette(3)));
Renderer::GetInstance().RenderBitmap(&tile16_individual_[i]);
}
return absl::OkStatus();
}
absl::Status ScreenEditor::SaveDungeonMapTile16() {
for (int i = 0; i < 186; i++) {
int addr = zelda3::screen::kDungeonMapTile16;
if (rom()->data()[zelda3::screen::kDungeonMapExpCheck] != 0xB9) {
addr = zelda3::screen::kDungeonMapTile16Expanded;
}
gfx::TileInfo t1 = tile16_sheet_.tile_info()[i].tiles[0];
gfx::TileInfo t2 = tile16_sheet_.tile_info()[i].tiles[1];
gfx::TileInfo t3 = tile16_sheet_.tile_info()[i].tiles[2];
gfx::TileInfo t4 = tile16_sheet_.tile_info()[i].tiles[3];
auto tl = gfx::TileInfoToWord(t1);
RETURN_IF_ERROR(rom()->WriteWord(addr + (i * 8), tl));
auto tr = gfx::TileInfoToWord(t2);
RETURN_IF_ERROR(rom()->WriteWord(addr + 2 + (i * 8), tr));
auto bl = gfx::TileInfoToWord(t3);
RETURN_IF_ERROR(rom()->WriteWord(addr + 4 + (i * 8), bl));
auto br = gfx::TileInfoToWord(t4);
RETURN_IF_ERROR(rom()->WriteWord(addr + 6 + (i * 8), br));
}
return absl::OkStatus();
}
void ScreenEditor::DrawDungeonMapsTabs() {
auto current_dungeon = dungeon_maps_[selected_dungeon];
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);
int basement_num = current_dungeon.nbr_of_basement - i;
std::string tab_name = absl::StrFormat("Basement %d", basement_num);
if (i >= current_dungeon.nbr_of_basement) {
tab_name = absl::StrFormat("Floor %d",
i - current_dungeon.nbr_of_basement + 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 tile16_id = current_dungeon.floor_gfx[floor_number][j];
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;
tile16_individual_[tile16_id] =
tile16_sheet_.GetTile16(tile16_id);
Renderer::GetInstance().RenderBitmap(
&tile16_individual_[tile16_id]);
}
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);
64, kRedPen);
}
std::string label =
dungeon_map_labels_[selected_dungeon][floor_number][j];
screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
std::string gfx_id = core::UppercaseHexByte(tile16_id);
screen_canvas_.DrawText(gfx_id, (posX * 2), (posY * 2) + 16);
}
}
@@ -316,31 +343,76 @@ void ScreenEditor::DrawDungeonMapsTabs() {
gui::InputHexWord("Boss Room", &current_dungeon.boss_room);
if (ImGui::Button("Copy Floor", ImVec2(100, 0))) {
const ImVec2 button_size = ImVec2(130, 0);
// Add Floor Button
if (ImGui::Button("Add Floor", button_size) &&
current_dungeon.nbr_of_floor < 8) {
current_dungeon.nbr_of_floor++;
dungeon_map_labels_[selected_dungeon].emplace_back();
}
ImGui::SameLine();
if (ImGui::Button("Remove Floor", button_size) &&
current_dungeon.nbr_of_floor > 0) {
current_dungeon.nbr_of_floor--;
dungeon_map_labels_[selected_dungeon].pop_back();
}
// Add Basement Button
if (ImGui::Button("Add Basement", button_size) &&
current_dungeon.nbr_of_basement < 8) {
current_dungeon.nbr_of_basement++;
dungeon_map_labels_[selected_dungeon].emplace_back();
}
ImGui::SameLine();
if (ImGui::Button("Remove Basement", button_size) &&
current_dungeon.nbr_of_basement > 0) {
current_dungeon.nbr_of_basement--;
dungeon_map_labels_[selected_dungeon].pop_back();
}
if (ImGui::Button("Copy Floor", button_size)) {
copy_button_pressed = true;
}
ImGui::SameLine();
if (ImGui::Button("Paste Floor", ImVec2(100, 0))) {
if (ImGui::Button("Paste Floor", button_size)) {
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 {
if (!LoadDungeonMaps().ok()) {
ImGui::Text("Failed to load dungeon maps");
}
if (LoadDungeonMapTile16(rom()->graphics_buffer()).ok()) {
// TODO: Load roomset gfx based on dungeon ID
sheets_.emplace(0, rom()->gfx_sheets()[212]);
sheets_.emplace(1, rom()->gfx_sheets()[213]);
sheets_.emplace(2, rom()->gfx_sheets()[214]);
sheets_.emplace(3, rom()->gfx_sheets()[215]);
dungeon_maps_loaded_ = true;
} else {
ImGui::Text("Failed to load dungeon map tile16");
}
}
if (ImGui::BeginTable("##DungeonMapToolset", 2, ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Draw Mode");
ImGui::TableSetupColumn("Edit Mode");
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_DRAW)) {
current_mode_ = EditingMode::DRAW;
}
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_EDIT)) {
current_mode_ = EditingMode::EDIT;
}
ImGui::EndTable();
}
static std::vector<std::string> dungeon_names = {
@@ -350,7 +422,10 @@ void ScreenEditor::DrawDungeonMapsEditor() {
"Thieves' Town", "Ice Palace", "Misery Mire",
"Turtle Rock", "Ganon's Tower"};
if (ImGui::BeginTable("DungeonMapsTable", 4, ImGuiTableFlags_Resizable)) {
if (ImGui::BeginTable("DungeonMapsTable", 4,
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Hideable)) {
ImGui::TableSetupColumn("Dungeon");
ImGui::TableSetupColumn("Map");
ImGui::TableSetupColumn("Rooms Gfx");
@@ -384,32 +459,113 @@ void ScreenEditor::DrawDungeonMapsEditor() {
if (!tilesheet_canvas_.points().empty()) {
selected_tile16_ = tilesheet_canvas_.points().front().x / 32 +
(tilesheet_canvas_.points().front().y / 32) * 16;
current_tile16_info = tile16_sheet_.tile_info().at(selected_tile16_);
// Draw the selected tile
if (!screen_canvas_.points().empty()) {
dungeon_maps_[selected_dungeon]
.floor_gfx[floor_number][selected_room] = selected_tile16_;
tilesheet_canvas_.mutable_points()->clear();
}
}
ImGui::Separator();
current_tile_canvas_.DrawBackground(ImVec2(64 * 2 + 2, 64 * 2 + 4));
current_tile_canvas_.DrawContextMenu();
current_tile_canvas_.DrawBitmap(tile16_individual_[selected_tile16_], 2,
4.0f);
current_tile_canvas_.DrawGrid(16.f);
current_tile_canvas_.DrawOverlay();
gui::InputTileInfo("TL", &current_tile16_info.tiles[0]);
ImGui::SameLine();
gui::InputTileInfo("TR", &current_tile16_info.tiles[1]);
gui::InputTileInfo("BL", &current_tile16_info.tiles[2]);
ImGui::SameLine();
gui::InputTileInfo("BR", &current_tile16_info.tiles[3]);
if (ImGui::Button("Modify Tile16")) {
tile16_sheet_.ModifyTile16(
rom()->graphics_buffer(), current_tile16_info.tiles[0],
current_tile16_info.tiles[1], current_tile16_info.tiles[2],
current_tile16_info.tiles[3], selected_tile16_, 212);
tile16_individual_[selected_tile16_] =
tile16_sheet_.GetTile16(selected_tile16_);
RETURN_VOID_IF_ERROR(tile16_individual_[selected_tile16_].ApplyPalette(
*rom()->mutable_dungeon_palette(3)));
Renderer::GetInstance().RenderBitmap(
&tile16_individual_[selected_tile16_]);
}
}
ImGui::EndChild();
ImGui::TableNextColumn();
tilemap_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
tilemap_canvas_.DrawBackground();
tilemap_canvas_.DrawContextMenu();
if (tilemap_canvas_.DrawTileSelector(16.f)) {
// Get the tile8 ID to use for the tile16 drawing above
selected_tile8_ = tilemap_canvas_.GetTileIdFromMousePos();
}
tilemap_canvas_.DrawBitmapTable(sheets_);
tilemap_canvas_.DrawGrid();
tilemap_canvas_.DrawOverlay();
ImGui::Text("Selected tile8: %d", selected_tile8_);
ImGui::Separator();
ImGui::Text("For use with custom inserted graphics assembly patches.");
if (ImGui::Button("Load GFX from BIN file")) LoadBinaryGfx();
ImGui::EndTable();
}
}
void ScreenEditor::LoadBinaryGfx() {
std::string bin_file = core::FileDialogWrapper::ShowOpenFileDialog();
if (!bin_file.empty()) {
std::ifstream file(bin_file, std::ios::binary);
if (file.is_open()) {
// Read the gfx data into a buffer
std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
gfx_bin_data_ = converted_bin;
tile16_sheet_.clear();
if (LoadDungeonMapTile16(converted_bin, true).ok()) {
sheets_.clear();
std::vector<std::vector<uint8_t>> gfx_sheets;
for (int i = 0; i < 4; i++) {
gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
converted_bin.begin() + ((i + 1) * 0x1000));
sheets_.emplace(i, gfx::Bitmap(128, 32, 8, gfx_sheets[i]));
sheets_[i].ApplyPalette(*rom()->mutable_dungeon_palette(3));
Renderer::GetInstance().RenderBitmap(&sheets_[i]);
}
binary_gfx_loaded_ = true;
} else {
status_ = absl::InternalError("Failed to load dungeon map tile16");
}
file.close();
}
}
}
void ScreenEditor::DrawTitleScreenEditor() {
TAB_ITEM("Title Screen")
END_TAB_ITEM()
if (ImGui::BeginTabItem("Title Screen")) {
ImGui::EndTabItem();
}
}
void ScreenEditor::DrawNamingScreenEditor() {
TAB_ITEM("Naming Screen")
END_TAB_ITEM()
if (ImGui::BeginTabItem("Naming Screen")) {
ImGui::EndTabItem();
}
}
void ScreenEditor::DrawOverworldMapEditor() {
TAB_ITEM("Overworld Map")
END_TAB_ITEM()
if (ImGui::BeginTabItem("Overworld Map")) {
ImGui::EndTabItem();
}
}
void ScreenEditor::DrawToolset() {

View File

@@ -1,23 +1,18 @@
#ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H
#define YAZE_APP_EDITOR_SCREEN_EDITOR_H
#include "imgui/imgui.h"
#include <array>
#include "absl/status/status.h"
#include "app/core/constants.h"
#include "app/editor/utils/editor.h"
#include "app/editor/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/rom.h"
#include "app/zelda3/screen/dungeon_map.h"
#include "app/zelda3/screen/inventory.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
@@ -38,7 +33,7 @@ namespace editor {
* The class inherits from the SharedRom class.
*/
class ScreenEditor : public SharedRom, public Editor {
public:
public:
ScreenEditor() {
screen_canvas_.SetCanvasSize(ImVec2(512, 512));
type_ = EditorType::kScreen;
@@ -55,7 +50,7 @@ class ScreenEditor : public SharedRom, public Editor {
absl::Status SaveDungeonMaps();
private:
private:
void DrawTitleScreenEditor();
void DrawNamingScreenEditor();
void DrawOverworldMapEditor();
@@ -65,42 +60,58 @@ class ScreenEditor : public SharedRom, public Editor {
void DrawInventoryToolset();
absl::Status LoadDungeonMaps();
absl::Status LoadDungeonMapTile16();
absl::Status LoadDungeonMapTile16(const std::vector<uint8_t> &gfx_data,
bool bin_mode = false);
absl::Status SaveDungeonMapTile16();
void DrawDungeonMapsTabs();
void DrawDungeonMapsEditor();
std::vector<zelda3::screen::DungeonMap> dungeon_maps_;
std::vector<std::vector<std::array<std::string, 25>>> dungeon_map_labels_;
void LoadBinaryGfx();
std::unordered_map<int, gfx::Bitmap> tile16_individual_;
enum class EditingMode { DRAW, EDIT };
EditingMode current_mode_ = EditingMode::DRAW;
bool dungeon_maps_loaded_ = false;
bool binary_gfx_loaded_ = false;
int selected_tile16_ = 0;
int selected_dungeon = 0;
uint8_t selected_room = 0;
uint8_t boss_room = 0;
int selected_tile16_ = 0;
int selected_tile8_ = 0;
int selected_dungeon = 0;
int floor_number = 1;
bool copy_button_pressed = false;
bool paste_button_pressed = false;
Bytes all_gfx_;
zelda3::screen::Inventory inventory_;
gfx::SnesPalette palette_;
gui::Canvas screen_canvas_;
gui::Canvas tilesheet_canvas_;
gui::Canvas tilemap_canvas_;
gfx::BitmapTable sheets_;
gfx::Tilesheet tile16_sheet_;
std::vector<uint8_t> all_gfx_;
std::unordered_map<int, gfx::Bitmap> tile16_individual_;
std::vector<zelda3::screen::DungeonMap> dungeon_maps_;
std::vector<std::vector<std::array<std::string, 25>>> dungeon_map_labels_;
std::array<uint16_t, 4> current_tile16_data_;
std::vector<uint8_t> gfx_bin_data_;
absl::Status status_;
gfx::SnesPalette palette_;
gfx::BitmapTable sheets_;
gfx::Tilesheet tile16_sheet_;
gfx::InternalTile16 current_tile16_info;
gui::Canvas current_tile_canvas_{"##CurrentTileCanvas"};
gui::Canvas screen_canvas_;
gui::Canvas tilesheet_canvas_;
gui::Canvas tilemap_canvas_{"##TilemapCanvas",
ImVec2(128 + 2, (192) + 4),
gui::CanvasGridSize::k8x8, 2.f};
zelda3::screen::Inventory inventory_;
};
} // namespace editor
} // namespace app
} // namespace yaze
} // namespace editor
} // namespace app
} // namespace yaze
#endif

View File

@@ -1,14 +1,13 @@
#include "tile16_editor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "imgui/imgui.h"
#include <cmath>
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/core/platform/renderer.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
@@ -19,11 +18,14 @@
#include "app/gui/style.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace editor {
using core::Renderer;
using ImGui::BeginChild;
using ImGui::BeginMenu;
using ImGui::BeginMenuBar;
@@ -48,14 +50,13 @@ using ImGui::TableSetupColumn;
using ImGui::Text;
absl::Status Tile16Editor::InitBlockset(
gfx::Bitmap* tile16_blockset_bmp, gfx::Bitmap current_gfx_bmp,
const gfx::Bitmap& tile16_blockset_bmp, const gfx::Bitmap& current_gfx_bmp,
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();
RETURN_IF_ERROR(LoadTile8());
ImVector<std::string> tile16_names;
for (int i = 0; i < 0x200; ++i) {
@@ -75,7 +76,7 @@ absl::Status Tile16Editor::Update() {
RETURN_IF_ERROR(DrawMenu());
if (BeginTabBar("Tile16 Editor Tabs")) {
RETURN_IF_ERROR(DrawTile16Editor());
DrawTile16Editor();
RETURN_IF_ERROR(UpdateTile16Transfer());
EndTabBar();
}
@@ -97,7 +98,7 @@ absl::Status Tile16Editor::DrawMenu() {
return absl::OkStatus();
}
absl::Status Tile16Editor::DrawTile16Editor() {
void Tile16Editor::DrawTile16Editor() {
if (BeginTabItem("Tile16 Editing")) {
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) {
@@ -108,18 +109,23 @@ absl::Status Tile16Editor::DrawTile16Editor() {
TableHeadersRow();
TableNextRow();
TableNextColumn();
RETURN_IF_ERROR(UpdateBlockset());
status_ = UpdateBlockset();
if (!status_.ok()) {
EndTable();
}
TableNextColumn();
RETURN_IF_ERROR(UpdateTile16Edit());
RETURN_IF_ERROR(DrawTileEditControls());
status_ = UpdateTile16Edit();
if (status_ != absl::OkStatus()) {
EndTable();
}
status_ = DrawTileEditControls();
EndTable();
}
EndTabItem();
}
return absl::OkStatus();
}
absl::Status Tile16Editor::UpdateBlockset() {
@@ -127,14 +133,12 @@ absl::Status Tile16Editor::UpdateBlockset() {
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();
EndChild();
}
blockset_canvas_.DrawContextMenu();
blockset_canvas_.DrawTileSelector(32);
blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 0, map_blockset_loaded_);
blockset_canvas_.DrawGrid();
blockset_canvas_.DrawOverlay();
EndChild();
if (!blockset_canvas_.points().empty()) {
notify_tile16.mutable_get() = blockset_canvas_.GetTileIdFromMousePos();
@@ -142,11 +146,11 @@ absl::Status Tile16Editor::UpdateBlockset() {
if (notify_tile16.modified()) {
current_tile16_ = notify_tile16.get();
current_tile16_bmp_ = &tile16_individual_[notify_tile16];
current_tile16_bmp_ = tile16_individual_[notify_tile16];
auto ow_main_pal_group = rom()->palette_group().overworld_main;
RETURN_IF_ERROR(current_tile16_bmp_->ApplyPalette(
RETURN_IF_ERROR(current_tile16_bmp_.ApplyPalette(
ow_main_pal_group[current_palette_]));
rom()->RenderBitmap(current_tile16_bmp_);
Renderer::GetInstance().RenderBitmap(&current_tile16_bmp_);
}
}
@@ -166,8 +170,8 @@ absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
// Calculate the pixel start position within the Tile16
ImVec2 start_position;
start_position.x = ((tile_index_x) / 4) * 0x40;
start_position.y = ((tile_index_y) / 4) * 0x40;
start_position.x = tile_index_x * 0x40;
start_position.y = tile_index_y * 0x40;
std::cout << "Start Position X: " << start_position.x << std::endl;
std::cout << "Start Position Y: " << start_position.y << std::endl;
@@ -177,7 +181,7 @@ absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
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(
current_tile16_bmp_.WriteToPixel(
pixel_index,
current_gfx_individual_[current_tile8_].data()[gfx_pixel_index]);
}
@@ -197,7 +201,8 @@ absl::Status Tile16Editor::UpdateTile16Edit() {
RETURN_IF_ERROR(
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
ow_main_pal_group[0], current_palette_));
rom()->UpdateBitmap(&current_gfx_individual_[current_tile8_]);
Renderer::GetInstance().UpdateBitmap(
&current_gfx_individual_[current_tile8_]);
}
tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 4.0f);
tile8_source_canvas_.DrawGrid();
@@ -214,20 +219,21 @@ absl::Status Tile16Editor::UpdateTile16Edit() {
RETURN_IF_ERROR(
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
ow_main_pal_group[0], current_palette_));
rom()->UpdateBitmap(&current_gfx_individual_[current_tile8_]);
Renderer::GetInstance().UpdateBitmap(
&current_gfx_individual_[current_tile8_]);
}
if (BeginChild("Tile16 Editor Options",
ImVec2(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);
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_);
Renderer::GetInstance().UpdateBitmap(&current_tile16_bmp_);
}
}
tile16_edit_canvas_.DrawGrid();
@@ -258,10 +264,11 @@ absl::Status Tile16Editor::DrawTileEditControls() {
if (value > 0x00) {
RETURN_IF_ERROR(
current_gfx_bmp_.ApplyPaletteWithTransparent(palette, value));
Renderer::GetInstance().UpdateBitmap(&current_gfx_bmp_);
RETURN_IF_ERROR(
current_tile16_bmp_->ApplyPaletteWithTransparent(palette, value));
rom()->UpdateBitmap(&current_gfx_bmp_);
rom()->UpdateBitmap(current_tile16_bmp_);
current_tile16_bmp_.ApplyPaletteWithTransparent(palette, value));
Renderer::GetInstance().UpdateBitmap(&current_tile16_bmp_);
}
}
@@ -290,7 +297,6 @@ absl::Status Tile16Editor::LoadTile8() {
// 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);
@@ -310,7 +316,7 @@ absl::Status Tile16Editor::LoadTile8() {
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]);
Renderer::GetInstance().RenderBitmap(&current_gfx_individual_[index]);
}
map_blockset_loaded_ = true;
@@ -318,8 +324,17 @@ absl::Status Tile16Editor::LoadTile8() {
return absl::OkStatus();
}
// ============================================================================
// Tile16 Transfer
absl::Status Tile16Editor::SetCurrentTile(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_]));
Renderer::GetInstance().RenderBitmap(&current_tile16_bmp_);
return absl::OkStatus();
}
#pragma mark - Tile16Transfer
absl::Status Tile16Editor::UpdateTile16Transfer() {
if (BeginTabItem("Tile16 Transfer")) {
@@ -363,16 +378,15 @@ absl::Status Tile16Editor::UpdateTransferTileCanvas() {
// TODO: Implement tile16 transfer
if (transfer_started_ && !transfer_blockset_loaded_) {
PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData())
graphics_bin_ = transfer_rom_.graphics_bin();
// Load the Link to the Past overworld.
PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_))
transfer_overworld_.set_current_map(0);
palette_ = transfer_overworld_.AreaPalette();
palette_ = transfer_overworld_.current_area_palette();
// Create the tile16 blockset image
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
0x80, 0x2000, 0x80, transfer_overworld_.Tile16Blockset(),
RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
0x80, 0x2000, 0x80, transfer_overworld_.tile16_blockset_data(),
transfer_blockset_bmp_, palette_));
transfer_blockset_loaded_ = true;
}
@@ -385,16 +399,6 @@ absl::Status Tile16Editor::UpdateTransferTileCanvas() {
return absl::OkStatus();
}
absl::Status Tile16Editor::SetCurrentTile(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();
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,23 +1,16 @@
#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
#define YAZE_APP_EDITOR_TILE16EDITOR_H
#include "imgui/imgui.h"
#include <cmath>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/core/common.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/utils/gfx_context.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
@@ -26,17 +19,17 @@ namespace editor {
/**
* @brief Popup window to edit Tile16 data
*/
class Tile16Editor : public context::GfxContext, public SharedRom {
class Tile16Editor : public GfxContext, public SharedRom {
public:
absl::Status InitBlockset(gfx::Bitmap* tile16_blockset_bmp,
gfx::Bitmap current_gfx_bmp,
absl::Status InitBlockset(const gfx::Bitmap& tile16_blockset_bmp,
const gfx::Bitmap& current_gfx_bmp,
const std::vector<gfx::Bitmap>& tile16_individual,
uint8_t all_tiles_types[0x200]);
absl::Status Update();
absl::Status DrawMenu();
absl::Status DrawTile16Editor();
void DrawTile16Editor();
absl::Status UpdateTile16Transfer();
absl::Status UpdateBlockset();
@@ -75,38 +68,32 @@ class Tile16Editor : public context::GfxContext, public SharedRom {
// Tile16 blockset for selecting the tile to edit
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000),
gui::CanvasGridSize::k32x32};
gfx::Bitmap* tile16_blockset_bmp_;
gfx::Bitmap tile16_blockset_bmp_;
// Canvas for editing the selected tile
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", ImVec2(0x40, 0x40),
gui::CanvasGridSize::k64x64};
gfx::Bitmap* current_tile16_bmp_;
gfx::Bitmap current_tile16_bmp_;
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
gui::Canvas tile8_source_canvas_{
"Tile8SourceCanvas",
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
ImVec2(gfx::kTilesheetWidth * 4, gfx::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k32x32};
gfx::Bitmap current_gfx_bmp_;
gui::Canvas transfer_canvas_;
gfx::Bitmap transfer_blockset_bmp_;
std::vector<Bytes> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_;
std::vector<gfx::Bitmap> current_gfx_individual_;
std::vector<uint8_t> current_tile16_data_;
std::vector<uint8_t> tile8_gfx_data_;
PaletteEditor palette_editor_;
gfx::SnesPalette palette_;
zelda3::overworld::Overworld transfer_overworld_;
gfx::BitmapTable graphics_bin_;
absl::Status status_;
Rom transfer_rom_;
absl::Status transfer_status_;
@@ -115,4 +102,4 @@ class Tile16Editor : public context::GfxContext, public SharedRom {
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_TILE16EDITOR_H
#endif // YAZE_APP_EDITOR_TILE16EDITOR_H

View File

@@ -1,10 +0,0 @@
#include "master_editor.h"
namespace yaze {
namespace app {
namespace editor {
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,182 @@
#include "message_data.h"
#include "app/core/common.h"
namespace yaze {
namespace app {
namespace editor {
uint8_t FindMatchingCharacter(char value) {
for (const auto [key, char_value] : CharEncoder) {
if (value == char_value) {
return key;
}
}
return 0xFF;
}
uint8_t FindDictionaryEntry(uint8_t value) {
if (value < DICTOFF || value == 0xFF) {
return -1;
}
return value - DICTOFF;
}
TextElement FindMatchingCommand(uint8_t b) {
TextElement empty_element;
for (const auto& text_element : TextCommands) {
if (text_element.ID == b) {
return text_element;
}
}
return empty_element;
}
TextElement FindMatchingSpecial(uint8_t value) {
auto it = std::find_if(SpecialChars.begin(), SpecialChars.end(),
[value](const TextElement& text_element) {
return text_element.ID == value;
});
if (it != SpecialChars.end()) {
return *it;
}
return TextElement();
}
ParsedElement FindMatchingElement(const std::string& str) {
std::smatch match;
for (auto& textElement : TextCommands) {
match = textElement.MatchMe(str);
if (match.size() > 0) {
if (textElement.HasArgument) {
return ParsedElement(textElement,
std::stoi(match[1].str(), nullptr, 16));
} else {
return ParsedElement(textElement, 0);
}
}
}
const auto dictionary_element =
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
match = dictionary_element.MatchMe(str);
if (match.size() > 0) {
return ParsedElement(dictionary_element,
DICTOFF + std::stoi(match[1].str(), nullptr, 16));
}
return ParsedElement();
}
std::string ParseTextDataByte(uint8_t value) {
if (CharEncoder.contains(value)) {
char c = CharEncoder.at(value);
std::string str = "";
str.push_back(c);
return str;
}
// Check for command.
TextElement textElement = FindMatchingCommand(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
}
// Check for special characters.
textElement = FindMatchingSpecial(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(value);
if (dictionary >= 0) {
return absl::StrFormat("[%s:%X]", DICTIONARYTOKEN, dictionary);
}
return "";
}
std::vector<uint8_t> ParseMessageToData(std::string str) {
std::vector<uint8_t> bytes;
std::string temp_string = str;
int pos = 0;
while (pos < temp_string.size()) {
// Get next text fragment.
if (temp_string[pos] == '[') {
int next = temp_string.find(']', pos);
if (next == -1) {
break;
}
ParsedElement parsedElement =
FindMatchingElement(temp_string.substr(pos, next - pos + 1));
const auto dictionary_element =
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
if (!parsedElement.Active) {
core::logf("Error parsing message: %s", temp_string);
break;
} else if (parsedElement.Parent == dictionary_element) {
bytes.push_back(parsedElement.Value);
} else {
bytes.push_back(parsedElement.Parent.ID);
if (parsedElement.Parent.HasArgument) {
bytes.push_back(parsedElement.Value);
}
}
pos = next + 1;
continue;
} else {
uint8_t bb = FindMatchingCharacter(temp_string[pos++]);
if (bb != 0xFF) {
core::logf("Error parsing message: %s", temp_string);
bytes.push_back(bb);
}
}
}
return bytes;
}
std::vector<DictionaryEntry> BuildDictionaryEntries(app::Rom* rom) {
std::vector<DictionaryEntry> AllDictionaries;
for (int i = 0; i < kNumDictionaryEntries; i++) {
std::vector<uint8_t> bytes;
std::stringstream stringBuilder;
int address = core::SnesToPc(
kTextData + (rom->data()[kPointersDictionaries + (i * 2) + 1] << 8) +
rom->data()[kPointersDictionaries + (i * 2)]);
int temppush_backress = core::SnesToPc(
kTextData +
(rom->data()[kPointersDictionaries + ((i + 1) * 2) + 1] << 8) +
rom->data()[kPointersDictionaries + ((i + 1) * 2)]);
while (address < temppush_backress) {
uint8_t uint8_tDictionary = rom->data()[address++];
bytes.push_back(uint8_tDictionary);
stringBuilder << ParseTextDataByte(uint8_tDictionary);
}
AllDictionaries.push_back(DictionaryEntry{(uint8_t)i, stringBuilder.str()});
}
std::sort(AllDictionaries.begin(), AllDictionaries.end(),
[](const DictionaryEntry& a, const DictionaryEntry& b) {
return a.Contents.size() > b.Contents.size();
});
return AllDictionaries;
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,23 +1,90 @@
#ifndef YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
#define YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
#include <regex>
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
const uint8_t MESSAGETERMINATOR = 0x7F;
const uint8_t kMessageTerminator = 0x7F;
const std::string BANKToken = "BANK";
const std::string DICTIONARYTOKEN = "D";
constexpr uint8_t DICTOFF = 0x88;
static std::string AddNewLinesToCommands(std::string str);
static std::string ReplaceAllDictionaryWords(std::string str);
static std::vector<uint8_t> ParseMessageToData(std::string str);
static const std::unordered_map<uint8_t, wchar_t> CharEncoder = {
{0x00, 'A'}, {0x01, 'B'}, {0x02, 'C'}, {0x03, 'D'}, {0x04, 'E'},
{0x05, 'F'}, {0x06, 'G'}, {0x07, 'H'}, {0x08, 'I'}, {0x09, 'J'},
{0x0A, 'K'}, {0x0B, 'L'}, {0x0C, 'M'}, {0x0D, 'N'}, {0x0E, 'O'},
{0x0F, 'P'}, {0x10, 'Q'}, {0x11, 'R'}, {0x12, 'S'}, {0x13, 'T'},
{0x14, 'U'}, {0x15, 'V'}, {0x16, 'W'}, {0x17, 'X'}, {0x18, 'Y'},
{0x19, 'Z'}, {0x1A, 'a'}, {0x1B, 'b'}, {0x1C, 'c'}, {0x1D, 'd'},
{0x1E, 'e'}, {0x1F, 'f'}, {0x20, 'g'}, {0x21, 'h'}, {0x22, 'i'},
{0x23, 'j'}, {0x24, 'k'}, {0x25, 'l'}, {0x26, 'm'}, {0x27, 'n'},
{0x28, 'o'}, {0x29, 'p'}, {0x2A, 'q'}, {0x2B, 'r'}, {0x2C, 's'},
{0x2D, 't'}, {0x2E, 'u'}, {0x2F, 'v'}, {0x30, 'w'}, {0x31, 'x'},
{0x32, 'y'}, {0x33, 'z'}, {0x34, '0'}, {0x35, '1'}, {0x36, '2'},
{0x37, '3'}, {0x38, '4'}, {0x39, '5'}, {0x3A, '6'}, {0x3B, '7'},
{0x3C, '8'}, {0x3D, '9'}, {0x3E, '!'}, {0x3F, '?'}, {0x40, '-'},
{0x41, '.'}, {0x42, ','}, {0x44, '>'}, {0x45, '('}, {0x46, ')'},
{0x4C, '"'}, {0x51, '\''}, {0x59, ' '}, {0x5A, '<'}, {0x5F, L'¡'},
{0x60, L'¡'}, {0x61, L'¡'}, {0x62, L' '}, {0x63, L' '}, {0x64, L' '},
{0x65, ' '}, {0x66, '_'},
};
const std::string CHEESE = "\uBEBE"; // Inserted into commands to protect
// them from dictionary replacements.
uint8_t FindMatchingCharacter(char value);
uint8_t FindDictionaryEntry(uint8_t value);
std::vector<uint8_t> ParseMessageToData(std::string str);
struct DictionaryEntry {
uint8_t ID;
std::string Contents;
std::vector<uint8_t> Data;
int Length;
std::string Token;
DictionaryEntry() = default;
DictionaryEntry(uint8_t i, std::string s)
: Contents(s), ID(i), Length(s.length()) {
Token = absl::StrFormat("[%s:%00X]", DICTIONARYTOKEN, ID);
Data = ParseMessageToData(Contents);
}
bool ContainedInString(std::string s) {
return s.find(Contents) != std::string::npos;
}
std::string ReplaceInstancesOfIn(std::string s) {
std::string replacedString = s;
size_t pos = replacedString.find(Contents);
while (pos != std::string::npos) {
replacedString.replace(pos, Contents.length(), Token);
pos = replacedString.find(Contents, pos + Token.length());
}
return replacedString;
}
};
constexpr int kTextData = 0xE0000;
constexpr int kTextDataEnd = 0xE7FFF;
constexpr int kNumDictionaryEntries = 97;
constexpr int kPointersDictionaries = 0x74703;
std::vector<DictionaryEntry> BuildDictionaryEntries(app::Rom* rom);
std::string ReplaceAllDictionaryWords(std::string str,
std::vector<DictionaryEntry> dictionary);
// Inserted into commands to protect them from dictionary replacements.
const std::string CHEESE = "\uBEBE";
struct MessageData {
int ID;
@@ -49,35 +116,13 @@ struct MessageData {
ContentsParsed = other.ContentsParsed;
}
void SetMessage(std::string messageString) {
ContentsParsed = messageString;
RawString = OptimizeMessageForDictionary(messageString);
RecalculateData();
}
std::string ToString() {
return absl::StrFormat("%0X - %s", ID, ContentsParsed);
}
std::string GetReadableDumpedContents() {
std::stringstream stringBuilder;
for (const auto& b : Data) {
stringBuilder << absl::StrFormat("%0X ", b);
}
stringBuilder << absl::StrFormat("%00X", MESSAGETERMINATOR);
return absl::StrFormat(
"[[[[\r\nMessage "
"%000X]]]]\r\n[Contents]\r\n%s\r\n\r\n[Data]\r\n%s"
"\r\n\r\n\r\n\r\n",
ID, AddNewLinesToCommands(ContentsParsed), stringBuilder.str());
}
std::string GetDumpedContents() {
return absl::StrFormat("%000X : %s\r\n\r\n", ID, ContentsParsed);
}
std::string OptimizeMessageForDictionary(std::string messageString) {
std::string OptimizeMessageForDictionary(
std::string messageString,
const std::vector<DictionaryEntry>& dictionary) {
std::stringstream protons;
bool command = false;
for (const auto& c : messageString) {
@@ -94,16 +139,18 @@ struct MessageData {
}
std::string protonsString = protons.str();
std::string replacedString = ReplaceAllDictionaryWords(protonsString);
std::string replacedString =
ReplaceAllDictionaryWords(protonsString, dictionary);
std::string finalString =
absl::StrReplaceAll(replacedString, {{CHEESE, ""}});
return finalString;
}
void RecalculateData() {
Data = ParseMessageToData(RawString);
DataParsed = ParseMessageToData(ContentsParsed);
void SetMessage(const std::string& message,
const std::vector<DictionaryEntry>& dictionary) {
RawString = message;
ContentsParsed = OptimizeMessageForDictionary(message, dictionary);
}
};
@@ -155,8 +202,64 @@ struct TextElement {
}
bool Empty() { return ID == 0; }
// Comparison operator
bool operator==(const TextElement& other) const { return ID == other.ID; }
};
static const std::vector<TextElement> TextCommands = {
TextElement(0x6B, "W", true, "Window border"),
TextElement(0x6D, "P", true, "Window position"),
TextElement(0x6E, "SPD", true, "Scroll speed"),
TextElement(0x7A, "S", true, "Text draw speed"),
TextElement(0x77, "C", true, "Text color"),
TextElement(0x6A, "L", false, "Player name"),
TextElement(0x74, "1", false, "Line 1"),
TextElement(0x75, "2", false, "Line 2"),
TextElement(0x76, "3", false, "Line 3"),
TextElement(0x7E, "K", false, "Wait for key"),
TextElement(0x73, "V", false, "Scroll text"),
TextElement(0x78, "WT", true, "Delay X"),
TextElement(0x6C, "N", true, "BCD number"),
TextElement(0x79, "SFX", true, "Sound effect"),
TextElement(0x71, "CH3", false, "Choose 3"),
TextElement(0x72, "CH2", false, "Choose 2 high"),
TextElement(0x6F, "CH2L", false, "Choose 2 low"),
TextElement(0x68, "CH2I", false, "Choose 2 indented"),
TextElement(0x69, "CHI", false, "Choose item"),
TextElement(0x67, "IMG", false, "Next attract image"),
TextElement(0x80, BANKToken, false, "Bank marker (automatic)"),
TextElement(0x70, "NONO", false, "Crash"),
};
TextElement FindMatchingCommand(uint8_t b);
static const std::vector<TextElement> SpecialChars = {
TextElement(0x43, "...", false, "Ellipsis …"),
TextElement(0x4D, "UP", false, "Arrow ↑"),
TextElement(0x4E, "DOWN", false, "Arrow ↓"),
TextElement(0x4F, "LEFT", false, "Arrow ←"),
TextElement(0x50, "RIGHT", false, "Arrow →"),
TextElement(0x5B, "A", false, "Button Ⓐ"),
TextElement(0x5C, "B", false, "Button Ⓑ"),
TextElement(0x5D, "X", false, "Button ⓧ"),
TextElement(0x5E, "Y", false, "Button ⓨ"),
TextElement(0x52, "HP1L", false, "1 HP left"),
TextElement(0x53, "HP1R", false, "1 HP right"),
TextElement(0x54, "HP2L", false, "2 HP left"),
TextElement(0x55, "HP3L", false, "3 HP left"),
TextElement(0x56, "HP3R", false, "3 HP right"),
TextElement(0x57, "HP4L", false, "4 HP left"),
TextElement(0x58, "HP4R", false, "4 HP right"),
TextElement(0x47, "HY0", false, "Hieroglyph ☥"),
TextElement(0x48, "HY1", false, "Hieroglyph 𓈗"),
TextElement(0x49, "HY2", false, "Hieroglyph Ƨ"),
TextElement(0x4A, "LFL", false, "Link face left"),
TextElement(0x4B, "LFR", false, "Link face right"),
};
TextElement FindMatchingSpecial(uint8_t b);
struct ParsedElement {
TextElement Parent;
uint8_t Value;
@@ -170,8 +273,12 @@ struct ParsedElement {
}
};
ParsedElement FindMatchingElement(const std::string& str);
std::string ParseTextDataByte(uint8_t value);
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
#endif // YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H

View File

@@ -1,7 +1,5 @@
#include "message_editor.h"
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
@@ -9,26 +7,24 @@
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "app/core/common.h"
#include "app/editor/utils/editor.h"
#include "app/core/platform/renderer.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "imgui.h"
namespace yaze {
namespace app {
namespace editor {
using ImGui::Begin;
using core::Renderer;
using ImGui::BeginChild;
using ImGui::BeginTable;
using ImGui::Button;
using ImGui::End;
using ImGui::EndChild;
using ImGui::EndTable;
using ImGui::InputText;
@@ -37,216 +33,24 @@ using ImGui::SameLine;
using ImGui::Separator;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
using ImGui::Text;
using ImGui::TextWrapped;
using ImGui::TreeNode;
static ParsedElement FindMatchingElement(string str) {
std::smatch match;
for (auto& textElement : TextCommands) {
match = textElement.MatchMe(str);
if (match.size() > 0) {
if (textElement.HasArgument) {
return ParsedElement(textElement,
std::stoi(match[1].str(), nullptr, 16));
} else {
return ParsedElement(textElement, 0);
}
}
}
constexpr ImGuiTableFlags kMessageTableFlags = ImGuiTableFlags_Hideable |
ImGuiTableFlags_Borders |
ImGuiTableFlags_Resizable;
match = DictionaryElement.MatchMe(str);
if (match.size() > 0) {
return ParsedElement(DictionaryElement,
DICTOFF + std::stoi(match[1].str(), nullptr, 16));
}
return ParsedElement();
}
static string ReplaceAllDictionaryWords(string str) {
string temp = str;
for (const auto& entry : AllDictionaries) {
if (absl::StrContains(temp, entry.Contents)) {
temp = absl::StrReplaceAll(temp, {{entry.Contents, entry.Contents}});
}
}
return temp;
}
static std::vector<uint8_t> ParseMessageToData(string str) {
std::vector<uint8_t> bytes;
string tempString = str;
int pos = 0;
while (pos < tempString.size()) {
// Get next text fragment.
if (tempString[pos] == '[') {
int next = tempString.find(']', pos);
if (next == -1) {
break;
}
ParsedElement parsedElement =
FindMatchingElement(tempString.substr(pos, next - pos + 1));
if (!parsedElement.Active) {
break; // TODO: handle badness.
// } else if (parsedElement.Parent == DictionaryElement) {
// bytes.push_back(parsedElement.Value);
} else {
bytes.push_back(parsedElement.Parent.ID);
if (parsedElement.Parent.HasArgument) {
bytes.push_back(parsedElement.Value);
}
}
pos = next + 1;
continue;
} else {
uint8_t bb = MessageEditor::FindMatchingCharacter(tempString[pos++]);
if (bb != 0xFF) {
// TODO: handle badness.
bytes.push_back(bb);
}
}
}
return bytes;
}
absl::Status MessageEditor::Update() {
if (rom()->is_loaded() && !data_loaded_) {
RETURN_IF_ERROR(Initialize());
CurrentMessage = ListOfTexts[1];
data_loaded_ = true;
}
if (BeginTable("##MessageEditor", 3,
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) {
TableSetupColumn("List");
TableSetupColumn("Contents");
TableSetupColumn("Commands");
TableHeadersRow();
TableNextColumn();
DrawMessageList();
TableNextColumn();
DrawCurrentMessage();
TableNextColumn();
DrawTextCommands();
EndTable();
}
return absl::OkStatus();
}
void MessageEditor::DrawMessageList() {
if (InputText("Search", &search_text_)) {
DisplayedMessages.clear();
for (const auto& message : ListOfTexts) {
if (absl::StrContains(message.ContentsParsed, search_text_)) {
DisplayedMessages.push_back(message);
}
}
}
if (BeginChild("##MessagesList", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
if (BeginTable("##MessagesTable", 3,
ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders |
ImGuiTableFlags_Resizable)) {
TableSetupColumn("ID");
TableSetupColumn("Contents");
TableSetupColumn("Data");
TableHeadersRow();
for (const auto& message : ListOfTexts) {
TableNextColumn();
if (Button(core::UppercaseHexWord(message.ID).c_str())) {
CurrentMessage = message;
DrawMessagePreview();
}
TableNextColumn();
TextWrapped("%s", ParsedMessages[message.ID].c_str());
TableNextColumn();
TextWrapped(
"%s",
core::UppercaseHexLong(ListOfTexts[message.ID].Address).c_str());
}
EndTable();
}
EndChild();
}
}
void MessageEditor::DrawCurrentMessage() {
Button(absl::StrCat("Message ", CurrentMessage.ID).c_str());
if (InputTextMultiline("##MessageEditor", &ParsedMessages[CurrentMessage.ID],
ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
CurrentMessage.Data = ParseMessageToData(message_text_box_.text);
DrawMessagePreview();
}
Separator();
Text("Font Graphics");
gui::BeginPadding(1);
BeginChild("MessageEditorCanvas", ImVec2(0, 130));
font_gfx_canvas_.DrawBackground();
font_gfx_canvas_.DrawContextMenu();
font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0);
font_gfx_canvas_.DrawGrid();
font_gfx_canvas_.DrawOverlay();
EndChild();
gui::EndPadding();
Separator();
Text("Message Preview");
if (Button("Refresh Bitmap")) {
rom()->UpdateBitmap(&current_font_gfx16_bitmap_);
}
gui::BeginPadding(1);
BeginChild("CurrentGfxFont", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
current_font_gfx16_canvas_.DrawBackground();
gui::EndPadding();
current_font_gfx16_canvas_.DrawContextMenu();
current_font_gfx16_canvas_.DrawBitmap(current_font_gfx16_bitmap_, 0, 0);
current_font_gfx16_canvas_.DrawGrid();
current_font_gfx16_canvas_.DrawOverlay();
EndChild();
}
void MessageEditor::DrawTextCommands() {
if (BeginChild("##TextCommands", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
for (const auto& text_element : TextCommands) {
if (Button(text_element.GenericToken.c_str())) {
}
SameLine();
TextWrapped("%s", text_element.Description.c_str());
Separator();
}
EndChild();
}
}
constexpr ImGuiTableFlags kDictTableFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
absl::Status MessageEditor::Initialize() {
for (int i = 0; i < 100; i++) {
for (int i = 0; i < kWidthArraySize; i++) {
width_array[i] = rom()->data()[kCharactersWidth + i];
}
BuildDictionaryEntries();
ReadAllTextData();
all_dictionaries_ = BuildDictionaryEntries(rom());
ReadAllTextDataV2();
font_preview_colors_.AddColor(0x7FFF); // White
font_preview_colors_.AddColor(0x7C00); // Red
@@ -257,21 +61,23 @@ absl::Status MessageEditor::Initialize() {
for (int i = 0; i < 0x4000; i++) {
data[i] = rom()->data()[kGfxFont + i];
}
font_gfx16_data = gfx::SnesTo8bppSheet(data, /*bpp=*/2);
font_gfx16_data_ = gfx::SnesTo8bppSheet(data, /*bpp=*/2, /*num_sheets=*/2);
// 4bpp
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
128, 128, 8, font_gfx16_data, font_gfx_bitmap_, font_preview_colors_))
RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
kFontGfxMessageSize, kFontGfxMessageSize, kFontGfxMessageDepth,
font_gfx16_data_, font_gfx_bitmap_, font_preview_colors_))
current_font_gfx16_data_.reserve(172 * 4096);
for (int i = 0; i < 172 * 4096; i++) {
current_font_gfx16_data_.reserve(kCurrentMessageWidth *
kCurrentMessageHeight);
for (int i = 0; i < kCurrentMessageWidth * kCurrentMessageHeight; i++) {
current_font_gfx16_data_.push_back(0);
}
// 8bpp
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
172, 4096, 64, current_font_gfx16_data_, current_font_gfx16_bitmap_,
font_preview_colors_))
RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
kCurrentMessageWidth, kCurrentMessageHeight, 64, current_font_gfx16_data_,
current_font_gfx16_bitmap_, font_preview_colors_))
gfx::SnesPalette color_palette = font_gfx_bitmap_.palette();
for (int i = 0; i < font_preview_colors_.size(); i++) {
@@ -280,11 +86,11 @@ absl::Status MessageEditor::Initialize() {
*font_gfx_bitmap_.mutable_palette() = color_palette;
for (const auto& message : ListOfTexts) {
DisplayedMessages.push_back(message);
}
for (const auto& each_message : list_of_texts_) {
std::cout << "Message #" << each_message.ID << " at address "
<< core::UppercaseHexLong(each_message.Address) << std::endl;
std::cout << " " << each_message.RawString << std::endl;
for (const auto& each_message : ListOfTexts) {
// Each string has a [:XX] char encoded
// The corresponding character is found in CharEncoder unordered_map
std::string parsed_message = "";
@@ -313,7 +119,8 @@ absl::Status MessageEditor::Initialize() {
}
}
}
ParsedMessages.push_back(parsed_message);
std::cout << " > " << parsed_message << std::endl;
parsed_messages_.push_back(parsed_message);
}
DrawMessagePreview();
@@ -321,38 +128,245 @@ absl::Status MessageEditor::Initialize() {
return absl::OkStatus();
}
void MessageEditor::BuildDictionaryEntries() {
for (int i = 0; i < 97; i++) {
std::vector<uint8_t> bytes;
std::stringstream stringBuilder;
int address = core::SnesToPc(
0x0E0000 + (rom()->data()[kPointersDictionaries + (i * 2) + 1] << 8) +
rom()->data()[kPointersDictionaries + (i * 2)]);
int temppush_backress = core::SnesToPc(
0x0E0000 +
(rom()->data()[kPointersDictionaries + ((i + 1) * 2) + 1] << 8) +
rom()->data()[kPointersDictionaries + ((i + 1) * 2)]);
while (address < temppush_backress) {
uint8_t uint8_tDictionary = rom()->data()[address++];
bytes.push_back(uint8_tDictionary);
stringBuilder << ParseTextDataByte(uint8_tDictionary);
}
// AllDictionaries[i] = DictionaryEntry{(uint8_t)i, stringBuilder.str()};
AllDictionaries.push_back(DictionaryEntry{(uint8_t)i, stringBuilder.str()});
absl::Status MessageEditor::Update() {
if (rom()->is_loaded() && !data_loaded_) {
RETURN_IF_ERROR(Initialize());
current_message_ = list_of_texts_[1];
data_loaded_ = true;
}
// AllDictionaries.OrderByDescending(dictionary = > dictionary.Length);
AllDictionaries[0].Length = 0;
if (BeginTable("##MessageEditor", 4, kDictTableFlags)) {
TableSetupColumn("List");
TableSetupColumn("Contents");
TableSetupColumn("Commands");
TableSetupColumn("Dictionary");
TableHeadersRow();
TableNextColumn();
DrawMessageList();
TableNextColumn();
DrawCurrentMessage();
TableNextColumn();
DrawTextCommands();
TableNextColumn();
DrawDictionary();
EndTable();
}
return absl::OkStatus();
}
void MessageEditor::DrawMessageList() {
if (InputText("Search", &search_text_)) {
// TODO: ImGui style text filtering
}
if (BeginChild("##MessagesList", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
if (BeginTable("##MessagesTable", 3, kMessageTableFlags)) {
TableSetupColumn("ID");
TableSetupColumn("Contents");
TableSetupColumn("Data");
TableHeadersRow();
for (const auto& message : list_of_texts_) {
TableNextColumn();
if (Button(core::UppercaseHexWord(message.ID).c_str())) {
current_message_ = message;
DrawMessagePreview();
}
TableNextColumn();
TextWrapped("%s", parsed_messages_[message.ID].c_str());
TableNextColumn();
TextWrapped(
"%s",
core::UppercaseHexLong(list_of_texts_[message.ID].Address).c_str());
}
EndTable();
}
EndChild();
}
}
void MessageEditor::DrawCurrentMessage() {
Button(absl::StrCat("Message ", current_message_.ID).c_str());
if (InputTextMultiline("##MessageEditor",
&parsed_messages_[current_message_.ID],
ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
current_message_.Data = ParseMessageToData(message_text_box_.text);
DrawMessagePreview();
}
Separator();
Text("Font Graphics");
gui::BeginPadding(1);
BeginChild("MessageEditorCanvas", ImVec2(0, 130));
font_gfx_canvas_.DrawBackground();
font_gfx_canvas_.DrawContextMenu();
font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0);
font_gfx_canvas_.DrawGrid();
font_gfx_canvas_.DrawOverlay();
EndChild();
gui::EndPadding();
Separator();
Text("Message Preview");
if (Button("Refresh Bitmap")) {
Renderer::GetInstance().UpdateBitmap(&current_font_gfx16_bitmap_);
}
gui::BeginPadding(1);
BeginChild("CurrentGfxFont", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
current_font_gfx16_canvas_.DrawBackground();
gui::EndPadding();
current_font_gfx16_canvas_.DrawContextMenu();
current_font_gfx16_canvas_.DrawBitmap(current_font_gfx16_bitmap_, 0, 0);
current_font_gfx16_canvas_.DrawGrid();
current_font_gfx16_canvas_.DrawOverlay();
EndChild();
}
void MessageEditor::DrawTextCommands() {
if (BeginChild("##TextCommands", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
for (const auto& text_element : TextCommands) {
if (Button(text_element.GenericToken.c_str())) {
}
SameLine();
TextWrapped("%s", text_element.Description.c_str());
Separator();
}
EndChild();
}
}
void MessageEditor::DrawDictionary() {
if (ImGui::BeginChild("##DictionaryChild", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
if (BeginTable("##Dictionary", 2, kDictTableFlags)) {
TableSetupColumn("ID");
TableSetupColumn("Contents");
for (const auto& dictionary : all_dictionaries_) {
TableNextColumn();
Text("%s", core::UppercaseHexWord(dictionary.ID).c_str());
TableNextColumn();
Text("%s", dictionary.Contents.c_str());
}
EndTable();
}
EndChild();
}
}
// TODO: Fix the command parsing.
void MessageEditor::ReadAllTextDataV2() {
// Read all text data from the ROM.
int pos = kTextData;
int message_id = 0;
std::vector<uint8_t> raw_message;
std::vector<uint8_t> parsed_message;
std::string current_raw_message;
std::string current_parsed_message;
uint8_t current_byte = 0;
while (current_byte != 0xFF) {
current_byte = rom()->data()[pos++];
if (current_byte == kMessageTerminator) {
auto message =
MessageData(message_id++, pos, current_raw_message, raw_message,
current_parsed_message, parsed_message);
list_of_texts_.push_back(message);
raw_message.clear();
parsed_message.clear();
current_raw_message.clear();
current_parsed_message.clear();
continue;
}
raw_message.push_back(current_byte);
// Check for command.
TextElement text_element = FindMatchingCommand(current_byte);
if (!text_element.Empty()) {
parsed_message.push_back(current_byte);
if (text_element.HasArgument) {
current_byte = rom()->data()[pos++];
raw_message.push_back(current_byte);
parsed_message.push_back(current_byte);
}
current_raw_message.append(
text_element.GetParameterizedToken(current_byte));
current_parsed_message.append(
text_element.GetParameterizedToken(current_byte));
if (text_element.Token == BANKToken) {
pos = kTextData2;
}
continue;
}
// Check for special characters.
text_element = FindMatchingSpecial(current_byte);
if (!text_element.Empty()) {
current_raw_message.append(text_element.GetParameterizedToken());
current_parsed_message.append(text_element.GetParameterizedToken());
parsed_message.push_back(current_byte);
continue;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(current_byte);
if (dictionary >= 0) {
current_raw_message.append("[");
current_raw_message.append(DICTIONARYTOKEN);
current_raw_message.append(":");
current_raw_message.append(core::UppercaseHexWord(dictionary));
current_raw_message.append("]");
uint32_t address = core::Get24LocalFromPC(
rom()->data(), kPointersDictionaries + (dictionary * 2));
uint32_t address_end = core::Get24LocalFromPC(
rom()->data(), kPointersDictionaries + ((dictionary + 1) * 2));
for (uint32_t i = address; i < address_end; i++) {
parsed_message.push_back(rom()->data()[i]);
current_parsed_message.append(ParseTextDataByte(rom()->data()[i]));
}
continue;
}
// Everything else.
if (CharEncoder.contains(current_byte)) {
std::string str = "";
str.push_back(CharEncoder.at(current_byte));
current_raw_message.append(str);
current_parsed_message.append(str);
parsed_message.push_back(current_byte);
}
}
}
void MessageEditor::ReadAllTextData() {
int messageID = 0;
uint8_t current_byte;
int pos = kTextData;
int message_id = 0;
uint8_t current_byte;
std::vector<uint8_t> temp_bytes_raw;
std::vector<uint8_t> temp_bytes_parsed;
@@ -363,12 +377,12 @@ void MessageEditor::ReadAllTextData() {
while (true) {
current_byte = rom()->data()[pos++];
if (current_byte == MESSAGETERMINATOR) {
if (current_byte == kMessageTerminator) {
auto message =
MessageData(messageID++, pos, current_message_raw, temp_bytes_raw,
MessageData(message_id++, pos, current_message_raw, temp_bytes_raw,
current_message_parsed, temp_bytes_parsed);
ListOfTexts.push_back(message);
list_of_texts_.push_back(message);
temp_bytes_raw.clear();
temp_bytes_parsed.clear();
@@ -407,7 +421,6 @@ void MessageEditor::ReadAllTextData() {
// Check for special characters.
text_element = FindMatchingSpecial(current_byte);
if (!text_element.Empty()) {
current_message_raw.append(text_element.GetParameterizedToken());
current_message_parsed.append(text_element.GetParameterizedToken());
@@ -449,83 +462,29 @@ void MessageEditor::ReadAllTextData() {
}
}
TextElement MessageEditor::FindMatchingCommand(uint8_t b) {
TextElement empty_element;
for (const auto text_element : TextCommands) {
if (text_element.ID == b) {
return text_element;
std::string ReplaceAllDictionaryWords(std::string str,
std::vector<DictionaryEntry> dictionary) {
std::string temp = str;
for (const auto& entry : dictionary) {
if (absl::StrContains(temp, entry.Contents)) {
temp = absl::StrReplaceAll(temp, {{entry.Contents, entry.Contents}});
}
}
return empty_element;
return temp;
}
TextElement MessageEditor::FindMatchingSpecial(uint8_t value) {
TextElement empty_element;
for (const auto text_element : SpecialChars) {
if (text_element.ID == value) {
return text_element;
}
}
return empty_element;
}
MessageEditor::DictionaryEntry MessageEditor::GetDictionaryFromID(
uint8_t value) {
if (value < 0 || value >= AllDictionaries.size()) {
DictionaryEntry MessageEditor::GetDictionaryFromID(uint8_t value) {
if (value < 0 || value >= all_dictionaries_.size()) {
return DictionaryEntry();
}
return AllDictionaries[value];
}
uint8_t MessageEditor::FindDictionaryEntry(uint8_t value) {
if (value < DICTOFF || value == 0xFF) {
return -1;
}
return value - DICTOFF;
}
uint8_t MessageEditor::FindMatchingCharacter(char value) {
for (const auto [key, char_value] : CharEncoder) {
if (value == char_value) {
return key;
}
}
return 0xFF;
}
string MessageEditor::ParseTextDataByte(uint8_t value) {
if (CharEncoder.contains(value)) {
char c = CharEncoder.at(value);
string str = "";
str.push_back(c);
return str;
}
// Check for command.
TextElement textElement = FindMatchingCommand(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
}
// Check for special characters.
textElement = FindMatchingSpecial(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(value);
if (dictionary >= 0) {
return absl::StrFormat("[%s:%X]", DICTIONARYTOKEN, dictionary);
}
return "";
return all_dictionaries_[value];
}
void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
int sizex, int sizey) {
int drawid = srcx + (srcy * 32);
const int num_x_tiles = 16;
const int img_width = 512; // (imgwidth/2)
int draw_id = srcx + (srcy * 32);
for (int yl = 0; yl < sizey * 8; yl++) {
for (int xl = 0; xl < 4; xl++) {
int mx = xl;
@@ -533,8 +492,9 @@ void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
// Formula information to get tile index position in the array.
// ((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
int tx = ((drawid / 16) * 512) + ((drawid - ((drawid / 16) * 16)) * 4);
uint8_t pixel = font_gfx16_data[tx + (yl * 64) + xl];
int tx = ((draw_id / num_x_tiles) * img_width) +
((draw_id - ((draw_id / 16) * 16)) * 4);
uint8_t pixel = font_gfx16_data_[tx + (yl * 64) + xl];
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
// position
@@ -552,7 +512,7 @@ void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
}
}
void MessageEditor::DrawStringToPreview(string str) {
void MessageEditor::DrawStringToPreview(std::string str) {
for (const auto c : str) {
DrawCharacterToPreview(c);
}
@@ -573,25 +533,25 @@ void MessageEditor::DrawCharacterToPreview(const std::vector<uint8_t>& text) {
int srcy = value / 16;
int srcx = value - (value & (~0xF));
if (text_pos >= 170) {
text_pos = 0;
text_line++;
if (text_position_ >= 170) {
text_position_ = 0;
text_line_++;
}
DrawTileToPreview(text_pos, text_line * 16, srcx, srcy, 0, 1, 2);
text_pos += width_array[value];
DrawTileToPreview(text_position_, text_line_ * 16, srcx, srcy, 0, 1, 2);
text_position_ += width_array[value];
} else if (value == kLine1) {
text_pos = 0;
text_line = 0;
text_position_ = 0;
text_line_ = 0;
} else if (value == kScrollVertical) {
text_pos = 0;
text_line += 1;
text_position_ = 0;
text_line_ += 1;
} else if (value == kLine2) {
text_pos = 0;
text_line = 1;
text_position_ = 0;
text_line_ = 1;
} else if (value == kLine3) {
text_pos = 0;
text_line = 2;
text_position_ = 0;
text_line_ = 2;
} else if (value == 0x6B || value == 0x6D || value == 0x6E ||
value == 0x77 || value == 0x78 || value == 0x79 ||
value == 0x7A) {
@@ -615,15 +575,15 @@ void MessageEditor::DrawCharacterToPreview(const std::vector<uint8_t>& text) {
}
}
void MessageEditor::DrawMessagePreview() // From Parsing.
{
text_line = 0;
void MessageEditor::DrawMessagePreview() {
// From Parsing.
text_line_ = 0;
for (int i = 0; i < (172 * 4096); i++) {
current_font_gfx16_data_[i] = 0;
}
text_pos = 0;
DrawCharacterToPreview(CurrentMessage.Data);
shown_lines = 0;
text_position_ = 0;
DrawCharacterToPreview(current_message_.Data);
shown_lines_ = 0;
}
absl::Status MessageEditor::Cut() {
@@ -675,7 +635,7 @@ absl::Status MessageEditor::Save() {
int pos = kTextData;
bool in_second_bank = false;
for (const auto& message : ListOfTexts) {
for (const auto& message : list_of_texts_) {
for (const auto value : message.Data) {
RETURN_IF_ERROR(rom()->Write(pos, value));
@@ -695,7 +655,7 @@ absl::Status MessageEditor::Save() {
}
RETURN_IF_ERROR(
rom()->Write(pos++, MESSAGETERMINATOR)); // , true, "Terminator text"
rom()->Write(pos++, kMessageTerminator)); // , true, "Terminator text"
}
// Verify that we didn't go over the space available for the second block.
@@ -712,9 +672,10 @@ absl::Status MessageEditor::Save() {
std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
string bankSTR = bank ? "1st" : "2nd";
string posSTR = bank ? absl::StrFormat("%X4", pos & 0xFFFF)
: absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
std::string bankSTR = bank ? "1st" : "2nd";
std::string posSTR =
bank ? absl::StrFormat("%X4", pos & 0xFFFF)
: absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
std::string message = absl::StrFormat(
"There is too much text data in the %s block to save.\n"
"Available: %X4 | Used: %s",
@@ -722,30 +683,6 @@ std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
return message;
}
// push_backs a command to the text field when the push_back command button is
// pressed or the command is double clicked in the list.
void MessageEditor::InsertCommandButton_Click_1() {
// InsertSelectedText(
// TextCommands[TextCommandList.SelectedIndex].GetParameterizedToken(
// (uint8_t)ParamsBox.HexValue));
}
// push_backs a special character to the text field when the push_back command
// button is pressed or the character is double clicked in the list.
void MessageEditor::InsertSpecialButton_Click() {
// InsertSelectedText(
// SpecialChars[SpecialsList.SelectedIndex].GetParameterizedToken());
}
void MessageEditor::InsertSelectedText(string str) {
int textboxPos = message_text_box_.selection_start;
from_form = true;
// message_text_box_.Text = message_text_box_.Text.Insert(textboxPos, str);
from_form = false;
message_text_box_.selection_start = textboxPos + str.size();
message_text_box_.Focus();
}
void MessageEditor::Delete() {
// Determine if any text is selected in the TextBox control.
if (message_text_box_.selection_length == 0) {

View File

@@ -1,229 +1,54 @@
#ifndef YAZE_APP_EDITOR_MESSAGE_EDITOR_H
#define YAZE_APP_EDITOR_MESSAGE_EDITOR_H
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "app/editor/message/message_data.h"
#include "app/editor/utils/editor.h"
#include "app/editor/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
using std::string;
// TEXT EDITOR RELATED CONSTANTS
const int kGfxFont = 0x70000; // 2bpp format
const int kTextData = 0xE0000;
const int kTextDataEnd = 0xE7FFF;
const int kTextData2 = 0x75F40;
const int kTextData2End = 0x773FF;
const int kPointersDictionaries = 0x74703;
const int kCharactersWidth = 0x74ADF;
const string DICTIONARYTOKEN = "D";
const uint8_t DICTOFF = 0x88;
const string BANKToken = "BANK";
const uint8_t BANKID = 0x80;
constexpr int kGfxFont = 0x70000; // 2bpp format
constexpr int kTextData2 = 0x75F40;
constexpr int kTextData2End = 0x773FF;
constexpr int kCharactersWidth = 0x74ADF;
constexpr int kNumMessages = 396;
constexpr int kCurrentMessageWidth = 172;
constexpr int kCurrentMessageHeight = 4096;
constexpr int kFontGfxMessageSize = 128;
constexpr int kFontGfxMessageDepth = 8;
constexpr uint8_t kWidthArraySize = 100;
constexpr uint8_t kBlockTerminator = 0x80;
static std::vector<uint8_t> ParseMessageToData(string str);
static ParsedElement FindMatchingElement(string str);
constexpr uint8_t kMessageBankChangeId = 0x80;
constexpr uint8_t kScrollVertical = 0x73;
constexpr uint8_t kLine1 = 0x74;
constexpr uint8_t kLine2 = 0x75;
constexpr uint8_t kLine3 = 0x76;
static const TextElement TextCommands[] = {
TextElement(0x6B, "W", true, "Window border"),
TextElement(0x6D, "P", true, "Window position"),
TextElement(0x6E, "SPD", true, "Scroll speed"),
TextElement(0x7A, "S", true, "Text draw speed"),
TextElement(0x77, "C", true, "Text color"),
TextElement(0x6A, "L", false, "Player name"),
TextElement(0x74, "1", false, "Line 1"),
TextElement(0x75, "2", false, "Line 2"),
TextElement(0x76, "3", false, "Line 3"),
TextElement(0x7E, "K", false, "Wait for key"),
TextElement(0x73, "V", false, "Scroll text"),
TextElement(0x78, "WT", true, "Delay X"),
TextElement(0x6C, "N", true, "BCD number"),
TextElement(0x79, "SFX", true, "Sound effect"),
TextElement(0x71, "CH3", false, "Choose 3"),
TextElement(0x72, "CH2", false, "Choose 2 high"),
TextElement(0x6F, "CH2L", false, "Choose 2 low"),
TextElement(0x68, "CH2I", false, "Choose 2 indented"),
TextElement(0x69, "CHI", false, "Choose item"),
TextElement(0x67, "IMG", false, "Next attract image"),
TextElement(0x80, BANKToken, false, "Bank marker (automatic)"),
TextElement(0x70, "NONO", false, "Crash"),
};
static std::vector<TextElement> SpecialChars = {
TextElement(0x43, "...", false, "Ellipsis …"),
TextElement(0x4D, "UP", false, "Arrow ↑"),
TextElement(0x4E, "DOWN", false, "Arrow ↓"),
TextElement(0x4F, "LEFT", false, "Arrow ←"),
TextElement(0x50, "RIGHT", false, "Arrow →"),
TextElement(0x5B, "A", false, "Button Ⓐ"),
TextElement(0x5C, "B", false, "Button Ⓑ"),
TextElement(0x5D, "X", false, "Button ⓧ"),
TextElement(0x5E, "Y", false, "Button ⓨ"),
TextElement(0x52, "HP1L", false, "1 HP left"),
TextElement(0x53, "HP1R", false, "1 HP right"),
TextElement(0x54, "HP2L", false, "2 HP left"),
TextElement(0x55, "HP3L", false, "3 HP left"),
TextElement(0x56, "HP3R", false, "3 HP right"),
TextElement(0x57, "HP4L", false, "4 HP left"),
TextElement(0x58, "HP4R", false, "4 HP right"),
TextElement(0x47, "HY0", false, "Hieroglyph ☥"),
TextElement(0x48, "HY1", false, "Hieroglyph 𓈗"),
TextElement(0x49, "HY2", false, "Hieroglyph Ƨ"),
TextElement(0x4A, "LFL", false, "Link face left"),
TextElement(0x4B, "LFR", false, "Link face right"),
};
static const std::unordered_map<uint8_t, wchar_t> CharEncoder = {
{0x00, 'A'},
{0x01, 'B'},
{0x02, 'C'},
{0x03, 'D'},
{0x04, 'E'},
{0x05, 'F'},
{0x06, 'G'},
{0x07, 'H'},
{0x08, 'I'},
{0x09, 'J'},
{0x0A, 'K'},
{0x0B, 'L'},
{0x0C, 'M'},
{0x0D, 'N'},
{0x0E, 'O'},
{0x0F, 'P'},
{0x10, 'Q'},
{0x11, 'R'},
{0x12, 'S'},
{0x13, 'T'},
{0x14, 'U'},
{0x15, 'V'},
{0x16, 'W'},
{0x17, 'X'},
{0x18, 'Y'},
{0x19, 'Z'},
{0x1A, 'a'},
{0x1B, 'b'},
{0x1C, 'c'},
{0x1D, 'd'},
{0x1E, 'e'},
{0x1F, 'f'},
{0x20, 'g'},
{0x21, 'h'},
{0x22, 'i'},
{0x23, 'j'},
{0x24, 'k'},
{0x25, 'l'},
{0x26, 'm'},
{0x27, 'n'},
{0x28, 'o'},
{0x29, 'p'},
{0x2A, 'q'},
{0x2B, 'r'},
{0x2C, 's'},
{0x2D, 't'},
{0x2E, 'u'},
{0x2F, 'v'},
{0x30, 'w'},
{0x31, 'x'},
{0x32, 'y'},
{0x33, 'z'},
{0x34, '0'},
{0x35, '1'},
{0x36, '2'},
{0x37, '3'},
{0x38, '4'},
{0x39, '5'},
{0x3A, '6'},
{0x3B, '7'},
{0x3C, '8'},
{0x3D, '9'},
{0x3E, '!'},
{0x3F, '?'},
{0x40, '-'},
{0x41, '.'},
{0x42, ','},
{0x44, '>'},
{0x45, '('},
{0x46, ')'},
{0x4C, '"'},
{0x51, '\''},
{0x59, ' '},
{0x5A, '<'},
// {0x5F, '¡'}, {0x60, '¡'}, {0x61, '¡'}, {0x62, ' '}, {0x63, ' '}, {0x64,
// ' '},
{0x65, ' '},
{0x66, '_'},
};
static TextElement DictionaryElement =
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
class MessageEditor : public Editor, public SharedRom {
public:
struct DictionaryEntry {
uint8_t ID;
std::string Contents;
std::vector<uint8_t> Data;
int Length;
std::string Token;
DictionaryEntry() = default;
DictionaryEntry(uint8_t i, std::string s)
: Contents(s), ID(i), Length(s.length()) {
Token = absl::StrFormat("[%s:%00X]", DICTIONARYTOKEN, ID);
Data = ParseMessageToData(Contents);
}
bool ContainedInString(std::string s) {
return s.find(Contents) != std::string::npos;
}
std::string ReplaceInstancesOfIn(std::string s) {
std::string replacedString = s;
size_t pos = replacedString.find(Contents);
while (pos != std::string::npos) {
replacedString.replace(pos, Contents.length(), Token);
pos = replacedString.find(Contents, pos + Token.length());
}
return replacedString;
}
};
MessageEditor() { type_ = EditorType::kMessage; }
absl::Status Initialize();
absl::Status Update() override;
void DrawMessageList();
void DrawCurrentMessage();
void DrawTextCommands();
void DrawDictionary();
absl::Status Initialize();
void ReadAllTextData();
void BuildDictionaryEntries();
void ReadAllTextDataV2();
[[deprecated]] void ReadAllTextData();
absl::Status Cut() override;
absl::Status Copy() override;
@@ -238,67 +63,47 @@ class MessageEditor : public Editor, public SharedRom {
absl::Status Save();
void Delete();
void SelectAll();
// void RegisterTests(ImGuiTestEngine* e) override;
TextElement FindMatchingCommand(uint8_t byte);
TextElement FindMatchingSpecial(uint8_t value);
string ParseTextDataByte(uint8_t value);
DictionaryEntry GetDictionaryFromID(uint8_t value);
static uint8_t FindDictionaryEntry(uint8_t value);
static uint8_t FindMatchingCharacter(char value);
void DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
int sizex = 1, int sizey = 1);
void DrawCharacterToPreview(char c);
void DrawCharacterToPreview(const std::vector<uint8_t>& text);
void DrawStringToPreview(string str);
void DrawStringToPreview(std::string str);
void DrawMessagePreview();
std::string DisplayTextOverflowError(int pos, bool bank);
void InsertCommandButton_Click_1();
void InsertSpecialButton_Click();
void InsertSelectedText(string str);
static const std::vector<DictionaryEntry> AllDicts;
uint8_t width_array[100];
string romname = "";
int text_line = 0;
int text_pos = 0;
int shown_lines = 0;
int selected_tile = 0;
bool skip_next = false;
bool from_form = false;
std::vector<MessageData> ListOfTexts;
std::vector<MessageData> DisplayedMessages;
std::vector<std::string> ParsedMessages;
MessageData CurrentMessage;
private:
static const TextElement DictionaryElement;
bool skip_next = false;
bool data_loaded_ = false;
int current_message_id_ = 0;
int text_line_ = 0;
int text_position_ = 0;
int shown_lines_ = 0;
uint8_t width_array[kWidthArraySize];
std::string search_text_ = "";
std::vector<uint8_t> font_gfx16_data_;
std::vector<uint8_t> current_font_gfx16_data_;
std::vector<std::string> parsed_messages_;
std::vector<MessageData> list_of_texts_;
std::vector<DictionaryEntry> all_dictionaries_;
MessageData current_message_;
gfx::Bitmap font_gfx_bitmap_;
gfx::Bitmap current_font_gfx16_bitmap_;
gfx::SnesPalette font_preview_colors_;
gui::Canvas font_gfx_canvas_{"##FontGfxCanvas", ImVec2(128, 128)};
gui::Canvas current_font_gfx16_canvas_{"##CurrentMessageGfx",
ImVec2(172, 4096)};
gfx::Bitmap font_gfx_bitmap_;
gfx::Bitmap current_font_gfx16_bitmap_;
Bytes font_gfx16_data;
Bytes current_font_gfx16_data_;
gfx::SnesPalette font_preview_colors_;
struct TextBox {
std::string text;
std::string buffer;
@@ -357,8 +162,6 @@ class MessageEditor : public Editor, public SharedRom {
TextBox message_text_box_;
};
static std::vector<MessageEditor::DictionaryEntry> AllDictionaries;
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,7 +0,0 @@
#include "message_editor.h"
namespace yaze {
namespace app {
namespace editor {} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,19 +1,15 @@
#ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H
#define YAZE_APP_EDITOR_MUSIC_EDITOR_H
#include "imgui/imgui.h"
#include "absl/strings/str_format.h"
#include "app/editor/code/assembly_editor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/editor.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/rom.h"
#include "app/zelda3/music/tracker.h"
// #include "snes_spc/demo/demo_util.h"
// #include "snes_spc/demo/wave_writer.h"
// #include "snes_spc/snes_spc/spc.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
@@ -81,20 +77,11 @@ class MusicEditor : public SharedRom, public Editor {
zelda3::music::Tracker music_tracker_;
// Mix_Music* current_song_ = NULL;
AssemblyEditor assembly_editor_;
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
ImGuiTableFlags music_editor_flags_ = ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Reorderable;
ImGuiTableFlags channel_table_flags_ =
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable |
ImGuiTableFlags_SortMulti | ImGuiTableFlags_RowBg |
ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV |
ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY;
};
} // namespace editor

View File

@@ -1,5 +1,6 @@
#include "app/editor/overworld/entity.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/style.h"
@@ -8,7 +9,6 @@ namespace app {
namespace editor {
using ImGui::BeginChild;
using ImGui::BeginGroup;
using ImGui::Button;
using ImGui::Checkbox;
using ImGui::EndChild;
@@ -16,7 +16,9 @@ using ImGui::SameLine;
using ImGui::Selectable;
using ImGui::Text;
bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity,
constexpr float kInputFieldSize = 30.f;
bool IsMouseHoveringOverEntity(const zelda3::GameEntity &entity,
ImVec2 canvas_p0, ImVec2 scrolling) {
// Get the mouse position relative to the canvas
const ImGuiIO &io = ImGui::GetIO();
@@ -31,7 +33,7 @@ bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity,
return false;
}
void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool free_movement) {
// Get the mouse position relative to the canvas
const ImGuiIO &io = ImGui::GetIO();
@@ -51,19 +53,20 @@ void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
entity->set_y(new_y);
}
void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
void HandleEntityDragging(zelda3::GameEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool &is_dragging_entity,
zelda3::OverworldEntity *&dragged_entity,
zelda3::OverworldEntity *&current_entity,
zelda3::GameEntity *&dragged_entity,
zelda3::GameEntity *&current_entity,
bool free_movement) {
std::string entity_type = "Entity";
if (entity->type_ == zelda3::OverworldEntity::EntityType::kExit) {
if (entity->entity_type_ == zelda3::GameEntity::EntityType::kExit) {
entity_type = "Exit";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kEntrance) {
} else if (entity->entity_type_ ==
zelda3::GameEntity::EntityType::kEntrance) {
entity_type = "Entrance";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kSprite) {
} else if (entity->entity_type_ == zelda3::GameEntity::EntityType::kSprite) {
entity_type = "Sprite";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kItem) {
} else if (entity->entity_type_ == zelda3::GameEntity::EntityType::kItem) {
entity_type = "Item";
}
const auto is_hovering =
@@ -87,7 +90,7 @@ void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
} else if (is_dragging_entity && dragged_entity == entity) {
if (ImGui::BeginDragDropSource()) {
ImGui::SetDragDropPayload("ENTITY_PAYLOAD", &entity,
sizeof(zelda3::OverworldEntity));
sizeof(zelda3::GameEntity));
Text("Moving %s ID: %s", entity_type.c_str(),
core::UppercaseHexByte(entity->entity_id_).c_str());
ImGui::EndDragDropSource();
@@ -132,7 +135,7 @@ bool DrawOverworldEntrancePopup(
}
if (ImGui::BeginPopupModal("Entrance editor", NULL,
ImGuiWindowFlags_AlwaysAutoResize)) {
gui::InputHex("Map ID", &entrance.map_id_);
gui::InputHexWord("Map ID", &entrance.map_id_);
gui::InputHexByte("Entrance ID", &entrance.entrance_id_,
kInputFieldSize + 20);
gui::InputHex("X", &entrance.x_);
@@ -209,7 +212,7 @@ bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit) {
gui::InputHexWord("Room", &exit.room_id_);
SameLine();
gui::InputHex("Entity ID", &exit.entity_id_, 4);
gui::InputHex("Map", &exit.map_id_);
gui::InputHexWord("Map", &exit.map_id_);
SameLine();
Checkbox("Automatic", &exit.is_automatic_);
@@ -310,11 +313,11 @@ bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit) {
void DrawItemInsertPopup() {
// Contents of the Context Menu
if (ImGui::BeginPopup("Item Inserter")) {
static int new_item_id = 0;
static size_t new_item_id = 0;
Text("Add Item");
BeginChild("ScrollRegion", ImVec2(150, 150), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) {
for (size_t i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) {
if (Selectable(zelda3::overworld::kSecretItemNames[i].c_str(),
i == new_item_id)) {
new_item_id = i;
@@ -348,10 +351,10 @@ bool DrawItemEditorPopup(zelda3::overworld::OverworldItem &item) {
BeginChild("ScrollRegion", ImVec2(150, 150), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
ImGui::BeginGroup();
for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) {
for (size_t i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) {
if (Selectable(zelda3::overworld::kSecretItemNames[i].c_str(),
item.id == i)) {
item.id = i;
item.id_ == i)) {
item.id_ = i;
}
}
ImGui::EndGroup();
@@ -384,7 +387,7 @@ void DrawSpriteTable(std::function<void(int)> onSpriteSelect) {
// Initialize items if empty
if (items.empty()) {
for (int i = 0; i < 256; ++i) {
items.push_back(SpriteItem{i, core::kSpriteDefaultNames[i].data()});
items.push_back(SpriteItem{i, zelda3::kSpriteDefaultNames[i].data()});
}
}

View File

@@ -3,7 +3,6 @@
#include "imgui/imgui.h"
#include "app/editor/overworld_editor.h"
#include "app/zelda3/common.h"
#include "app/zelda3/overworld/overworld.h"
@@ -11,16 +10,16 @@ namespace yaze {
namespace app {
namespace editor {
bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity,
bool IsMouseHoveringOverEntity(const zelda3::GameEntity &entity,
ImVec2 canvas_p0, ImVec2 scrolling);
void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool free_movement = false);
void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
void HandleEntityDragging(zelda3::GameEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool &is_dragging_entity,
zelda3::OverworldEntity *&dragged_entity,
zelda3::OverworldEntity *&current_entity,
zelda3::GameEntity *&dragged_entity,
zelda3::GameEntity *&current_entity,
bool free_movement = false);
bool DrawEntranceInserterPopup();
@@ -85,4 +84,4 @@ bool DrawSpriteEditorPopup(zelda3::Sprite &sprite);
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_OVERWORLD_ENTITY_H
#endif // YAZE_APP_EDITOR_OVERWORLD_ENTITY_H

View File

@@ -1,8 +1,6 @@
#ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H
#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
#include "imgui/imgui.h"
#include <cmath>
#include <unordered_map>
@@ -10,13 +8,11 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "app/core/common.h"
#include "app/editor/editor.h"
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/tile16_editor.h"
#include "app/editor/overworld/entity.h"
#include "app/editor/utils/editor.h"
#include "app/editor/utils/gfx_context.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
@@ -25,24 +21,24 @@
#include "app/gui/zeml.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace editor {
static constexpr uint k4BPP = 4;
static constexpr uint kByteSize = 3;
static constexpr uint kMessageIdSize = 5;
static constexpr uint kNumSheetsToLoad = 223;
static constexpr uint kTile8DisplayHeight = 64;
static constexpr float kInputFieldSize = 30.f;
static constexpr absl::string_view kToolsetColumnNames[] = {
"#undoTool", "#redoTool", "#separator2", "#zoomOutTool",
"#zoomInTool", "#separator", "#drawTool", "#history",
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
"#transportTool", "#musicTool", "#separator3", "#tilemapTool",
"propertiesTool"};
constexpr uint k4BPP = 4;
constexpr uint kByteSize = 3;
constexpr uint kMessageIdSize = 5;
constexpr uint kNumSheetsToLoad = 223;
constexpr uint kTile8DisplayHeight = 64;
constexpr uint kOverworldMapSize = 0x200;
constexpr float kInputFieldSize = 30.f;
constexpr ImVec2 kOverworldCanvasSize(kOverworldMapSize * 8,
kOverworldMapSize * 8);
constexpr ImVec2 kCurrentGfxCanvasSize(0x100 + 1, 0x10 * 0x40 + 1);
constexpr ImVec2 kBlocksetCanvasSize(0x100 + 1, 0x4000 + 1);
constexpr ImVec2 kGraphicsBinCanvasSize(0x100 + 1, kNumSheetsToLoad * 0x40 + 1);
constexpr ImGuiTableFlags kOWMapFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
@@ -52,6 +48,14 @@ constexpr ImGuiTableFlags kOWEditFlags =
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV;
static constexpr absl::string_view kToolsetColumnNames[] = {
"#undoTool", "#redoTool", "#separator2", "#zoomOutTool",
"#zoomInTool", "#separator", "#drawTool", "#history",
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
"#transportTool", "#musicTool", "#separator3", "#tilemapTool",
"propertiesTool", "#separator4", "#experimentalTool", "#properties",
"#separator5"};
constexpr absl::string_view kWorldList =
"Light World\0Dark World\0Extra World\0";
@@ -61,13 +65,17 @@ constexpr absl::string_view kTileSelectorTab = "##TileSelectorTabBar";
constexpr absl::string_view kOWEditTable = "##OWEditTable";
constexpr absl::string_view kOWMapTable = "#MapSettingsTable";
constexpr int kEntranceTileTypePtrLow = 0xDB8BF;
constexpr int kEntranceTileTypePtrHigh = 0xDB917;
constexpr int kNumEntranceTileTypes = 0x2C;
class EntranceContext {
public:
absl::Status LoadEntranceTileTypes(Rom& rom) {
int offset_low = 0xDB8BF;
int offset_high = 0xDB917;
int offset_low = kEntranceTileTypePtrLow;
int offset_high = kEntranceTileTypePtrHigh;
for (int i = 0; i < 0x2C; i++) {
for (int i = 0; i < kNumEntranceTileTypes; i++) {
// Load entrance tile types
ASSIGN_OR_RETURN(auto value_low, rom.ReadWord(offset_low + i));
entrance_tile_types_low_.push_back(value_low);
@@ -101,8 +109,8 @@ class EntranceContext {
*/
class OverworldEditor : public Editor,
public SharedRom,
public context::GfxContext,
public EntranceContext,
public GfxContext,
public core::ExperimentFlags {
public:
OverworldEditor() { type_ = EditorType::kOverworld; }
@@ -110,12 +118,15 @@ class OverworldEditor : public Editor,
void InitializeZeml();
absl::Status Update() final;
absl::Status Undo() { return absl::UnimplementedError("Undo"); }
absl::Status Redo() { return absl::UnimplementedError("Redo"); }
absl::Status Cut() { return absl::UnimplementedError("Cut"); }
absl::Status Copy() { return absl::UnimplementedError("Copy"); }
absl::Status Paste() { return absl::UnimplementedError("Paste"); }
absl::Status Find() { return absl::UnimplementedError("Find Unused Tiles"); }
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override {
return absl::UnimplementedError("Find Unused Tiles");
}
absl::Status Save();
auto overworld() { return &overworld_; }
@@ -135,11 +146,26 @@ class OverworldEditor : public Editor,
absl::Status LoadGraphics();
private:
absl::Status UpdateFullscreenCanvas();
/**
* @brief Draws the canvas, tile16 selector, and toolset in fullscreen
*/
void DrawFullscreenCanvas();
absl::Status DrawToolset();
/**
* @brief Toolset for entrances, exits, items, sprites, and transports.
*/
void DrawToolset();
/**
* @brief Draws the overworld map settings. Graphics, palettes, etc.
*/
void DrawOverworldMapSettings();
/**
* @brief Draw the overworld settings for ZSCustomOverworld.
*/
void DrawCustomOverworldMapSettings();
void RefreshChildMap(int i);
void RefreshOverworldMap();
absl::Status RefreshMapPalette();
@@ -155,9 +181,28 @@ class OverworldEditor : public Editor,
void DrawOverworldMaps();
void DrawOverworldEdits();
void RenderUpdatedMapBitmap(const ImVec2& click_position,
const Bytes& tile_data);
const std::vector<uint8_t>& tile_data);
/**
* @brief Check for changes to the overworld map.
*
* This function either draws the tile painter with the current tile16 or
* group of tile16 data with ow_map_canvas_ and DrawOverworldEdits or it
* checks for left mouse button click/drag to select a tile16 or group of
* tile16 data from the overworld map canvas. Similar to ZScream selection.
*/
void CheckForOverworldEdits();
/**
* @brief Draw and create the tile16 IDs that are currently selected.
*/
void CheckForSelectRectangle();
/**
* @brief Check for changes to the overworld map. Calls RefreshOverworldMap
* and RefreshTile16Blockset on the current map if it is modified and is
* actively being edited.
*/
absl::Status CheckForCurrentMap();
void CheckForMousePan();
@@ -175,17 +220,10 @@ class OverworldEditor : public Editor,
void DrawOverworldProperties();
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 {
DRAW_TILE,
ENTRANCES,
@@ -200,84 +238,55 @@ class OverworldEditor : public Editor,
EditingMode current_mode = EditingMode::DRAW_TILE;
EditingMode previous_mode = EditingMode::DRAW_TILE;
enum OverworldProperty {
LW_AREA_GFX,
DW_AREA_GFX,
LW_AREA_PAL,
DW_AREA_PAL,
LW_SPR_GFX_PART1,
LW_SPR_GFX_PART2,
DW_SPR_GFX_PART1,
DW_SPR_GFX_PART2,
LW_SPR_PAL_PART1,
LW_SPR_PAL_PART2,
DW_SPR_PAL_PART1,
DW_SPR_PAL_PART2,
};
int current_world_ = 0;
int current_map_ = 0;
int current_parent_ = 0;
int current_entrance_id_ = 0;
int current_exit_id_ = 0;
int current_item_id_ = 0;
int current_sprite_id_ = 0;
int current_blockset_ = 0;
int game_state_ = 1;
int current_tile16_ = 0;
int selected_tile_ = 0;
int current_blockset_ = 0;
int selected_entrance_ = 0;
int selected_usage_map_ = 0xFFFF;
char map_gfx_[3] = "";
char map_palette_[3] = "";
char spr_gfx_[3] = "";
char spr_palette_[3] = "";
char message_id_[5] = "";
char staticgfx[16];
uint32_t tilemap_file_offset_high_ = 0;
uint32_t tilemap_file_offset_low_ = 0;
uint32_t light_maps_to_load_ = 0x51;
uint32_t dark_maps_to_load_ = 0x2A;
uint32_t sp_maps_to_load_ = 0x07;
bool opt_enable_grid = true;
bool all_gfx_loaded_ = false;
bool map_blockset_loaded_ = false;
bool selected_tile_loaded_ = false;
bool update_selected_tile_ = true;
bool is_dragging_entrance_ = false;
bool show_tile16_editor_ = false;
bool show_gfx_group_editor_ = false;
bool overworld_canvas_fullscreen_ = false;
bool middle_mouse_dragging_ = false;
bool is_dragging_entity_ = false;
zelda3::OverworldEntity* dragged_entity_;
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;
std::string ow_tilemap_filename_ = "";
std::string tile32_configuration_filename_ = "";
Bytes selected_tile_data_;
std::vector<Bytes> tile16_individual_data_;
std::vector<uint8_t> selected_tile_data_;
std::vector<std::vector<uint8_t>> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_;
std::vector<Bytes> tile8_individual_data_;
std::vector<std::vector<uint8_t>> tile8_individual_data_;
std::vector<gfx::Bitmap> tile8_individual_;
Tile16Editor tile16_editor_;
GfxGroupEditor gfx_group_editor_;
PaletteEditor palette_editor_;
zelda3::overworld::Overworld overworld_;
gui::Canvas ow_map_canvas_{"owMapCanvas", ImVec2(0x200 * 8, 0x200 * 8),
gui::CanvasGridSize::k64x64};
gui::Canvas current_gfx_canvas_{"customGfxCanvas",
ImVec2(0x100 + 1, 0x10 * 0x40 + 1),
gui::CanvasGridSize::k32x32};
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100 + 1, 0x2000 + 1),
gui::CanvasGridSize::k32x32};
gui::Canvas graphics_bin_canvas_{
"graphicsBinCanvas", ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1),
gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_;
gfx::SnesPalette palette_;
gfx::Bitmap selected_tile_bmp_;
gfx::Bitmap tile16_blockset_bmp_;
gfx::Bitmap current_gfx_bmp_;
@@ -286,10 +295,28 @@ class OverworldEditor : public Editor,
gfx::BitmapTable maps_bmp_;
gfx::BitmapTable current_graphics_set_;
gfx::BitmapTable sprite_previews_;
gfx::BitmapTable animated_maps_;
OWBlockset refresh_blockset_;
zelda3::overworld::Overworld overworld_;
zelda3::OWBlockset refresh_blockset_;
zelda3::Sprite current_sprite_;
zelda3::overworld::OverworldEntrance current_entrance_;
zelda3::overworld::OverworldExit current_exit_;
zelda3::overworld::OverworldItem current_item_;
zelda3::GameEntity* current_entity_;
zelda3::GameEntity* dragged_entity_;
gui::Canvas ow_map_canvas_{"OwMap", kOverworldCanvasSize,
gui::CanvasGridSize::k64x64};
gui::Canvas current_gfx_canvas_{"CurrentGfx", kCurrentGfxCanvasSize,
gui::CanvasGridSize::k32x32};
gui::Canvas blockset_canvas_{"OwBlockset", kBlocksetCanvasSize,
gui::CanvasGridSize::k32x32};
gui::Canvas graphics_bin_canvas_{"GraphicsBin", kGraphicsBinCanvasSize,
gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_;
gui::zeml::Node layout_node_;
absl::Status status_;
};
@@ -297,4 +324,4 @@ class OverworldEditor : public Editor,
} // namespace app
} // namespace yaze
#endif
#endif

View File

@@ -1,157 +0,0 @@
#include "app/editor/overworld_editor.h"
namespace yaze {
namespace app {
namespace editor {
void OverworldEditor::RefreshChildMap(int map_index) {
overworld_.mutable_overworld_map(map_index)->LoadAreaGraphics();
status_ = overworld_.mutable_overworld_map(map_index)->BuildTileset();
PRINT_IF_ERROR(status_);
status_ = overworld_.mutable_overworld_map(map_index)->BuildTiles16Gfx(
overworld_.tiles16().size());
PRINT_IF_ERROR(status_);
status_ = overworld_.mutable_overworld_map(map_index)->BuildBitmap(
overworld_.GetMapTiles(current_world_));
maps_bmp_[map_index].set_data(
overworld_.mutable_overworld_map(map_index)->bitmap_data());
maps_bmp_[map_index].set_modified(true);
PRINT_IF_ERROR(status_);
}
void OverworldEditor::RefreshOverworldMap() {
std::vector<std::future<void>> futures;
int indices[4];
auto refresh_map_async = [this](int map_index) {
RefreshChildMap(map_index);
};
int source_map_id = current_map_;
bool is_large = overworld_.overworld_map(current_map_)->is_large_map();
if (is_large) {
source_map_id = current_parent_;
// We need to update the map and its siblings if it's a large map
for (int i = 1; i < 4; i++) {
int sibling_index = overworld_.overworld_map(source_map_id)->parent() + i;
if (i >= 2) sibling_index += 6;
futures.push_back(
std::async(std::launch::async, refresh_map_async, sibling_index));
indices[i] = sibling_index;
}
}
indices[0] = source_map_id;
futures.push_back(
std::async(std::launch::async, refresh_map_async, source_map_id));
for (auto &each : futures) {
each.get();
}
int n = is_large ? 4 : 1;
// We do texture updating on the main thread
for (int i = 0; i < n; ++i) {
rom()->UpdateBitmap(&maps_bmp_[indices[i]]);
}
}
absl::Status OverworldEditor::RefreshMapPalette() {
RETURN_IF_ERROR(
overworld_.mutable_overworld_map(current_map_)->LoadPalette());
const auto current_map_palette = overworld_.AreaPalette();
if (overworld_.overworld_map(current_map_)->is_large_map()) {
// We need to update the map and its siblings if it's a large map
for (int i = 1; i < 4; i++) {
int sibling_index = overworld_.overworld_map(current_map_)->parent() + i;
if (i >= 2) sibling_index += 6;
RETURN_IF_ERROR(
overworld_.mutable_overworld_map(sibling_index)->LoadPalette());
RETURN_IF_ERROR(
maps_bmp_[sibling_index].ApplyPalette(current_map_palette));
}
}
RETURN_IF_ERROR(maps_bmp_[current_map_].ApplyPalette(current_map_palette));
return absl::OkStatus();
}
void OverworldEditor::RefreshMapProperties() {
auto &current_ow_map = *overworld_.mutable_overworld_map(current_map_);
if (current_ow_map.is_large_map()) {
// We need to copy the properties from the parent map to the children
for (int i = 1; i < 4; i++) {
int sibling_index = current_ow_map.parent() + i;
if (i >= 2) {
sibling_index += 6;
}
auto &map = *overworld_.mutable_overworld_map(sibling_index);
map.set_area_graphics(current_ow_map.area_graphics());
map.set_area_palette(current_ow_map.area_palette());
map.set_sprite_graphics(game_state_,
current_ow_map.sprite_graphics(game_state_));
map.set_sprite_palette(game_state_,
current_ow_map.sprite_palette(game_state_));
map.set_message_id(current_ow_map.message_id());
}
}
}
absl::Status OverworldEditor::RefreshTile16Blockset() {
if (current_blockset_ ==
overworld_.overworld_map(current_map_)->area_graphics()) {
return absl::OkStatus();
}
current_blockset_ = overworld_.overworld_map(current_map_)->area_graphics();
overworld_.set_current_map(current_map_);
palette_ = overworld_.AreaPalette();
// Create the tile16 blockset image
rom()->UpdateBitmap(&tile16_blockset_bmp_);
RETURN_IF_ERROR(tile16_blockset_bmp_.ApplyPalette(palette_));
// Copy the tile16 data into individual tiles.
auto tile16_data = overworld_.Tile16Blockset();
std::vector<std::future<void>> futures;
// Loop through the tiles and copy their pixel data into separate vectors
for (int i = 0; i < 4096; i++) {
futures.push_back(std::async(
std::launch::async,
[&](int index) {
// Create a new vector for the pixel data of the current tile
Bytes tile_data(16 * 16, 0x00); // More efficient initialization
// Copy the pixel data for the current tile into the vector
for (int ty = 0; ty < 16; ty++) {
for (int tx = 0; tx < 16; tx++) {
int position = tx + (ty * 0x10);
uint8_t value =
tile16_data[(index % 8 * 16) + (index / 8 * 16 * 0x80) +
(ty * 0x80) + tx];
tile_data[position] = value;
}
}
// Add the vector for the current tile to the vector of tile pixel
// data
tile16_individual_[index].set_data(tile_data);
},
i));
}
for (auto &future : futures) {
future.get();
}
// Render the bitmaps of each tile.
for (int id = 0; id < 4096; id++) {
RETURN_IF_ERROR(tile16_individual_[id].ApplyPalette(palette_));
rom()->UpdateBitmap(&tile16_individual_[id]);
}
return absl::OkStatus();
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -4,6 +4,7 @@
#include "app/editor/sprite/zsprite.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/zelda3/sprite/sprite.h"
namespace yaze {
namespace app {
@@ -71,13 +72,13 @@ void SpriteEditor::DrawVanillaSpriteEditor() {
for (int n = 0; n < active_sprites_.Size;) {
bool open = true;
if (active_sprites_[n] > sizeof(core::kSpriteDefaultNames) / 4) {
if (active_sprites_[n] > sizeof(zelda3::kSpriteDefaultNames) / 4) {
active_sprites_.erase(active_sprites_.Data + n);
continue;
}
if (ImGui::BeginTabItem(
core::kSpriteDefaultNames[active_sprites_[n]].data(), &open,
zelda3::kSpriteDefaultNames[active_sprites_[n]].data(), &open,
ImGuiTabItemFlags_None)) {
DrawSpriteCanvas();
ImGui::EndTabItem();
@@ -175,7 +176,7 @@ void SpriteEditor::DrawCurrentSheets() {
graphics_sheet_canvas_.DrawTileSelector(32);
for (int i = 0; i < 8; i++) {
graphics_sheet_canvas_.DrawBitmap(
rom()->bitmap_manager()[current_sheets_[i]], 1, (i * 0x40) + 1, 2);
rom()->gfx_sheets().at(current_sheets_[i]), 1, (i * 0x40) + 1, 2);
}
graphics_sheet_canvas_.DrawGrid();
graphics_sheet_canvas_.DrawOverlay();
@@ -188,10 +189,10 @@ void SpriteEditor::DrawSpritesList() {
ImVec2(ImGui::GetContentRegionAvail().x, 0), true,
ImGuiWindowFlags_NoDecoration)) {
int i = 0;
for (const auto each_sprite_name : core::kSpriteDefaultNames) {
for (const auto each_sprite_name : zelda3::kSpriteDefaultNames) {
rom()->resource_label()->SelectableLabelWithNameEdit(
current_sprite_id_ == i, "Sprite Names", core::UppercaseHexByte(i),
core::kSpriteDefaultNames[i].data());
zelda3::kSpriteDefaultNames[i].data());
if (ImGui::IsItemClicked()) {
current_sprite_id_ = i;
if (!active_sprites_.contains(i)) {
@@ -243,7 +244,7 @@ void SpriteEditor::DrawCustomSpritesMetadata() {
// ZSprite Maker format open file dialog
if (ImGui::Button("Open ZSprite")) {
// Open ZSprite file
std::string file_path = FileDialogWrapper::ShowOpenFileDialog();
std::string file_path = core::FileDialogWrapper::ShowOpenFileDialog();
if (!file_path.empty()) {
zsprite::ZSprite zsprite;
status_ = zsprite.Load(file_path);

View File

@@ -3,7 +3,7 @@
#include "absl/status/status.h"
#include "app/editor/sprite/zsprite.h"
#include "app/editor/utils/editor.h"
#include "app/editor/editor.h"
#include "app/gui/canvas.h"
#include "app/rom.h"

View File

@@ -1,16 +1,16 @@
#ifndef YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
#define YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
#include "imgui/imgui.h"
#include <algorithm>
#include <cstdint>
#include <fstream>
#include <string>
#include <vector>
#include "app/core/constants.h"
#include "absl/status/status.h"
#include "app/gfx/snes_tile.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
@@ -46,7 +46,7 @@ struct OamTile {
struct AnimationGroup {
AnimationGroup() = default;
AnimationGroup(uint8_t fs, uint8_t fe, uint8_t fsp, std::string fn)
: frame_start(fs), frame_end(fe), frame_speed(fsp), frame_name(fn) {}
: frame_name(fn), frame_start(fs), frame_end(fe), frame_speed(fsp) {}
std::string frame_name;
uint8_t frame_start;

View File

@@ -0,0 +1,143 @@
#include "command_manager.h"
#include <fstream>
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace editor {
ImGuiKey MapKeyToImGuiKey(char key) {
switch (key) {
case 'A':
return ImGuiKey_A;
case 'B':
return ImGuiKey_B;
case 'C':
return ImGuiKey_C;
case 'D':
return ImGuiKey_D;
case 'E':
return ImGuiKey_E;
case 'F':
return ImGuiKey_F;
case 'G':
return ImGuiKey_G;
case 'H':
return ImGuiKey_H;
case 'I':
return ImGuiKey_I;
case 'J':
return ImGuiKey_J;
case 'K':
return ImGuiKey_K;
case 'L':
return ImGuiKey_L;
case 'M':
return ImGuiKey_M;
case 'N':
return ImGuiKey_N;
case 'O':
return ImGuiKey_O;
case 'P':
return ImGuiKey_P;
case 'Q':
return ImGuiKey_Q;
case 'R':
return ImGuiKey_R;
case 'S':
return ImGuiKey_S;
case 'T':
return ImGuiKey_T;
case 'U':
return ImGuiKey_U;
case 'V':
return ImGuiKey_V;
case 'W':
return ImGuiKey_W;
case 'X':
return ImGuiKey_X;
case 'Y':
return ImGuiKey_Y;
case 'Z':
return ImGuiKey_Z;
case '/':
return ImGuiKey_Slash;
case '-':
return ImGuiKey_Minus;
default:
return ImGuiKey_COUNT;
}
}
// When the player presses Space, a popup will appear fixed to the bottom of the
// ImGui window with a list of the available key commands which can be used.
void CommandManager::ShowWhichKey() {
if (ImGui::IsKeyPressed(ImGuiKey_Space)) {
ImGui::OpenPopup("WhichKey");
}
ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetIO().DisplaySize.y - 100),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, 100),
ImGuiCond_Always);
if (ImGui::BeginPopup("WhichKey")) {
// ESC to close the popup
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
ImGui::CloseCurrentPopup();
}
const ImVec4 colors[] = {
ImVec4(0.8f, 0.2f, 0.2f, 1.0f), // Soft Red
ImVec4(0.2f, 0.8f, 0.2f, 1.0f), // Soft Green
ImVec4(0.2f, 0.2f, 0.8f, 1.0f), // Soft Blue
ImVec4(0.8f, 0.8f, 0.2f, 1.0f), // Soft Yellow
ImVec4(0.8f, 0.2f, 0.8f, 1.0f), // Soft Magenta
ImVec4(0.2f, 0.8f, 0.8f, 1.0f) // Soft Cyan
};
const int numColors = sizeof(colors) / sizeof(colors[0]);
int colorIndex = 0;
if (ImGui::BeginTable("CommandsTable", commands_.size(),
ImGuiTableFlags_SizingStretchProp)) {
for (const auto &[shortcut, info] : commands_) {
ImGui::TableNextColumn();
ImGui::TextColored(colors[colorIndex], "%c: %s",
info.command_info.mnemonic,
info.command_info.name.c_str());
colorIndex = (colorIndex + 1) % numColors;
}
ImGui::EndTable();
}
ImGui::EndPopup();
}
}
void CommandManager::SaveKeybindings(const std::string &filepath) {
std::ofstream out(filepath);
if (out.is_open()) {
for (const auto &[shortcut, info] : commands_) {
out << shortcut << " " << info.command_info.mnemonic << " "
<< info.command_info.name << " " << info.command_info.desc << "\n";
}
out.close();
}
}
void CommandManager::LoadKeybindings(const std::string &filepath) {
std::ifstream in(filepath);
if (in.is_open()) {
commands_.clear();
std::string shortcut, name, desc;
char mnemonic;
while (in >> shortcut >> mnemonic >> name >> desc) {
commands_[shortcut].command_info = {nullptr, mnemonic, name, desc};
}
in.close();
}
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,83 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_COMMAND_MANAGER_H
#define YAZE_APP_EDITOR_SYSTEM_COMMAND_MANAGER_H
#include <functional>
#include <string>
#include <unordered_map>
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace editor {
ImGuiKey MapKeyToImGuiKey(char key);
class CommandManager {
public:
CommandManager() = default;
~CommandManager() = default;
using Command = std::function<void()>;
struct CommandInfo {
Command command;
char mnemonic;
std::string name;
std::string desc;
CommandInfo(Command command, char mnemonic, const std::string &name,
const std::string &desc)
: command(std::move(command)), mnemonic(mnemonic), name(name),
desc(desc) {}
CommandInfo() = default;
};
// New command info which supports subsections of commands
struct CommandInfoOrPrefix {
CommandInfo command_info;
std::unordered_map<std::string, CommandInfoOrPrefix> subcommands;
CommandInfoOrPrefix(CommandInfo command_info)
: command_info(std::move(command_info)) {}
CommandInfoOrPrefix() = default;
};
void RegisterPrefix(const std::string &group_name, const char prefix,
const std::string &name, const std::string &desc) {
commands_[group_name].command_info = {nullptr, prefix, name, desc};
}
void RegisterSubcommand(const std::string &group_name,
const std::string &shortcut, const char mnemonic,
const std::string &name, const std::string &desc,
Command command) {
commands_[group_name].subcommands[shortcut].command_info = {
command, mnemonic, name, desc};
}
void RegisterCommand(const std::string &shortcut, Command command,
char mnemonic, const std::string &name,
const std::string &desc) {
commands_[shortcut].command_info = {std::move(command), mnemonic, name,
desc};
}
void ExecuteCommand(const std::string &shortcut) {
if (commands_.find(shortcut) != commands_.end()) {
commands_[shortcut].command_info.command();
}
}
void ShowWhichKey();
void SaveKeybindings(const std::string &filepath);
void LoadKeybindings(const std::string &filepath);
private:
std::unordered_map<std::string, CommandInfoOrPrefix> commands_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_SYSTEM_COMMAND_MANAGER_H

View File

@@ -0,0 +1,77 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_CONSTANT_MANAGER_H
#define YAZE_APP_EDITOR_SYSTEM_CONSTANT_MANAGER_H
#include <cstddef>
#include "app/zelda3/overworld/overworld_map.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace editor {
class ConstantManager {
public:
void ShowConstantManager() {
ImGui::Begin("Constant Manager");
// Tab bar for the different types of constants
// Overworld, dungeon, graphics, expanded
ImGui::TextWrapped(
"This is the constant manager. It allows you to view and edit "
"constants in the ROM. You should only edit these if you know what "
"you're doing.");
if (ImGui::BeginTabBar("Constant Manager Tabs")) {
if (ImGui::BeginTabItem("Overworld")) {
ImGui::Text("Overworld constants");
ImGui::Separator();
ImGui::Text("OverworldCustomASMHasBeenApplied: %d",
zelda3::overworld::OverworldCustomASMHasBeenApplied);
ImGui::Text("OverworldCustomAreaSpecificBGPalette: %d",
zelda3::overworld::OverworldCustomAreaSpecificBGPalette);
ImGui::Text("OverworldCustomAreaSpecificBGEnabled: %d",
zelda3::overworld::OverworldCustomAreaSpecificBGEnabled);
ImGui::Text("OverworldCustomMainPaletteArray: %d",
zelda3::overworld::OverworldCustomMainPaletteArray);
ImGui::Text("OverworldCustomMainPaletteEnabled: %d",
zelda3::overworld::OverworldCustomMainPaletteEnabled);
ImGui::Text("OverworldCustomMosaicArray: %d",
zelda3::overworld::OverworldCustomMosaicArray);
ImGui::Text("OverworldCustomMosaicEnabled: %d",
zelda3::overworld::OverworldCustomMosaicEnabled);
ImGui::Text("OverworldCustomAnimatedGFXArray: %d",
zelda3::overworld::OverworldCustomAnimatedGFXArray);
ImGui::Text("OverworldCustomAnimatedGFXEnabled: %d",
zelda3::overworld::OverworldCustomAnimatedGFXEnabled);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Dungeon")) {
ImGui::Text("Dungeon constants");
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Graphics")) {
ImGui::Text("Graphics constants");
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Expanded")) {
ImGui::Text("Expanded constants");
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::End();
}
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_SYSTEM_CONSTANT_MANAGER_H

View File

@@ -0,0 +1,85 @@
#include "extension_manager.h"
#if defined(__unix__) || defined(__unix) || defined(unix) || \
defined(__APPLE__) && defined(__MACH__)
#include <dlfcn.h>
#endif
#include <system/extension.h>
#include <iostream>
#include <vector>
namespace yaze {
namespace app {
namespace editor {
void ExtensionManager::LoadExtension(const std::string& filename,
yaze_editor_context* context) {
#if defined(__unix__) || defined(__unix) || defined(unix) || \
defined(__APPLE__) && defined(__MACH__)
auto extension_path = filename.c_str();
void* handle = dlopen(extension_path, RTLD_LAZY);
if (!handle) {
std::cerr << "Cannot open extension: " << dlerror() << std::endl;
return;
}
dlerror(); // Clear any existing error
// Load the symbol to retrieve the extension
auto get_extension = reinterpret_cast<yaze_extension* (*)()>(
dlsym(handle, "get_yaze_extension"));
const char* dlsym_error = dlerror();
if (dlsym_error) {
std::cerr << "Cannot load symbol 'get_yaze_extension': " << dlsym_error
<< std::endl;
dlclose(handle);
return;
}
yaze_extension* extension = get_extension();
if (extension && extension->initialize) {
extension->initialize(context);
} else {
std::cerr << "Failed to initialize the extension." << std::endl;
dlclose(handle);
return;
}
extensions_.push_back(extension);
#endif
}
void ExtensionManager::RegisterExtension(yaze_extension* extension) {
extensions_.push_back(extension);
}
void ExtensionManager::InitializeExtensions(yaze_editor_context* context) {
for (auto& extension : extensions_) {
extension->initialize(context);
}
}
void ExtensionManager::ShutdownExtensions() {
for (auto& extension : extensions_) {
extension->cleanup();
}
// if (handle) {
// dlclose(handle);
// handle = nullptr;
// extension = nullptr;
// }
}
void ExtensionManager::ExecuteExtensionUI(yaze_editor_context* context) {
for (auto& extension : extensions_) {
if (extension->extend_ui) {
extension->extend_ui(context);
}
}
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,29 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_EXTENSION_MANAGER_H
#define YAZE_APP_EDITOR_SYSTEM_EXTENSION_MANAGER_H
#include <system/extension.h>
#include <string>
#include <vector>
namespace yaze {
namespace app {
namespace editor {
class ExtensionManager {
public:
void LoadExtension(const std::string& filename, yaze_editor_context* context);
void RegisterExtension(yaze_extension* extension);
void InitializeExtensions(yaze_editor_context* context);
void ShutdownExtensions();
void ExecuteExtensionUI(yaze_editor_context* context);
private:
std::vector<yaze_extension*> extensions_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_SYSTEM_EXTENSION_MANAGER_H

View File

@@ -1,9 +1,8 @@
#ifndef YAZE_APP_EDITOR_UTILS_FLAGS_H
#define YAZE_APP_EDITOR_UTILS_FLAGS_H
#include "imgui/imgui.h"
#include "core/common.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
@@ -31,6 +30,8 @@ struct FlagsMenu : public core::ExperimentFlags {
&mutable_flags()->overworld.kSaveOverworldItems);
Checkbox("Save Overworld Properties",
&mutable_flags()->overworld.kSaveOverworldProperties);
Checkbox("Load Custom Overworld",
&mutable_flags()->overworld.kLoadCustomOverworld);
ImGui::EndMenu();
}
@@ -42,21 +43,16 @@ struct FlagsMenu : public core::ExperimentFlags {
ImGui::EndMenu();
}
if (BeginMenu("Emulator Flags")) {
Checkbox("Load Audio Device", &mutable_flags()->kLoadAudioDevice);
ImGui::EndMenu();
}
Checkbox("Use built-in file dialog",
&mutable_flags()->kNewFileDialogWrapper);
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 Gfx Groups", &mutable_flags()->kSaveGfxGroups);
Checkbox("Save Graphics Sheets", &mutable_flags()->kSaveGraphicsSheet);
Checkbox("Use New ImGui Input", &mutable_flags()->kUseNewImGuiInput);
}
};

View File

@@ -0,0 +1,32 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_HISTORY_MANAGER_H
#define YAZE_APP_EDITOR_SYSTEM_HISTORY_MANAGER_H
#include <cstddef>
#include <stack>
#include <vector>
namespace yaze {
namespace app {
namespace editor {
// System history manager, undo and redo.
class HistoryManager {
public:
HistoryManager() = default;
~HistoryManager() = default;
void Add(const char* data);
void Undo();
void Redo();
private:
std::vector<const char*> history_;
std::stack<const char*> undo_;
std::stack<const char*> redo_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_SYSTEM_HISTORY_MANAGER_H

View File

@@ -0,0 +1,19 @@
#include "popup_manager.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace editor {
PopupManager::PopupManager() {
}
PopupManager::~PopupManager() {
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,21 @@
#ifndef YAZE_APP_EDITOR_POPUP_MANAGER_H
#define YAZE_APP_EDITOR_POPUP_MANAGER_H
namespace yaze {
namespace app {
namespace editor {
// ImGui popup manager.
class PopupManager {
public:
PopupManager();
~PopupManager();
void Show(const char* name);
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_POPUP_MANAGER_H

View File

@@ -0,0 +1,30 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_RESOURCE_MANAGER_H
#define YAZE_APP_EDITOR_SYSTEM_RESOURCE_MANAGER_H
#include <cstddef>
namespace yaze {
namespace app {
namespace editor {
// System resource manager.
class ResourceManager {
public:
ResourceManager() : count_(0) {}
~ResourceManager() = default;
void Load(const char* path);
void Unload(const char* path);
void UnloadAll();
size_t Count() const;
private:
size_t count_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_SYSTEM_RESOURCE_MANAGER_H

View File

@@ -1,11 +1,9 @@
#include "imgui/imgui.h"
#include "app/editor/system/settings_editor.h"
#include "absl/status/status.h"
#include "app/editor/utils/flags.h"
#include "app/editor/utils/keyboard_shortcuts.h"
#include "app/editor/settings_editor.h"
#include "app/editor/utils/flags.h"
#include "app/editor/system/flags.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {

View File

@@ -4,7 +4,7 @@
#include "imgui/imgui.h"
#include "absl/status/status.h"
#include "app/editor/utils/editor.h"
#include "app/editor/editor.h"
namespace yaze {
namespace app {

View File

@@ -1,26 +0,0 @@
#include "app/editor/utils/gfx_context.h"
#include "imgui/imgui.h"
#include <cmath>
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
namespace context {
std::unordered_map<uint8_t, gfx::Paletteset> GfxContext::palettesets_;
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,36 +0,0 @@
#ifndef YAZE_APP_EDITOR_VRAM_CONTEXT_H
#define YAZE_APP_EDITOR_VRAM_CONTEXT_H
#include "imgui/imgui.h"
#include <cmath>
#include <vector>
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
namespace context {
/**
* @brief Shared graphical context across editors.
*/
class GfxContext {
protected:
// Palettesets for the tile16 individual tiles
static std::unordered_map<uint8_t, gfx::Paletteset> palettesets_;
};
} // namespace context
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_VRAM_CONTEXT_H

View File

@@ -1,25 +0,0 @@
#ifndef YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H
#define YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace editor {
struct KeyboardShortcuts {
enum class ShortcutType {
kCut,
kCopy,
kPaste,
kUndo,
kRedo,
kFind,
};
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H_

View File

@@ -1,64 +0,0 @@
#ifndef YAZE_APP_EDITOR_UTILS_RECENT_FILES_H
#define YAZE_APP_EDITOR_UTILS_RECENT_FILES_H
#include <algorithm>
#include <fstream>
#include <string>
#include <vector>
namespace yaze {
namespace app {
namespace editor {
class RecentFilesManager {
public:
RecentFilesManager(const std::string& filename) : filename_(filename) {}
void AddFile(const std::string& filePath) {
// Add a file to the list, avoiding duplicates
auto it = std::find(recentFiles_.begin(), recentFiles_.end(), filePath);
if (it == recentFiles_.end()) {
recentFiles_.push_back(filePath);
}
}
void Save() {
std::ofstream file(filename_);
if (!file.is_open()) {
return; // Handle the error appropriately
}
for (const auto& filePath : recentFiles_) {
file << filePath << std::endl;
}
}
void Load() {
std::ifstream file(filename_);
if (!file.is_open()) {
return; // Handle the error appropriately
}
recentFiles_.clear();
std::string line;
while (std::getline(file, line)) {
if (!line.empty()) {
recentFiles_.push_back(line);
}
}
}
const std::vector<std::string>& GetRecentFiles() const {
return recentFiles_;
}
private:
std::string filename_;
std::vector<std::string> recentFiles_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_UTILS_RECENT_FILES_H

View File

@@ -4,6 +4,7 @@
#include <cstdint>
#include <iostream>
#include <vector>
#include <array>
#include "app/emu/audio/dsp.h"
#include "app/emu/audio/spc700.h"

View File

@@ -56,7 +56,6 @@ void Spc700::RunOpcode() {
}
void Spc700::ExecuteInstructions(uint8_t opcode) {
uint16_t initialPC = PC;
switch (opcode) {
case 0x00: { // nop imp
read(PC);

View File

@@ -10,7 +10,6 @@
#include "app/core/common.h"
#include "app/emu/cpu/clock.h"
#include "app/emu/cpu/internal/opcodes.h"
#include "app/emu/debug/log.h"
#include "app/emu/memory/memory.h"
namespace yaze {
@@ -36,7 +35,7 @@ class InstructionEntry {
std::string instruction; // Human-readable instruction text
};
class Cpu : public Loggable, public core::ExperimentFlags {
class Cpu : public core::ExperimentFlags {
public:
explicit Cpu(memory::Memory& mem, Clock& vclock,
memory::CpuCallbacks& callbacks)

View File

@@ -1,56 +0,0 @@
#ifndef YAZE_APP_EMU_DEBUG_DEBUGGER_H_
#define YAZE_APP_EMU_DEBUG_DEBUGGER_H_
#include "app/emu/audio/apu.h"
#include "app/emu/cpu/cpu.h"
#include "app/emu/video/ppu.h"
namespace yaze {
namespace app {
namespace emu {
class Debugger {
public:
Debugger() = default;
// Attach the debugger to the emulator
// Debugger(CPU &cpu, PPU &ppu, Apu &apu);
// Set a breakpoint
void SetBreakpoint(uint16_t address);
// Remove a breakpoint
void RemoveBreakpoint(uint16_t address);
// Step through the code
void Step();
// Inspect memory
uint8_t InspectMemory(uint16_t address);
// Modify memory
void ModifyMemory(uint16_t address, uint8_t value);
// Inspect registers
uint8_t InspectRegister(uint8_t reg);
// Modify registers
void ModifyRegister(uint8_t reg, uint8_t value);
// Handle other debugger tasks
// ...
private:
// References to the emulator's components
// CPU &cpu;
// PPU &ppu;
// Apu &apu;
// Breakpoints, watchpoints, etc.
// ...
};
} // namespace emu
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EMU_DBG_H_

View File

@@ -1,43 +0,0 @@
#ifndef YAZE_APP_EMU_LOG_H_
#define YAZE_APP_EMU_LOG_H_
#include <iostream>
#include <string>
namespace yaze {
namespace app {
namespace emu {
// Logger.h
class Logger {
public:
static Logger& GetInstance() {
static Logger instance;
return instance;
}
void Log(const std::string& message) const {
// Write log messages to a file or console
std::cout << message << std::endl;
}
private:
Logger() = default;
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
};
// Loggable.h
class Loggable {
protected:
Logger& logger_ = Logger::GetInstance();
virtual ~Loggable() = default;
virtual void LogMessage(const std::string& message) { logger_.Log(message); }
};
} // namespace emu
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EMU_LOG_H_

View File

@@ -5,10 +5,6 @@
#endif
#include <SDL.h>
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
#include <memory>
#include <string>
@@ -25,24 +21,14 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "app/core/utils/sdl_deleter.h"
#include "app/emu/snes.h"
#include "app/rom.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
using namespace yaze::app;
struct sdl_deleter {
void operator()(SDL_Window *p) const {
if (p) {
SDL_DestroyWindow(p);
}
}
void operator()(SDL_Renderer *p) const {
if (p) {
SDL_DestroyRenderer(p);
}
}
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
};
using yaze::app::core::SDL_Deleter;
int main(int argc, char **argv) {
absl::InitializeSymbolizer(argv[0]);
@@ -52,33 +38,30 @@ int main(int argc, char **argv) {
options.alarm_on_failure_secs = true;
absl::InstallFailureSignalHandler(options);
std::unique_ptr<SDL_Window, sdl_deleter> window_;
std::unique_ptr<SDL_Renderer, sdl_deleter> renderer_;
std::unique_ptr<SDL_Window, SDL_Deleter> window_;
std::unique_ptr<SDL_Renderer, SDL_Deleter> renderer_;
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
return EXIT_FAILURE;
} else {
SDL_DisplayMode displayMode;
SDL_GetCurrentDisplayMode(0, &displayMode);
int screenWidth = displayMode.w * 0.8;
int screenHeight = displayMode.h * 0.8;
window_ = std::unique_ptr<SDL_Window, sdl_deleter>(
window_ = std::unique_ptr<SDL_Window, SDL_Deleter>(
SDL_CreateWindow("Yaze Emulator", // window title
SDL_WINDOWPOS_UNDEFINED, // initial x position
SDL_WINDOWPOS_UNDEFINED, // initial y position
512, // width, in pixels
480, // height, in pixels
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI),
sdl_deleter());
SDL_Deleter());
if (window_ == nullptr) {
return EXIT_FAILURE;
}
}
renderer_ = std::unique_ptr<SDL_Renderer, sdl_deleter>(
renderer_ = std::unique_ptr<SDL_Renderer, SDL_Deleter>(
SDL_CreateRenderer(window_.get(), -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC),
sdl_deleter());
SDL_Deleter());
if (renderer_ == nullptr) {
return EXIT_FAILURE;
} else {
@@ -102,7 +85,7 @@ int main(int argc, char **argv) {
SDL_PauseAudioDevice(audio_device_, 0);
#ifdef __APPLE__
InitializeCocoa();
yaze_initialize_cocoa();
#endif
auto ppu_texture_ =
@@ -115,7 +98,7 @@ int main(int argc, char **argv) {
Rom rom_;
emu::SNES snes_;
Bytes rom_data_;
std::vector<uint8_t> rom_data_;
bool running = true;
bool loaded = false;
@@ -126,7 +109,10 @@ int main(int argc, char **argv) {
int wanted_samples_ = 0;
SDL_Event event;
rom_.LoadFromFile("inidisp_hammer_0f00.sfc");
if (!rom_.LoadFromFile("inidisp_hammer_0f00.sfc").ok()) {
return EXIT_FAILURE;
}
if (rom_.is_loaded()) {
rom_data_ = rom_.vector();
snes_.Init(rom_data_);

View File

@@ -1,7 +1,7 @@
add_executable(
yaze_emu
app/rom.cc
app/emu/debug/emu.cc
app/emu/emu.cc
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
@@ -11,13 +11,27 @@ add_executable(
${IMGUI_SRC}
)
if (APPLE)
list(APPEND YAZE_APP_CORE_SRC
app/core/platform/app_delegate.mm)
endif()
target_include_directories(
yaze_emu PUBLIC
lib/
app/
${CMAKE_SOURCE_DIR}/incl/
${CMAKE_SOURCE_DIR}/src/
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
)
target_link_libraries(yaze_emu PUBLIC ${ABSL_TARGETS} ${SDL_TARGETS} ${PNG_LIBRARIES} ${CMAKE_DL_LIBS} ImGui)
target_link_libraries(
yaze_emu PUBLIC
${ABSL_TARGETS}
${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS}
ImGui
ImGuiTestEngine
)

View File

@@ -1,18 +1,18 @@
#include "app/emu/emulator.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
#include <cstdint>
#include <vector>
#include "app/core/constants.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/platform/renderer.h"
#include "app/emu/snes.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/zeml.h"
#include "app/rom.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
namespace yaze {
namespace app {
@@ -52,9 +52,9 @@ using ImGui::Text;
void Emulator::Run() {
static bool loaded = false;
if (!snes_.running() && rom()->is_loaded()) {
ppu_texture_ =
SDL_CreateTexture(rom()->renderer().get(), SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, 512, 480);
ppu_texture_ = SDL_CreateTexture(core::Renderer::GetInstance().renderer(),
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, 512, 480);
if (ppu_texture_ == NULL) {
printf("Failed to create texture: %s\n", SDL_GetError());
return;
@@ -118,7 +118,8 @@ void Emulator::RenderSnesPpu() {
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
ImGui::SetCursorPosX((ImGui::GetWindowSize().x - size.x) * 0.5f);
ImGui::SetCursorPosY((ImGui::GetWindowSize().y - size.y) * 0.5f);
ImGui::Image((void*)ppu_texture_, size, ImVec2(0, 0), ImVec2(1, 1));
ImGui::Image((ImTextureID)(intptr_t)ppu_texture_, size, ImVec2(0, 0),
ImVec2(1, 1));
ImGui::EndChild();
} else {
@@ -246,7 +247,7 @@ void Emulator::RenderNavBar() {
}
if (open_file) {
auto file_name = FileDialogWrapper::ShowOpenFileDialog();
auto file_name = core::FileDialogWrapper::ShowOpenFileDialog();
if (!file_name.empty()) {
std::ifstream file(file_name, std::ios::binary);
// Load the data directly into rom_data
@@ -260,7 +261,101 @@ void Emulator::RenderNavBar() {
void Emulator::HandleEvents() {
// Handle user input events
// ...
if (ImGui::IsKeyPressed(keybindings_.a_button)) {
snes_.SetButtonState(1, 0, true);
}
if (ImGui::IsKeyPressed(keybindings_.b_button)) {
snes_.SetButtonState(1, 1, true);
}
if (ImGui::IsKeyPressed(keybindings_.select_button)) {
snes_.SetButtonState(1, 2, true);
}
if (ImGui::IsKeyPressed(keybindings_.start_button)) {
snes_.SetButtonState(1, 3, true);
}
if (ImGui::IsKeyPressed(keybindings_.up_button)) {
snes_.SetButtonState(1, 4, true);
}
if (ImGui::IsKeyPressed(keybindings_.down_button)) {
snes_.SetButtonState(1, 5, true);
}
if (ImGui::IsKeyPressed(keybindings_.left_button)) {
snes_.SetButtonState(1, 6, true);
}
if (ImGui::IsKeyPressed(keybindings_.right_button)) {
snes_.SetButtonState(1, 7, true);
}
if (ImGui::IsKeyPressed(keybindings_.x_button)) {
snes_.SetButtonState(1, 8, true);
}
if (ImGui::IsKeyPressed(keybindings_.y_button)) {
snes_.SetButtonState(1, 9, true);
}
if (ImGui::IsKeyPressed(keybindings_.l_button)) {
snes_.SetButtonState(1, 10, true);
}
if (ImGui::IsKeyPressed(keybindings_.r_button)) {
snes_.SetButtonState(1, 11, true);
}
if (ImGui::IsKeyReleased(keybindings_.a_button)) {
snes_.SetButtonState(1, 0, false);
}
if (ImGui::IsKeyReleased(keybindings_.b_button)) {
snes_.SetButtonState(1, 1, false);
}
if (ImGui::IsKeyReleased(keybindings_.select_button)) {
snes_.SetButtonState(1, 2, false);
}
if (ImGui::IsKeyReleased(keybindings_.start_button)) {
snes_.SetButtonState(1, 3, false);
}
if (ImGui::IsKeyReleased(keybindings_.up_button)) {
snes_.SetButtonState(1, 4, false);
}
if (ImGui::IsKeyReleased(keybindings_.down_button)) {
snes_.SetButtonState(1, 5, false);
}
if (ImGui::IsKeyReleased(keybindings_.left_button)) {
snes_.SetButtonState(1, 6, false);
}
if (ImGui::IsKeyReleased(keybindings_.right_button)) {
snes_.SetButtonState(1, 7, false);
}
if (ImGui::IsKeyReleased(keybindings_.x_button)) {
snes_.SetButtonState(1, 8, false);
}
if (ImGui::IsKeyReleased(keybindings_.y_button)) {
snes_.SetButtonState(1, 9, false);
}
if (ImGui::IsKeyReleased(keybindings_.l_button)) {
snes_.SetButtonState(1, 10, false);
}
if (ImGui::IsKeyReleased(keybindings_.r_button)) {
snes_.SetButtonState(1, 11, false);
}
}
void Emulator::RenderBreakpointList() {

View File

@@ -1,14 +1,13 @@
#ifndef YAZE_APP_CORE_EMULATOR_H
#define YAZE_APP_CORE_EMULATOR_H
#include "imgui/imgui.h"
#include <cstdint>
#include <vector>
#include "app/emu/snes.h"
#include "app/gui/zeml.h"
#include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
@@ -19,6 +18,46 @@ namespace app {
*/
namespace emu {
// case SDLK_z:
// editor.emulator().snes().SetButtonState(1, 0, false);
// case SDLK_a:
// editor.emulator().snes().SetButtonState(1, 1, false);
// case SDLK_RSHIFT:
// editor.emulator().snes().SetButtonState(1, 2, false);
// case SDLK_RETURN:
// editor.emulator().snes().SetButtonState(1, 3, false);
// case SDLK_UP:
// editor.emulator().snes().SetButtonState(1, 4, false);
// case SDLK_DOWN:
// editor.emulator().snes().SetButtonState(1, 5, false);
// case SDLK_LEFT:
// editor.emulator().snes().SetButtonState(1, 6, false);
// case SDLK_RIGHT:
// editor.emulator().snes().SetButtonState(1, 7, false);
// case SDLK_x:
// editor.emulator().snes().SetButtonState(1, 8, false);
// case SDLK_s:
// editor.emulator().snes().SetButtonState(1, 9, false);
// case SDLK_d:
// editor.emulator().snes().SetButtonState(1, 10, false);
// case SDLK_c:
// editor.emulator().snes().SetButtonState(1, 11, false);
struct EmulatorKeybindings {
ImGuiKey a_button = ImGuiKey_Z;
ImGuiKey b_button = ImGuiKey_A;
ImGuiKey x_button = ImGuiKey_S;
ImGuiKey y_button = ImGuiKey_X;
ImGuiKey l_button = ImGuiKey_Q;
ImGuiKey r_button = ImGuiKey_W;
ImGuiKey start_button = ImGuiKey_Enter;
ImGuiKey select_button = ImGuiKey_Backspace;
ImGuiKey up_button = ImGuiKey_UpArrow;
ImGuiKey down_button = ImGuiKey_DownArrow;
ImGuiKey left_button = ImGuiKey_LeftArrow;
ImGuiKey right_button = ImGuiKey_RightArrow;
};
/**
* @class Emulator
* @brief A class for emulating and debugging SNES games.
@@ -145,6 +184,8 @@ class Emulator : public SharedRom {
std::vector<uint8_t> rom_data_;
EmulatorKeybindings keybindings_;
gui::zeml::Node emulator_node_;
};

View File

@@ -17,23 +17,23 @@ static const int transferLength[8] = {1, 2, 2, 4, 4, 4, 2, 4};
void Reset(MemoryImpl* memory) {
auto channel = memory->dma_channels();
for (int i = 0; i < 8; i++) {
channel[i].bAdr = 0xff;
channel[i].aAdr = 0xffff;
channel[i].aBank = 0xff;
channel[i].b_addr = 0xff;
channel[i].a_addr = 0xffff;
channel[i].a_bank = 0xff;
channel[i].size = 0xffff;
channel[i].indBank = 0xff;
channel[i].tableAdr = 0xffff;
channel[i].repCount = 0xff;
channel[i].ind_bank = 0xff;
channel[i].table_addr = 0xffff;
channel[i].rep_count = 0xff;
channel[i].unusedByte = 0xff;
channel[i].dmaActive = false;
channel[i].hdmaActive = false;
channel[i].dma_active = false;
channel[i].hdma_active = false;
channel[i].mode = 7;
channel[i].fixed = true;
channel[i].decrement = true;
channel[i].indirect = true;
channel[i].fromB = true;
channel[i].from_b = true;
channel[i].unusedBit = true;
channel[i].doTransfer = false;
channel[i].do_transfer = false;
channel[i].terminated = false;
}
memory->set_dma_state(0);
@@ -51,20 +51,20 @@ uint8_t Read(MemoryImpl* memory, uint16_t adr) {
val |= channel[c].decrement << 4;
val |= channel[c].unusedBit << 5;
val |= channel[c].indirect << 6;
val |= channel[c].fromB << 7;
val |= channel[c].from_b << 7;
return val;
}
case 0x1: {
return channel[c].bAdr;
return channel[c].b_addr;
}
case 0x2: {
return channel[c].aAdr & 0xff;
return channel[c].a_addr & 0xff;
}
case 0x3: {
return channel[c].aAdr >> 8;
return channel[c].a_addr >> 8;
}
case 0x4: {
return channel[c].aBank;
return channel[c].a_bank;
}
case 0x5: {
return channel[c].size & 0xff;
@@ -73,16 +73,16 @@ uint8_t Read(MemoryImpl* memory, uint16_t adr) {
return channel[c].size >> 8;
}
case 0x7: {
return channel[c].indBank;
return channel[c].ind_bank;
}
case 0x8: {
return channel[c].tableAdr & 0xff;
return channel[c].table_addr & 0xff;
}
case 0x9: {
return channel[c].tableAdr >> 8;
return channel[c].table_addr >> 8;
}
case 0xa: {
return channel[c].repCount;
return channel[c].rep_count;
}
case 0xb:
case 0xf: {
@@ -104,23 +104,23 @@ void Write(MemoryImpl* memory, uint16_t adr, uint8_t val) {
channel[c].decrement = val & 0x10;
channel[c].unusedBit = val & 0x20;
channel[c].indirect = val & 0x40;
channel[c].fromB = val & 0x80;
channel[c].from_b = val & 0x80;
break;
}
case 0x1: {
channel[c].bAdr = val;
channel[c].b_addr = val;
break;
}
case 0x2: {
channel[c].aAdr = (channel[c].aAdr & 0xff00) | val;
channel[c].a_addr = (channel[c].a_addr & 0xff00) | val;
break;
}
case 0x3: {
channel[c].aAdr = (channel[c].aAdr & 0xff) | (val << 8);
channel[c].a_addr = (channel[c].a_addr & 0xff) | (val << 8);
break;
}
case 0x4: {
channel[c].aBank = val;
channel[c].a_bank = val;
break;
}
case 0x5: {
@@ -132,19 +132,19 @@ void Write(MemoryImpl* memory, uint16_t adr, uint8_t val) {
break;
}
case 0x7: {
channel[c].indBank = val;
channel[c].ind_bank = val;
break;
}
case 0x8: {
channel[c].tableAdr = (channel[c].tableAdr & 0xff00) | val;
channel[c].table_addr = (channel[c].table_addr & 0xff00) | val;
break;
}
case 0x9: {
channel[c].tableAdr = (channel[c].tableAdr & 0xff) | (val << 8);
channel[c].table_addr = (channel[c].table_addr & 0xff) | (val << 8);
break;
}
case 0xa: {
channel[c].repCount = val;
channel[c].rep_count = val;
break;
}
case 0xb:
@@ -168,22 +168,22 @@ void DoDma(SNES* snes, MemoryImpl* memory, int cpuCycles) {
// full transfer overhead
WaitCycle(snes, memory);
for (int i = 0; i < 8; i++) {
if (!channel[i].dmaActive) continue;
if (!channel[i].dma_active) continue;
// do channel i
WaitCycle(snes, memory); // overhead per channel
int offIndex = 0;
while (channel[i].dmaActive) {
while (channel[i].dma_active) {
WaitCycle(snes, memory);
TransferByte(snes, memory, channel[i].aAdr, channel[i].aBank,
channel[i].bAdr + bAdrOffsets[channel[i].mode][offIndex++],
channel[i].fromB);
TransferByte(snes, memory, channel[i].a_addr, channel[i].a_bank,
channel[i].b_addr + bAdrOffsets[channel[i].mode][offIndex++],
channel[i].from_b);
offIndex &= 3;
if (!channel[i].fixed) {
channel[i].aAdr += channel[i].decrement ? -1 : 1;
channel[i].a_addr += channel[i].decrement ? -1 : 1;
}
channel[i].size--;
if (channel[i].size == 0) {
channel[i].dmaActive = false;
channel[i].dma_active = false;
}
}
}
@@ -224,8 +224,8 @@ void InitHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cpu_cycles) {
bool hdmaEnabled = false;
// check if a channel is enabled, and do reset
for (int i = 0; i < 8; i++) {
if (channel[i].hdmaActive) hdmaEnabled = true;
channel[i].doTransfer = false;
if (channel[i].hdma_active) hdmaEnabled = true;
channel[i].do_transfer = false;
channel[i].terminated = false;
}
if (!hdmaEnabled) return;
@@ -235,24 +235,24 @@ void InitHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cpu_cycles) {
// full transfer overhead
snes->RunCycles(8);
for (int i = 0; i < 8; i++) {
if (channel[i].hdmaActive) {
if (channel[i].hdma_active) {
// terminate any dma
channel[i].dmaActive = false;
channel[i].dma_active = false;
// load address, repCount, and indirect address if needed
snes->RunCycles(8);
channel[i].tableAdr = channel[i].aAdr;
channel[i].repCount =
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++);
if (channel[i].repCount == 0) channel[i].terminated = true;
channel[i].table_addr = channel[i].a_addr;
channel[i].rep_count =
snes->Read((channel[i].a_bank << 16) | channel[i].table_addr++);
if (channel[i].rep_count == 0) channel[i].terminated = true;
if (channel[i].indirect) {
snes->RunCycles(8);
channel[i].size =
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++);
snes->Read((channel[i].a_bank << 16) | channel[i].table_addr++);
snes->RunCycles(8);
channel[i].size |=
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++) << 8;
snes->Read((channel[i].a_bank << 16) | channel[i].table_addr++) << 8;
}
channel[i].doTransfer = true;
channel[i].do_transfer = true;
}
}
if (do_sync) snes->SyncCycles(false, cpu_cycles);
@@ -264,7 +264,7 @@ void DoHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles) {
bool hdmaActive = false;
int lastActive = 0;
for (int i = 0; i < 8; i++) {
if (channel[i].hdmaActive) {
if (channel[i].hdma_active) {
hdmaActive = true;
if (!channel[i].terminated) lastActive = i;
}
@@ -280,20 +280,20 @@ void DoHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles) {
// do all copies
for (int i = 0; i < 8; i++) {
// terminate any dma
if (channel[i].hdmaActive) channel[i].dmaActive = false;
if (channel[i].hdmaActive && !channel[i].terminated) {
if (channel[i].hdma_active) channel[i].dma_active = false;
if (channel[i].hdma_active && !channel[i].terminated) {
// do the hdma
if (channel[i].doTransfer) {
if (channel[i].do_transfer) {
for (int j = 0; j < transferLength[channel[i].mode]; j++) {
snes->RunCycles(8);
if (channel[i].indirect) {
TransferByte(snes, memory, channel[i].size++, channel[i].indBank,
channel[i].bAdr + bAdrOffsets[channel[i].mode][j],
channel[i].fromB);
TransferByte(snes, memory, channel[i].size++, channel[i].ind_bank,
channel[i].b_addr + bAdrOffsets[channel[i].mode][j],
channel[i].from_b);
} else {
TransferByte(snes, memory, channel[i].tableAdr++, channel[i].aBank,
channel[i].bAdr + bAdrOffsets[channel[i].mode][j],
channel[i].fromB);
TransferByte(snes, memory, channel[i].table_addr++, channel[i].a_bank,
channel[i].b_addr + bAdrOffsets[channel[i].mode][j],
channel[i].from_b);
}
}
}
@@ -301,31 +301,31 @@ void DoHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles) {
}
// do all updates
for (int i = 0; i < 8; i++) {
if (channel[i].hdmaActive && !channel[i].terminated) {
channel[i].repCount--;
channel[i].doTransfer = channel[i].repCount & 0x80;
if (channel[i].hdma_active && !channel[i].terminated) {
channel[i].rep_count--;
channel[i].do_transfer = channel[i].rep_count & 0x80;
snes->RunCycles(8);
uint8_t newRepCount =
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr);
if ((channel[i].repCount & 0x7f) == 0) {
channel[i].repCount = newRepCount;
channel[i].tableAdr++;
snes->Read((channel[i].a_bank << 16) | channel[i].table_addr);
if ((channel[i].rep_count & 0x7f) == 0) {
channel[i].rep_count = newRepCount;
channel[i].table_addr++;
if (channel[i].indirect) {
if (channel[i].repCount == 0 && i == lastActive) {
if (channel[i].rep_count == 0 && i == lastActive) {
// if this is the last active channel, only fetch high, and use 0
// for low
channel[i].size = 0;
} else {
snes->RunCycles(8);
channel[i].size =
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++);
snes->Read((channel[i].a_bank << 16) | channel[i].table_addr++);
}
snes->RunCycles(8);
channel[i].size |=
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++) << 8;
snes->Read((channel[i].a_bank << 16) | channel[i].table_addr++) << 8;
}
if (channel[i].repCount == 0) channel[i].terminated = true;
channel[i].doTransfer = true;
if (channel[i].rep_count == 0) channel[i].terminated = true;
channel[i].do_transfer = true;
}
}
}
@@ -359,9 +359,9 @@ void StartDma(MemoryImpl* memory, uint8_t val, bool hdma) {
auto channel = memory->dma_channels();
for (int i = 0; i < 8; i++) {
if (hdma) {
channel[i].hdmaActive = val & (1 << i);
channel[i].hdma_active = val & (1 << i);
} else {
channel[i].dmaActive = val & (1 << i);
channel[i].dma_active = val & (1 << i);
}
}
if (!hdma) {

View File

@@ -9,24 +9,24 @@ namespace emu {
namespace memory {
typedef struct DmaChannel {
uint8_t bAdr;
uint16_t aAdr;
uint8_t aBank;
uint16_t size; // also indirect hdma adr
uint8_t indBank; // hdma
uint16_t tableAdr; // hdma
uint8_t repCount; // hdma
uint8_t b_addr;
uint16_t a_addr;
uint8_t a_bank;
uint16_t size; // also indirect hdma adr
uint8_t ind_bank; // hdma
uint16_t table_addr; // hdma
uint8_t rep_count; // hdma
uint8_t unusedByte;
bool dmaActive;
bool hdmaActive;
bool dma_active;
bool hdma_active;
uint8_t mode;
bool fixed;
bool decrement;
bool indirect; // hdma
bool fromB;
bool from_b;
bool unusedBit;
bool doTransfer; // hdma
bool terminated; // hdma
bool do_transfer; // hdma
bool terminated; // hdma
} DmaChannel;
} // namespace memory

View File

@@ -1,34 +1,33 @@
#include "app/emu/memory/memory.h"
#include "imgui/imgui.h"
#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
#include "app/emu/debug/log.h"
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace emu {
namespace memory {
void MemoryImpl::Initialize(const std::vector<uint8_t>& romData, bool verbose) {
void MemoryImpl::Initialize(const std::vector<uint8_t>& rom_data,
bool verbose) {
verbose_ = verbose;
type_ = 1;
auto location = 0x7FC0; // GetHeaderOffset();
romSize = 0x400 << romData[location + 0x17];
sramSize = 0x400 << romData[location + 0x18];
rom_.resize(romSize);
rom_size_ = 0x400 << rom_data[location + 0x17];
sram_size_ = 0x400 << rom_data[location + 0x18];
rom_.resize(rom_size_);
// Copy memory into rom_
for (size_t i = 0; i < romSize; i++) {
rom_[i] = romData[i];
for (size_t i = 0; i < rom_size_; i++) {
rom_[i] = rom_data[i];
}
ram_.resize(sramSize);
for (size_t i = 0; i < sramSize; i++) {
ram_.resize(sram_size_);
for (size_t i = 0; i < sram_size_; i++) {
ram_[i] = 0;
}
@@ -37,71 +36,19 @@ void MemoryImpl::Initialize(const std::vector<uint8_t>& romData, bool verbose) {
std::fill(memory_.begin(), memory_.end(), 0);
// Load ROM data into memory based on LoROM mapping
size_t romSize = romData.size();
size_t romAddress = 0;
size_t rom_data_size = rom_data.size();
size_t rom_address = 0;
const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB
for (size_t bank = 0x00; bank <= 0x3F; ++bank) {
for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) {
if (romAddress < romSize) {
std::copy(romData.begin() + romAddress,
romData.begin() + romAddress + ROM_CHUNK_SIZE,
if (rom_address < rom_data_size) {
std::copy(rom_data.begin() + rom_address,
rom_data.begin() + rom_address + ROM_CHUNK_SIZE,
memory_.begin() + (bank << 16) + offset);
romAddress += ROM_CHUNK_SIZE;
rom_address += ROM_CHUNK_SIZE;
}
}
}
}
memory::RomInfo MemoryImpl::ReadRomHeader() {
memory::RomInfo romInfo;
uint32_t offset = GetHeaderOffset();
// Read cartridge title
char title[22];
for (int i = 0; i < 21; ++i) {
title[i] = ReadByte(offset + i);
}
title[21] = '\0'; // Null-terminate the string
romInfo.title = std::string(title);
// Read ROM speed and memory map mode
uint8_t romSpeedAndMapMode = ReadByte(offset + 0x15);
romInfo.romSpeed = (memory::RomSpeed)(romSpeedAndMapMode & 0x07);
romInfo.bankSize = (memory::BankSize)((romSpeedAndMapMode >> 5) & 0x01);
// Read ROM type
romInfo.romType = (memory::RomType)ReadByte(offset + 0x16);
// Read ROM size
romInfo.romSize = (memory::RomSize)ReadByte(offset + 0x17);
// Read RAM size
romInfo.sramSize = (memory::SramSize)ReadByte(offset + 0x18);
// Read country code
romInfo.countryCode = (memory::CountryCode)ReadByte(offset + 0x19);
// Read license
romInfo.license = (memory::License)ReadByte(offset + 0x1A);
// Read ROM version
romInfo.version = ReadByte(offset + 0x1B);
// Read checksum complement
romInfo.checksumComplement = ReadWord(offset + 0x1E);
// Read checksum
romInfo.checksum = ReadWord(offset + 0x1C);
// Read NMI VBL vector
romInfo.nmiVblVector = ReadWord(offset + 0x3E);
// Read reset vector
romInfo.resetVector = ReadWord(offset + 0x3C);
return romInfo;
}
uint8_t MemoryImpl::cart_read(uint8_t bank, uint16_t adr) {
@@ -136,59 +83,59 @@ void MemoryImpl::cart_write(uint8_t bank, uint16_t adr, uint8_t val) {
uint8_t MemoryImpl::cart_readLorom(uint8_t bank, uint16_t adr) {
if (((bank >= 0x70 && bank < 0x7e) || bank >= 0xf0) && adr < 0x8000 &&
sramSize > 0) {
sram_size_ > 0) {
// banks 70-7e and f0-ff, adr 0000-7fff
return ram_[(((bank & 0xf) << 15) | adr) & (sramSize - 1)];
return ram_[(((bank & 0xf) << 15) | adr) & (sram_size_ - 1)];
}
bank &= 0x7f;
if (adr >= 0x8000 || bank >= 0x40) {
// adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff
return rom_[((bank << 15) | (adr & 0x7fff)) & (romSize - 1)];
return rom_[((bank << 15) | (adr & 0x7fff)) & (rom_size_ - 1)];
}
return open_bus_;
}
void MemoryImpl::cart_writeLorom(uint8_t bank, uint16_t adr, uint8_t val) {
if (((bank >= 0x70 && bank < 0x7e) || bank > 0xf0) && adr < 0x8000 &&
sramSize > 0) {
sram_size_ > 0) {
// banks 70-7e and f0-ff, adr 0000-7fff
ram_[(((bank & 0xf) << 15) | adr) & (sramSize - 1)] = val;
ram_[(((bank & 0xf) << 15) | adr) & (sram_size_ - 1)] = val;
}
}
uint8_t MemoryImpl::cart_readHirom(uint8_t bank, uint16_t adr) {
bank &= 0x7f;
if (bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && sramSize > 0) {
if (bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && sram_size_ > 0) {
// banks 00-3f and 80-bf, adr 6000-7fff
return ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sramSize - 1)];
return ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sram_size_ - 1)];
}
if (adr >= 0x8000 || bank >= 0x40) {
// adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff
return rom_[(((bank & 0x3f) << 16) | adr) & (romSize - 1)];
return rom_[(((bank & 0x3f) << 16) | adr) & (rom_size_ - 1)];
}
return open_bus_;
}
uint8_t MemoryImpl::cart_readExHirom(uint8_t bank, uint16_t adr) {
if ((bank & 0x7f) < 0x40 && adr >= 0x6000 && adr < 0x8000 && sramSize > 0) {
if ((bank & 0x7f) < 0x40 && adr >= 0x6000 && adr < 0x8000 && sram_size_ > 0) {
// banks 00-3f and 80-bf, adr 6000-7fff
return ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sramSize - 1)];
return ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sram_size_ - 1)];
}
bool secondHalf = bank < 0x80;
bank &= 0x7f;
if (adr >= 0x8000 || bank >= 0x40) {
// adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff
return rom_[(((bank & 0x3f) << 16) | (secondHalf ? 0x400000 : 0) | adr) &
(romSize - 1)];
(rom_size_ - 1)];
}
return open_bus_;
}
void MemoryImpl::cart_writeHirom(uint8_t bank, uint16_t adr, uint8_t val) {
bank &= 0x7f;
if (bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && sramSize > 0) {
if (bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && sram_size_ > 0) {
// banks 00-3f and 80-bf, adr 6000-7fff
ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sramSize - 1)] = val;
ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sram_size_ - 1)] = val;
}
}
@@ -218,70 +165,6 @@ uint32_t MemoryImpl::GetMappedAddress(uint32_t address) const {
return address; // Return the original address if no mapping is defined
}
void DrawSnesMemoryMapping(const MemoryImpl& memory) {
// Using those as a base value to create width/height that are factor of the
// size of our font
const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x;
const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
const char* column_names[] = {
"Offset", "0x00", "0x01", "0x02", "0x03", "0x04", "0x05", "0x06", "0x07",
"0x08", "0x09", "0x0A", "0x0B", "0x0C", "0x0D", "0x0E", "0x0F", "0x10",
"0x11", "0x12", "0x13", "0x14", "0x15", "0x16", "0x17", "0x18", "0x19",
"0x1A", "0x1B", "0x1C", "0x1D", "0x1E", "0x1F"};
const int columns_count = IM_ARRAYSIZE(column_names);
const int rows_count = 16;
static ImGuiTableFlags table_flags =
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX |
ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_Hideable |
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
ImGuiTableFlags_HighlightHoveredColumn;
static bool bools[columns_count * rows_count] = {};
static int frozen_cols = 1;
static int frozen_rows = 2;
ImGui::CheckboxFlags("_ScrollX", &table_flags, ImGuiTableFlags_ScrollX);
ImGui::CheckboxFlags("_ScrollY", &table_flags, ImGuiTableFlags_ScrollY);
ImGui::CheckboxFlags("_NoBordersInBody", &table_flags,
ImGuiTableFlags_NoBordersInBody);
ImGui::CheckboxFlags("_HighlightHoveredColumn", &table_flags,
ImGuiTableFlags_HighlightHoveredColumn);
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
ImGui::SliderInt("Frozen columns", &frozen_cols, 0, 2);
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
ImGui::SliderInt("Frozen rows", &frozen_rows, 0, 2);
if (ImGui::BeginTable("table_angled_headers", columns_count, table_flags,
ImVec2(0.0f, TEXT_BASE_HEIGHT * 12))) {
ImGui::TableSetupColumn(
column_names[0],
ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_NoReorder);
for (int n = 1; n < columns_count; n++)
ImGui::TableSetupColumn(column_names[n],
ImGuiTableColumnFlags_AngledHeader |
ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupScrollFreeze(frozen_cols, frozen_rows);
ImGui::TableAngledHeadersRow();
ImGui::TableHeadersRow();
for (int row = 0; row < rows_count; row++) {
ImGui::PushID(row);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
ImGui::Text("Offset 0x%04X", row);
for (int column = 1; column < columns_count; column++)
if (ImGui::TableSetColumnIndex(column)) {
ImGui::PushID(column);
ImGui::Checkbox("", &bools[row * columns_count + column]);
ImGui::PopID();
}
ImGui::PopID();
}
ImGui::EndTable();
}
}
} // namespace memory
} // namespace emu
} // namespace app

View File

@@ -7,7 +7,6 @@
#include <string>
#include <vector>
#include "app/emu/debug/log.h"
#include "app/emu/memory/dma_channel.h"
// LoROM (Mode 20):
@@ -32,67 +31,6 @@ namespace app {
namespace emu {
namespace memory {
enum RomSpeed { SLOW_ROM = 0x00, FAST_ROM = 0x07 };
enum BankSize { LOW_ROM = 0x00, HI_ROM = 0x01 };
enum RomType {
ROM_DEFAULT = 0x00,
ROM_RAM = 0x01,
ROM_SRAM = 0x02,
ROM_DSP1 = 0x03,
ROM_DSP1_RAM = 0x04,
ROM_DSP1_SRAM = 0x05,
FX = 0x06
};
enum RomSize {
SIZE_2_MBIT = 0x08,
SIZE_4_MBIT = 0x09,
SIZE_8_MBIT = 0x0A,
SIZE_16_MBIT = 0x0B,
SIZE_32_MBIT = 0x0C
};
enum SramSize {
NO_SRAM = 0x00,
SRAM_16_KBIT = 0x01,
SRAM_32_KBIT = 0x02,
SRAM_64_KBIT = 0x03
};
enum CountryCode {
JAPAN = 0x00,
USA = 0x01,
EUROPE_OCEANIA_ASIA = 0x02,
// ... and other countries
};
enum License {
INVALID = 0,
NINTENDO = 1,
ZAMUSE = 5,
CAPCOM = 8,
// ... and other licenses
};
class RomInfo {
public:
std::string title;
RomSpeed romSpeed;
BankSize bankSize;
RomType romType;
RomSize romSize;
SramSize sramSize;
CountryCode countryCode;
License license;
uint8_t version;
uint16_t checksumComplement;
uint16_t checksum;
uint16_t nmiVblVector;
uint16_t resetVector;
};
typedef struct CpuCallbacks {
std::function<uint8_t(uint32_t)> read_byte;
std::function<void(uint32_t, uint8_t)> write_byte;
@@ -161,17 +99,13 @@ class Memory {
* system.
*
*/
class MemoryImpl : public Memory, public Loggable {
class MemoryImpl : public Memory {
public:
uint32_t romSize;
uint32_t sramSize;
void Initialize(const std::vector<uint8_t>& romData, bool verbose = false);
uint16_t GetHeaderOffset() {
uint8_t mapMode = memory_[(0x00 << 16) + 0xFFD5];
uint16_t offset;
switch (mapMode & 0x07) {
switch (memory_[(0x00 << 16) + 0xFFD5] & 0x07) {
case 0: // LoROM
offset = 0x7FC0;
break;
@@ -190,8 +124,6 @@ class MemoryImpl : public Memory, public Loggable {
return offset;
}
memory::RomInfo ReadRomHeader();
uint8_t cart_read(uint8_t bank, uint16_t adr);
void cart_write(uint8_t bank, uint16_t adr, uint8_t val);
@@ -348,6 +280,10 @@ class MemoryImpl : public Memory, public Loggable {
bool pal_timing_ = false;
// Memory regions
uint32_t rom_size_;
uint32_t sram_size_;
// Frame timing
uint16_t h_pos_ = 0;
uint16_t v_pos_ = 0;
@@ -355,9 +291,6 @@ class MemoryImpl : public Memory, public Loggable {
// Dma State
uint8_t dma_state_ = 0;
// Dma Channels
DmaChannel channel[8];
// Open bus
uint8_t open_bus_ = 0;
@@ -367,12 +300,13 @@ class MemoryImpl : public Memory, public Loggable {
// Cart Type
uint8_t type_ = 1;
// Dma Channels
DmaChannel channel[8];
// Memory (64KB)
std::vector<uint8_t> memory_;
};
void DrawSnesMemoryMapping(const MemoryImpl& memory);
} // namespace memory
} // namespace emu
} // namespace app

View File

@@ -1,229 +0,0 @@
#ifndef YAZE_TEST_MOCK_MOCK_MEMORY_H
#define YAZE_TEST_MOCK_MOCK_MEMORY_H
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "app/emu/cpu/clock.h"
#include "app/emu/cpu/cpu.h"
#include "app/emu/memory/memory.h"
namespace yaze {
namespace app {
namespace emu {
namespace memory {
/**
* @brief Mock CPU class for testing
*/
class MockClock : public Clock {
public:
MOCK_METHOD(void, UpdateClock, (double delta), (override));
MOCK_METHOD(unsigned long long, GetCycleCount, (), (const, override));
MOCK_METHOD(void, ResetAccumulatedTime, (), (override));
MOCK_METHOD(void, SetFrequency, (float new_frequency), (override));
MOCK_METHOD(float, GetFrequency, (), (const, override));
};
/**
* @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 {
public:
MOCK_CONST_METHOD1(ReadByte, uint8_t(uint32_t address));
MOCK_CONST_METHOD1(ReadWord, uint16_t(uint32_t address));
MOCK_CONST_METHOD1(ReadWordLong, uint32_t(uint32_t address));
MOCK_METHOD(std::vector<uint8_t>, ReadByteVector,
(uint32_t address, uint16_t length), (const, override));
MOCK_METHOD2(WriteByte, void(uint32_t address, uint8_t value));
MOCK_METHOD2(WriteWord, void(uint32_t address, uint16_t value));
MOCK_METHOD2(WriteLong, void(uint32_t address, uint32_t value));
MOCK_METHOD1(PushByte, void(uint8_t value));
MOCK_METHOD0(PopByte, uint8_t());
MOCK_METHOD1(PushWord, void(uint16_t value));
MOCK_METHOD0(PopWord, uint16_t());
MOCK_METHOD1(PushLong, void(uint32_t value));
MOCK_METHOD0(PopLong, uint32_t());
MOCK_CONST_METHOD0(SP, uint16_t());
MOCK_METHOD1(SetSP, void(uint16_t value));
MOCK_METHOD1(SetMemory, void(const std::vector<uint8_t>& data));
MOCK_METHOD1(LoadData, void(const std::vector<uint8_t>& data));
MOCK_METHOD0(ClearMemory, void());
MOCK_CONST_METHOD1(at, uint8_t(int i));
uint8_t operator[](int i) const override { return memory_[i]; }
MOCK_METHOD0(init_hdma_request, void());
MOCK_METHOD0(run_hdma_request, void());
MOCK_METHOD1(set_hdma_run_requested, void(bool value));
MOCK_METHOD1(set_hdma_init_requested, void(bool value));
MOCK_CONST_METHOD0(hdma_init_requested, bool());
MOCK_CONST_METHOD0(hdma_run_requested, bool());
MOCK_METHOD1(set_pal_timing, void(bool value));
MOCK_CONST_METHOD0(pal_timing, bool());
MOCK_CONST_METHOD0(h_pos, uint16_t());
MOCK_CONST_METHOD0(v_pos, uint16_t());
MOCK_METHOD1(set_h_pos, void(uint16_t value));
MOCK_METHOD1(set_v_pos, void(uint16_t value));
MOCK_METHOD1(set_open_bus, void(uint8_t value));
MOCK_CONST_METHOD0(open_bus, uint8_t());
void SetMemoryContents(const std::vector<uint8_t>& data) {
if (data.size() > memory_.size()) {
memory_.resize(data.size());
}
std::copy(data.begin(), data.end(), memory_.begin());
}
void SetMemoryContents(const std::vector<uint16_t>& data) {
if (data.size() > memory_.size()) {
memory_.resize(data.size());
}
int i = 0;
for (const auto& each : data) {
memory_[i] = each & 0xFF;
memory_[i + 1] = (each >> 8) & 0xFF;
i += 2;
}
}
void InsertMemory(const uint64_t address, const std::vector<uint8_t>& data) {
if (address > memory_.size()) {
memory_.resize(address + data.size());
}
int i = 0;
for (const auto& each : data) {
memory_[address + i] = each;
i++;
}
}
// 16MB = 0x1000000
// 02MB = 0x200000
void Initialize(const std::vector<uint8_t>& romData) {
// 16 MB, simplifying the memory layout for testing
memory_.resize(0x1000000);
// Clear memory
std::fill(memory_.begin(), memory_.end(), 0);
// Load ROM data into mock memory
size_t romSize = romData.size();
size_t romAddress = 0;
const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB
for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) {
for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) {
if (romAddress < romSize) {
std::copy(romData.begin() + romAddress,
romData.begin() + romAddress + ROM_CHUNK_SIZE,
memory_.begin() + (bank << 16) + offset);
romAddress += ROM_CHUNK_SIZE;
}
}
}
}
void Init() {
ON_CALL(*this, ReadByte(::testing::_))
.WillByDefault(
[this](uint32_t address) { return memory_.at(address); });
ON_CALL(*this, ReadWord(::testing::_))
.WillByDefault([this](uint32_t address) {
return static_cast<uint16_t>(memory_.at(address)) |
(static_cast<uint16_t>(memory_.at(address + 1)) << 8);
});
ON_CALL(*this, ReadWordLong(::testing::_))
.WillByDefault([this](uint32_t address) {
return static_cast<uint32_t>(memory_.at(address)) |
(static_cast<uint32_t>(memory_.at(address + 1)) << 8) |
(static_cast<uint32_t>(memory_.at(address + 2)) << 16);
});
ON_CALL(*this, ReadByteVector(::testing::_, ::testing::_))
.WillByDefault([this](uint32_t address, uint16_t length) {
std::vector<uint8_t> data;
for (int i = 0; i < length; i++) {
data.push_back(memory_.at(address + i));
}
return data;
});
ON_CALL(*this, WriteByte(::testing::_, ::testing::_))
.WillByDefault([this](uint32_t address, uint8_t value) {
memory_[address] = value;
});
ON_CALL(*this, WriteWord(::testing::_, ::testing::_))
.WillByDefault([this](uint32_t address, uint16_t value) {
memory_[address] = value & 0xFF;
memory_[address + 1] = (value >> 8) & 0xFF;
});
ON_CALL(*this, PushByte(::testing::_)).WillByDefault([this](uint8_t value) {
memory_.at(SP_--) = value;
});
ON_CALL(*this, PopByte()).WillByDefault([this]() {
uint8_t value = memory_.at(SP_);
this->SetSP(SP_ + 1);
return value;
});
ON_CALL(*this, PushWord(::testing::_))
.WillByDefault([this](uint16_t value) {
memory_.at(SP_) = value & 0xFF;
memory_.at(SP_ + 1) = (value >> 8) & 0xFF;
this->SetSP(SP_ - 2);
});
ON_CALL(*this, PopWord()).WillByDefault([this]() {
uint16_t value = static_cast<uint16_t>(memory_.at(SP_)) |
(static_cast<uint16_t>(memory_.at(SP_ + 1)) << 8);
this->SetSP(SP_ + 2);
return value;
});
ON_CALL(*this, PushLong(::testing::_))
.WillByDefault([this](uint32_t value) {
memory_.at(SP_) = value & 0xFF;
memory_.at(SP_ + 1) = (value >> 8) & 0xFF;
memory_.at(SP_ + 2) = (value >> 16) & 0xFF;
});
ON_CALL(*this, PopLong()).WillByDefault([this]() {
uint32_t value = static_cast<uint32_t>(memory_.at(SP_)) |
(static_cast<uint32_t>(memory_.at(SP_ + 1)) << 8) |
(static_cast<uint32_t>(memory_.at(SP_ + 2)) << 16);
this->SetSP(SP_ + 3);
return value;
});
ON_CALL(*this, SP()).WillByDefault([this]() { return SP_; });
ON_CALL(*this, SetSP(::testing::_)).WillByDefault([this](uint16_t value) {
SP_ = value;
});
ON_CALL(*this, ClearMemory()).WillByDefault([this]() {
memory_.resize(64000, 0x00);
});
}
std::vector<uint8_t> memory_;
uint16_t SP_ = 0x01FF;
};
} // namespace memory
} // namespace emu
} // namespace app
} // namespace yaze
#endif // YAZE_TEST_MOCK_MOCK_MEMORY_H

View File

@@ -9,7 +9,6 @@
#include "app/emu/audio/spc700.h"
#include "app/emu/cpu/clock.h"
#include "app/emu/cpu/cpu.h"
#include "app/emu/debug/debugger.h"
#include "app/emu/memory/dma.h"
#include "app/emu/memory/memory.h"
#include "app/emu/video/ppu.h"

View File

@@ -2,18 +2,12 @@
#define YAZE_APP_EMU_SNES_H
#include <cstdint>
#include <memory>
#include <string>
#include <thread>
#include "app/emu/audio/apu.h"
#include "app/emu/audio/spc700.h"
#include "app/emu/cpu/clock.h"
#include "app/emu/cpu/cpu.h"
#include "app/emu/debug/debugger.h"
#include "app/emu/memory/memory.h"
#include "app/emu/video/ppu.h"
#include "app/rom.h"
namespace yaze {
namespace app {
@@ -80,7 +74,6 @@ class SNES {
private:
// Components of the SNES
ClockImpl clock_;
Debugger debugger;
memory::MemoryImpl memory_;
memory::CpuCallbacks cpu_callbacks_ = {
@@ -141,4 +134,4 @@ class SNES {
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EMU_SNES_H
#endif // YAZE_APP_EMU_SNES_H

View File

@@ -1,29 +1,32 @@
#include "bitmap.h"
#include <SDL.h>
#if YAZE_LIB_PNG == 1
#include <png.h>
#endif
#include <cstdint>
#include <memory>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "app/core/constants.h"
#include "app/gfx/snes_palette.h"
#define SDL_RETURN_IF_ERROR() \
if (SDL_GetError() != nullptr) { \
return absl::InternalError(SDL_GetError()); \
}
namespace yaze {
namespace app {
namespace gfx {
namespace {
void GrayscalePalette(SDL_Palette *palette) {
for (int i = 0; i < 8; i++) {
palette->colors[i].r = i * 31;
palette->colors[i].g = i * 31;
palette->colors[i].b = i * 31;
}
}
using core::SDL_Surface_Deleter;
using core::SDL_Texture_Deleter;
#if YAZE_LIB_PNG == 1
namespace png_internal {
void PngWriteCallback(png_structp png_ptr, png_bytep data, png_size_t length) {
std::vector<uint8_t> *p = (std::vector<uint8_t> *)png_get_io_ptr(png_ptr);
@@ -47,20 +50,7 @@ void PngReadCallback(png_structp png_ptr, png_bytep outBytes,
}
}
Uint32 GetSnesPixelFormat(int format) {
switch (format) {
case 0:
return SDL_PIXELFORMAT_INDEX8;
case 1:
return SNES_PIXELFORMAT_2BPP;
case 2:
return SNES_PIXELFORMAT_4BPP;
case 3:
return SNES_PIXELFORMAT_8BPP;
}
return SDL_PIXELFORMAT_INDEX8;
}
} // namespace
} // namespace png_internal
bool ConvertSurfaceToPNG(SDL_Surface *surface, std::vector<uint8_t> &buffer) {
png_structp png_ptr = png_create_write_struct("1.6.40", NULL, NULL, NULL);
@@ -82,7 +72,7 @@ bool ConvertSurfaceToPNG(SDL_Surface *surface, std::vector<uint8_t> &buffer) {
return false;
}
png_set_write_fn(png_ptr, &buffer, PngWriteCallback, NULL);
png_set_write_fn(png_ptr, &buffer, png_internal::PngWriteCallback, NULL);
png_colorp pal_ptr;
@@ -154,7 +144,7 @@ void ConvertPngToSurface(const std::vector<uint8_t> &png_data,
}
// Set our custom read function
png_set_read_fn(png_ptr, &data, PngReadCallback);
png_set_read_fn(png_ptr, &data, png_internal::PngReadCallback);
// Read the PNG info
png_read_info(png_ptr, info_ptr);
@@ -197,40 +187,78 @@ void ConvertPngToSurface(const std::vector<uint8_t> &png_data,
SDL_Log("Successfully created SDL_Surface from PNG data");
}
Bitmap::Bitmap(int width, int height, int depth, int data_size) {
Create(width, height, depth, Bytes(data_size, 0));
std::vector<uint8_t> Bitmap::GetPngData() {
ConvertSurfaceToPNG(surface_.get(), png_data_);
return png_data_;
}
void Bitmap::Create(int width, int height, int depth, const Bytes &data) {
active_ = true;
width_ = width;
height_ = height;
depth_ = depth;
data_ = data;
data_size_ = data.size();
pixel_data_ = data_.data();
surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_,
SDL_PIXELFORMAT_INDEX8),
SDL_Surface_Deleter());
surface_->pixels = pixel_data_;
GrayscalePalette(surface_->format->palette);
active_ = true;
#endif // YAZE_LIB_PNG
namespace {
void GrayscalePalette(SDL_Palette *palette) {
for (int i = 0; i < 8; i++) {
palette->colors[i].r = i * 31;
palette->colors[i].g = i * 31;
palette->colors[i].b = i * 31;
}
}
Uint32 GetSnesPixelFormat(int format) {
switch (format) {
case 0:
return SDL_PIXELFORMAT_INDEX8;
case 1:
return SNES_PIXELFORMAT_2BPP;
case 2:
return SNES_PIXELFORMAT_4BPP;
case 3:
return SNES_PIXELFORMAT_8BPP;
}
return SDL_PIXELFORMAT_INDEX8;
}
} // namespace
void Bitmap::SaveSurfaceToFile(std::string_view filename) {
SDL_SaveBMP(surface_.get(), filename.data());
}
Bitmap::Bitmap(int width, int height, int depth, int data_size) {
Create(width, height, depth, std::vector<uint8_t>(data_size, 0));
}
void Bitmap::Create(int width, int height, int depth,
const std::vector<uint8_t> &data) {
Create(width, height, depth, kIndexed, data);
}
void Bitmap::Create(int width, int height, int depth, int format,
const Bytes &data) {
const std::vector<uint8_t> &data) {
if (data.empty()) {
SDL_Log("Bitmap data is empty\n");
active_ = false;
return;
}
active_ = true;
width_ = width;
height_ = height;
depth_ = depth;
data_ = data;
data_size_ = data.size();
if (data_size_ == 0) {
SDL_Log("Data provided to Bitmap is empty.\n");
return;
}
pixel_data_ = data_.data();
surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
surface_ = std::shared_ptr<SDL_Surface>{
SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_,
GetSnesPixelFormat(format)),
SDL_Surface_Deleter());
SDL_Surface_Deleter{}};
if (surface_ == nullptr) {
SDL_Log("SDL_CreateRGBSurfaceWithFormat failed: %s\n", SDL_GetError());
active_ = false;
return;
}
surface_->pixels = pixel_data_;
active_ = true;
}
@@ -242,6 +270,11 @@ void Bitmap::Reformat(int format) {
SDL_Surface_Deleter());
surface_->pixels = pixel_data_;
active_ = true;
auto apply_palette = ApplyPalette(palette_);
if (!apply_palette.ok()) {
SDL_Log("Failed to apply palette: %s\n", apply_palette.message().data());
active_ = false;
}
}
void Bitmap::CreateTexture(SDL_Renderer *renderer) {
@@ -259,6 +292,22 @@ void Bitmap::CreateTexture(SDL_Renderer *renderer) {
SDL_Log("SDL_CreateTextureFromSurface failed: %s\n", SDL_GetError());
}
converted_surface_ = std::shared_ptr<SDL_Surface>{
SDL_ConvertSurfaceFormat(surface_.get(), SDL_PIXELFORMAT_ARGB8888, 0),
SDL_Surface_Deleter{}};
if (converted_surface_ == nullptr) {
SDL_Log("SDL_ConvertSurfaceFormat failed: %s\n", SDL_GetError());
return;
}
SDL_LockTexture(texture_.get(), nullptr, (void **)&texture_pixels,
&converted_surface_->pitch);
memcpy(texture_pixels, converted_surface_->pixels,
converted_surface_->h * converted_surface_->pitch);
SDL_UnlockTexture(texture_.get());
}
void Bitmap::UpdateTexture(SDL_Renderer *renderer) {
SDL_Surface *converted_surface =
SDL_ConvertSurfaceFormat(surface_.get(), SDL_PIXELFORMAT_ARGB8888, 0);
if (converted_surface) {
@@ -275,92 +324,32 @@ void Bitmap::CreateTexture(SDL_Renderer *renderer) {
SDL_UnlockTexture(texture_.get());
}
void Bitmap::UpdateTexture(SDL_Renderer *renderer, bool use_sdl_update) {
SDL_Surface *converted_surface =
SDL_ConvertSurfaceFormat(surface_.get(), SDL_PIXELFORMAT_ARGB8888, 0);
if (converted_surface) {
converted_surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
converted_surface, SDL_Surface_Deleter());
} else {
SDL_Log("SDL_ConvertSurfaceFormat failed: %s\n", SDL_GetError());
}
SDL_LockTexture(texture_.get(), nullptr, (void **)&texture_pixels,
&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);
}
SDL_UnlockTexture(texture_.get());
}
void Bitmap::CreateTexture(std::shared_ptr<SDL_Renderer> renderer) {
texture_ = std::shared_ptr<SDL_Texture>{
SDL_CreateTextureFromSurface(renderer.get(), surface_.get()),
SDL_Texture_Deleter{}};
}
void Bitmap::UpdateTexture(std::shared_ptr<SDL_Renderer> renderer) {
texture_ = std::shared_ptr<SDL_Texture>{
SDL_CreateTextureFromSurface(renderer.get(), surface_.get()),
SDL_Texture_Deleter{}};
}
void Bitmap::SaveSurfaceToFile(std::string_view filename) {
SDL_SaveBMP(surface_.get(), filename.data());
}
void Bitmap::SetSurface(SDL_Surface *surface) {
surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
surface, SDL_Surface_Deleter());
}
std::vector<uint8_t> Bitmap::GetPngData() {
ConvertSurfaceToPNG(surface_.get(), png_data_);
return png_data_;
}
void Bitmap::LoadFromPngData(const std::vector<uint8_t> &png_data, int width,
int height) {
width_ = width;
height_ = height;
SDL_Surface *surface = surface_.get();
ConvertPngToSurface(png_data, &surface);
surface_.reset(surface);
}
// Convert SNESPalette to SDL_Palette for surface.
absl::Status Bitmap::ApplyPalette(const SnesPalette &palette) {
if (surface_ == nullptr) {
return absl::FailedPreconditionError("Surface is null");
return absl::FailedPreconditionError(
"Surface is null. Palette not applied");
}
if (surface_->format == nullptr || surface_->format->palette == nullptr) {
return absl::FailedPreconditionError("Surface format or palette is null");
return absl::FailedPreconditionError(
"Surface format or palette is null. Palette not applied.");
}
palette_ = palette;
SDL_Palette *sdlPalette = surface_->format->palette;
if (sdlPalette == nullptr) {
SDL_Palette *sdl_palette = surface_->format->palette;
if (sdl_palette == nullptr) {
return absl::InternalError("Failed to get SDL palette");
}
SDL_UnlockSurface(surface_.get());
for (int i = 0; i < palette.size(); ++i) {
for (size_t i = 0; i < palette.size(); ++i) {
ASSIGN_OR_RETURN(gfx::SnesColor pal_color, palette.GetColor(i));
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_palette->colors[i].r = pal_color.rgb().x;
sdl_palette->colors[i].g = pal_color.rgb().y;
sdl_palette->colors[i].b = pal_color.rgb().z;
sdl_palette->colors[i].a = pal_color.rgb().w;
}
SDL_LockSurface(surface_.get());
// SDL_RETURN_IF_ERROR()
return absl::OkStatus();
}
@@ -369,7 +358,7 @@ absl::Status Bitmap::ApplyPaletteFromPaletteGroup(const SnesPalette &palette,
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) {
for (size_t 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;
@@ -384,11 +373,29 @@ absl::Status Bitmap::ApplyPaletteFromPaletteGroup(const SnesPalette &palette,
}
}
SDL_LockSurface(surface_.get());
// SDL_RETURN_IF_ERROR()
return absl::OkStatus();
}
absl::Status Bitmap::ApplyPaletteWithTransparent(const SnesPalette &palette,
int index, int length) {
size_t index, int length) {
if (index < 0 || index >= palette.size()) {
return absl::InvalidArgumentError("Invalid palette index");
}
if (length < 0 || length > palette.size()) {
return absl::InvalidArgumentError("Invalid palette length");
}
if (index + length > palette.size()) {
return absl::InvalidArgumentError("Palette index + length exceeds size");
}
if (surface_ == nullptr) {
return absl::FailedPreconditionError(
"Surface is null. Palette not applied");
}
auto start_index = index * 7;
palette_ = palette.sub_palette(start_index, start_index + 7);
std::vector<ImVec4> colors;
@@ -408,12 +415,13 @@ absl::Status Bitmap::ApplyPaletteWithTransparent(const SnesPalette &palette,
i++;
}
SDL_LockSurface(surface_.get());
// SDL_RETURN_IF_ERROR()
return absl::OkStatus();
}
void Bitmap::ApplyPalette(const std::vector<SDL_Color> &palette) {
SDL_UnlockSurface(surface_.get());
for (int i = 0; i < palette.size(); ++i) {
for (size_t i = 0; i < palette.size(); ++i) {
surface_->format->palette->colors[i].r = palette[i].r;
surface_->format->palette->colors[i].g = palette[i].g;
surface_->format->palette->colors[i].b = palette[i].b;
@@ -422,6 +430,57 @@ void Bitmap::ApplyPalette(const std::vector<SDL_Color> &palette) {
SDL_LockSurface(surface_.get());
}
void Bitmap::Get8x8Tile(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 * 8) % width_;
int tile_y = (y * 8) % height_;
for (int i = 0; i < 8; i++) {
int row_offset = tile_offset + ((tile_y + i) * width_);
for (int j = 0; j < 8; j++) {
int pixel_offset = row_offset + (tile_x + j);
int pixel_value = data_[pixel_offset];
tile_data[tile_data_offset] = pixel_value;
tile_data_offset++;
}
}
}
void Bitmap::Get16x16Tile(int tile_x, int tile_y,
std::vector<uint8_t> &tile_data,
int &tile_data_offset) {
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 Bitmap::WriteColor(int position, const ImVec4 &color) {
// Convert ImVec4 (RGBA) to SDL_Color (RGBA)
SDL_Color sdl_color;
sdl_color.r = static_cast<Uint8>(color.x * 255);
sdl_color.g = static_cast<Uint8>(color.y * 255);
sdl_color.b = static_cast<Uint8>(color.z * 255);
sdl_color.a = static_cast<Uint8>(color.w * 255);
// Map SDL_Color to the nearest color index in the surface's palette
Uint8 index =
SDL_MapRGB(surface_->format, sdl_color.r, sdl_color.g, sdl_color.b);
// Write the color index to the pixel data
pixel_data_[position] = index;
modified_ = true;
}
} // namespace gfx
} // namespace app
} // namespace yaze

View File

@@ -6,11 +6,9 @@
#include <cstdint>
#include <memory>
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "app/core/constants.h"
#include "app/core/utils/sdl_deleter.h"
#include "app/gfx/snes_palette.h"
namespace yaze {
@@ -22,6 +20,10 @@ namespace app {
*/
namespace gfx {
// Same as SDL_PIXELFORMAT_INDEX8 for reference
constexpr Uint32 SNES_PIXELFORMAT_INDEXED =
SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX8, 0, 0, 8, 1);
constexpr Uint32 SNES_PIXELFORMAT_2BPP = SDL_DEFINE_PIXELFORMAT(
/*type=*/SDL_PIXELTYPE_INDEX8, /*order=*/0,
/*layouts=*/0, /*bits=*/2, /*bytes=*/1);
@@ -34,13 +36,14 @@ constexpr Uint32 SNES_PIXELFORMAT_8BPP = SDL_DEFINE_PIXELFORMAT(
/*type=*/SDL_PIXELTYPE_INDEX8, /*order=*/0,
/*layouts=*/0, /*bits=*/8, /*bytes=*/1);
// SDL_PIXELFORMAT_INDEX8 =
// SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX8, 0, 0, 8, 1),
constexpr int kFormat2bppIndexed = 1;
constexpr int kFormat4bppIndexed = 2;
constexpr int kFormat8bppIndexed = 3;
enum BitmapFormat {
kIndexed = 0,
k2bpp = 1,
k4bpp = 2,
k8bpp = 3,
};
#if YAZE_LIB_PNG == 1
/**
* @brief Convert SDL_Surface to PNG image data.
*/
@@ -51,6 +54,7 @@ bool ConvertSurfaceToPNG(SDL_Surface *surface, std::vector<uint8_t> &buffer);
*/
void ConvertPngToSurface(const std::vector<uint8_t> &png_data,
SDL_Surface **outSurface);
#endif
/**
* @brief Represents a bitmap image.
@@ -65,11 +69,11 @@ class Bitmap {
Bitmap() = default;
Bitmap(int width, int height, int depth, int data_size);
Bitmap(int width, int height, int depth, const Bytes &data)
Bitmap(int width, int height, int depth, const std::vector<uint8_t> &data)
: width_(width), height_(height), depth_(depth), data_(data) {
Create(width, height, depth, data);
}
Bitmap(int width, int height, int depth, const Bytes &data,
Bitmap(int width, int height, int depth, const std::vector<uint8_t> &data,
const SnesPalette &palette)
: width_(width),
height_(height),
@@ -82,11 +86,19 @@ class Bitmap {
}
}
#if YAZE_LIB_PNG == 1
std::vector<uint8_t> GetPngData();
#endif
void SaveSurfaceToFile(std::string_view filename);
/**
* @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, int format, const Bytes &data);
void Create(int width, int height, int depth,
const std::vector<uint8_t> &data);
void Create(int width, int height, int depth, int format,
const std::vector<uint8_t> &data);
void Reformat(int format);
@@ -96,31 +108,29 @@ class Bitmap {
* 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(SDL_Renderer *renderer);
/**
* @brief Updates the underlying SDL_Texture when it already exists.
*/
void UpdateTexture(std::shared_ptr<SDL_Renderer> renderer);
void CreateTexture(SDL_Renderer *renderer);
void UpdateTexture(SDL_Renderer *renderer, bool use_sdl_update = false);
void SaveSurfaceToFile(std::string_view filename);
void SetSurface(SDL_Surface *surface);
std::vector<uint8_t> GetPngData();
void LoadFromPngData(const std::vector<uint8_t> &png_data, int width,
int height);
void UpdateTexture(SDL_Renderer *renderer);
/**
* @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);
size_t index, int length = 7);
void ApplyPalette(const std::vector<SDL_Color> &palette);
absl::Status ApplyPaletteFromPaletteGroup(const SnesPalette &palette,
int palette_id);
void Get8x8Tile(int tile_index, int x, int y, std::vector<uint8_t> &tile_data,
int &tile_data_offset);
void Get16x16Tile(int tile_x, int tile_y, std::vector<uint8_t> &tile_data,
int &tile_data_offset);
void WriteToPixel(int position, uchar value) {
if (pixel_data_ == nullptr) {
pixel_data_ = data_.data();
@@ -138,73 +148,7 @@ class Bitmap {
modified_ = true;
}
void Get8x8Tile(int tile_index, int x, int y, std::vector<uint8_t> &tile_data,
int &tile_data_offset) {
int tile_offset = tile_index * (width_ * height_);
int tile_x = (x * 8) % width_;
int tile_y = (y * 8) % height_;
for (int i = 0; i < 8; i++) {
int row_offset = tile_offset + ((tile_y + i) * width_);
for (int j = 0; j < 8; j++) {
int pixel_offset = row_offset + (tile_x + j);
int pixel_value = data_[pixel_offset];
tile_data[tile_data_offset] = pixel_value;
tile_data_offset++;
}
}
}
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) {
// Convert ImVec4 (RGBA) to SDL_Color (RGBA)
SDL_Color sdl_color;
sdl_color.r = static_cast<Uint8>(color.x * 255);
sdl_color.g = static_cast<Uint8>(color.y * 255);
sdl_color.b = static_cast<Uint8>(color.z * 255);
sdl_color.a = static_cast<Uint8>(color.w * 255);
// Map SDL_Color to the nearest color index in the surface's palette
Uint8 index =
SDL_MapRGB(surface_->format, sdl_color.r, sdl_color.g, sdl_color.b);
// Write the color index to the pixel data
pixel_data_[position] = index;
modified_ = true;
}
void WriteColor(int position, const ImVec4 &color);
void Cleanup() {
active_ = false;
@@ -212,7 +156,7 @@ class Bitmap {
height_ = 0;
depth_ = 0;
data_size_ = 0;
palette_.Clear();
palette_.clear();
}
auto sdl_palette() {
@@ -236,45 +180,28 @@ class Bitmap {
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; }
auto vector() const { return data_; }
auto at(int i) const { return data_[i]; }
auto texture() const { return texture_.get(); }
auto modified() const { return modified_; }
void set_modified(bool modified) { modified_ = modified; }
auto is_active() const { return active_; }
auto set_active(bool active) { active_ = active; }
void set_active(bool active) { active_ = active; }
void set_data(const std::vector<uint8_t> &data) { data_ = data; }
void set_modified(bool modified) { modified_ = modified; }
private:
struct SDL_Texture_Deleter {
void operator()(SDL_Texture *p) const {
if (p != nullptr) {
SDL_DestroyTexture(p);
}
}
};
struct SDL_Surface_Deleter {
void operator()(SDL_Surface *p) const {
if (p != nullptr) {
SDL_FreeSurface(p);
}
}
};
int width_ = 0;
int height_ = 0;
int depth_ = 0;
int data_size_ = 0;
bool freed_ = false;
bool active_ = false;
bool modified_ = false;
void *texture_pixels = nullptr;
uchar *pixel_data_;
Bytes data_;
uint8_t *pixel_data_ = nullptr;
std::vector<uint8_t> data_;
std::vector<uint8_t> png_data_;
@@ -286,52 +213,8 @@ class Bitmap {
using BitmapTable = std::unordered_map<int, gfx::Bitmap>;
/**
* @brief Hash map container of shared pointers to Bitmaps.
*/
class BitmapManager {
private:
std::unordered_map<int, gfx::Bitmap> bitmap_cache_;
public:
void LoadBitmap(int id, const Bytes &data, int width, int height, int depth) {
bitmap_cache_[id].Create(width, height, depth, data);
}
gfx::Bitmap &operator[](int id) {
auto it = bitmap_cache_.find(id);
if (it != bitmap_cache_.end()) {
return it->second;
}
return bitmap_cache_.begin()->second;
}
gfx::Bitmap &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]; }
void clear_cache() { bitmap_cache_.clear(); }
auto size() const { return bitmap_cache_.size(); }
auto at(int id) const { return bitmap_cache_.at(id); }
using value_type = std::pair<const int, gfx::Bitmap>;
using iterator = std::unordered_map<int, gfx::Bitmap>::iterator;
using const_iterator = std::unordered_map<int, gfx::Bitmap>::const_iterator;
iterator begin() noexcept { return bitmap_cache_.begin(); }
iterator end() noexcept { return bitmap_cache_.end(); }
const_iterator begin() const noexcept { return bitmap_cache_.begin(); }
const_iterator end() const noexcept { return bitmap_cache_.end(); }
const_iterator cbegin() const noexcept { return bitmap_cache_.cbegin(); }
const_iterator cend() const noexcept { return bitmap_cache_.cend(); }
};
} // namespace gfx
} // namespace app
} // namespace yaze
#endif // YAZE_APP_GFX_BITMAP_H
#endif // YAZE_APP_GFX_BITMAP_H

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