diff --git a/app/src/main/cpp/GakumasLocalify/Hook.cpp b/app/src/main/cpp/GakumasLocalify/Hook.cpp index e01b143..c6b31cb 100644 --- a/app/src/main/cpp/GakumasLocalify/Hook.cpp +++ b/app/src/main/cpp/GakumasLocalify/Hook.cpp @@ -11,6 +11,7 @@ #include "shadowhook.h" #include #include +#include std::unordered_set hookedStubs{}; @@ -85,14 +86,18 @@ namespace GakumasLocal::HookMain { Log::LogUnityLog(ANDROID_LOG_VERBOSE, "Internal_Log:\n%s", content->ToString().c_str()); } + bool IsNativeObjectAlive(void* obj) { + static UnityResolve::Method* IsNativeObjectAliveMtd = NULL; + if (!IsNativeObjectAliveMtd) IsNativeObjectAliveMtd = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll", "UnityEngine", + "Object", "IsNativeObjectAlive"); + return IsNativeObjectAliveMtd->Invoke(obj); + } + 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(mainCameraCache)) return; + if (IsNativeObjectAlive(mainCameraCache)) return; mainCameraCache = UnityResolve::UnityType::Camera::GetMain(); cameraTransformCache = mainCameraCache->GetTransform(); @@ -137,44 +142,90 @@ namespace GakumasLocal::HookMain { return Unity_get_fieldOfView_Orig(_this); } + UnityResolve::UnityType::Transform* cacheTrans = NULL; + UnityResolve::UnityType::Quaternion cacheRotation{}; + UnityResolve::UnityType::Vector3 cachePosition{}; + UnityResolve::UnityType::Vector3 cacheForward{}; + UnityResolve::UnityType::Vector3 cacheLookAt{}; + + DEFINE_HOOK(void, Unity_set_rotation_Injected, (UnityResolve::UnityType::Transform* _this, UnityResolve::UnityType::Quaternion* value)) { + if (Config::enableFreeCamera) { + static auto lookat_injected = reinterpret_cast( + Il2cppUtils::il2cpp_resolve_icall( + "UnityEngine.Transform::Internal_LookAt_Injected(UnityEngine.Vector3&,UnityEngine.Vector3&)")); + static auto worldUp = UnityResolve::UnityType::Vector3(0, 1, 0); + + if (cameraTransformCache == _this) { + const auto cameraMode = GKCamera::GetCameraMode(); + if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) { + if (cacheTrans && IsNativeObjectAlive(cacheTrans)) { + if (GKCamera::GetFirstPersonRoll() == GKCamera::FirstPersonRoll::ENABLE_ROLL) { + *value = cacheRotation; + } + else { + lookat_injected(_this, &cacheLookAt, &worldUp); + return; + } + } + } + else if (cameraMode == GKCamera::CameraMode::FOLLOW) { + auto newLookAtPos = GKCamera::CalcFollowModeLookAt(cachePosition, + GKCamera::followPosOffset, true); + lookat_injected(_this, &newLookAtPos, &worldUp); + return; + } + else { + auto& origCameraLookat = GKCamera::baseCamera.lookAt; + lookat_injected(_this, &origCameraLookat, &worldUp); + // Log::DebugFmt("fov: %f, target: %f", Unity_get_fieldOfView_Orig(mainCameraCache), GKCamera::baseCamera.fov); + return; + } + } + } + return Unity_set_rotation_Injected_Orig(_this, value); + } + 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; + const auto cameraMode = GKCamera::GetCameraMode(); + if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) { + if (cacheTrans && IsNativeObjectAlive(cacheTrans)) { + *data = GKCamera::CalcFirstPersonPosition(cachePosition, cacheForward, GKCamera::firstPersonPosOffset); + } + + } + else if (cameraMode == GKCamera::CameraMode::FOLLOW) { + auto newLookAtPos = GKCamera::CalcFollowModeLookAt(cachePosition, GKCamera::followPosOffset); + auto pos = GKCamera::CalcPositionFromLookAt(newLookAtPos, GKCamera::followPosOffset); + data->x = pos.x; + data->y = pos.y; + data->z = pos.z; + } + else { + //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( - 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); - // Log::DebugFmt("fov: %f, target: %f", Unity_get_fieldOfView_Orig(mainCameraCache), GKCamera::baseCamera.fov); - return; - } - } - return Unity_set_rotation_Injected_Orig(_this, value); - } - DEFINE_HOOK(void, EndCameraRendering, (void* ctx, void* camera, void* method)) { EndCameraRendering_Orig(ctx, camera, method); if (Config::enableFreeCamera && mainCameraCache) { Unity_set_fieldOfView_Orig(mainCameraCache, GKCamera::baseCamera.fov); + if (GKCamera::GetCameraMode() == GKCamera::CameraMode::FIRST_PERSON) { + mainCameraCache->SetNearClipPlane(0.001f); + } } } @@ -243,10 +294,8 @@ namespace GakumasLocal::HookMain { "UnityEngine", "Font"); static auto Font_ctor = Il2cppUtils::GetMethod("UnityEngine.TextRenderingModule.dll", "UnityEngine", "Font", ".ctor"); - static auto IsNativeObjectAlive = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll", "UnityEngine", - "Object", "IsNativeObjectAlive"); if (fontCache) { - if (IsNativeObjectAlive->Invoke(fontCache)) { + if (IsNativeObjectAlive(fontCache)) { return fontCache; } } @@ -390,10 +439,10 @@ namespace GakumasLocal::HookMain { PictureBookLiveThumbnailView_SetData_Orig(_this, liveData, isUnlocked, isNew); } - + bool needRestoreHides = false; DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_MoveLiveScene, (void* _this, void* produceLive, Il2cppString* characterId, Il2cppString* costumeId, Il2cppString* costumeHeadId)) { - + needRestoreHides = false; Log::InfoFmt("MoveLiveScene: characterId: %s, costumeId: %s, costumeHeadId: %s,", characterId->ToString().c_str(), costumeId->ToString().c_str(), costumeHeadId->ToString().c_str()); @@ -517,6 +566,136 @@ namespace GakumasLocal::HookMain { return VLUtility_GetLimitedResolution_Orig(screenWidth, screenHeight, aspectRatio, maxBufferPixel, bufferScale, firstCall); } + + DEFINE_HOOK(void, CampusActorModelParts_OnRegisterBone, (void* _this, Il2cppString** name, UnityResolve::UnityType::Transform* bone)) { + CampusActorModelParts_OnRegisterBone_Orig(_this, name, bone); + // Log::DebugFmt("CampusActorModelParts_OnRegisterBone: %s, %p", (*name)->ToString().c_str(), bone); + } + + bool InitBodyParts() { + static auto isInit = false; + if (isInit) return true; + + const auto Enum_GetValues = Il2cppUtils::GetMethod("mscorlib.dll", "System", "Enum", "GetValues"); + const auto Enum_GetNames = Il2cppUtils::GetMethod("mscorlib.dll", "System", "Enum", "GetNames"); + + const auto HumanBodyBones_klass = Il2cppUtils::GetClass( + "UnityEngine.AnimationModule.dll", "UnityEngine", "HumanBodyBones"); + + const auto values = Enum_GetValues->Invoke*>(HumanBodyBones_klass->GetType())->ToVector(); + const auto names = Enum_GetNames->Invoke*>(HumanBodyBones_klass->GetType())->ToVector(); + if (values.size() != names.size()) { + Log::ErrorFmt("InitBodyParts Error: values count: %ld, names count: %ld", values.size(), names.size()); + return false; + } + + std::vector namesVec{}; + for (auto i :names) { + namesVec.push_back(i->ToString()); + } + GKCamera::bodyPartsEnum = Misc::CSEnum(namesVec, values); + GKCamera::bodyPartsEnum.SetIndex(GKCamera::bodyPartsEnum.GetValueByName("Head")); + isInit = true; + return true; + } + + void HideHead(UnityResolve::UnityType::GameObject* obj, const bool isFace) { + static UnityResolve::UnityType::GameObject* lastFaceObj = nullptr; + static UnityResolve::UnityType::GameObject* lastHairObj = nullptr; + +#define lastHidedObj (isFace ? lastFaceObj : lastHairObj) + + static auto get_activeInHierarchy = reinterpret_cast( + Il2cppUtils::il2cpp_resolve_icall("UnityEngine.GameObject::get_activeInHierarchy()")); + + const auto isFirstPerson = GKCamera::GetCameraMode() == GKCamera::CameraMode::FIRST_PERSON; + + if (isFirstPerson && obj) { + if (obj == lastHidedObj) return; + if (lastHidedObj && IsNativeObjectAlive(lastHidedObj) && get_activeInHierarchy(lastHidedObj)) { + lastHidedObj->SetActive(true); + } + if (IsNativeObjectAlive(obj)) { + obj->SetActive(false); + lastHidedObj = obj; + } + } + else { + if (lastHidedObj && IsNativeObjectAlive(lastHidedObj)) { + lastHidedObj->SetActive(true); + lastHidedObj = nullptr; + } + } + } + + DEFINE_HOOK(void, CampusActorController_LateUpdate, (void* _this, void* mtd)) { + if (!Config::enableFreeCamera || (GKCamera::GetCameraMode() == GKCamera::CameraMode::FREE)) { + if (needRestoreHides) { + needRestoreHides = false; + HideHead(NULL, false); + HideHead(NULL, true); + } + return CampusActorController_LateUpdate_Orig(_this, mtd); + } + + static auto CampusActorController_klass = Il2cppUtils::GetClass("campus-submodule.Runtime.dll", + "Campus.Common", "CampusActorController"); + static auto rootBody_field = CampusActorController_klass->Get("_rootBody"); + static auto parentKlass = UnityResolve::Invoke("il2cpp_class_get_parent", CampusActorController_klass->address); + static auto GetHumanBodyBoneTransform_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(parentKlass, "GetHumanBodyBoneTransform", 1); + static auto GetHumanBodyBoneTransform = reinterpret_cast( + GetHumanBodyBoneTransform_mtd->methodPointer + ); + static auto get_index_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(CampusActorController_klass->address, "get_index", 0); + static auto get_Index = get_index_mtd ? reinterpret_cast( + get_index_mtd->methodPointer) : [](void*){return 0;}; + + const auto currIndex = get_Index(_this); + if (currIndex == GKCamera::followCharaIndex) { + static auto initPartsSuccess = InitBodyParts(); + static auto headBodyId = initPartsSuccess ? GKCamera::bodyPartsEnum.GetValueByName("Head") : 0xA; + const auto isFirstPerson = GKCamera::GetCameraMode() == GKCamera::CameraMode::FIRST_PERSON; + + auto targetTrans = GetHumanBodyBoneTransform(_this, + isFirstPerson ? headBodyId : GKCamera::bodyPartsEnum.GetCurrent().second); + + if (targetTrans) { + cacheTrans = targetTrans; + cacheRotation = cacheTrans->GetRotation(); + cachePosition = cacheTrans->GetPosition(); + cacheForward = cacheTrans->GetForward(); + cacheLookAt = cacheTrans->GetPosition() + cacheTrans->GetForward() * 3; + + auto rootBody = Il2cppUtils::ClassGetFieldValue(_this, rootBody_field); + auto rootModel = rootBody->GetParent(); + auto rootModelChildCount = rootModel->GetChildCount(); + for (int i = 0; i < rootModelChildCount; i++) { + auto rootChild = rootModel->GetChild(i); + const auto childName = rootChild->GetName(); + if (childName == "Root_Face") { + for (int n = 0; n < rootChild->GetChildCount(); n++) { + auto vLSkinningRenderer = rootChild->GetChild(n); + if (vLSkinningRenderer->GetName() == "VLSkinningRenderer") { + HideHead(vLSkinningRenderer->GetGameObject(), true); + needRestoreHides = true; + } + } + } + else if (childName == "Root_Hair") { + HideHead(rootChild->GetGameObject(), false); + needRestoreHides = true; + } + } + } + else { + cacheTrans = NULL; + } + + } + + CampusActorController_LateUpdate_Orig(_this, mtd); + } + void StartInjectFunctions() { const auto hookInstaller = Plugin::GetInstance().GetHookInstaller(); UnityResolve::Init(xdl_open(hookInstaller->m_il2cppLibraryPath.c_str(), RTLD_NOW), UnityResolve::Mode::Il2Cpp); @@ -589,6 +768,20 @@ namespace GakumasLocal::HookMain { "VLUtility", "GetLimitedResolution", {"*", "*", "*", "*", "*", "*"})); + ADD_HOOK(CampusActorModelParts_OnRegisterBone, + Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common", + "CampusActorModelParts", "OnRegisterBone")); + ADD_HOOK(CampusActorController_LateUpdate, + Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common", + "CampusActorController", "LateUpdate")); + + static auto CampusActorController_klass = Il2cppUtils::GetClass("campus-submodule.Runtime.dll", + "Campus.Common", "CampusActorController"); + for (const auto& i : CampusActorController_klass->methods) { + Log::DebugFmt("CampusActorController.%s at %p", i->name.c_str(), i->function); + } + + ADD_HOOK(CampusQualityManager_set_TargetFrameRate, Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common", "CampusQualityManager", "set_TargetFrameRate")); diff --git a/app/src/main/cpp/GakumasLocalify/Il2cppUtils.hpp b/app/src/main/cpp/GakumasLocalify/Il2cppUtils.hpp index 580c5f6..9d0e942 100644 --- a/app/src/main/cpp/GakumasLocalify/Il2cppUtils.hpp +++ b/app/src/main/cpp/GakumasLocalify/Il2cppUtils.hpp @@ -125,6 +125,9 @@ namespace Il2cppUtils { return static_cast(*static_cast(std::assume_aligned(instance))); } + MethodInfo* il2cpp_class_get_method_from_name(void* klass, const char* name, int argsCount) { + return UnityResolve::Invoke("il2cpp_class_get_method_from_name", klass, name, argsCount); + } void* find_nested_class(void* klass, std::predicate auto&& predicate) { @@ -147,4 +150,15 @@ namespace Il2cppUtils { }); } + template + auto ClassGetFieldValue(void* obj, UnityResolve::Field* field) -> RType { + return *reinterpret_cast(reinterpret_cast(obj) + field->offset); + } + + template + auto ClassSetFieldValue(void* obj, UnityResolve::Field* field, RType value) -> void { + return *reinterpret_cast(reinterpret_cast(obj) + field->offset) = value; + } + + } diff --git a/app/src/main/cpp/GakumasLocalify/Log.cpp b/app/src/main/cpp/GakumasLocalify/Log.cpp index 80d20ab..6272f1f 100644 --- a/app/src/main/cpp/GakumasLocalify/Log.cpp +++ b/app/src/main/cpp/GakumasLocalify/Log.cpp @@ -1,6 +1,6 @@ #include "Log.h" #include -#include +#include #include #include #include diff --git a/app/src/main/cpp/GakumasLocalify/Misc.cpp b/app/src/main/cpp/GakumasLocalify/Misc.cpp index 1745880..1ac4204 100644 --- a/app/src/main/cpp/GakumasLocalify/Misc.cpp +++ b/app/src/main/cpp/GakumasLocalify/Misc.cpp @@ -1,4 +1,4 @@ -#include "Misc.h" +#include "Misc.hpp" #include #include @@ -31,4 +31,68 @@ namespace GakumasLocal::Misc { return env; } -} // namespace UmaPyogin::Misc + CSEnum::CSEnum(const std::string& name, const int value) { + this->Add(name, value); + } + + CSEnum::CSEnum(const std::vector& names, const std::vector& values) { + if (names.size() != values.size()) return; + this->names = names; + this->values = values; + } + + int CSEnum::GetIndex() { + return currIndex; + } + + void CSEnum::SetIndex(int index) { + if (index < 0) return; + if (index + 1 >= values.size()) return; + currIndex = index; + } + + int CSEnum::GetTotalLength() { + return values.size(); + } + + void CSEnum::Add(const std::string &name, const int value) { + this->names.push_back(name); + this->values.push_back(value); + } + + std::pair CSEnum::GetCurrent() { + return std::make_pair(names[currIndex], values[currIndex]); + } + + std::pair CSEnum::Last() { + const auto maxIndex = this->GetTotalLength() - 1; + if (currIndex <= 0) { + currIndex = maxIndex; + } + else { + currIndex--; + } + return this->GetCurrent(); + } + + std::pair CSEnum::Next() { + const auto maxIndex = this->GetTotalLength() - 1; + if (currIndex >= maxIndex) { + currIndex = 0; + } + else { + currIndex++; + } + return this->GetCurrent(); + } + + int CSEnum::GetValueByName(const std::string &name) { + for (int i = 0; i < names.size(); i++) { + if (names[i].compare(name) == 0) { + return values[i]; + } + } + return values[0]; + } + +} diff --git a/app/src/main/cpp/GakumasLocalify/Misc.h b/app/src/main/cpp/GakumasLocalify/Misc.h deleted file mode 100644 index 3932d3e..0000000 --- a/app/src/main/cpp/GakumasLocalify/Misc.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef GAKUMAS_LOCALIFY_MISC_H -#define GAKUMAS_LOCALIFY_MISC_H - -#include -#include -#include - -namespace GakumasLocal { - using OpaqueFunctionPointer = void (*)(); - - namespace Misc { - std::u16string ToUTF16(const std::string_view& str); - std::string ToUTF8(const std::u16string_view& str); - JNIEnv* GetJNIEnv(); - } -} - -#endif //GAKUMAS_LOCALIFY_MISC_H diff --git a/app/src/main/cpp/GakumasLocalify/Misc.hpp b/app/src/main/cpp/GakumasLocalify/Misc.hpp new file mode 100644 index 0000000..7da646f --- /dev/null +++ b/app/src/main/cpp/GakumasLocalify/Misc.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include +#include + + +namespace GakumasLocal { + using OpaqueFunctionPointer = void (*)(); + + namespace Misc { + std::u16string ToUTF16(const std::string_view& str); + std::string ToUTF8(const std::u16string_view& str); + JNIEnv* GetJNIEnv(); + + class CSEnum { + public: + CSEnum(const std::string& name, const int value); + + CSEnum(const std::vector& names, const std::vector& values); + + int GetIndex(); + + void SetIndex(int index); + + int GetTotalLength(); + + void Add(const std::string& name, const int value); + + std::pair GetCurrent(); + + std::pair Last(); + + std::pair Next(); + + int GetValueByName(const std::string& name); + + private: + int currIndex = 0; + std::vector names{}; + std::vector values{}; + + }; + + template + class FixedSizeQueue { + static_assert(std::is_arithmetic::value, "T must be an arithmetic type"); + + public: + FixedSizeQueue(size_t maxSize) : maxSize(maxSize), sum(0) {} + + void Push(T value) { + if (deque.size() >= maxSize) { + sum -= deque.front(); + deque.pop_front(); + } + deque.push_back(value); + sum += value; + } + + float Average() { + if (deque.empty()) { + return 0.0; + } + return static_cast(sum) / deque.size(); + } + + private: + std::deque deque; + size_t maxSize; + T sum; + }; + } +} diff --git a/app/src/main/cpp/GakumasLocalify/Plugin.h b/app/src/main/cpp/GakumasLocalify/Plugin.h index d4e986e..ac63013 100644 --- a/app/src/main/cpp/GakumasLocalify/Plugin.h +++ b/app/src/main/cpp/GakumasLocalify/Plugin.h @@ -1,7 +1,7 @@ #ifndef GAKUMAS_LOCALIFY_PLUGIN_H #define GAKUMAS_LOCALIFY_PLUGIN_H -#include "Misc.h" +#include "Misc.hpp" #include #include #include diff --git a/app/src/main/cpp/GakumasLocalify/camera/camera.cpp b/app/src/main/cpp/GakumasLocalify/camera/camera.cpp index 72491cc..fc5b11f 100644 --- a/app/src/main/cpp/GakumasLocalify/camera/camera.cpp +++ b/app/src/main/cpp/GakumasLocalify/camera/camera.cpp @@ -1,5 +1,7 @@ #include "baseCamera.hpp" +#include "camera.hpp" #include +#include "Misc.hpp" #define KEY_W 51 #define KEY_S 47 @@ -8,11 +10,12 @@ #define KEY_R 46 #define KEY_Q 45 #define KEY_E 33 +#define KEY_F 34 #define KEY_I 37 #define KEY_K 39 #define KEY_J 38 #define KEY_L 40 -#define KEY_R 46 +#define KEY_V 50 #define KEY_UP 19 #define KEY_DOWN 20 #define KEY_LEFT 21 @@ -28,42 +31,139 @@ namespace GKCamera { BaseCamera::Camera baseCamera{}; + CameraMode cameraMode = CameraMode::FREE; + FirstPersonRoll firstPersonRoll = FirstPersonRoll::ENABLE_ROLL; + FollowModeY followModeY = FollowModeY::SMOOTH_Y; - bool rMousePressFlg = false; + UnityResolve::UnityType::Vector3 firstPersonPosOffset{0, 0.064f, 0.000f}; + UnityResolve::UnityType::Vector3 followPosOffset{0, 0, 1.5}; + UnityResolve::UnityType::Vector2 followLookAtOffset{0, 0}; + float offsetMoveStep = 0.008; + int followCharaIndex = 0; + GakumasLocal::Misc::CSEnum bodyPartsEnum("Head", 0xa); - void reset_camera() { + // bool rMousePressFlg = false; + + void SetCameraMode(CameraMode mode) { + cameraMode = mode; + } + + CameraMode GetCameraMode() { + return cameraMode; + } + + void SetFirstPersonRoll(FirstPersonRoll mode) { + firstPersonRoll = mode; + } + + FirstPersonRoll GetFirstPersonRoll() { + return firstPersonRoll; + } + + + void reset_camera() { + followCharaIndex = 0; + firstPersonPosOffset = {0, 0.064f, 0.000f}; // f3: 0.008f + followPosOffset = {0, 0, 1.5}; + followLookAtOffset = {0, 0}; baseCamera.reset(); } void camera_forward() { // 向前 - baseCamera.set_lon_move(0, LonMoveHState::LonMoveForward); + switch (cameraMode) { + case CameraMode::FREE: { + baseCamera.set_lon_move(0, LonMoveHState::LonMoveForward); + } break; + case CameraMode::FIRST_PERSON: { + firstPersonPosOffset.z += offsetMoveStep; + } break; + case CameraMode::FOLLOW: { + followPosOffset.z -= offsetMoveStep; + } + } + } void camera_back() { // 后退 - baseCamera.set_lon_move(180, LonMoveHState::LonMoveBack); + switch (cameraMode) { + case CameraMode::FREE: { + baseCamera.set_lon_move(180, LonMoveHState::LonMoveBack); + } break; + case CameraMode::FIRST_PERSON: { + firstPersonPosOffset.z -= offsetMoveStep; + } break; + case CameraMode::FOLLOW: { + followPosOffset.z += offsetMoveStep; + } + } } void camera_left() { // 向左 - baseCamera.set_lon_move(90); + switch (cameraMode) { + case CameraMode::FREE: { + baseCamera.set_lon_move(90); + } break; + case CameraMode::FOLLOW: { + // followPosOffset.x += 0.8; + followLookAtOffset.x += offsetMoveStep; + } + default: + break; + } + } void camera_right() { // 向右 - baseCamera.set_lon_move(-90); + switch (cameraMode) { + case CameraMode::FREE: { + baseCamera.set_lon_move(-90); + } break; + case CameraMode::FOLLOW: { + // followPosOffset.x -= 0.8; + followLookAtOffset.x -= offsetMoveStep; + } + default: + break; + } } + void camera_down() { // 向下 - float preStep = BaseCamera::moveStep / BaseCamera::smoothLevel; + switch (cameraMode) { + case CameraMode::FREE: { + 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)); - } + 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)); + } + } break; + case CameraMode::FIRST_PERSON: { + firstPersonPosOffset.y -= offsetMoveStep; + } break; + case CameraMode::FOLLOW: { + // followPosOffset.y -= offsetMoveStep; + followLookAtOffset.y -= offsetMoveStep; + } + } } - 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 camera_up() { // 向上 + switch (cameraMode) { + case CameraMode::FREE: { + 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)); + } + } break; + case CameraMode::FIRST_PERSON: { + firstPersonPosOffset.y += offsetMoveStep; + } break; + case CameraMode::FOLLOW: { + // followPosOffset.y += offsetMoveStep; + followLookAtOffset.y += offsetMoveStep; + } + } } void cameraLookat_up(float mAngel, bool mouse = false) { baseCamera.horizontalAngle += mAngel; @@ -89,6 +189,173 @@ namespace GKCamera { baseCamera.fov += value; } + void SwitchCameraMode() { + switch (cameraMode) { + case CameraMode::FREE: { + cameraMode = CameraMode::FOLLOW; + GakumasLocal::Log::Info("CameraMode: FOLLOW"); + } break; + case CameraMode::FOLLOW: { + cameraMode = CameraMode::FIRST_PERSON; + GakumasLocal::Log::Info("CameraMode: FIRST_PERSON"); + } break; + case CameraMode::FIRST_PERSON: { + cameraMode = CameraMode::FREE; + GakumasLocal::Log::Info("CameraMode: FREE"); + + } break; + } + } + + void SwitchCameraSubMode() { + switch (cameraMode) { + case CameraMode::FIRST_PERSON: { + if (firstPersonRoll == FirstPersonRoll::ENABLE_ROLL) { + firstPersonRoll = FirstPersonRoll::DISABLE_ROLL; + GakumasLocal::Log::Info("FirstPersonRoll: DISABLE_ROLL"); + } + else { + firstPersonRoll = FirstPersonRoll::ENABLE_ROLL; + GakumasLocal::Log::Info("FirstPersonRoll: ENABLE_ROLL"); + } + } break; + + case CameraMode::FOLLOW: { + if (followModeY == FollowModeY::APPLY_Y) { + followModeY = FollowModeY::SMOOTH_Y; + GakumasLocal::Log::Info("FollowModeY: SMOOTH_Y"); + } + else { + followModeY = FollowModeY::APPLY_Y; + GakumasLocal::Log::Info("FollowModeY: APPLY_Y"); + } + } break; + + default: break; + } + } + + void OnLeftDown() { + if (cameraMode == CameraMode::FREE) return; + if (followCharaIndex >= 1) { + followCharaIndex--; + } + } + + void OnRightDown() { + if (cameraMode == CameraMode::FREE) return; + followCharaIndex++; + } + + void OnUpDown() { + if (cameraMode == CameraMode::FOLLOW) { + const auto currPart = bodyPartsEnum.Last(); + GakumasLocal::Log::InfoFmt("Look at: %s (0x%x)", currPart.first.c_str(), currPart.second); + } + } + + void OnDownDown() { + if (cameraMode == CameraMode::FOLLOW) { + const auto currPart = bodyPartsEnum.Next(); + GakumasLocal::Log::InfoFmt("Look at: %s (0x%x)", currPart.first.c_str(), currPart.second); + } + } + + void ChangeLiveFollowCameraOffsetY(const float value) { + if (cameraMode == CameraMode::FOLLOW) { + followPosOffset.y += value; + } + } + + void ChangeLiveFollowCameraOffsetX(const float value) { + if (cameraMode == CameraMode::FOLLOW) { + followPosOffset.x += value; + } + } + + UnityResolve::UnityType::Vector3 CalcPositionFromLookAt(const UnityResolve::UnityType::Vector3& target, + const UnityResolve::UnityType::Vector3& offset) { + // offset: z 远近, y 高低, x角度 + const float angleX = offset.x; + const float distanceZ = offset.z; + const float angleRad = angleX * (M_PI / 180.0f); + const float newX = target.x + distanceZ * std::sin(angleRad); + const float newZ = target.z + distanceZ * std::cos(angleRad); + const float newY = target.y + offset.y; + return UnityResolve::UnityType::Vector3(newX, newY, newZ); + } + + + float CheckNewY(const UnityResolve::UnityType::Vector3& targetPos, const bool recordY) { + static GakumasLocal::Misc::FixedSizeQueue recordsY(60); + const auto currentY = targetPos.y; + static auto lastRetY = currentY; + + if (followModeY == FollowModeY::APPLY_Y) { + lastRetY = currentY; + return currentY; + } + + const auto currentAvg = recordsY.Average(); + // GakumasLocal::Log::DebugFmt("currentY: %f, currentAvg: %f, diff: %f", currentY, currentAvg, abs(currentY - currentAvg)); + + if (recordY) { + recordsY.Push(currentY); + } + + if (abs(currentY - currentAvg) < 0.02) { + return lastRetY; + } + + const auto retAvg = recordsY.Average(); + lastRetY = lastRetY + (retAvg - lastRetY) / 8; + return lastRetY; + } + + UnityResolve::UnityType::Vector3 CalcFollowModeLookAt(const UnityResolve::UnityType::Vector3& targetPos, + const UnityResolve::UnityType::Vector3& posOffset, + const bool recordY) { + const float angleX = posOffset.x; + const float angleRad = (angleX + (followPosOffset.z >= 0 ? 90.0f : -90.0f)) * (M_PI / 180.0f); + + UnityResolve::UnityType::Vector3 newTargetPos = targetPos; + newTargetPos.y = CheckNewY(targetPos, recordY); + + const float offsetX = followLookAtOffset.x * sin(angleRad); + const float offsetZ = followLookAtOffset.x * cos(angleRad); + + newTargetPos.x += offsetX; + newTargetPos.z += offsetZ; + newTargetPos.y += followLookAtOffset.y; + + return newTargetPos; + } + + UnityResolve::UnityType::Vector3 CalcFirstPersonPosition(const UnityResolve::UnityType::Vector3& position, + const UnityResolve::UnityType::Vector3& forward, + const UnityResolve::UnityType::Vector3& offset) { + using Vector3 = UnityResolve::UnityType::Vector3; + + // 计算角色的右方向 + Vector3 up(0, 1, 0); // Y轴方向 + Vector3 right = forward.cross(up).Normalize(); + Vector3 fwd = forward; + Vector3 pos = position; + + // 计算角色的左方向 + Vector3 left = right * -1.0f; + + // 计算最终位置 + Vector3 backwardOffset = fwd * -offset.z; + Vector3 leftOffset = left * offset.x; + + Vector3 finalPosition = pos + backwardOffset + leftOffset; + finalPosition.y += offset.y; + + return finalPosition; + + } + struct CameraMoveState { bool w = false; bool s = false; @@ -137,10 +404,10 @@ namespace GKCamera { 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); + if (cameraMoveState.i) ChangeLiveFollowCameraOffsetY(offsetMoveStep); + if (cameraMoveState.k) ChangeLiveFollowCameraOffsetY(-offsetMoveStep); + if (cameraMoveState.j) ChangeLiveFollowCameraOffsetX(0.8); + if (cameraMoveState.l) ChangeLiveFollowCameraOffsetX(-0.8); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } }).detach(); @@ -161,14 +428,30 @@ namespace GKCamera { 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_UP: { + if (message == WM_KEYDOWN) { + OnUpDown(); + } + cameraMoveState.up = message == WM_KEYDOWN; + } break; + case KEY_DOWN: { + if (message == WM_KEYDOWN) { + OnDownDown(); + } + cameraMoveState.down = message == WM_KEYDOWN; + } break; + case KEY_LEFT: { + if (message == WM_KEYDOWN) { + OnLeftDown(); + } + cameraMoveState.left = message == WM_KEYDOWN; + } break; + case KEY_RIGHT: { + if (message == WM_KEYDOWN) { + OnRightDown(); + } + cameraMoveState.right = message == WM_KEYDOWN; + } break; case KEY_Q: cameraMoveState.q = message == WM_KEYDOWN; break; case KEY_E: @@ -183,7 +466,9 @@ namespace GKCamera { cameraMoveState.l = message == WM_KEYDOWN; break; case KEY_R: { if (message == WM_KEYDOWN) reset_camera(); - }; break; + } break; + case KEY_F: if (message == WM_KEYDOWN) SwitchCameraMode(); break; + case KEY_V: if (message == WM_KEYDOWN) SwitchCameraSubMode(); break; default: break; } } diff --git a/app/src/main/cpp/GakumasLocalify/camera/camera.hpp b/app/src/main/cpp/GakumasLocalify/camera/camera.hpp index 0be5fcd..7ce2ebe 100644 --- a/app/src/main/cpp/GakumasLocalify/camera/camera.hpp +++ b/app/src/main/cpp/GakumasLocalify/camera/camera.hpp @@ -2,7 +2,43 @@ #include "baseCamera.hpp" namespace GKCamera { - extern BaseCamera::Camera baseCamera; + enum class CameraMode { + FREE, + FIRST_PERSON, + FOLLOW + }; + + enum class FirstPersonRoll { + ENABLE_ROLL, + DISABLE_ROLL + }; + + enum class FollowModeY { + APPLY_Y, + SMOOTH_Y + }; + + void SetCameraMode(CameraMode mode); + CameraMode GetCameraMode(); + void SetFirstPersonRoll(FirstPersonRoll mode); + FirstPersonRoll GetFirstPersonRoll(); + + extern BaseCamera::Camera baseCamera; + extern UnityResolve::UnityType::Vector3 firstPersonPosOffset; + extern UnityResolve::UnityType::Vector3 followPosOffset; + extern int followCharaIndex; + extern GakumasLocal::Misc::CSEnum bodyPartsEnum; + + UnityResolve::UnityType::Vector3 CalcPositionFromLookAt(const UnityResolve::UnityType::Vector3& target, + const UnityResolve::UnityType::Vector3& offset); + + UnityResolve::UnityType::Vector3 CalcFirstPersonPosition(const UnityResolve::UnityType::Vector3& position, + const UnityResolve::UnityType::Vector3& forward, + const UnityResolve::UnityType::Vector3& offset); + + UnityResolve::UnityType::Vector3 CalcFollowModeLookAt(const UnityResolve::UnityType::Vector3& targetPos, + const UnityResolve::UnityType::Vector3& posOffset, + const bool recordY = false); void on_cam_rawinput_keyboard(int message, int key); void initCameraSettings(); diff --git a/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp b/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp index 9cd69c7..d199bf5 100644 --- a/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp +++ b/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp @@ -45,7 +45,7 @@ #include "xdl.h" #include "../../GakumasLocalify/Log.h" -#include "../../GakumasLocalify/Misc.h" +#include "../../GakumasLocalify/Misc.hpp" class UnityResolve final { public: @@ -906,6 +906,14 @@ public: } auto operator ==(const Vector3 x) const -> bool { return this->x == x.x && this->y == x.y && this->z == x.z; } + + Vector3 cross(const Vector3& other) const { + return Vector3( + y * other.z - z * other.y, + z * other.x - x * other.z, + x * other.y - y * other.x + ); + } }; struct Vector2 { @@ -1580,6 +1588,14 @@ public: if (!method) method = Get("UnityEngine.CoreModule.dll")->Get("Object")->Get("Destroy", { "*" }); if (method) return method->Invoke(original); } + + static auto op_Implicit(UnityObject* exists) -> bool { + if (!exists) return false; + static Method* method; + if (!method) method = Get("UnityEngine.CoreModule.dll")->Get("Object", "UnityEngine")->Get("op_Implicit"); + if (method) return method->Invoke(exists); + return false; + } }; struct Component : UnityObject { @@ -1816,6 +1832,20 @@ public: if (!method) method = Get("UnityEngine.CoreModule.dll")->Get("Camera")->Get("get_orthographicSize"); return method->Invoke(this); } + + void SetNearClipPlane(float value) { + if (!this) return; + static Method* method; + if (!method) method = Get("UnityEngine.CoreModule.dll")->Get("Camera")->Get("set_nearClipPlane"); + return method->Invoke(this, value); + } + + float GetNearClipPlane() { + if (!this) return -1; + static Method* method; + if (!method) method = Get("UnityEngine.CoreModule.dll")->Get("Camera")->Get("get_nearClipPlane"); + return method->Invoke(this); + } }; struct Transform : Component {