More actions
This page or section contains undocumented information. Please ensure all of the info is complete! |
Setting up the IDE
The only supported IDEs for now are CLion, CLion EAP and CLion Nova.
CLion
The following steps require specific tools. You will need: - Ghidra - BDSP Rombase - DevKitPro |
After opening the project, go to File
> Settings
, and navigate to Build, Execution, Deployment
> CMake
.
Then, in CMake options
, add -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake
and reload the CMake project.
To make sure everything is set up correctly, check the dropdown in the top-right, select RomBase_zip_all
, and click the hammer icon next to the dropdown to build the project. After running, you should find a folder in cmake-build-debug/RomBase_releases
with zip files for each emulator and for hardware.
Mapping Types
To use the game's types in our code, we first need to map them.
For this example, we'll be mapping the type Dpr_Battle_Logic_MyStatus_o
.
Knowing we're looking at Dpr_Battle_Logic_MyStatus
, this means the class is at `Dpr::Battle::Logic::MyStatus`, so we create the file externals/Dpr/Battle/Logic/MyStatus.h
.
namespace Dpr::Battle::Logic { struct MyStatus { }; }
First, we open it in Ghidra's type browser, and we're greeted with this:
There's two situations here; Either it has a klass
and monitor
field, or it only has fields
. In the former case, we call it an ILClass, otherwise we call it an ILStruct. In this case, that means it's an ILClass.
namespace Dpr::Battle::Logic { // If ILClass: `struct T : ILClass<T>` // If ILStruct: `struct T : ILStruct<T>` struct MyStatus : ILClass<MyStatus> { }; }
Next, we right-click the fields
row and click "Edit component field". The following shows up:
namespace Dpr::Battle::Logic { struct MyStatus : ILClass<MyStatus> { // All fields go in a nested Fields struct. If it has no fields, don't add it. // If your type has a supertype (for example, UnityEngine_Behaviour_fields has a field called super of type UnityEngine_Component_fields), // You would copy that here (i.e. `struct Fields : UnityEngine::Component::Fields {`) struct Fields { // Fill in all elements from the image, creating their types if needed. // You need to remap e.g. System_String_o* to System::String::Object* // or UnityEngine_GameObject_array to UnityEngine::GameObject::Array System::String::Object* name; bool sex; int32_t lang; uint32_t id; uint8_t fashion; uint8_t body_type; uint8_t hat; uint8_t shoes; }; }; }
Creating Hooks
Injecting custom code is done through Exlaunch hooks. In this example, we'll be changing how Affection works in battle. We'll look at two scenarios:
- Disable it completely
- Disable it based on a flag
First things first, we need to find what handles friendship. Searching through the functions, we'll quickly find Dpr.Battle.Logic.MainModule$$IsFriendshipActive
. It seems like we want to change the behavior of this function, so let's write down the following:
- The function signature:
bool Dpr.Battle.Logic.MainModule$$IsFriendshipActive(Dpr_Battle_Logic_MainModule_o *__this,Dpr_Battle_Logic_BTL_POKEPARAM_o *bpp,MethodInfo *method)
- The function address:
020378d0
(if you're using the Ghidra Switch Loader plugin, make sure to leave out the leading "71"
Let's start with a hook to completely disable it first. Since we don't need the original, we can use a REPLACE hook:
HOOK_DEFINE_REPLACE(ChangeFriendship) { // Copy the arguments and return // You can ignore the MethodInfo, UNLESS it has an extended type name like MethodInfo_1A49590 // Map the names similar to how you did in the Remapping Types section static bool Callback(Dpr::Battle::Logic::MainModule::Object* __this, Dpr::Battle::Logic::BTL_POKEPARAM::Object* bpp) { return false; // IsFriendshipActive now always returns false } }; // Then, in your main function: // ... ChangeFriendship::InstallAtOffset(0x020378d0); // This is the address we copied before.
Next, we'll look at making it conditional. Since we need the original, we'll use a TRAMPOLINE hook. Note that trampoline hooks use more memory.
// Change the type here HOOK_DEFINE_TRAMPOLINE(ChangeFriendship) { static bool Callback(Dpr::Battle::Logic::MainModule::Object* __this, Dpr::Battle::Logic::BTL_POKEPARAM::Object* bpp) { if (FlagWork::GetFlag(...)) { // You can use an unused flag ID here, or use an existing flag. return Orig(__this, bpp); // Call Orig to use the original function } else { return false; } } }; // You can set it up the same way: // ... ChangeFriendship::InstallAtOffset(0x020378d0);