├── 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 |
--------------------------------------------------------------------------------