增加 free camera,支持配置修改,增加图标
@ -66,6 +66,9 @@ android {
|
|||||||
pickFirst '**/libxdl.so'
|
pickFirst '**/libxdl.so'
|
||||||
pickFirst '**/libshadowhook.so'
|
pickFirst '**/libshadowhook.so'
|
||||||
}
|
}
|
||||||
|
dataBinding {
|
||||||
|
enable true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -78,6 +81,9 @@ dependencies {
|
|||||||
implementation 'androidx.compose.ui:ui-graphics'
|
implementation 'androidx.compose.ui:ui-graphics'
|
||||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||||
implementation 'androidx.compose.material3:material3'
|
implementation 'androidx.compose.material3:material3'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
@ -89,4 +95,6 @@ dependencies {
|
|||||||
implementation 'io.github.hexhacking:xdl:2.1.1'
|
implementation 'io.github.hexhacking:xdl:2.1.1'
|
||||||
implementation 'com.bytedance.android:shadowhook:1.0.9'
|
implementation 'com.bytedance.android:shadowhook:1.0.9'
|
||||||
compileOnly 'de.robv.android.xposed:api:82'
|
compileOnly 'de.robv.android.xposed:api:82'
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.0"
|
||||||
|
implementation 'com.google.code.gson:gson:2.11.0'
|
||||||
}
|
}
|
BIN
app/src/main/1024.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
@ -8,7 +8,6 @@
|
|||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.GakumasLocalify"
|
android:theme="@style/Theme.GakumasLocalify"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
@ -16,20 +15,16 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="xposedmodule"
|
android:name="xposedmodule"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="xposeddescription"
|
android:name="xposeddescription"
|
||||||
android:value="IDOLM@STER Gakuen localify" />
|
android:value="IDOLM@STER Gakuen localify" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="xposedminversion"
|
android:name="xposedminversion"
|
||||||
android:value="53" />
|
android:value="53" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="xposedsharedprefs"
|
android:name="xposedsharedprefs"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
@ -36,12 +36,15 @@ find_package(shadowhook REQUIRED CONFIG)
|
|||||||
# used in the AndroidManifest.xml file.
|
# used in the AndroidManifest.xml file.
|
||||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
||||||
localify.cpp
|
libMarryKotone.cpp
|
||||||
GakumasLocalify/Plugin.cpp
|
GakumasLocalify/Plugin.cpp
|
||||||
GakumasLocalify/Hook.cpp
|
GakumasLocalify/Hook.cpp
|
||||||
GakumasLocalify/Log.cpp
|
GakumasLocalify/Log.cpp
|
||||||
GakumasLocalify/Misc.cpp
|
GakumasLocalify/Misc.cpp
|
||||||
GakumasLocalify/Local.cpp
|
GakumasLocalify/Local.cpp
|
||||||
|
GakumasLocalify/camera/baseCamera.cpp
|
||||||
|
GakumasLocalify/camera/camera.cpp
|
||||||
|
GakumasLocalify/config/Config.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(${CMAKE_PROJECT_NAME} xdl::xdl)
|
target_link_libraries(${CMAKE_PROJECT_NAME} xdl::xdl)
|
||||||
|
@ -6,8 +6,15 @@
|
|||||||
#include "Il2cppUtils.hpp"
|
#include "Il2cppUtils.hpp"
|
||||||
#include "Local.h"
|
#include "Local.h"
|
||||||
#include <unordered_set>
|
#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) \
|
#define DEFINE_HOOK(returnType, name, params) \
|
||||||
using name##_Type = returnType(*) params; \
|
using name##_Type = returnType(*) params; \
|
||||||
name##_Type name##_Addr = nullptr; \
|
name##_Type name##_Addr = nullptr; \
|
||||||
@ -18,13 +25,32 @@
|
|||||||
#define ADD_HOOK(name, addr) \
|
#define ADD_HOOK(name, addr) \
|
||||||
name##_Addr = reinterpret_cast<name##_Type>(addr); \
|
name##_Addr = reinterpret_cast<name##_Type>(addr); \
|
||||||
if (addr) { \
|
if (addr) { \
|
||||||
hookInstaller->InstallHook(reinterpret_cast<void*>(addr), \
|
auto stub = hookInstaller->InstallHook(reinterpret_cast<void*>(addr), \
|
||||||
reinterpret_cast<void*>(name##_Hook), \
|
reinterpret_cast<void*>(name##_Hook), \
|
||||||
reinterpret_cast<void**>(&name##_Orig)); \
|
reinterpret_cast<void**>(&name##_Orig)); \
|
||||||
GakumasLocal::Log::InfoFmt("ADD_HOOK: %s at %p", #name, addr); \
|
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)
|
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 {
|
namespace GakumasLocal::HookMain {
|
||||||
using Il2cppString = UnityResolve::UnityType::String;
|
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());
|
// 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{};
|
std::unordered_map<void*, std::string> loadHistory{};
|
||||||
|
|
||||||
DEFINE_HOOK(void*, AssetBundle_LoadAssetAsync, (void* _this, Il2cppString* name, void* type)) {
|
DEFINE_HOOK(void*, AssetBundle_LoadAssetAsync, (void* _this, Il2cppString* name, void* type)) {
|
||||||
@ -192,14 +264,10 @@ namespace GakumasLocal::HookMain {
|
|||||||
TextMeshProUGUI_Awake_Orig(_this, method);
|
TextMeshProUGUI_Awake_Orig(_this, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, UI_Text_set_text, (void* _this, Il2cppString* value)) {
|
// TODO 文本未hook完整 思路:从tips下手...
|
||||||
// UI_Text_set_text_Orig(_this, Il2cppString::New("[US]" + value->ToString()));
|
DEFINE_HOOK(void, TextField_set_value, (void* _this, Il2cppString* value)) {
|
||||||
UI_Text_set_text_Orig(_this, value);
|
Log::DebugFmt("TextField_set_value: %s", value->ToString().c_str());
|
||||||
|
TextField_set_value_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(Il2cppString*, OctoCaching_GetResourceFileName, (void* data, void* method)) {
|
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",
|
ADD_HOOK(TMP_Text_set_text, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro",
|
||||||
"TMP_Text", "set_text"));
|
"TMP_Text", "set_text"));
|
||||||
|
|
||||||
ADD_HOOK(UI_Text_set_text, Il2cppUtils::GetMethodPointer("UnityEngine.UI.dll", "UnityEngine.UI",
|
ADD_HOOK(TextField_set_value, Il2cppUtils::GetMethodPointer("UnityEngine.UIElementsModule.dll", "UnityEngine.UIElements",
|
||||||
"Text", "set_text"));
|
"TextField", "set_value"));
|
||||||
|
|
||||||
ADD_HOOK(OctoCaching_GetResourceFileName, Il2cppUtils::GetMethodPointer("Octo.dll", "Octo.Caching",
|
ADD_HOOK(OctoCaching_GetResourceFileName, Il2cppUtils::GetMethodPointer("Octo.dll", "Octo.Caching",
|
||||||
"OctoCaching", "GetResourceFileName"));
|
"OctoCaching", "GetResourceFileName"));
|
||||||
@ -292,6 +360,11 @@ namespace GakumasLocal::HookMain {
|
|||||||
"UnityEngine.DebugLogHandler::Internal_LogException(System.Exception,UnityEngine.Object)"));
|
"UnityEngine.DebugLogHandler::Internal_LogException(System.Exception,UnityEngine.Object)"));
|
||||||
ADD_HOOK(Internal_Log, Il2cppUtils::il2cpp_resolve_icall(
|
ADD_HOOK(Internal_Log, Il2cppUtils::il2cpp_resolve_icall(
|
||||||
"UnityEngine.DebugLogHandler::Internal_Log(UnityEngine.LogType,UnityEngine.LogOption,System.String,UnityEngine.Object)"));
|
"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
|
// 77 2640 5000
|
||||||
|
|
||||||
@ -299,9 +372,20 @@ namespace GakumasLocal::HookMain {
|
|||||||
const auto ret = il2cpp_init_Orig(domain_name);
|
const auto ret = il2cpp_init_Orig(domain_name);
|
||||||
// InjectFunctions();
|
// 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...");
|
Log::Info("Start init plugin...");
|
||||||
|
|
||||||
StartInjectFunctions();
|
StartInjectFunctions();
|
||||||
|
GKCamera::initCameraSettings();
|
||||||
Local::LoadData();
|
Local::LoadData();
|
||||||
|
|
||||||
Log::Info("Plugin init finished.");
|
Log::Info("Plugin init finished.");
|
||||||
@ -321,4 +405,4 @@ namespace GakumasLocal::Hook {
|
|||||||
|
|
||||||
Log::Info("Hook installed");
|
Log::Info("Hook installed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ namespace GakumasLocal {
|
|||||||
struct HookInstaller
|
struct HookInstaller
|
||||||
{
|
{
|
||||||
virtual ~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;
|
virtual OpaqueFunctionPointer LookupSymbol(const char* name) = 0;
|
||||||
|
|
||||||
std::string m_il2cppLibraryPath;
|
std::string m_il2cppLibraryPath;
|
||||||
|
125
app/src/main/cpp/GakumasLocalify/camera/baseCamera.cpp
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
49
app/src/main/cpp/GakumasLocalify/camera/baseCamera.hpp
Normal 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; // 垂直方向角度
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
197
app/src/main/cpp/GakumasLocalify/camera/camera.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
app/src/main/cpp/GakumasLocalify/camera/camera.hpp
Normal 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();
|
||||||
|
}
|
24
app/src/main/cpp/GakumasLocalify/config/Config.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
15
app/src/main/cpp/GakumasLocalify/config/Config.hpp
Normal 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
|
@ -45,6 +45,7 @@
|
|||||||
|
|
||||||
#include "xdl.h"
|
#include "xdl.h"
|
||||||
#include "../../GakumasLocalify/Log.h"
|
#include "../../GakumasLocalify/Log.h"
|
||||||
|
#include "../../GakumasLocalify/Misc.h"
|
||||||
|
|
||||||
class UnityResolve final {
|
class UnityResolve final {
|
||||||
public:
|
public:
|
||||||
|
@ -5,8 +5,9 @@
|
|||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
#include "string"
|
#include "string"
|
||||||
#include "shadowhook.h"
|
#include "shadowhook.h"
|
||||||
|
|
||||||
#include "xdl.h"
|
#include "xdl.h"
|
||||||
|
#include "GakumasLocalify/camera/camera.hpp"
|
||||||
|
#include "GakumasLocalify/config/Config.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@ -24,9 +25,9 @@ namespace
|
|||||||
xdl_close(m_Il2CppLibrary);
|
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
|
GakumasLocal::OpaqueFunctionPointer LookupSymbol(const char* name) override
|
||||||
@ -44,8 +45,6 @@ extern "C"
|
|||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_initHook(JNIEnv *env, jclass clazz, jstring targetLibraryPath,
|
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_initHook(JNIEnv *env, jclass clazz, jstring targetLibraryPath,
|
||||||
jstring localizationFilesDir) {
|
jstring localizationFilesDir) {
|
||||||
GakumasLocal::Log::Info("Hello initHook!");
|
|
||||||
|
|
||||||
const auto targetLibraryPathChars = env->GetStringUTFChars(targetLibraryPath, nullptr);
|
const auto targetLibraryPathChars = env->GetStringUTFChars(targetLibraryPath, nullptr);
|
||||||
const std::string targetLibraryPathStr = targetLibraryPathChars;
|
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();
|
auto& plugin = GakumasLocal::Plugin::GetInstance();
|
||||||
plugin.InstallHook(std::make_unique<AndroidHookInstaller>(targetLibraryPathStr, localizationFilesDirCharsStr));
|
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);
|
||||||
}
|
}
|
@ -1,8 +1,13 @@
|
|||||||
package io.github.chinosk.gakumas.localify
|
package io.github.chinosk.gakumas.localify
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
import android.app.AndroidAppHelper
|
import android.app.AndroidAppHelper
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.bytedance.shadowhook.ShadowHook
|
import com.bytedance.shadowhook.ShadowHook
|
||||||
import com.bytedance.shadowhook.ShadowHook.ConfigBuilder
|
import com.bytedance.shadowhook.ShadowHook.ConfigBuilder
|
||||||
@ -12,20 +17,63 @@ import de.robv.android.xposed.XC_MethodHook
|
|||||||
import de.robv.android.xposed.XposedHelpers
|
import de.robv.android.xposed.XposedHelpers
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
|
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
|
||||||
import kotlinx.coroutines.currentCoroutineContext
|
import android.view.KeyEvent
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import de.robv.android.xposed.XposedBridge
|
||||||
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
||||||
private lateinit var modulePath: String
|
private lateinit var modulePath: String
|
||||||
|
private var nativeLibLoadSuccess: Boolean
|
||||||
private var alreadyInitialized = false
|
private var alreadyInitialized = false
|
||||||
private val TAG = "GakumasLocalify"
|
private val TAG = "GakumasLocalify"
|
||||||
|
private val targetPackageName = "com.bandainamcoent.idolmaster_gakuen"
|
||||||
|
private val nativeLibName = "MarryKotone"
|
||||||
|
|
||||||
|
private var gkmsDataInited = false
|
||||||
|
|
||||||
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
|
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
|
||||||
if (lpparam.packageName != "com.bandainamcoent.idolmaster_gakuen") {
|
if (lpparam.packageName != targetPackageName) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XposedHelpers.findAndHookMethod(
|
||||||
|
"android.app.Activity",
|
||||||
|
lpparam.classLoader,
|
||||||
|
"dispatchKeyEvent",
|
||||||
|
KeyEvent::class.java,
|
||||||
|
object : XC_MethodHook() {
|
||||||
|
override fun beforeHookedMethod(param: MethodHookParam) {
|
||||||
|
val keyEvent = param.args[0] as KeyEvent
|
||||||
|
val keyCode = keyEvent.keyCode
|
||||||
|
val action = keyEvent.action
|
||||||
|
// Log.d(TAG, "Key event: keyCode=$keyCode, action=$action")
|
||||||
|
keyboardEvent(keyCode, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val appActivityClass = XposedHelpers.findClass("android.app.Activity", lpparam.classLoader)
|
||||||
|
XposedBridge.hookAllMethods(appActivityClass, "onStart", object : XC_MethodHook() {
|
||||||
|
override fun beforeHookedMethod(param: MethodHookParam) {
|
||||||
|
super.beforeHookedMethod(param)
|
||||||
|
Log.d(TAG, "onStart")
|
||||||
|
val currActivity = param.thisObject as Activity
|
||||||
|
initGkmsConfig(currActivity)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
XposedBridge.hookAllMethods(appActivityClass, "onResume", object : XC_MethodHook() {
|
||||||
|
override fun beforeHookedMethod(param: MethodHookParam) {
|
||||||
|
Log.d(TAG, "onResume")
|
||||||
|
val currActivity = param.thisObject as Activity
|
||||||
|
initGkmsConfig(currActivity)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
val cls = lpparam.classLoader.loadClass("com.unity3d.player.UnityPlayer")
|
val cls = lpparam.classLoader.loadClass("com.unity3d.player.UnityPlayer")
|
||||||
XposedHelpers.findAndHookMethod(
|
XposedHelpers.findAndHookMethod(
|
||||||
cls,
|
cls,
|
||||||
@ -43,17 +91,66 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val app = AndroidAppHelper.currentApplication()
|
val app = AndroidAppHelper.currentApplication()
|
||||||
|
if (nativeLibLoadSuccess) {
|
||||||
|
showToast("lib$nativeLibName.so 已加载")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showToast("加载 native 库 lib$nativeLibName.so 失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gkmsDataInited) {
|
||||||
|
requestConfig(app.applicationContext)
|
||||||
|
}
|
||||||
|
|
||||||
FilesChecker.initAndCheck(app.filesDir, modulePath)
|
FilesChecker.initAndCheck(app.filesDir, modulePath)
|
||||||
|
initHook(
|
||||||
initHook("${app.applicationInfo.nativeLibraryDir}/libil2cpp.so",
|
"${app.applicationInfo.nativeLibraryDir}/libil2cpp.so",
|
||||||
File(app.filesDir.absolutePath, FilesChecker.localizationFilesDir).absolutePath)
|
File(
|
||||||
|
app.filesDir.absolutePath,
|
||||||
|
FilesChecker.localizationFilesDir
|
||||||
|
).absolutePath
|
||||||
|
)
|
||||||
|
|
||||||
alreadyInitialized = true
|
alreadyInitialized = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showToast(message: String) {
|
||||||
|
val app = AndroidAppHelper.currentApplication()
|
||||||
|
val context = app?.applicationContext
|
||||||
|
if (context != null) {
|
||||||
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
handler.post {
|
||||||
|
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log.e(TAG, "showToast: $message failed: applicationContext is null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initGkmsConfig(activity: Activity) {
|
||||||
|
val intent = activity.intent
|
||||||
|
val gkmsData = intent.getStringExtra("gkmsData")
|
||||||
|
if (gkmsData != null) {
|
||||||
|
gkmsDataInited = true
|
||||||
|
loadConfig(gkmsData)
|
||||||
|
Log.d(TAG, "gkmsData: $gkmsData")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestConfig(activity: Context) {
|
||||||
|
val intent = Intent().apply {
|
||||||
|
setClassName("io.github.chinosk.gakumas.localify", "io.github.chinosk.gakumas.localify.MainActivity")
|
||||||
|
putExtra("gkmsData", "芜湖")
|
||||||
|
flags = FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
// activity.startActivityForResult(intent, 114514)
|
||||||
|
activity.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
|
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
|
||||||
modulePath = startupParam.modulePath
|
modulePath = startupParam.modulePath
|
||||||
}
|
}
|
||||||
@ -61,6 +158,10 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun initHook(targetLibraryPath: String, localizationFilesDir: String)
|
external fun initHook(targetLibraryPath: String, localizationFilesDir: String)
|
||||||
|
@JvmStatic
|
||||||
|
external fun keyboardEvent(keyCode: Int, action: Int)
|
||||||
|
@JvmStatic
|
||||||
|
external fun loadConfig(configJsonStr: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -70,6 +171,12 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||||||
.setMode(ShadowHook.Mode.UNIQUE)
|
.setMode(ShadowHook.Mode.UNIQUE)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
System.loadLibrary("MarryKotone")
|
|
||||||
|
nativeLibLoadSuccess = try {
|
||||||
|
System.loadLibrary(nativeLibName)
|
||||||
|
true
|
||||||
|
} catch (e: UnsatisfiedLinkError) {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,48 +1,94 @@
|
|||||||
package io.github.chinosk.gakumas.localify
|
package io.github.chinosk.gakumas.localify
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
import android.content.Intent
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import io.github.chinosk.gakumas.localify.databinding.ActivityMainBinding
|
||||||
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
interface ConfigListener {
|
||||||
|
fun onClickStartGame()
|
||||||
|
fun onEnabledChanged(value: Boolean)
|
||||||
|
fun onEnableFreeCameraChanged(value: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity(), ConfigListener {
|
||||||
|
private lateinit var config: GakumasConfig
|
||||||
|
private val TAG = "GakumasLocalify"
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.i("GakumasLocal", "Hello Producer!")
|
setContentView(R.layout.activity_main)
|
||||||
setContent {
|
|
||||||
GakumasLocalifyTheme {
|
loadConfig()
|
||||||
// A surface container using the 'background' color from the theme
|
|
||||||
Surface(
|
val requestData = intent.getStringExtra("gkmsData")
|
||||||
modifier = Modifier.fillMaxSize(),
|
if (requestData != null) {
|
||||||
color = MaterialTheme.colorScheme.background
|
onClickStartGame()
|
||||||
) {
|
finish()
|
||||||
Greeting("Producer")
|
}
|
||||||
}
|
|
||||||
}
|
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
|
||||||
|
binding.config = config
|
||||||
|
binding.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showToast(message: String) {
|
||||||
|
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getConfigContent(): String {
|
||||||
|
val configFile = File(filesDir, "gkms-config.json")
|
||||||
|
return if (configFile.exists()) {
|
||||||
|
configFile.readText()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showToast("检测到第一次启动,初始化配置文件...")
|
||||||
|
"{}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
private fun loadConfig() {
|
||||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
val configStr = getConfigContent()
|
||||||
Text(
|
val config = try {
|
||||||
text = "Hello $name!",
|
Gson().fromJson(configStr, GakumasConfig::class.java)
|
||||||
modifier = modifier
|
}
|
||||||
)
|
catch (e: JsonSyntaxException) {
|
||||||
}
|
showToast("配置文件异常,已重置: $e")
|
||||||
|
Gson().fromJson("{}", GakumasConfig::class.java)
|
||||||
|
}
|
||||||
|
this.config = config
|
||||||
|
saveConfig()
|
||||||
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
private fun saveConfig() {
|
||||||
@Composable
|
val configFile = File(filesDir, "gkms-config.json")
|
||||||
fun GreetingPreview() {
|
configFile.writeText(Gson().toJson(config))
|
||||||
GakumasLocalifyTheme {
|
}
|
||||||
Greeting("Producer")
|
|
||||||
|
override fun onEnabledChanged(value: Boolean) {
|
||||||
|
config.enabled = value
|
||||||
|
saveConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEnableFreeCameraChanged(value: Boolean) {
|
||||||
|
config.enableFreeCamera = value
|
||||||
|
saveConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickStartGame() {
|
||||||
|
val intent = Intent().apply {
|
||||||
|
setClassName("com.bandainamcoent.idolmaster_gakuen", "com.google.firebase.MessagingUnityPlayerActivity")
|
||||||
|
putExtra("gkmsData", getConfigContent())
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.models
|
||||||
|
|
||||||
|
data class GakumasConfig(
|
||||||
|
var enabled: Boolean = true,
|
||||||
|
var enableFreeCamera: Boolean = false
|
||||||
|
)
|
||||||
|
|
BIN
app/src/main/play_store_512.png
Normal file
After Width: | Height: | Size: 423 KiB |
84
app/src/main/res/layout/activity_main.xml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<data>
|
||||||
|
<variable name="config" type="io.github.chinosk.gakumas.localify.models.GakumasConfig" />
|
||||||
|
<variable
|
||||||
|
name="listener"
|
||||||
|
type="io.github.chinosk.gakumas.localify.ConfigListener" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textViewTitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:text="@string/gakumas_localify" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="25sp" />
|
||||||
|
|
||||||
|
<TableLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TableRow
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/SwitchEnablePlugin"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:checked="@={config.enabled}"
|
||||||
|
android:onCheckedChanged="@{(view, value) -> listener.onEnabledChanged(value)}"
|
||||||
|
android:text="@string/enable_plugin" />
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/SwitchEnableFreeCameraPlugin"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:checked="@={config.enableFreeCamera}"
|
||||||
|
android:onCheckedChanged="@{(view, value) -> listener.onEnableFreeCameraChanged(value)}"
|
||||||
|
android:text="@string/enable_free_camera" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/StartGameButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:onClick="@{() -> listener.onClickStartGame()}"
|
||||||
|
android:text="@string/start_game" />
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
</TableLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@mipmap/ic_launcher_adaptive_back"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@mipmap/ic_launcher_adaptive_fore"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_back.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_back.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_adaptive_back.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png
Normal file
After Width: | Height: | Size: 170 KiB |
Before Width: | Height: | Size: 7.6 KiB |
@ -1,3 +1,7 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Gakumas Localify</string>
|
<string name="app_name">Gakumas Localify</string>
|
||||||
|
<string name="gakumas_localify">Gakumas Localify</string>
|
||||||
|
<string name="enable_plugin">启用插件 (不可热重载)</string>
|
||||||
|
<string name="enable_free_camera">启用自由视角(可热重载; 需使用实体键盘)</string>
|
||||||
|
<string name="start_game">以上述配置启动游戏/重载配置</string>
|
||||||
</resources>
|
</resources>
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<style name="Theme.GakumasLocalify" parent="@style/Theme.MaterialComponents.Light.NoActionBar" />
|
||||||
|
|
||||||
<style name="Theme.GakumasLocalify" parent="android:Theme.Material.Light.NoActionBar" />
|
|
||||||
</resources>
|
</resources>
|