mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2025-02-07 07:43:23 +01:00
Modding function hooks (#530)
This commit updates the runtime for function hooking and implements any recomp-side functionality needed for them to fully hook (namely ROM decompression). For more details, see the relevant N64Recomp and N64ModernRuntime PRs.
This commit is contained in:
parent
4945172ead
commit
c4c0f928b6
2
.github/workflows/validate.yml
vendored
2
.github/workflows/validate.yml
vendored
@ -9,7 +9,7 @@ on:
|
|||||||
N64RECOMP_COMMIT:
|
N64RECOMP_COMMIT:
|
||||||
type: string
|
type: string
|
||||||
required: false
|
required: false
|
||||||
default: 'fc696046da3e703450559154d9370ca74c197f8b'
|
default: 'b18e0ca2dd359d62dcc019771f0ccc4a1302bd03'
|
||||||
DXC_CHECKSUM:
|
DXC_CHECKSUM:
|
||||||
type: string
|
type: string
|
||||||
required: false
|
required: false
|
||||||
|
@ -150,6 +150,7 @@ set (SOURCES
|
|||||||
${CMAKE_SOURCE_DIR}/src/game/debug.cpp
|
${CMAKE_SOURCE_DIR}/src/game/debug.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/game/quicksaving.cpp
|
${CMAKE_SOURCE_DIR}/src/game/quicksaving.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/game/recomp_api.cpp
|
${CMAKE_SOURCE_DIR}/src/game/recomp_api.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/src/game/rom_decompression.cpp
|
||||||
|
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp
|
||||||
${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp
|
${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 50d63debd58ce9f01957142ea91b67b6c7018c96
|
Subproject commit a325f8fa5c48c925616bd43685ef14bbabc29537
|
@ -1,9 +1,14 @@
|
|||||||
#ifndef __ZELDA_GAME_H__
|
#ifndef __ZELDA_GAME_H__
|
||||||
#define __ZELDA_GAME_H__
|
#define __ZELDA_GAME_H__
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace zelda64 {
|
namespace zelda64 {
|
||||||
void quicksave_save();
|
void quicksave_save();
|
||||||
void quicksave_load();
|
void quicksave_load();
|
||||||
|
std::vector<uint8_t> decompress_mm(std::span<const uint8_t> compressed_rom);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 50029c70fdc5c90f08d6e1198df6462a54aa3f40
|
Subproject commit cdfe41680904b122d7226d750d5df6f56f9086d6
|
@ -3,8 +3,8 @@ __start = 0x80000000;
|
|||||||
/* Dummy addresses that get recompiled into function calls */
|
/* Dummy addresses that get recompiled into function calls */
|
||||||
recomp_puts = 0x8F000000;
|
recomp_puts = 0x8F000000;
|
||||||
recomp_exit = 0x8F000004;
|
recomp_exit = 0x8F000004;
|
||||||
recomp_handle_quicksave_actions = 0x8F000008;
|
/* recomp_handle_quicksave_actions = 0x8F000008;
|
||||||
recomp_handle_quicksave_actions_main = 0x8F00000C;
|
recomp_handle_quicksave_actions_main = 0x8F00000C; */
|
||||||
osRecvMesg_recomp = 0x8F000010;
|
osRecvMesg_recomp = 0x8F000010;
|
||||||
osSendMesg_recomp = 0x8F000014;
|
osSendMesg_recomp = 0x8F000014;
|
||||||
recomp_get_gyro_deltas = 0x8F000018;
|
recomp_get_gyro_deltas = 0x8F000018;
|
||||||
|
168
src/game/rom_decompression.cpp
Normal file
168
src/game/rom_decompression.cpp
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "zelda_game.h"
|
||||||
|
|
||||||
|
void naive_copy(std::span<uint8_t> dst, std::span<const uint8_t> src) {
|
||||||
|
for (size_t i = 0; i < src.size(); i++) {
|
||||||
|
dst[i] = src[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void yaz0_decompress(std::span<const uint8_t> input, std::span<uint8_t> output) {
|
||||||
|
int32_t layoutBitIndex;
|
||||||
|
uint8_t layoutBits;
|
||||||
|
|
||||||
|
size_t input_pos = 0;
|
||||||
|
size_t output_pos = 0;
|
||||||
|
|
||||||
|
size_t input_size = input.size();
|
||||||
|
size_t output_size = output.size();
|
||||||
|
|
||||||
|
while (input_pos < input_size) {
|
||||||
|
int32_t layoutBitIndex = 0;
|
||||||
|
uint8_t layoutBits = input[input_pos++];
|
||||||
|
|
||||||
|
while (layoutBitIndex < 8 && input_pos < input_size && output_pos < output_size) {
|
||||||
|
if (layoutBits & 0x80) {
|
||||||
|
output[output_pos++] = input[input_pos++];
|
||||||
|
} else {
|
||||||
|
int32_t firstByte = input[input_pos++];
|
||||||
|
int32_t secondByte = input[input_pos++];
|
||||||
|
uint32_t bytes = firstByte << 8 | secondByte;
|
||||||
|
uint32_t offset = (bytes & 0x0FFF) + 1;
|
||||||
|
uint32_t length;
|
||||||
|
|
||||||
|
// Check how the group length is encoded
|
||||||
|
if ((firstByte & 0xF0) == 0) {
|
||||||
|
// 3 byte encoding, 0RRRNN
|
||||||
|
int32_t thirdByte = input[input_pos++];
|
||||||
|
length = thirdByte + 0x12;
|
||||||
|
} else {
|
||||||
|
// 2 byte encoding, NRRR
|
||||||
|
length = ((bytes & 0xF000) >> 12) + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
naive_copy(output.subspan(output_pos, length), output.subspan(output_pos - offset, length));
|
||||||
|
output_pos += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutBitIndex++;
|
||||||
|
layoutBits <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
inline uint32_t byteswap(uint32_t val) {
|
||||||
|
return _byteswap_ulong(val);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
constexpr uint32_t byteswap(uint32_t val) {
|
||||||
|
return __builtin_bswap32(val);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Produces a decompressed MM 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) {
|
||||||
|
// 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) {
|
||||||
|
assert(false);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compressed_rom[0x3B] != 'N' || compressed_rom[0x3C] != 'Z' || compressed_rom[0x3D] != 'S' || compressed_rom[0x3E] != 'E') {
|
||||||
|
assert(false);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DmaDataEntry {
|
||||||
|
uint32_t vrom_start;
|
||||||
|
uint32_t vrom_end;
|
||||||
|
uint32_t rom_start;
|
||||||
|
uint32_t rom_end;
|
||||||
|
|
||||||
|
void bswap() {
|
||||||
|
vrom_start = byteswap(vrom_start);
|
||||||
|
vrom_end = byteswap(vrom_end);
|
||||||
|
rom_start = byteswap(rom_start);
|
||||||
|
rom_end = byteswap(rom_end);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
size_t content_end = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Read the entry from the compressed rom.
|
||||||
|
size_t cur_entry_rom_address = dma_data_rom_addr + (cur_entry_index++) * sizeof(DmaDataEntry);
|
||||||
|
memcpy(&cur_entry, compressed_rom.data() + cur_entry_rom_address, sizeof(DmaDataEntry));
|
||||||
|
// Swap the entry to native endianness after reading from the big endian data.
|
||||||
|
cur_entry.bswap();
|
||||||
|
|
||||||
|
// Rom end being 0 means the data is already uncompressed, so copy it as-is to vrom start.
|
||||||
|
size_t entry_decompressed_size = cur_entry.vrom_end - cur_entry.vrom_start;
|
||||||
|
if (cur_entry.rom_end == 0) {
|
||||||
|
memcpy(ret.data() + cur_entry.vrom_start, compressed_rom.data() + cur_entry.rom_start, entry_decompressed_size);
|
||||||
|
|
||||||
|
// Edit the entry to account for it being in a new location now.
|
||||||
|
cur_entry.rom_start = cur_entry.vrom_start;
|
||||||
|
}
|
||||||
|
// Otherwise, decompress the input data into the output data.
|
||||||
|
else {
|
||||||
|
if (cur_entry.rom_end != cur_entry.rom_start) {
|
||||||
|
// Validate the presence of the yaz0 header.
|
||||||
|
if (compressed_rom[cur_entry.rom_start + 0] != 'Y' ||
|
||||||
|
compressed_rom[cur_entry.rom_start + 1] != 'a' ||
|
||||||
|
compressed_rom[cur_entry.rom_start + 2] != 'z' ||
|
||||||
|
compressed_rom[cur_entry.rom_start + 3] != '0')
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
// Skip the yaz0 header.
|
||||||
|
size_t compressed_data_rom_start = cur_entry.rom_start + 0x10;
|
||||||
|
size_t entry_compressed_size = cur_entry.rom_end - compressed_data_rom_start;
|
||||||
|
|
||||||
|
std::span input_span = std::span{ compressed_rom }.subspan(compressed_data_rom_start, entry_compressed_size);
|
||||||
|
std::span output_span = std::span{ ret }.subspan(cur_entry.vrom_start, entry_decompressed_size);
|
||||||
|
yaz0_decompress(input_span, output_span);
|
||||||
|
|
||||||
|
// Edit the entry to account for it being decompressed now.
|
||||||
|
cur_entry.rom_start = cur_entry.vrom_start;
|
||||||
|
cur_entry.rom_end = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry_decompressed_size != 0) {
|
||||||
|
if (cur_entry.vrom_end > content_end) {
|
||||||
|
content_end = cur_entry.vrom_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap the entry back to big endian for writing.
|
||||||
|
cur_entry.bswap();
|
||||||
|
// Write the modified entry to the decompressed rom.
|
||||||
|
memcpy(ret.data() + cur_entry_rom_address, &cur_entry, sizeof(DmaDataEntry));
|
||||||
|
} while (cur_entry.vrom_end != 0);
|
||||||
|
|
||||||
|
// Align the start of padding to the closest 0x1000 (matches decomp rom decompression behavior).
|
||||||
|
content_end = (content_end + 0x1000 - 1) & -0x1000;
|
||||||
|
|
||||||
|
// Write 0xFF as the padding.
|
||||||
|
std::fill(ret.begin() + content_end, ret.end(), 0xFF);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
@ -25,6 +25,7 @@
|
|||||||
#include "zelda_config.h"
|
#include "zelda_config.h"
|
||||||
#include "zelda_sound.h"
|
#include "zelda_sound.h"
|
||||||
#include "zelda_render.h"
|
#include "zelda_render.h"
|
||||||
|
#include "zelda_game.h"
|
||||||
#include "ovl_patches.hpp"
|
#include "ovl_patches.hpp"
|
||||||
#include "librecomp/game.hpp"
|
#include "librecomp/game.hpp"
|
||||||
#include "librecomp/mods.hpp"
|
#include "librecomp/mods.hpp"
|
||||||
@ -330,7 +331,9 @@ std::vector<recomp::GameEntry> supported_games = {
|
|||||||
.game_id = u8"mm.n64.us.1.0",
|
.game_id = u8"mm.n64.us.1.0",
|
||||||
.mod_game_id = "mm",
|
.mod_game_id = "mm",
|
||||||
.save_type = recomp::SaveType::Flashram,
|
.save_type = recomp::SaveType::Flashram,
|
||||||
.is_enabled = true,
|
.is_enabled = false,
|
||||||
|
.decompression_routine = zelda64::decompress_mm,
|
||||||
|
.has_compressed_code = true,
|
||||||
.entrypoint_address = get_entrypoint_address(),
|
.entrypoint_address = get_entrypoint_address(),
|
||||||
.entrypoint = recomp_entrypoint,
|
.entrypoint = recomp_entrypoint,
|
||||||
},
|
},
|
||||||
@ -538,6 +541,8 @@ void disable_texture_pack(recomp::mods::ModContext& context, const recomp::mods:
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
|
(void)argc;
|
||||||
|
(void)argv;
|
||||||
recomp::Version project_version{};
|
recomp::Version project_version{};
|
||||||
if (!recomp::Version::from_string(version_string, project_version)) {
|
if (!recomp::Version::from_string(version_string, project_version)) {
|
||||||
ultramodern::error_handling::message_box(("Invalid version string: " + version_string).c_str());
|
ultramodern::error_handling::message_box(("Invalid version string: " + version_string).c_str());
|
||||||
|
@ -9,4 +9,5 @@ void zelda64::register_patches() {
|
|||||||
recomp::overlays::register_patches(mm_patches_bin, sizeof(mm_patches_bin), section_table, ARRLEN(section_table));
|
recomp::overlays::register_patches(mm_patches_bin, sizeof(mm_patches_bin), section_table, ARRLEN(section_table));
|
||||||
recomp::overlays::register_base_exports(export_table);
|
recomp::overlays::register_base_exports(export_table);
|
||||||
recomp::overlays::register_base_events(event_names);
|
recomp::overlays::register_base_events(event_names);
|
||||||
|
recomp::overlays::register_manual_patch_symbols(manual_patch_symbols);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user