backend-infra-engineer: Pre-0.2.2 snapshot (2022)
This commit is contained in:
183
src/CMakeLists.txt
Normal file
183
src/CMakeLists.txt
Normal file
@@ -0,0 +1,183 @@
|
||||
# yaze source files -----------------------------------------------------------
|
||||
set(
|
||||
YAZE_APP_CORE_SRC
|
||||
app/core/common.cc
|
||||
app/core/controller.cc
|
||||
)
|
||||
|
||||
set(
|
||||
YAZE_APP_EDITOR_SRC
|
||||
app/editor/assembly_editor.cc
|
||||
app/editor/dungeon_editor.cc
|
||||
app/editor/master_editor.cc
|
||||
app/editor/music_editor.cc
|
||||
app/editor/overworld_editor.cc
|
||||
app/editor/palette_editor.cc
|
||||
app/editor/screen_editor.cc
|
||||
)
|
||||
|
||||
set(
|
||||
YAZE_APP_GFX_SRC
|
||||
app/gfx/bitmap.cc
|
||||
app/gfx/snes_palette.cc
|
||||
app/gfx/snes_tile.cc
|
||||
)
|
||||
|
||||
set(
|
||||
YAZE_APP_ZELDA3_SRC
|
||||
app/zelda3/inventory.cc
|
||||
app/zelda3/overworld_map.cc
|
||||
app/zelda3/overworld.cc
|
||||
app/zelda3/title_screen.cc
|
||||
app/zelda3/sprite.cc
|
||||
)
|
||||
|
||||
set(
|
||||
YAZE_GUI_SRC
|
||||
gui/canvas.cc
|
||||
gui/input.cc
|
||||
gui/style.cc
|
||||
gui/widgets.cc
|
||||
gui/color.cc
|
||||
)
|
||||
|
||||
# executable creation ---------------------------------------------------------
|
||||
|
||||
add_executable(
|
||||
yaze
|
||||
app/yaze.cc
|
||||
app/rom.cc
|
||||
${YAZE_APP_CORE_SRC}
|
||||
${YAZE_APP_EDITOR_SRC}
|
||||
${YAZE_APP_GFX_SRC}
|
||||
${YAZE_APP_ZELDA3_SRC}
|
||||
${YAZE_GUI_SRC}
|
||||
${ASAR_STATIC_SRC}
|
||||
${SNES_SPC_SOURCES}
|
||||
${IMGUI_SRC}
|
||||
)
|
||||
|
||||
# including libraries ---------------------------------------------------------
|
||||
|
||||
target_include_directories(
|
||||
yaze PUBLIC
|
||||
lib/
|
||||
app/
|
||||
${CMAKE_SOURCE_DIR}/src/
|
||||
${PNG_INCLUDE_DIRS}
|
||||
${SDL2_INCLUDE_DIR}
|
||||
lib/SDL_mixer/include/
|
||||
${GLEW_INCLUDE_DIRS}
|
||||
lib/asar/src/asar/
|
||||
lib/snes_spc/snes_spc/
|
||||
)
|
||||
|
||||
set(SDL_TARGETS SDL2::SDL2)
|
||||
|
||||
if(WIN32 OR MINGW)
|
||||
list(PREPEND SDL_TARGETS SDL2::SDL2main)
|
||||
add_definitions(-DSDL_MAIN_HANDLED)
|
||||
endif()
|
||||
|
||||
# linking libraries -----------------------------------------------------------
|
||||
|
||||
target_link_libraries(
|
||||
yaze PUBLIC
|
||||
${ABSL_TARGETS}
|
||||
${SDL_TARGETS}
|
||||
${SDLMIXER_LIBRARY}
|
||||
SDL2_mixer
|
||||
${PNG_LIBRARIES}
|
||||
${GLEW_LIBRARIES}
|
||||
${OPENGL_LIBRARIES}
|
||||
${CMAKE_DL_LIBS}
|
||||
asar-static
|
||||
snes_spc
|
||||
ImGui
|
||||
)
|
||||
|
||||
if (UNIX)
|
||||
target_compile_definitions(yaze PRIVATE "linux")
|
||||
target_compile_definitions(yaze PRIVATE "stricmp=strcasecmp")
|
||||
endif()
|
||||
|
||||
if(MACOS)
|
||||
set(MACOSX_BUNDLE_ICON_FILE ${CMAKE_SOURCE_DIR}/yaze.ico)
|
||||
set_target_properties(yaze
|
||||
PROPERTIES
|
||||
BUNDLE True
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
# MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/yaze.plist.in
|
||||
)
|
||||
elseif(UNIX)
|
||||
set_target_properties(yaze
|
||||
PROPERTIES
|
||||
BUNDLE True
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
)
|
||||
else()
|
||||
set_target_properties(yaze
|
||||
PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res"
|
||||
)
|
||||
endif()
|
||||
|
||||
# add_subdirectory(app/delta)
|
||||
|
||||
# add_executable(
|
||||
# yaze_delta
|
||||
# app/delta/delta.cc
|
||||
# app/delta/viewer.cc
|
||||
# app/delta/service.cc
|
||||
# app/delta/client.cc
|
||||
# app/rom.cc
|
||||
# ${YAZE_APP_ASM_SRC}
|
||||
# ${YAZE_APP_CORE_SRC}
|
||||
# ${YAZE_APP_EDITOR_SRC}
|
||||
# ${YAZE_APP_GFX_SRC}
|
||||
# ${YAZE_APP_ZELDA3_SRC}
|
||||
# ${YAZE_GUI_SRC}
|
||||
# ${IMGUI_SRC}
|
||||
# ${ASAR_STATIC_SRC}
|
||||
# )
|
||||
|
||||
# target_include_directories(
|
||||
# yaze_delta PUBLIC
|
||||
# lib/
|
||||
# app/
|
||||
# lib/asar/src/
|
||||
# ${ASAR_INCLUDE_DIR}
|
||||
# ${CMAKE_SOURCE_DIR}/src/
|
||||
# ${PNG_INCLUDE_DIRS}
|
||||
# ${SDL2_INCLUDE_DIR}
|
||||
# ${GLEW_INCLUDE_DIRS}
|
||||
# )
|
||||
|
||||
# target_link_libraries(
|
||||
# yaze_delta PUBLIC
|
||||
# ${ABSL_TARGETS}
|
||||
# ${SDL_TARGETS}
|
||||
# ${PNG_LIBRARIES}
|
||||
# ${GLEW_LIBRARIES}
|
||||
# ${OPENGL_LIBRARIES}
|
||||
# ${CMAKE_DL_LIBS}
|
||||
# delta-service
|
||||
# asar-static
|
||||
# ImGui
|
||||
# )
|
||||
# target_compile_definitions(yaze_delta PRIVATE "linux")
|
||||
# target_compile_definitions(yaze_delta PRIVATE "stricmp=strcasecmp")
|
||||
|
||||
# set (source "${CMAKE_SOURCE_DIR}/assets")
|
||||
# set (destination "${CMAKE_CURRENT_BINARY_DIR}/assets")
|
||||
# add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
# COMMAND ${CMAKE_COMMAND} -E create_symlink ${source} ${destination}
|
||||
# DEPENDS ${destination}
|
||||
# COMMENT "symbolic link resources folder from ${source} => ${destination}")
|
||||
Submodule src/Library/imgui deleted from 0b1bcfcc20
63
src/app/core/common.cc
Normal file
63
src/app/core/common.cc
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
unsigned int SnesToPc(unsigned int addr) {
|
||||
if (addr >= 0x808000) {
|
||||
addr -= 0x808000;
|
||||
}
|
||||
unsigned int temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
|
||||
return (temp + 0x0);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (j == ceiling) {
|
||||
result += value;
|
||||
continue;
|
||||
}
|
||||
|
||||
result += (value * power16);
|
||||
power16 *= 16;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
str.replace(start, from.length(), to);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
21
src/app/core/common.h
Normal file
21
src/app/core/common.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef YAZE_CORE_COMMON_H
|
||||
#define YAZE_CORE_COMMON_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
unsigned int SnesToPc(unsigned int addr);
|
||||
int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3);
|
||||
int HexToDec(char *input, int length);
|
||||
bool StringReplace(std::string &str, const std::string &from,
|
||||
const std::string &to);
|
||||
|
||||
} // namespace core
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
1604
src/app/core/constants.h
Normal file
1604
src/app/core/constants.h
Normal file
File diff suppressed because it is too large
Load Diff
229
src/app/core/controller.cc
Normal file
229
src/app/core/controller.cc
Normal file
@@ -0,0 +1,229 @@
|
||||
#include "controller.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_mixer.h>
|
||||
#include <imgui/backends/imgui_impl_sdl.h>
|
||||
#include <imgui/backends/imgui_impl_sdlrenderer.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/master_editor.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/style.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
namespace {
|
||||
|
||||
void InitializeKeymap() {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
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_Tab] = SDL_GetScancodeFromKey(SDLK_TAB);
|
||||
io.KeyMap[ImGuiKey_LeftCtrl] = SDL_GetScancodeFromKey(SDLK_LCTRL);
|
||||
}
|
||||
|
||||
void HandleKeyDown(SDL_Event &event) {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
switch (event.key.keysym.sym) {
|
||||
case SDLK_UP:
|
||||
case SDLK_DOWN:
|
||||
case SDLK_RETURN:
|
||||
case SDLK_BACKSPACE:
|
||||
case SDLK_LSHIFT:
|
||||
case SDLK_LCTRL:
|
||||
case SDLK_TAB:
|
||||
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleKeyUp(SDL_Event &event) {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
io.DeltaTime = 1.0f / 60.0f;
|
||||
io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
|
||||
io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
|
||||
io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
|
||||
io.MouseWheel = static_cast<float>(wheel);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Controller::isActive() const { return active_; }
|
||||
|
||||
absl::Status Controller::onEntry() {
|
||||
RETURN_IF_ERROR(CreateWindow())
|
||||
RETURN_IF_ERROR(CreateRenderer())
|
||||
RETURN_IF_ERROR(CreateGuiContext())
|
||||
InitializeKeymap();
|
||||
master_editor_.SetupScreen(renderer_);
|
||||
active_ = true;
|
||||
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);
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
HandleKeyUp(event);
|
||||
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);
|
||||
}
|
||||
|
||||
void Controller::onLoad() { master_editor_.UpdateScreen(); }
|
||||
|
||||
void Controller::doRender() const {
|
||||
SDL_RenderClear(renderer_.get());
|
||||
ImGui::Render();
|
||||
ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData());
|
||||
SDL_RenderPresent(renderer_.get());
|
||||
}
|
||||
|
||||
void Controller::onExit() const {
|
||||
ImGui_ImplSDLRenderer_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
absl::Status Controller::CreateWindow() {
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
|
||||
} else {
|
||||
window_ = std::unique_ptr<SDL_Window, sdl_deleter>(
|
||||
SDL_CreateWindow("Yet Another Zelda3 Editor", // window title
|
||||
SDL_WINDOWPOS_UNDEFINED, // initial x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // initial y position
|
||||
kScreenWidth, // width, in pixels
|
||||
kScreenHeight, // height, in pixels
|
||||
SDL_WINDOW_RESIZABLE),
|
||||
sdl_deleter());
|
||||
if (window_ == nullptr) {
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
|
||||
}
|
||||
// Initialize SDL_mixer
|
||||
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
|
||||
printf("SDL_mixer could not initialize! SDL_mixer Error: %s\n",
|
||||
Mix_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();
|
||||
}
|
||||
|
||||
absl::Status Controller::CreateGuiContext() {
|
||||
ImGui::CreateContext();
|
||||
|
||||
// Initialize ImGui for SDL
|
||||
ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), renderer_.get());
|
||||
ImGui_ImplSDLRenderer_Init(renderer_.get());
|
||||
|
||||
// Load available fonts
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/Karla-Regular.ttf", 14.0f);
|
||||
|
||||
// merge in icons from Google Material Design
|
||||
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
|
||||
ImFontConfig icons_config;
|
||||
icons_config.MergeMode = true;
|
||||
icons_config.GlyphOffset.y = 5.0f;
|
||||
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||
icons_config.PixelSnapH = true;
|
||||
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, 18.0f, &icons_config,
|
||||
icons_ranges);
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/Roboto-Medium.ttf", 14.0f);
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/Cousine-Regular.ttf", 14.0f);
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/DroidSans.ttf", 16.0f);
|
||||
|
||||
// Set the default style
|
||||
gui::ColorsYaze();
|
||||
|
||||
// Build a new ImGui frame
|
||||
ImGui_ImplSDLRenderer_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame(window_.get());
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
57
src/app/core/controller.h
Normal file
57
src/app/core/controller.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef YAZE_APP_CORE_CONTROLLER_H
|
||||
#define YAZE_APP_CORE_CONTROLLER_H
|
||||
|
||||
#include <SDL.h>
|
||||
#include <imgui/backends/imgui_impl_sdl.h>
|
||||
#include <imgui/backends/imgui_impl_sdlrenderer.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/editor/master_editor.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/style.h"
|
||||
|
||||
int main(int argc, char **argv);
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
class Controller {
|
||||
public:
|
||||
bool isActive() const;
|
||||
absl::Status onEntry();
|
||||
void onInput();
|
||||
void onLoad();
|
||||
void onLoadDelta();
|
||||
void doRender() const;
|
||||
void onExit() const;
|
||||
|
||||
private:
|
||||
struct sdl_deleter {
|
||||
void operator()(SDL_Window *p) const { SDL_DestroyWindow(p); }
|
||||
void operator()(SDL_Renderer *p) const { SDL_DestroyRenderer(p); }
|
||||
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
|
||||
};
|
||||
|
||||
absl::Status CreateWindow();
|
||||
absl::Status CreateRenderer();
|
||||
absl::Status CreateGuiContext();
|
||||
void CloseWindow() { active_ = false; }
|
||||
|
||||
friend int ::main(int argc, char **argv);
|
||||
|
||||
bool active_;
|
||||
editor::MasterEditor master_editor_;
|
||||
std::shared_ptr<SDL_Window> window_;
|
||||
std::shared_ptr<SDL_Renderer> renderer_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_CORE_CONTROLLER_H
|
||||
32
src/app/delta/CMakeLists.txt
Normal file
32
src/app/delta/CMakeLists.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
add_library(delta-service delta.proto)
|
||||
target_link_libraries(delta-service
|
||||
PUBLIC
|
||||
protobuf::libprotobuf
|
||||
gRPC::grpc
|
||||
gRPC::grpc++
|
||||
)
|
||||
|
||||
target_include_directories(delta-service
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${PROTOBUF_INCLUDE_PATH}
|
||||
)
|
||||
|
||||
get_target_property(grpc_cpp_plugin_location gRPC::grpc_cpp_plugin LOCATION)
|
||||
|
||||
# compile the message types
|
||||
protobuf_generate(TARGET delta-service LANGUAGE cpp)
|
||||
|
||||
# compile the GRPC services
|
||||
protobuf_generate(
|
||||
TARGET
|
||||
delta-service
|
||||
LANGUAGE
|
||||
grpc
|
||||
GENERATE_EXTENSIONS
|
||||
.grpc.pb.h
|
||||
.grpc.pb.cc
|
||||
PLUGIN
|
||||
"protoc-gen-grpc=${grpc_cpp_plugin_location}"
|
||||
)
|
||||
54
src/app/delta/client.cc
Normal file
54
src/app/delta/client.cc
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "client.h"
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Channel;
|
||||
using grpc::ClientAsyncResponseReader;
|
||||
using grpc::ClientContext;
|
||||
using grpc::CompletionQueue;
|
||||
using grpc::Server;
|
||||
using grpc::ServerBuilder;
|
||||
using grpc::Status;
|
||||
|
||||
void Client::CreateChannel() {
|
||||
auto channel = grpc::CreateChannel("localhost:50051",
|
||||
grpc::InsecureChannelCredentials());
|
||||
stub_ = ::YazeDelta::NewStub(channel);
|
||||
}
|
||||
|
||||
absl::Status Client::InitRepo(std::string author_name,
|
||||
std::string project_name) {
|
||||
Repository new_repo;
|
||||
new_repo.set_author_name(author_name);
|
||||
new_repo.set_project_name(project_name);
|
||||
new_repo.mutable_tree()->Add();
|
||||
|
||||
InitRequest request;
|
||||
request.set_allocated_repo(&new_repo);
|
||||
|
||||
InitResponse response;
|
||||
Status status = stub_->Init(&rpc_context, request, &response);
|
||||
|
||||
if (!status.ok()) {
|
||||
std::cerr << status.error_code() << ": " << status.error_message()
|
||||
<< std::endl;
|
||||
return absl::InternalError(status.error_message());
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
44
src/app/delta/client.h
Normal file
44
src/app/delta/client.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef YAZE_APP_DELTA_CLIENT_H
|
||||
#define YAZE_APP_DELTA_CLIENT_H
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Channel;
|
||||
using grpc::ClientAsyncResponseReader;
|
||||
using grpc::ClientContext;
|
||||
using grpc::CompletionQueue;
|
||||
using grpc::Server;
|
||||
using grpc::ServerBuilder;
|
||||
using grpc::Status;
|
||||
|
||||
class Client {
|
||||
public:
|
||||
Client() = default;
|
||||
void CreateChannel();
|
||||
absl::Status InitRepo(std::string author_name, std::string project_name);
|
||||
|
||||
private:
|
||||
ClientContext rpc_context;
|
||||
std::vector<Repository> repos_;
|
||||
std::unique_ptr<YazeDelta::Stub> stub_;
|
||||
};
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
32
src/app/delta/delta.cc
Normal file
32
src/app/delta/delta.cc
Normal file
@@ -0,0 +1,32 @@
|
||||
#if defined(_WIN32)
|
||||
#define main SDL_main
|
||||
#endif
|
||||
|
||||
#include "absl/debugging/failure_signal_handler.h"
|
||||
#include "absl/debugging/symbolize.h"
|
||||
#include "app/core/controller.h"
|
||||
#include "app/delta/viewer.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
absl::InitializeSymbolizer(argv[0]);
|
||||
|
||||
absl::FailureSignalHandlerOptions options;
|
||||
absl::InstallFailureSignalHandler(options);
|
||||
|
||||
yaze::app::core::Controller controller;
|
||||
yaze::app::delta::Viewer viewer;
|
||||
|
||||
auto entry_status = controller.onEntry();
|
||||
if (!entry_status.ok()) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
while (controller.isActive()) {
|
||||
controller.onInput();
|
||||
viewer.Update();
|
||||
controller.doRender();
|
||||
}
|
||||
controller.onExit();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
105
src/app/delta/delta.proto
Normal file
105
src/app/delta/delta.proto
Normal file
@@ -0,0 +1,105 @@
|
||||
syntax = "proto3";
|
||||
option cc_enable_arenas = true;
|
||||
|
||||
service YazeDelta {
|
||||
rpc Init(InitRequest) returns (InitResponse) {}
|
||||
|
||||
rpc Clone(CloneRequest) returns (CloneResponse) {}
|
||||
|
||||
rpc Push(PushRequest) returns (PushResponse) {}
|
||||
rpc Pull(PullRequest) returns (PullResponse) {}
|
||||
|
||||
rpc Sync(stream SyncRequest) returns (stream SyncResponse) {}
|
||||
|
||||
rpc CreateBranch(CreateBranchRequest) returns (CreateBranchResponse) {}
|
||||
rpc DeleteBranch(DeleteBranchRequest) returns (DeleteBranchResponse) {}
|
||||
|
||||
rpc Merge(MergeRequest) returns (MergeResponse) {}
|
||||
rpc UndoMerge(UndoMergeRequest) returns (UndoMergeResponse) {}
|
||||
}
|
||||
|
||||
enum ChangeType {
|
||||
OVERWORLD_MAP = 0;
|
||||
DUNGEON_MAP = 1;
|
||||
MONOLOGUE = 2;
|
||||
PALETTE = 3;
|
||||
OBJECT = 4;
|
||||
ASSEMBLY = 5;
|
||||
MISC = 6;
|
||||
}
|
||||
|
||||
message Delta {
|
||||
int64 offset = 1;
|
||||
int64 length = 2;
|
||||
bytes data = 3;
|
||||
ChangeType type = 4;
|
||||
}
|
||||
|
||||
message Commit {
|
||||
int64 commit_id = 1;
|
||||
int64 parent_commit_id = 2;
|
||||
string author_name = 3;
|
||||
string message_header = 4;
|
||||
optional string message_body = 5;
|
||||
repeated Delta delta = 6;
|
||||
int64 signature = 7;
|
||||
}
|
||||
|
||||
message Branch {
|
||||
string branch_name = 1;
|
||||
optional string parent_name = 2;
|
||||
repeated Commit commits = 3;
|
||||
}
|
||||
|
||||
message Repository {
|
||||
string project_name = 1;
|
||||
string author_name = 2;
|
||||
int64 signature = 3;
|
||||
optional bool locked = 4;
|
||||
optional string password = 5;
|
||||
repeated Branch tree = 6;
|
||||
}
|
||||
|
||||
message InitRequest {
|
||||
Repository repo = 1;
|
||||
}
|
||||
|
||||
message InitResponse {
|
||||
int32 response = 1;
|
||||
}
|
||||
|
||||
message CloneRequest {}
|
||||
|
||||
message CloneResponse {}
|
||||
|
||||
message PushRequest {
|
||||
string author_name = 1;
|
||||
string repository_name= 2;
|
||||
string branch_name = 3;
|
||||
repeated Commit commits = 4;
|
||||
}
|
||||
message PushResponse {}
|
||||
|
||||
message PullRequest {
|
||||
string repository_name = 1;
|
||||
string branch_name = 2;
|
||||
repeated Commit commits = 3;
|
||||
}
|
||||
|
||||
message PullResponse {}
|
||||
|
||||
message SyncRequest {}
|
||||
|
||||
message SyncResponse {}
|
||||
|
||||
message CreateBranchRequest {}
|
||||
message CreateBranchResponse {}
|
||||
|
||||
message DeleteBranchRequest {}
|
||||
message DeleteBranchResponse {}
|
||||
|
||||
message MergeRequest {}
|
||||
message MergeResponse {}
|
||||
|
||||
message UndoMergeRequest {}
|
||||
message UndoMergeResponse {}
|
||||
87
src/app/delta/service.cc
Normal file
87
src/app/delta/service.cc
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "service.h"
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Channel;
|
||||
using grpc::ClientAsyncResponseReader;
|
||||
using grpc::ClientContext;
|
||||
using grpc::CompletionQueue;
|
||||
using grpc::Server;
|
||||
using grpc::ServerBuilder;
|
||||
using grpc::Status;
|
||||
|
||||
namespace {
|
||||
auto FindRepository(std::vector<Repository>& repos, const std::string& name) {
|
||||
for (auto& repo : repos) {
|
||||
if (repo.project_name() == name) {
|
||||
return repo.mutable_tree();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto FindBranch(google::protobuf::RepeatedPtrField<Branch>* repo,
|
||||
const std::string& branch_name) {
|
||||
for (auto it = repo->begin(); it != repo->end(); ++it) {
|
||||
if (it->branch_name() == branch_name) {
|
||||
return it->mutable_commits();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Status DeltaService::Init(grpc::ServerContext* context,
|
||||
const InitRequest* request, InitResponse* reply) {
|
||||
std::filesystem::create_directories("./.yaze");
|
||||
repos_.push_back(request->repo());
|
||||
// std::ofstream commit_stream("./.yaze/commits");
|
||||
// for (const auto& repo : repos_) {
|
||||
// for (const auto& branch : repo.tree()) {
|
||||
// for (const auto& commit : branch.commits()) {
|
||||
// commit_stream << commit.DebugString();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status DeltaService::Push(grpc::ServerContext* context,
|
||||
const PushRequest* request, PushResponse* reply) {
|
||||
const auto& repository_name = request->repository_name();
|
||||
const auto& branch_name = request->branch_name();
|
||||
auto repo = FindRepository(repos_, repository_name);
|
||||
auto mutable_commits = FindBranch(repo, branch_name);
|
||||
auto size = request->commits().size();
|
||||
for (int i = 1; i < size; ++i) {
|
||||
*mutable_commits->Add() = request->commits().at(i);
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status DeltaService::Pull(grpc::ServerContext* context,
|
||||
const PullRequest* request, PullResponse* reply) {
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status DeltaService::Clone(grpc::ServerContext* context,
|
||||
const CloneRequest* request, CloneResponse* reply) {
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
48
src/app/delta/service.h
Normal file
48
src/app/delta/service.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef YAZE_APP_DELTA_SERVICE_H
|
||||
#define YAZE_APP_DELTA_SERVICE_H
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Status;
|
||||
|
||||
class DeltaService final : public ::YazeDelta::Service {
|
||||
public:
|
||||
Status Init(grpc::ServerContext* context, const InitRequest* request,
|
||||
InitResponse* reply) override;
|
||||
|
||||
Status Push(grpc::ServerContext* context, const PushRequest* request,
|
||||
PushResponse* reply) override;
|
||||
|
||||
Status Pull(grpc::ServerContext* context, const PullRequest* request,
|
||||
PullResponse* reply) override;
|
||||
|
||||
Status Clone(grpc::ServerContext* context, const CloneRequest* request,
|
||||
CloneResponse* reply) override;
|
||||
|
||||
auto Repos() const { return repos_; }
|
||||
|
||||
private:
|
||||
std::vector<Repository> repos_;
|
||||
};
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
229
src/app/delta/viewer.cc
Normal file
229
src/app/delta/viewer.cc
Normal file
@@ -0,0 +1,229 @@
|
||||
#include "viewer.h"
|
||||
|
||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
namespace {
|
||||
|
||||
constexpr ImGuiWindowFlags kMainEditorFlags =
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
|
||||
|
||||
void NewMasterFrame() {
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui::NewFrame();
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
|
||||
ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
|
||||
|
||||
if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Viewer::Update() {
|
||||
NewMasterFrame();
|
||||
DrawYazeMenu();
|
||||
DrawFileDialog();
|
||||
|
||||
ImGui::Text(ICON_MD_CHANGE_HISTORY);
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("%s", rom_.GetTitle());
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Button(ICON_MD_SYNC);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_ARROW_UPWARD);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_ARROW_DOWNWARD);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_MERGE);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Button(ICON_MD_MANAGE_HISTORY);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_LAN);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_COMMIT);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_DIFFERENCE);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::SetNextItemWidth(75.f);
|
||||
ImGui::Button(ICON_MD_SEND);
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("Server Address", &client_address_);
|
||||
|
||||
ImGui::SetNextItemWidth(75.f);
|
||||
ImGui::Button(ICON_MD_DOWNLOAD);
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("Repository Source", &client_address_);
|
||||
|
||||
ImGui::Separator();
|
||||
DrawBranchTree();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void Viewer::DrawFileDialog() {
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
|
||||
if (ImGuiFileDialog::Instance()->IsOk()) {
|
||||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
rom_.LoadFromFile(filePathName);
|
||||
}
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewer::DrawYazeMenu() {
|
||||
MENU_BAR()
|
||||
DrawFileMenu();
|
||||
DrawViewMenu();
|
||||
END_MENU_BAR()
|
||||
}
|
||||
|
||||
void Viewer::DrawFileMenu() const {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
|
||||
".sfc,.smc", ".");
|
||||
}
|
||||
|
||||
MENU_ITEM2("Save", "Ctrl+S") {}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewer::DrawViewMenu() {
|
||||
static bool show_imgui_metrics = false;
|
||||
static bool show_imgui_style_editor = false;
|
||||
static bool show_memory_editor = false;
|
||||
static bool show_imgui_demo = false;
|
||||
|
||||
if (show_imgui_metrics) {
|
||||
ImGui::ShowMetricsWindow(&show_imgui_metrics);
|
||||
}
|
||||
|
||||
if (show_memory_editor) {
|
||||
static MemoryEditor mem_edit;
|
||||
mem_edit.DrawWindow("Memory Editor", (void*)&rom_, rom_.size());
|
||||
}
|
||||
|
||||
if (show_imgui_demo) {
|
||||
ImGui::ShowDemoWindow();
|
||||
}
|
||||
|
||||
if (show_imgui_style_editor) {
|
||||
ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor);
|
||||
ImGui::ShowStyleEditor();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
ImGui::MenuItem("HEX Editor", nullptr, &show_memory_editor);
|
||||
ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginMenu("GUI Tools")) {
|
||||
ImGui::MenuItem("Metrics (ImGui)", nullptr, &show_imgui_metrics);
|
||||
ImGui::MenuItem("Style Editor (ImGui)", nullptr,
|
||||
&show_imgui_style_editor);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewer::DrawBranchTree() {
|
||||
static ImGuiTableFlags flags =
|
||||
ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH |
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg |
|
||||
ImGuiTableFlags_NoBordersInBody;
|
||||
|
||||
if (ImGui::BeginTable("3ways", 3, flags)) {
|
||||
// The first column will use the default _WidthStretch when ScrollX is Off
|
||||
// and _WidthFixed when ScrollX is On
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide);
|
||||
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed,
|
||||
10 * 12.0f);
|
||||
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
|
||||
10 * 18.0f);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
// Simple storage to output a dummy file-system.
|
||||
struct MyTreeNode {
|
||||
const char* Name;
|
||||
const char* Type;
|
||||
int Size;
|
||||
int ChildIdx;
|
||||
int ChildCount;
|
||||
static void DisplayNode(const MyTreeNode* node,
|
||||
const MyTreeNode* all_nodes) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
const bool is_folder = (node->ChildCount > 0);
|
||||
if (is_folder) {
|
||||
bool open =
|
||||
ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextDisabled("--");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(node->Type);
|
||||
if (open) {
|
||||
for (int child_n = 0; child_n < node->ChildCount; child_n++)
|
||||
DisplayNode(&all_nodes[node->ChildIdx + child_n], all_nodes);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
} else {
|
||||
ImGui::TreeNodeEx(
|
||||
node->Name, ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet |
|
||||
ImGuiTreeNodeFlags_NoTreePushOnOpen |
|
||||
ImGuiTreeNodeFlags_SpanFullWidth);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", node->Size);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(node->Type);
|
||||
}
|
||||
}
|
||||
};
|
||||
static const MyTreeNode nodes[] = {
|
||||
{"lttp-redux", "Repository", -1, 1, 3},
|
||||
{"main", "Branch", -1, 4, 2},
|
||||
{"hyrule-castle", "Branch", -1, 4, 2},
|
||||
{"lost-woods", "Branch", -1, 6, 3},
|
||||
{"Added some bushes", "Commit", 1024, -1, -1},
|
||||
{"Constructed a new house", "Commit", 123000, -1, -1},
|
||||
{"File1_b.wav", "Commit", 456000, -1, -1},
|
||||
{"Image001.png", "Commit", 203128, -1, -1},
|
||||
{"Copy of Image001.png", "Commit", 203256, -1, -1},
|
||||
{"Copy of Image001 (Final2).png", "Commit", 203512, -1, -1},
|
||||
};
|
||||
|
||||
MyTreeNode::DisplayNode(&nodes[0], nodes);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
45
src/app/delta/viewer.h
Normal file
45
src/app/delta/viewer.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef YAZE_APP_DELTA_VIEWER_H
|
||||
#define YAZE_APP_DELTA_VIEWER_H
|
||||
|
||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/delta/client.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
class Viewer {
|
||||
public:
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void DrawFileDialog();
|
||||
|
||||
void DrawYazeMenu();
|
||||
void DrawFileMenu() const;
|
||||
void DrawViewMenu();
|
||||
|
||||
void DrawBranchTree();
|
||||
|
||||
std::string client_address_;
|
||||
|
||||
ROM rom_;
|
||||
Client client_;
|
||||
};
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
115
src/app/editor/assembly_editor.cc
Normal file
115
src/app/editor/assembly_editor.cc
Normal file
@@ -0,0 +1,115 @@
|
||||
#include "assembly_editor.h"
|
||||
|
||||
#include "core/constants.h"
|
||||
#include "gui/widgets.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
AssemblyEditor::AssemblyEditor() {
|
||||
text_editor_.SetLanguageDefinition(gui::widgets::GetAssemblyLanguageDef());
|
||||
text_editor_.SetPalette(TextEditor::GetDarkPalette());
|
||||
}
|
||||
|
||||
void AssemblyEditor::Update() {
|
||||
ImGui::Begin("Assembly Editor", &file_is_loaded_);
|
||||
MENU_BAR()
|
||||
DrawFileMenu();
|
||||
DrawEditMenu();
|
||||
END_MENU_BAR()
|
||||
|
||||
auto cpos = text_editor_.GetCursorPosition();
|
||||
SetEditorText();
|
||||
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||
text_editor_.CanUndo() ? "*" : " ",
|
||||
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||
current_file_.c_str());
|
||||
|
||||
text_editor_.Render("##asm_editor");
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AssemblyEditor::InlineUpdate() {
|
||||
ChangeActiveFile("assets/asm/template_song.asm");
|
||||
auto cpos = text_editor_.GetCursorPosition();
|
||||
SetEditorText();
|
||||
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||
text_editor_.CanUndo() ? "*" : " ",
|
||||
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||
current_file_.c_str());
|
||||
|
||||
text_editor_.Render("##asm_editor", ImVec2(0, 0));
|
||||
}
|
||||
|
||||
void AssemblyEditor::ChangeActiveFile(const std::string& filename) {
|
||||
current_file_ = filename;
|
||||
}
|
||||
|
||||
void AssemblyEditor::DrawFileMenu() {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||
ImGuiFileDialog::Instance()->OpenDialog(
|
||||
"ChooseASMFileDlg", "Open ASM file", ".asm,.txt", ".");
|
||||
}
|
||||
if (ImGui::MenuItem("Save", "Ctrl+S")) {
|
||||
// TODO: Implement this
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseASMFileDlg")) {
|
||||
if (ImGuiFileDialog::Instance()->IsOk()) {
|
||||
ChangeActiveFile(ImGuiFileDialog::Instance()->GetFilePathName());
|
||||
}
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void AssemblyEditor::DrawEditMenu() {
|
||||
if (ImGui::BeginMenu("Edit")) {
|
||||
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {
|
||||
text_editor_.Undo();
|
||||
}
|
||||
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {
|
||||
text_editor_.Redo();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Cut", "Ctrl+X")) {
|
||||
text_editor_.Cut();
|
||||
}
|
||||
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
|
||||
text_editor_.Copy();
|
||||
}
|
||||
if (ImGui::MenuItem("Paste", "Ctrl+V")) {
|
||||
text_editor_.Paste();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Find", "Ctrl+F")) {
|
||||
// TODO: Implement this.
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void AssemblyEditor::SetEditorText() {
|
||||
if (!file_is_loaded_) {
|
||||
std::ifstream t(current_file_);
|
||||
if (t.good()) {
|
||||
std::string str((std::istreambuf_iterator<char>(t)),
|
||||
std::istreambuf_iterator<char>());
|
||||
text_editor_.SetText(str);
|
||||
file_is_loaded_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
38
src/app/editor/assembly_editor.h
Normal file
38
src/app/editor/assembly_editor.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#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>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
class AssemblyEditor {
|
||||
public:
|
||||
AssemblyEditor();
|
||||
|
||||
void Update();
|
||||
void InlineUpdate();
|
||||
void ChangeActiveFile(const std::string &);
|
||||
|
||||
private:
|
||||
void DrawFileMenu();
|
||||
void DrawEditMenu();
|
||||
void SetEditorText();
|
||||
|
||||
bool file_is_loaded_ = false;
|
||||
|
||||
std::string current_file_;
|
||||
TextEditor text_editor_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
61
src/app/editor/dungeon_editor.cc
Normal file
61
src/app/editor/dungeon_editor.cc
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "dungeon_editor.h"
|
||||
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
void DungeonEditor::Update() {
|
||||
DrawToolset();
|
||||
canvas_.DrawBackground();
|
||||
canvas_.DrawContextMenu();
|
||||
canvas_.DrawGrid();
|
||||
canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawToolset() {
|
||||
if (ImGui::BeginTable("DWToolset", 9, toolset_table_flags_, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("#undoTool");
|
||||
ImGui::TableSetupColumn("#redoTool");
|
||||
ImGui::TableSetupColumn("#history");
|
||||
ImGui::TableSetupColumn("#separator");
|
||||
ImGui::TableSetupColumn("#bg1Tool");
|
||||
ImGui::TableSetupColumn("#bg2Tool");
|
||||
ImGui::TableSetupColumn("#bg3Tool");
|
||||
ImGui::TableSetupColumn("#itemTool");
|
||||
ImGui::TableSetupColumn("#spriteTool");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_UNDO);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_REDO);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_MANAGE_HISTORY);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(ICON_MD_MORE_VERT);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_FILTER_1);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_FILTER_2);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_FILTER_3);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_GRASS);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_PEST_CONTROL_RODENT);
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
26
src/app/editor/dungeon_editor.h
Normal file
26
src/app/editor/dungeon_editor.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
||||
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
class DungeonEditor {
|
||||
public:
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void DrawToolset();
|
||||
|
||||
gui::Canvas canvas_;
|
||||
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
|
||||
};
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
322
src/app/editor/master_editor.cc
Normal file
322
src/app/editor/master_editor.cc
Normal file
@@ -0,0 +1,322 @@
|
||||
#include "master_editor.h"
|
||||
|
||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/editor/assembly_editor.h"
|
||||
#include "app/editor/dungeon_editor.h"
|
||||
#include "app/editor/music_editor.h"
|
||||
#include "app/editor/overworld_editor.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
#include "gui/widgets.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr ImGuiWindowFlags kMainEditorFlags =
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
|
||||
|
||||
void NewMasterFrame() {
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
ImGui::NewFrame();
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
|
||||
ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
|
||||
|
||||
if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool BeginCentered(const char *name) {
|
||||
ImGuiIO const &io = ImGui::GetIO();
|
||||
ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
|
||||
ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
ImGuiWindowFlags flags =
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings;
|
||||
return ImGui::Begin(name, nullptr, flags);
|
||||
}
|
||||
|
||||
void DisplayStatus(absl::Status &status) {
|
||||
if (BeginCentered("StatusWindow")) {
|
||||
ImGui::Text("%s", status.ToString().c_str());
|
||||
ImGui::Spacing();
|
||||
ImGui::NextColumn();
|
||||
ImGui::Columns(1);
|
||||
ImGui::Separator();
|
||||
ImGui::NewLine();
|
||||
ImGui::SameLine(270);
|
||||
if (ImGui::Button("OK", ImVec2(200, 0))) {
|
||||
status = absl::OkStatus();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void MasterEditor::SetupScreen(std::shared_ptr<SDL_Renderer> renderer) {
|
||||
sdl_renderer_ = renderer;
|
||||
rom_.SetupRenderer(renderer);
|
||||
}
|
||||
|
||||
void MasterEditor::UpdateScreen() {
|
||||
NewMasterFrame();
|
||||
|
||||
DrawYazeMenu();
|
||||
DrawFileDialog();
|
||||
DrawStatusPopup();
|
||||
DrawAboutPopup();
|
||||
DrawInfoPopup();
|
||||
|
||||
TAB_BAR("##TabBar")
|
||||
DrawOverworldEditor();
|
||||
DrawDungeonEditor();
|
||||
DrawMusicEditor();
|
||||
DrawSpriteEditor();
|
||||
DrawScreenEditor();
|
||||
DrawPaletteEditor();
|
||||
END_TAB_BAR()
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void MasterEditor::DrawFileDialog() {
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
|
||||
if (ImGuiFileDialog::Instance()->IsOk()) {
|
||||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
status_ = rom_.LoadFromFile(filePathName);
|
||||
overworld_editor_.SetupROM(rom_);
|
||||
screen_editor_.SetupROM(rom_);
|
||||
palette_editor_.SetupROM(rom_);
|
||||
}
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawStatusPopup() {
|
||||
if (!status_.ok()) {
|
||||
DisplayStatus(status_);
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawAboutPopup() {
|
||||
if (about_) ImGui::OpenPopup("About");
|
||||
if (ImGui::BeginPopupModal("About", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Yet Another Zelda3 Editor - v0.02");
|
||||
ImGui::Text("Written by: scawful");
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Special Thanks: Zarby89, JaredBrian");
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button("Close", ImVec2(200, 0))) {
|
||||
about_ = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawInfoPopup() {
|
||||
if (rom_info_) ImGui::OpenPopup("ROM Information");
|
||||
if (ImGui::BeginPopupModal("ROM Information", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Title: %s", rom_.GetTitle());
|
||||
ImGui::Text("ROM Size: %ld", rom_.size());
|
||||
|
||||
if (ImGui::Button("Close", ImVec2(200, 0))) {
|
||||
rom_info_ = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawYazeMenu() {
|
||||
MENU_BAR()
|
||||
DrawFileMenu();
|
||||
DrawEditMenu();
|
||||
DrawViewMenu();
|
||||
DrawHelpMenu();
|
||||
END_MENU_BAR()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawFileMenu() const {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
|
||||
".sfc,.smc", ".");
|
||||
}
|
||||
|
||||
MENU_ITEM2("Save", "Ctrl+S") {}
|
||||
MENU_ITEM("Save As..") {}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// TODO: Make these options matter
|
||||
if (ImGui::BeginMenu("Options")) {
|
||||
static bool enabled = true;
|
||||
ImGui::MenuItem("Enabled", "", &enabled);
|
||||
ImGui::BeginChild("child", ImVec2(0, 60), true);
|
||||
for (int i = 0; i < 10; i++) ImGui::Text("Scrolling Text %d", i);
|
||||
ImGui::EndChild();
|
||||
static float f = 0.5f;
|
||||
static int n = 0;
|
||||
ImGui::SliderFloat("Value", &f, 0.0f, 1.0f);
|
||||
ImGui::InputFloat("Input", &f, 0.1f);
|
||||
ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0");
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawEditMenu() {
|
||||
if (ImGui::BeginMenu("Edit")) {
|
||||
MENU_ITEM2("Undo", "Ctrl+Z") { status_ = overworld_editor_.Undo(); }
|
||||
MENU_ITEM2("Redo", "Ctrl+Y") { status_ = overworld_editor_.Redo(); }
|
||||
ImGui::Separator();
|
||||
MENU_ITEM2("Cut", "Ctrl+X") { status_ = overworld_editor_.Cut(); }
|
||||
MENU_ITEM2("Copy", "Ctrl+C") { status_ = overworld_editor_.Copy(); }
|
||||
MENU_ITEM2("Paste", "Ctrl+V") { status_ = overworld_editor_.Paste(); }
|
||||
ImGui::Separator();
|
||||
MENU_ITEM2("Find", "Ctrl+F") {}
|
||||
ImGui::Separator();
|
||||
MENU_ITEM("ROM Information") rom_info_ = true;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawViewMenu() {
|
||||
static bool show_imgui_metrics = false;
|
||||
static bool show_imgui_style_editor = false;
|
||||
static bool show_memory_editor = false;
|
||||
static bool show_asm_editor = false;
|
||||
static bool show_imgui_demo = false;
|
||||
static bool show_memory_viewer = false;
|
||||
|
||||
if (show_imgui_metrics) {
|
||||
ImGui::ShowMetricsWindow(&show_imgui_metrics);
|
||||
}
|
||||
|
||||
if (show_memory_editor) {
|
||||
static MemoryEditor mem_edit;
|
||||
mem_edit.DrawWindow("Memory Editor", (void *)&rom_, rom_.size());
|
||||
}
|
||||
|
||||
if (show_imgui_demo) {
|
||||
ImGui::ShowDemoWindow();
|
||||
}
|
||||
|
||||
if (show_asm_editor) {
|
||||
assembly_editor_.Update();
|
||||
}
|
||||
|
||||
if (show_imgui_style_editor) {
|
||||
ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor);
|
||||
ImGui::ShowStyleEditor();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (show_memory_viewer) {
|
||||
ImGui::Begin("Memory Viewer (ImGui)", &show_memory_viewer);
|
||||
|
||||
const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
|
||||
static ImGuiTableFlags flags =
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
|
||||
ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_RowBg |
|
||||
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX;
|
||||
if (auto outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 5.5f);
|
||||
ImGui::BeginTable("table1", 3, flags, outer_size)) {
|
||||
for (int row = 0; row < 10; row++) {
|
||||
ImGui::TableNextRow();
|
||||
for (int column = 0; column < 3; column++) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Cell %d,%d", column, row);
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
ImGui::MenuItem("HEX Editor", nullptr, &show_memory_editor);
|
||||
ImGui::MenuItem("ASM Editor", nullptr, &show_asm_editor);
|
||||
ImGui::MenuItem("Memory Viewer", nullptr, &show_memory_viewer);
|
||||
ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginMenu("GUI Tools")) {
|
||||
ImGui::MenuItem("Metrics (ImGui)", nullptr, &show_imgui_metrics);
|
||||
ImGui::MenuItem("Style Editor (ImGui)", nullptr,
|
||||
&show_imgui_style_editor);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawHelpMenu() {
|
||||
if (ImGui::BeginMenu("Help")) {
|
||||
if (ImGui::MenuItem("About")) about_ = true;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawOverworldEditor() {
|
||||
TAB_ITEM("Overworld")
|
||||
status_ = overworld_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawDungeonEditor() {
|
||||
TAB_ITEM("Dungeon")
|
||||
dungeon_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawPaletteEditor() {
|
||||
TAB_ITEM("Palettes")
|
||||
status_ = palette_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawScreenEditor() {
|
||||
TAB_ITEM("Screens")
|
||||
screen_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawMusicEditor() {
|
||||
TAB_ITEM("Music")
|
||||
music_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawSpriteEditor() {
|
||||
TAB_ITEM("Sprites")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
72
src/app/editor/master_editor.h
Normal file
72
src/app/editor/master_editor.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_MASTER_EDITOR_H
|
||||
|
||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/editor/assembly_editor.h"
|
||||
#include "app/editor/dungeon_editor.h"
|
||||
#include "app/editor/music_editor.h"
|
||||
#include "app/editor/overworld_editor.h"
|
||||
#include "app/editor/palette_editor.h"
|
||||
#include "app/editor/screen_editor.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
class MasterEditor {
|
||||
public:
|
||||
void SetupScreen(std::shared_ptr<SDL_Renderer> renderer);
|
||||
void UpdateScreen();
|
||||
|
||||
private:
|
||||
void DrawFileDialog();
|
||||
void DrawStatusPopup();
|
||||
void DrawAboutPopup();
|
||||
void DrawInfoPopup();
|
||||
|
||||
void DrawYazeMenu();
|
||||
void DrawFileMenu() const;
|
||||
void DrawEditMenu();
|
||||
void DrawViewMenu();
|
||||
void DrawHelpMenu();
|
||||
|
||||
void DrawOverworldEditor();
|
||||
void DrawDungeonEditor();
|
||||
void DrawPaletteEditor();
|
||||
void DrawMusicEditor();
|
||||
void DrawScreenEditor();
|
||||
void DrawSpriteEditor();
|
||||
|
||||
bool about_ = false;
|
||||
bool rom_info_ = false;
|
||||
|
||||
std::shared_ptr<SDL_Renderer> sdl_renderer_;
|
||||
absl::Status status_;
|
||||
|
||||
AssemblyEditor assembly_editor_;
|
||||
DungeonEditor dungeon_editor_;
|
||||
OverworldEditor overworld_editor_;
|
||||
PaletteEditor palette_editor_;
|
||||
ScreenEditor screen_editor_;
|
||||
MusicEditor music_editor_;
|
||||
ROM rom_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H
|
||||
292
src/app/editor/music_editor.cc
Normal file
292
src/app/editor/music_editor.cc
Normal file
@@ -0,0 +1,292 @@
|
||||
#include "music_editor.h"
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/assembly_editor.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
#include "snes_spc/demo/demo_util.h"
|
||||
#include "snes_spc/demo/wave_writer.h"
|
||||
#include "snes_spc/snes_spc/spc.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
namespace {
|
||||
|
||||
#define BUF_SIZE 2048
|
||||
|
||||
void PlaySPC() {
|
||||
/* Create emulator and filter */
|
||||
SNES_SPC* snes_spc = spc_new();
|
||||
SPC_Filter* filter = spc_filter_new();
|
||||
if (!snes_spc || !filter) error("Out of memory");
|
||||
|
||||
/* Load SPC */
|
||||
{
|
||||
/* Load file into memory */
|
||||
long spc_size;
|
||||
void* spc = load_file("assets/music/hyrule_field.spc", &spc_size);
|
||||
|
||||
/* Load SPC data into emulator */
|
||||
error(spc_load_spc(snes_spc, spc, spc_size));
|
||||
free(spc); /* emulator makes copy of data */
|
||||
|
||||
/* Most SPC files have garbage data in the echo buffer, so clear that */
|
||||
spc_clear_echo(snes_spc);
|
||||
|
||||
/* Clear filter before playing */
|
||||
spc_filter_clear(filter);
|
||||
}
|
||||
|
||||
/* Record 20 seconds to wave file */
|
||||
wave_open(spc_sample_rate, "out.wav");
|
||||
wave_enable_stereo();
|
||||
while (wave_sample_count() < 30 * spc_sample_rate * 2) {
|
||||
/* Play into buffer */
|
||||
short buf[BUF_SIZE];
|
||||
error(spc_play(snes_spc, BUF_SIZE, buf));
|
||||
|
||||
/* Filter samples */
|
||||
spc_filter_run(filter, buf, BUF_SIZE);
|
||||
|
||||
wave_write(buf, BUF_SIZE);
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
spc_filter_delete(filter);
|
||||
spc_delete(snes_spc);
|
||||
wave_close();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void MusicEditor::Update() {
|
||||
if (ImGui::BeginTable("MusicEditorColumns", 2, music_editor_flags_,
|
||||
ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("Assembly");
|
||||
ImGui::TableSetupColumn("Composition");
|
||||
ImGui::TableHeadersRow();
|
||||
ImGui::TableNextRow();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
assembly_editor_.InlineUpdate();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
DrawToolset();
|
||||
DrawChannels();
|
||||
DrawPianoRoll();
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void MusicEditor::DrawChannels() {
|
||||
if (ImGui::BeginTabBar("MyTabBar", ImGuiTabBarFlags_None)) {
|
||||
for (int i = 1; i <= 8; ++i) {
|
||||
if (ImGui::BeginTabItem(absl::StrFormat("%d", i).data())) {
|
||||
DrawPianoStaff();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
}
|
||||
|
||||
static const int NUM_KEYS = 25;
|
||||
static bool keys[NUM_KEYS];
|
||||
|
||||
void MusicEditor::DrawPianoStaff() {
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9);
|
||||
ImGui::BeginChild(child_id, ImVec2(0, 170), false)) {
|
||||
const int NUM_LINES = 5;
|
||||
const int LINE_THICKNESS = 2;
|
||||
const int LINE_SPACING = 40;
|
||||
|
||||
// Get the draw list for the current window
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
// Draw the staff lines
|
||||
ImVec2 canvas_p0 =
|
||||
ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y);
|
||||
ImVec2 canvas_p1 = ImVec2(canvas_p0.x + ImGui::GetContentRegionAvail().x,
|
||||
canvas_p0.y + ImGui::GetContentRegionAvail().y);
|
||||
draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(32, 32, 32, 255));
|
||||
for (int i = 0; i < NUM_LINES; i++) {
|
||||
auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING);
|
||||
auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
|
||||
canvas_p0.y + i * LINE_SPACING);
|
||||
draw_list->AddLine(line_start, line_end, IM_COL32(200, 200, 200, 255),
|
||||
LINE_THICKNESS);
|
||||
}
|
||||
|
||||
// Draw the ledger lines
|
||||
const int NUM_LEDGER_LINES = 3;
|
||||
for (int i = -NUM_LEDGER_LINES; i <= NUM_LINES + NUM_LEDGER_LINES; i++) {
|
||||
if (i % 2 == 0) continue; // skip every other line
|
||||
auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING / 2);
|
||||
auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
|
||||
canvas_p0.y + i * LINE_SPACING / 2);
|
||||
draw_list->AddLine(line_start, line_end, IM_COL32(150, 150, 150, 255),
|
||||
LINE_THICKNESS);
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void MusicEditor::DrawPianoRoll() {
|
||||
// Render the piano roll
|
||||
float key_width = ImGui::GetContentRegionAvail().x / NUM_KEYS;
|
||||
float white_key_height = ImGui::GetContentRegionAvail().y * 0.8f;
|
||||
float black_key_height = ImGui::GetContentRegionAvail().y * 0.5f;
|
||||
ImGui::Text("Piano Roll");
|
||||
ImGui::Separator();
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
// Draw the staff lines
|
||||
ImVec2 canvas_p0 =
|
||||
ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y);
|
||||
ImVec2 canvas_p1 = ImVec2(canvas_p0.x + ImGui::GetContentRegionAvail().x,
|
||||
canvas_p0.y + ImGui::GetContentRegionAvail().y);
|
||||
draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(200, 200, 200, 255));
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.f, 0.f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.f);
|
||||
for (int i = 0; i < NUM_KEYS; i++) {
|
||||
// Calculate the position and size of the key
|
||||
ImVec2 key_pos = ImVec2(i * key_width, 0.0f);
|
||||
ImVec2 key_size;
|
||||
ImVec4 key_color;
|
||||
ImVec4 text_color;
|
||||
if (i % 12 == 1 || i % 12 == 3 || i % 12 == 6 || i % 12 == 8 ||
|
||||
i % 12 == 10) {
|
||||
// This is a black key
|
||||
key_size = ImVec2(key_width * 0.6f, black_key_height);
|
||||
key_color = ImVec4(0, 0, 0, 255);
|
||||
text_color = ImVec4(255, 255, 255, 255);
|
||||
} else {
|
||||
// This is a white key
|
||||
key_size = ImVec2(key_width, white_key_height);
|
||||
key_color = ImVec4(255, 255, 255, 255);
|
||||
text_color = ImVec4(0, 0, 0, 255);
|
||||
}
|
||||
|
||||
ImGui::PushID(i);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, key_color);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, text_color);
|
||||
if (ImGui::Button(kSongNotes[i].data(), key_size)) {
|
||||
keys[i] ^= 1;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImVec2 button_pos = ImGui::GetItemRectMin();
|
||||
ImVec2 button_size = ImGui::GetItemRectSize();
|
||||
if (keys[i]) {
|
||||
ImVec2 dest;
|
||||
dest.x = button_pos.x + button_size.x;
|
||||
dest.y = button_pos.y + button_size.y;
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(button_pos, dest,
|
||||
IM_COL32(200, 200, 255, 200));
|
||||
}
|
||||
ImGui::PopID();
|
||||
ImGui::SameLine();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
void MusicEditor::DrawSongToolset() {
|
||||
if (ImGui::BeginTable("DWToolset", 8, toolset_table_flags_, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("#1");
|
||||
ImGui::TableSetupColumn("#play");
|
||||
ImGui::TableSetupColumn("#rewind");
|
||||
ImGui::TableSetupColumn("#fastforward");
|
||||
ImGui::TableSetupColumn("volumeController");
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void MusicEditor::DrawToolset() {
|
||||
static bool is_playing = false;
|
||||
static int selected_option = 0;
|
||||
static int current_volume = 0;
|
||||
static bool has_loaded_song = false;
|
||||
const int MAX_VOLUME = 100;
|
||||
|
||||
if (is_playing) {
|
||||
if (!has_loaded_song) {
|
||||
PlaySPC();
|
||||
current_song_ = Mix_LoadMUS("out.wav");
|
||||
Mix_PlayMusic(current_song_, -1);
|
||||
has_loaded_song = true;
|
||||
}
|
||||
|
||||
// // If there is no music playing
|
||||
// if (Mix_PlayingMusic() == 0) {
|
||||
// Mix_PlayMusic(current_song_, -1);
|
||||
// }
|
||||
// // If music is being played
|
||||
// else {
|
||||
// // If the music is paused
|
||||
// if (Mix_PausedMusic() == 1) {
|
||||
// // Resume the music
|
||||
// Mix_ResumeMusic();
|
||||
// }
|
||||
// // If the music is playing
|
||||
// else {
|
||||
// // Pause the music
|
||||
// Mix_PauseMusic();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
gui::ItemLabel("Select a song to edit: ", gui::ItemLabelFlags::Left);
|
||||
ImGui::Combo("#songs_in_game", &selected_option, kGameSongs, 30);
|
||||
|
||||
gui::ItemLabel("Controls: ", gui::ItemLabelFlags::Left);
|
||||
if (ImGui::BeginTable("SongToolset", 5, toolset_table_flags_, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("#play");
|
||||
ImGui::TableSetupColumn("#rewind");
|
||||
ImGui::TableSetupColumn("#fastforward");
|
||||
ImGui::TableSetupColumn("#volume");
|
||||
ImGui::TableSetupColumn("#slider");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button(is_playing ? ICON_MD_STOP : ICON_MD_PLAY_ARROW)) {
|
||||
if (is_playing) {
|
||||
Mix_HaltMusic();
|
||||
has_loaded_song = false;
|
||||
}
|
||||
is_playing = !is_playing;
|
||||
}
|
||||
|
||||
BUTTON_COLUMN(ICON_MD_FAST_REWIND)
|
||||
BUTTON_COLUMN(ICON_MD_FAST_FORWARD)
|
||||
BUTTON_COLUMN(ICON_MD_VOLUME_UP)
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SliderInt("Volume", ¤t_volume, 0, 100);
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
const int SONG_DURATION = 120; // duration of the song in seconds
|
||||
static int current_time = 0; // current time in the song in seconds
|
||||
|
||||
// Display the current time in the song
|
||||
gui::ItemLabel("Current Time: ", gui::ItemLabelFlags::Left);
|
||||
ImGui::Text("%d:%02d", current_time / 60, current_time % 60);
|
||||
ImGui::SameLine();
|
||||
// Display the song duration/progress using a progress bar
|
||||
ImGui::ProgressBar((float)current_time / SONG_DURATION);
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
86
src/app/editor/music_editor.h
Normal file
86
src/app/editor/music_editor.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_MUSIC_EDITOR_H
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/assembly_editor.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
#include "snes_spc/demo/demo_util.h"
|
||||
#include "snes_spc/demo/wave_writer.h"
|
||||
#include "snes_spc/snes_spc/spc.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
static const char* kGameSongs[] = {"Title",
|
||||
"Light World",
|
||||
"Beginning",
|
||||
"Rabbit",
|
||||
"Forest",
|
||||
"Intro",
|
||||
"Town",
|
||||
"Warp",
|
||||
"Dark world",
|
||||
"Master sword",
|
||||
"File select",
|
||||
"Soldier",
|
||||
"Mountain",
|
||||
"Shop",
|
||||
"Fanfare",
|
||||
"Castle",
|
||||
"Palace (Pendant)",
|
||||
"Cave (Same as Secret Way)",
|
||||
"Clear (Dungeon end)",
|
||||
"Church",
|
||||
"Boss",
|
||||
"Dungeon (Crystal)",
|
||||
"Psychic",
|
||||
"Secret Way (Same as Cave)",
|
||||
"Rescue",
|
||||
"Crystal",
|
||||
"Fountain",
|
||||
"Pyramid",
|
||||
"Kill Agahnim",
|
||||
"Ganon Room",
|
||||
"Last Boss"};
|
||||
|
||||
static constexpr absl::string_view kSongNotes[] = {
|
||||
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C",
|
||||
"C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C"};
|
||||
class MusicEditor {
|
||||
public:
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void DrawChannels();
|
||||
void DrawPianoStaff();
|
||||
void DrawPianoRoll();
|
||||
void DrawSongToolset();
|
||||
void DrawToolset();
|
||||
|
||||
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
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
352
src/app/editor/overworld_editor.cc
Normal file
352
src/app/editor/overworld_editor.cc
Normal file
@@ -0,0 +1,352 @@
|
||||
#include "overworld_editor.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
absl::Status OverworldEditor::Update() {
|
||||
// Initialize overworld graphics, maps, and palettes
|
||||
if (rom_.isLoaded() && !all_gfx_loaded_) {
|
||||
RETURN_IF_ERROR(LoadGraphics())
|
||||
all_gfx_loaded_ = true;
|
||||
}
|
||||
|
||||
// Draws the toolset for editing the Overworld.
|
||||
RETURN_IF_ERROR(DrawToolset())
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginTable("#owEditTable", 2, ow_edit_flags, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
ImGui::TableSetupColumn("Tile Selector");
|
||||
ImGui::TableHeadersRow();
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
DrawOverworldCanvas();
|
||||
ImGui::TableNextColumn();
|
||||
DrawTileSelector();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
absl::Status OverworldEditor::DrawToolset() {
|
||||
if (ImGui::BeginTable("OWToolset", 17, toolset_table_flags, ImVec2(0, 0))) {
|
||||
for (const auto &name : kToolsetColumnNames)
|
||||
ImGui::TableSetupColumn(name.data());
|
||||
|
||||
BUTTON_COLUMN(ICON_MD_UNDO) // Undo
|
||||
BUTTON_COLUMN(ICON_MD_REDO) // Redo
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
|
||||
BUTTON_COLUMN(ICON_MD_ZOOM_OUT) // Zoom Out
|
||||
BUTTON_COLUMN(ICON_MD_ZOOM_IN) // Zoom In
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
|
||||
BUTTON_COLUMN(ICON_MD_DRAW) // Draw Tile
|
||||
BUTTON_COLUMN(ICON_MD_DOOR_FRONT) // Entrances
|
||||
BUTTON_COLUMN(ICON_MD_DOOR_BACK) // Exits
|
||||
BUTTON_COLUMN(ICON_MD_GRASS) // Items
|
||||
BUTTON_COLUMN(ICON_MD_PEST_CONTROL_RODENT) // Sprites
|
||||
BUTTON_COLUMN(ICON_MD_ADD_LOCATION) // Transports
|
||||
BUTTON_COLUMN(ICON_MD_MUSIC_NOTE) // Music
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
|
||||
ImGui::TableNextColumn(); // Palette
|
||||
palette_editor_.DisplayPalette(palette_, overworld_.isLoaded());
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void OverworldEditor::DrawOverworldMapSettings() {
|
||||
if (ImGui::BeginTable("#mapSettings", 8, ow_map_flags, ImVec2(0, 0), -1)) {
|
||||
for (const auto &name : kOverworldSettingsColumnNames)
|
||||
ImGui::TableSetupColumn(name.data());
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(50.f);
|
||||
ImGui::InputInt("Current Map", ¤t_map_);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(100.f);
|
||||
ImGui::Combo("##world", ¤t_world_,
|
||||
"Light World\0Dark World\0Extra World\0");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("GFX");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(kInputFieldSize);
|
||||
ImGui::InputText("##mapGFX", map_gfx_, kByteSize);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Palette");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(kInputFieldSize);
|
||||
ImGui::InputText("##mapPal", map_palette_, kByteSize);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Spr GFX");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(kInputFieldSize);
|
||||
ImGui::InputText("##sprGFX", spr_gfx_, kByteSize);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Spr Palette");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(kInputFieldSize);
|
||||
ImGui::InputText("##sprPal", spr_palette_, kByteSize);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Msg ID");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(50.f);
|
||||
ImGui::InputText("##msgid", spr_palette_, kMessageIdSize);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Checkbox("Show grid", &opt_enable_grid); // TODO
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void OverworldEditor::DrawOverworldEntrances() {
|
||||
for (const auto &each : overworld_.Entrances()) {
|
||||
if (each.map_id_ < 0x40 + (current_world_ * 0x40) &&
|
||||
each.map_id_ >= (current_world_ * 0x40)) {
|
||||
ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16,
|
||||
ImVec4(210, 24, 210, 150));
|
||||
std::string str = absl::StrFormat("%#x", each.entrance_id_);
|
||||
ow_map_canvas_.DrawText(str, each.x_ - 4, each.y_ - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void OverworldEditor::DrawOverworldMaps() {
|
||||
int xx = 0;
|
||||
int yy = 0;
|
||||
for (int i = 0; i < 0x40; i++) {
|
||||
int world_index = i + (current_world_ * 0x40);
|
||||
int map_x = (xx * 0x200);
|
||||
int map_y = (yy * 0x200);
|
||||
ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y);
|
||||
xx++;
|
||||
if (xx >= 8) {
|
||||
yy++;
|
||||
xx = 0;
|
||||
}
|
||||
DrawOverworldEntrances();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Overworld Editor canvas
|
||||
// Allows the user to make changes to the overworld map.
|
||||
void OverworldEditor::DrawOverworldCanvas() {
|
||||
DrawOverworldMapSettings();
|
||||
ImGui::Separator();
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)7);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar |
|
||||
ImGuiWindowFlags_AlwaysHorizontalScrollbar)) {
|
||||
ow_map_canvas_.DrawBackground(ImVec2(0x200 * 8, 0x200 * 8));
|
||||
ow_map_canvas_.DrawContextMenu();
|
||||
if (overworld_.isLoaded()) {
|
||||
DrawOverworldMaps();
|
||||
// User has selected a tile they want to draw from the blockset.
|
||||
if (!blockset_canvas_.Points().empty()) {
|
||||
int x = blockset_canvas_.Points().front().x / 32;
|
||||
int y = blockset_canvas_.Points().front().y / 32;
|
||||
std::cout << x << " " << y << std::endl;
|
||||
current_tile16_ = x + (y * 8);
|
||||
std::cout << current_tile16_ << std::endl;
|
||||
ow_map_canvas_.DrawTilePainter(tile16_individual_[current_tile16_], 16);
|
||||
}
|
||||
}
|
||||
ow_map_canvas_.DrawGrid(64.0f);
|
||||
ow_map_canvas_.DrawOverlay();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Tile 16 Selector
|
||||
// Displays all the tiles in the game.
|
||||
void OverworldEditor::DrawTile16Selector() {
|
||||
blockset_canvas_.DrawBackground(ImVec2(0x100 + 1, (8192 * 2) + 1));
|
||||
blockset_canvas_.DrawContextMenu();
|
||||
blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 2, map_blockset_loaded_);
|
||||
blockset_canvas_.DrawTileSelector(32);
|
||||
blockset_canvas_.DrawGrid(32.0f);
|
||||
blockset_canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Tile 8 Selector
|
||||
// Displays all the individual tiles that make up a tile16.
|
||||
void OverworldEditor::DrawTile8Selector() {
|
||||
graphics_bin_canvas_.DrawBackground(
|
||||
ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1));
|
||||
graphics_bin_canvas_.DrawContextMenu();
|
||||
if (all_gfx_loaded_) {
|
||||
for (const auto &[key, value] : graphics_bin_) {
|
||||
int offset = 0x40 * (key + 1);
|
||||
int top_left_y = graphics_bin_canvas_.GetZeroPoint().y + 2;
|
||||
if (key >= 1) {
|
||||
top_left_y = graphics_bin_canvas_.GetZeroPoint().y + 0x40 * key;
|
||||
}
|
||||
graphics_bin_canvas_.GetDrawList()->AddImage(
|
||||
(void *)value.GetTexture(),
|
||||
ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 2, top_left_y),
|
||||
ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 0x100,
|
||||
graphics_bin_canvas_.GetZeroPoint().y + offset));
|
||||
}
|
||||
}
|
||||
graphics_bin_canvas_.DrawGrid(16.0f);
|
||||
graphics_bin_canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Displays the graphics tilesheets that are available on the current selected
|
||||
// overworld map.
|
||||
void OverworldEditor::DrawAreaGraphics() {
|
||||
current_gfx_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
|
||||
current_gfx_canvas_.DrawContextMenu();
|
||||
current_gfx_canvas_.DrawTileSelector(32);
|
||||
current_gfx_canvas_.DrawBitmap(current_gfx_bmp_, 2, overworld_.isLoaded());
|
||||
current_gfx_canvas_.DrawGrid(32.0f);
|
||||
current_gfx_canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void OverworldEditor::DrawTileSelector() {
|
||||
if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
|
||||
if (ImGui::BeginTabItem("Tile16")) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)2);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
DrawTile16Selector();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Tile8")) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)1);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
DrawTile8Selector();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Area Graphics")) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)3);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
DrawAreaGraphics();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
absl::Status OverworldEditor::LoadGraphics() {
|
||||
// Load all of the graphics data from the game.
|
||||
PRINT_IF_ERROR(rom_.LoadAllGraphicsData())
|
||||
graphics_bin_ = rom_.GetGraphicsBin();
|
||||
|
||||
// Load the Link to the Past overworld.
|
||||
RETURN_IF_ERROR(overworld_.Load(rom_))
|
||||
palette_ = overworld_.AreaPalette();
|
||||
current_gfx_bmp_.Create(0x80, 0x200, 0x40, overworld_.AreaGraphics());
|
||||
current_gfx_bmp_.ApplyPalette(palette_);
|
||||
rom_.RenderBitmap(¤t_gfx_bmp_);
|
||||
|
||||
// Create the tile16 blockset image
|
||||
tile16_blockset_bmp_.Create(0x80, 8192, 0x80, overworld_.Tile16Blockset());
|
||||
tile16_blockset_bmp_.ApplyPalette(palette_);
|
||||
rom_.RenderBitmap(&tile16_blockset_bmp_);
|
||||
map_blockset_loaded_ = true;
|
||||
|
||||
// Copy the tile16 data into individual tiles.
|
||||
auto tile16_data = overworld_.Tile16Blockset();
|
||||
|
||||
// Loop through the tiles and copy their pixel data into separate vectors
|
||||
for (int i = 0; i < 4096; i++) {
|
||||
// Create a new vector for the pixel data of the current tile
|
||||
Bytes tile_data;
|
||||
for (int j = 0; j < 32 * 32; j++) tile_data.push_back(0x00);
|
||||
|
||||
// Copy the pixel data for the current tile into the vector
|
||||
for (int ty = 0; ty < 32; ty++) {
|
||||
for (int tx = 0; tx < 32; tx++) {
|
||||
int position = (tx + (ty * 0x20));
|
||||
uchar value = tile16_data[i + tx + (ty * 0x80)];
|
||||
tile_data[position] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the vector for the current tile to the vector of tile pixel data
|
||||
tile16_individual_data_.push_back(tile_data);
|
||||
}
|
||||
|
||||
// Render the bitmaps of each tile.
|
||||
for (int id = 0; id < 4096; id++) {
|
||||
gfx::Bitmap new_tile16;
|
||||
tile16_individual_.emplace_back(new_tile16);
|
||||
tile16_individual_[id].Create(0x10, 0x10, 0x80,
|
||||
tile16_individual_data_[id]);
|
||||
tile16_individual_[id].ApplyPalette(palette_);
|
||||
rom_.RenderBitmap(&tile16_individual_[id]);
|
||||
}
|
||||
|
||||
// Render the overworld maps loaded from the ROM.
|
||||
for (int i = 0; i < core::kNumOverworldMaps; ++i) {
|
||||
overworld_.SetCurrentMap(i);
|
||||
auto palette = overworld_.AreaPalette();
|
||||
maps_bmp_[i].Create(0x200, 0x200, 0x200, overworld_.BitmapData());
|
||||
maps_bmp_[i].ApplyPalette(palette);
|
||||
rom_.RenderBitmap(&(maps_bmp_[i]));
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
118
src/app/editor/overworld_editor.h
Normal file
118
src/app/editor/overworld_editor.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H
|
||||
#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.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", "#drawTool", "#separator2",
|
||||
"#zoomOutTool", "#zoomInTool", "#separator", "#history",
|
||||
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
|
||||
"#transportTool", "#musicTool"};
|
||||
|
||||
static constexpr absl::string_view kOverworldSettingsColumnNames[] = {
|
||||
"##1stCol", "##gfxCol", "##palCol", "##sprgfxCol",
|
||||
"##sprpalCol", "##msgidCol", "##2ndCol"};
|
||||
|
||||
class OverworldEditor {
|
||||
public:
|
||||
absl::Status Update();
|
||||
absl::Status Undo() const { return absl::UnimplementedError("Undo"); }
|
||||
absl::Status Redo() const { return absl::UnimplementedError("Redo"); }
|
||||
absl::Status Cut() const { return absl::UnimplementedError("Cut"); }
|
||||
absl::Status Copy() const { return absl::UnimplementedError("Copy"); }
|
||||
absl::Status Paste() const { return absl::UnimplementedError("Paste"); }
|
||||
void SetupROM(ROM &rom) { rom_ = rom; }
|
||||
|
||||
private:
|
||||
absl::Status DrawToolset();
|
||||
void DrawOverworldMapSettings();
|
||||
|
||||
void DrawOverworldEntrances();
|
||||
void DrawOverworldMaps();
|
||||
void DrawOverworldCanvas();
|
||||
|
||||
void DrawTile16Selector();
|
||||
void DrawTile8Selector();
|
||||
void DrawAreaGraphics();
|
||||
void DrawTileSelector();
|
||||
absl::Status LoadGraphics();
|
||||
|
||||
int current_world_ = 0;
|
||||
int current_map_ = 0;
|
||||
int current_tile16_ = 0;
|
||||
int selected_tile_ = 0;
|
||||
char map_gfx_[3] = "";
|
||||
char map_palette_[3] = "";
|
||||
char spr_gfx_[3] = "";
|
||||
char spr_palette_[3] = "";
|
||||
char message_id_[5] = "";
|
||||
char staticgfx[16];
|
||||
|
||||
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;
|
||||
|
||||
ImGuiTableFlags toolset_table_flags = ImGuiTableFlags_SizingFixedFit;
|
||||
ImGuiTableFlags ow_map_flags = ImGuiTableFlags_Borders;
|
||||
ImGuiTableFlags ow_edit_flags = ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Resizable |
|
||||
ImGuiTableFlags_SizingStretchSame;
|
||||
|
||||
Bytes selected_tile_data_;
|
||||
std::unordered_map<int, gfx::Bitmap> graphics_bin_;
|
||||
std::unordered_map<int, gfx::Bitmap> current_graphics_set_;
|
||||
std::unordered_map<int, gfx::Bitmap> maps_bmp_;
|
||||
std::unordered_map<int, gfx::Bitmap> sprite_previews_;
|
||||
|
||||
std::vector<Bytes> tile16_individual_data_;
|
||||
std::vector<gfx::Bitmap> tile16_individual_;
|
||||
|
||||
ROM rom_;
|
||||
PaletteEditor palette_editor_;
|
||||
zelda3::Overworld overworld_;
|
||||
|
||||
gfx::SNESPalette palette_;
|
||||
gfx::Bitmap selected_tile_bmp_;
|
||||
gfx::Bitmap tile16_blockset_bmp_;
|
||||
gfx::Bitmap current_gfx_bmp_;
|
||||
gfx::Bitmap all_gfx_bmp;
|
||||
|
||||
gui::Canvas ow_map_canvas_;
|
||||
gui::Canvas current_gfx_canvas_;
|
||||
gui::Canvas blockset_canvas_;
|
||||
gui::Canvas graphics_bin_canvas_;
|
||||
};
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
128
src/app/editor/palette_editor.cc
Normal file
128
src/app/editor/palette_editor.cc
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "palette_editor.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
absl::Status PaletteEditor::Update() {
|
||||
for (int i = 0; i < 11; ++i) {
|
||||
if (ImGui::TreeNode(kPaletteCategoryNames[i].data())) {
|
||||
auto size = rom_.GetPaletteGroup(kPaletteGroupNames[i].data()).size;
|
||||
auto palettes = rom_.GetPaletteGroup(kPaletteGroupNames[i].data());
|
||||
for (int j = 0; j < size; j++) {
|
||||
ImGui::Text("%d", j);
|
||||
auto palette = palettes[j];
|
||||
for (int n = 0; n < size; n++) {
|
||||
ImGui::PushID(n);
|
||||
if ((n % 8) != 0)
|
||||
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
ImGuiColorEditFlags palette_button_flags =
|
||||
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker;
|
||||
if (ImGui::ColorButton("##palette", palette[n].RGB(),
|
||||
palette_button_flags, ImVec2(20, 20)))
|
||||
current_color_ =
|
||||
ImVec4(palette[n].rgb.x, palette[n].rgb.y, palette[n].rgb.z,
|
||||
current_color_.w); // Preserve alpha!
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void PaletteEditor::DisplayPalette(gfx::SNESPalette& palette, bool loaded) {
|
||||
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
|
||||
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
|
||||
ImGuiColorEditFlags_NoDragDrop |
|
||||
ImGuiColorEditFlags_NoOptions;
|
||||
|
||||
// Generate a default palette. The palette will persist and can be edited.
|
||||
static bool init = false;
|
||||
static ImVec4 saved_palette[256] = {};
|
||||
if (loaded && !init) {
|
||||
for (int n = 0; n < palette.size_; n++) {
|
||||
saved_palette[n].x = palette.GetColor(n).rgb.x / 255;
|
||||
saved_palette[n].y = palette.GetColor(n).rgb.y / 255;
|
||||
saved_palette[n].z = palette.GetColor(n).rgb.z / 255;
|
||||
saved_palette[n].w = 255; // Alpha
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
static ImVec4 backup_color;
|
||||
bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags);
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
open_popup |= ImGui::Button("Palette");
|
||||
if (open_popup) {
|
||||
ImGui::OpenPopup("mypicker");
|
||||
backup_color = color;
|
||||
}
|
||||
if (ImGui::BeginPopup("mypicker")) {
|
||||
ImGui::Text("Current Overworld Palette");
|
||||
ImGui::Separator();
|
||||
ImGui::ColorPicker4("##picker", (float*)&color,
|
||||
misc_flags | ImGuiColorEditFlags_NoSidePreview |
|
||||
ImGuiColorEditFlags_NoSmallPreview);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginGroup(); // Lock X position
|
||||
ImGui::Text("Current ==>");
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Previous");
|
||||
|
||||
ImGui::ColorButton(
|
||||
"##current", color,
|
||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||
ImVec2(60, 40));
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::ColorButton(
|
||||
"##previous", backup_color,
|
||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||
ImVec2(60, 40)))
|
||||
color = backup_color;
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Palette");
|
||||
for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) {
|
||||
ImGui::PushID(n);
|
||||
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha |
|
||||
ImGuiColorEditFlags_NoPicker |
|
||||
ImGuiColorEditFlags_NoTooltip;
|
||||
if (ImGui::ColorButton("##palette", saved_palette[n],
|
||||
palette_button_flags, ImVec2(20, 20)))
|
||||
color = ImVec4(saved_palette[n].x, saved_palette[n].y,
|
||||
saved_palette[n].z, color.w); // Preserve alpha!
|
||||
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
|
||||
memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3);
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
|
||||
memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4);
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
42
src/app/editor/palette_editor.h
Normal file
42
src/app/editor/palette_editor.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
static constexpr absl::string_view kPaletteCategoryNames[] = {
|
||||
"Sword", "Shield", "Clothes", "World Colors",
|
||||
"Area Colors", "Enemies", "Dungeons", "World Map",
|
||||
"Dungeon Map", "Triforce", "Crystal"};
|
||||
|
||||
static constexpr absl::string_view kPaletteGroupNames[] = {
|
||||
"swords", "shields", "armors", "ow_main",
|
||||
"ow_aux", "global_sprites", "dungeon_main", "ow_mini_map",
|
||||
"ow_mini_map", "3d_object", "3d_object"};
|
||||
|
||||
class PaletteEditor {
|
||||
public:
|
||||
absl::Status Update();
|
||||
void DisplayPalette(gfx::SNESPalette& palette, bool loaded);
|
||||
|
||||
auto SetupROM(ROM& rom) { rom_ = rom; }
|
||||
|
||||
private:
|
||||
ImVec4 current_color_;
|
||||
ROM rom_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
201
src/app/editor/screen_editor.cc
Normal file
201
src/app/editor/screen_editor.cc
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "app/editor/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/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
ScreenEditor::ScreenEditor() { screen_canvas_.SetCanvasSize(ImVec2(512, 512)); }
|
||||
|
||||
void ScreenEditor::Update() {
|
||||
TAB_BAR("##TabBar")
|
||||
DrawInventoryMenuEditor();
|
||||
DrawTitleScreenEditor();
|
||||
DrawNamingScreenEditor();
|
||||
DrawOverworldMapEditor();
|
||||
DrawDungeonMapsEditor();
|
||||
DrawMosaicEditor();
|
||||
END_TAB_BAR()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawWorldGrid(int world, int h, int w) {
|
||||
const float time = (float)ImGui::GetTime();
|
||||
|
||||
int i = 0;
|
||||
if (world == 1) {
|
||||
i = 64;
|
||||
} else if (world == 2) {
|
||||
i = 128;
|
||||
}
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++) {
|
||||
if (x > 0) ImGui::SameLine();
|
||||
ImGui::PushID(y * 4 + x);
|
||||
std::string label = absl::StrCat(" #", absl::StrFormat("%x", i));
|
||||
if (ImGui::Selectable(label.c_str(), mosaic_tiles_[i] != 0, 0,
|
||||
ImVec2(35, 25))) {
|
||||
mosaic_tiles_[i] ^= 1;
|
||||
}
|
||||
ImGui::PopID();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawInventoryMenuEditor() {
|
||||
TAB_ITEM("Inventory Menu")
|
||||
|
||||
static bool create = false;
|
||||
if (!create && rom_.isLoaded()) {
|
||||
inventory_.Create();
|
||||
palette_ = inventory_.Palette();
|
||||
create = true;
|
||||
}
|
||||
|
||||
DrawInventoryToolset();
|
||||
|
||||
if (ImGui::BeginTable("InventoryScreen", 3, ImGuiTableFlags_Resizable)) {
|
||||
ImGui::TableSetupColumn("Canvas");
|
||||
ImGui::TableSetupColumn("Tiles");
|
||||
ImGui::TableSetupColumn("Palette");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
screen_canvas_.DrawBackground();
|
||||
screen_canvas_.DrawContextMenu();
|
||||
screen_canvas_.DrawBitmap(inventory_.Bitmap(), 2, create);
|
||||
screen_canvas_.DrawGrid(32.0f);
|
||||
screen_canvas_.DrawOverlay();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
|
||||
tilesheet_canvas_.DrawContextMenu();
|
||||
tilesheet_canvas_.DrawBitmap(inventory_.Tilesheet(), 2, create);
|
||||
tilesheet_canvas_.DrawGrid(16.0f);
|
||||
tilesheet_canvas_.DrawOverlay();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
gui::DisplayPalette(palette_, create);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::Separator();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawTitleScreenEditor() {
|
||||
TAB_ITEM("Title Screen")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
void ScreenEditor::DrawNamingScreenEditor() {
|
||||
TAB_ITEM("Naming Screen")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
void ScreenEditor::DrawOverworldMapEditor() {
|
||||
TAB_ITEM("Overworld Map")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
void ScreenEditor::DrawDungeonMapsEditor() {
|
||||
TAB_ITEM("Dungeon Maps")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawMosaicEditor() {
|
||||
TAB_ITEM("Mosaic Transitions")
|
||||
|
||||
if (ImGui::BeginTable("Worlds", 3, ImGuiTableFlags_Borders)) {
|
||||
ImGui::TableSetupColumn("Light World");
|
||||
ImGui::TableSetupColumn("Dark World");
|
||||
ImGui::TableSetupColumn("Special World");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
DrawWorldGrid(0);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
DrawWorldGrid(1);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
DrawWorldGrid(2, 4);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
gui::InputHex("Routine Location", &overworldCustomMosaicASM);
|
||||
|
||||
if (ImGui::Button("Generate Mosaic Assembly")) {
|
||||
auto mosaic =
|
||||
rom_.PatchOverworldMosaic(mosaic_tiles_, overworldCustomMosaicASM);
|
||||
if (!mosaic.ok()) {
|
||||
std::cout << mosaic;
|
||||
}
|
||||
}
|
||||
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawToolset() {
|
||||
static bool show_bg1 = true;
|
||||
static bool show_bg2 = true;
|
||||
static bool show_bg3 = true;
|
||||
|
||||
static bool drawing_bg1 = true;
|
||||
static bool drawing_bg2 = false;
|
||||
static bool drawing_bg3 = false;
|
||||
|
||||
ImGui::Checkbox("Show BG1", &show_bg1);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Show BG2", &show_bg2);
|
||||
|
||||
ImGui::Checkbox("Draw BG1", &drawing_bg1);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Draw BG2", &drawing_bg2);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Draw BG3", &drawing_bg3);
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawInventoryToolset() {
|
||||
if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
|
||||
ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("#drawTool");
|
||||
ImGui::TableSetupColumn("#sep1");
|
||||
ImGui::TableSetupColumn("#zoomOut");
|
||||
ImGui::TableSetupColumn("#zoomIN");
|
||||
ImGui::TableSetupColumn("#sep2");
|
||||
ImGui::TableSetupColumn("#bg2Tool");
|
||||
ImGui::TableSetupColumn("#bg3Tool");
|
||||
ImGui::TableSetupColumn("#itemTool");
|
||||
|
||||
BUTTON_COLUMN(ICON_MD_UNDO)
|
||||
BUTTON_COLUMN(ICON_MD_REDO)
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT)
|
||||
BUTTON_COLUMN(ICON_MD_ZOOM_OUT)
|
||||
BUTTON_COLUMN(ICON_MD_ZOOM_IN)
|
||||
TEXT_COLUMN(ICON_MD_MORE_VERT)
|
||||
BUTTON_COLUMN(ICON_MD_DRAW)
|
||||
BUTTON_COLUMN(ICON_MD_BUILD)
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
60
src/app/editor/screen_editor.h
Normal file
60
src/app/editor/screen_editor.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "app/core/constants.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/inventory.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/color.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
using MosaicArray = std::array<int, core::kNumOverworldMaps>;
|
||||
static int overworldCustomMosaicASM = 0x1301D0;
|
||||
|
||||
class ScreenEditor {
|
||||
public:
|
||||
ScreenEditor();
|
||||
void SetupROM(ROM &rom) {
|
||||
rom_ = rom;
|
||||
inventory_.SetupROM(rom_);
|
||||
}
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void DrawMosaicEditor();
|
||||
void DrawTitleScreenEditor();
|
||||
void DrawNamingScreenEditor();
|
||||
void DrawOverworldMapEditor();
|
||||
void DrawDungeonMapsEditor();
|
||||
void DrawInventoryMenuEditor();
|
||||
|
||||
void DrawToolset();
|
||||
void DrawInventoryToolset();
|
||||
void DrawWorldGrid(int world, int h = 8, int w = 8);
|
||||
|
||||
char mosaic_tiles_[core::kNumOverworldMaps];
|
||||
|
||||
ROM rom_;
|
||||
Bytes all_gfx_;
|
||||
zelda3::Inventory inventory_;
|
||||
gfx::SNESPalette palette_;
|
||||
gui::Canvas screen_canvas_;
|
||||
gui::Canvas tilesheet_canvas_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
135
src/app/gfx/bitmap.cc
Normal file
135
src/app/gfx/bitmap.cc
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "bitmap.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#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"
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bitmap::Bitmap(int width, int height, int depth, uchar *data) {
|
||||
Create(width, height, depth, data);
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(int width, int height, int depth, int data_size) {
|
||||
Create(width, height, depth, data_size);
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(int width, int height, int depth, uchar *data, int data_size) {
|
||||
Create(width, height, depth, data, data_size);
|
||||
}
|
||||
|
||||
// Pass raw pixel data directly to the surface
|
||||
void Bitmap::Create(int width, int height, int depth, uchar *data) {
|
||||
active_ = true;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
depth_ = depth;
|
||||
pixel_data_ = data;
|
||||
surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
|
||||
SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_,
|
||||
SDL_PIXELFORMAT_INDEX8),
|
||||
SDL_Surface_Deleter());
|
||||
surface_->pixels = pixel_data_;
|
||||
GrayscalePalette(surface_->format->palette);
|
||||
}
|
||||
|
||||
// Reserves data to later draw to surface via pointer
|
||||
void Bitmap::Create(int width, int height, int depth, int size) {
|
||||
active_ = true;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
depth_ = depth;
|
||||
data_size_ = size;
|
||||
data_.reserve(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);
|
||||
}
|
||||
|
||||
// Pass raw pixel data directly to the surface
|
||||
void Bitmap::Create(int width, int height, int depth, uchar *data, int size) {
|
||||
active_ = true;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
depth_ = depth;
|
||||
pixel_data_ = data;
|
||||
data_size_ = size;
|
||||
surface_ = std::unique_ptr<SDL_Surface, SDL_Surface_Deleter>(
|
||||
SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_,
|
||||
SDL_PIXELFORMAT_INDEX8),
|
||||
SDL_Surface_Deleter());
|
||||
surface_->pixels = pixel_data_;
|
||||
GrayscalePalette(surface_->format->palette);
|
||||
}
|
||||
|
||||
void Bitmap::Create(int width, int height, int depth, Bytes data) {
|
||||
active_ = true;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
depth_ = depth;
|
||||
data_ = data;
|
||||
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);
|
||||
}
|
||||
|
||||
void Bitmap::Apply(Bytes data) {
|
||||
pixel_data_ = data.data();
|
||||
data_ = data;
|
||||
}
|
||||
|
||||
// Creates the texture that will be displayed to the screen.
|
||||
void Bitmap::CreateTexture(std::shared_ptr<SDL_Renderer> renderer) {
|
||||
texture_ = std::shared_ptr<SDL_Texture>{
|
||||
SDL_CreateTextureFromSurface(renderer.get(), surface_.get()),
|
||||
SDL_Texture_Deleter{}};
|
||||
}
|
||||
|
||||
// Convert SNESPalette to SDL_Palette for surface.
|
||||
void Bitmap::ApplyPalette(const SNESPalette &palette) {
|
||||
palette_ = palette;
|
||||
for (int i = 0; i < palette.size_; ++i) {
|
||||
if (palette.GetColor(i).transparent) {
|
||||
surface_->format->palette->colors[i].r = 0;
|
||||
surface_->format->palette->colors[i].g = 0;
|
||||
surface_->format->palette->colors[i].b = 0;
|
||||
surface_->format->palette->colors[i].a = 0;
|
||||
} else {
|
||||
surface_->format->palette->colors[i].r = palette.GetColor(i).rgb.x;
|
||||
surface_->format->palette->colors[i].g = palette.GetColor(i).rgb.y;
|
||||
surface_->format->palette->colors[i].b = palette.GetColor(i).rgb.z;
|
||||
surface_->format->palette->colors[i].a = palette.GetColor(i).rgb.w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
87
src/app/gfx/bitmap.h
Normal file
87
src/app/gfx/bitmap.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#ifndef YAZE_APP_GFX_BITMAP_H
|
||||
#define YAZE_APP_GFX_BITMAP_H
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#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"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gfx {
|
||||
|
||||
class Bitmap {
|
||||
public:
|
||||
Bitmap() = default;
|
||||
Bitmap(int width, int height, int depth, uchar *data);
|
||||
Bitmap(int width, int height, int depth, int data_size);
|
||||
Bitmap(int width, int height, int depth, uchar *data, int data_size);
|
||||
|
||||
void Create(int width, int height, int depth, uchar *data);
|
||||
void Create(int width, int height, int depth, int data_size);
|
||||
void Create(int width, int height, int depth, uchar *data, int data_size);
|
||||
void Create(int width, int height, int depth, Bytes data);
|
||||
|
||||
void Apply(Bytes data);
|
||||
|
||||
void CreateTexture(std::shared_ptr<SDL_Renderer> renderer);
|
||||
|
||||
void ApplyPalette(const SNESPalette &palette);
|
||||
|
||||
void WriteToPixel(int position, uchar value) {
|
||||
this->pixel_data_[position] = value;
|
||||
}
|
||||
|
||||
int GetWidth() const { return width_; }
|
||||
int GetHeight() const { return height_; }
|
||||
auto GetSize() const { return data_size_; }
|
||||
auto GetData() const { return pixel_data_; }
|
||||
auto GetByte(int i) const { return pixel_data_[i]; }
|
||||
auto GetTexture() const { return texture_.get(); }
|
||||
auto GetSurface() const { return surface_.get(); }
|
||||
auto IsActive() const { return active_; }
|
||||
|
||||
private:
|
||||
struct SDL_Texture_Deleter {
|
||||
void operator()(SDL_Texture *p) const {
|
||||
if (p != nullptr) {
|
||||
SDL_DestroyTexture(p);
|
||||
p = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct SDL_Surface_Deleter {
|
||||
void operator()(SDL_Surface *p) const {
|
||||
if (p != nullptr) {
|
||||
p->pixels = nullptr;
|
||||
SDL_FreeSurface(p);
|
||||
p = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
int depth_ = 0;
|
||||
int data_size_ = 0;
|
||||
bool freed_ = false;
|
||||
bool active_ = false;
|
||||
uchar *pixel_data_;
|
||||
Bytes data_;
|
||||
gfx::SNESPalette palette_;
|
||||
std::shared_ptr<SDL_Texture> texture_ = nullptr;
|
||||
std::shared_ptr<SDL_Surface> surface_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GFX_BITMAP_H
|
||||
197
src/app/gfx/snes_palette.cc
Normal file
197
src/app/gfx/snes_palette.cc
Normal file
@@ -0,0 +1,197 @@
|
||||
#include "snes_palette.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/constants.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gfx {
|
||||
|
||||
ushort ConvertRGBtoSNES(const snes_color color) {
|
||||
uchar red = color.red / 8;
|
||||
uchar green = color.green / 8;
|
||||
uchar blue = color.blue / 8;
|
||||
return blue * 1024 + green * 32 + red;
|
||||
}
|
||||
|
||||
snes_color ConvertSNEStoRGB(const ushort color) {
|
||||
snes_color toret;
|
||||
|
||||
toret.red = ((color) % 32) * 8;
|
||||
toret.green = ((color / 32) % 32) * 8;
|
||||
toret.blue = ((color / 1024) % 32) * 8;
|
||||
|
||||
toret.red = toret.red + toret.red / 32;
|
||||
toret.green = toret.green + toret.green / 32;
|
||||
toret.blue = toret.blue + toret.blue / 32;
|
||||
return toret;
|
||||
}
|
||||
|
||||
snes_palette* Extract(const char* data, const unsigned int offset,
|
||||
const unsigned int palette_size) {
|
||||
snes_palette* toret = nullptr; // palette_create(palette_size, 0)
|
||||
unsigned colnum = 0;
|
||||
for (int i = 0; i < palette_size * 2; i += 2) {
|
||||
unsigned short snes_color;
|
||||
snes_color = ((uchar)data[offset + i + 1]) << 8;
|
||||
snes_color = snes_color | ((uchar)data[offset + i]);
|
||||
toret->colors[colnum] = ConvertSNEStoRGB(snes_color);
|
||||
colnum++;
|
||||
}
|
||||
return toret;
|
||||
}
|
||||
|
||||
char* Convert(const snes_palette pal) {
|
||||
char* toret = (char*)malloc(pal.size * 2);
|
||||
for (unsigned int i = 0; i < pal.size; i++) {
|
||||
unsigned short snes_data = ConvertRGBtoSNES(pal.colors[i]);
|
||||
toret[i * 2] = snes_data & 0xFF;
|
||||
toret[i * 2 + 1] = snes_data >> 8;
|
||||
}
|
||||
return toret;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
SNESColor::SNESColor() : rgb(ImVec4(0.f, 0.f, 0.f, 0.f)) {}
|
||||
|
||||
SNESColor::SNESColor(snes_color val) {
|
||||
rgb.x = val.red;
|
||||
rgb.y = val.green;
|
||||
rgb.z = val.blue;
|
||||
}
|
||||
|
||||
SNESColor::SNESColor(ImVec4 val) : rgb(val) {
|
||||
snes_color col;
|
||||
col.red = (uchar)val.x;
|
||||
col.blue = (uchar)val.y;
|
||||
col.green = (uchar)val.z;
|
||||
snes = ConvertRGBtoSNES(col);
|
||||
}
|
||||
|
||||
void SNESColor::setRgb(ImVec4 val) {
|
||||
rgb = val;
|
||||
snes_color col;
|
||||
col.red = val.x;
|
||||
col.blue = val.y;
|
||||
col.green = val.z;
|
||||
snes = ConvertRGBtoSNES(col);
|
||||
}
|
||||
|
||||
void SNESColor::setSNES(snes_color val) {
|
||||
rgb = ImVec4(val.red, val.green, val.blue, 255.f);
|
||||
}
|
||||
|
||||
void SNESColor::setSNES(uint16_t val) {
|
||||
snes = val;
|
||||
snes_color col = ConvertSNEStoRGB(val);
|
||||
rgb = ImVec4(col.red, col.green, col.blue, 0.f);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
SNESPalette::SNESPalette(uint8_t mSize) : size_(mSize) {
|
||||
for (unsigned int i = 0; i < mSize; i++) {
|
||||
SNESColor col;
|
||||
colors.push_back(col);
|
||||
}
|
||||
}
|
||||
|
||||
SNESPalette::SNESPalette(char* data) : size_(sizeof(data) / 2) {
|
||||
assert((sizeof(data) % 4 == 0) && (sizeof(data) <= 32));
|
||||
for (unsigned i = 0; i < sizeof(data); i += 2) {
|
||||
SNESColor col;
|
||||
col.snes = static_cast<uchar>(data[i + 1]) << 8;
|
||||
col.snes = col.snes | static_cast<uchar>(data[i]);
|
||||
snes_color mColor = ConvertSNEStoRGB(col.snes);
|
||||
col.rgb = ImVec4(mColor.red, mColor.green, mColor.blue, 1.f);
|
||||
colors.push_back(col);
|
||||
}
|
||||
}
|
||||
|
||||
SNESPalette::SNESPalette(const unsigned char* snes_pal)
|
||||
: size_(sizeof(snes_pal) / 2) {
|
||||
assert((sizeof(snes_pal) % 4 == 0) && (sizeof(snes_pal) <= 32));
|
||||
for (unsigned i = 0; i < sizeof(snes_pal); i += 2) {
|
||||
SNESColor col;
|
||||
col.snes = snes_pal[i + 1] << (uint16_t)8;
|
||||
col.snes = col.snes | snes_pal[i];
|
||||
snes_color mColor = ConvertSNEStoRGB(col.snes);
|
||||
col.rgb = ImVec4(mColor.red, mColor.green, mColor.blue, 1.f);
|
||||
colors.push_back(col);
|
||||
}
|
||||
}
|
||||
|
||||
SNESPalette::SNESPalette(const std::vector<ImVec4>& cols) {
|
||||
for (const auto& each : cols) {
|
||||
SNESColor scol;
|
||||
scol.setRgb(each);
|
||||
colors.push_back(scol);
|
||||
}
|
||||
size_ = cols.size();
|
||||
}
|
||||
|
||||
SNESPalette::SNESPalette(const std::vector<snes_color>& cols) {
|
||||
for (const auto& each : cols) {
|
||||
SNESColor scol;
|
||||
scol.setSNES(each);
|
||||
colors.push_back(scol);
|
||||
}
|
||||
size_ = cols.size();
|
||||
}
|
||||
|
||||
SNESPalette::SNESPalette(const std::vector<SNESColor>& cols) {
|
||||
for (const auto& each : cols) {
|
||||
colors.push_back(each);
|
||||
}
|
||||
size_ = cols.size();
|
||||
}
|
||||
|
||||
void SNESPalette::Create(const std::vector<SNESColor>& cols) {
|
||||
for (const auto each : cols) {
|
||||
colors.push_back(each);
|
||||
}
|
||||
size_ = cols.size();
|
||||
}
|
||||
|
||||
char* SNESPalette::encode() {
|
||||
auto data = new char[size_ * 2];
|
||||
for (unsigned int i = 0; i < size_; i++) {
|
||||
std::cout << colors[i].snes << std::endl;
|
||||
data[i * 2] = (char)(colors[i].snes & 0xFF);
|
||||
data[i * 2 + 1] = (char)(colors[i].snes >> 8);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
SDL_Palette* SNESPalette::GetSDL_Palette() {
|
||||
auto sdl_palette = std::make_shared<SDL_Palette>();
|
||||
sdl_palette->ncolors = size_;
|
||||
|
||||
auto color = std::vector<SDL_Color>(size_);
|
||||
for (int i = 0; i < size_; i++) {
|
||||
color[i].r = (uint8_t)colors[i].rgb.x * 100;
|
||||
color[i].g = (uint8_t)colors[i].rgb.y * 100;
|
||||
color[i].b = (uint8_t)colors[i].rgb.z * 100;
|
||||
color[i].a = 0;
|
||||
std::cout << "Color " << i << " added (R:" << color[i].r
|
||||
<< " G:" << color[i].g << " B:" << color[i].b << ")" << std::endl;
|
||||
}
|
||||
sdl_palette->colors = color.data();
|
||||
return sdl_palette.get();
|
||||
}
|
||||
|
||||
PaletteGroup::PaletteGroup(uint8_t mSize) : size(mSize) {}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
117
src/app/gfx/snes_palette.h
Normal file
117
src/app/gfx/snes_palette.h
Normal file
@@ -0,0 +1,117 @@
|
||||
#ifndef YAZE_APP_GFX_PALETTE_H
|
||||
#define YAZE_APP_GFX_PALETTE_H
|
||||
|
||||
#include <SDL.h>
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/constants.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gfx {
|
||||
|
||||
struct snes_color {
|
||||
uchar red;
|
||||
uchar blue;
|
||||
uchar green;
|
||||
};
|
||||
using snes_color = struct snes_color;
|
||||
|
||||
struct snes_palette {
|
||||
uint id;
|
||||
uint size;
|
||||
snes_color* colors;
|
||||
};
|
||||
using snes_palette = struct snes_palette;
|
||||
|
||||
ushort ConvertRGBtoSNES(const snes_color color);
|
||||
snes_color ConvertSNEStoRGB(const ushort snes_color);
|
||||
snes_palette* Extract(const char* data, const unsigned int offset,
|
||||
const unsigned int palette_size);
|
||||
char* Convert(const snes_palette pal);
|
||||
|
||||
struct SNESColor {
|
||||
SNESColor();
|
||||
explicit SNESColor(ImVec4);
|
||||
explicit SNESColor(snes_color);
|
||||
|
||||
void setRgb(ImVec4);
|
||||
void setSNES(snes_color);
|
||||
void setSNES(uint16_t);
|
||||
void setTransparent(bool t) { transparent = t; }
|
||||
|
||||
auto RGB() {
|
||||
return ImVec4(rgb.x / 255, rgb.y / 255, rgb.z / 255, rgb.w);
|
||||
}
|
||||
|
||||
bool transparent = false;
|
||||
uint16_t snes = 0;
|
||||
ImVec4 rgb;
|
||||
};
|
||||
|
||||
class SNESPalette {
|
||||
public:
|
||||
SNESPalette() = default;
|
||||
explicit SNESPalette(uint8_t mSize);
|
||||
explicit SNESPalette(char* snesPal);
|
||||
explicit SNESPalette(const unsigned char* snes_pal);
|
||||
explicit SNESPalette(const std::vector<ImVec4>&);
|
||||
explicit SNESPalette(const std::vector<snes_color>&);
|
||||
explicit SNESPalette(const std::vector<SNESColor>&);
|
||||
|
||||
void Create(const std::vector<SNESColor>&);
|
||||
void AddColor(SNESColor color) { colors.push_back(color); }
|
||||
auto GetColor(int i) const { return colors[i]; }
|
||||
|
||||
SNESColor operator[](int i) {
|
||||
if (i > size_) {
|
||||
std::cout << "SNESPalette: Index out of bounds" << std::endl;
|
||||
return colors[0];
|
||||
}
|
||||
return colors[i];
|
||||
}
|
||||
|
||||
char* encode();
|
||||
SDL_Palette* GetSDL_Palette();
|
||||
|
||||
int size_ = 0;
|
||||
std::vector<SNESColor> colors;
|
||||
};
|
||||
|
||||
struct PaletteGroup {
|
||||
PaletteGroup() = default;
|
||||
explicit PaletteGroup(uint8_t mSize);
|
||||
void AddPalette(SNESPalette pal) {
|
||||
palettes.emplace_back(pal);
|
||||
size = palettes.size();
|
||||
}
|
||||
void AddColor(SNESColor color) {
|
||||
if (size == 0) {
|
||||
SNESPalette empty_pal;
|
||||
palettes.emplace_back(empty_pal);
|
||||
}
|
||||
palettes[0].AddColor(color);
|
||||
}
|
||||
SNESPalette operator[](int i) {
|
||||
if (i > size) {
|
||||
std::cout << "PaletteGroup: Index out of bounds" << std::endl;
|
||||
return palettes[0];
|
||||
}
|
||||
return palettes[i];
|
||||
}
|
||||
int size = 0;
|
||||
std::vector<SNESPalette> palettes;
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GFX_PALETTE_H
|
||||
29
src/app/gfx/snes_tile.cc
Normal file
29
src/app/gfx/snes_tile.cc
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "snes_tile.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/constants.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gfx {
|
||||
|
||||
TileInfo GetTilesInfo(ushort tile) {
|
||||
// vhopppcc cccccccc
|
||||
bool o = false;
|
||||
bool v = false;
|
||||
bool h = false;
|
||||
auto tid = (ushort)(tile & core::TileNameMask);
|
||||
auto p = (uchar)((tile >> 10) & 0x07);
|
||||
|
||||
o = ((tile & core::TilePriorityBit) == core::TilePriorityBit);
|
||||
h = ((tile & core::TileHFlipBit) == core::TileHFlipBit);
|
||||
v = ((tile & core::TileVFlipBit) == core::TileVFlipBit);
|
||||
|
||||
return TileInfo(tid, p, v, h, o);
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
94
src/app/gfx/snes_tile.h
Normal file
94
src/app/gfx/snes_tile.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#ifndef YAZE_APP_GFX_SNES_TILE_H
|
||||
#define YAZE_APP_GFX_SNES_TILE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/constants.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace gfx {
|
||||
|
||||
struct tile8 {
|
||||
unsigned int id;
|
||||
char data[64];
|
||||
unsigned int palette_id;
|
||||
};
|
||||
using tile8 = struct tile8;
|
||||
|
||||
// vhopppcc cccccccc
|
||||
// [0, 1]
|
||||
// [2, 3]
|
||||
class TileInfo {
|
||||
public:
|
||||
ushort id_;
|
||||
bool over_;
|
||||
bool vertical_mirror_;
|
||||
bool horizontal_mirror_;
|
||||
uchar palette_;
|
||||
TileInfo() = default;
|
||||
TileInfo(ushort id, uchar palette, bool v, bool h, bool o)
|
||||
: id_(id),
|
||||
over_(o),
|
||||
vertical_mirror_(v),
|
||||
horizontal_mirror_(h),
|
||||
palette_(palette) {}
|
||||
};
|
||||
|
||||
TileInfo GetTilesInfo(ushort tile);
|
||||
|
||||
class Tile32 {
|
||||
public:
|
||||
ushort tile0_;
|
||||
ushort tile1_;
|
||||
ushort tile2_;
|
||||
ushort tile3_;
|
||||
|
||||
Tile32(ushort t0, ushort t1, ushort t2, ushort t3)
|
||||
: tile0_(t0), tile1_(t1), tile2_(t2), tile3_(t3) {}
|
||||
};
|
||||
|
||||
class Tile16 {
|
||||
public:
|
||||
TileInfo tile0_;
|
||||
TileInfo tile1_;
|
||||
TileInfo tile2_;
|
||||
TileInfo tile3_;
|
||||
std::vector<TileInfo> tiles_info;
|
||||
|
||||
Tile16() = default;
|
||||
Tile16(TileInfo t0, TileInfo t1, TileInfo t2, TileInfo t3)
|
||||
: tile0_(t0), tile1_(t1), tile2_(t2), tile3_(t3) {
|
||||
tiles_info.push_back(tile0_);
|
||||
tiles_info.push_back(tile1_);
|
||||
tiles_info.push_back(tile2_);
|
||||
tiles_info.push_back(tile3_);
|
||||
}
|
||||
};
|
||||
|
||||
class OAMTile {
|
||||
public:
|
||||
int x_;
|
||||
int y_;
|
||||
int mx_;
|
||||
int my_;
|
||||
int pal_;
|
||||
ushort tile_;
|
||||
OAMTile() = default;
|
||||
OAMTile(int x, int y, ushort tile, int pal, bool upper = false, int mx = 0,
|
||||
int my = 0)
|
||||
: x_(x), y_(y), mx_(mx), my_(my), pal_(pal) {
|
||||
if (upper) {
|
||||
tile_ = (ushort)(tile + 512);
|
||||
} else {
|
||||
tile_ = (ushort)(tile + 256 + 512);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GFX_SNES_TILE_H
|
||||
851
src/app/rom.cc
Normal file
851
src/app/rom.cc
Normal file
@@ -0,0 +1,851 @@
|
||||
#include "rom.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <asar/src/asar/interface-lib.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_cat.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/gfx/bitmap.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
|
||||
namespace lc_lz2 {
|
||||
|
||||
void PrintCompressionPiece(const std::shared_ptr<CompressionPiece>& piece) {
|
||||
printf("Command: %d\n", piece->command);
|
||||
printf("Command kength: %d\n", piece->length);
|
||||
printf("Argument:");
|
||||
auto arg_size = piece->argument.size();
|
||||
for (int i = 0; i < arg_size; ++i) {
|
||||
printf("%02X ", piece->argument.at(i));
|
||||
}
|
||||
printf("\nArgument length: %d\n", piece->argument_length);
|
||||
}
|
||||
|
||||
void PrintCompressionChain(
|
||||
const std::shared_ptr<CompressionPiece>& compressed_chain_start) {
|
||||
auto compressed_chain = compressed_chain_start->next;
|
||||
while (compressed_chain != nullptr) {
|
||||
printf("- Compression Piece -\n");
|
||||
PrintCompressionPiece(compressed_chain);
|
||||
compressed_chain = compressed_chain->next;
|
||||
}
|
||||
}
|
||||
|
||||
void CheckByteRepeat(const uchar* rom_data, DataSizeArray& data_size_taken,
|
||||
CommandArgumentArray& cmd_args, uint& src_data_pos,
|
||||
const uint last_pos) {
|
||||
uint pos = src_data_pos;
|
||||
char byte_to_repeat = rom_data[pos];
|
||||
while (pos <= last_pos && rom_data[pos] == byte_to_repeat) {
|
||||
data_size_taken[kCommandByteFill]++;
|
||||
pos++;
|
||||
}
|
||||
cmd_args[kCommandByteFill][0] = byte_to_repeat;
|
||||
}
|
||||
|
||||
void CheckWordRepeat(const uchar* rom_data, DataSizeArray& data_size_taken,
|
||||
CommandArgumentArray& cmd_args, uint& src_data_pos,
|
||||
const uint last_pos) {
|
||||
if (src_data_pos + 2 <= last_pos &&
|
||||
rom_data[src_data_pos] != rom_data[src_data_pos + 1]) {
|
||||
uint pos = src_data_pos;
|
||||
char byte1 = rom_data[pos];
|
||||
char byte2 = rom_data[pos + 1];
|
||||
pos += 2;
|
||||
data_size_taken[kCommandWordFill] = 2;
|
||||
while (pos + 1 <= last_pos) {
|
||||
if (rom_data[pos] == byte1 && rom_data[pos + 1] == byte2)
|
||||
data_size_taken[kCommandWordFill] += 2;
|
||||
else
|
||||
break;
|
||||
pos += 2;
|
||||
}
|
||||
cmd_args[kCommandWordFill][0] = byte1;
|
||||
cmd_args[kCommandWordFill][1] = byte2;
|
||||
}
|
||||
}
|
||||
|
||||
void CheckIncByte(const uchar* rom_data, DataSizeArray& data_size_taken,
|
||||
CommandArgumentArray& cmd_args, uint& src_data_pos,
|
||||
const uint last_pos) {
|
||||
uint pos = src_data_pos;
|
||||
char byte = rom_data[pos];
|
||||
pos++;
|
||||
data_size_taken[kCommandIncreasingFill] = 1;
|
||||
byte++;
|
||||
while (pos <= last_pos && byte == rom_data[pos]) {
|
||||
data_size_taken[kCommandIncreasingFill]++;
|
||||
byte++;
|
||||
pos++;
|
||||
}
|
||||
cmd_args[kCommandIncreasingFill][0] = rom_data[src_data_pos];
|
||||
}
|
||||
|
||||
void CheckIntraCopy(const uchar* rom_data, DataSizeArray& data_size_taken,
|
||||
CommandArgumentArray& cmd_args, uint& src_data_pos,
|
||||
const uint last_pos, uint start) {
|
||||
if (src_data_pos != start) {
|
||||
uint searching_pos = start;
|
||||
uint current_pos_u = src_data_pos;
|
||||
uint copied_size = 0;
|
||||
uint search_start = start;
|
||||
|
||||
while (searching_pos < src_data_pos && current_pos_u <= last_pos) {
|
||||
while (rom_data[current_pos_u] != rom_data[searching_pos] &&
|
||||
searching_pos < src_data_pos)
|
||||
searching_pos++;
|
||||
search_start = searching_pos;
|
||||
while (current_pos_u <= last_pos &&
|
||||
rom_data[current_pos_u] == rom_data[searching_pos] &&
|
||||
searching_pos < src_data_pos) {
|
||||
copied_size++;
|
||||
current_pos_u++;
|
||||
searching_pos++;
|
||||
}
|
||||
if (copied_size > data_size_taken[kCommandRepeatingBytes]) {
|
||||
search_start -= start;
|
||||
printf("- Found repeat of %d at %d\n", copied_size, search_start);
|
||||
data_size_taken[kCommandRepeatingBytes] = copied_size;
|
||||
cmd_args[kCommandRepeatingBytes][0] = search_start & kSnesByteMax;
|
||||
cmd_args[kCommandRepeatingBytes][1] = search_start >> 8;
|
||||
}
|
||||
current_pos_u = src_data_pos;
|
||||
copied_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a command managed to pick up `max_win` or more bytes
|
||||
// Avoids being even with copy command, since it's possible to merge copy
|
||||
void ValidateForByteGain(const DataSizeArray& data_size_taken,
|
||||
const CommandSizeArray& cmd_size, uint& max_win,
|
||||
uint& cmd_with_max) {
|
||||
for (uint cmd_i = 1; cmd_i < 5; cmd_i++) {
|
||||
uint cmd_size_taken = data_size_taken[cmd_i];
|
||||
// TODO(@scawful): Replace conditional with table of command sizes
|
||||
// "Table that is even with copy but all other cmd are 2"
|
||||
auto table_check =
|
||||
!(cmd_i == kCommandRepeatingBytes && cmd_size_taken == 3);
|
||||
if (cmd_size_taken > max_win && cmd_size_taken > cmd_size[cmd_i] &&
|
||||
table_check) {
|
||||
printf("==> C:%d / S:%d\n", cmd_i, cmd_size_taken);
|
||||
cmd_with_max = cmd_i;
|
||||
max_win = cmd_size_taken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompressionCommandAlternative(
|
||||
const uchar* rom_data, std::shared_ptr<CompressionPiece>& compressed_chain,
|
||||
const CommandSizeArray& cmd_size, const CommandArgumentArray& cmd_args,
|
||||
uint& src_data_pos, uint& comp_accumulator, uint& cmd_with_max,
|
||||
uint& max_win) {
|
||||
printf("- Ok we get a gain from %d\n", cmd_with_max);
|
||||
std::string buffer;
|
||||
buffer.push_back(cmd_args[cmd_with_max][0]);
|
||||
if (cmd_size[cmd_with_max] == 2) {
|
||||
buffer.push_back(cmd_args[cmd_with_max][1]);
|
||||
}
|
||||
|
||||
auto new_comp_piece = std::make_shared<CompressionPiece>(
|
||||
cmd_with_max, max_win, buffer, cmd_size[cmd_with_max]);
|
||||
PrintCompressionPiece(new_comp_piece);
|
||||
// If we let non compressed stuff, we need to add a copy chunk before
|
||||
if (comp_accumulator != 0) {
|
||||
std::string copy_buff;
|
||||
copy_buff.resize(comp_accumulator);
|
||||
for (int i = 0; i < comp_accumulator; ++i) {
|
||||
copy_buff[i] = rom_data[i + src_data_pos - comp_accumulator];
|
||||
}
|
||||
auto copy_chunk = std::make_shared<CompressionPiece>(
|
||||
kCommandDirectCopy, comp_accumulator, copy_buff, comp_accumulator);
|
||||
compressed_chain->next = copy_chunk;
|
||||
compressed_chain = copy_chunk;
|
||||
} else {
|
||||
compressed_chain->next = new_comp_piece;
|
||||
compressed_chain = new_comp_piece;
|
||||
}
|
||||
src_data_pos += max_win;
|
||||
comp_accumulator = 0;
|
||||
}
|
||||
|
||||
absl::StatusOr<std::shared_ptr<CompressionPiece>> SplitCompressionPiece(
|
||||
std::shared_ptr<CompressionPiece>& piece, int mode) {
|
||||
std::shared_ptr<CompressionPiece> new_piece;
|
||||
uint length_left = piece->length - kMaxLengthCompression;
|
||||
piece->length = kMaxLengthCompression;
|
||||
|
||||
switch (piece->command) {
|
||||
case kCommandByteFill:
|
||||
case kCommandWordFill:
|
||||
new_piece = std::make_shared<CompressionPiece>(
|
||||
piece->command, length_left, piece->argument, piece->argument_length);
|
||||
break;
|
||||
case kCommandIncreasingFill:
|
||||
new_piece = std::make_shared<CompressionPiece>(
|
||||
piece->command, length_left, piece->argument, piece->argument_length);
|
||||
new_piece->argument[0] =
|
||||
(char)(piece->argument[0] + kMaxLengthCompression);
|
||||
break;
|
||||
case kCommandDirectCopy:
|
||||
piece->argument_length = kMaxLengthCompression;
|
||||
new_piece = std::make_shared<CompressionPiece>(
|
||||
piece->command, length_left, nullptr, length_left);
|
||||
// MEMCPY
|
||||
for (int i = 0; i < length_left; ++i) {
|
||||
new_piece->argument[i] = piece->argument[i + kMaxLengthCompression];
|
||||
}
|
||||
break;
|
||||
case kCommandRepeatingBytes: {
|
||||
piece->argument_length = kMaxLengthCompression;
|
||||
uint offset = piece->argument[0] + (piece->argument[1] << 8);
|
||||
new_piece = std::make_shared<CompressionPiece>(
|
||||
piece->command, length_left, piece->argument, piece->argument_length);
|
||||
if (mode == kNintendoMode2) {
|
||||
new_piece->argument[0] =
|
||||
(offset + kMaxLengthCompression) & kSnesByteMax;
|
||||
new_piece->argument[1] = (offset + kMaxLengthCompression) >> 8;
|
||||
}
|
||||
if (mode == kNintendoMode1) {
|
||||
new_piece->argument[1] =
|
||||
(offset + kMaxLengthCompression) & kSnesByteMax;
|
||||
new_piece->argument[0] = (offset + kMaxLengthCompression) >> 8;
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
return absl::InvalidArgumentError(
|
||||
"SplitCompressionCommand: Invalid Command");
|
||||
}
|
||||
}
|
||||
return new_piece;
|
||||
}
|
||||
|
||||
Bytes CreateCompressionString(std::shared_ptr<CompressionPiece>& start,
|
||||
int mode) {
|
||||
uint pos = 0;
|
||||
auto piece = start;
|
||||
Bytes output;
|
||||
|
||||
while (piece != nullptr) {
|
||||
if (piece->length <= kMaxLengthNormalHeader) { // Normal header
|
||||
output.push_back(BUILD_HEADER(piece->command, piece->length));
|
||||
pos++;
|
||||
} else {
|
||||
if (piece->length <= kMaxLengthCompression) {
|
||||
output.push_back(kCompressionStringMod | ((uchar)piece->command << 2) |
|
||||
(((piece->length - 1) & 0xFF00) >> 8));
|
||||
pos++;
|
||||
printf("Building extended header : cmd: %d, length: %d - %02X\n",
|
||||
piece->command, piece->length, output[pos - 1]);
|
||||
output.push_back(((piece->length - 1) & 0x00FF)); // (char)
|
||||
pos++;
|
||||
} else {
|
||||
// We need to split the command
|
||||
auto new_piece = SplitCompressionPiece(piece, mode);
|
||||
if (!new_piece.ok()) {
|
||||
std::cout << new_piece.status().ToString() << std::endl;
|
||||
}
|
||||
printf("New added piece\n");
|
||||
auto piece_data = new_piece.value();
|
||||
PrintCompressionPiece(piece_data);
|
||||
piece_data->next = piece->next;
|
||||
piece->next = piece_data;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (piece->command == kCommandRepeatingBytes) {
|
||||
char tmp[2];
|
||||
tmp[0] = piece->argument[0];
|
||||
tmp[1] = piece->argument[1];
|
||||
if (mode == kNintendoMode1) {
|
||||
tmp[0] = piece->argument[1];
|
||||
tmp[1] = piece->argument[0];
|
||||
}
|
||||
for (const auto& each : tmp) {
|
||||
output.push_back(each);
|
||||
pos++;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < piece->argument_length; ++i) {
|
||||
output.push_back(piece->argument[i]);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
pos += piece->argument_length;
|
||||
piece = piece->next;
|
||||
}
|
||||
output.push_back(kSnesByteMax);
|
||||
return output;
|
||||
}
|
||||
|
||||
absl::Status ValidateCompressionResult(
|
||||
CompressionPiecePointer& compressed_chain_start, int mode, int start,
|
||||
int src_data_pos) {
|
||||
if (compressed_chain_start->next != nullptr) {
|
||||
ROM temp_rom;
|
||||
RETURN_IF_ERROR(temp_rom.LoadFromBytes(
|
||||
CreateCompressionString(compressed_chain_start->next, mode)))
|
||||
ASSIGN_OR_RETURN(auto decomp_data, temp_rom.Decompress(0, temp_rom.size()))
|
||||
if (!std::equal(decomp_data.begin() + start, decomp_data.end(),
|
||||
temp_rom.begin())) {
|
||||
return absl::InternalError(absl::StrFormat(
|
||||
"Compressed data does not match uncompressed data at %d\n",
|
||||
(uint)(src_data_pos - start)));
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// Merge consecutive copy if possible
|
||||
CompressionPiecePointer MergeCopy(CompressionPiecePointer& start) {
|
||||
CompressionPiecePointer piece = start;
|
||||
|
||||
while (piece != nullptr) {
|
||||
if (piece->command == kCommandDirectCopy && piece->next != nullptr &&
|
||||
piece->next->command == kCommandDirectCopy &&
|
||||
piece->length + piece->next->length <= kMaxLengthCompression) {
|
||||
uint previous_length = piece->length;
|
||||
piece->length = piece->length + piece->next->length;
|
||||
|
||||
for (int i = 0; i < piece->next->argument_length; ++i) {
|
||||
piece->argument[i + previous_length] = piece->next->argument[i];
|
||||
}
|
||||
piece->argument_length = piece->length;
|
||||
PrintCompressionPiece(piece);
|
||||
|
||||
auto p_next_next = piece->next->next;
|
||||
piece->next = p_next_next;
|
||||
continue; // Next could be another copy
|
||||
}
|
||||
piece = piece->next;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
} // namespace lc_lz2
|
||||
|
||||
namespace {
|
||||
|
||||
int GetGraphicsAddress(const uchar* data, uint8_t offset) {
|
||||
auto part_one = data[kOverworldGraphicsPos1 + offset] << 16;
|
||||
auto part_two = data[kOverworldGraphicsPos2 + offset] << 8;
|
||||
auto part_three = data[kOverworldGraphicsPos3 + offset];
|
||||
auto snes_addr = (part_one | part_two | part_three);
|
||||
return core::SnesToPc(snes_addr);
|
||||
}
|
||||
|
||||
Bytes SnesTo8bppSheet(Bytes sheet, int bpp) {
|
||||
int xx = 0; // positions where we are at on the sheet
|
||||
int yy = 0;
|
||||
int pos = 0;
|
||||
int ypos = 0;
|
||||
int num_tiles = 64;
|
||||
int buffer_size = 0x1000;
|
||||
if (bpp == 2) {
|
||||
bpp = 16;
|
||||
num_tiles = 128;
|
||||
buffer_size = 0x2000;
|
||||
} else if (bpp == 3) {
|
||||
bpp = 24;
|
||||
}
|
||||
Bytes sheet_buffer_out(buffer_size);
|
||||
|
||||
for (int i = 0; i < num_tiles; i++) { // for each tiles, 16 per line
|
||||
for (int y = 0; y < 8; y++) { // for each line
|
||||
for (int x = 0; x < 8; x++) { //[0] + [1] + [16]
|
||||
auto b1 = (sheet[(y * 2) + (bpp * pos)] & (kGraphicsBitmap[x]));
|
||||
auto b2 = (sheet[((y * 2) + (bpp * pos)) + 1] & (kGraphicsBitmap[x]));
|
||||
auto b3 = (sheet[(16 + y) + (bpp * pos)] & (kGraphicsBitmap[x]));
|
||||
unsigned char b = 0;
|
||||
if (b1 != 0) {
|
||||
b |= 1;
|
||||
}
|
||||
if (b2 != 0) {
|
||||
b |= 2;
|
||||
}
|
||||
if (b3 != 0 && bpp != 16) {
|
||||
b |= 4;
|
||||
}
|
||||
sheet_buffer_out[x + xx + (y * 128) + (yy * 1024)] = b;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
ypos++;
|
||||
xx += 8;
|
||||
if (ypos >= 16) {
|
||||
yy++;
|
||||
xx = 0;
|
||||
ypos = 0;
|
||||
}
|
||||
}
|
||||
return sheet_buffer_out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO TEST compressed data border for each cmd
|
||||
absl::StatusOr<Bytes> ROM::Compress(const int start, const int length, int mode,
|
||||
bool check) {
|
||||
// Worse case should be a copy of the string with extended header
|
||||
auto compressed_chain = std::make_shared<CompressionPiece>(1, 1, "aaa", 2);
|
||||
auto compressed_chain_start = compressed_chain;
|
||||
|
||||
CommandArgumentArray cmd_args = {{}};
|
||||
DataSizeArray data_size_taken = {0, 0, 0, 0, 0};
|
||||
CommandSizeArray cmd_size = {0, 1, 2, 1, 2};
|
||||
|
||||
uint src_data_pos = start;
|
||||
uint last_pos = start + length - 1;
|
||||
uint comp_accumulator = 0; // Used when skipping using copy
|
||||
|
||||
while (true) {
|
||||
data_size_taken.fill({});
|
||||
cmd_args.fill({{}});
|
||||
|
||||
lc_lz2::CheckByteRepeat(rom_data_.data(), data_size_taken, cmd_args,
|
||||
src_data_pos, last_pos);
|
||||
lc_lz2::CheckWordRepeat(rom_data_.data(), data_size_taken, cmd_args,
|
||||
src_data_pos, last_pos);
|
||||
lc_lz2::CheckIncByte(rom_data_.data(), data_size_taken, cmd_args,
|
||||
src_data_pos, last_pos);
|
||||
lc_lz2::CheckIntraCopy(rom_data_.data(), data_size_taken, cmd_args,
|
||||
src_data_pos, last_pos, start);
|
||||
|
||||
uint max_win = 2;
|
||||
uint cmd_with_max = kCommandDirectCopy;
|
||||
lc_lz2::ValidateForByteGain(data_size_taken, cmd_size, max_win,
|
||||
cmd_with_max);
|
||||
|
||||
if (cmd_with_max == kCommandDirectCopy) {
|
||||
// This is the worst case scenario
|
||||
// Progress through the next byte, in case there's a different
|
||||
// compression command we can implement before we hit 32 bytes.
|
||||
src_data_pos++;
|
||||
comp_accumulator++;
|
||||
|
||||
// Arbitrary choice to do a 32 bytes grouping for copy.
|
||||
if (comp_accumulator == 32 || src_data_pos > last_pos) {
|
||||
std::string buffer;
|
||||
for (int i = 0; i < comp_accumulator; ++i) {
|
||||
buffer.push_back(rom_data_[i + src_data_pos - comp_accumulator]);
|
||||
}
|
||||
auto new_comp_piece = std::make_shared<CompressionPiece>(
|
||||
kCommandDirectCopy, comp_accumulator, buffer, comp_accumulator);
|
||||
compressed_chain->next = new_comp_piece;
|
||||
compressed_chain = new_comp_piece;
|
||||
comp_accumulator = 0;
|
||||
}
|
||||
} else {
|
||||
lc_lz2::CompressionCommandAlternative(
|
||||
rom_data_.data(), compressed_chain, cmd_size, cmd_args, src_data_pos,
|
||||
comp_accumulator, cmd_with_max, max_win);
|
||||
}
|
||||
|
||||
if (src_data_pos > last_pos) {
|
||||
printf("Breaking compression loop\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (check) {
|
||||
RETURN_IF_ERROR(lc_lz2::ValidateCompressionResult(
|
||||
compressed_chain_start, mode, start, src_data_pos))
|
||||
}
|
||||
}
|
||||
|
||||
// Skipping compression chain header
|
||||
lc_lz2::MergeCopy(compressed_chain_start->next);
|
||||
lc_lz2::PrintCompressionChain(compressed_chain_start);
|
||||
return lc_lz2::CreateCompressionString(compressed_chain_start->next, mode);
|
||||
}
|
||||
|
||||
absl::StatusOr<Bytes> ROM::CompressGraphics(const int pos, const int length) {
|
||||
return Compress(pos, length, kNintendoMode2);
|
||||
}
|
||||
|
||||
absl::StatusOr<Bytes> ROM::CompressOverworld(const int pos, const int length) {
|
||||
return Compress(pos, length, kNintendoMode1);
|
||||
}
|
||||
|
||||
absl::StatusOr<Bytes> ROM::Decompress(int offset, int size, int mode) {
|
||||
Bytes buffer(size, 0);
|
||||
uint length = 0;
|
||||
uint buffer_pos = 0;
|
||||
uchar command = 0;
|
||||
uchar header = rom_data_[offset];
|
||||
|
||||
while (header != kSnesByteMax) {
|
||||
if ((header & kExpandedMod) == kExpandedMod) {
|
||||
// Expanded Command
|
||||
command = ((header >> 2) & kCommandMod);
|
||||
length = (((header << 8) | rom_data_[offset + 1]) & kExpandedLengthMod);
|
||||
offset += 2; // Advance 2 bytes in ROM
|
||||
} else {
|
||||
// Normal Command
|
||||
command = ((header >> 5) & kCommandMod);
|
||||
length = (header & kNormalLengthMod);
|
||||
offset += 1; // Advance 1 byte in ROM
|
||||
}
|
||||
length += 1; // each commands is at least of size 1 even if index 00
|
||||
|
||||
switch (command) {
|
||||
case kCommandDirectCopy: // Does not advance in the ROM
|
||||
memcpy(buffer.data() + buffer_pos, rom_data_.data() + offset, length);
|
||||
buffer_pos += length;
|
||||
offset += length;
|
||||
break;
|
||||
case kCommandByteFill:
|
||||
memset(buffer.data() + buffer_pos, (int)(rom_data_[offset]), length);
|
||||
buffer_pos += length;
|
||||
offset += 1; // Advances 1 byte in the ROM
|
||||
break;
|
||||
case kCommandWordFill: {
|
||||
auto a = rom_data_[offset];
|
||||
auto b = rom_data_[offset + 1];
|
||||
for (int i = 0; i < length; i = i + 2) {
|
||||
buffer[buffer_pos + i] = a;
|
||||
if ((i + 1) < length) buffer[buffer_pos + i + 1] = b;
|
||||
}
|
||||
buffer_pos += length;
|
||||
offset += 2; // Advance 2 byte in the ROM
|
||||
} break;
|
||||
case kCommandIncreasingFill: {
|
||||
auto inc_byte = rom_data_[offset];
|
||||
for (int i = 0; i < length; i++) {
|
||||
buffer[buffer_pos] = inc_byte++;
|
||||
buffer_pos++;
|
||||
}
|
||||
offset += 1; // Advance 1 byte in the ROM
|
||||
} break;
|
||||
case kCommandRepeatingBytes: {
|
||||
ushort s1 = ((rom_data_[offset + 1] & kSnesByteMax) << 8);
|
||||
ushort s2 = ((rom_data_[offset] & kSnesByteMax));
|
||||
int addr = (s1 | s2);
|
||||
if (mode == kNintendoMode1) { // Reversed byte order for overworld maps
|
||||
addr = (rom_data_[offset + 1] & kSnesByteMax) |
|
||||
((rom_data_[offset] & kSnesByteMax) << 8);
|
||||
}
|
||||
if (addr > offset) {
|
||||
return absl::InternalError(absl::StrFormat(
|
||||
"Decompress: Offset for command copy exceeds current position "
|
||||
"(Offset : %#04x | Pos : %#06x)\n",
|
||||
addr, offset));
|
||||
}
|
||||
if (buffer_pos + length >= size) {
|
||||
size *= 2;
|
||||
buffer.resize(size);
|
||||
}
|
||||
memcpy(buffer.data() + buffer_pos, buffer.data() + addr, length);
|
||||
buffer_pos += length;
|
||||
offset += 2;
|
||||
} break;
|
||||
default: {
|
||||
std::cout << absl::StrFormat(
|
||||
"Decompress: Invalid header (Offset : %#06x, Command: %#04x)\n",
|
||||
offset, command);
|
||||
} break;
|
||||
}
|
||||
// check next byte
|
||||
header = rom_data_[offset];
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
absl::StatusOr<Bytes> ROM::DecompressGraphics(int pos, int size) {
|
||||
return Decompress(pos, size, kNintendoMode2);
|
||||
}
|
||||
|
||||
absl::StatusOr<Bytes> ROM::DecompressOverworld(int pos, int size) {
|
||||
return Decompress(pos, size, kNintendoMode1);
|
||||
}
|
||||
|
||||
absl::StatusOr<Bytes> ROM::Load2bppGraphics() {
|
||||
Bytes sheet;
|
||||
const uint8_t sheets[] = {113, 114, 218, 219, 220, 221};
|
||||
|
||||
for (const auto& sheet_id : sheets) {
|
||||
auto offset = GetGraphicsAddress(rom_data_.data(), sheet_id);
|
||||
ASSIGN_OR_RETURN(auto decomp_sheet, Decompress(offset))
|
||||
auto converted_sheet = SnesTo8bppSheet(decomp_sheet, 2);
|
||||
for (const auto& each_pixel : converted_sheet) {
|
||||
sheet.push_back(each_pixel);
|
||||
}
|
||||
}
|
||||
return sheet;
|
||||
}
|
||||
|
||||
// 0-112 -> compressed 3bpp bgr -> (decompressed each) 0x600 chars
|
||||
// 113-114 -> compressed 2bpp -> (decompressed each) 0x800 chars
|
||||
// 115-126 -> uncompressed 3bpp sprites -> (each) 0x600 chars
|
||||
// 127-217 -> compressed 3bpp sprites -> (decompressed each) 0x600 chars
|
||||
// 218-222 -> compressed 2bpp -> (decompressed each) 0x800 chars
|
||||
absl::Status ROM::LoadAllGraphicsData() {
|
||||
Bytes sheet;
|
||||
bool bpp3 = false;
|
||||
|
||||
for (int i = 0; i < core::NumberOfSheets; i++) {
|
||||
if (i >= 115 && i <= 126) { // uncompressed sheets
|
||||
sheet.resize(core::Uncompressed3BPPSize);
|
||||
auto offset = GetGraphicsAddress(rom_data_.data(), i);
|
||||
for (int j = 0; j < core::Uncompressed3BPPSize; j++) {
|
||||
sheet[j] = rom_data_[j + offset];
|
||||
}
|
||||
bpp3 = true;
|
||||
} else if (i == 113 || i == 114 || i >= 218) {
|
||||
bpp3 = false;
|
||||
} else {
|
||||
auto offset = GetGraphicsAddress(rom_data_.data(), i);
|
||||
ASSIGN_OR_RETURN(sheet, Decompress(offset))
|
||||
bpp3 = true;
|
||||
}
|
||||
|
||||
if (bpp3) {
|
||||
auto converted_sheet = SnesTo8bppSheet(sheet, 3);
|
||||
graphics_bin_[i] =
|
||||
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
|
||||
core::kTilesheetDepth, converted_sheet.data(), 0x1000);
|
||||
graphics_bin_.at(i).CreateTexture(renderer_);
|
||||
|
||||
for (int j = 0; j < graphics_bin_.at(i).GetSize(); ++j) {
|
||||
graphics_buffer_.push_back(graphics_bin_.at(i).GetByte(j));
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < graphics_bin_.at(0).GetSize(); ++j) {
|
||||
graphics_buffer_.push_back(0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ROM::LoadFromFile(const absl::string_view& filename) {
|
||||
filename_ = filename;
|
||||
std::ifstream file(filename.data(), std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return absl::InternalError(
|
||||
absl::StrCat("Could not open ROM file: ", filename));
|
||||
}
|
||||
|
||||
size_ = std::filesystem::file_size(filename);
|
||||
rom_data_.resize(size_);
|
||||
for (auto i = 0; i < size_; ++i) {
|
||||
char byte_to_read = ' ';
|
||||
file.read(&byte_to_read, sizeof(char));
|
||||
rom_data_[i] = byte_to_read;
|
||||
}
|
||||
|
||||
// copy ROM title
|
||||
memcpy(title, rom_data_.data() + kTitleStringOffset, kTitleStringLength);
|
||||
|
||||
file.close();
|
||||
LoadAllPalettes();
|
||||
is_loaded_ = true;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ROM::LoadFromPointer(uchar* data, size_t length) {
|
||||
if (!data)
|
||||
return absl::InvalidArgumentError(
|
||||
"Could not load ROM: parameter `data` is empty.");
|
||||
|
||||
for (int i = 0; i < length; ++i) rom_data_.push_back(data[i]);
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ROM::LoadFromBytes(const Bytes& data) {
|
||||
if (data.empty()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Could not load ROM: parameter `data` is empty.");
|
||||
}
|
||||
rom_data_ = data;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ROM::SaveToFile() {
|
||||
std::fstream file(filename_.data(), std::ios::binary | std::ios::out);
|
||||
if (!file.is_open()) {
|
||||
return absl::InternalError(
|
||||
absl::StrCat("Could not open ROM file: ", filename_));
|
||||
}
|
||||
for (auto i = 0; i < size_; ++i) {
|
||||
file << rom_data_[i];
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void ROM::RenderBitmap(gfx::Bitmap* bitmap) const {
|
||||
bitmap->CreateTexture(renderer_);
|
||||
}
|
||||
|
||||
gfx::SNESColor ROM::ReadColor(int offset) {
|
||||
short color = toint16(offset);
|
||||
gfx::snes_color new_color;
|
||||
new_color.red = (color & 0x1F) * 8;
|
||||
new_color.green = ((color >> 5) & 0x1F) * 8;
|
||||
new_color.blue = ((color >> 10) & 0x1F) * 8;
|
||||
gfx::SNESColor snes_color(new_color);
|
||||
return snes_color;
|
||||
}
|
||||
|
||||
gfx::SNESPalette ROM::ReadPalette(int offset, int num_colors) {
|
||||
int color_offset = 0;
|
||||
std::vector<gfx::SNESColor> colors(num_colors);
|
||||
|
||||
while (color_offset < num_colors) {
|
||||
short color = toint16(offset);
|
||||
gfx::snes_color new_color;
|
||||
new_color.red = (color & 0x1F) * 8;
|
||||
new_color.green = ((color >> 5) & 0x1F) * 8;
|
||||
new_color.blue = ((color >> 10) & 0x1F) * 8;
|
||||
colors[color_offset].setSNES(new_color);
|
||||
color_offset++;
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
gfx::SNESPalette palette(colors);
|
||||
return palette;
|
||||
}
|
||||
|
||||
void ROM::LoadAllPalettes() {
|
||||
// 35 colors each, 7x5 (0,2 on grid)
|
||||
for (int i = 0; i < 6; i++) {
|
||||
palette_groups_["ow_main"].AddPalette(
|
||||
ReadPalette(core::overworldPaletteMain + (i * (35 * 2)), 35));
|
||||
}
|
||||
// 21 colors each, 7x3 (8,2 and 8,5 on grid)
|
||||
for (int i = 0; i < 20; i++) {
|
||||
palette_groups_["ow_aux"].AddPalette(
|
||||
ReadPalette(core::overworldPaletteAuxialiary + (i * (21 * 2)), 21));
|
||||
}
|
||||
// 7 colors each 7x1 (0,7 on grid)
|
||||
for (int i = 0; i < 14; i++) {
|
||||
palette_groups_["ow_animated"].AddPalette(
|
||||
ReadPalette(core::overworldPaletteAnimated + (i * (7 * 2)), 7));
|
||||
}
|
||||
// 32 colors each 16x2 (0,0 on grid)
|
||||
for (int i = 0; i < 2; i++) {
|
||||
palette_groups_["hud"].AddPalette(
|
||||
ReadPalette(core::hudPalettes + (i * 64), 32));
|
||||
}
|
||||
|
||||
palette_groups_["global_sprites"].AddPalette(
|
||||
ReadPalette(core::globalSpritePalettesLW, 60));
|
||||
palette_groups_["global_sprites"].AddPalette(
|
||||
ReadPalette(core::globalSpritePalettesDW, 60));
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
palette_groups_["armors"].AddPalette(
|
||||
ReadPalette(core::armorPalettes + (i * 30), 15));
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
palette_groups_["swords"].AddPalette(
|
||||
ReadPalette(core::swordPalettes + (i * 6), 3));
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
palette_groups_["shields"].AddPalette(
|
||||
ReadPalette(core::shieldPalettes + (i * 8), 4));
|
||||
}
|
||||
for (int i = 0; i < 12; i++) {
|
||||
palette_groups_["sprites_aux1"].AddPalette(
|
||||
ReadPalette(core::spritePalettesAux1 + (i * 14), 7));
|
||||
}
|
||||
for (int i = 0; i < 11; i++) {
|
||||
palette_groups_["sprites_aux2"].AddPalette(
|
||||
ReadPalette(core::spritePalettesAux2 + (i * 14), 7));
|
||||
}
|
||||
for (int i = 0; i < 24; i++) {
|
||||
palette_groups_["sprites_aux3"].AddPalette(
|
||||
ReadPalette(core::spritePalettesAux3 + (i * 14), 7));
|
||||
}
|
||||
for (int i = 0; i < 20; i++) {
|
||||
palette_groups_["dungeon_main"].AddPalette(
|
||||
ReadPalette(core::dungeonMainPalettes + (i * 180), 90));
|
||||
}
|
||||
|
||||
palette_groups_["grass"].AddColor(ReadColor(core::hardcodedGrassLW));
|
||||
palette_groups_["grass"].AddColor(ReadColor(core::hardcodedGrassDW));
|
||||
palette_groups_["grass"].AddColor(ReadColor(core::hardcodedGrassSpecial));
|
||||
|
||||
palette_groups_["3d_object"].AddPalette(
|
||||
ReadPalette(core::triforcePalette, 8));
|
||||
palette_groups_["3d_object"].AddPalette(ReadPalette(core::crystalPalette, 8));
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
palette_groups_["ow_mini_map"].AddPalette(
|
||||
ReadPalette(core::overworldMiniMapPalettes + (i * 256), 128));
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status ROM::ApplyAssembly(const absl::string_view& filename,
|
||||
size_t patch_size) {
|
||||
int count = 0;
|
||||
auto patch = filename.data();
|
||||
auto data = (char*)rom_data_.data();
|
||||
if (int size = size_; !asar_patch(patch, data, patch_size, &size)) {
|
||||
auto asar_error = asar_geterrors(&count);
|
||||
auto full_error = asar_error->fullerrdata;
|
||||
return absl::InternalError(absl::StrCat("ASAR Error: ", full_error));
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// TODO(scawful): Test me!
|
||||
absl::Status ROM::PatchOverworldMosaic(
|
||||
char mosaic_tiles[core::kNumOverworldMaps], int routine_offset) {
|
||||
// Write the data for the mosaic tile array used by the assembly code.
|
||||
for (int i = 0; i < core::kNumOverworldMaps; i++) {
|
||||
if (mosaic_tiles[i]) {
|
||||
rom_data_[core::overworldCustomMosaicArray + i] = 0x01;
|
||||
} else {
|
||||
rom_data_[core::overworldCustomMosaicArray + i] = 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
std::string filename = "assets/asm/mosaic_change.asm";
|
||||
std::fstream file(filename, std::ios::out | std::ios::in);
|
||||
if (!file.is_open()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Unable to open mosaic change assembly source");
|
||||
}
|
||||
|
||||
std::stringstream assembly;
|
||||
assembly << file.rdbuf();
|
||||
file.close();
|
||||
auto assembly_string = assembly.str();
|
||||
|
||||
if (!core::StringReplace(assembly_string, "<HOOK>", kMosaicChangeOffset)) {
|
||||
return absl::InternalError(
|
||||
"Mosaic template did not have proper `<HOOK>` to replace.");
|
||||
}
|
||||
|
||||
if (!core::StringReplace(
|
||||
assembly_string, "<EXPANDED_SPACE>",
|
||||
absl::StrFormat("$%x", routine_offset + kSNESToPCOffset))) {
|
||||
return absl::InternalError(
|
||||
"Mosaic template did not have proper `<EXPANDED_SPACE>` to replace.");
|
||||
}
|
||||
|
||||
return ApplyAssembly(filename, assembly_string.size());
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
157
src/app/rom.h
Normal file
157
src/app/rom.h
Normal file
@@ -0,0 +1,157 @@
|
||||
#ifndef YAZE_APP_ROM_H
|
||||
#define YAZE_APP_ROM_H
|
||||
|
||||
#include <SDL.h>
|
||||
#include <asar/src/asar/interface-lib.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_cat.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/gfx/bitmap.h"
|
||||
|
||||
#define BUILD_HEADER(command, length) (command << 5) + (length - 1)
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
|
||||
constexpr int kCommandDirectCopy = 0;
|
||||
constexpr int kCommandByteFill = 1;
|
||||
constexpr int kCommandWordFill = 2;
|
||||
constexpr int kCommandIncreasingFill = 3;
|
||||
constexpr int kCommandRepeatingBytes = 4;
|
||||
constexpr int kCommandLongLength = 7;
|
||||
constexpr int kMaxLengthNormalHeader = 32;
|
||||
constexpr int kMaxLengthCompression = 1024;
|
||||
constexpr int kNintendoMode1 = 0;
|
||||
constexpr int kNintendoMode2 = 1;
|
||||
constexpr int kTile32Num = 4432;
|
||||
constexpr int kTitleStringOffset = 0x7FC0;
|
||||
constexpr int kTitleStringLength = 20;
|
||||
constexpr int kOverworldGraphicsPos1 = 0x4F80;
|
||||
constexpr int kOverworldGraphicsPos2 = 0x505F;
|
||||
constexpr int kOverworldGraphicsPos3 = 0x513E;
|
||||
constexpr int kSnesByteMax = 0xFF;
|
||||
constexpr int kCommandMod = 0x07;
|
||||
constexpr int kExpandedMod = 0xE0;
|
||||
constexpr int kExpandedLengthMod = 0x3FF;
|
||||
constexpr int kNormalLengthMod = 0x1F;
|
||||
constexpr int kCompressionStringMod = 7 << 5;
|
||||
constexpr uchar kGraphicsBitmap[8] = {0x80, 0x40, 0x20, 0x10,
|
||||
0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
const std::string kMosaicChangeOffset = "$02AADB";
|
||||
constexpr int kSNESToPCOffset = 0x138000;
|
||||
|
||||
using CommandArgumentArray = std::array<std::array<char, 2>, 5>;
|
||||
using CommandSizeArray = std::array<uint, 5>;
|
||||
using DataSizeArray = std::array<uint, 5>;
|
||||
struct CompressionPiece {
|
||||
char command;
|
||||
int length;
|
||||
int argument_length;
|
||||
std::string argument;
|
||||
std::shared_ptr<CompressionPiece> next = nullptr;
|
||||
CompressionPiece() = default;
|
||||
CompressionPiece(int cmd, int len, std::string args, int arg_len)
|
||||
: command(cmd), length(len), argument_length(arg_len), argument(args) {}
|
||||
};
|
||||
using CompressionPiece = struct CompressionPiece;
|
||||
using CompressionPiecePointer = std::shared_ptr<CompressionPiece>;
|
||||
|
||||
class ROM {
|
||||
public:
|
||||
absl::StatusOr<Bytes> Compress(const int start, const int length,
|
||||
int mode = 1, bool check = false);
|
||||
absl::StatusOr<Bytes> CompressGraphics(const int pos, const int length);
|
||||
absl::StatusOr<Bytes> CompressOverworld(const int pos, const int length);
|
||||
|
||||
absl::StatusOr<Bytes> Decompress(int offset, int size = 0x800, int mode = 1);
|
||||
absl::StatusOr<Bytes> DecompressGraphics(int pos, int size);
|
||||
absl::StatusOr<Bytes> DecompressOverworld(int pos, int size);
|
||||
|
||||
absl::StatusOr<Bytes> Load2bppGraphics();
|
||||
|
||||
absl::Status LoadAllGraphicsData();
|
||||
absl::Status LoadFromFile(const absl::string_view& filename);
|
||||
absl::Status LoadFromPointer(uchar* data, size_t length);
|
||||
absl::Status LoadFromBytes(const Bytes& data);
|
||||
void LoadAllPalettes();
|
||||
|
||||
absl::Status SaveToFile();
|
||||
|
||||
gfx::SNESColor ReadColor(int offset);
|
||||
gfx::SNESPalette ReadPalette(int offset, int num_colors);
|
||||
|
||||
void RenderBitmap(gfx::Bitmap* bitmap) const;
|
||||
|
||||
absl::Status ApplyAssembly(const absl::string_view& filename,
|
||||
size_t patch_size);
|
||||
absl::Status PatchOverworldMosaic(char mosaic_tiles[core::kNumOverworldMaps],
|
||||
int routine_offset);
|
||||
|
||||
auto GetTitle() const { return title; }
|
||||
auto GetGraphicsBin() const { return graphics_bin_; }
|
||||
auto GetGraphicsBuffer() const { return graphics_buffer_; }
|
||||
auto GetPaletteGroup(std::string group) { return palette_groups_[group]; }
|
||||
void SetupRenderer(std::shared_ptr<SDL_Renderer> renderer) {
|
||||
renderer_ = renderer;
|
||||
}
|
||||
auto isLoaded() const { return is_loaded_; }
|
||||
auto begin() { return rom_data_.begin(); }
|
||||
auto end() { return rom_data_.end(); }
|
||||
auto data() { return rom_data_.data(); }
|
||||
auto char_data() { return reinterpret_cast<char*>(rom_data_.data()); }
|
||||
auto size() const { return size_; }
|
||||
|
||||
uchar& operator[](int i) {
|
||||
if (i > size_) {
|
||||
std::cout << "ROM: Index out of bounds" << std::endl;
|
||||
return rom_data_[0];
|
||||
}
|
||||
return rom_data_[i];
|
||||
}
|
||||
uchar& operator+(int i) {
|
||||
if (i > size_) {
|
||||
std::cout << "ROM: Index out of bounds" << std::endl;
|
||||
return rom_data_[0];
|
||||
}
|
||||
return rom_data_[i];
|
||||
}
|
||||
const uchar* operator&() { return rom_data_.data(); }
|
||||
|
||||
ushort toint16(int offset) {
|
||||
return (ushort)((rom_data_[offset + 1]) << 8) | rom_data_[offset];
|
||||
}
|
||||
|
||||
private:
|
||||
long size_ = 0;
|
||||
uchar title[21] = "ROM Not Loaded";
|
||||
bool is_loaded_ = false;
|
||||
bool isbpp3[223];
|
||||
std::string filename_;
|
||||
|
||||
Bytes rom_data_;
|
||||
Bytes graphics_buffer_;
|
||||
std::shared_ptr<SDL_Renderer> renderer_;
|
||||
std::unordered_map<int, gfx::Bitmap> graphics_bin_;
|
||||
std::unordered_map<std::string, gfx::PaletteGroup> palette_groups_;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
26
src/app/spc700/spc700.def
Normal file
26
src/app/spc700/spc700.def
Normal file
@@ -0,0 +1,26 @@
|
||||
LIBRARY snes_spc
|
||||
DESCRIPTION "snes_spc 0.9.0"
|
||||
EXPORTS
|
||||
spc_new @1
|
||||
spc_delete @2
|
||||
spc_init_rom @3
|
||||
spc_set_output @4
|
||||
spc_sample_count @5
|
||||
spc_reset @6
|
||||
spc_soft_reset @7
|
||||
spc_read_port @8
|
||||
spc_write_port @9
|
||||
spc_end_frame @10
|
||||
spc_mute_voices @11
|
||||
spc_disable_surround @12
|
||||
spc_set_tempo @13
|
||||
spc_load_spc @14
|
||||
spc_clear_echo @15
|
||||
spc_play @16
|
||||
spc_skip @17
|
||||
spc_filter_new @18
|
||||
spc_filter_delete @19
|
||||
spc_filter_run @20
|
||||
spc_filter_clear @21
|
||||
spc_filter_set_gain @22
|
||||
spc_filter_set_bass @23
|
||||
31
src/app/yaze.cc
Normal file
31
src/app/yaze.cc
Normal file
@@ -0,0 +1,31 @@
|
||||
#if defined(_WIN32)
|
||||
#define main SDL_main
|
||||
#endif
|
||||
|
||||
#include "absl/debugging/failure_signal_handler.h"
|
||||
#include "absl/debugging/symbolize.h"
|
||||
#include "app/core/controller.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
absl::InitializeSymbolizer(argv[0]);
|
||||
|
||||
absl::FailureSignalHandlerOptions options;
|
||||
absl::InstallFailureSignalHandler(options);
|
||||
|
||||
yaze::app::core::Controller controller;
|
||||
|
||||
auto entry_status = controller.onEntry();
|
||||
if (!entry_status.ok()) {
|
||||
// TODO(@scawful): log the specific error
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
while (controller.isActive()) {
|
||||
controller.onInput();
|
||||
controller.onLoad();
|
||||
controller.doRender();
|
||||
}
|
||||
controller.onExit();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
89
src/app/zelda3/inventory.cc
Normal file
89
src/app/zelda3/inventory.cc
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "inventory.h"
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace zelda3 {
|
||||
|
||||
void Inventory::Create() {
|
||||
data_.reserve(256 * 256);
|
||||
for (int i = 0; i < 256 * 256; i++) {
|
||||
data_.push_back(0xFF);
|
||||
}
|
||||
PRINT_IF_ERROR(BuildTileset())
|
||||
for (int i = 0; i < 0x500; i += 0x08) {
|
||||
tiles_.push_back(gfx::GetTilesInfo(rom_.toint16(i + kBowItemPos)));
|
||||
tiles_.push_back(gfx::GetTilesInfo(rom_.toint16(i + kBowItemPos + 0x02)));
|
||||
tiles_.push_back(gfx::GetTilesInfo(rom_.toint16(i + kBowItemPos + 0x04)));
|
||||
tiles_.push_back(gfx::GetTilesInfo(rom_.toint16(i + kBowItemPos + 0x08)));
|
||||
}
|
||||
const int offsets[] = {0x00, 0x08, 0x800, 0x808};
|
||||
auto xx = 0;
|
||||
auto yy = 0;
|
||||
|
||||
int i = 0;
|
||||
for (const auto& tile : tiles_) {
|
||||
int offset = offsets[i];
|
||||
for (auto y = 0; y < 0x08; ++y) {
|
||||
for (auto x = 0; x < 0x08; ++x) {
|
||||
int mx = x;
|
||||
int my = y;
|
||||
|
||||
if (tile.horizontal_mirror_ != 0) {
|
||||
mx = 0x07 - x;
|
||||
}
|
||||
|
||||
if (tile.vertical_mirror_ != 0) {
|
||||
my = 0x07 - y;
|
||||
}
|
||||
|
||||
int xpos = ((tile.id_ % 0x10) * 0x08);
|
||||
int ypos = (((tile.id_ / 0x10)) * 0x400);
|
||||
int source = ypos + xpos + (x + (y * 0x80));
|
||||
|
||||
auto destination = xx + yy + offset + (mx + (my * 0x100));
|
||||
data_[destination] = (test_[source] & 0x0F) + tile.palette_ * 0x08;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == 4) {
|
||||
i = 0;
|
||||
xx += 0x10;
|
||||
if (xx >= 0x100) {
|
||||
yy += 0x1000;
|
||||
xx = 0;
|
||||
}
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
bitmap_.Create(256, 256, 128, data_);
|
||||
bitmap_.ApplyPalette(palette_);
|
||||
rom_.RenderBitmap(&bitmap_);
|
||||
}
|
||||
|
||||
absl::Status Inventory::BuildTileset() {
|
||||
tilesheets_.reserve(6 * 0x2000);
|
||||
for (int i = 0; i < 6 * 0x2000; i++) tilesheets_.push_back(0xFF);
|
||||
ASSIGN_OR_RETURN(tilesheets_, rom_.Load2bppGraphics())
|
||||
Bytes test;
|
||||
for (int i = 0; i < 0x4000; i++) {
|
||||
test_.push_back(tilesheets_[i]);
|
||||
}
|
||||
for (int i = 0x8000; i < +0x8000 + 0x2000; i++) {
|
||||
test_.push_back(tilesheets_[i]);
|
||||
}
|
||||
tilesheets_bmp_.Create(128, 0x130, 64, test_);
|
||||
palette_ = rom_.GetPaletteGroup("hud")[0];
|
||||
tilesheets_bmp_.ApplyPalette(palette_);
|
||||
rom_.RenderBitmap(&tilesheets_bmp_);
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
47
src/app/zelda3/inventory.h
Normal file
47
src/app/zelda3/inventory.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef YAZE_APP_ZELDA3_INVENTORY_H
|
||||
#define YAZE_APP_ZELDA3_INVENTORY_H
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace zelda3 {
|
||||
|
||||
constexpr int kInventoryStart = 0x6564A;
|
||||
constexpr int kBowItemPos = 0x6F631;
|
||||
|
||||
class Inventory {
|
||||
public:
|
||||
void SetupROM(ROM& rom) { rom_ = rom; }
|
||||
auto Bitmap() const { return bitmap_; }
|
||||
auto Tilesheet() const { return tilesheets_bmp_; }
|
||||
auto Palette() const { return palette_; }
|
||||
|
||||
void Create();
|
||||
|
||||
private:
|
||||
absl::Status BuildTileset();
|
||||
|
||||
ROM rom_;
|
||||
|
||||
Bytes data_;
|
||||
gfx::Bitmap bitmap_;
|
||||
|
||||
Bytes tilesheets_;
|
||||
Bytes test_;
|
||||
gfx::Bitmap tilesheets_bmp_;
|
||||
gfx::SNESPalette palette_;
|
||||
|
||||
gui::Canvas canvas_;
|
||||
std::vector<gfx::TileInfo> tiles_;
|
||||
};
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
384
src/app/zelda3/overworld.cc
Normal file
384
src/app/zelda3/overworld.cc
Normal file
@@ -0,0 +1,384 @@
|
||||
#include "overworld.h"
|
||||
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace zelda3 {
|
||||
|
||||
namespace {
|
||||
|
||||
uint GetOwMapGfxHighPtr(const uchar *rom, int index) {
|
||||
int map_high_ptr = core::compressedAllMap32PointersHigh;
|
||||
int p1 = (rom[(map_high_ptr) + 2 + (3 * index)] << 16) +
|
||||
(rom[(map_high_ptr) + 1 + (3 * index)] << 8) +
|
||||
(rom[(map_high_ptr + (3 * index))]);
|
||||
return core::SnesToPc(p1);
|
||||
}
|
||||
|
||||
uint GetOwMapGfxLowPtr(const uchar *rom, int index) {
|
||||
int map_low_ptr = core::compressedAllMap32PointersLow;
|
||||
int p2 = (rom[(map_low_ptr) + 2 + (3 * index)] << 16) +
|
||||
(rom[(map_low_ptr) + 1 + (3 * index)] << 8) +
|
||||
(rom[(map_low_ptr + (3 * index))]);
|
||||
return core::SnesToPc(p2);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
absl::Status Overworld::Load(ROM &rom) {
|
||||
rom_ = rom;
|
||||
|
||||
AssembleMap32Tiles();
|
||||
AssembleMap16Tiles();
|
||||
RETURN_IF_ERROR(DecompressAllMapTiles())
|
||||
|
||||
for (int map_index = 0; map_index < core::kNumOverworldMaps; ++map_index)
|
||||
overworld_maps_.emplace_back(map_index, rom_, tiles16);
|
||||
|
||||
FetchLargeMaps();
|
||||
LoadEntrances();
|
||||
LoadSprites();
|
||||
|
||||
auto size = tiles16.size();
|
||||
for (int i = 0; i < core::kNumOverworldMaps; ++i) {
|
||||
if (i < 64) {
|
||||
RETURN_IF_ERROR(overworld_maps_[i].BuildMap(
|
||||
size, game_state_, 0, map_parent_, map_tiles_.light_world))
|
||||
} else if (i < 0x80 && i >= 0x40) {
|
||||
RETURN_IF_ERROR(overworld_maps_[i].BuildMap(
|
||||
size, game_state_, 1, map_parent_, map_tiles_.dark_world))
|
||||
} else {
|
||||
RETURN_IF_ERROR(overworld_maps_[i].BuildMap(
|
||||
size, game_state_, 2, map_parent_, map_tiles_.special_world))
|
||||
}
|
||||
}
|
||||
|
||||
is_loaded_ = true;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
ushort Overworld::GenerateTile32(int i, int k, int dimension) {
|
||||
return (ushort)(rom_[map32address[dimension] + k + (i)] +
|
||||
(((rom_[map32address[dimension] + (i) + (k <= 1 ? 4 : 5)] >>
|
||||
(k % 2 == 0 ? 4 : 0)) &
|
||||
0x0F) *
|
||||
256));
|
||||
}
|
||||
|
||||
void Overworld::AssembleMap32Tiles() {
|
||||
for (int i = 0; i < 0x33F0; i += 6) {
|
||||
for (int k = 0; k < 4; k++) {
|
||||
tiles32.push_back(gfx::Tile32(
|
||||
/*top-left=*/GenerateTile32(i, k, (int)Dimension::map32TilesTL),
|
||||
/*top-right=*/GenerateTile32(i, k, (int)Dimension::map32TilesTR),
|
||||
/*bottom-left=*/GenerateTile32(i, k, (int)Dimension::map32TilesBL),
|
||||
/*bottom-right=*/GenerateTile32(i, k, (int)Dimension::map32TilesBR)));
|
||||
}
|
||||
}
|
||||
map_tiles_.light_world.resize(kTile32Num);
|
||||
map_tiles_.dark_world.resize(kTile32Num);
|
||||
map_tiles_.special_world.resize(kTile32Num);
|
||||
for (int i = 0; i < kTile32Num; i++) {
|
||||
map_tiles_.light_world[i].resize(kTile32Num);
|
||||
map_tiles_.dark_world[i].resize(kTile32Num);
|
||||
map_tiles_.special_world[i].resize(kTile32Num);
|
||||
}
|
||||
}
|
||||
|
||||
void Overworld::AssembleMap16Tiles() {
|
||||
int tpos = core::map16Tiles;
|
||||
for (int i = 0; i < 4096; i += 1) {
|
||||
auto t0 = gfx::GetTilesInfo((rom_.toint16(tpos)));
|
||||
tpos += 2;
|
||||
auto t1 = gfx::GetTilesInfo((rom_.toint16(tpos)));
|
||||
tpos += 2;
|
||||
auto t2 = gfx::GetTilesInfo((rom_.toint16(tpos)));
|
||||
tpos += 2;
|
||||
auto t3 = gfx::GetTilesInfo((rom_.toint16(tpos)));
|
||||
tpos += 2;
|
||||
tiles16.emplace_back(t0, t1, t2, t3);
|
||||
}
|
||||
}
|
||||
|
||||
void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
|
||||
OWBlockset &world) {
|
||||
int position_x1 = (x * 2) + (sx * 32);
|
||||
int position_y1 = (y * 2) + (sy * 32);
|
||||
int position_x2 = (x * 2) + 1 + (sx * 32);
|
||||
int position_y2 = (y * 2) + 1 + (sy * 32);
|
||||
world[position_x1][position_y1] = tiles32[tpos].tile0_;
|
||||
world[position_x2][position_y1] = tiles32[tpos].tile1_;
|
||||
world[position_x1][position_y2] = tiles32[tpos].tile2_;
|
||||
world[position_x2][position_y2] = tiles32[tpos].tile3_;
|
||||
}
|
||||
|
||||
void Overworld::OrganizeMapTiles(Bytes &bytes, Bytes &bytes2, int i, int sx,
|
||||
int sy, int &ttpos) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
auto tidD = (ushort)((bytes2[ttpos] << 8) + bytes[ttpos]);
|
||||
int tpos = tidD;
|
||||
if (tpos < tiles32.size()) {
|
||||
if (i < 64) {
|
||||
AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.light_world);
|
||||
} else if (i < 128 && i >= 64) {
|
||||
AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.dark_world);
|
||||
} else {
|
||||
AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.special_world);
|
||||
}
|
||||
}
|
||||
ttpos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status Overworld::DecompressAllMapTiles() {
|
||||
int lowest = 0x0FFFFF;
|
||||
int highest = 0x0F8000;
|
||||
int sx = 0;
|
||||
int sy = 0;
|
||||
int c = 0;
|
||||
for (int i = 0; i < 160; i++) {
|
||||
auto p1 = GetOwMapGfxHighPtr(rom_.data(), i);
|
||||
auto p2 = GetOwMapGfxLowPtr(rom_.data(), i);
|
||||
int ttpos = 0;
|
||||
|
||||
if (p1 >= highest) {
|
||||
highest = p1;
|
||||
}
|
||||
if (p2 >= highest) {
|
||||
highest = p2;
|
||||
}
|
||||
|
||||
if (p1 <= lowest && p1 > 0x0F8000) {
|
||||
lowest = p1;
|
||||
}
|
||||
if (p2 <= lowest && p2 > 0x0F8000) {
|
||||
lowest = p2;
|
||||
}
|
||||
|
||||
ASSIGN_OR_RETURN(auto bytes, rom_.DecompressOverworld(p2, 1000))
|
||||
ASSIGN_OR_RETURN(auto bytes2, rom_.DecompressOverworld(p1, 1000))
|
||||
OrganizeMapTiles(bytes, bytes2, i, sx, sy, ttpos);
|
||||
|
||||
sx++;
|
||||
if (sx >= 8) {
|
||||
sy++;
|
||||
sx = 0;
|
||||
}
|
||||
|
||||
c++;
|
||||
if (c >= 64) {
|
||||
sx = 0;
|
||||
sy = 0;
|
||||
c = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "MapPointers(lowest) : " << lowest << std::endl;
|
||||
std::cout << "MapPointers(highest) : " << highest << std::endl;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void Overworld::FetchLargeMaps() {
|
||||
for (int i = 128; i < 145; i++) {
|
||||
map_parent_[i] = 0;
|
||||
}
|
||||
|
||||
map_parent_[128] = 128;
|
||||
map_parent_[129] = 129;
|
||||
map_parent_[130] = 129;
|
||||
map_parent_[137] = 129;
|
||||
map_parent_[138] = 129;
|
||||
map_parent_[136] = 136;
|
||||
overworld_maps_[136].SetLargeMap(false);
|
||||
|
||||
bool mapChecked[64];
|
||||
for (int i = 0; i < 64; i++) {
|
||||
mapChecked[i] = false;
|
||||
}
|
||||
int xx = 0;
|
||||
int yy = 0;
|
||||
while (true) {
|
||||
int i = xx + (yy * 8);
|
||||
if (mapChecked[i] == false) {
|
||||
if (overworld_maps_[i].IsLargeMap() == true) {
|
||||
mapChecked[i] = true;
|
||||
map_parent_[i] = (uchar)i;
|
||||
map_parent_[i + 64] = (uchar)(i + 64);
|
||||
|
||||
mapChecked[i + 1] = true;
|
||||
map_parent_[i + 1] = (uchar)i;
|
||||
map_parent_[i + 65] = (uchar)(i + 64);
|
||||
|
||||
mapChecked[i + 8] = true;
|
||||
map_parent_[i + 8] = (uchar)i;
|
||||
map_parent_[i + 72] = (uchar)(i + 64);
|
||||
|
||||
mapChecked[i + 9] = true;
|
||||
map_parent_[i + 9] = (uchar)i;
|
||||
map_parent_[i + 73] = (uchar)(i + 64);
|
||||
xx++;
|
||||
} else {
|
||||
map_parent_[i] = (uchar)i;
|
||||
map_parent_[i + 64] = (uchar)(i + 64);
|
||||
mapChecked[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
xx++;
|
||||
if (xx >= 8) {
|
||||
xx = 0;
|
||||
yy += 1;
|
||||
if (yy >= 8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Overworld::LoadEntrances() {
|
||||
for (int i = 0; i < 129; i++) {
|
||||
short mapId = rom_.toint16(core::OWEntranceMap + (i * 2));
|
||||
ushort mapPos = rom_.toint16(core::OWEntrancePos + (i * 2));
|
||||
uchar entranceId = (rom_[core::OWEntranceEntranceId + i]);
|
||||
int p = mapPos >> 1;
|
||||
int x = (p % 64);
|
||||
int y = (p >> 6);
|
||||
bool deleted = false;
|
||||
if (mapPos == 0xFFFF) {
|
||||
deleted = true;
|
||||
}
|
||||
all_entrances_.emplace_back(
|
||||
(x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512),
|
||||
(y * 16) + (((mapId % 64) / 8) * 512), entranceId, mapId, mapPos,
|
||||
deleted);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 0x13; i++) {
|
||||
auto mapId = (short)((rom_[core::OWHoleArea + (i * 2) + 1] << 8) +
|
||||
(rom_[core::OWHoleArea + (i * 2)]));
|
||||
auto mapPos = (short)((rom_[core::OWHolePos + (i * 2) + 1] << 8) +
|
||||
(rom_[core::OWHolePos + (i * 2)]));
|
||||
uchar entranceId = (rom_[core::OWHoleEntrance + i]);
|
||||
int p = (mapPos + 0x400) >> 1;
|
||||
int x = (p % 64);
|
||||
int y = (p >> 6);
|
||||
all_holes_.emplace_back(
|
||||
(x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512),
|
||||
(y * 16) + (((mapId % 64) / 8) * 512), entranceId, mapId,
|
||||
(ushort)(mapPos + 0x400), true);
|
||||
}
|
||||
}
|
||||
|
||||
void Overworld::LoadSprites() {
|
||||
// LW[0] = RainState 0 to 63 there's no data for DW
|
||||
// LW[1] = ZeldaState 0 to 128 ; Contains LW and DW <128 or 144 wtf
|
||||
// LW[2] = AgahState 0 to ?? ;Contains data for LW and DW
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
all_sprites_.emplace_back(std::vector<Sprite>());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (map_parent_[i] == i) {
|
||||
// Beginning Sprites
|
||||
int ptrPos = core::overworldSpritesBegining + (i * 2);
|
||||
int spriteAddress = core::SnesToPc((0x09 << 0x10) + rom_.toint16(ptrPos));
|
||||
while (true) {
|
||||
uchar b1 = rom_[spriteAddress];
|
||||
uchar b2 = rom_[spriteAddress + 1];
|
||||
uchar b3 = rom_[spriteAddress + 2];
|
||||
if (b1 == 0xFF) {
|
||||
break;
|
||||
}
|
||||
|
||||
int mapY = (i / 8);
|
||||
int mapX = (i % 8);
|
||||
|
||||
int realX = ((b2 & 0x3F) * 16) + mapX * 512;
|
||||
int realY = ((b1 & 0x3F) * 16) + mapY * 512;
|
||||
|
||||
all_sprites_[0].emplace_back(overworld_maps_[i].AreaGraphics(),
|
||||
(uchar)i, b3, (uchar)(b2 & 0x3F),
|
||||
(uchar)(b1 & 0x3F), realX, realY);
|
||||
|
||||
spriteAddress += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 144; i++) {
|
||||
if (map_parent_[i] == i) {
|
||||
// Zelda Saved Sprites
|
||||
int ptrPos = core::overworldSpritesZelda + (i * 2);
|
||||
int spriteAddress = core::SnesToPc((0x09 << 0x10) + rom_.toint16(ptrPos));
|
||||
while (true) {
|
||||
uchar b1 = rom_[spriteAddress];
|
||||
uchar b2 = rom_[spriteAddress + 1];
|
||||
uchar b3 = rom_[spriteAddress + 2];
|
||||
if (b1 == 0xFF) {
|
||||
break;
|
||||
}
|
||||
|
||||
int editorMapIndex = i;
|
||||
if (editorMapIndex >= 128) {
|
||||
editorMapIndex = i - 128;
|
||||
} else if (editorMapIndex >= 64) {
|
||||
editorMapIndex = i - 64;
|
||||
}
|
||||
|
||||
int mapY = (editorMapIndex / 8);
|
||||
int mapX = (editorMapIndex % 8);
|
||||
|
||||
int realX = ((b2 & 0x3F) * 16) + mapX * 512;
|
||||
int realY = ((b1 & 0x3F) * 16) + mapY * 512;
|
||||
|
||||
all_sprites_[1].emplace_back(overworld_maps_[i].AreaGraphics(),
|
||||
(uchar)i, b3, (uchar)(b2 & 0x3F),
|
||||
(uchar)(b1 & 0x3F), realX, realY);
|
||||
|
||||
spriteAddress += 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Agahnim Dead Sprites
|
||||
if (map_parent_[i] == i) {
|
||||
int ptrPos = core::overworldSpritesAgahnim + (i * 2);
|
||||
int spriteAddress = core::SnesToPc((0x09 << 0x10) + rom_.toint16(ptrPos));
|
||||
while (true) {
|
||||
uchar b1 = rom_[spriteAddress];
|
||||
uchar b2 = rom_[spriteAddress + 1];
|
||||
uchar b3 = rom_[spriteAddress + 2];
|
||||
if (b1 == 0xFF) {
|
||||
break;
|
||||
}
|
||||
|
||||
int editorMapIndex = i;
|
||||
if (editorMapIndex >= 128) {
|
||||
editorMapIndex = i - 128;
|
||||
} else if (editorMapIndex >= 64) {
|
||||
editorMapIndex = i - 64;
|
||||
}
|
||||
|
||||
int mapY = (editorMapIndex / 8);
|
||||
int mapX = (editorMapIndex % 8);
|
||||
|
||||
int realX = ((b2 & 0x3F) * 16) + mapX * 512;
|
||||
int realY = ((b1 & 0x3F) * 16) + mapY * 512;
|
||||
|
||||
all_sprites_[2].emplace_back(overworld_maps_[i].AreaGraphics(),
|
||||
(uchar)i, b3, (uchar)(b2 & 0x3F),
|
||||
(uchar)(b1 & 0x3F), realX, realY);
|
||||
|
||||
spriteAddress += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
136
src/app/zelda3/overworld.h
Normal file
136
src/app/zelda3/overworld.h
Normal file
@@ -0,0 +1,136 @@
|
||||
#ifndef YAZE_APP_DATA_OVERWORLD_H
|
||||
#define YAZE_APP_DATA_OVERWORLD_H
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld_map.h"
|
||||
#include "app/zelda3/sprite.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace zelda3 {
|
||||
|
||||
class OverworldEntrance {
|
||||
public:
|
||||
int x_;
|
||||
int y_;
|
||||
ushort map_pos_;
|
||||
uchar entrance_id_;
|
||||
uchar area_x_;
|
||||
uchar area_y_;
|
||||
short map_id_;
|
||||
bool is_hole_ = false;
|
||||
bool deleted = false;
|
||||
|
||||
OverworldEntrance(int x, int y, uchar entranceId, short mapId, ushort mapPos,
|
||||
bool hole)
|
||||
: x_(x),
|
||||
y_(y),
|
||||
map_pos_(mapPos),
|
||||
entrance_id_(entranceId),
|
||||
map_id_(mapId),
|
||||
is_hole_(hole) {
|
||||
int mapX = (map_id_ - ((map_id_ / 8) * 8));
|
||||
int mapY = (map_id_ / 8);
|
||||
|
||||
area_x_ = (uchar)((std::abs(x - (mapX * 512)) / 16));
|
||||
area_y_ = (uchar)((std::abs(y - (mapY * 512)) / 16));
|
||||
}
|
||||
|
||||
auto Copy() {
|
||||
return new OverworldEntrance(x_, y_, entrance_id_, map_id_, map_pos_,
|
||||
is_hole_);
|
||||
}
|
||||
|
||||
void updateMapStuff(short mapId) {
|
||||
map_id_ = mapId;
|
||||
|
||||
if (map_id_ >= 64) {
|
||||
map_id_ -= 64;
|
||||
}
|
||||
|
||||
int mapX = (map_id_ - ((map_id_ / 8) * 8));
|
||||
int mapY = (map_id_ / 8);
|
||||
|
||||
area_x_ = (uchar)((std::abs(x_ - (mapX * 512)) / 16));
|
||||
area_y_ = (uchar)((std::abs(y_ - (mapY * 512)) / 16));
|
||||
|
||||
map_pos_ = (ushort)((((area_y_) << 6) | (area_x_ & 0x3F)) << 1);
|
||||
}
|
||||
};
|
||||
|
||||
class Overworld {
|
||||
public:
|
||||
absl::Status Load(ROM &rom);
|
||||
auto GetTiles16() const { return tiles16; }
|
||||
auto GetOverworldMap(uint index) { return overworld_maps_[index]; }
|
||||
auto GetOverworldMaps() const { return overworld_maps_; }
|
||||
auto Sprites() const { return all_sprites_[game_state_]; }
|
||||
auto AreaGraphics() const {
|
||||
return overworld_maps_[current_map_].AreaGraphics();
|
||||
}
|
||||
auto Entrances() const { return all_entrances_; }
|
||||
auto AreaPalette() const {
|
||||
return overworld_maps_[current_map_].AreaPalette();
|
||||
}
|
||||
auto BitmapData() const { return overworld_maps_[current_map_].BitmapData(); }
|
||||
auto Tile16Blockset() const {
|
||||
return overworld_maps_[current_map_].Tile16Blockset();
|
||||
}
|
||||
auto GameState() const { return game_state_; }
|
||||
auto isLoaded() const { return is_loaded_; }
|
||||
void SetCurrentMap(int i) { current_map_ = i; }
|
||||
|
||||
private:
|
||||
const int map32address[4] = {core::map32TilesTL, core::map32TilesTR,
|
||||
core::map32TilesBL, core::map32TilesBR};
|
||||
enum Dimension {
|
||||
map32TilesTL = 0,
|
||||
map32TilesTR = 1,
|
||||
map32TilesBL = 2,
|
||||
map32TilesBR = 3
|
||||
};
|
||||
|
||||
ushort GenerateTile32(int i, int k, int dimension);
|
||||
void AssembleMap32Tiles();
|
||||
void AssembleMap16Tiles();
|
||||
void AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
|
||||
OWBlockset &world);
|
||||
void OrganizeMapTiles(Bytes &bytes, Bytes &bytes2, int i, int sx, int sy,
|
||||
int &ttpos);
|
||||
absl::Status DecompressAllMapTiles();
|
||||
void FetchLargeMaps();
|
||||
void LoadEntrances();
|
||||
void LoadSprites();
|
||||
|
||||
void LoadOverworldMap();
|
||||
|
||||
int game_state_ = 0;
|
||||
int current_map_ = 0;
|
||||
uchar map_parent_[160];
|
||||
bool is_loaded_ = false;
|
||||
|
||||
ROM rom_;
|
||||
OWMapTiles map_tiles_;
|
||||
|
||||
std::vector<gfx::Tile16> tiles16;
|
||||
std::vector<gfx::Tile32> tiles32;
|
||||
std::vector<OverworldMap> overworld_maps_;
|
||||
std::vector<OverworldEntrance> all_entrances_;
|
||||
std::vector<OverworldEntrance> all_holes_;
|
||||
std::vector<std::vector<Sprite>> all_sprites_;
|
||||
};
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
546
src/app/zelda3/overworld_map.cc
Normal file
546
src/app/zelda3/overworld_map.cc
Normal file
@@ -0,0 +1,546 @@
|
||||
#include "overworld_map.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/common.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace zelda3 {
|
||||
|
||||
namespace {
|
||||
|
||||
void CopyTile8bpp16(int x, int y, int tile, Bytes& bitmap, Bytes& blockset) {
|
||||
int src_pos =
|
||||
((tile - ((tile / 0x08) * 0x08)) * 0x10) + ((tile / 0x08) * 2048);
|
||||
int dest_pos = (x + (y * 0x200));
|
||||
for (int yy = 0; yy < 0x10; yy++) {
|
||||
for (int xx = 0; xx < 0x10; xx++) {
|
||||
bitmap[dest_pos + xx + (yy * 0x200)] =
|
||||
blockset[src_pos + xx + (yy * 0x80)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetColorsPalette(ROM& rom, int index, gfx::SNESPalette& current,
|
||||
gfx::SNESPalette main, gfx::SNESPalette animated,
|
||||
gfx::SNESPalette aux1, gfx::SNESPalette aux2,
|
||||
gfx::SNESPalette hud, gfx::SNESColor bgrcolor,
|
||||
gfx::SNESPalette spr, gfx::SNESPalette spr2) {
|
||||
// Palettes infos, color 0 of a palette is always transparent (the arrays
|
||||
// contains 7 colors width wide) There is 16 color per line so 16*Y
|
||||
|
||||
// Left side of the palette - Main, Animated
|
||||
std::vector<gfx::SNESColor> new_palette(256);
|
||||
|
||||
// Main Palette, Location 0,2 : 35 colors [7x5]
|
||||
int k = 0;
|
||||
for (int y = 2; y < 7; y++) {
|
||||
for (int x = 1; x < 8; x++) {
|
||||
new_palette[x + (16 * y)] = main[k];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
// Animated Palette, Location 0,7 : 7colors
|
||||
for (int x = 1; x < 8; x++) {
|
||||
new_palette[(16 * 7) + (x)] = animated[(x - 1)];
|
||||
}
|
||||
|
||||
// Right side of the palette - Aux1, Aux2
|
||||
|
||||
// Aux1 Palette, Location 8,2 : 21 colors [7x3]
|
||||
k = 0;
|
||||
for (int y = 2; y < 5; y++) {
|
||||
for (int x = 9; x < 16; x++) {
|
||||
new_palette[x + (16 * y)] = aux1[k];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
// Aux2 Palette, Location 8,5 : 21 colors [7x3]
|
||||
k = 0;
|
||||
for (int y = 5; y < 8; y++) {
|
||||
for (int x = 9; x < 16; x++) {
|
||||
new_palette[x + (16 * y)] = aux2[k];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
// Hud Palette, Location 0,0 : 32 colors [16x2]
|
||||
for (int i = 0; i < 32; i++) {
|
||||
new_palette[i] = hud[i];
|
||||
}
|
||||
|
||||
// Hardcoded grass color (that might change to become invisible instead)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
new_palette[(i * 16)] = bgrcolor;
|
||||
new_palette[(i * 16) + 8] = bgrcolor;
|
||||
}
|
||||
|
||||
// Sprite Palettes
|
||||
k = 0;
|
||||
for (int y = 8; y < 9; y++) {
|
||||
for (int x = 1; x < 8; x++) {
|
||||
new_palette[x + (16 * y)] = rom.GetPaletteGroup("sprites_aux1")[1][k];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
// Sprite Palettes
|
||||
k = 0;
|
||||
for (int y = 8; y < 9; y++) {
|
||||
for (int x = 9; x < 16; x++) {
|
||||
new_palette[x + (16 * y)] = rom.GetPaletteGroup("sprites_aux3")[0][k];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
// Sprite Palettes
|
||||
k = 0;
|
||||
for (int y = 9; y < 13; y++) {
|
||||
for (int x = 1; x < 16; x++) {
|
||||
new_palette[x + (16 * y)] = rom.GetPaletteGroup("global_sprites")[0][k];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
// Sprite Palettes
|
||||
k = 0;
|
||||
for (int y = 13; y < 14; y++) {
|
||||
for (int x = 1; x < 8; x++) {
|
||||
new_palette[x + (16 * y)] = spr[k];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
// Sprite Palettes
|
||||
k = 0;
|
||||
for (int y = 14; y < 15; y++) {
|
||||
for (int x = 1; x < 8; x++) {
|
||||
new_palette[x + (16 * y)] = spr2[k];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
// Sprite Palettes
|
||||
k = 0;
|
||||
for (int y = 15; y < 16; y++) {
|
||||
for (int x = 1; x < 16; x++) {
|
||||
new_palette[x + (16 * y)] = rom.GetPaletteGroup("armors")[0][k];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
current.Create(new_palette);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
current[(i / 16) * 16].setTransparent(true);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OverworldMap::OverworldMap(int index, ROM& rom,
|
||||
std::vector<gfx::Tile16>& tiles16)
|
||||
: parent_(index), index_(index), rom_(rom), tiles16_(tiles16) {
|
||||
LoadAreaInfo();
|
||||
}
|
||||
|
||||
absl::Status OverworldMap::BuildMap(int count, int game_state, int world,
|
||||
uchar* map_parent,
|
||||
OWBlockset& world_blockset) {
|
||||
game_state_ = game_state;
|
||||
world_ = world;
|
||||
if (large_map_) {
|
||||
parent_ = map_parent[index_];
|
||||
if (parent_ != index_ && !initialized_) {
|
||||
if (index_ >= 0x80 && index_ <= 0x8A && index_ != 0x88) {
|
||||
area_graphics_ =
|
||||
rom_[core::overworldSpecialGFXGroup + (parent_ - 0x80)];
|
||||
area_palette_ = rom_[core::overworldSpecialPALGroup + 1];
|
||||
} else if (index_ == 0x88) {
|
||||
area_graphics_ = 0x51;
|
||||
area_palette_ = 0x00;
|
||||
} else {
|
||||
area_graphics_ = rom_[core::mapGfx + parent_];
|
||||
area_palette_ = rom_[core::overworldMapPalette + parent_];
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
LoadAreaGraphics();
|
||||
RETURN_IF_ERROR(BuildTileset())
|
||||
RETURN_IF_ERROR(BuildTiles16Gfx(count))
|
||||
LoadPalette();
|
||||
RETURN_IF_ERROR(BuildBitmap(world_blockset))
|
||||
built_ = true;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void OverworldMap::LoadAreaInfo() {
|
||||
if (index_ != 0x80 && index_ <= 150 &&
|
||||
rom_[core::overworldMapSize + (index_ & 0x3F)] != 0) {
|
||||
large_map_ = true;
|
||||
}
|
||||
if (index_ < 64) {
|
||||
area_graphics_ = rom_[core::mapGfx + parent_];
|
||||
area_palette_ = rom_[core::overworldMapPalette + parent_];
|
||||
|
||||
area_music_[0] = rom_[core::overworldMusicBegining + parent_];
|
||||
area_music_[1] = rom_[core::overworldMusicZelda + parent_];
|
||||
area_music_[2] = rom_[core::overworldMusicMasterSword + parent_];
|
||||
area_music_[3] = rom_[core::overworldMusicAgahim + parent_];
|
||||
|
||||
sprite_graphics_[0] = rom_[core::overworldSpriteset + parent_];
|
||||
sprite_graphics_[1] = rom_[core::overworldSpriteset + parent_ + 0x40];
|
||||
sprite_graphics_[2] = rom_[core::overworldSpriteset + parent_ + 0x80];
|
||||
|
||||
sprite_palette_[0] = rom_[core::overworldSpritePalette + parent_];
|
||||
sprite_palette_[1] = rom_[core::overworldSpritePalette + parent_ + 0x40];
|
||||
sprite_palette_[2] = rom_[core::overworldSpritePalette + parent_ + 0x80];
|
||||
} else if (index_ < 0x80) {
|
||||
area_graphics_ = rom_[core::mapGfx + parent_];
|
||||
area_palette_ = rom_[core::overworldMapPalette + parent_];
|
||||
area_music_[0] = rom_[core::overworldMusicDW + (parent_ - 64)];
|
||||
|
||||
sprite_graphics_[0] = rom_[core::overworldSpriteset + parent_ + 0x80];
|
||||
sprite_graphics_[1] = rom_[core::overworldSpriteset + parent_ + 0x80];
|
||||
sprite_graphics_[2] = rom_[core::overworldSpriteset + parent_ + 0x80];
|
||||
|
||||
sprite_palette_[0] = rom_[core::overworldSpritePalette + parent_ + 0x80];
|
||||
sprite_palette_[1] = rom_[core::overworldSpritePalette + parent_ + 0x80];
|
||||
sprite_palette_[2] = rom_[core::overworldSpritePalette + parent_ + 0x80];
|
||||
} else {
|
||||
if (index_ == 0x94) {
|
||||
parent_ = 0x80;
|
||||
} else if (index_ == 0x95) {
|
||||
parent_ = 0x03;
|
||||
} else if (index_ == 0x96) {
|
||||
parent_ = 0x5B; // pyramid bg use 0x5B map
|
||||
} else if (index_ == 0x97) {
|
||||
parent_ = 0x00; // pyramid bg use 0x5B map
|
||||
} else if (index_ == 0x9C) {
|
||||
parent_ = 0x43;
|
||||
} else if (index_ == 0x9D) {
|
||||
parent_ = 0x00;
|
||||
} else if (index_ == 0x9E) {
|
||||
parent_ = 0x00;
|
||||
} else if (index_ == 0x9F) {
|
||||
parent_ = 0x2C;
|
||||
} else if (index_ == 0x88) {
|
||||
parent_ = 0x88;
|
||||
}
|
||||
|
||||
area_palette_ = rom_[core::overworldSpecialPALGroup + parent_ - 0x80];
|
||||
if (index_ >= 0x80 && index_ <= 0x8A && index_ != 0x88) {
|
||||
area_graphics_ = rom_[core::overworldSpecialGFXGroup + (parent_ - 0x80)];
|
||||
area_palette_ = rom_[core::overworldSpecialPALGroup + 1];
|
||||
} else if (index_ == 0x88) {
|
||||
area_graphics_ = 0x51;
|
||||
area_palette_ = 0x00;
|
||||
} else {
|
||||
// pyramid bg use 0x5B map
|
||||
area_graphics_ = rom_[core::mapGfx + parent_];
|
||||
area_palette_ = rom_[core::overworldMapPalette + parent_];
|
||||
}
|
||||
|
||||
message_id_ = rom_[core::overworldMessages + parent_];
|
||||
|
||||
sprite_graphics_[0] = rom_[core::overworldSpriteset + parent_ + 0x80];
|
||||
sprite_graphics_[1] = rom_[core::overworldSpriteset + parent_ + 0x80];
|
||||
sprite_graphics_[2] = rom_[core::overworldSpriteset + parent_ + 0x80];
|
||||
|
||||
sprite_palette_[0] = rom_[core::overworldSpritePalette + parent_ + 0x80];
|
||||
sprite_palette_[1] = rom_[core::overworldSpritePalette + parent_ + 0x80];
|
||||
sprite_palette_[2] = rom_[core::overworldSpritePalette + parent_ + 0x80];
|
||||
}
|
||||
}
|
||||
|
||||
void OverworldMap::LoadAreaGraphics() {
|
||||
int world_index = 0x20;
|
||||
if (parent_ < 0x40) {
|
||||
world_index = 0x20;
|
||||
} else if (parent_ >= 0x40 && parent_ < 0x80) {
|
||||
world_index = 0x21;
|
||||
} else if (parent_ == 0x88) {
|
||||
world_index = 0x24;
|
||||
}
|
||||
|
||||
// Sprites Blocksets
|
||||
static_graphics_[8] = 0x73 + 0x00;
|
||||
static_graphics_[9] = 0x73 + 0x01;
|
||||
static_graphics_[10] = 0x73 + 0x06;
|
||||
static_graphics_[11] = 0x73 + 0x07;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
static_graphics_[12 + i] = (rom_[core::kSpriteBlocksetPointer +
|
||||
(sprite_graphics_[game_state_] * 4) + i] +
|
||||
0x73);
|
||||
}
|
||||
|
||||
// Main Blocksets
|
||||
for (int i = 0; i < 8; i++) {
|
||||
static_graphics_[i] =
|
||||
rom_[core::overworldgfxGroups2 + (world_index * 8) + i];
|
||||
}
|
||||
|
||||
if (rom_[core::overworldgfxGroups + (area_graphics_ * 4)] != 0) {
|
||||
static_graphics_[3] = rom_[core::overworldgfxGroups + (area_graphics_ * 4)];
|
||||
}
|
||||
if (rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 1] != 0) {
|
||||
static_graphics_[4] =
|
||||
rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 1];
|
||||
}
|
||||
if (rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 2] != 0) {
|
||||
static_graphics_[5] =
|
||||
rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 2];
|
||||
}
|
||||
if (rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 3] != 0) {
|
||||
static_graphics_[6] =
|
||||
rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 3];
|
||||
}
|
||||
|
||||
// Hardcoded overworld GFX Values, for death mountain
|
||||
if ((parent_ >= 0x03 && parent_ <= 0x07) ||
|
||||
(parent_ >= 0x0B && parent_ <= 0x0E)) {
|
||||
static_graphics_[7] = 0x59;
|
||||
} else if ((parent_ >= 0x43 && parent_ <= 0x47) ||
|
||||
(parent_ >= 0x4B && parent_ <= 0x4E)) {
|
||||
static_graphics_[7] = 0x59;
|
||||
} else {
|
||||
static_graphics_[7] = 0x5B;
|
||||
}
|
||||
}
|
||||
|
||||
void OverworldMap::LoadPalette() {
|
||||
int previousPalId = 0;
|
||||
int previousSprPalId = 0;
|
||||
if (index_ > 0) {
|
||||
previousPalId = rom_[core::overworldMapPalette + parent_ - 1];
|
||||
previousSprPalId = rom_[core::overworldSpritePalette + parent_ - 1];
|
||||
}
|
||||
|
||||
if (area_palette_ >= 0xA3) {
|
||||
area_palette_ = 0xA3;
|
||||
}
|
||||
|
||||
uchar pal0 = 0;
|
||||
|
||||
uchar pal1 = rom_[core::overworldMapPaletteGroup + (area_palette_ * 4)];
|
||||
uchar pal2 =
|
||||
rom_[core::overworldMapPaletteGroup + (area_palette_ * 4) + 1]; // aux2
|
||||
uchar pal3 = rom_[core::overworldMapPaletteGroup + (area_palette_ * 4) +
|
||||
2]; // animated
|
||||
|
||||
uchar pal4 = rom_[core::overworldSpritePaletteGroup +
|
||||
(sprite_palette_[game_state_] * 2)]; // spr3
|
||||
uchar pal5 = rom_[core::overworldSpritePaletteGroup +
|
||||
(sprite_palette_[game_state_] * 2) + 1]; // spr4
|
||||
|
||||
gfx::SNESPalette aux1;
|
||||
gfx::SNESPalette aux2;
|
||||
gfx::SNESPalette main;
|
||||
gfx::SNESPalette animated;
|
||||
gfx::SNESPalette hud;
|
||||
gfx::SNESPalette spr;
|
||||
gfx::SNESPalette spr2;
|
||||
gfx::SNESColor bgr = rom_.GetPaletteGroup("grass")[0].GetColor(0);
|
||||
|
||||
if (pal1 == 255) {
|
||||
pal1 = rom_[core::overworldMapPaletteGroup + (previousPalId * 4)];
|
||||
}
|
||||
if (pal1 != 255) {
|
||||
if (pal1 >= 20) {
|
||||
pal1 = 19;
|
||||
}
|
||||
|
||||
aux1 = rom_.GetPaletteGroup("ow_aux")[pal1];
|
||||
} else {
|
||||
aux1 = rom_.GetPaletteGroup("ow_aux")[0];
|
||||
}
|
||||
|
||||
if (pal2 == 255) {
|
||||
pal2 = rom_[core::overworldMapPaletteGroup + (previousPalId * 4) + 1];
|
||||
}
|
||||
if (pal2 != 255) {
|
||||
if (pal2 >= 20) {
|
||||
pal2 = 19;
|
||||
}
|
||||
|
||||
aux2 = rom_.GetPaletteGroup("ow_aux")[pal2];
|
||||
} else {
|
||||
aux2 = rom_.GetPaletteGroup("ow_aux")[0];
|
||||
}
|
||||
|
||||
if (pal3 == 255) {
|
||||
pal3 = rom_[core::overworldMapPaletteGroup + (previousPalId * 4) + 2];
|
||||
}
|
||||
|
||||
if (parent_ < 0x40) {
|
||||
// Default LW Palette
|
||||
pal0 = 0;
|
||||
bgr = rom_.GetPaletteGroup("grass")[0].GetColor(0);
|
||||
if (parent_ == 0x03 || parent_ == 0x05 || parent_ == 0x07) {
|
||||
pal0 = 2;
|
||||
}
|
||||
} else if (parent_ >= 0x40 && parent_ < 0x80) {
|
||||
// Default DW Palette
|
||||
pal0 = 1;
|
||||
bgr = rom_.GetPaletteGroup("grass")[0].GetColor(1);
|
||||
if (parent_ == 0x43 || parent_ == 0x45 || parent_ == 0x47) {
|
||||
pal0 = 3;
|
||||
}
|
||||
} else if (parent_ >= 128 && parent_ < core::kNumOverworldMaps) {
|
||||
// Default SP Palette
|
||||
pal0 = 0;
|
||||
bgr = rom_.GetPaletteGroup("grass")[0].GetColor(2);
|
||||
}
|
||||
|
||||
if (parent_ == 0x88) {
|
||||
pal0 = 4;
|
||||
}
|
||||
|
||||
if (pal0 != 255) {
|
||||
main = rom_.GetPaletteGroup("ow_main")[pal0];
|
||||
} else {
|
||||
main = rom_.GetPaletteGroup("ow_main")[0];
|
||||
}
|
||||
|
||||
if (pal3 >= 14) {
|
||||
pal3 = 13;
|
||||
}
|
||||
animated = rom_.GetPaletteGroup("ow_animated")[(pal3)];
|
||||
|
||||
hud = rom_.GetPaletteGroup("hud")[0];
|
||||
if (pal4 == 255) {
|
||||
pal4 = rom_[core::overworldSpritePaletteGroup +
|
||||
(previousSprPalId * 2)]; // spr3
|
||||
}
|
||||
if (pal4 == 255) {
|
||||
pal4 = 0;
|
||||
}
|
||||
if (pal4 >= 24) {
|
||||
pal4 = 23;
|
||||
}
|
||||
spr = rom_.GetPaletteGroup("sprites_aux3")[pal4];
|
||||
|
||||
if (pal5 == 255) {
|
||||
pal5 = rom_[core::overworldSpritePaletteGroup + (previousSprPalId * 2) +
|
||||
1]; // spr3
|
||||
}
|
||||
if (pal5 == 255) {
|
||||
pal5 = 0;
|
||||
}
|
||||
if (pal5 >= 24) {
|
||||
pal5 = 23;
|
||||
}
|
||||
spr2 = rom_.GetPaletteGroup("sprites_aux3")[pal5];
|
||||
|
||||
SetColorsPalette(rom_, parent_, current_palette_, main, animated, aux1, aux2,
|
||||
hud, bgr, spr, spr2);
|
||||
}
|
||||
|
||||
absl::Status OverworldMap::BuildTileset() {
|
||||
all_gfx_ = rom_.GetGraphicsBuffer();
|
||||
current_gfx_.reserve(0x10000);
|
||||
for (int i = 0; i < 0x10000; i++) {
|
||||
current_gfx_.push_back(0x00);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 0x10; i++) {
|
||||
for (int j = 0; j < 0x1000; j++) {
|
||||
auto byte = all_gfx_[j + (static_graphics_[i] * 0x1000)];
|
||||
switch (i) {
|
||||
case 0:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
byte += 0x88;
|
||||
break;
|
||||
}
|
||||
current_gfx_[(i * 0x1000) + j] = byte;
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status OverworldMap::BuildTiles16Gfx(int count) {
|
||||
current_blockset_.reserve(0x100000);
|
||||
for (int i = 0; i < 0x100000; i++) {
|
||||
current_blockset_.push_back(0x00);
|
||||
}
|
||||
const int offsets[] = {0x00, 0x08, 0x400, 0x408};
|
||||
auto yy = 0;
|
||||
auto xx = 0;
|
||||
|
||||
for (auto i = 0; i < count; i++) {
|
||||
for (auto tile = 0; tile < 0x04; tile++) {
|
||||
gfx::TileInfo info = tiles16_[i].tiles_info[tile];
|
||||
int offset = offsets[tile];
|
||||
for (auto y = 0; y < 0x08; ++y) {
|
||||
for (auto x = 0; x < 0x08; ++x) {
|
||||
int mx = x;
|
||||
int my = y;
|
||||
|
||||
if (info.horizontal_mirror_ != 0) {
|
||||
mx = 0x07 - x;
|
||||
}
|
||||
|
||||
if (info.vertical_mirror_ != 0) {
|
||||
my = 0x07 - y;
|
||||
}
|
||||
|
||||
int xpos = ((info.id_ % 0x10) * 0x08);
|
||||
int ypos = (((info.id_ / 0x10)) * 0x400);
|
||||
int source = ypos + xpos + (x + (y * 0x80));
|
||||
|
||||
auto destination = xx + yy + offset + (mx + (my * 0x80));
|
||||
current_blockset_[destination] =
|
||||
(current_gfx_[source] & 0x0F) + (info.palette_ * 0x10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xx += 0x10;
|
||||
if (xx >= 0x80) {
|
||||
yy += 0x800;
|
||||
xx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status OverworldMap::BuildBitmap(OWBlockset& world_blockset) {
|
||||
bitmap_data_.reserve(0x40000);
|
||||
for (int i = 0; i < 0x40000; i++) {
|
||||
bitmap_data_.push_back(0x00);
|
||||
}
|
||||
|
||||
int superY = ((index_ - (world_ * 0x40)) / 0x08);
|
||||
int superX = index_ - (world_ * 0x40) - (superY * 0x08);
|
||||
|
||||
for (int y = 0; y < 0x20; y++) {
|
||||
for (int x = 0; x < 0x20; x++) {
|
||||
auto xt = x + (superX * 0x20);
|
||||
auto yt = y + (superY * 0x20);
|
||||
CopyTile8bpp16((x * 0x10), (y * 0x10), world_blockset[xt][yt],
|
||||
bitmap_data_, current_blockset_);
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
81
src/app/zelda3/overworld_map.h
Normal file
81
src/app/zelda3/overworld_map.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#ifndef YAZE_APP_ZELDA3_OVERWORLD_MAP_H
|
||||
#define YAZE_APP_ZELDA3_OVERWORLD_MAP_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace zelda3 {
|
||||
|
||||
static constexpr int kTileOffsets[] = {0, 8, 4096, 4104};
|
||||
|
||||
class OverworldMap {
|
||||
public:
|
||||
OverworldMap(int index, ROM& rom, std::vector<gfx::Tile16>& tiles16);
|
||||
|
||||
absl::Status BuildMap(int count, int game_state, int world, uchar* map_parent,
|
||||
OWBlockset& world_blockset);
|
||||
|
||||
auto Tile16Blockset() const { return current_blockset_; }
|
||||
auto AreaGraphics() const { return current_gfx_; }
|
||||
auto AreaPalette() const { return current_palette_; }
|
||||
auto BitmapData() const { return bitmap_data_; }
|
||||
auto SetLargeMap(bool is_set) { large_map_ = is_set; }
|
||||
auto IsLargeMap() const { return large_map_; }
|
||||
auto IsInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
void LoadAreaInfo();
|
||||
void LoadAreaGraphics();
|
||||
void LoadPalette();
|
||||
|
||||
absl::Status BuildTileset();
|
||||
absl::Status BuildTiles16Gfx(int count);
|
||||
absl::Status BuildBitmap(OWBlockset& world_blockset);
|
||||
|
||||
int parent_ = 0;
|
||||
int index_ = 0;
|
||||
int world_ = 0;
|
||||
int message_id_ = 0;
|
||||
int area_graphics_ = 0;
|
||||
int area_palette_ = 0;
|
||||
int game_state_ = 0;
|
||||
|
||||
uchar sprite_graphics_[3];
|
||||
uchar sprite_palette_[3];
|
||||
uchar area_music_[4];
|
||||
uchar static_graphics_[16];
|
||||
|
||||
bool initialized_ = false;
|
||||
bool built_ = false;
|
||||
bool large_map_ = false;
|
||||
|
||||
ROM rom_;
|
||||
Bytes all_gfx_;
|
||||
Bytes current_blockset_;
|
||||
Bytes current_gfx_;
|
||||
Bytes bitmap_data_;
|
||||
OWMapTiles map_tiles_;
|
||||
|
||||
gfx::SNESPalette current_palette_;
|
||||
|
||||
std::vector<gfx::Tile16> tiles16_;
|
||||
};
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
921
src/app/zelda3/sprite.cc
Normal file
921
src/app/zelda3/sprite.cc
Normal file
@@ -0,0 +1,921 @@
|
||||
#include "sprite.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace zelda3 {
|
||||
|
||||
Sprite::Sprite(Bytes src, uchar mapid, uchar id, uchar x, uchar y, int map_x,
|
||||
int map_y) {
|
||||
current_gfx_ = src;
|
||||
overworld_ = true;
|
||||
map_id_ = mapid;
|
||||
id_ = id;
|
||||
x_ = x;
|
||||
y_ = y;
|
||||
nx_ = x;
|
||||
ny_ = y;
|
||||
name_ = core::kSpriteDefaultNames[id];
|
||||
map_x_ = map_x;
|
||||
map_y_ = map_y;
|
||||
preview_gfx_.reserve(64 * 64);
|
||||
for (int i = 0; i < 64 * 64; i++) {
|
||||
preview_gfx_.push_back(0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite::updateBBox() {
|
||||
lowerX_ = 1;
|
||||
lowerY_ = 1;
|
||||
higherX_ = 15;
|
||||
higherY_ = 15;
|
||||
}
|
||||
|
||||
void Sprite::Draw(bool picker) {
|
||||
uchar x = nx_;
|
||||
uchar y = ny_;
|
||||
picker_ = picker;
|
||||
|
||||
if (overlord_ == 0x07) {
|
||||
if (id_ == 0x1A) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 6, 11); // bomb
|
||||
} else if (id_ == 0x05) {
|
||||
DrawSpriteTile((x * 16), (y * 16) - 12, 12, 16, 12, false, true);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 12, false, true);
|
||||
} else if (id_ == 0x06) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 10, 26, 14, true, true, 2,
|
||||
2); // snek
|
||||
} else if (id_ == 0x09) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 26, 14);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 8, 26, 14);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 27, 14, false, false, 1, 1);
|
||||
} else if (id_ == 0x14) {
|
||||
DrawSpriteTile((x * 16), (y * 16) + 8, 12, 06, 12, false, false, 2,
|
||||
1); // shadow tile
|
||||
DrawSpriteTile((x * 16), (y * 16) - 8, 3, 29, 8, false, false, 1,
|
||||
1); // tile
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 3, 29, 8, true, false, 1,
|
||||
1); // tile
|
||||
DrawSpriteTile((x * 16), (y * 16), 3, 29, 8, false, true, 1,
|
||||
1); // tile
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16), 3, 29, 8, true, true, 1,
|
||||
1); // tile
|
||||
} else {
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 4, 5);
|
||||
}
|
||||
|
||||
if (nx_ != x || ny_ != y) {
|
||||
bounding_box_.x = (lowerX_ + (nx_ * 16));
|
||||
bounding_box_.y = (lowerY_ + (ny_ * 16));
|
||||
bounding_box_.w = width_;
|
||||
bounding_box_.h = height_;
|
||||
} else {
|
||||
bounding_box_.x = (lowerX_ + (x * 16));
|
||||
bounding_box_.y = (lowerY_ + (y * 16));
|
||||
bounding_box_.w = width_;
|
||||
bounding_box_.h = height_;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (id_ == 0x00) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 28, 10);
|
||||
} else if (id_ == 0x01) {
|
||||
DrawSpriteTile((x * 16) - 8, (y * 16), 6, 24, 12, false, false, 2, 2);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16), 6, 24, 12, true, false, 2, 2);
|
||||
} else if (id_ == 0x02) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
|
||||
} else if (id_ == 0x04) {
|
||||
uchar p = 3;
|
||||
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
|
||||
} else if (id_ == 0x05) {
|
||||
uchar p = 3;
|
||||
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
|
||||
} else if (id_ == 0x06) {
|
||||
uchar p = 3;
|
||||
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
|
||||
} else if (id_ == 0x07) {
|
||||
uchar p = 3;
|
||||
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
|
||||
} else if (id_ == 0x08) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 24, 6);
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 6, 0, 24, 6, false, false, 1, 1);
|
||||
} else if (id_ == 0x09) {
|
||||
DrawSpriteTile((x * 16) - 22, (y * 16) - 24, 12, 24, 12, false, false, 2,
|
||||
2); // Moldorm tail
|
||||
DrawSpriteTile((x * 16) - 16, (y * 16) - 20, 8, 24, 12, false, false, 2,
|
||||
2); // Moldorm b2
|
||||
DrawSpriteTile((x * 16) - 12, (y * 16) - 16, 4, 24, 12, false, false, 4,
|
||||
4); // Moldorm b
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 24, 12, false, false, 4,
|
||||
4); // Moldorm head
|
||||
|
||||
DrawSpriteTile((x * 16) + 20, (y * 16) + 12, 8, 26, 14, false, false, 2,
|
||||
2); // Moldorm eye
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 20, 8, 26, 14, false, false, 2,
|
||||
2); // Moldorm eye
|
||||
} else if (id_ == 0x0A) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 24, 8);
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 6, 0, 24, 8, false, false, 1, 1);
|
||||
} else if (id_ == 0x0B) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 10, 30, 10);
|
||||
} else if (id_ == 0x0C) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 24, 8);
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 6, 0, 24, 8, false, false, 1, 1);
|
||||
} else if (id_ == 0x0D) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 28, 12);
|
||||
} else if (id_ == 0x0E) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 18, 10, false, false, 3, 2);
|
||||
} else if (id_ == 0x0F) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 24, 8, false, false, 2, 3);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 30, 8, 8, true, false, 1, 3);
|
||||
} else if (id_ == 0x10) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 12, 31, 8, false, false, 1, 1);
|
||||
} else if (id_ == 0x11) {
|
||||
DrawSpriteTile((x * 16), (y * 16) + 16, 6, 16, 8, false, false, 2,
|
||||
2); // Feet
|
||||
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 4, 18, 8, false, false, 2,
|
||||
2); // Body1
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 4, 18, 8, true, false, 2,
|
||||
2); // Body2
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8, false, false, 2,
|
||||
2); // Head
|
||||
} else if (id_ == 0x12) {
|
||||
DrawSpriteTile((x * 16), (y * 16) + 8, 8, 26, 8);
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 24, 8);
|
||||
} else if (id_ == 0x13) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 22, 2);
|
||||
} else if (id_ == 0x15) {
|
||||
// Antifairy
|
||||
DrawSpriteTile((x * 16) + 2, (y * 16) + 8, 3, 30, 5, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 2, 3, 30, 5, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 14, (y * 16) + 8, 3, 30, 5, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 14, 3, 30, 5, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 1, 30, 5, false, false, 1,
|
||||
1); // Middle
|
||||
} else if (id_ == 0x16) {
|
||||
DrawSpriteTile((x * 16), (y * 16) + 8, 2, 26, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 26, 2);
|
||||
} else if (id_ == 0x17) // Bush hoarder
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 30, 10);
|
||||
} else if (id_ == 0x18) // Mini moldorm
|
||||
{
|
||||
DrawSpriteTile((x * 16) + 13, (y * 16) + 17, 13, 21, 8, false, false, 1,
|
||||
1); // Tail
|
||||
DrawSpriteTile((x * 16) + 5, (y * 16) + 8, 2, 22, 8); // Body
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 22, 8); // Head
|
||||
DrawSpriteTile((x * 16), (y * 16) - 4, 13, 20, 8, false, false, 1,
|
||||
1); // Eyes
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16), 13, 20, 8, false, false, 1,
|
||||
1); // Eyes
|
||||
} else if (id_ == 0x19) // Poe - ghost
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 31, 2); //
|
||||
} else if (id_ == 0x1A) // Smith
|
||||
{
|
||||
// DrawSpriteTile((x*16), (y *16), 2, 4, 10,true); // Smitty
|
||||
// DrawSpriteTile((x*16)+12, (y *16) - 7, 0, 6, 10); // Hammer
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 22, 10);
|
||||
} else if (id_ == 0x1C) // Statue
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16) + 8, 0, 28, 15);
|
||||
DrawSpriteTile((x * 16), (y * 16), 2, 28, 15, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16), 2, 28, 15, true, false, 1, 1);
|
||||
} else if (id_ == 0x1E) // Crystal switch
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 30, 5);
|
||||
} else if (id_ == 0x1F) // Sick kid
|
||||
{
|
||||
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 10, 16, 14);
|
||||
DrawSpriteTile((x * 16) + 16 - 8, (y * 16) + 8, 10, 16, 14, true);
|
||||
DrawSpriteTile((x * 16) - 8, (y * 16) + 16, 10, 16, 14, false, true, 2, 2);
|
||||
DrawSpriteTile((x * 16) + 16 - 8, (y * 16) + 16, 10, 16, 14, true, true, 2,
|
||||
2);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 4, 14, 16, 10);
|
||||
} else if (id_ == 0x20) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 2, 24, 7);
|
||||
} else if (id_ == 0x21) // Push switch
|
||||
{
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 20, 13, 29, 3, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 28, 12, 29, 3, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 8, 10, 28, 3);
|
||||
} else if (id_ == 0x22) // Rope
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 26, 5);
|
||||
} else if (id_ == 0x23) // Red bari
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 2, 18, 4, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16), 2, 18, 4, true, false, 1, 2);
|
||||
} else if (id_ == 0x24) // Blue bari
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 2, 18, 6, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16), 2, 18, 6, true, false, 1, 2);
|
||||
} else if (id_ == 0x25) // Talking tree?
|
||||
{
|
||||
// TODO: Add something here?
|
||||
} else if (id_ == 0x26) // Hardhat beetle
|
||||
{
|
||||
if ((x & 0x01) == 0x00) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 20, 8);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 6, 0, 20, 8);
|
||||
} else {
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 20, 10);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 6, 0, 20, 10);
|
||||
}
|
||||
} else if (id_ == 0x27) // Deadrock
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 2, 30, 10);
|
||||
} else if (id_ == 0x28) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x29) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x2A) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x2B) // ???
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x2C) // Lumberjack
|
||||
{
|
||||
DrawSpriteTile((x * 16) - 24, (y * 16) + 12, 6, 26, 12, true); // Body
|
||||
DrawSpriteTile((x * 16) - 24, (y * 16), 8, 26, 12, true); // Head
|
||||
|
||||
DrawSpriteTile((x * 16) - 14, (y * 16) + 12, 14, 27, 10, false, false, 1,
|
||||
1); // Saw left edge
|
||||
DrawSpriteTile((x * 16) - 6, (y * 16) + 12, 15, 27, 10, false, false, 1,
|
||||
1); // Saw left edge
|
||||
DrawSpriteTile((x * 16) + 2, (y * 16) + 12, 15, 27, 10, false, false, 1,
|
||||
1); // Saw left edge
|
||||
DrawSpriteTile((x * 16) + 10, (y * 16) + 12, 15, 27, 10, false, false, 1,
|
||||
1); // Saw left edge
|
||||
DrawSpriteTile((x * 16) + 18, (y * 16) + 12, 15, 27, 10, false, false, 1,
|
||||
1); // Saw left edge
|
||||
DrawSpriteTile((x * 16) + 26, (y * 16) + 12, 15, 27, 10, false, false, 1,
|
||||
1); // Saw left edge
|
||||
DrawSpriteTile((x * 16) + 34, (y * 16) + 12, 14, 27, 10, true, false, 1,
|
||||
1); // Saw left edge
|
||||
|
||||
DrawSpriteTile((x * 16) + 40, (y * 16) + 12, 4, 26, 12); // Body
|
||||
DrawSpriteTile((x * 16) + 40, (y * 16), 8, 26, 12); // Head
|
||||
} else if (id_ == 0x2D) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x2E) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x2F) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x30) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x31) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x32) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
}
|
||||
/*
|
||||
else if (id_== 0x33) // Pull for rupees
|
||||
{
|
||||
|
||||
}
|
||||
*/
|
||||
else if (id_ == 0x34) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x35) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x36) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
}
|
||||
/*
|
||||
else if (id_== 0x37) // Waterfall
|
||||
{
|
||||
DrawSpriteTile((x*16), (y *16), 14, 6, 10);
|
||||
}
|
||||
*/
|
||||
else if (id_ == 0x38) // Arrowtarget
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x39) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x3A) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x3B) // Dash item
|
||||
{
|
||||
} else if (id_ == 0x3C) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x3D) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x3E) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x3F) // Npcs
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
|
||||
} else if (id_ == 0x40) // Lightning lock (agah tower)
|
||||
{
|
||||
DrawSpriteTile((x * 16) - 24, (y * 16), 10, 28, 2, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16) - 16, (y * 16), 6, 30, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 30, 2);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 6, 30, 2);
|
||||
DrawSpriteTile((x * 16) + 24, (y * 16), 10, 28, 2, false, false, 1, 2);
|
||||
} else if (id_ == 0x41) // Blue soldier
|
||||
{
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 10);
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 10, true, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 20, 10);
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 10, false, false, 1,
|
||||
2); // Shield
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 14, 22, 10, false, true, 1,
|
||||
2); // Sword
|
||||
} else if (id_ == 0x42) // Green soldier
|
||||
{
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 12);
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 12, true, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 12, false, false, 1,
|
||||
2); // Shield
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 14, 22, 12, false, true, 1,
|
||||
2); // Sword
|
||||
} else if (id_ == 0x43) // Red spear soldier
|
||||
{
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8);
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 8, true, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 20, 8);
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 8, false, false, 1,
|
||||
2); // Shield
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 11, 22, 8, false, true, 1,
|
||||
2); // Spear
|
||||
} else if (id_ == 0x44) // Sword blue holding up
|
||||
{
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 10);
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 10, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10); // Head
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 14, 22, 10, false, true, 1,
|
||||
2); // Sword
|
||||
} else if (id_ == 0x45) // Green spear soldier
|
||||
{
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 12);
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 12, true, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 12, false, false, 1,
|
||||
2); // Shield
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 11, 22, 12, false, true, 1,
|
||||
2); // Spear
|
||||
} else if (id_ == 0x46) // Blue archer
|
||||
{
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 10);
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 10, true, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 20, 10); // Head
|
||||
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 16, 10, false, false, 1,
|
||||
1); // Bow1
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 16, 10, 16, 10, true, false, 1,
|
||||
1); // Bow2
|
||||
} else if (id_ == 0x47) // Green archer
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16) + 8, 14, 16, 12);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 16, 12, false, false, 1,
|
||||
1); // Bow1
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 16, 10, 16, 12, true, false, 1,
|
||||
1); // Bow2
|
||||
} else if (id_ == 0x48) // Javelin soldier red
|
||||
{
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 8);
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8); // Head
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 11, 22, 8, false, true, 1,
|
||||
2); // Sword
|
||||
} else if (id_ == 0x49) // Javelin soldier red from bush
|
||||
{
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 8);
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 18, 8); // Head
|
||||
DrawSpriteTile((x * 16), (y * 16) + 24, 0, 20, 2);
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 11, 22, 8, false, true, 1,
|
||||
2); // Sword
|
||||
} else if (id_ == 0x4A) // Red bomb soldier
|
||||
{
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 8);
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8); // Head
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 14, 22, 11); // Bomb
|
||||
} else if (id_ == 0x4B) // Green soldier recruit
|
||||
{
|
||||
// 0,4
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 24, 12);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 10, 0, 20, 12);
|
||||
} else if (id_ == 0x4C) // Jazzhand
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 26, 14, false, false, 6, 2);
|
||||
} else if (id_ == 0x4D) // Rabit??
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 26, 12, false, false, 6, 2);
|
||||
} else if (id_ == 0x4E) // Popo1
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 20, 10);
|
||||
} else if (id_ == 0x4F) // Popo2
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 2, 20, 10);
|
||||
} else if (id_ == 0x50) // Canon ball
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 24, 10);
|
||||
} else if (id_ == 0x51) // Armos
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 28, 11, false, false, 2, 4);
|
||||
} else if (id_ == 0x53) // Armos Knight
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 28, 10, false, false, 4, 4);
|
||||
} else if (id_ == 0x54) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 2, 28, 12);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 10, 6, 28, 12);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16) + 18, 10, 28, 12);
|
||||
} else if (id_ == 0x55) // Fireball Zora
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 26, 11);
|
||||
} else if (id_ == 0x56) // Zora
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 10, 20, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 8, 8, 30, 2);
|
||||
} else if (id_ == 0x57) // Desert Rocks
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 24, 2, false, false, 2, 4);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 14, 24, 2, true, false, 2, 4);
|
||||
} else if (id_ == 0x58) // Crab
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 24, 12);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 14, 24, 12, true);
|
||||
} else if (id_ == 0x5B) // Spark
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 18, 4);
|
||||
} else if (id_ == 0x5C) // Spark
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 18, 4, true);
|
||||
} else if (id_ == 0x5D) // Roller vertical1
|
||||
{
|
||||
// Subset3
|
||||
if (((y * 16) & 0x10) == 0x10) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
DrawSpriteTile((x * 16) + 8 + (i * 16), (y * 16), 9, 24, 11);
|
||||
}
|
||||
|
||||
DrawSpriteTile((x * 16) + (16 * 7), (y * 16), 8, 24, 11, true);
|
||||
} else {
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 9, 24, 11);
|
||||
DrawSpriteTile((x * 16) + 32, (y * 16), 9, 24, 11);
|
||||
DrawSpriteTile((x * 16) + 48, (y * 16), 8, 24, 11, true);
|
||||
}
|
||||
|
||||
} else if (id_ == 0x5E) // Roller vertical2
|
||||
{
|
||||
// Subset3
|
||||
if (((y * 16) & 0x10) == 0x10) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
DrawSpriteTile((x * 16) + 8 + (i * 16), (y * 16), 9, 24, 11);
|
||||
}
|
||||
|
||||
DrawSpriteTile((x * 16) + (16 * 7), (y * 16), 8, 24, 11, true);
|
||||
} else {
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 9, 24, 11);
|
||||
DrawSpriteTile((x * 16) + 32, (y * 16), 9, 24, 11);
|
||||
DrawSpriteTile((x * 16) + 48, (y * 16), 8, 24, 11, true);
|
||||
}
|
||||
} else if (id_ == 0x5F) // Roller horizontal
|
||||
{
|
||||
if (((x * 16) & 0x10) == 0x10) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 16, 14, 25, 11);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 32, 14, 25, 11);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 48, 14, 24, 11, false, true);
|
||||
} else {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
DrawSpriteTile((x * 16), (y * 16) + i * 16, 14, 25, 11);
|
||||
}
|
||||
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
|
||||
DrawSpriteTile((x * 16), (y * 16) + (7 * 16), 14, 24, 11, false, true);
|
||||
}
|
||||
} else if (id_ == 0x60) // Roller horizontal2 (right to left)
|
||||
{
|
||||
// Subset3
|
||||
if (((x * 16) & 0x10) == 0x10) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 16, 14, 25, 11);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 32, 14, 25, 11);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 48, 14, 24, 11, false, true);
|
||||
} else {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
DrawSpriteTile((x * 16), (y * 16) + i * 16, 14, 25, 11);
|
||||
}
|
||||
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
|
||||
DrawSpriteTile((x * 16), (y * 16) + (7 * 16), 14, 24, 11, false, true);
|
||||
}
|
||||
|
||||
} else if (id_ == 0x61) // Beamos
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16) - 16, 8, 20, 14, false, false, 2, 4);
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) - 8, 10, 20, 14, false, false, 1, 1);
|
||||
} else if (id_ == 0x63) // Devalant non-shooter
|
||||
{
|
||||
DrawSpriteTile((x * 16) - 8, (y * 16) - 8, 2, 16, 2);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 2, 16, 2, true);
|
||||
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 2, 16, 2, false, true);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 2, 16, 2, true, true);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
|
||||
} else if (id_ == 0x64) // Devalant non-shooter
|
||||
{
|
||||
DrawSpriteTile((x * 16) - 8, (y * 16) - 8, 2, 16, 2);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 2, 16, 2, true);
|
||||
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 2, 16, 2, false, true);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 2, 16, 2, true, true);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8);
|
||||
} else if (id_ == 0x66) // Moving wall canon right
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 16, 14, true);
|
||||
} else if (id_ == 0x67) // Moving wall canon right
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 16, 14);
|
||||
} else if (id_ == 0x68) // Moving wall canon right
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 12, 16, 14);
|
||||
} else if (id_ == 0x69) // Moving wall canon right
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 12, 16, 14, false, true);
|
||||
} else if (id_ == 0x6A) // Chainball soldier
|
||||
{
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 14);
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 14, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 14); // Head
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) - 16, 10, 18, 14); // Ball
|
||||
} else if (id_ == 0x6B) // Cannon soldier
|
||||
{
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 14);
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 14, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 14); // Head
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 4, 18, 14); // Cannon
|
||||
} else if (id_ == 0x6C) // Mirror portal
|
||||
{
|
||||
// Useless
|
||||
} else if (id_ == 0x6D) // Rat
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 24, 5);
|
||||
} else if (id_ == 0x6E) // Rope
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 10, 26, 5);
|
||||
} else if (id_ == 0x6F) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 24, 10);
|
||||
} else if (id_ == 0x70) // Helma fireball
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 10, 28, 4);
|
||||
} else if (id_ == 0x71) // Leever
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 16, 4);
|
||||
} else if (id_ == 0x73) // Uncle priest
|
||||
{
|
||||
} else if (id_ == 0x79) // Bee
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 14, 11, false, false, 1, 1);
|
||||
} else if (id_ == 0x7A) {
|
||||
DrawSpriteTile((x * 16), (y * 16) - 16, 2, 24, 12, false, false, 2, 4);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16) - 16, 2, 24, 12, true, false, 2, 4);
|
||||
} else if (id_ == 0x7C) // Skull head
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
|
||||
} else if (id_ == 0x7D) // Big spike
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 28, 11);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 4, 28, 11, true);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 16, 4, 28, 11, false, true);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16) + 16, 4, 28, 11, true, true);
|
||||
} else if (id_ == 0x7E) // Guruguru clockwise
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16) - 14, 8, 18, 4);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 28, 8, 18, 4);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 42, 8, 18, 4);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 56, 8, 18, 4);
|
||||
} else if (id_ == 0x7F) // Guruguru Counterclockwise
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16) - 14, 8, 18, 4);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 28, 8, 18, 4);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 42, 8, 18, 4);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 56, 8, 18, 4);
|
||||
} else if (id_ == 0x80) // Winder (moving firebar)
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 18, 4);
|
||||
DrawSpriteTile((x * 16) - 14, (y * 16), 8, 18, 4);
|
||||
DrawSpriteTile((x * 16) - 28, (y * 16), 8, 18, 4);
|
||||
DrawSpriteTile((x * 16) - 42, (y * 16), 8, 18, 4);
|
||||
DrawSpriteTile((x * 16) - 56, (y * 16), 8, 18, 4);
|
||||
} else if (id_ == 0x81) // Water tektite
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 24, 11);
|
||||
} else if (id_ == 0x82) // circle antifairy
|
||||
{
|
||||
// Antifairy top
|
||||
DrawSpriteTile((x * 16 + 2) - 4, (y * 16 + 8) - 16, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 2) - 16, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 14) - 4, (y * 16 + 8) - 16, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 14) - 16, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 8) - 16, 1, 30, 5, false, false,
|
||||
1, 1); // Middle
|
||||
// Left
|
||||
DrawSpriteTile((x * 16 + 2) - 16, (y * 16 + 8) - 4, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) - 16, (y * 16 + 2) - 4, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 14) - 16, (y * 16 + 8) - 4, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) - 16, (y * 16 + 14) - 4, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) - 16, (y * 16 + 8) - 4, 1, 30, 5, false, false,
|
||||
1, 1); // Middle
|
||||
|
||||
DrawSpriteTile((x * 16 + 2) - 4, (y * 16 + 8) + 8, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 2) + 8, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 14) - 4, (y * 16 + 8) + 8, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 14) + 8, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 8) + 8, 1, 30, 5, false, false,
|
||||
1, 1); // Middle
|
||||
// Left
|
||||
DrawSpriteTile((x * 16 + 2) + 8, (y * 16 + 8) - 4, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) + 8, (y * 16 + 2) - 4, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 14) + 8, (y * 16 + 8) - 4, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) + 8, (y * 16 + 14) - 4, 3, 30, 5, false, false,
|
||||
1, 1);
|
||||
DrawSpriteTile((x * 16 + 8) + 8, (y * 16 + 8) - 4, 1, 30, 5, false, false,
|
||||
1, 1); // Middle
|
||||
} else if (id_ == 0x83) // Green eyegore
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 12, 24, 14, false, false, 2, 3);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 24, 14, true, false, 1, 3);
|
||||
} else if (id_ == 0x84) // Red eyegore
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 12, 24, 8, false, false, 2, 3);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 24, 8, true, false, 1, 3);
|
||||
} else if (id_ == 0x85) // Yellow stalfos
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 10, 16, 11);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 12, 0, 16, 11); // Head
|
||||
} else if (id_ == 0x86) // Kodongo
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 26, 14);
|
||||
} else if (id_ == 0x88) // Mothula
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 24, 14, false, false, 2, 4);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 8, 24, 14, true, false, 2, 4);
|
||||
} else if (id_ == 0x8A) // Spike
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 30, 15);
|
||||
} else if (id_ == 0x8B) // Gibdo
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 10, 24, 14);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 8, 0, 24, 14);
|
||||
} else if (id_ == 0x8C) // Arrghus
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 24, 14, false, false, 2, 4);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 0, 24, 14, true, false, 2, 4);
|
||||
} else if (id_ == 0x8D) // Arrghus spawn
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 24, 14);
|
||||
} else if (id_ == 0x8E) // Terrorpin
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 24, 12);
|
||||
} else if (id_ == 0x8F) // Slime
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
|
||||
} else if (id_ == 0x90) // Wall master
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 26, 12);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 15, 26, 12, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16) + 8, 9, 26, 12, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 27, 12, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 16, 8, 27, 12, false, false, 1, 1);
|
||||
} else if (id_ == 0x91) // Stalfos knight
|
||||
{
|
||||
DrawSpriteTile((x * 16) - 2, (y * 16) + 12, 4, 22, 12, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16) + 10, (y * 16) + 12, 4, 22, 12, true, false, 1, 2);
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16) + 4, 1, 22, 12);
|
||||
DrawSpriteTile((x * 16) + 12, (y * 16) + 4, 3, 22, 12, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 8, 6, 20, 12);
|
||||
} else if (id_ == 0x92) // Helmaking
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16) + 32, 14, 26, 14);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16) + 32, 0, 28, 14);
|
||||
DrawSpriteTile((x * 16) + 32, (y * 16) + 32, 14, 26, 14, true);
|
||||
|
||||
DrawSpriteTile((x * 16), (y * 16) + 16 + 32, 2, 28, 14);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16) + 16 + 32, 4, 28, 14);
|
||||
DrawSpriteTile((x * 16) + 32, (y * 16) + 16 + 32, 2, 28, 14, true);
|
||||
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 32 + 32, 6, 28, 14);
|
||||
DrawSpriteTile((x * 16) + 24, (y * 16) + 32 + 32, 6, 28, 14, true);
|
||||
} else if (id_ == 0x93) // Bumper
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 12, 30, 7);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 30, 7, true);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16) + 16, 12, 30, 7, true, true);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 16, 12, 30, 7, false, true);
|
||||
} else if (id_ == 0x95) // Right laser eye
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 9, 28, 3, true, false, 1, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16) + 16, 9, 28, 3, true, true, 1, 1);
|
||||
} else if (id_ == 0x96) // Left laser eye
|
||||
{
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) - 4, 9, 28, 3, false, false, 1, 2);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 12, 9, 28, 3, false, true, 1, 1);
|
||||
} else if (id_ == 0x97) // Right laser eye
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 28, 3, false, false, 2, 1);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 6, 28, 3, true, false, 1, 1);
|
||||
} else if (id_ == 0x98) // Right laser eye
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 28, 3, false, true, 2, 1);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 6, 28, 3, true, true, 1, 1);
|
||||
} else if (id_ == 0x99) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 24, 12);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 8, 0, 24, 12);
|
||||
} else if (id_ == 0x9A) // Water bubble kyameron
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 10, 24, 6);
|
||||
} else if (id_ == 0x9B) // Water bubble kyameron
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 6, 24, 11);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 8, 2, 27, 11, false, false, 2, 1);
|
||||
} else if (id_ == 0x9C) // Water bubble kyameron
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 12, 22, 11);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 13, 22, 11, false, false, 1, 2);
|
||||
} else if (id_ == 0x9D) // Water bubble kyameron
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 21, 11);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 16, 14, 20, 11, false, false, 2, 1);
|
||||
} else if (id_ == 0xA1) {
|
||||
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 6, 26, 14);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 6, 26, 14, true);
|
||||
} else if (id_ == 0xA2) {
|
||||
DrawSpriteTile((x * 16), (y * 16) + 8, 0, 24, 14, false, false, 4, 4);
|
||||
} else if (id_ == 0xA5) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 26, 10, false, false, 3, 2);
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) - 8, 0, 24, 10);
|
||||
} else if (id_ == 0xA6) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 26, 8, false, false, 3, 2);
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) - 8, 0, 24, 8);
|
||||
} else if (id_ == 0xA7) {
|
||||
DrawSpriteTile((x * 16), (y * 16) + 12, 12, 16, 10);
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
|
||||
} else if (id_ == 0xAC) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 5, 14, 4);
|
||||
} else if (id_ == 0xAD) {
|
||||
DrawSpriteTile((x * 16), (y * 16) + 8, 14, 10, 10);
|
||||
DrawSpriteTile((x * 16), (y * 16), 12, 10, 10);
|
||||
} else if (id_ == 0xBA) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 14, 6);
|
||||
} else if (id_ == 0xC1) {
|
||||
DrawSpriteTile((x * 16), (y * 16) - 16, 2, 24, 12, false, false, 2, 4);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16) - 16, 2, 24, 12, true, false, 2, 4);
|
||||
} else if (id_ == 0xC3) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 10, 14, 12);
|
||||
} else if (id_ == 0xC4) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 18, 14);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 8, 0, 16, 14);
|
||||
} else if (id_ == 0xC5) {
|
||||
} else if (id_ == 0xC6) {
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 14, 3, 30, 14, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 14, (y * 16) + 4, 3, 30, 14, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 2, 1, 31, 14, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) - 6, (y * 16) + 4, 3, 30, 14, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) - 6, 3, 30, 14, false, false, 1, 1);
|
||||
} else if (id_ == 0xC7) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 26, 4);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 10, 0, 26, 4);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 20, 0, 26, 4);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 30, 2, 26, 4);
|
||||
} else if (id_ == 0xC8) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 12, 24, 12, false, false, 2, 3);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 24, 12, true, false, 1, 3);
|
||||
} else if (id_ == 0xC9) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 28, 8, false);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16), 8, 28, 8, true);
|
||||
} else if (id_ == 0xCA) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 8, 10, 10);
|
||||
} else if (id_ == 0xD0) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 7, 14, 11, false, false, 3, 2);
|
||||
DrawSpriteTile((x * 16), (y * 16) - 10, 8, 12, 11);
|
||||
} else if (id_ == 0xD1) {
|
||||
DrawSpriteTile((x * 16) + 2, (y * 16) + 8, 7, 13, 11, true, true, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 2, 7, 13, 11, true, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 14, (y * 16) + 8, 7, 13, 11, true, true, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 14, 7, 13, 11, false, true, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 7, 13, 11, false, false, 1,
|
||||
1); // Middle
|
||||
// 6,7 / 13
|
||||
} else if (id_ == 0xD4) {
|
||||
DrawSpriteTile((x * 16) - 4, (y * 16), 0, 7, 7, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16), 0, 7, 7, true, false, 1, 1);
|
||||
} else if (id_ == 0xE3) // Fairy
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 10, 14, 10);
|
||||
} else if (id_ == 0xE4) // Key
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 11, 06, 11, false, false, 1, 2);
|
||||
} else if (id_ == 0xE7) // Mushroom
|
||||
{
|
||||
DrawSpriteTile((x * 16), (y * 16), 14, 30, 16);
|
||||
} else if (id_ == 0xE8) // Fake ms
|
||||
{
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16), 4, 31, 10, false, false, 1, 1);
|
||||
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 5, 31, 10, false, false, 1, 1);
|
||||
} else if (id_ == 0xEB) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 0, 14, 5);
|
||||
} else if (id_ == 0xF2) {
|
||||
DrawSpriteTile((x * 16), (y * 16) - 16, 12, 24, 2, false, false, 2, 4);
|
||||
DrawSpriteTile((x * 16) + 16, (y * 16) - 16, 12, 24, 2, true, false, 2, 4);
|
||||
} else if (id_ == 0xF4) {
|
||||
DrawSpriteTile((x * 16), (y * 16), 12, 28, 5, false, false, 4, 4);
|
||||
} else {
|
||||
// stringtodraw.Add(new SpriteName(x, (y *16), sprites_name.name[id]));
|
||||
DrawSpriteTile((x * 16), (y * 16), 4, 4, 5);
|
||||
}
|
||||
|
||||
bounding_box_.x = (lowerX_ + (x * 16));
|
||||
bounding_box_.y = (lowerY_ + (y * 16));
|
||||
bounding_box_.w = width_;
|
||||
bounding_box_.h = height_;
|
||||
}
|
||||
|
||||
void Sprite::DrawSpriteTile(int x, int y, int srcx, int srcy, int pal,
|
||||
bool mirror_x, bool mirror_y, int sizex, int sizey,
|
||||
bool iskey) {
|
||||
x += 16;
|
||||
y += 16;
|
||||
int drawid_ = (srcx + (srcy * 16)) + 512;
|
||||
for (auto yl = 0; yl < sizey * 8; yl++) {
|
||||
for (auto xl = 0; xl < (sizex * 8) / 2; xl++) {
|
||||
int mx = xl;
|
||||
int my = yl;
|
||||
|
||||
if (mirror_x) {
|
||||
mx = (((sizex * 8) / 2) - 1) - xl;
|
||||
}
|
||||
if (mirror_y) {
|
||||
my = (((sizey * 8)) - 1) - yl;
|
||||
}
|
||||
|
||||
// Formula information to get tile index position in the array
|
||||
//((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
|
||||
|
||||
int tx = ((drawid_ / 0x10) * 0x400) +
|
||||
((drawid_ - ((drawid_ / 0x10) * 0x10)) * 8);
|
||||
auto pixel = current_gfx_[tx + (yl * 0x80) + xl];
|
||||
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
|
||||
// position
|
||||
int index = (x) + (y * 64) + (mx + (my * 0x80));
|
||||
|
||||
if (index >= 0 && index <= 4096) {
|
||||
preview_gfx_[index] = (uchar)((pixel & 0x0F) + 112 + (pal * 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
68
src/app/zelda3/sprite.h
Normal file
68
src/app/zelda3/sprite.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef YAZE_APP_ZELDA3_SPRITE_H
|
||||
#define YAZE_APP_ZELDA3_SPRITE_H
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace zelda3 {
|
||||
|
||||
class Sprite {
|
||||
public:
|
||||
uchar x_, y_, id_;
|
||||
uchar nx_, ny_;
|
||||
uchar layer_ = 0;
|
||||
uchar subtype_ = 0;
|
||||
uchar overlord_ = 0;
|
||||
std::string name_;
|
||||
uchar keyDrop_ = 0;
|
||||
int sizeMap_ = 512;
|
||||
bool overworld_ = false;
|
||||
bool preview_ = false;
|
||||
uchar map_id_ = 0;
|
||||
int map_x_ = 0;
|
||||
int map_y_ = 0;
|
||||
short room_id_ = 0;
|
||||
bool picker_ = false;
|
||||
bool selected_ = false;
|
||||
SDL_Rect bounding_box_;
|
||||
|
||||
Bytes current_gfx_;
|
||||
Bytes preview_gfx_;
|
||||
|
||||
int lowerX_ = 32;
|
||||
int lowerY_ = 32;
|
||||
int higherX_ = 0;
|
||||
int higherY_ = 0;
|
||||
int width_ = 16;
|
||||
int height_ = 16;
|
||||
|
||||
Sprite(Bytes src, uchar mapid, uchar id, uchar x, uchar y, int map_x,
|
||||
int map_y);
|
||||
void updateBBox();
|
||||
|
||||
void Draw(bool picker = false);
|
||||
|
||||
void DrawSpriteTile(int x, int y, int srcx, int srcy, int pal,
|
||||
bool mirror_x = false, bool mirror_y = false,
|
||||
int sizex = 2, int sizey = 2, bool iskey = false);
|
||||
|
||||
auto PreviewGraphics() { return preview_gfx_; }
|
||||
};
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
129
src/app/zelda3/title_screen.cc
Normal file
129
src/app/zelda3/title_screen.cc
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "title_screen.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "app/core/common.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace zelda3 {
|
||||
|
||||
void TitleScreen::Create() {
|
||||
tiles8Bitmap.Create(128, 512, 8, 0x20000);
|
||||
tilesBG1Bitmap.Create(256, 256, 8, 0x80000);
|
||||
tilesBG2Bitmap.Create(256, 256, 8, 0x80000);
|
||||
oamBGBitmap.Create(256, 256, 8, 0x80000);
|
||||
|
||||
BuildTileset();
|
||||
|
||||
LoadTitleScreen();
|
||||
}
|
||||
|
||||
void TitleScreen::BuildTileset() {
|
||||
uchar staticgfx[16];
|
||||
|
||||
// Main Blocksets
|
||||
|
||||
// TODO: get the gfx from the GFX class rather than the rom.
|
||||
// for (int i = 0; i < 8; i++) {
|
||||
// staticgfx[i] = GfxGroups.mainGfx[titleScreenTilesGFX][i];
|
||||
// }
|
||||
|
||||
staticgfx[8] = 115 + 0;
|
||||
// staticgfx[9] = (GfxGroups.spriteGfx[titleScreenSpritesGFX][3] + 115);
|
||||
staticgfx[10] = 115 + 6;
|
||||
staticgfx[11] = 115 + 7;
|
||||
// staticgfx[12] = (GfxGroups.spriteGfx[titleScreenSpritesGFX][0] + 115);
|
||||
staticgfx[13] = 112;
|
||||
staticgfx[14] = 112;
|
||||
staticgfx[15] = 112;
|
||||
|
||||
// Loaded gfx for the current screen (empty at this point)
|
||||
uchar* currentmapgfx8Data = tiles8Bitmap.GetData();
|
||||
|
||||
// All gfx of the game pack of 2048 bytes (4bpp)
|
||||
uchar* allgfxData = nullptr; // rom_.GetMasterGraphicsBin();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < 2048; j++) {
|
||||
uchar mapByte = allgfxData[j + (staticgfx[i] * 2048)];
|
||||
switch (i) {
|
||||
case 0:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
mapByte += 0x88;
|
||||
break;
|
||||
}
|
||||
|
||||
currentmapgfx8Data[(i * 2048) + j] = mapByte; // Upload used gfx data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TitleScreen::LoadTitleScreen() {
|
||||
int pos =
|
||||
(rom_[0x138C + 3] << 16) + (rom_[0x1383 + 3] << 8) + rom_[0x137A + 3];
|
||||
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
tilesBG1Buffer[i] = 492;
|
||||
tilesBG2Buffer[i] = 492;
|
||||
}
|
||||
|
||||
pos = core::SnesToPc(pos);
|
||||
|
||||
while ((rom_[pos] & 0x80) != 0x80) {
|
||||
int dest_addr = pos; // $03 and $04
|
||||
pos += 2;
|
||||
short length = pos;
|
||||
bool increment64 = (length & 0x8000) == 0x8000;
|
||||
bool fixsource = (length & 0x4000) == 0x4000;
|
||||
pos += 2;
|
||||
|
||||
length = (short)((length & 0x07FF));
|
||||
|
||||
int j = 0;
|
||||
int jj = 0;
|
||||
int posB = pos;
|
||||
while (j < (length / 2) + 1) {
|
||||
ushort tiledata = (ushort)pos;
|
||||
if (dest_addr >= 0x1000) {
|
||||
// destAddr -= 0x1000;
|
||||
if (dest_addr < 0x2000) {
|
||||
tilesBG1Buffer[dest_addr - 0x1000] = tiledata;
|
||||
}
|
||||
} else {
|
||||
if (dest_addr < 0x1000) {
|
||||
tilesBG2Buffer[dest_addr] = tiledata;
|
||||
}
|
||||
}
|
||||
|
||||
if (increment64) {
|
||||
dest_addr += 32;
|
||||
} else {
|
||||
dest_addr++;
|
||||
}
|
||||
|
||||
if (!fixsource) {
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
jj += 2;
|
||||
j++;
|
||||
}
|
||||
|
||||
if (fixsource) {
|
||||
pos += 2;
|
||||
} else {
|
||||
pos = posB + jj;
|
||||
}
|
||||
}
|
||||
|
||||
pal_selected_ = 2;
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
81
src/app/zelda3/title_screen.h
Normal file
81
src/app/zelda3/title_screen.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#ifndef YAZE_APP_ZELDA3_SCREEN_H
|
||||
#define YAZE_APP_ZELDA3_SCREEN_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "app/core/common.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace zelda3 {
|
||||
|
||||
class TitleScreen {
|
||||
public:
|
||||
void Create();
|
||||
|
||||
private:
|
||||
void BuildTileset();
|
||||
void LoadTitleScreen();
|
||||
|
||||
int sword_x_ = 0;
|
||||
int mx_click_ = 0;
|
||||
int my_click_ = 0;
|
||||
int mx_dist_ = 0;
|
||||
int my_dist_ = 0;
|
||||
int last_x_ = 0;
|
||||
int last_y_ = 0;
|
||||
int x_in_ = 0;
|
||||
int y_in_ = 0;
|
||||
int dungmap_selected_tile_ = 0;
|
||||
int dungmap_selected_ = 0;
|
||||
int selected_palette_ = 0;
|
||||
int total_floors_ = 0;
|
||||
int current_floor_ = 0;
|
||||
int num_basement_ = 0;
|
||||
int num_floor_ = 0;
|
||||
int selected_map_tile = 0;
|
||||
int current_floor_rooms; // [1][];
|
||||
int current_floor_gfx; // [1][];
|
||||
int copied_data_rooms; // 25
|
||||
int copied_data_gfx; // 25
|
||||
int pal_selected_;
|
||||
int addresses[7] = {0x53de4, 0x53e2c, 0x53e08, 0x53e50,
|
||||
0x53e74, 0x53e98, 0x53ebc};
|
||||
int addressesgfx[7] = {0x53ee0, 0x53f04, 0x53ef2, 0x53f16,
|
||||
0x53f28, 0x53f3a, 0x53f4c};
|
||||
|
||||
ushort bossRoom = 0x000F;
|
||||
ushort selected_tile = 0;
|
||||
ushort tilesBG1Buffer[0x1000]; // 0x1000
|
||||
ushort tilesBG2Buffer[0x1000]; // 0x1000
|
||||
uchar mapdata; // 64 * 64
|
||||
uchar dwmapdata; // 64 * 64
|
||||
|
||||
bool mDown = false;
|
||||
bool swordSelected = false;
|
||||
bool darkWorld = false;
|
||||
bool currentDungeonChanged = false;
|
||||
bool editedFromEditor = false;
|
||||
bool mouseDown = false;
|
||||
bool mdown = false;
|
||||
|
||||
ROM rom_;
|
||||
|
||||
gfx::OAMTile oam_data[10];
|
||||
gfx::OAMTile selected_oam_tile;
|
||||
gfx::OAMTile last_selected_oam_tile;
|
||||
|
||||
gfx::Bitmap tilesBG1Bitmap; // 0x80000
|
||||
gfx::Bitmap tilesBG2Bitmap; // 0x80000
|
||||
gfx::Bitmap oamBGBitmap; // 0x80000
|
||||
gfx::Bitmap tiles8Bitmap; // 0x20000
|
||||
};
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
199
src/gui/canvas.cc
Normal file
199
src/gui/canvas.cc
Normal file
@@ -0,0 +1,199 @@
|
||||
#include "canvas.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
|
||||
void Canvas::DrawBackground(ImVec2 canvas_size) {
|
||||
canvas_p0_ = ImGui::GetCursorScreenPos();
|
||||
if (!custom_canvas_size_) canvas_sz_ = ImGui::GetContentRegionAvail();
|
||||
if (canvas_size.x != 0) canvas_sz_ = canvas_size;
|
||||
|
||||
canvas_p1_ = ImVec2(canvas_p0_.x + canvas_sz_.x, canvas_p0_.y + canvas_sz_.y);
|
||||
|
||||
// Draw border and background color
|
||||
draw_list_ = ImGui::GetWindowDrawList();
|
||||
draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, IM_COL32(32, 32, 32, 255));
|
||||
draw_list_->AddRect(canvas_p0_, canvas_p1_, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
|
||||
void Canvas::DrawContextMenu() {
|
||||
// This will catch our interactions
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
ImGui::InvisibleButton(
|
||||
"canvas", canvas_sz_,
|
||||
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
|
||||
const bool is_hovered = ImGui::IsItemHovered(); // Hovered
|
||||
const bool is_active = ImGui::IsItemActive(); // Held
|
||||
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
|
||||
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
|
||||
const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x,
|
||||
io.MousePos.y - origin.y);
|
||||
|
||||
// Pan (we use a zero mouse threshold when there's no context menu)
|
||||
const float mouse_threshold_for_pan = enable_context_menu_ ? -1.0f : 0.0f;
|
||||
if (is_active &&
|
||||
ImGui::IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) {
|
||||
scrolling_.x += io.MouseDelta.x;
|
||||
scrolling_.y += io.MouseDelta.y;
|
||||
}
|
||||
|
||||
// Context menu (under default mouse threshold)
|
||||
ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right);
|
||||
if (enable_context_menu_ && drag_delta.x == 0.0f && drag_delta.y == 0.0f)
|
||||
ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
|
||||
|
||||
if (ImGui::BeginPopup("context")) {
|
||||
ImGui::MenuItem("Show Grid", nullptr, &enable_grid_);
|
||||
if (ImGui::MenuItem("Reset Position", nullptr, false)) {
|
||||
scrolling_.x = 0;
|
||||
scrolling_.y = 0;
|
||||
}
|
||||
if (ImGui::MenuItem("Remove all", nullptr, false, points_.Size > 0)) {
|
||||
points_.clear();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawTilePainter(const Bitmap &bitmap, int size) {
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
const bool is_hovered = ImGui::IsItemHovered(); // Hovered
|
||||
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
|
||||
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
|
||||
const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x,
|
||||
io.MousePos.y - origin.y);
|
||||
|
||||
if (is_hovered) {
|
||||
if (!points_.empty()) {
|
||||
points_.clear();
|
||||
}
|
||||
ImVec2 draw_tile_outline_pos;
|
||||
draw_tile_outline_pos.x =
|
||||
std::floor((double)mouse_pos_in_canvas.x / size) * size;
|
||||
draw_tile_outline_pos.y =
|
||||
std::floor((double)mouse_pos_in_canvas.y / size) * size;
|
||||
|
||||
points_.push_back(draw_tile_outline_pos);
|
||||
points_.push_back(
|
||||
ImVec2(draw_tile_outline_pos.x + size, draw_tile_outline_pos.y + size));
|
||||
|
||||
if (bitmap.IsActive()) {
|
||||
draw_list_->AddImage(
|
||||
(void *)bitmap.GetTexture(),
|
||||
ImVec2(origin.x + draw_tile_outline_pos.x,
|
||||
origin.y + draw_tile_outline_pos.y),
|
||||
ImVec2(origin.x + draw_tile_outline_pos.x + bitmap.GetWidth(),
|
||||
origin.y + draw_tile_outline_pos.y + bitmap.GetHeight()));
|
||||
}
|
||||
|
||||
} else {
|
||||
points_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawTileSelector(int size) {
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
const bool is_hovered = ImGui::IsItemHovered(); // Hovered
|
||||
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
|
||||
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
|
||||
const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x,
|
||||
io.MousePos.y - origin.y);
|
||||
|
||||
if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
if (!points_.empty()) {
|
||||
points_.clear();
|
||||
}
|
||||
ImVec2 draw_tile_outline_pos;
|
||||
draw_tile_outline_pos.x =
|
||||
std::floor((double)mouse_pos_in_canvas.x / size) * size;
|
||||
draw_tile_outline_pos.y =
|
||||
std::floor((double)mouse_pos_in_canvas.y / size) * size;
|
||||
|
||||
points_.push_back(draw_tile_outline_pos);
|
||||
points_.push_back(
|
||||
ImVec2(draw_tile_outline_pos.x + size, draw_tile_outline_pos.y + size));
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawBitmap(const Bitmap &bitmap, int border_offset, bool ready) {
|
||||
if (ready) {
|
||||
draw_list_->AddImage(
|
||||
(void *)bitmap.GetTexture(),
|
||||
ImVec2(canvas_p0_.x + border_offset, canvas_p0_.y + border_offset),
|
||||
ImVec2(canvas_p0_.x + (bitmap.GetWidth() * 2),
|
||||
canvas_p0_.y + (bitmap.GetHeight() * 2)));
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawBitmap(const Bitmap &bitmap, int x_offset, int y_offset) {
|
||||
draw_list_->AddImage(
|
||||
(void *)bitmap.GetTexture(),
|
||||
ImVec2(canvas_p0_.x + x_offset + scrolling_.x,
|
||||
canvas_p0_.y + y_offset + scrolling_.y),
|
||||
ImVec2(canvas_p0_.x + x_offset + scrolling_.x + (bitmap.GetWidth()),
|
||||
canvas_p0_.y + y_offset + scrolling_.y + (bitmap.GetHeight())));
|
||||
}
|
||||
|
||||
void Canvas::DrawOutline(int x, int y, int w, int h) {
|
||||
ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
|
||||
canvas_p0_.y + scrolling_.y + y);
|
||||
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
|
||||
canvas_p0_.y + scrolling_.y + y + h);
|
||||
draw_list_->AddRect(origin, size, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
|
||||
void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
|
||||
ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
|
||||
canvas_p0_.y + scrolling_.y + y);
|
||||
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
|
||||
canvas_p0_.y + scrolling_.y + y + h);
|
||||
draw_list_->AddRectFilled(origin, size,
|
||||
IM_COL32(color.x, color.y, color.z, color.w));
|
||||
}
|
||||
|
||||
void Canvas::DrawText(std::string text, int x, int y) {
|
||||
draw_list_->AddText(
|
||||
ImVec2(canvas_p0_.x + scrolling_.x + x, canvas_p0_.y + scrolling_.y + y),
|
||||
IM_COL32(255, 255, 255, 255), text.data());
|
||||
}
|
||||
|
||||
void Canvas::DrawGrid(float grid_step) {
|
||||
// Draw grid + all lines in the canvas
|
||||
draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
|
||||
if (enable_grid_) {
|
||||
for (float x = fmodf(scrolling_.x, grid_step); x < canvas_sz_.x;
|
||||
x += grid_step)
|
||||
draw_list_->AddLine(ImVec2(canvas_p0_.x + x, canvas_p0_.y),
|
||||
ImVec2(canvas_p0_.x + x, canvas_p1_.y),
|
||||
IM_COL32(200, 200, 200, 40));
|
||||
for (float y = fmodf(scrolling_.y, grid_step); y < canvas_sz_.y;
|
||||
y += grid_step)
|
||||
draw_list_->AddLine(ImVec2(canvas_p0_.x, canvas_p0_.y + y),
|
||||
ImVec2(canvas_p1_.x, canvas_p0_.y + y),
|
||||
IM_COL32(200, 200, 200, 40));
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawOverlay() {
|
||||
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
|
||||
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
|
||||
for (int n = 0; n < points_.Size; n += 2) {
|
||||
draw_list_->AddRect(
|
||||
ImVec2(origin.x + points_[n].x, origin.y + points_[n].y),
|
||||
ImVec2(origin.x + points_[n + 1].x, origin.y + points_[n + 1].y),
|
||||
IM_COL32(255, 255, 255, 255), 1.0f);
|
||||
}
|
||||
|
||||
draw_list_->PopClipRect();
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
67
src/gui/canvas.h
Normal file
67
src/gui/canvas.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef YAZE_GUI_CANVAS_H
|
||||
#define YAZE_GUI_CANVAS_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
|
||||
using app::gfx::Bitmap;
|
||||
|
||||
class Canvas {
|
||||
public:
|
||||
Canvas() = default;
|
||||
explicit Canvas(ImVec2 canvas_size)
|
||||
: custom_canvas_size_(true), canvas_sz_(canvas_size) {}
|
||||
|
||||
void DrawBackground(ImVec2 canvas_size = ImVec2(0, 0));
|
||||
void DrawContextMenu();
|
||||
void DrawTilePainter(const Bitmap& bitmap, int size);
|
||||
void DrawTileSelector(int size);
|
||||
void DrawBitmap(const Bitmap& bitmap, int border_offset = 0,
|
||||
bool ready = true);
|
||||
void DrawBitmap(const Bitmap& bitmap, int x_offset, int y_offset);
|
||||
void DrawOutline(int x, int y, int w, int h);
|
||||
void DrawRect(int x, int y, int w, int h, ImVec4 color);
|
||||
void DrawText(std::string text, int x, int y);
|
||||
void DrawGrid(float grid_step = 64.0f);
|
||||
void DrawOverlay(); // last
|
||||
|
||||
auto Points() const { return points_; }
|
||||
auto GetDrawList() const { return draw_list_; }
|
||||
auto GetZeroPoint() const { return canvas_p0_; }
|
||||
void SetCanvasSize(ImVec2 canvas_size) {
|
||||
canvas_sz_ = canvas_size;
|
||||
custom_canvas_size_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool enable_grid_ = true;
|
||||
bool enable_context_menu_ = true;
|
||||
bool custom_canvas_size_ = false;
|
||||
bool is_hovered_ = false;
|
||||
|
||||
ImDrawList* draw_list_;
|
||||
ImVector<ImVec2> points_;
|
||||
ImVec2 scrolling_;
|
||||
ImVec2 canvas_sz_;
|
||||
ImVec2 canvas_p0_;
|
||||
ImVec2 canvas_p1_;
|
||||
ImVec2 mouse_pos_in_canvas_;
|
||||
|
||||
std::vector<app::gfx::Bitmap> changed_tiles_;
|
||||
app::gfx::Bitmap current_tile_;
|
||||
|
||||
std::string title_;
|
||||
};
|
||||
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
84
src/gui/color.cc
Normal file
84
src/gui/color.cc
Normal file
@@ -0,0 +1,84 @@
|
||||
#include "color.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
void DisplayPalette(app::gfx::SNESPalette& palette, bool loaded) {
|
||||
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
|
||||
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
|
||||
ImGuiColorEditFlags_NoDragDrop |
|
||||
ImGuiColorEditFlags_NoOptions;
|
||||
|
||||
// Generate a default palette. The palette will persist and can be edited.
|
||||
static bool init = false;
|
||||
static ImVec4 saved_palette[32] = {};
|
||||
if (loaded && !init) {
|
||||
for (int n = 0; n < palette.size_; n++) {
|
||||
saved_palette[n].x = palette.GetColor(n).rgb.x / 255;
|
||||
saved_palette[n].y = palette.GetColor(n).rgb.y / 255;
|
||||
saved_palette[n].z = palette.GetColor(n).rgb.z / 255;
|
||||
saved_palette[n].w = 255; // Alpha
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
static ImVec4 backup_color;
|
||||
ImGui::Text("Current ==>");
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Previous");
|
||||
|
||||
ImGui::ColorButton(
|
||||
"##current", color,
|
||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||
ImVec2(60, 40));
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::ColorButton(
|
||||
"##previous", backup_color,
|
||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||
ImVec2(60, 40)))
|
||||
color = backup_color;
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::BeginGroup(); // Lock X position
|
||||
ImGui::Text("Palette");
|
||||
for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) {
|
||||
ImGui::PushID(n);
|
||||
if ((n % 4) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha |
|
||||
ImGuiColorEditFlags_NoPicker |
|
||||
ImGuiColorEditFlags_NoTooltip;
|
||||
if (ImGui::ColorButton("##palette", saved_palette[n], palette_button_flags,
|
||||
ImVec2(20, 20)))
|
||||
color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z,
|
||||
color.w); // Preserve alpha!
|
||||
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
|
||||
memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3);
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
|
||||
memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4);
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::ColorPicker4("##picker", (float*)&color,
|
||||
misc_flags | ImGuiColorEditFlags_NoSidePreview |
|
||||
ImGuiColorEditFlags_NoSmallPreview);
|
||||
}
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
20
src/gui/color.h
Normal file
20
src/gui/color.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef YAZE_GUI_COLOR_H
|
||||
#define YAZE_GUI_COLOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
|
||||
void DisplayPalette(app::gfx::SNESPalette& palette, bool loaded);
|
||||
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
2192
src/gui/icons.h
Normal file
2192
src/gui/icons.h
Normal file
File diff suppressed because it is too large
Load Diff
70
src/gui/input.cc
Normal file
70
src/gui/input.cc
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "input.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
|
||||
const int kStepOneHex = 0x01;
|
||||
const int kStepFastHex = 0x0F;
|
||||
|
||||
bool InputHex(const char* label, int* data) {
|
||||
return ImGui::InputScalar(label, ImGuiDataType_U64, data, &kStepOneHex,
|
||||
&kStepFastHex, "%06X",
|
||||
ImGuiInputTextFlags_CharsHexadecimal);
|
||||
}
|
||||
|
||||
bool InputHexShort(const char* label, int* data) {
|
||||
return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex,
|
||||
&kStepFastHex, "%06X",
|
||||
ImGuiInputTextFlags_CharsHexadecimal);
|
||||
}
|
||||
|
||||
void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
const ImVec2 lineStart = ImGui::GetCursorScreenPos();
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
float fullWidth = ImGui::GetContentRegionAvail().x;
|
||||
float itemWidth = ImGui::CalcItemWidth() + style.ItemSpacing.x;
|
||||
ImVec2 textSize = ImGui::CalcTextSize(title.begin(), title.end());
|
||||
ImRect textRect;
|
||||
textRect.Min = ImGui::GetCursorScreenPos();
|
||||
if (flags & ItemLabelFlag::Right) textRect.Min.x = textRect.Min.x + itemWidth;
|
||||
textRect.Max = textRect.Min;
|
||||
textRect.Max.x += fullWidth - itemWidth;
|
||||
textRect.Max.y += textSize.y;
|
||||
|
||||
ImGui::SetCursorScreenPos(textRect.Min);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
// Adjust text rect manually because we render it directly into a drawlist
|
||||
// instead of using public functions.
|
||||
textRect.Min.y += window->DC.CurrLineTextBaseOffset;
|
||||
textRect.Max.y += window->DC.CurrLineTextBaseOffset;
|
||||
|
||||
ImGui::ItemSize(textRect);
|
||||
if (ImGui::ItemAdd(
|
||||
textRect, window->GetID(title.data(), title.data() + title.size()))) {
|
||||
ImGui::RenderTextEllipsis(
|
||||
ImGui::GetWindowDrawList(), textRect.Min, textRect.Max, textRect.Max.x,
|
||||
textRect.Max.x, title.data(), title.data() + title.size(), &textSize);
|
||||
|
||||
if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%.*s", (int)title.size(), title.data());
|
||||
}
|
||||
if (flags & ItemLabelFlag::Left) {
|
||||
ImVec2 result;
|
||||
auto other = ImVec2{0, textSize.y + window->DC.CurrLineTextBaseOffset};
|
||||
result.x = textRect.Max.x - other.x;
|
||||
result.y = textRect.Max.y - other.y;
|
||||
ImGui::SetCursorScreenPos(result);
|
||||
ImGui::SameLine();
|
||||
} else if (flags & ItemLabelFlag::Right)
|
||||
ImGui::SetCursorScreenPos(lineStart);
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
29
src/gui/input.h
Normal file
29
src/gui/input.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef YAZE_APP_CORE_INPUT_H
|
||||
#define YAZE_APP_CORE_INPUT_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
|
||||
IMGUI_API bool InputHex(const char* label, int* data);
|
||||
IMGUI_API bool InputHexShort(const char* label, int* data);
|
||||
|
||||
using ItemLabelFlags = enum ItemLabelFlag {
|
||||
Left = 1u << 0u,
|
||||
Right = 1u << 1u,
|
||||
Default = Left,
|
||||
};
|
||||
|
||||
IMGUI_API void ItemLabel(absl::string_view title, ItemLabelFlags flags);
|
||||
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
108
src/gui/style.cc
Normal file
108
src/gui/style.cc
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "style.h"
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
#include "imgui/imgui_internal.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
|
||||
void ColorsYaze() {
|
||||
ImGuiStyle *style = &ImGui::GetStyle();
|
||||
ImVec4 *colors = style->Colors;
|
||||
|
||||
style->WindowPadding = ImVec2(10.f, 10.f);
|
||||
style->FramePadding = ImVec2(10.f, 3.f);
|
||||
style->CellPadding = ImVec2(4.f, 5.f);
|
||||
style->ItemSpacing = ImVec2(10.f, 5.f);
|
||||
style->ItemInnerSpacing = ImVec2(5.f, 5.f);
|
||||
style->TouchExtraPadding = ImVec2(0.f, 0.f);
|
||||
style->IndentSpacing = 20.f;
|
||||
style->ScrollbarSize = 14.f;
|
||||
style->GrabMinSize = 15.f;
|
||||
|
||||
style->WindowBorderSize = 0.f;
|
||||
style->ChildBorderSize = 1.f;
|
||||
style->PopupBorderSize = 1.f;
|
||||
style->FrameBorderSize = 0.f;
|
||||
style->TabBorderSize = 0.f;
|
||||
|
||||
style->WindowRounding = 0.f;
|
||||
style->ChildRounding = 0.f;
|
||||
style->FrameRounding = 5.f;
|
||||
style->PopupRounding = 0.f;
|
||||
style->ScrollbarRounding = 5.f;
|
||||
|
||||
auto alttpDarkGreen = ImVec4(0.18f, 0.26f, 0.18f, 1.0f);
|
||||
auto alttpMidGreen = ImVec4(0.28f, 0.36f, 0.28f, 1.0f);
|
||||
auto allttpLightGreen = ImVec4(0.36f, 0.45f, 0.36f, 1.0f);
|
||||
auto allttpLightestGreen = ImVec4(0.49f, 0.57f, 0.49f, 1.0f);
|
||||
|
||||
colors[ImGuiCol_MenuBarBg] = alttpDarkGreen;
|
||||
colors[ImGuiCol_TitleBg] = alttpMidGreen;
|
||||
|
||||
colors[ImGuiCol_Header] = alttpDarkGreen;
|
||||
colors[ImGuiCol_HeaderHovered] = allttpLightGreen;
|
||||
colors[ImGuiCol_HeaderActive] = alttpMidGreen;
|
||||
|
||||
colors[ImGuiCol_TitleBgActive] = alttpDarkGreen;
|
||||
colors[ImGuiCol_TitleBgCollapsed] = alttpMidGreen;
|
||||
|
||||
colors[ImGuiCol_Tab] = alttpDarkGreen;
|
||||
colors[ImGuiCol_TabHovered] = alttpMidGreen;
|
||||
colors[ImGuiCol_TabActive] = ImVec4(0.347f, 0.466f, 0.347f, 1.000f);
|
||||
|
||||
colors[ImGuiCol_Button] = alttpMidGreen;
|
||||
colors[ImGuiCol_ButtonHovered] = allttpLightestGreen;
|
||||
colors[ImGuiCol_ButtonActive] = allttpLightGreen;
|
||||
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.36f, 0.45f, 0.36f, 0.60f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.36f, 0.45f, 0.36f, 0.30f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.36f, 0.45f, 0.36f, 0.40f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.36f, 0.45f, 0.36f, 0.60f);
|
||||
|
||||
colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.85f);
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f);
|
||||
colors[ImGuiCol_Border] = allttpLightGreen;
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.28f, 0.36f, 0.28f, 0.40f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.28f, 0.36f, 0.28f, 0.69f);
|
||||
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.36f, 0.45f, 0.36f, 0.60f);
|
||||
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 0.60f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.10f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f);
|
||||
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f);
|
||||
|
||||
colors[ImGuiCol_TabUnfocused] =
|
||||
ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f);
|
||||
colors[ImGuiCol_TabUnfocusedActive] =
|
||||
ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f);
|
||||
colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_TableHeaderBg] = alttpDarkGreen;
|
||||
colors[ImGuiCol_TableBorderStrong] = alttpMidGreen;
|
||||
colors[ImGuiCol_TableBorderLight] =
|
||||
ImVec4(0.26f, 0.26f, 0.28f, 1.00f); // Prefer using Alpha=1.0 here
|
||||
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f);
|
||||
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
|
||||
colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered];
|
||||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
|
||||
}
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
15
src/gui/style.h
Normal file
15
src/gui/style.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef YAZE_APP_CORE_STYLE_H
|
||||
#define YAZE_APP_CORE_STYLE_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
|
||||
void ColorsYaze();
|
||||
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
68
src/gui/widgets.cc
Normal file
68
src/gui/widgets.cc
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "widgets.h"
|
||||
|
||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace widgets {
|
||||
|
||||
TextEditor::LanguageDefinition GetAssemblyLanguageDef() {
|
||||
TextEditor::LanguageDefinition language_65816;
|
||||
for (auto &k : app::core::kKeywords) language_65816.mKeywords.emplace(k);
|
||||
|
||||
for (auto &k : app::core::kIdentifiers) {
|
||||
TextEditor::Identifier id;
|
||||
id.mDeclaration = "Built-in function";
|
||||
language_65816.mIdentifiers.insert(std::make_pair(std::string(k), id));
|
||||
}
|
||||
|
||||
language_65816.mTokenRegexStrings.push_back(
|
||||
std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"[ \\t]*#[ \\t]*[a-zA-Z_]+", TextEditor::PaletteIndex::Preprocessor));
|
||||
language_65816.mTokenRegexStrings.push_back(
|
||||
std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"L?\\\"(\\\\.|[^\\\"])*\\\"", TextEditor::PaletteIndex::String));
|
||||
language_65816.mTokenRegexStrings.push_back(
|
||||
std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"\\'\\\\?[^\\']\\'", TextEditor::PaletteIndex::CharLiteral));
|
||||
language_65816.mTokenRegexStrings.push_back(
|
||||
std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?",
|
||||
TextEditor::PaletteIndex::Number));
|
||||
language_65816.mTokenRegexStrings.push_back(
|
||||
std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"[+-]?[0-9]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number));
|
||||
language_65816.mTokenRegexStrings.push_back(
|
||||
std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"0[0-7]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number));
|
||||
language_65816.mTokenRegexStrings.push_back(
|
||||
std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?",
|
||||
TextEditor::PaletteIndex::Number));
|
||||
language_65816.mTokenRegexStrings.push_back(
|
||||
std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"[a-zA-Z_][a-zA-Z0-9_]*", TextEditor::PaletteIndex::Identifier));
|
||||
language_65816.mTokenRegexStrings.push_back(
|
||||
std::make_pair<std::string, TextEditor::PaletteIndex>(
|
||||
"[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/"
|
||||
"\\;\\,\\.]",
|
||||
TextEditor::PaletteIndex::Punctuation));
|
||||
|
||||
language_65816.mCommentStart = "/*";
|
||||
language_65816.mCommentEnd = "*/";
|
||||
language_65816.mSingleLineComment = ";";
|
||||
|
||||
language_65816.mCaseSensitive = false;
|
||||
language_65816.mAutoIndentation = true;
|
||||
|
||||
language_65816.mName = "65816";
|
||||
|
||||
return language_65816;
|
||||
}
|
||||
|
||||
} // namespace widgets
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
19
src/gui/widgets.h
Normal file
19
src/gui/widgets.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef YAZE_GUI_WIDGETS_H
|
||||
#define YAZE_GUI_WIDGETS_H
|
||||
|
||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace widgets {
|
||||
|
||||
TextEditor::LanguageDefinition GetAssemblyLanguageDef();
|
||||
|
||||
} // namespace widgets
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
1
src/lib/ImGuiColorTextEdit
Submodule
1
src/lib/ImGuiColorTextEdit
Submodule
Submodule src/lib/ImGuiColorTextEdit added at 0a88824f7d
1
src/lib/ImGuiFileDialog
Submodule
1
src/lib/ImGuiFileDialog
Submodule
Submodule src/lib/ImGuiFileDialog added at af4800e85a
1
src/lib/SDL
Submodule
1
src/lib/SDL
Submodule
Submodule src/lib/SDL added at 5b904a103a
1
src/lib/SDL_mixer
Submodule
1
src/lib/SDL_mixer
Submodule
Submodule src/lib/SDL_mixer added at 7f73f724f2
1
src/lib/abseil-cpp
Submodule
1
src/lib/abseil-cpp
Submodule
Submodule src/lib/abseil-cpp added at 8c0b94e793
1
src/lib/asar
Submodule
1
src/lib/asar
Submodule
Submodule src/lib/asar added at 634d6baf7a
1
src/lib/imgui
Submodule
1
src/lib/imgui
Submodule
Submodule src/lib/imgui added at e57871bb95
742
src/lib/imgui_memory_editor.h
Normal file
742
src/lib/imgui_memory_editor.h
Normal file
@@ -0,0 +1,742 @@
|
||||
// Mini memory editor for Dear ImGui (to embed in your game/tools)
|
||||
// Get latest version at http://www.github.com/ocornut/imgui_club
|
||||
//
|
||||
// Right-click anywhere to access the Options menu!
|
||||
// You can adjust the keyboard repeat delay/rate in ImGuiIO.
|
||||
// The code assume a mono-space font for simplicity!
|
||||
// If you don't use the default font, use ImGui::PushFont()/PopFont() to switch to a mono-space font before calling this.
|
||||
//
|
||||
// Usage:
|
||||
// // Create a window and draw memory editor inside it:
|
||||
// static MemoryEditor mem_edit_1;
|
||||
// static char data[0x10000];
|
||||
// size_t data_size = 0x10000;
|
||||
// mem_edit_1.DrawWindow("Memory Editor", data, data_size);
|
||||
//
|
||||
// Usage:
|
||||
// // If you already have a window, use DrawContents() instead:
|
||||
// static MemoryEditor mem_edit_2;
|
||||
// ImGui::Begin("MyWindow")
|
||||
// mem_edit_2.DrawContents(this, sizeof(*this), (size_t)this);
|
||||
// ImGui::End();
|
||||
//
|
||||
// Changelog:
|
||||
// - v0.10: initial version
|
||||
// - v0.23 (2017/08/17): added to github. fixed right-arrow triggering a byte write.
|
||||
// - v0.24 (2018/06/02): changed DragInt("Rows" to use a %d data format (which is desirable since imgui 1.61).
|
||||
// - v0.25 (2018/07/11): fixed wording: all occurrences of "Rows" renamed to "Columns".
|
||||
// - v0.26 (2018/08/02): fixed clicking on hex region
|
||||
// - v0.30 (2018/08/02): added data preview for common data types
|
||||
// - v0.31 (2018/10/10): added OptUpperCaseHex option to select lower/upper casing display [@samhocevar]
|
||||
// - v0.32 (2018/10/10): changed signatures to use void* instead of unsigned char*
|
||||
// - v0.33 (2018/10/10): added OptShowOptions option to hide all the interactive option setting.
|
||||
// - v0.34 (2019/05/07): binary preview now applies endianness setting [@nicolasnoble]
|
||||
// - v0.35 (2020/01/29): using ImGuiDataType available since Dear ImGui 1.69.
|
||||
// - v0.36 (2020/05/05): minor tweaks, minor refactor.
|
||||
// - v0.40 (2020/10/04): fix misuse of ImGuiListClipper API, broke with Dear ImGui 1.79. made cursor position appears on left-side of edit box. option popup appears on mouse release. fix MSVC warnings where _CRT_SECURE_NO_WARNINGS wasn't working in recent versions.
|
||||
// - v0.41 (2020/10/05): fix when using with keyboard/gamepad navigation enabled.
|
||||
// - v0.42 (2020/10/14): fix for . character in ASCII view always being greyed out.
|
||||
// - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel]
|
||||
// - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width.
|
||||
// - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size.
|
||||
//
|
||||
// Todo/Bugs:
|
||||
// - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day.
|
||||
// - PageUp/PageDown are supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own...
|
||||
// - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame.
|
||||
// - Using InputText() is awkward and maybe overkill here, consider implementing something custom.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h> // sprintf, scanf
|
||||
#include <stdint.h> // uint8_t, etc.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define _PRISizeT "I"
|
||||
#define ImSnprintf _snprintf
|
||||
#else
|
||||
#define _PRISizeT "z"
|
||||
#define ImSnprintf snprintf
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
|
||||
#endif
|
||||
|
||||
struct MemoryEditor
|
||||
{
|
||||
enum DataFormat
|
||||
{
|
||||
DataFormat_Bin = 0,
|
||||
DataFormat_Dec = 1,
|
||||
DataFormat_Hex = 2,
|
||||
DataFormat_COUNT
|
||||
};
|
||||
|
||||
// Settings
|
||||
bool Open; // = true // set to false when DrawWindow() was closed. ignore if not using DrawWindow().
|
||||
bool ReadOnly; // = false // disable any editing.
|
||||
int Cols; // = 16 // number of columns to display.
|
||||
bool OptShowOptions; // = true // display options button/context menu. when disabled, options will be locked unless you provide your own UI for them.
|
||||
bool OptShowDataPreview; // = false // display a footer previewing the decimal/binary/hex/float representation of the currently selected bytes.
|
||||
bool OptShowHexII; // = false // display values in HexII representation instead of regular hexadecimal: hide null/zero bytes, ascii values as ".X".
|
||||
bool OptShowAscii; // = true // display ASCII representation on the right side.
|
||||
bool OptGreyOutZeroes; // = true // display null/zero bytes using the TextDisabled color.
|
||||
bool OptUpperCaseHex; // = true // display hexadecimal values as "FF" instead of "ff".
|
||||
int OptMidColsCount; // = 8 // set to 0 to disable extra spacing between every mid-cols.
|
||||
int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr).
|
||||
float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets
|
||||
ImU32 HighlightColor; // // background color of highlighted bytes.
|
||||
ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes.
|
||||
void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes.
|
||||
bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
|
||||
|
||||
// [Internal State]
|
||||
bool ContentsWidthChanged;
|
||||
size_t DataPreviewAddr;
|
||||
size_t DataEditingAddr;
|
||||
bool DataEditingTakeFocus;
|
||||
char DataInputBuf[32];
|
||||
char AddrInputBuf[32];
|
||||
size_t GotoAddr;
|
||||
size_t HighlightMin, HighlightMax;
|
||||
int PreviewEndianess;
|
||||
ImGuiDataType PreviewDataType;
|
||||
|
||||
MemoryEditor()
|
||||
{
|
||||
// Settings
|
||||
Open = true;
|
||||
ReadOnly = false;
|
||||
Cols = 16;
|
||||
OptShowOptions = true;
|
||||
OptShowDataPreview = false;
|
||||
OptShowHexII = false;
|
||||
OptShowAscii = true;
|
||||
OptGreyOutZeroes = true;
|
||||
OptUpperCaseHex = true;
|
||||
OptMidColsCount = 8;
|
||||
OptAddrDigitsCount = 0;
|
||||
OptFooterExtraHeight = 0.0f;
|
||||
HighlightColor = IM_COL32(255, 255, 255, 50);
|
||||
ReadFn = NULL;
|
||||
WriteFn = NULL;
|
||||
HighlightFn = NULL;
|
||||
|
||||
// State/Internals
|
||||
ContentsWidthChanged = false;
|
||||
DataPreviewAddr = DataEditingAddr = (size_t)-1;
|
||||
DataEditingTakeFocus = false;
|
||||
memset(DataInputBuf, 0, sizeof(DataInputBuf));
|
||||
memset(AddrInputBuf, 0, sizeof(AddrInputBuf));
|
||||
GotoAddr = (size_t)-1;
|
||||
HighlightMin = HighlightMax = (size_t)-1;
|
||||
PreviewEndianess = 0;
|
||||
PreviewDataType = ImGuiDataType_S32;
|
||||
}
|
||||
|
||||
void GotoAddrAndHighlight(size_t addr_min, size_t addr_max)
|
||||
{
|
||||
GotoAddr = addr_min;
|
||||
HighlightMin = addr_min;
|
||||
HighlightMax = addr_max;
|
||||
}
|
||||
|
||||
struct Sizes
|
||||
{
|
||||
int AddrDigitsCount;
|
||||
float LineHeight;
|
||||
float GlyphWidth;
|
||||
float HexCellWidth;
|
||||
float SpacingBetweenMidCols;
|
||||
float PosHexStart;
|
||||
float PosHexEnd;
|
||||
float PosAsciiStart;
|
||||
float PosAsciiEnd;
|
||||
float WindowWidth;
|
||||
|
||||
Sizes() { memset(this, 0, sizeof(*this)); }
|
||||
};
|
||||
|
||||
void CalcSizes(Sizes& s, size_t mem_size, size_t base_display_addr)
|
||||
{
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
s.AddrDigitsCount = OptAddrDigitsCount;
|
||||
if (s.AddrDigitsCount == 0)
|
||||
for (size_t n = base_display_addr + mem_size - 1; n > 0; n >>= 4)
|
||||
s.AddrDigitsCount++;
|
||||
s.LineHeight = ImGui::GetTextLineHeight();
|
||||
s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space
|
||||
s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere
|
||||
s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing
|
||||
s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth;
|
||||
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols);
|
||||
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd;
|
||||
if (OptShowAscii)
|
||||
{
|
||||
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1;
|
||||
if (OptMidColsCount > 0)
|
||||
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
|
||||
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth;
|
||||
}
|
||||
s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
|
||||
}
|
||||
|
||||
// Standalone Memory Editor window
|
||||
void DrawWindow(const char* title, void* mem_data, size_t mem_size, size_t base_display_addr = 0x0000)
|
||||
{
|
||||
Sizes s;
|
||||
CalcSizes(s, mem_size, base_display_addr);
|
||||
ImGui::SetNextWindowSize(ImVec2(s.WindowWidth, s.WindowWidth * 0.60f), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX));
|
||||
|
||||
Open = true;
|
||||
if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar))
|
||||
{
|
||||
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
|
||||
ImGui::OpenPopup("context");
|
||||
DrawContents(mem_data, mem_size, base_display_addr);
|
||||
if (ContentsWidthChanged)
|
||||
{
|
||||
CalcSizes(s, mem_size, base_display_addr);
|
||||
ImGui::SetWindowSize(ImVec2(s.WindowWidth, ImGui::GetWindowSize().y));
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// Memory Editor contents only
|
||||
void DrawContents(void* mem_data_void, size_t mem_size, size_t base_display_addr = 0x0000)
|
||||
{
|
||||
if (Cols < 1)
|
||||
Cols = 1;
|
||||
|
||||
ImU8* mem_data = (ImU8*)mem_data_void;
|
||||
Sizes s;
|
||||
CalcSizes(s, mem_size, base_display_addr);
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
|
||||
// We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window.
|
||||
// This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move.
|
||||
const float height_separator = style.ItemSpacing.y;
|
||||
float footer_height = OptFooterExtraHeight;
|
||||
if (OptShowOptions)
|
||||
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
|
||||
if (OptShowDataPreview)
|
||||
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3;
|
||||
ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||
|
||||
// We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function.
|
||||
const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
|
||||
ImGuiListClipper clipper;
|
||||
clipper.Begin(line_total_count, s.LineHeight);
|
||||
|
||||
bool data_next = false;
|
||||
|
||||
if (ReadOnly || DataEditingAddr >= mem_size)
|
||||
DataEditingAddr = (size_t)-1;
|
||||
if (DataPreviewAddr >= mem_size)
|
||||
DataPreviewAddr = (size_t)-1;
|
||||
|
||||
size_t preview_data_type_size = OptShowDataPreview ? DataTypeGetSize(PreviewDataType) : 0;
|
||||
|
||||
size_t data_editing_addr_next = (size_t)-1;
|
||||
if (DataEditingAddr != (size_t)-1)
|
||||
{
|
||||
// Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered)
|
||||
if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; }
|
||||
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; }
|
||||
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; }
|
||||
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; }
|
||||
}
|
||||
|
||||
// Draw vertical separator
|
||||
ImVec2 window_pos = ImGui::GetWindowPos();
|
||||
if (OptShowAscii)
|
||||
draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
|
||||
|
||||
const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text;
|
||||
|
||||
const char* format_address = OptUpperCaseHex ? "%0*" _PRISizeT "X: " : "%0*" _PRISizeT "x: ";
|
||||
const char* format_data = OptUpperCaseHex ? "%0*" _PRISizeT "X" : "%0*" _PRISizeT "x";
|
||||
const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x";
|
||||
const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
|
||||
|
||||
while (clipper.Step())
|
||||
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines
|
||||
{
|
||||
size_t addr = (size_t)(line_i * Cols);
|
||||
ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr);
|
||||
|
||||
// Draw Hexadecimal
|
||||
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
|
||||
{
|
||||
float byte_pos_x = s.PosHexStart + s.HexCellWidth * n;
|
||||
if (OptMidColsCount > 0)
|
||||
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
|
||||
ImGui::SameLine(byte_pos_x);
|
||||
|
||||
// Draw highlight
|
||||
bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
|
||||
bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr));
|
||||
bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size);
|
||||
if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
|
||||
{
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
float highlight_width = s.GlyphWidth * 2;
|
||||
bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1)));
|
||||
if (is_next_byte_highlighted || (n + 1 == Cols))
|
||||
{
|
||||
highlight_width = s.HexCellWidth;
|
||||
if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0)
|
||||
highlight_width += s.SpacingBetweenMidCols;
|
||||
}
|
||||
draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor);
|
||||
}
|
||||
|
||||
if (DataEditingAddr == addr)
|
||||
{
|
||||
// Display text input on current byte
|
||||
bool data_write = false;
|
||||
ImGui::PushID((void*)addr);
|
||||
if (DataEditingTakeFocus)
|
||||
{
|
||||
ImGui::SetKeyboardFocusHere(0);
|
||||
sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr);
|
||||
sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]);
|
||||
}
|
||||
struct UserData
|
||||
{
|
||||
// FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here.
|
||||
static int Callback(ImGuiInputTextCallbackData* data)
|
||||
{
|
||||
UserData* user_data = (UserData*)data->UserData;
|
||||
if (!data->HasSelection())
|
||||
user_data->CursorPos = data->CursorPos;
|
||||
if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen)
|
||||
{
|
||||
// When not editing a byte, always refresh its InputText content pulled from underlying memory data
|
||||
// (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there)
|
||||
data->DeleteChars(0, data->BufTextLen);
|
||||
data->InsertChars(0, user_data->CurrentBufOverwrite);
|
||||
data->SelectionStart = 0;
|
||||
data->SelectionEnd = 2;
|
||||
data->CursorPos = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
char CurrentBufOverwrite[3]; // Input
|
||||
int CursorPos; // Output
|
||||
};
|
||||
UserData user_data;
|
||||
user_data.CursorPos = -1;
|
||||
sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]);
|
||||
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
|
||||
#if IMGUI_VERSION_NUM >= 18104
|
||||
flags |= ImGuiInputTextFlags_AlwaysOverwrite;
|
||||
#else
|
||||
flags |= ImGuiInputTextFlags_AlwaysInsertMode;
|
||||
#endif
|
||||
ImGui::SetNextItemWidth(s.GlyphWidth * 2);
|
||||
if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, UserData::Callback, &user_data))
|
||||
data_write = data_next = true;
|
||||
else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
|
||||
DataEditingAddr = data_editing_addr_next = (size_t)-1;
|
||||
DataEditingTakeFocus = false;
|
||||
if (user_data.CursorPos >= 2)
|
||||
data_write = data_next = true;
|
||||
if (data_editing_addr_next != (size_t)-1)
|
||||
data_write = data_next = false;
|
||||
unsigned int data_input_value = 0;
|
||||
if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1)
|
||||
{
|
||||
if (WriteFn)
|
||||
WriteFn(mem_data, addr, (ImU8)data_input_value);
|
||||
else
|
||||
mem_data[addr] = (ImU8)data_input_value;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
else
|
||||
{
|
||||
// NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on.
|
||||
ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr];
|
||||
|
||||
if (OptShowHexII)
|
||||
{
|
||||
if ((b >= 32 && b < 128))
|
||||
ImGui::Text(".%c ", b);
|
||||
else if (b == 0xFF && OptGreyOutZeroes)
|
||||
ImGui::TextDisabled("## ");
|
||||
else if (b == 0x00)
|
||||
ImGui::Text(" ");
|
||||
else
|
||||
ImGui::Text(format_byte_space, b);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (b == 0 && OptGreyOutZeroes)
|
||||
ImGui::TextDisabled("00 ");
|
||||
else
|
||||
ImGui::Text(format_byte_space, b);
|
||||
}
|
||||
if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0))
|
||||
{
|
||||
DataEditingTakeFocus = true;
|
||||
data_editing_addr_next = addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (OptShowAscii)
|
||||
{
|
||||
// Draw ASCII values
|
||||
ImGui::SameLine(s.PosAsciiStart);
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
addr = line_i * Cols;
|
||||
ImGui::PushID(line_i);
|
||||
if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight)))
|
||||
{
|
||||
DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth);
|
||||
DataEditingTakeFocus = true;
|
||||
}
|
||||
ImGui::PopID();
|
||||
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
|
||||
{
|
||||
if (addr == DataEditingAddr)
|
||||
{
|
||||
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg));
|
||||
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
|
||||
}
|
||||
unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr];
|
||||
char display_c = (c < 32 || c >= 128) ? '.' : c;
|
||||
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
|
||||
pos.x += s.GlyphWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::EndChild();
|
||||
|
||||
// Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child)
|
||||
ImGui::SetCursorPosX(s.WindowWidth);
|
||||
|
||||
if (data_next && DataEditingAddr + 1 < mem_size)
|
||||
{
|
||||
DataEditingAddr = DataPreviewAddr = DataEditingAddr + 1;
|
||||
DataEditingTakeFocus = true;
|
||||
}
|
||||
else if (data_editing_addr_next != (size_t)-1)
|
||||
{
|
||||
DataEditingAddr = DataPreviewAddr = data_editing_addr_next;
|
||||
DataEditingTakeFocus = true;
|
||||
}
|
||||
|
||||
const bool lock_show_data_preview = OptShowDataPreview;
|
||||
if (OptShowOptions)
|
||||
{
|
||||
ImGui::Separator();
|
||||
DrawOptionsLine(s, mem_data, mem_size, base_display_addr);
|
||||
}
|
||||
|
||||
if (lock_show_data_preview)
|
||||
{
|
||||
ImGui::Separator();
|
||||
DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr)
|
||||
{
|
||||
IM_UNUSED(mem_data);
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
|
||||
|
||||
// Options menu
|
||||
if (ImGui::Button("Options"))
|
||||
ImGui::OpenPopup("context");
|
||||
if (ImGui::BeginPopup("context"))
|
||||
{
|
||||
ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f);
|
||||
if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; }
|
||||
ImGui::Checkbox("Show Data Preview", &OptShowDataPreview);
|
||||
ImGui::Checkbox("Show HexII", &OptShowHexII);
|
||||
if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) { ContentsWidthChanged = true; }
|
||||
ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes);
|
||||
ImGui::Checkbox("Uppercase Hex", &OptUpperCaseHex);
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth((s.AddrDigitsCount + 1) * s.GlyphWidth + style.FramePadding.x * 2.0f);
|
||||
if (ImGui::InputText("##addr", AddrInputBuf, IM_ARRAYSIZE(AddrInputBuf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue))
|
||||
{
|
||||
size_t goto_addr;
|
||||
if (sscanf(AddrInputBuf, "%" _PRISizeT "X", &goto_addr) == 1)
|
||||
{
|
||||
GotoAddr = goto_addr - base_display_addr;
|
||||
HighlightMin = HighlightMax = (size_t)-1;
|
||||
}
|
||||
}
|
||||
|
||||
if (GotoAddr != (size_t)-1)
|
||||
{
|
||||
if (GotoAddr < mem_size)
|
||||
{
|
||||
ImGui::BeginChild("##scrolling");
|
||||
ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight());
|
||||
ImGui::EndChild();
|
||||
DataEditingAddr = DataPreviewAddr = GotoAddr;
|
||||
DataEditingTakeFocus = true;
|
||||
}
|
||||
GotoAddr = (size_t)-1;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr)
|
||||
{
|
||||
IM_UNUSED(base_display_addr);
|
||||
ImU8* mem_data = (ImU8*)mem_data_void;
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("Preview as:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
|
||||
if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
|
||||
{
|
||||
for (int n = 0; n < ImGuiDataType_COUNT; n++)
|
||||
if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n))
|
||||
PreviewDataType = (ImGuiDataType)n;
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
|
||||
ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0");
|
||||
|
||||
char buf[128] = "";
|
||||
float x = s.GlyphWidth * 6.0f;
|
||||
bool has_value = DataPreviewAddr != (size_t)-1;
|
||||
if (has_value)
|
||||
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Dec, buf, (size_t)IM_ARRAYSIZE(buf));
|
||||
ImGui::Text("Dec"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
|
||||
if (has_value)
|
||||
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Hex, buf, (size_t)IM_ARRAYSIZE(buf));
|
||||
ImGui::Text("Hex"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
|
||||
if (has_value)
|
||||
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Bin, buf, (size_t)IM_ARRAYSIZE(buf));
|
||||
buf[IM_ARRAYSIZE(buf) - 1] = 0;
|
||||
ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
|
||||
}
|
||||
|
||||
// Utilities for Data Preview
|
||||
const char* DataTypeGetDesc(ImGuiDataType data_type) const
|
||||
{
|
||||
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" };
|
||||
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
|
||||
return descs[data_type];
|
||||
}
|
||||
|
||||
size_t DataTypeGetSize(ImGuiDataType data_type) const
|
||||
{
|
||||
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) };
|
||||
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
|
||||
return sizes[data_type];
|
||||
}
|
||||
|
||||
const char* DataFormatGetDesc(DataFormat data_format) const
|
||||
{
|
||||
const char* descs[] = { "Bin", "Dec", "Hex" };
|
||||
IM_ASSERT(data_format >= 0 && data_format < DataFormat_COUNT);
|
||||
return descs[data_format];
|
||||
}
|
||||
|
||||
bool IsBigEndian() const
|
||||
{
|
||||
uint16_t x = 1;
|
||||
char c[2];
|
||||
memcpy(c, &x, 2);
|
||||
return c[0] != 0;
|
||||
}
|
||||
|
||||
static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
|
||||
{
|
||||
if (is_little_endian)
|
||||
{
|
||||
uint8_t* dst = (uint8_t*)_dst;
|
||||
uint8_t* src = (uint8_t*)_src + s - 1;
|
||||
for (int i = 0, n = (int)s; i < n; ++i)
|
||||
memcpy(dst++, src--, 1);
|
||||
return _dst;
|
||||
}
|
||||
else
|
||||
{
|
||||
return memcpy(_dst, _src, s);
|
||||
}
|
||||
}
|
||||
|
||||
static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
|
||||
{
|
||||
if (is_little_endian)
|
||||
{
|
||||
return memcpy(_dst, _src, s);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t* dst = (uint8_t*)_dst;
|
||||
uint8_t* src = (uint8_t*)_src + s - 1;
|
||||
for (int i = 0, n = (int)s; i < n; ++i)
|
||||
memcpy(dst++, src--, 1);
|
||||
return _dst;
|
||||
}
|
||||
}
|
||||
|
||||
void* EndianessCopy(void* dst, void* src, size_t size) const
|
||||
{
|
||||
static void* (*fp)(void*, void*, size_t, int) = NULL;
|
||||
if (fp == NULL)
|
||||
fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian;
|
||||
return fp(dst, src, size, PreviewEndianess);
|
||||
}
|
||||
|
||||
const char* FormatBinary(const uint8_t* buf, int width) const
|
||||
{
|
||||
IM_ASSERT(width <= 64);
|
||||
size_t out_n = 0;
|
||||
static char out_buf[64 + 8 + 1];
|
||||
int n = width / 8;
|
||||
for (int j = n - 1; j >= 0; --j)
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
out_buf[out_n++] = (buf[j] & (1 << (7 - i))) ? '1' : '0';
|
||||
out_buf[out_n++] = ' ';
|
||||
}
|
||||
IM_ASSERT(out_n < IM_ARRAYSIZE(out_buf));
|
||||
out_buf[out_n] = 0;
|
||||
return out_buf;
|
||||
}
|
||||
|
||||
// [Internal]
|
||||
void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const
|
||||
{
|
||||
uint8_t buf[8];
|
||||
size_t elem_size = DataTypeGetSize(data_type);
|
||||
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
|
||||
if (ReadFn)
|
||||
for (int i = 0, n = (int)size; i < n; ++i)
|
||||
buf[i] = ReadFn(mem_data, addr + i);
|
||||
else
|
||||
memcpy(buf, mem_data + addr, size);
|
||||
|
||||
if (data_format == DataFormat_Bin)
|
||||
{
|
||||
uint8_t binbuf[8];
|
||||
EndianessCopy(binbuf, buf, size);
|
||||
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
|
||||
return;
|
||||
}
|
||||
|
||||
out_buf[0] = 0;
|
||||
switch (data_type)
|
||||
{
|
||||
case ImGuiDataType_S8:
|
||||
{
|
||||
int8_t int8 = 0;
|
||||
EndianessCopy(&int8, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_U8:
|
||||
{
|
||||
uint8_t uint8 = 0;
|
||||
EndianessCopy(&uint8, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_S16:
|
||||
{
|
||||
int16_t int16 = 0;
|
||||
EndianessCopy(&int16, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_U16:
|
||||
{
|
||||
uint16_t uint16 = 0;
|
||||
EndianessCopy(&uint16, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_S32:
|
||||
{
|
||||
int32_t int32 = 0;
|
||||
EndianessCopy(&int32, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_U32:
|
||||
{
|
||||
uint32_t uint32 = 0;
|
||||
EndianessCopy(&uint32, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_S64:
|
||||
{
|
||||
int64_t int64 = 0;
|
||||
EndianessCopy(&int64, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_U64:
|
||||
{
|
||||
uint64_t uint64 = 0;
|
||||
EndianessCopy(&uint64, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_Float:
|
||||
{
|
||||
float float32 = 0.0f;
|
||||
EndianessCopy(&float32, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_Double:
|
||||
{
|
||||
double float64 = 0.0;
|
||||
EndianessCopy(&float64, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_COUNT:
|
||||
break;
|
||||
} // Switch
|
||||
IM_ASSERT(0); // Shouldn't reach
|
||||
}
|
||||
};
|
||||
|
||||
#undef _PRISizeT
|
||||
#undef ImSnprintf
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
1
src/lib/snes_spc
Submodule
1
src/lib/snes_spc
Submodule
Submodule src/lib/snes_spc added at ec8ee2bbe3
1
src/lib/sneshacking
Submodule
1
src/lib/sneshacking
Submodule
Submodule src/lib/sneshacking added at 3cf5ab8681
BIN
src/yaze.ico
Normal file
BIN
src/yaze.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 318 B |
1
src/yaze.rc
Normal file
1
src/yaze.rc
Normal file
@@ -0,0 +1 @@
|
||||
IDI_ICON1 ICON DISCARDABLE "yaze.ico"
|
||||
BIN
src/yaze.res
Normal file
BIN
src/yaze.res
Normal file
Binary file not shown.
Reference in New Issue
Block a user