增加 free camera,支持配置修改,增加图标

This commit is contained in:
chinosk
2024-05-22 23:25:15 +08:00
parent f7239159d0
commit c692684352
48 changed files with 845 additions and 80 deletions

View File

@@ -36,12 +36,15 @@ find_package(shadowhook REQUIRED CONFIG)
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
localify.cpp
libMarryKotone.cpp
GakumasLocalify/Plugin.cpp
GakumasLocalify/Hook.cpp
GakumasLocalify/Log.cpp
GakumasLocalify/Misc.cpp
GakumasLocalify/Local.cpp
GakumasLocalify/camera/baseCamera.cpp
GakumasLocalify/camera/camera.cpp
GakumasLocalify/config/Config.cpp
)
target_link_libraries(${CMAKE_PROJECT_NAME} xdl::xdl)

View File

@@ -6,8 +6,15 @@
#include "Il2cppUtils.hpp"
#include "Local.h"
#include <unordered_set>
#include "camera/camera.hpp"
#include "config/Config.hpp"
#include "shadowhook.h"
#include <jni.h>
#include <thread>
std::unordered_set<void*> hookedStubs{};
#define DEFINE_HOOK(returnType, name, params) \
using name##_Type = returnType(*) params; \
name##_Type name##_Addr = nullptr; \
@@ -18,13 +25,32 @@
#define ADD_HOOK(name, addr) \
name##_Addr = reinterpret_cast<name##_Type>(addr); \
if (addr) { \
hookInstaller->InstallHook(reinterpret_cast<void*>(addr), \
reinterpret_cast<void*>(name##_Hook), \
reinterpret_cast<void**>(&name##_Orig)); \
GakumasLocal::Log::InfoFmt("ADD_HOOK: %s at %p", #name, addr); \
auto stub = hookInstaller->InstallHook(reinterpret_cast<void*>(addr), \
reinterpret_cast<void*>(name##_Hook), \
reinterpret_cast<void**>(&name##_Orig)); \
if (stub == NULL) { \
int error_num = shadowhook_get_errno(); \
const char *error_msg = shadowhook_to_errmsg(error_num); \
Log::ErrorFmt("ADD_HOOK: %s at %p failed: %s", #name, addr, error_msg); \
} \
else { \
hookedStubs.emplace(stub); \
GakumasLocal::Log::InfoFmt("ADD_HOOK: %s at %p", #name, addr); \
} \
} \
else GakumasLocal::Log::ErrorFmt("Hook failed: %s is NULL", #name, addr)
void UnHookAll() {
for (const auto i: hookedStubs) {
int result = shadowhook_unhook(i);
if(result != 0)
{
int error_num = shadowhook_get_errno();
const char *error_msg = shadowhook_to_errmsg(error_num);
GakumasLocal::Log::ErrorFmt("unhook failed: %d - %s", error_num, error_msg);
}
}
}
namespace GakumasLocal::HookMain {
using Il2cppString = UnityResolve::UnityType::String;
@@ -58,6 +84,52 @@ namespace GakumasLocal::HookMain {
// Log::LogFmt(ANDROID_LOG_VERBOSE, "UnityLog - Internal_Log: %s", content->ToString().c_str());
}
UnityResolve::UnityType::Camera* mainCameraCache = nullptr;
UnityResolve::UnityType::Transform* cameraTransformCache = nullptr;
void CheckAndUpdateMainCamera() {
if (!Config::enableFreeCamera) return;
static auto IsNativeObjectAlive = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll", "UnityEngine",
"Object", "IsNativeObjectAlive");
if (IsNativeObjectAlive->Invoke<bool>(mainCameraCache)) return;
mainCameraCache = UnityResolve::UnityType::Camera::GetMain();
cameraTransformCache = mainCameraCache->GetTransform();
}
DEFINE_HOOK(void, Unity_set_position_Injected, (UnityResolve::UnityType::Transform* _this, UnityResolve::UnityType::Vector3* data)) {
if (Config::enableFreeCamera) {
CheckAndUpdateMainCamera();
if (cameraTransformCache == _this) {
//Log::DebugFmt("MainCamera set pos: %f, %f, %f", data->x, data->y, data->z);
auto& origCameraPos = GKCamera::baseCamera.pos;
data->x = origCameraPos.x;
data->y = origCameraPos.y;
data->z = origCameraPos.z;
}
}
return Unity_set_position_Injected_Orig(_this, data);
}
DEFINE_HOOK(void, Unity_set_rotation_Injected, (UnityResolve::UnityType::Transform* _this, UnityResolve::UnityType::Quaternion* value)) {
if (Config::enableFreeCamera) {
if (cameraTransformCache == _this) {
auto& origCameraLookat = GKCamera::baseCamera.lookAt;
static auto lookat_injected = reinterpret_cast<void (*)(void*_this,
UnityResolve::UnityType::Vector3* worldPosition, UnityResolve::UnityType::Vector3* worldUp)>(
Il2cppUtils::il2cpp_resolve_icall(
"UnityEngine.Transform::Internal_LookAt_Injected(UnityEngine.Vector3&,UnityEngine.Vector3&)"));
static auto worldUp = UnityResolve::UnityType::Vector3(0, 1, 0);
lookat_injected(_this, &origCameraLookat, &worldUp);
// TODO 相机 FOV
return;
}
}
return Unity_set_rotation_Injected_Orig(_this, value);
}
std::unordered_map<void*, std::string> loadHistory{};
DEFINE_HOOK(void*, AssetBundle_LoadAssetAsync, (void* _this, Il2cppString* name, void* type)) {
@@ -192,14 +264,10 @@ namespace GakumasLocal::HookMain {
TextMeshProUGUI_Awake_Orig(_this, method);
}
DEFINE_HOOK(void, UI_Text_set_text, (void* _this, Il2cppString* value)) {
// UI_Text_set_text_Orig(_this, Il2cppString::New("[US]" + value->ToString()));
UI_Text_set_text_Orig(_this, value);
static auto set_font = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
"TMP_Text", "set_font");
auto newFont = GetReplaceFont();
set_font->Invoke<void>(_this, newFont);
// TODO 文本未hook完整 思路从tips下手...
DEFINE_HOOK(void, TextField_set_value, (void* _this, Il2cppString* value)) {
Log::DebugFmt("TextField_set_value: %s", value->ToString().c_str());
TextField_set_value_Orig(_this, value);
}
DEFINE_HOOK(Il2cppString*, OctoCaching_GetResourceFileName, (void* data, void* method)) {
@@ -273,8 +341,8 @@ namespace GakumasLocal::HookMain {
ADD_HOOK(TMP_Text_set_text, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro",
"TMP_Text", "set_text"));
ADD_HOOK(UI_Text_set_text, Il2cppUtils::GetMethodPointer("UnityEngine.UI.dll", "UnityEngine.UI",
"Text", "set_text"));
ADD_HOOK(TextField_set_value, Il2cppUtils::GetMethodPointer("UnityEngine.UIElementsModule.dll", "UnityEngine.UIElements",
"TextField", "set_value"));
ADD_HOOK(OctoCaching_GetResourceFileName, Il2cppUtils::GetMethodPointer("Octo.dll", "Octo.Caching",
"OctoCaching", "GetResourceFileName"));
@@ -292,6 +360,11 @@ namespace GakumasLocal::HookMain {
"UnityEngine.DebugLogHandler::Internal_LogException(System.Exception,UnityEngine.Object)"));
ADD_HOOK(Internal_Log, Il2cppUtils::il2cpp_resolve_icall(
"UnityEngine.DebugLogHandler::Internal_Log(UnityEngine.LogType,UnityEngine.LogOption,System.String,UnityEngine.Object)"));
ADD_HOOK(Unity_set_position_Injected, Il2cppUtils::il2cpp_resolve_icall(
"UnityEngine.Transform::set_position_Injected(UnityEngine.Vector3&)"));
ADD_HOOK(Unity_set_rotation_Injected, Il2cppUtils::il2cpp_resolve_icall(
"UnityEngine.Transform::set_rotation_Injected(UnityEngine.Quaternion&)"));
}
// 77 2640 5000
@@ -299,9 +372,20 @@ namespace GakumasLocal::HookMain {
const auto ret = il2cpp_init_Orig(domain_name);
// InjectFunctions();
Log::Info("Waiting for config...");
while (!Config::isConfigInit) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (!Config::enabled) {
Log::Info("Plugin not enabled");
return ret;
}
Log::Info("Start init plugin...");
StartInjectFunctions();
GKCamera::initCameraSettings();
Local::LoadData();
Log::Info("Plugin init finished.");
@@ -321,4 +405,4 @@ namespace GakumasLocal::Hook {
Log::Info("Hook installed");
}
}
}

View File

@@ -9,7 +9,7 @@ namespace GakumasLocal {
struct HookInstaller
{
virtual ~HookInstaller();
virtual void InstallHook(void* addr, void* hook, void** orig) = 0;
virtual void* InstallHook(void* addr, void* hook, void** orig) = 0;
virtual OpaqueFunctionPointer LookupSymbol(const char* name) = 0;
std::string m_il2cppLibraryPath;

View File

@@ -0,0 +1,125 @@
#include "baseCamera.hpp"
#include <thread>
namespace BaseCamera {
using Vector3_t = UnityResolve::UnityType::Vector3;
float moveStep = 0.05;
float look_radius = 5; // 转向半径
float moveAngel = 1.5; // 转向角度
int smoothLevel = 1;
unsigned long sleepTime = 0;
Camera::Camera() {
Camera(0, 0, 0, 0, 0, 0);
}
Camera::Camera(Vector3_t* vec, Vector3_t* lookAt) {
Camera(vec->x, vec->y, vec->z, lookAt->x, lookAt->y, lookAt->z);
}
Camera::Camera(Vector3_t& vec, Vector3_t& lookAt) {
Camera(vec.x, vec.y, vec.z, lookAt.x, lookAt.y, lookAt.z);
}
Camera::Camera(float x, float y, float z, float lx, float ly, float lz) {
pos.x = x;
pos.y = y;
pos.z = z;
lookAt.x = lx;
lookAt.y = ly;
lookAt.z = lz;
}
void Camera::setPos(float x, float y, float z) {
pos.x = x;
pos.y = y;
pos.z = z;
}
void Camera::setLookAt(float x, float y, float z) {
lookAt.x = x;
lookAt.y = y;
lookAt.z = z;
}
void Camera::reset() {
setPos(0.5, 1.1, 1.3);
setLookAt(0.5, 1.1, -3.7);
fov = 60;
verticalAngle = 0;
horizontalAngle = 0;
}
Vector3_t Camera::GetPos() {
return pos;
}
Vector3_t Camera::GetLookAt() {
return lookAt;
}
void Camera::set_lon_move(float vertanglePlus, LonMoveHState moveState) { // 前后移动
auto radian = (verticalAngle + vertanglePlus) * M_PI / 180;
auto radianH = (double)horizontalAngle * M_PI / 180;
auto f_step = cos(radian) * moveStep * cos(radianH) / smoothLevel; // ↑↓
auto l_step = sin(radian) * moveStep * cos(radianH) / smoothLevel; // ←→
// auto h_step = tan(radianH) * sqrt(pow(f_step, 2) + pow(l_step, 2));
auto h_step = sin(radianH) * moveStep / smoothLevel;
switch (moveState)
{
case LonMoveForward: break;
case LonMoveBack: h_step = -h_step; break;
default: h_step = 0; break;
}
for (int i = 0; i < smoothLevel; i++) {
pos.z -= f_step;
lookAt.z -= f_step;
pos.x += l_step;
lookAt.x += l_step;
pos.y += h_step;
lookAt.y += h_step;
std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime));
}
}
void Camera::updateVertLook() { // 上+
auto radian = verticalAngle * M_PI / 180;
auto radian2 = ((double)horizontalAngle - 90) * M_PI / 180; // 日
auto stepX1 = look_radius * sin(radian2) * cos(radian) / smoothLevel;
auto stepX2 = look_radius * sin(radian2) * sin(radian) / smoothLevel;
auto stepX3 = look_radius * cos(radian2) / smoothLevel;
for (int i = 0; i < smoothLevel; i++) {
lookAt.z = pos.z + stepX1;
lookAt.y = pos.y + stepX3;
lookAt.x = pos.x - stepX2;
std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime));
}
}
void Camera::setHoriLook(float vertangle) { // 左+
auto radian = vertangle * M_PI / 180;
auto radian2 = horizontalAngle * M_PI / 180;
auto stepBt = cos(radian) * look_radius * cos(radian2) / smoothLevel;
auto stepHi = sin(radian) * look_radius * cos(radian2) / smoothLevel;
auto stepY = sin(radian2) * look_radius / smoothLevel;
for (int i = 0; i < smoothLevel; i++) {
lookAt.x = pos.x + stepHi;
lookAt.z = pos.z - stepBt;
lookAt.y = pos.y + stepY;
std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime));
}
}
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include "../deps/UnityResolve/UnityResolve.hpp"
enum LonMoveHState {
LonMoveLeftAndRight,
LonMoveForward,
LonMoveBack
};
namespace BaseCamera {
using Vector3_t = UnityResolve::UnityType::Vector3;
extern float moveStep;
extern float look_radius; // 转向半径
extern float moveAngel; // 转向角度
extern int smoothLevel;
extern unsigned long sleepTime;
class Camera {
public:
Camera();
Camera(Vector3_t& vec, Vector3_t& lookAt);
Camera(Vector3_t* vec, Vector3_t* lookAt);
Camera(float x, float y, float z, float lx, float ly, float lz);
void reset();
void setPos(float x, float y, float z);
void setLookAt(float x, float y, float z);
void set_lon_move(float vertanglePlus, LonMoveHState moveState = LonMoveHState::LonMoveLeftAndRight);
void updateVertLook();
void setHoriLook(float vertangle);
Vector3_t GetPos();
Vector3_t GetLookAt();
Vector3_t pos{0.5, 1.1, 1.3};
Vector3_t lookAt{0.5, 1.1, -3.7};
float fov = 60;
float horizontalAngle = 0; // 水平方向角度
float verticalAngle = 0; // 垂直方向角度
};
}

View File

@@ -0,0 +1,197 @@
#include "baseCamera.hpp"
#include <thread>
#define KEY_W 51
#define KEY_S 47
#define KEY_A 29
#define KEY_D 32
#define KEY_R 46
#define KEY_Q 45
#define KEY_E 33
#define KEY_I 37
#define KEY_K 39
#define KEY_J 38
#define KEY_L 40
#define KEY_R 46
#define KEY_UP 19
#define KEY_DOWN 20
#define KEY_LEFT 21
#define KEY_RIGHT 22
#define KEY_CTRL 113
#define KEY_SHIFT 59
#define KEY_ALT 57
#define KEY_SPACE 62
#define WM_KEYDOWN 0
#define WM_KEYUP 1
namespace GKCamera {
BaseCamera::Camera baseCamera{};
bool rMousePressFlg = false;
void reset_camera() {
baseCamera.reset();
}
void camera_forward() { // 向前
baseCamera.set_lon_move(0, LonMoveHState::LonMoveForward);
}
void camera_back() { // 后退
baseCamera.set_lon_move(180, LonMoveHState::LonMoveBack);
}
void camera_left() { // 向左
baseCamera.set_lon_move(90);
}
void camera_right() { // 向右
baseCamera.set_lon_move(-90);
}
void camera_down() { // 向下
float preStep = BaseCamera::moveStep / BaseCamera::smoothLevel;
for (int i = 0; i < BaseCamera::smoothLevel; i++) {
baseCamera.pos.y -= preStep;
baseCamera.lookAt.y -= preStep;
std::this_thread::sleep_for(std::chrono::milliseconds(BaseCamera::sleepTime));
}
}
void camera_up() { // 向上
float preStep = BaseCamera::moveStep / BaseCamera::smoothLevel;
for (int i = 0; i < BaseCamera::smoothLevel; i++) {
baseCamera.pos.y += preStep;
baseCamera.lookAt.y += preStep;
std::this_thread::sleep_for(std::chrono::milliseconds(BaseCamera::sleepTime));
}
}
void cameraLookat_up(float mAngel, bool mouse = false) {
baseCamera.horizontalAngle += mAngel;
if (baseCamera.horizontalAngle >= 90) baseCamera.horizontalAngle = 89.99;
baseCamera.updateVertLook();
}
void cameraLookat_down(float mAngel, bool mouse = false) {
baseCamera.horizontalAngle -= mAngel;
if (baseCamera.horizontalAngle <= -90) baseCamera.horizontalAngle = -89.99;
baseCamera.updateVertLook();
}
void cameraLookat_left(float mAngel) {
baseCamera.verticalAngle += mAngel;
if (baseCamera.verticalAngle >= 360) baseCamera.verticalAngle = -360;
baseCamera.setHoriLook(baseCamera.verticalAngle);
}
void cameraLookat_right(float mAngel) {
baseCamera.verticalAngle -= mAngel;
if (baseCamera.verticalAngle <= -360) baseCamera.verticalAngle = 360;
baseCamera.setHoriLook(baseCamera.verticalAngle);
}
void changeCameraFOV(float value) {
baseCamera.fov += value;
}
struct CameraMoveState {
bool w = false;
bool s = false;
bool a = false;
bool d = false;
bool ctrl = false;
bool space = false;
bool up = false;
bool down = false;
bool left = false;
bool right = false;
bool q = false;
bool e = false;
bool i = false;
bool k = false;
bool j = false;
bool l = false;
bool threadRunning = false;
void resetAll() {
auto p = reinterpret_cast<bool*>(this);
const auto numMembers = sizeof(*this) / sizeof(bool);
for (size_t idx = 0; idx < numMembers; ++idx) {
p[idx] = false;
}
}
} cameraMoveState;
void cameraRawInputThread() {
using namespace BaseCamera;
std::thread([]() {
if (cameraMoveState.threadRunning) return;
cameraMoveState.threadRunning = true;
while (true) {
if (cameraMoveState.w) camera_forward();
if (cameraMoveState.s) camera_back();
if (cameraMoveState.a) camera_left();
if (cameraMoveState.d) camera_right();
if (cameraMoveState.ctrl) camera_down();
if (cameraMoveState.space) camera_up();
if (cameraMoveState.up) cameraLookat_up(moveAngel);
if (cameraMoveState.down) cameraLookat_down(moveAngel);
if (cameraMoveState.left) cameraLookat_left(moveAngel);
if (cameraMoveState.right) cameraLookat_right(moveAngel);
if (cameraMoveState.q) changeCameraFOV(0.5f);
if (cameraMoveState.e) changeCameraFOV(-0.5f);
// if (cameraMoveState.i) changeLiveFollowCameraOffsetY(moveStep / 3);
// if (cameraMoveState.k) changeLiveFollowCameraOffsetY(-moveStep / 3);
// if (cameraMoveState.j) changeLiveFollowCameraOffsetX(moveStep * 10);
// if (cameraMoveState.l) changeLiveFollowCameraOffsetX(-moveStep * 10);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}).detach();
}
void on_cam_rawinput_keyboard(int message, int key) {
if (message == WM_KEYDOWN || message == WM_KEYUP) {
switch (key) {
case KEY_W:
cameraMoveState.w = message == WM_KEYDOWN; break;
case KEY_S:
cameraMoveState.s = message == WM_KEYDOWN; break;
case KEY_A:
cameraMoveState.a = message == WM_KEYDOWN; break;
case KEY_D:
cameraMoveState.d = message == WM_KEYDOWN; break;
case KEY_CTRL:
cameraMoveState.ctrl = message == WM_KEYDOWN; break;
case KEY_SPACE:
cameraMoveState.space = message == WM_KEYDOWN; break;
case KEY_UP:
cameraMoveState.up = message == WM_KEYDOWN; break;
case KEY_DOWN:
cameraMoveState.down = message == WM_KEYDOWN; break;
case KEY_LEFT:
cameraMoveState.left = message == WM_KEYDOWN; break;
case KEY_RIGHT:
cameraMoveState.right = message == WM_KEYDOWN; break;
case KEY_Q:
cameraMoveState.q = message == WM_KEYDOWN; break;
case KEY_E:
cameraMoveState.e = message == WM_KEYDOWN; break;
case KEY_I:
cameraMoveState.i = message == WM_KEYDOWN; break;
case KEY_K:
cameraMoveState.k = message == WM_KEYDOWN; break;
case KEY_J:
cameraMoveState.j = message == WM_KEYDOWN; break;
case KEY_L:
cameraMoveState.l = message == WM_KEYDOWN; break;
case KEY_R: {
if (message == WM_KEYDOWN) reset_camera();
}; break;
default: break;
}
}
}
void initCameraSettings() {
reset_camera();
cameraRawInputThread();
}
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include "baseCamera.hpp"
namespace GKCamera {
extern BaseCamera::Camera baseCamera;
void on_cam_rawinput_keyboard(int message, int key);
void initCameraSettings();
}

View File

@@ -0,0 +1,24 @@
#include <string>
#include "nlohmann/json.hpp"
#include "../Log.h"
namespace GakumasLocal::Config {
bool isConfigInit = false;
bool enabled = true;
bool enableFreeCamera = false;
void LoadConfig(const std::string& configStr) {
try {
const auto config = nlohmann::json::parse(configStr);
enabled = config["enabled"];
enableFreeCamera = config["enableFreeCamera"];
}
catch (std::exception& e) {
Log::ErrorFmt("LoadConfig error: %s", e.what());
}
isConfigInit = true;
}
}

View File

@@ -0,0 +1,15 @@
#ifndef GAKUMAS_LOCALIFY_CONFIG_HPP
#define GAKUMAS_LOCALIFY_CONFIG_HPP
namespace GakumasLocal::Config {
extern bool isConfigInit;
extern bool enabled;
extern bool enableFreeCamera;
void LoadConfig(const std::string& configStr);
}
#endif //GAKUMAS_LOCALIFY_CONFIG_HPP

View File

@@ -45,6 +45,7 @@
#include "xdl.h"
#include "../../GakumasLocalify/Log.h"
#include "../../GakumasLocalify/Misc.h"
class UnityResolve final {
public:

View File

@@ -5,8 +5,9 @@
#include <android/log.h>
#include "string"
#include "shadowhook.h"
#include "xdl.h"
#include "GakumasLocalify/camera/camera.hpp"
#include "GakumasLocalify/config/Config.hpp"
namespace
{
@@ -24,9 +25,9 @@ namespace
xdl_close(m_Il2CppLibrary);
}
void InstallHook(void* addr, void* hook, void** orig) override
void* InstallHook(void* addr, void* hook, void** orig) override
{
shadowhook_hook_func_addr(addr, hook, orig);
return shadowhook_hook_func_addr(addr, hook, orig);
}
GakumasLocal::OpaqueFunctionPointer LookupSymbol(const char* name) override
@@ -44,8 +45,6 @@ extern "C"
JNIEXPORT void JNICALL
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_initHook(JNIEnv *env, jclass clazz, jstring targetLibraryPath,
jstring localizationFilesDir) {
GakumasLocal::Log::Info("Hello initHook!");
const auto targetLibraryPathChars = env->GetStringUTFChars(targetLibraryPath, nullptr);
const std::string targetLibraryPathStr = targetLibraryPathChars;
@@ -54,4 +53,19 @@ Java_io_github_chinosk_gakumas_localify_GakumasHookMain_initHook(JNIEnv *env, jc
auto& plugin = GakumasLocal::Plugin::GetInstance();
plugin.InstallHook(std::make_unique<AndroidHookInstaller>(targetLibraryPathStr, localizationFilesDirCharsStr));
}
extern "C"
JNIEXPORT void JNICALL
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_keyboardEvent(JNIEnv *env, jclass clazz, jint key_code, jint action) {
GKCamera::on_cam_rawinput_keyboard(action, key_code);
}
extern "C"
JNIEXPORT void JNICALL
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_loadConfig(JNIEnv *env, jclass clazz,
jstring config_json_str) {
const auto configJsonStrChars = env->GetStringUTFChars(config_json_str, nullptr);
const std::string configJson = configJsonStrChars;
GakumasLocal::Config::LoadConfig(configJson);
}