mirror of
https://github.com/Zelda64Recomp/Zelda64Recomp
synced 2026-08-27 08:47:11 -04:00
recreate original launcher structure via recompui + prep multigame ui
This commit is contained in:
@@ -141,6 +141,7 @@ add_executable(Zelda64Recompiled)
|
||||
|
||||
set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/main/launcher_animation.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/launcher.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/main.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/support.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/register_overlays.cpp
|
||||
|
||||
@@ -5,10 +5,25 @@
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "librecomp/game.hpp"
|
||||
|
||||
namespace zelda64 {
|
||||
enum Game {
|
||||
OoT,
|
||||
MM
|
||||
};
|
||||
|
||||
constexpr zelda64::Game default_game = Game::MM;
|
||||
|
||||
zelda64::Game get_current_game();
|
||||
zelda64::Game swap_current_game();
|
||||
|
||||
const recomp::GameEntry &get_game_entry(Game game);
|
||||
|
||||
void quicksave_save();
|
||||
void quicksave_load();
|
||||
std::vector<uint8_t> decompress_mm(std::span<const uint8_t> compressed_rom);
|
||||
std::vector<uint8_t> decompress_oot(std::span<const uint8_t> compressed_rom);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "recompui/recompui.h"
|
||||
|
||||
namespace zelda64 {
|
||||
void on_launcher_init(recompui::LauncherMenu *menu);
|
||||
|
||||
void launcher_animation_setup(recompui::LauncherMenu *menu);
|
||||
void launcher_animation_update(recompui::LauncherMenu *menu);
|
||||
}
|
||||
|
||||
@@ -64,11 +64,17 @@ constexpr uint32_t byteswap(uint32_t val) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Produces a decompressed MM rom. This is only needed because the game has compressed code.
|
||||
|
||||
// Produces a decompressed rom. This is only needed because the game has compressed code.
|
||||
// For other recomps using this repo as an example, you can omit the decompression routine and
|
||||
// set the corresponding fields in the GameEntry if the game doesn't have compressed code,
|
||||
// even if it does have compressed data.
|
||||
std::vector<uint8_t> zelda64::decompress_mm(std::span<const uint8_t> compressed_rom) {
|
||||
static std::vector<uint8_t> decompress(
|
||||
std::span<const uint8_t> compressed_rom,
|
||||
const std::u8string &header_code,
|
||||
const size_t dma_data_rom_addr,
|
||||
const size_t decompressed_size
|
||||
) {
|
||||
// Sanity check the rom size and header. These should already be correct from the runtime's check,
|
||||
// but it should prevent this file from accidentally being copied to another recomp.
|
||||
if (compressed_rom.size() != 0x2000000) {
|
||||
@@ -76,7 +82,7 @@ std::vector<uint8_t> zelda64::decompress_mm(std::span<const uint8_t> compressed_
|
||||
return {};
|
||||
}
|
||||
|
||||
if (compressed_rom[0x3B] != 'N' || compressed_rom[0x3C] != 'Z' || compressed_rom[0x3D] != 'S' || compressed_rom[0x3E] != 'E') {
|
||||
if (compressed_rom[0x3B] != header_code[0] || compressed_rom[0x3C] != header_code[1] || compressed_rom[0x3D] != header_code[2] || compressed_rom[0x3E] != header_code[3]) {
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
@@ -98,10 +104,8 @@ std::vector<uint8_t> zelda64::decompress_mm(std::span<const uint8_t> compressed_
|
||||
DmaDataEntry cur_entry{};
|
||||
size_t cur_entry_index = 0;
|
||||
|
||||
constexpr size_t dma_data_rom_addr = 0x1A500;
|
||||
|
||||
std::vector<uint8_t> ret{};
|
||||
ret.resize(0x2F00000);
|
||||
ret.resize(decompressed_size);
|
||||
|
||||
size_t content_end = 0;
|
||||
|
||||
@@ -166,3 +170,11 @@ std::vector<uint8_t> zelda64::decompress_mm(std::span<const uint8_t> compressed_
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> zelda64::decompress_mm(std::span<const uint8_t> compressed_rom) {
|
||||
return decompress(compressed_rom, u8"NZSE", 0x1A500, 0x2F00000);
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> zelda64::decompress_oot(std::span<const uint8_t> compressed_rom) {
|
||||
return decompress(compressed_rom, u8"CZLE", 0x7430, 0x347F000);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
#include "recompui/recompui.h"
|
||||
#include "zelda_game.h"
|
||||
#include "zelda_launcher.h"
|
||||
|
||||
// UI Components
|
||||
namespace {
|
||||
using namespace recompui;
|
||||
|
||||
class SubtitleTitleButton : public Element {
|
||||
protected:
|
||||
Style selected_style;
|
||||
Style hover_style;
|
||||
Style focus_style;
|
||||
std::function<void()> pressed_callback;
|
||||
bool selected = false;
|
||||
bool right = false;
|
||||
bool soon = false;
|
||||
Element *exposed_contents = nullptr;
|
||||
|
||||
Label *title = nullptr;
|
||||
Label *subtitle = nullptr;
|
||||
Element *soon_label_wrap = nullptr;
|
||||
Label *soon_label = nullptr;
|
||||
Label *soon_label_tm = nullptr;
|
||||
int soon_opacity = 0;
|
||||
int soon_opacity_goal = 0;
|
||||
|
||||
// Element overrides.
|
||||
virtual void process_event(const Event &e) override {
|
||||
switch (e.type) {
|
||||
case EventType::Click:
|
||||
if (!soon && !selected && pressed_callback) {
|
||||
pressed_callback();
|
||||
}
|
||||
break;
|
||||
case EventType::Hover:
|
||||
if (soon) {
|
||||
soon_opacity_goal = std::get<EventHover>(e.variant).active ? 255 : 0;
|
||||
queue_update();
|
||||
} else {
|
||||
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && !selected);
|
||||
}
|
||||
|
||||
break;
|
||||
case EventType::Focus:
|
||||
if (!soon) {
|
||||
set_style_enabled(focus_state, std::get<EventFocus>(e.variant).active);
|
||||
}
|
||||
break;
|
||||
case EventType::Update:
|
||||
if (soon && soon_opacity_goal != soon_opacity) {
|
||||
int rate = soon_opacity_goal < soon_opacity ? -1 : 1;
|
||||
soon_opacity += rate;
|
||||
soon_label_tm->set_color(theme::color::TextDim, soon_opacity);
|
||||
queue_update();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unknown event type.");
|
||||
break;
|
||||
}
|
||||
};
|
||||
std::string_view get_type_name() override { return "SubtitleTitleButton"; }
|
||||
public:
|
||||
SubtitleTitleButton(ResourceId rid, Element *parent, const std::string &text, bool right_align = false, bool soon = false) : Element(rid, parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus, EventType::Update), "button", false) {
|
||||
auto context = recompui::get_current_context();
|
||||
this->soon = soon;
|
||||
right = right_align;
|
||||
set_display(Display::Block);
|
||||
set_position(Position::Relative);
|
||||
set_width_auto();
|
||||
set_height_auto();
|
||||
set_padding(0);
|
||||
set_background_color(theme::color::Transparent);
|
||||
set_color(theme::color::TextDim);
|
||||
set_text_align(right ? TextAlign::Right : TextAlign::Left);
|
||||
|
||||
set_opacity(0.5);
|
||||
|
||||
selected_style.set_color(theme::color::Text);
|
||||
selected_style.set_cursor(Cursor::None);
|
||||
selected_style.set_opacity(1);
|
||||
hover_style.set_color(theme::color::Primary);
|
||||
hover_style.set_opacity(1);
|
||||
focus_style.set_color(theme::color::Primary);
|
||||
focus_style.set_opacity(1);
|
||||
|
||||
if (!soon) {
|
||||
enable_focus();
|
||||
set_cursor(Cursor::Pointer);
|
||||
}
|
||||
add_style(&selected_style, checked_state);
|
||||
add_style(&hover_style, hover_state);
|
||||
add_style(&focus_style, focus_state);
|
||||
title = context.create_element<Label>(this, "Zelda 64: Recompiled", theme::Typography::Header3);
|
||||
title->set_margin_bottom(12);
|
||||
subtitle = context.create_element<Label>(this, text, theme::Typography::Header1);
|
||||
|
||||
if (soon) {
|
||||
soon_label_wrap = context.create_element<Element>(this, 0, "div", false);
|
||||
soon_label_wrap->set_color(theme::color::TextDim, 128);
|
||||
soon_label_wrap->set_margin_top(16);
|
||||
{
|
||||
soon_label = context.create_element<Label>(soon_label_wrap, "COMING SOON™", theme::Typography::LabelSM);
|
||||
soon_label->set_display(Display::Inline);
|
||||
soon_label_tm = context.create_element<Label>(soon_label_wrap, "™", theme::Typography::LabelSM);
|
||||
soon_label_tm->set_display(Display::Inline);
|
||||
soon_label_tm->set_color(theme::color::TextDim, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
void set_pressed_callback(std::function<void()> callback) { pressed_callback = callback; };
|
||||
Style* get_selected_style() { return &selected_style; }
|
||||
Style* get_hover_style() { return &hover_style; }
|
||||
Style* get_focus_style() { return &focus_style; }
|
||||
void set_selected(bool sel) {
|
||||
selected = sel;
|
||||
set_style_enabled(checked_state, selected);
|
||||
if (selected) exposed_contents->display_show();
|
||||
else exposed_contents->display_hide();
|
||||
}
|
||||
void set_exposed_contents(Element *el) { exposed_contents = el; }
|
||||
};
|
||||
|
||||
class GameHalf {
|
||||
private:
|
||||
Element *root;
|
||||
Element *title_wrapper;
|
||||
Element *content_wrapper;
|
||||
SubtitleTitleButton *title_button;
|
||||
zelda64::Game game;
|
||||
std::string name;
|
||||
std::function<void()> on_change_game;
|
||||
bool right = false;
|
||||
bool soon = false;
|
||||
|
||||
void style_vertical_split() {
|
||||
root->set_display(Display::Flex);
|
||||
root->set_position(Position::Absolute);
|
||||
root->set_top(0);
|
||||
root->set_bottom(0);
|
||||
root->set_flex_direction(FlexDirection::Column);
|
||||
root->set_justify_content(JustifyContent::SpaceBetween);
|
||||
// Reset styles set in LauncherMenu's constructor
|
||||
root->set_height_auto();
|
||||
root->set_width_auto();
|
||||
root->set_as_navigation_container(NavigationType::Vertical);
|
||||
|
||||
if (right) {
|
||||
root->set_right(0);
|
||||
root->set_left(50, Unit::Percent);
|
||||
root->set_align_items(AlignItems::FlexEnd);
|
||||
} else {
|
||||
root->set_right(50, Unit::Percent);
|
||||
root->set_left(0);
|
||||
root->set_align_items(AlignItems::FlexStart);
|
||||
}
|
||||
}
|
||||
|
||||
void add_title_wrapper() {
|
||||
auto context = get_current_context();
|
||||
title_wrapper = context.create_element<Element>(root, 0, "div", false);
|
||||
|
||||
title_wrapper->set_flex(1, 1);
|
||||
title_wrapper->set_flex_basis_auto();
|
||||
title_wrapper->set_height_auto();
|
||||
title_wrapper->set_width_auto();
|
||||
title_wrapper->set_padding_top(96);
|
||||
|
||||
if (right) {
|
||||
title_wrapper->set_padding_right(96);
|
||||
title_wrapper->set_padding_left(0);
|
||||
} else {
|
||||
title_wrapper->set_padding_right(0);
|
||||
title_wrapper->set_padding_left(96);
|
||||
}
|
||||
}
|
||||
|
||||
void add_content_wrapper() {
|
||||
auto context = get_current_context();
|
||||
content_wrapper = context.create_element<Element>(root, 0, "div", false);
|
||||
|
||||
content_wrapper->set_display(Display::Flex);
|
||||
content_wrapper->set_position(Position::Relative);
|
||||
content_wrapper->set_flex(1, 1);
|
||||
content_wrapper->set_flex_basis(100, Unit::Percent);
|
||||
content_wrapper->set_height_auto();
|
||||
content_wrapper->set_width(100, Unit::Percent);
|
||||
content_wrapper->set_flex_direction(FlexDirection::Column);
|
||||
content_wrapper->set_padding(32);
|
||||
content_wrapper->set_justify_content(JustifyContent::FlexEnd);
|
||||
|
||||
if (right) {
|
||||
content_wrapper->set_align_items(AlignItems::FlexEnd);
|
||||
} else {
|
||||
content_wrapper->set_align_items(AlignItems::FlexStart);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
GameHalf(zelda64::Game game, const std::string &name, bool right = false, bool soon = false) : game(game), name(name), right(right), soon(soon) {};
|
||||
|
||||
void init(Element *root_element) {
|
||||
root = root_element;
|
||||
root->clear_children();
|
||||
style_vertical_split();
|
||||
auto context = recompui::get_current_context();
|
||||
|
||||
add_title_wrapper();
|
||||
title_button = context.create_element<SubtitleTitleButton>(title_wrapper, name, right, soon);
|
||||
add_content_wrapper();
|
||||
|
||||
const recomp::GameEntry &game_entry = zelda64::get_game_entry(game);
|
||||
auto game_options_menu = context.create_element<GameOptionsMenu>(content_wrapper,
|
||||
game_entry.game_id,
|
||||
game_entry.mod_game_id,
|
||||
game_entry.display_name,
|
||||
game_entry.thumbnail_bytes,
|
||||
right ? recompui::GameOptionsMenuLayout::Right : recompui::GameOptionsMenuLayout::Left
|
||||
);
|
||||
game_options_menu->set_as_navigation_container(recompui::NavigationType::Vertical);
|
||||
game_options_menu->add_default_options();
|
||||
|
||||
title_button->set_exposed_contents(game_options_menu);
|
||||
}
|
||||
|
||||
void derive_selected() {
|
||||
title_button->set_selected(zelda64::get_current_game() == game);
|
||||
}
|
||||
|
||||
void set_on_changed_game(std::function<void()> on_change_game_callback) {
|
||||
on_change_game = on_change_game_callback;
|
||||
title_button->set_pressed_callback([this]() {
|
||||
on_change_game();
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
static GameHalf oot_half{zelda64::Game::OoT, "Ocarina of Time", false, true};
|
||||
static GameHalf mm_half{zelda64::Game::MM, "Majora's Mask", true};
|
||||
|
||||
|
||||
void zelda64::on_launcher_init(recompui::LauncherMenu *menu) {
|
||||
menu->remove_default_title();
|
||||
menu->set_as_navigation_container(recompui::NavigationType::Horizontal);
|
||||
|
||||
// changing the way these are used to be split into vertical sections (each game)
|
||||
|
||||
oot_half.init(menu->get_background_container());
|
||||
mm_half.init(menu->get_menu_container());
|
||||
|
||||
recompui::update_game_mod_id(get_game_entry(get_current_game()).mod_game_id);
|
||||
|
||||
oot_half.set_on_changed_game([]() {
|
||||
swap_current_game();
|
||||
oot_half.derive_selected();
|
||||
mm_half.derive_selected();
|
||||
});
|
||||
mm_half.set_on_changed_game([]() {
|
||||
swap_current_game();
|
||||
oot_half.derive_selected();
|
||||
mm_half.derive_selected();
|
||||
});
|
||||
|
||||
oot_half.derive_selected();
|
||||
mm_half.derive_selected();
|
||||
|
||||
// zelda64::launcher_animation_setup(menu);
|
||||
}
|
||||
+32
-17
@@ -366,6 +366,20 @@ gpr get_entrypoint_address();
|
||||
|
||||
// array of supported GameEntry objects
|
||||
std::vector<recomp::GameEntry> supported_games = {
|
||||
{
|
||||
.rom_hash = 0x9C427099CC30D135ULL,
|
||||
.internal_name = "THE LEGEND OF ZELDA",
|
||||
.display_name = "Ocarina of Time",
|
||||
.game_id = u8"oot.n64.us.1.0",
|
||||
.mod_game_id = "oot",
|
||||
.save_type = recomp::SaveType::Sram,
|
||||
.thumbnail_bytes = std::span<const char>(icon_bytes),
|
||||
.is_enabled = false,
|
||||
.decompression_routine = zelda64::decompress_oot,
|
||||
.has_compressed_code = true,
|
||||
.entrypoint_address = get_entrypoint_address(),
|
||||
.entrypoint = recomp_entrypoint,
|
||||
},
|
||||
{
|
||||
.rom_hash = 0xEF18B4A9E2386169ULL,
|
||||
.internal_name = "ZELDA MAJORA'S MASK",
|
||||
@@ -382,6 +396,22 @@ std::vector<recomp::GameEntry> supported_games = {
|
||||
},
|
||||
};
|
||||
|
||||
const recomp::GameEntry &zelda64::get_game_entry(zelda64::Game game) {
|
||||
return supported_games[game];
|
||||
}
|
||||
|
||||
static zelda64::Game current_game = zelda64::default_game;
|
||||
|
||||
zelda64::Game zelda64::get_current_game() {
|
||||
return current_game;
|
||||
}
|
||||
|
||||
zelda64::Game zelda64::swap_current_game() {
|
||||
current_game = current_game == zelda64::Game::MM ? zelda64::Game::OoT : zelda64::Game::MM;
|
||||
recompui::update_game_mod_id(supported_games[current_game].mod_game_id);
|
||||
return current_game;
|
||||
}
|
||||
|
||||
// TODO: move somewhere else
|
||||
namespace zelda64 {
|
||||
std::string get_game_thread_name(const OSThread* t) {
|
||||
@@ -598,21 +628,6 @@ void reorder_texture_pack(recomp::mods::ModContext&) {
|
||||
recompui::renderer::trigger_texture_pack_update();
|
||||
}
|
||||
|
||||
void on_launcher_init(recompui::LauncherMenu *menu) {
|
||||
auto game_options_menu = menu->init_game_options_menu(
|
||||
supported_games[0].game_id,
|
||||
supported_games[0].mod_game_id,
|
||||
supported_games[0].display_name,
|
||||
supported_games[0].thumbnail_bytes,
|
||||
recompui::GameOptionsMenuLayout::Right
|
||||
);
|
||||
game_options_menu->add_default_options();
|
||||
|
||||
recompui::Element *menu_container = menu->get_menu_container();
|
||||
menu->remove_default_title();
|
||||
zelda64::launcher_animation_setup(menu);
|
||||
}
|
||||
|
||||
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -737,8 +752,8 @@ int main(int argc, char** argv) {
|
||||
|
||||
zelda64::init_config();
|
||||
|
||||
recompui::register_launcher_init_callback(on_launcher_init);
|
||||
recompui::register_launcher_update_callback(zelda64::launcher_animation_update);
|
||||
recompui::register_launcher_init_callback(zelda64::on_launcher_init);
|
||||
// recompui::register_launcher_update_callback(zelda64::launcher_animation_update);
|
||||
|
||||
recomp::rsp::callbacks_t rsp_callbacks{
|
||||
.get_rsp_microcode = get_rsp_microcode,
|
||||
|
||||
Reference in New Issue
Block a user