├── CameraOoT ├── CameraOoT.vcxproj.user ├── packages.config ├── Proc.h ├── CameraOoT.user ├── Time.h ├── Time.cpp ├── CameraOoT.cfg ├── CameraOoT.filters ├── Proc.cpp ├── CameraOoT.vcxproj └── Main.cpp ├── .gitignore ├── README.md ├── CameraOoT.sln └── citra-qt.CT /CameraOoT/CameraOoT.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /CameraOoT/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CameraOoT/Proc.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINE_PROC 2 | #define ENGINE_PROC 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | DWORD GetProcId(const wchar_t* proc_name); 11 | uintptr_t SearchInProcessMemory(HANDLE h_process, const uint8_t* pattern, const char* mask); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /.vs/CameraOoT 6 | /CameraOoT/x64/Debug 7 | /x64/Debug 8 | /packages 9 | /CameraOoT/x64/Release 10 | /x64/Release 11 | -------------------------------------------------------------------------------- /CameraOoT/CameraOoT.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WindowsLocalDebugger 5 | 6 | 7 | WindowsLocalDebugger 8 | 9 | -------------------------------------------------------------------------------- /CameraOoT/Time.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINE_TIME 2 | #define ENGINE_TIME 3 | 4 | #include 5 | 6 | class Time 7 | { 8 | public: 9 | void StartTime(); 10 | void FixedUpdateTime(); 11 | float GetFixedDeltaTime() const; 12 | 13 | private: 14 | std::chrono::steady_clock::time_point m_start_time_; 15 | std::chrono::steady_clock::time_point m_current_time_f_; 16 | std::chrono::steady_clock::time_point m_last_time_f_; 17 | std::chrono::steady_clock::time_point m_current_time_; 18 | float m_time_ = 0.0f; 19 | float m_fixed_delta_time_ = 0.0f; 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /CameraOoT/Time.cpp: -------------------------------------------------------------------------------- 1 | #include "Time.h" 2 | 3 | void Time::StartTime() 4 | { 5 | m_start_time_ = std::chrono::high_resolution_clock::now(); 6 | m_current_time_ = std::chrono::high_resolution_clock::now(); 7 | m_current_time_f_ = std::chrono::high_resolution_clock::now(); 8 | } 9 | 10 | void Time::FixedUpdateTime() 11 | { 12 | m_last_time_f_ = m_current_time_f_; 13 | m_current_time_f_ = std::chrono::high_resolution_clock::now(); 14 | m_time_ = std::chrono::duration(m_current_time_f_ - m_start_time_).count(); 15 | m_fixed_delta_time_ = std::chrono::duration(m_current_time_f_ - m_last_time_f_).count(); 16 | } 17 | 18 | float Time::GetFixedDeltaTime() const 19 | { 20 | return m_fixed_delta_time_; 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Free camera control hack for The Legend of Zelda: Ocarina of Time 3D. 2 | 3 | **NOTICE: most users should use this mod instead https://github.com/Roberto-Nessy/OoT3D_Standalone_Free_Cam** 4 | 5 | This tool scans the memory of Citra to find and modify the addresses corresponding to the LocalPlayer and LocalCamera. 6 | 7 | The project can be compiled with Visual Studio. 8 | 9 | It supports any controller supported by SDL: 10 | - The right stick moves the free camera, toggling it on if it's off. 11 | - The L button resets the angle of the camera, and toggles off the free camera. 12 | 13 | Open citra-qt.exe and start the game, run the CameraOoT.exe once you can control Link. 14 | 15 | TODO: Find the memory addresses for Epona's coordinates (?), and allow free camera control when riding Epona. 16 | TODO: Check whether the jitter of the free camera can be further mitigated. 17 | TODO: Modify the camera function to zoom it further out when the Y coordinate increases. 18 | -------------------------------------------------------------------------------- /CameraOoT/CameraOoT.cfg: -------------------------------------------------------------------------------- 1 | # Format: KEY=VALUE 2 | 3 | # RESETCAMERA_BUTTON - Button used to reset the camera 4 | # Possible values: LEFT_SHOULDER_BUTTON 5 | # RIGHT_SHOULDER_BUTTON 6 | # LEFT_TRIGGER 7 | # RIGHT_TRIGGER 8 | # Default value: LEFT_SHOULDER_BUTTON 9 | RESETCAMERA_BUTTON=LEFT_SHOULDER_BUTTON 10 | 11 | # INVERT_HORIZONTAL - Invert the camera in the X-axis 12 | # Possible values: TRUE or FALSE 13 | # Default value: FALSE 14 | INVERT_HORIZONTAL=FALSE 15 | 16 | # INVERT_VERTICAL - Invert the camera in the Y-axis 17 | # Possible values: TRUE or FALSE 18 | # Default value: FALSE 19 | INVERT_VERTICAL=FALSE 20 | 21 | # HORIZONTAL_SENSITIVITY - Sensitivity of the camera in the X-axis 22 | # Possible values: any float number 23 | # Default value: 150 24 | HORIZONTAL_SENSITIVITY=150 25 | 26 | # VERTICAL_SENSITIVITY - Sensitivity of the camera in the Y-axis 27 | # Possible values: any float number 28 | # Default value: 400 29 | VERTICAL_SENSITIVITY=400 30 | 31 | # DEAD_ZONE_STICK - Dead zone of the right stick 32 | # Possible values: 0.0 to 1.0 33 | # Default value: 0.2 34 | DEAD_ZONE_STICK=0.2 35 | -------------------------------------------------------------------------------- /CameraOoT.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32602.215 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CameraOoT", "CameraOoT\CameraOoT.vcxproj", "{E6719C95-F0BE-4685-90EE-751B16BBB629}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {E6719C95-F0BE-4685-90EE-751B16BBB629}.Debug|x64.ActiveCfg = Debug|x64 17 | {E6719C95-F0BE-4685-90EE-751B16BBB629}.Debug|x64.Build.0 = Debug|x64 18 | {E6719C95-F0BE-4685-90EE-751B16BBB629}.Debug|x86.ActiveCfg = Debug|Win32 19 | {E6719C95-F0BE-4685-90EE-751B16BBB629}.Debug|x86.Build.0 = Debug|Win32 20 | {E6719C95-F0BE-4685-90EE-751B16BBB629}.Release|x64.ActiveCfg = Release|x64 21 | {E6719C95-F0BE-4685-90EE-751B16BBB629}.Release|x64.Build.0 = Release|x64 22 | {E6719C95-F0BE-4685-90EE-751B16BBB629}.Release|x86.ActiveCfg = Release|Win32 23 | {E6719C95-F0BE-4685-90EE-751B16BBB629}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {FF52C6E0-BD2C-4F2F-A967-1C30041720AC} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /CameraOoT/CameraOoT.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source files 20 | 21 | 22 | Source files 23 | 24 | 25 | Source files 26 | 27 | 28 | 29 | 30 | Header files 31 | 32 | 33 | Header files 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /CameraOoT/Proc.cpp: -------------------------------------------------------------------------------- 1 | #include "Proc.h" 2 | #include 3 | 4 | DWORD GetProcId(const wchar_t* proc_name) 5 | { 6 | DWORD proc_id = 0; 7 | const HANDLE h_snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 8 | 9 | if (h_snap == INVALID_HANDLE_VALUE) 10 | { 11 | return 0; 12 | } 13 | 14 | PROCESSENTRY32 proc_entry; 15 | proc_entry.dwSize = sizeof(proc_entry); 16 | 17 | if (Process32First(h_snap, &proc_entry)) 18 | { 19 | do 20 | { 21 | if (!_wcsicmp(proc_entry.szExeFile, proc_name)) 22 | { 23 | proc_id = proc_entry.th32ProcessID; 24 | break; 25 | } 26 | } while (Process32Next(h_snap, &proc_entry)); 27 | } 28 | 29 | CloseHandle(h_snap); 30 | return proc_id; 31 | } 32 | 33 | struct PartData 34 | { 35 | int32_t mask = 0; 36 | __m128i needle{}; 37 | 38 | PartData() 39 | { 40 | memset(&needle, 0, sizeof(needle)); 41 | } 42 | }; 43 | 44 | // Credits to @DarthTon for this function (source: https://github.com/learn-more/findpattern-bench/blob/master/patterns/DarthTon.h) 45 | const uint8_t* Search(const uint8_t* data, const uint32_t size, const uint8_t* pattern, const char* mask) 46 | { 47 | const uint8_t* result = nullptr; 48 | auto len = strlen(mask); 49 | const auto first = strchr(mask, '?'); 50 | const size_t len2 = (first != nullptr) ? (first - mask) : len; 51 | const auto firstlen = min(len2, 16); 52 | const intptr_t num_parts = (len < 16 || len % 16) ? (len / 16 + 1) : (len / 16); 53 | std::array parts; 54 | 55 | for (intptr_t i = 0; i < num_parts; ++i, len -= 16) 56 | { 57 | for (size_t j = 0; j < min(len, 16) - 1; ++j) 58 | if (mask[16 * i + j] == 'x') 59 | _bittestandset(reinterpret_cast(&parts[i].mask), j); 60 | 61 | parts[i].needle = _mm_loadu_si128(reinterpret_cast(pattern + i * 16)); 62 | } 63 | 64 | bool abort = false; 65 | 66 | #pragma omp parallel for 67 | for (intptr_t i = 0; i < static_cast(size) / 32 - 1; ++i) 68 | { 69 | #pragma omp flush(abort) 70 | if (!abort) 71 | { 72 | if (const auto block = _mm256_loadu_si256(reinterpret_cast(data) + i); _mm256_testz_si256(block, block)) 73 | continue; 74 | 75 | auto offset = _mm_cmpestri(parts[0].needle, firstlen, _mm_loadu_si128(reinterpret_cast(data + i * 32)), 16, 76 | _SIDD_CMP_EQUAL_ORDERED); 77 | if (offset == 16) 78 | { 79 | offset += _mm_cmpestri(parts[0].needle, firstlen, _mm_loadu_si128(reinterpret_cast(data + i * 32 + 16)), 80 | 16, _SIDD_CMP_EQUAL_ORDERED); 81 | if (offset == 32) 82 | continue; 83 | } 84 | 85 | for (intptr_t j = 0; j < num_parts; ++j) 86 | { 87 | const auto hay = _mm_loadu_si128(reinterpret_cast(data + (2 * i + j) * 16 + offset)); 88 | if (const auto bitmask = _mm_movemask_epi8(_mm_cmpeq_epi8(hay, parts[j].needle)); (bitmask & parts[j].mask) != parts[j].mask) 89 | goto next; 90 | } 91 | 92 | result = data + 32 * i + offset; 93 | abort = true; 94 | #pragma omp flush(abort) 95 | } 96 | 97 | next:; 98 | } 99 | 100 | return result; 101 | } 102 | 103 | uintptr_t SearchInProcessMemory(const HANDLE h_process, const uint8_t* pattern, const char* mask) 104 | { 105 | constexpr int buffer_size = 1024 * 1024; 106 | const auto buffer = std::make_unique(buffer_size); 107 | uintptr_t result = 0x0; 108 | uint8_t* read_addr = nullptr; 109 | 110 | MEMORY_BASIC_INFORMATION mem_info; 111 | while (VirtualQueryEx(h_process, reinterpret_cast(read_addr), &mem_info, sizeof(mem_info))) 112 | { 113 | if (mem_info.Protect == PAGE_NOACCESS) 114 | { 115 | read_addr += mem_info.RegionSize; 116 | continue; 117 | } 118 | break; 119 | } 120 | 121 | if (read_addr == nullptr) 122 | return 0x0; 123 | 124 | while (result == 0) 125 | { 126 | SIZE_T bytes_read = 0; 127 | if (const BOOL success = ReadProcessMemory(h_process, read_addr, buffer.get(), buffer_size, &bytes_read); !success || bytes_read == 0) 128 | { 129 | read_addr += buffer_size; 130 | continue; 131 | } 132 | 133 | if (const void* ptr = Search(buffer.get(), bytes_read, pattern, mask); ptr != nullptr) 134 | { 135 | result = reinterpret_cast(read_addr) + (reinterpret_cast(ptr) - reinterpret_cast(buffer.get())); 136 | break; 137 | } 138 | 139 | read_addr += buffer_size; 140 | } 141 | 142 | return result; 143 | } 144 | -------------------------------------------------------------------------------- /citra-qt.CT: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 91 6 | "PlayerPositionFinder" 7 | 030093 8 | Auto Assembler Script 9 | [ENABLE] 10 | aobscan(BASE,2A 00 00 60 6A 8F 09 00 00 00 00 00 00 02 FF 75 00) 11 | label(_base) 12 | registersymbol(_base) 13 | 14 | BASE: 15 | _base: 16 | 17 | 18 | [DISABLE] 19 | unregistersymbol(_base) 20 | 21 | 22 | 23 | 24 | 93 25 | "X" 26 | 0 27 | FD5151 28 | Float 29 |
_base+33
30 |
31 | 32 | 92 33 | "Y" 34 | 0 35 | 1F1AFF 36 | Float 37 |
_base+33+4
38 | 39 | 40 | Increase Value 41 | 42 | 104 43 | 44 | 500 45 | 0 46 | 47 | 48 |
49 | 50 | 94 51 | "Z" 52 | 0 53 | 6AFC5F 54 | Float 55 |
_base+33+8
56 |
57 | 58 | 170 59 | "LinkCrawl" 60 | 0 61 | 2 Bytes 62 |
_base+250B
63 |
64 | 65 | 438 66 | "LinkEpona" 67 | 0 68 | 1B719E 69 | 2 Bytes 70 |
_base+131
71 |
72 | 73 | 439 74 | "LinkClimb" 75 | 0 76 | 2 Bytes 77 |
_base+2B1
78 |
79 | 80 | 440 81 | "LinkOcarina" 82 | 0 83 | 2 Bytes 84 |
_base+C1
85 |
86 | 87 | 441 88 | "LinkFixed" 89 | 0 90 | 2 Bytes 91 |
_base+89
92 |
93 | 94 | 442 95 | "LinkJump" 96 | 0 97 | 2 Bytes 98 |
_base+2289
99 |
100 |
101 |
102 | 103 | 95 104 | "CameraPositionFinder" 105 | C600C6 106 | Auto Assembler Script 107 | [ENABLE] 108 | aobscan(BASECAM,80 3F 00 00 80 3F 00 00 80 3F 00 00 00 00 07 00 01) 109 | label(_basecam) 110 | registersymbol(_basecam) 111 | 112 | BASECAM: 113 | _basecam: 114 | 115 | 116 | [DISABLE] 117 | unregistersymbol(_basecam) 118 | 119 | 120 | 121 | 96 122 | "CX" 123 | 0 124 | FF717E 125 | Float 126 |
_basecam+B6
127 |
128 | 129 | 97 130 | "CY" 131 | 0 132 | 3A35FF 133 | Float 134 |
_basecam+B6+4
135 |
136 | 137 | 98 138 | "CZ" 139 | 0 140 | 28FF79 141 | Float 142 |
_basecam+B6+8
143 |
144 | 145 | 161 146 | "LookLink" 147 | 0 148 | 7B8B9B 149 | 2 Bytes 150 |
_basecam+60
151 |
152 | 153 | 162 154 | "LockedCamera" 155 | 0 156 | FFFFFF 157 | Float 158 |
_basecam+1E
159 |
160 |
161 |
162 |
163 | 164 |
165 | -------------------------------------------------------------------------------- /CameraOoT/CameraOoT.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {e6719c95-f0be-4685-90ee-751b16bbb629} 25 | CameraOoT 26 | 10.0.19041.0 27 | CameraOoT 28 | 29 | 30 | 31 | Application 32 | true 33 | v143 34 | Unicode 35 | 36 | 37 | Application 38 | false 39 | v143 40 | true 41 | Unicode 42 | 43 | 44 | Application 45 | true 46 | v143 47 | Unicode 48 | 49 | 50 | Application 51 | false 52 | v143 53 | true 54 | Unicode 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | False 75 | 76 | 77 | 78 | 79 | 80 | Level3 81 | true 82 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 83 | true 84 | 85 | 86 | Console 87 | true 88 | 89 | 90 | 91 | 92 | Level3 93 | true 94 | true 95 | true 96 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 97 | true 98 | 99 | 100 | Console 101 | true 102 | true 103 | true 104 | 105 | 106 | 107 | 108 | Level3 109 | true 110 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 111 | true 112 | 113 | 114 | Console 115 | true 116 | 117 | 118 | copy "$(SolutionDir)\CameraOoT\CameraOoT.cfg" "$(TargetDir)\CameraOoT.cfg" 119 | 120 | 121 | 122 | 123 | Level3 124 | true 125 | true 126 | true 127 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 128 | true 129 | stdcpp20 130 | 131 | 132 | 133 | 134 | Console 135 | true 136 | true 137 | true 138 | 139 | 140 | copy "$(SolutionDir)\CameraOoT\CameraOoT.cfg" "$(TargetDir)\CameraOoT.cfg" 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /CameraOoT/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "Proc.h" 10 | #include "Time.h" 11 | 12 | using namespace std; 13 | 14 | unordered_map ReadConfig(const string& filename) { 15 | unordered_map config; 16 | ifstream file(filename); 17 | string line; 18 | while (getline(file, line)) { 19 | if (line.empty() || line[0] == '#') { 20 | continue; 21 | } 22 | istringstream iss(line); 23 | if (string key; getline(iss, key, '=')) { 24 | if (string value; getline(iss, value)) { 25 | config[key] = value; 26 | } 27 | } 28 | } 29 | return config; 30 | } 31 | 32 | unordered_map button_map = { 33 | {"LEFT_SHOULDER_BUTTON", SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, 34 | {"RIGHT_SHOULDER_BUTTON", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER} 35 | }; 36 | 37 | unordered_map axis_map = { 38 | {"LEFT_TRIGGER", SDL_CONTROLLER_AXIS_TRIGGERLEFT}, 39 | {"RIGHT_TRIGGER", SDL_CONTROLLER_AXIS_TRIGGERRIGHT} 40 | }; 41 | 42 | int main(int, char**) 43 | { 44 | SetConsoleTitle(L"OoT Camera"); 45 | 46 | const DWORD proc_id = GetProcId(L"citra-qt.exe"); 47 | if (proc_id == 0) 48 | { 49 | cout << "citra-qt.exe not found" << endl; 50 | system("pause"); 51 | return 0; 52 | } 53 | cout << "citra-qt.exe found" << endl; 54 | 55 | const HANDLE h_process = OpenProcess(PROCESS_ALL_ACCESS, NULL, proc_id); 56 | if (h_process == INVALID_HANDLE_VALUE) 57 | { 58 | cout << "Process invalid" << endl; 59 | system("pause"); 60 | return 1; 61 | } 62 | cout << "Successfully hooked to citra-qt.exe, finding memory addresses..." << endl; 63 | 64 | const auto config = ReadConfig("CameraOoT.cfg"); 65 | if (config.empty()) { 66 | cout << "Couldn't read \"CameraOoT.cfg\"" << endl; 67 | system("pause"); 68 | return 1; 69 | } 70 | 71 | auto dead_zone_i = config.find("DEAD_ZONE_STICK"); 72 | if (dead_zone_i == config.end()) { 73 | cout << "Error: Key 'DEAD_ZONE_STICK' not found in config" << endl; 74 | system("pause"); 75 | return 1; 76 | } 77 | float dead_zone = 0.0f; 78 | try { 79 | dead_zone = stof(dead_zone_i->second); 80 | } 81 | catch (...) { 82 | cerr << "Error: Invalid DEAD_ZONE_STICK value '" << dead_zone_i->second << "'" << endl; 83 | system("pause"); 84 | return 1; 85 | } 86 | 87 | auto x_speed_i = config.find("HORIZONTAL_SENSITIVITY"); 88 | if (x_speed_i == config.end()) { 89 | cout << "Error: Key 'HORIZONTAL_SENSITIVITY' not found in config" << endl; 90 | system("pause"); 91 | return 1; 92 | } 93 | float x_speed = 0.0f; 94 | try { 95 | x_speed = stof(x_speed_i->second); 96 | } 97 | catch (...) { 98 | cerr << "Error: Invalid HORIZONTAL_SENSITIVITY value '" << x_speed_i->second << "'" << endl; 99 | system("pause"); 100 | return 1; 101 | } 102 | 103 | auto y_speed_i = config.find("VERTICAL_SENSITIVITY"); 104 | if (y_speed_i == config.end()) { 105 | cout << "Error: Key 'VERTICAL_SENSITIVITY' not found in config" << endl; 106 | system("pause"); 107 | return 1; 108 | } 109 | float y_speed = 0.0f; 110 | try { 111 | y_speed = stof(y_speed_i->second); 112 | } 113 | catch (...) { 114 | cerr << "Error: Invalid VERTICAL_SENSITIVITY value '" << y_speed_i->second << "'" << endl; 115 | return 1; 116 | } 117 | 118 | auto invert_x_i = config.find("INVERT_HORIZONTAL"); 119 | if (invert_x_i == config.end()) { 120 | cout << "Error: Key 'INVERT_HORIZONTAL' not found in config" << endl; 121 | system("pause"); 122 | return 1; 123 | } 124 | float invert_x = 1.0f; 125 | if (invert_x_i->second == "TRUE") { 126 | invert_x = -1.0f; 127 | } 128 | else if (invert_x_i->second != "FALSE") { 129 | cerr << "Error: Invalid INVERT_HORIZONTAL value '" << invert_x_i->second << "'" << endl; 130 | system("pause"); 131 | return 1; 132 | } 133 | 134 | auto invert_y_i = config.find("INVERT_VERTICAL"); 135 | if (invert_y_i == config.end()) { 136 | cout << "Error: Key 'INVERT_VERTICAL' not found in config" << endl; 137 | system("pause"); 138 | return 1; 139 | } 140 | float invert_y = 1.0f; 141 | if (invert_y_i->second == "TRUE") { 142 | invert_y = -1.0f; 143 | } 144 | else if (invert_y_i->second != "FALSE") { 145 | cerr << "Error: Invalid INVERT_VERTICAL value '" << invert_y_i->second << "'" << endl; 146 | system("pause"); 147 | return 1; 148 | } 149 | 150 | const auto reset_button_name_i = config.find("RESETCAMERA_BUTTON"); 151 | if (reset_button_name_i == config.end()) { 152 | cout << "Error: Key 'RESETCAMERA_BUTTON' not found in config" << endl; 153 | system("pause"); 154 | return 1; 155 | } 156 | const string reset_button_name = reset_button_name_i->second; 157 | bool reset_button_istrigger = false; 158 | if (reset_button_name == "LEFT_TRIGGER" || reset_button_name == "RIGHT_TRIGGER") 159 | { 160 | reset_button_istrigger = true; 161 | } 162 | 163 | SDL_GameControllerButton reset_button = {}; 164 | SDL_GameControllerAxis reset_trigger = {}; 165 | if (reset_button_istrigger) 166 | { 167 | if (!axis_map.contains(reset_button_name)) 168 | { 169 | cout << "Error: Invalid RESETCAMERA_BUTTON value '" << reset_button_name << "'" << endl; 170 | system("pause"); 171 | return 1; 172 | } 173 | reset_trigger = axis_map[reset_button_name]; 174 | } 175 | else 176 | { 177 | if (!button_map.contains(reset_button_name)) 178 | { 179 | cout << "Error: Invalid RESETCAMERA_BUTTON value '" << reset_button_name << "'" << endl; 180 | system("pause"); 181 | return 1; 182 | } 183 | reset_button = button_map[reset_button_name]; 184 | } 185 | 186 | const uint8_t pb_pattern_pp[17] = { 187 | 0x2A, 0x00, 0x00, 0x60, 0x6A, 0x8F, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x75, 0x00 }; 188 | const uint8_t pb_pattern_pc[17] = { 189 | 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01 }; 190 | const auto mask = "xxxxxxxxxxxxxxxx"; 191 | const uintptr_t local_player_addr = SearchInProcessMemory(h_process, pb_pattern_pp, mask) + 0x33; 192 | const uintptr_t link_crawl_addr = local_player_addr - 0x33 + 0x250B; 193 | const uintptr_t link_on_epona_addr = local_player_addr - 0x33 + 0x131; 194 | const uintptr_t link_climb_addr = local_player_addr - 0x33 + 0x2B1; 195 | const uintptr_t link_ocarina_addr = local_player_addr - 0x33 + 0xC1; 196 | const uintptr_t link_fixed_addr = local_player_addr - 0x33 + 0x89; 197 | const uintptr_t link_jump_addr = local_player_addr - 0x33 + 0x2289; 198 | printf("[+] Found LocalPlayer @ 0x%X\n", static_cast(local_player_addr)); 199 | const uintptr_t local_camera_addr = SearchInProcessMemory(h_process, pb_pattern_pc, mask) + 0xB6; 200 | const uintptr_t look_at_camera_addr = local_camera_addr - 0xB6 + 0x60; 201 | const uintptr_t camera_locked_addr = local_camera_addr - 0xB6 + 0x1E; 202 | printf("[+] Found LocalCamera @ 0x%X\n", static_cast(local_camera_addr)); 203 | 204 | float x = 0.0f; 205 | float y = 0.0f; 206 | float z = 0.0f; 207 | 208 | SDL_Init(SDL_INIT_EVERYTHING); 209 | SDL_GameController* controller = nullptr; 210 | SDL_Event event; 211 | 212 | Time time; 213 | float base_angle = 0.0f; 214 | float base_height = 50.0f; 215 | time.StartTime(); 216 | float dx, dz, dy; 217 | constexpr uint16_t look_link = 60; 218 | uint16_t crawl_link = 11012; 219 | uint16_t jump_link = 0; 220 | uint16_t epona_link = 0; 221 | uint16_t climb_link = 0; 222 | uint16_t ocarina_link = 2098; 223 | uint16_t fixed_link = 2178; 224 | 225 | uint16_t locked_camera = 1; 226 | 227 | float joystick_x = 0.0f; 228 | float joystick_y = 0.0f; 229 | bool reset_angle = true; 230 | bool pause = true; 231 | bool is_ocarina = false; 232 | 233 | while (true) 234 | { 235 | time.FixedUpdateTime(); 236 | 237 | while (SDL_PollEvent(&event)) 238 | { 239 | switch (event.type) 240 | { 241 | case SDL_CONTROLLERDEVICEADDED: 242 | controller = SDL_GameControllerOpen(event.cdevice.which); 243 | if (controller) 244 | { 245 | cout << "[+] Controller " << event.cdevice.which << " is connected" << endl; 246 | } 247 | break; 248 | case SDL_CONTROLLERDEVICEREMOVED: 249 | SDL_GameControllerClose(controller); 250 | cout << "[X] Controller " << event.cdevice.which << " is not connected" << endl; 251 | break; 252 | case SDL_CONTROLLERBUTTONDOWN: 253 | if (!reset_button_istrigger && event.cbutton.button == reset_button) 254 | { 255 | reset_angle = true; 256 | pause = true; 257 | } 258 | break; 259 | case SDL_CONTROLLERAXISMOTION: 260 | if (event.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX) 261 | { 262 | joystick_x = event.caxis.value / 32767.0f; 263 | if (joystick_x < dead_zone && joystick_x > -dead_zone) 264 | { 265 | joystick_x = 0.0f; 266 | } 267 | else 268 | { 269 | pause = false; 270 | } 271 | } 272 | else if (event.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY) 273 | { 274 | joystick_y = event.caxis.value / 32767.0f; 275 | if (joystick_y < dead_zone && joystick_y > -dead_zone) 276 | { 277 | joystick_y = 0.0f; 278 | } 279 | else 280 | { 281 | pause = false; 282 | } 283 | } 284 | else if (reset_button_istrigger && event.caxis.axis == reset_trigger && event.caxis.value > 16384.0f) 285 | { 286 | reset_angle = true; 287 | pause = true; 288 | } 289 | break; 290 | default: 291 | break; 292 | } 293 | } 294 | 295 | ReadProcessMemory(h_process, reinterpret_cast(link_crawl_addr), &crawl_link, sizeof(uint16_t), nullptr); 296 | ReadProcessMemory(h_process, reinterpret_cast(link_on_epona_addr), &epona_link, sizeof(uint16_t), nullptr); 297 | ReadProcessMemory(h_process, reinterpret_cast(link_climb_addr), &climb_link, sizeof(uint16_t), nullptr); 298 | ReadProcessMemory(h_process, reinterpret_cast(link_ocarina_addr), &ocarina_link, sizeof(uint16_t), nullptr); 299 | ReadProcessMemory(h_process, reinterpret_cast(link_fixed_addr), &fixed_link, sizeof(uint16_t), nullptr); 300 | ReadProcessMemory(h_process, reinterpret_cast(camera_locked_addr), &locked_camera, sizeof(uint16_t), nullptr); 301 | ReadProcessMemory(h_process, reinterpret_cast(link_jump_addr), &jump_link, sizeof(uint16_t), nullptr); 302 | 303 | if (ocarina_link == 2303 && !is_ocarina) 304 | { 305 | reset_angle = true; 306 | pause = true; 307 | is_ocarina = true; 308 | } 309 | else if (ocarina_link == 2098 && is_ocarina) 310 | { 311 | is_ocarina = false; 312 | } 313 | 314 | if (epona_link == 2448 || crawl_link == 21012 || climb_link == 143 || fixed_link == 2457 || locked_camera == 1) 315 | { 316 | reset_angle = true; 317 | pause = true; 318 | } 319 | 320 | if (!pause) 321 | { 322 | constexpr float length_base = 250.0f; 323 | WriteProcessMemory(h_process, reinterpret_cast(look_at_camera_addr), &look_link, sizeof(uint16_t), nullptr); 324 | ReadProcessMemory(h_process, reinterpret_cast(local_player_addr), &x, sizeof(float), nullptr); 325 | ReadProcessMemory(h_process, reinterpret_cast(local_player_addr + 0x04), &y, sizeof(float), nullptr); 326 | ReadProcessMemory(h_process, reinterpret_cast(local_player_addr + 0x08), &z, sizeof(float), nullptr); 327 | 328 | if (reset_angle) 329 | { 330 | reset_angle = false; 331 | ReadProcessMemory(h_process, reinterpret_cast(local_camera_addr), &dx, sizeof(float), nullptr); 332 | ReadProcessMemory(h_process, reinterpret_cast(local_camera_addr + 0x04), &dy, sizeof(float), nullptr); 333 | ReadProcessMemory(h_process, reinterpret_cast(local_camera_addr + 0x08), &dz, sizeof(float), nullptr); 334 | 335 | base_angle = atan2(x - dx, z - dz) * -180.0f / numbers::pi_v; 336 | base_angle -= 90.0f; 337 | if (base_angle < -180.0f) 338 | { 339 | base_angle += 360.0f; 340 | } 341 | else if (base_angle > 180.0f) 342 | { 343 | base_angle -= 360.0f; 344 | } 345 | 346 | base_height = dy - y; 347 | if (base_height > 360.0f) 348 | { 349 | base_height = 360.0f; 350 | } 351 | else if (base_height < -180.0f) 352 | { 353 | base_height = -180.0f; 354 | } 355 | } 356 | 357 | 358 | base_angle += time.GetFixedDeltaTime() * joystick_x * x_speed * invert_x; 359 | if (base_angle > 180.0f) 360 | { 361 | base_angle -= 360.0f; 362 | } 363 | else if (base_angle < -180.0f) 364 | { 365 | base_angle += 360.0f; 366 | } 367 | 368 | if (jump_link != 0) 369 | { 370 | base_height += time.GetFixedDeltaTime() * joystick_y * y_speed * invert_y; 371 | if (base_height > 360.0f) 372 | { 373 | base_height = 360.0f; 374 | } 375 | else if (base_height < -180.0f) 376 | { 377 | base_height = -180.0f; 378 | } 379 | } 380 | else 381 | { 382 | ReadProcessMemory(h_process, reinterpret_cast(local_camera_addr + 0x04), &dy, sizeof(float), nullptr); 383 | 384 | base_height = dy - y; 385 | if (base_height > 360.0f) 386 | { 387 | base_height = 360.0f; 388 | } 389 | else if (base_height < -180.0f) 390 | { 391 | base_height = -180.0f; 392 | } 393 | } 394 | 395 | 396 | const float theta = base_angle * numbers::pi_v / 180.0f; 397 | dx = (cos(theta) * length_base) + x; 398 | dy = base_height + y; 399 | dz = (sin(theta) * length_base) + z; 400 | 401 | WriteProcessMemory(h_process, reinterpret_cast(local_camera_addr), &dx, sizeof(float), nullptr); 402 | WriteProcessMemory(h_process, reinterpret_cast(local_camera_addr + 0x04), &dy, sizeof(float), nullptr); 403 | WriteProcessMemory(h_process, reinterpret_cast(local_camera_addr + 0x08), &dz, sizeof(float), nullptr); 404 | } 405 | } 406 | } 407 | --------------------------------------------------------------------------------