├── .gitignore
├── .vscode
├── c_cpp_properties.json
├── settings.json
└── tasks.json
├── CMakeLists.txt
├── LICENSE
├── README.md
├── TODO.md
├── build.ps1
├── copy.ps1
├── createqmod.ps1
├── extern.cmake
├── include
├── EventTypes.hpp
├── Recording
│ ├── NoteEventRecorder.hpp
│ ├── ObstacleEventRecorder.hpp
│ ├── PlayerRecorder.hpp
│ └── ReplayRecorder.hpp
├── ReplayManager.hpp
├── Replaying
│ ├── NoteEventReplayer.hpp
│ ├── ObstacleEventReplayer.hpp
│ ├── PlayerReplayer.hpp
│ └── Replayer.hpp
├── Sprites.hpp
├── UI
│ ├── ReplayViewController.hpp
│ └── UIManager.hpp
├── Utils
│ ├── FileUtils.hpp
│ ├── FindComponentsUtils.hpp
│ ├── MathUtils.hpp
│ ├── ModifiersUtils.hpp
│ ├── ReplayUtils.hpp
│ ├── SaberUtils.hpp
│ ├── SongUtils.hpp
│ ├── TimeUtils.hpp
│ ├── TypeUtils.hpp
│ ├── UIUtils.hpp
│ └── UnityUtils.hpp
├── hooks.hpp
└── static-defines.hpp
├── ndk-stack.ps1
├── qpm.json
├── qpm_defines.cmake
└── src
├── Hooks
├── AudioTimeSyncController.cpp
├── HapticFeedbackController.cpp
├── Notes
│ ├── BombNoteController.cpp
│ ├── CutScoreBuffer.cpp
│ ├── FlyingScoreEffect.cpp
│ ├── GameNoteController.cpp
│ ├── GoodCutScoringElement.cpp
│ ├── NoteController.cpp
│ ├── NoteCutter.cpp
│ ├── RelativeScoreAndImmediateRankCounter.cpp
│ └── SaberSwingRatingCounter.cpp
├── Obstacles
│ └── GameEnergyCounter.cpp
├── PauseController.cpp
├── Player
│ ├── PlayerTransforms.cpp
│ └── SaberMovementData.cpp
├── ResultsViewController.cpp
├── SaberManager.cpp
├── ScoreController.cpp
├── SinglePlayerLevelSelectionFlowCoordinator.cpp
├── SoloFreePlayFlowController.cpp
├── StandardLevelDetailView.cpp
├── StandardLevelGameplayManager.cpp
└── StandardLevelScenesTransitionSetupDataSO.cpp
├── Recording
├── NoteEventRecorder.cpp
├── ObstacleEventRecorder.cpp
├── PlayerRecorder.cpp
└── ReplayRecorder.cpp
├── ReplayManager.cpp
├── Replaying
├── NoteEventReplayer.cpp
├── ObstacleEventReplayer.cpp
├── PlayerReplayer.cpp
└── Replayer.cpp
├── UI
├── ReplayViewController.cpp
└── UIManager.cpp
├── Utils
├── FindComponentsUtils.cpp
└── SongUtils.cpp
└── main.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 |
34 | # VSCode config stuff
35 | !.vscode/c_cpp_properties.json
36 | !.vscode/tasks.json
37 |
38 | # NDK stuff
39 | out/
40 | [Ll]ib/
41 | [Ll]ibs/
42 | [Oo]bj/
43 | [Oo]bjs/
44 | ndkpath.txt
45 | *.zip
46 | *.txt
47 |
48 | extern
49 |
50 | build/
51 |
52 | qpm.shared.json
53 |
54 | Android.mk.backup
55 |
56 | *.qmod
--------------------------------------------------------------------------------
/.vscode/c_cpp_properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "defines": [
5 | "__GNUC__",
6 | "__aarch64__"
7 | ],
8 | "includePath": [
9 | "${workspaceFolder}/**",
10 | "${workspaceFolder}/include/**",
11 | "${workspaceFolder}/shared/**",
12 | "${workspaceFolder}/extern/**",
13 | "${workspaceFolder}/extern/codegen/include/**",
14 | "${workspaceFolder}/extern/ffmpeg/**",
15 | "c:/Users/henwi/Desktop/ModdingBeatSaber/android-ndk-r21d/**"
16 | ],
17 | "name": "Quest",
18 | "cStandard": "c11",
19 | "cppStandard": "c++20",
20 | "intelliSenseMode": "clang-x64"
21 | }
22 | ],
23 | "version": 4
24 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | "iosfwd": "cpp",
4 | "__config": "cpp",
5 | "__nullptr": "cpp",
6 | "thread": "cpp",
7 | "any": "cpp",
8 | "deque": "cpp",
9 | "list": "cpp",
10 | "map": "cpp",
11 | "optional": "cpp",
12 | "queue": "cpp",
13 | "set": "cpp",
14 | "stack": "cpp",
15 | "unordered_map": "cpp",
16 | "unordered_set": "cpp",
17 | "variant": "cpp",
18 | "vector": "cpp",
19 | "__bit_reference": "cpp",
20 | "__debug": "cpp",
21 | "__errc": "cpp",
22 | "__functional_base": "cpp",
23 | "__hash_table": "cpp",
24 | "__locale": "cpp",
25 | "__mutex_base": "cpp",
26 | "__node_handle": "cpp",
27 | "__split_buffer": "cpp",
28 | "__string": "cpp",
29 | "__threading_support": "cpp",
30 | "__tree": "cpp",
31 | "__tuple": "cpp",
32 | "algorithm": "cpp",
33 | "array": "cpp",
34 | "atomic": "cpp",
35 | "bit": "cpp",
36 | "bitset": "cpp",
37 | "cctype": "cpp",
38 | "cfenv": "cpp",
39 | "charconv": "cpp",
40 | "chrono": "cpp",
41 | "cinttypes": "cpp",
42 | "clocale": "cpp",
43 | "cmath": "cpp",
44 | "codecvt": "cpp",
45 | "compare": "cpp",
46 | "complex": "cpp",
47 | "condition_variable": "cpp",
48 | "csetjmp": "cpp",
49 | "csignal": "cpp",
50 | "cstdarg": "cpp",
51 | "cstddef": "cpp",
52 | "cstdint": "cpp",
53 | "cstdio": "cpp",
54 | "cstdlib": "cpp",
55 | "cstring": "cpp",
56 | "ctime": "cpp",
57 | "cwchar": "cpp",
58 | "cwctype": "cpp",
59 | "exception": "cpp",
60 | "coroutine": "cpp",
61 | "propagate_const": "cpp",
62 | "forward_list": "cpp",
63 | "fstream": "cpp",
64 | "functional": "cpp",
65 | "future": "cpp",
66 | "initializer_list": "cpp",
67 | "iomanip": "cpp",
68 | "ios": "cpp",
69 | "iostream": "cpp",
70 | "istream": "cpp",
71 | "iterator": "cpp",
72 | "limits": "cpp",
73 | "locale": "cpp",
74 | "memory": "cpp",
75 | "mutex": "cpp",
76 | "new": "cpp",
77 | "numeric": "cpp",
78 | "ostream": "cpp",
79 | "random": "cpp",
80 | "ratio": "cpp",
81 | "regex": "cpp",
82 | "scoped_allocator": "cpp",
83 | "span": "cpp",
84 | "sstream": "cpp",
85 | "stdexcept": "cpp",
86 | "streambuf": "cpp",
87 | "string": "cpp",
88 | "string_view": "cpp",
89 | "strstream": "cpp",
90 | "system_error": "cpp",
91 | "tuple": "cpp",
92 | "type_traits": "cpp",
93 | "typeindex": "cpp",
94 | "typeinfo": "cpp",
95 | "utility": "cpp",
96 | "valarray": "cpp",
97 | "xstring": "cpp",
98 | "xlocale": "cpp",
99 | "xlocbuf": "cpp",
100 | "concepts": "cpp",
101 | "filesystem": "cpp",
102 | "shared_mutex": "cpp",
103 | "xfacet": "cpp",
104 | "xhash": "cpp",
105 | "xiosbase": "cpp",
106 | "xlocinfo": "cpp",
107 | "xlocmes": "cpp",
108 | "xlocmon": "cpp",
109 | "xlocnum": "cpp",
110 | "xloctime": "cpp",
111 | "xmemory": "cpp",
112 | "xstddef": "cpp",
113 | "xtr1common": "cpp",
114 | "xtree": "cpp",
115 | "xutility": "cpp",
116 | "hash_map": "cpp",
117 | "hash_set": "cpp",
118 | "*.tcc": "cpp",
119 | "memory_resource": "cpp",
120 | "ranges": "cpp",
121 | "stop_token": "cpp",
122 | "resumable": "cpp",
123 | "xthread": "cpp",
124 | "xmemory0": "cpp"
125 | },
126 | "C_Cpp.errorSquiggles": "Disabled",
127 | "cmake.configureOnOpen": true
128 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "NDK Build",
6 | "detail": "Builds the library using ndk-build.cmd",
7 | "type": "shell",
8 | "command": "ndk-build",
9 | "windows": {
10 | "command": "ndk-build.cmd"
11 | },
12 | "args": ["NDK_PROJECT_PATH=.", "APP_BUILD_SCRIPT=./Android.mk", "NDK_APPLICATION_MK=./Application.mk"],
13 | "group": "build",
14 | "options": {
15 | "env": {}
16 | }
17 | },
18 | {
19 | "label": "Powershell Build",
20 | "detail": "Builds the library using Powershell (recommended)",
21 | "type": "shell",
22 | "command": "./build.ps1",
23 | "windows": {
24 | "command": "./build.ps1"
25 | },
26 | "group": {
27 | "kind": "build",
28 | "isDefault": true
29 | },
30 | "options": {
31 | "env": {}
32 | }
33 | },
34 | {
35 | "label": "Powershell Build and Copy",
36 | "detail": "Builds and copies the library to the Quest using adb and force-quits Beat Saber",
37 | "type": "shell",
38 | "command": "./copy.ps1",
39 | "windows": {
40 | "command": "./copy.ps1"
41 | },
42 | "group": "build",
43 | "options": {
44 | "env": {}
45 | }
46 | },
47 | {
48 | "label": "BMBF Build",
49 | "detail": "Builds a .zip to be uploaded into BMBF",
50 | "type": "shell",
51 | "command": "./buildBMBF.ps1",
52 | "windows": {
53 | "command": "./buildBMBF.ps1"
54 | },
55 | "args": [],
56 | "group": "build",
57 | "options": {
58 | "env": {}
59 | }
60 | },
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 |
2 | # include some defines automatically made by qpm
3 | include(qpm_defines.cmake)
4 |
5 | cmake_minimum_required(VERSION 3.21)
6 | project(${COMPILE_ID})
7 |
8 | # c++ standard
9 | set(CMAKE_CXX_STANDARD 20)
10 | set(CMAKE_CXX_STANDARD_REQUIRED 20)
11 |
12 | # define that stores the actual source directory
13 | set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
14 | set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)
15 |
16 | # compile options used
17 | add_compile_options(-frtti -fexceptions)
18 | add_compile_options(-O3)
19 | # compile definitions used
20 | add_compile_definitions(VERSION=\"${MOD_VERSION}\")
21 | add_compile_definitions(ID=\"${MOD_ID}\")
22 | add_compile_definitions(MOD_ID=\"${MOD_ID}\")
23 | add_compile_definitions(COMPILE_ID=\"replay\")
24 |
25 | # recursively get all src files
26 | RECURSE_FILES(cpp_file_list ${SOURCE_DIR}/*.cpp)
27 | RECURSE_FILES(c_file_list ${SOURCE_DIR}/*.c)
28 |
29 | # add all src files to compile
30 | add_library(
31 | ${COMPILE_ID}
32 | SHARED
33 | ${cpp_file_list}
34 | ${c_file_list}
35 | )
36 |
37 | target_include_directories(${COMPILE_ID} PRIVATE .)
38 |
39 | # add src dir as include dir
40 | target_include_directories(${COMPILE_ID} PRIVATE ${SOURCE_DIR})
41 | # add include dir as include dir
42 | target_include_directories(${COMPILE_ID} PRIVATE ${INCLUDE_DIR})
43 | # add shared dir as include dir
44 | target_include_directories(${COMPILE_ID} PUBLIC ${SHARED_DIR})
45 | # codegen includes
46 | target_include_directories(${COMPILE_ID} PRIVATE ${EXTERN_DIR}/includes/${CODEGEN_ID}/include)
47 | # rapidjson includes
48 | target_include_directories(${COMPILE_ID} PRIVATE ${EXTERN_DIR}/includes/beatsaber-hook/shared/rapidjson/include)
49 |
50 | target_link_libraries(${COMPILE_ID} PRIVATE -llog)
51 | # add extern stuff like libs and other includes
52 | include(extern.cmake)
53 |
54 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD
55 | COMMAND ${CMAKE_STRIP} -d --strip-all
56 | "lib${COMPILE_ID}.so" -o "stripped_lib${COMPILE_ID}.so"
57 | COMMENT "Strip debug symbols done on final binary.")
58 |
59 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD
60 | COMMAND ${CMAKE_COMMAND} -E make_directory debug
61 | COMMENT "Rename the lib to debug_ since it has debug symbols"
62 | )
63 |
64 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD
65 | COMMAND ${CMAKE_COMMAND} -E rename lib${COMPILE_ID}.so debug/lib${COMPILE_ID}.so
66 | COMMENT "Rename the lib to debug_ since it has debug symbols"
67 | )
68 |
69 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD
70 | COMMAND ${CMAKE_COMMAND} -E rename stripped_lib${COMPILE_ID}.so lib${COMPILE_ID}.so
71 | COMMENT "Rename the stripped lib to regular"
72 | )
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Replay
2 |
3 | ## Credits
4 |
5 | * [zoller27osu](https://github.com/zoller27osu), [Sc2ad](https://github.com/Sc2ad) and [jakibaki](https://github.com/jakibaki) - [beatsaber-hook](https://github.com/sc2ad/beatsaber-hook)
6 | * [raftario](https://github.com/raftario) - [vscode-bsqm](https://github.com/raftario/vscode-bsqm) and [this template](https://github.com/raftario/bmbf-mod-template)
7 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 | - Fix score not longer replaying as the exact same (might be because of obstacles)
3 | - Fix obstacle interaction not working
4 | - Make percent in ui be greener closer to 100%
5 | - After watching a replay and then playing a map modifiers are still what they were in the replay
6 | - Replay menu in level succeed and failed screens, can choose to overwrite current replay or watch the last play (saved in temp file)
7 | - Get note controller from https://discord.com/channels/441805394323439646/864240224400572467/939952451560288336
8 | - Set obstacle player interaction count manually to make more accurate
9 | - Time manipulation
10 | - UI for the above sad
11 | - Speed manipulation
12 | - more ui
13 | - Add config file with utils
14 | - Check replay edge cases and ensure correct total score
15 | - Add camera manipulation
16 | - check jump y offset
17 | - Add Avatar stuff
18 | - Add hollywood
19 | - Make fancy hollywood ui
20 | - Mux audio in game for mp4
21 | - Make hollywood render faster?
22 | - Upload mp4 from quest to youtube????
23 |
24 | # UI Page Ideas
25 | ## Center View Controller
26 | - openable graph of percentage and energy throughout play
27 |
28 | - Camera drop down menu?
29 |
30 | ## Right View Controller (Camera Manipulation)
31 | - drop down menu, each camera type has different ui
32 |
33 | ## Left View Controller (Hollywood)
34 | - fps
35 | - resolution
36 | - fov
37 | - bitrate
38 |
39 | Based on above settings:
40 | - approximate render time
41 | - approximate file size
42 |
43 | - Slider to choose when to start and end replay
44 |
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | Param(
2 | [Parameter(Mandatory=$false)]
3 | [Switch]$clean
4 | )
5 |
6 | # if user specified clean, remove all build files
7 | if ($clean.IsPresent)
8 | {
9 | if (Test-Path -Path "build")
10 | {
11 | remove-item build -R
12 | }
13 | }
14 |
15 | $NDKPath = Get-Content $PSScriptRoot/ndkpath.txt
16 |
17 | if (($clean.IsPresent) -or (-not (Test-Path -Path "build")))
18 | {
19 | $out = new-item -Path build -ItemType Directory
20 | }
21 |
22 | cd build
23 | & cmake -G "Ninja" -DCMAKE_BUILD_TYPE="RelWithDebInfo" ../
24 | & cmake --build . -j 8
25 | cd ..
--------------------------------------------------------------------------------
/copy.ps1:
--------------------------------------------------------------------------------
1 | & $PSScriptRoot/build.ps1
2 | if ($?) {
3 | adb push build/libreplay.so /sdcard/Android/data/com.beatgames.beatsaber/files/mods/libreplay.so
4 | if ($?) {
5 | adb shell am force-stop com.beatgames.beatsaber
6 | adb shell am start com.beatgames.beatsaber/com.unity3d.player.UnityPlayerActivity
7 | if ($args[0] -eq "--log") {
8 | $timestamp = Get-Date -Format "MM-dd HH:mm:ss.fff"
9 | adb logcat -c
10 | adb logcat -T "$timestamp" main-modloader:W QuestHook[Chroma`|v0.1.0]:* QuestHook[UtilsLogger`|v1.0.12]:* AndroidRuntime:E *:S
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/createqmod.ps1:
--------------------------------------------------------------------------------
1 | Param(
2 | [String]$qmodname="replay",
3 | [Parameter(Mandatory=$false)]
4 | [Switch]$clean
5 | )
6 |
7 | if ($qmodName -eq "")
8 | {
9 | echo "Give a proper qmod name and try again"
10 | exit
11 | }
12 | $mod = "./mod.json"
13 | $modJson = Get-Content $mod -Raw | ConvertFrom-Json
14 |
15 | $filelist = @($mod)
16 |
17 | $cover = "./" + $modJson.coverImage
18 | if ((-not ($cover -eq "./")) -and (Test-Path $cover))
19 | {
20 | $filelist += ,$cover
21 | }
22 |
23 | foreach ($mod in $modJson.modFiles)
24 | {
25 | $path = "./build/" + $mod
26 | if (-not (Test-Path $path))
27 | {
28 | $path = "./extern/libs/" + $mod
29 | }
30 | $filelist += $path
31 | }
32 |
33 | foreach ($lib in $modJson.libraryFiles)
34 | {
35 | $path = "./extern/libs/" + $lib
36 | if (-not (Test-Path $path))
37 | {
38 | $path = "./build/" + $lib
39 | }
40 | $filelist += $path
41 | }
42 |
43 | $zip = $qmodName + ".zip"
44 | $qmod = $qmodName + ".qmod"
45 |
46 | if ((-not ($clean.IsPresent)) -and (Test-Path $qmod))
47 | {
48 | echo "Making Clean Qmod"
49 | Move-Item $qmod $zip -Force
50 | }
51 |
52 | Compress-Archive -Path $filelist -DestinationPath $zip -Update
53 | Move-Item $zip $qmod -Force
--------------------------------------------------------------------------------
/extern.cmake:
--------------------------------------------------------------------------------
1 | # YOU SHOULD NOT MANUALLY EDIT THIS FILE, QPM WILL VOID ALL CHANGES
2 | # always added
3 | target_include_directories(${COMPILE_ID} PRIVATE ${EXTERN_DIR}/includes)
4 | target_include_directories(${COMPILE_ID} SYSTEM PRIVATE ${EXTERN_DIR}/includes/libil2cpp/il2cpp/libil2cpp)
5 |
6 | # includes and compile options added by other libraries
7 | RECURSE_FILES(src_inline_hook_beatsaber_hook_local_extra_c ${EXTERN_DIR}/includes/beatsaber-hook/src/inline-hook/*.c)
8 | RECURSE_FILES(src_inline_hook_beatsaber_hook_local_extra_cpp ${EXTERN_DIR}/includes/beatsaber-hook/src/inline-hook/*.cpp)
9 | target_sources(${COMPILE_ID} PRIVATE ${src_inline_hook_beatsaber_hook_local_extra_c})
10 | target_sources(${COMPILE_ID} PRIVATE ${src_inline_hook_beatsaber_hook_local_extra_cpp})
11 | target_include_directories(${COMPILE_ID} SYSTEM PRIVATE ${EXTERN_DIR}/includes/questui/shared/cppcodec)
12 |
13 | # libs dir -> stores .so or .a files (or symlinked!)
14 | target_link_directories(${COMPILE_ID} PRIVATE ${EXTERN_DIR}/libs)
15 | RECURSE_FILES(so_list ${EXTERN_DIR}/libs/*.so)
16 | RECURSE_FILES(a_list ${EXTERN_DIR}/libs/*.a)
17 |
18 | # every .so or .a that needs to be linked, put here!
19 | # I don't believe you need to specify if a lib is static or not, poggers!
20 | target_link_libraries(${COMPILE_ID} PRIVATE
21 | ${so_list}
22 | ${a_list}
23 | )
24 |
--------------------------------------------------------------------------------
/include/EventTypes.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "GlobalNamespace/NoteData.hpp"
5 | #include "GlobalNamespace/NoteLineLayer.hpp"
6 | #include "GlobalNamespace/ColorType.hpp"
7 | #include "GlobalNamespace/NoteCutDirection.hpp"
8 | #include "GlobalNamespace/NoteCutInfo.hpp"
9 | #include "GlobalNamespace/NoteController.hpp"
10 | #include "GlobalNamespace/SaberSwingRatingCounter.hpp"
11 | #include "GlobalNamespace/CutScoreBuffer.hpp"
12 | #include "UnityEngine/Transform.hpp"
13 | #include "UnityEngine/Quaternion.hpp"
14 | #include
15 |
16 | // avoid using namespace in headers, but do as you wish
17 | using namespace GlobalNamespace;
18 |
19 | namespace Replay {
20 | namespace PlayerEventTypes {
21 | struct EulerTransform {
22 | UnityEngine::Vector3 position;
23 | UnityEngine::Vector3 rotation;
24 |
25 | constexpr EulerTransform() = default;
26 |
27 | constexpr EulerTransform(const UnityEngine::Vector3 &position, const UnityEngine::Vector3 &rotation) : position(
28 | position), rotation(rotation) {}
29 | };
30 |
31 | struct EulerTransformEvent {
32 | float time;
33 | EulerTransform transform;
34 |
35 | constexpr EulerTransformEvent() = default;
36 |
37 | constexpr EulerTransformEvent(const float time, const EulerTransform& transform) : time(time), transform(transform) {}
38 |
39 | void Write(std::ofstream& writer) const {
40 | writer.write(reinterpret_cast(&time), sizeof(float));
41 | writer.write(reinterpret_cast(&transform), sizeof(EulerTransform));
42 | }
43 |
44 | constexpr EulerTransformEvent(std::ifstream& reader) {
45 | reader.read(reinterpret_cast(&time), sizeof(float));
46 | reader.read(reinterpret_cast(&transform), sizeof(EulerTransform));
47 | }
48 | };
49 |
50 | const inline static byte headEventID = 0b00000000;
51 |
52 | const inline static byte leftSaberEventID = 0b00000001;
53 |
54 | const inline static byte rightSaberEventID = 0b00000010;
55 |
56 | struct PlayerTransforms {
57 | EulerTransform head;
58 | EulerTransform leftSaber;
59 | EulerTransform rightSaber;
60 |
61 | constexpr PlayerTransforms() = default;
62 |
63 | constexpr PlayerTransforms(const EulerTransform &head, const EulerTransform &leftSaber,
64 | const EulerTransform &rightSaber) : head(head), leftSaber(leftSaber),
65 | rightSaber(rightSaber) {}
66 |
67 | constexpr PlayerTransforms(UnityEngine::Transform* headTransform, UnityEngine::Transform* leftTransform, UnityEngine::Transform* rightTransform) {
68 | head = {headTransform->get_position(), headTransform->get_eulerAngles()};
69 | leftSaber = {leftTransform->get_position(), leftTransform->get_eulerAngles()};
70 | rightSaber = {rightTransform->get_position(), rightTransform->get_eulerAngles()};
71 | }
72 |
73 | constexpr PlayerTransforms(UnityEngine::Vector3 headPos, UnityEngine::Quaternion headRot, UnityEngine::Vector3 leftPos, UnityEngine::Quaternion leftRot, UnityEngine::Vector3 rightPos, UnityEngine::Quaternion rightRot) {
74 | head = {headPos, headRot.get_eulerAngles()};
75 | leftSaber = {leftPos, leftRot.get_eulerAngles()};
76 | rightSaber = {rightPos, rightRot.get_eulerAngles()};
77 | }
78 | };
79 |
80 | struct PlayerEvent {
81 | float time;
82 | PlayerTransforms playerTransforms;
83 | UnityEngine::Vector3 leftSaberTopPos;
84 | UnityEngine::Vector3 rightSaberTopPos;
85 |
86 | constexpr PlayerEvent() = default;
87 |
88 | constexpr PlayerEvent(const float time, const PlayerTransforms& playerTransforms, const UnityEngine::Vector3& leftSaberTopPos, const UnityEngine::Vector3& rightSaberTopPos) :
89 | time(time), playerTransforms(playerTransforms), leftSaberTopPos(leftSaberTopPos), rightSaberTopPos(rightSaberTopPos) {}
90 | };
91 | }
92 |
93 | namespace NoteEventTypes {
94 | struct SimpleNoteCutInfo {
95 | bool speedOK;
96 | bool directionOK;
97 | bool saberTypeOK;
98 | bool wasCutTooSoon;
99 | float saberSpeed;
100 | UnityEngine::Vector3 saberDir;
101 | int saberType;
102 | float timeDeviation;
103 | float cutDirDeviation;
104 | UnityEngine::Vector3 cutPoint;
105 | UnityEngine::Vector3 cutNormal;
106 | float cutDistanceToCenter;
107 | float cutAngle;
108 | UnityEngine::Vector3 worldRotation;
109 | UnityEngine::Vector3 noteRotation;
110 | UnityEngine::Vector3 notePosition;
111 |
112 | constexpr SimpleNoteCutInfo() = default;
113 |
114 | SimpleNoteCutInfo(NoteCutInfo& noteCutInfo) {
115 | speedOK = noteCutInfo.speedOK;
116 | directionOK = noteCutInfo.directionOK;
117 | saberTypeOK = noteCutInfo.saberTypeOK;
118 | wasCutTooSoon = noteCutInfo.wasCutTooSoon;
119 | saberSpeed = noteCutInfo.saberSpeed;
120 | saberDir = noteCutInfo.saberDir;
121 | saberType = (int) noteCutInfo.saberType;
122 | timeDeviation = noteCutInfo.timeDeviation;
123 | cutDirDeviation = noteCutInfo.cutDirDeviation;
124 | cutPoint = noteCutInfo.cutPoint;
125 | cutNormal = noteCutInfo.cutNormal;
126 | cutDistanceToCenter = noteCutInfo.cutDistanceToCenter;
127 | cutAngle = noteCutInfo.cutAngle;
128 | worldRotation = noteCutInfo.worldRotation.get_eulerAngles();
129 | noteRotation = noteCutInfo.noteRotation.get_eulerAngles();
130 | notePosition = noteCutInfo.notePosition;
131 | }
132 |
133 | bool AllIsOkay() {
134 | return speedOK && directionOK && saberTypeOK && !wasCutTooSoon;
135 | }
136 | };
137 |
138 | struct SwingRating {
139 | int beforeCutRating;
140 | int afterCutRating;
141 |
142 | constexpr SwingRating() = default;
143 |
144 | constexpr SwingRating(CutScoreBuffer* cutScoreBuffer) {
145 | beforeCutRating = cutScoreBuffer->get_beforeCutScore();
146 | afterCutRating = cutScoreBuffer->get_afterCutScore();
147 | }
148 |
149 | constexpr SwingRating(int beforeCutRating, int afterCutRating) : beforeCutRating(beforeCutRating), afterCutRating(afterCutRating) {}
150 | };
151 |
152 | struct NoteCutEvent {
153 | int noteHash;
154 | float time;
155 | SimpleNoteCutInfo noteCutInfo;
156 | SwingRating swingRating;
157 |
158 | constexpr NoteCutEvent() = default;
159 |
160 | NoteCutEvent(int noteHash, float time, CutScoreBuffer* cutScoreBuffer) : noteHash(noteHash), time(time) {
161 | // I did not know a better way to make compiler happy, feel free to fix
162 | SimpleNoteCutInfo newNoteCutInfo(cutScoreBuffer->noteCutInfo);
163 | noteCutInfo = newNoteCutInfo;
164 |
165 | SwingRating newSwingRating(cutScoreBuffer);
166 | swingRating = newSwingRating;
167 | }
168 |
169 | NoteCutEvent(int noteHash, float time, NoteCutInfo badNoteCutInfo) : noteHash(noteHash), time(time) {
170 | // I did not know a better way to make compiler happy, feel free to fix
171 | SimpleNoteCutInfo newNoteCutInfo(badNoteCutInfo);
172 | noteCutInfo = newNoteCutInfo;
173 |
174 | SwingRating newSwingRating(0.0f, 0.0f);
175 | swingRating = newSwingRating;
176 | }
177 |
178 | constexpr NoteCutEvent(std::ifstream& reader) {
179 | reader.read(reinterpret_cast(¬eHash), sizeof(int));
180 | reader.read(reinterpret_cast(&time), sizeof(float));
181 | reader.read(reinterpret_cast(¬eCutInfo), sizeof(SimpleNoteCutInfo));
182 | reader.read(reinterpret_cast(&swingRating), sizeof(SwingRating));
183 | }
184 |
185 | void Write(std::ofstream& writer) const {
186 | writer.write(reinterpret_cast(¬eHash), sizeof(int));
187 | writer.write(reinterpret_cast(&time), sizeof(float));
188 | writer.write(reinterpret_cast(¬eCutInfo), sizeof(SimpleNoteCutInfo));
189 | writer.write(reinterpret_cast(&swingRating), sizeof(SwingRating));
190 | }
191 | };
192 |
193 | const inline static byte cutEventID = 0b00000011;
194 |
195 | struct NoteMissEvent {
196 | int noteHash;
197 | float time;
198 |
199 | // allows emplace to work
200 | NoteMissEvent(int noteHash, float time) : noteHash(noteHash), time(time) {}
201 |
202 | NoteMissEvent() = default;
203 |
204 | void Write(std::ofstream& writer) const {
205 | writer.write(reinterpret_cast(¬eHash), sizeof(int));
206 | writer.write(reinterpret_cast(&time), sizeof(float));
207 | }
208 |
209 | constexpr NoteMissEvent(std::ifstream& reader) {
210 | reader.read(reinterpret_cast(¬eHash), sizeof(int));
211 | reader.read(reinterpret_cast(&time), sizeof(float));
212 | }
213 | };
214 |
215 | const inline static byte missEventID = 0b00000100;
216 | }
217 |
218 | namespace ObstacleEventTypes {
219 | struct ObstacleEvent {
220 | float time;
221 | float energy;
222 |
223 | constexpr ObstacleEvent() = default;
224 |
225 | constexpr ObstacleEvent(float time, float energy) : time(time), energy(energy) {}
226 |
227 | constexpr ObstacleEvent(std::ifstream& reader) {
228 | reader.read(reinterpret_cast(&time), sizeof(float));
229 | reader.read(reinterpret_cast(&energy), sizeof(float));
230 | }
231 |
232 | void Write(std::ofstream& writer) const {
233 | writer.write(reinterpret_cast(&time), sizeof(float));
234 | writer.write(reinterpret_cast(&energy), sizeof(float));
235 | }
236 | };
237 |
238 | const inline static byte eventID = 0b00000101;
239 | }
240 | }
241 |
242 | template<>
243 | struct std::hash
244 | {
245 | std::size_t operator()(NoteData* s) const noexcept
246 | {
247 | std::size_t h1 = std::hash{}(s->time);
248 | std::size_t h2 = std::hash{}(s->lineIndex);
249 | std::size_t h3 = std::hash{}(s->noteLineLayer);
250 | std::size_t h4 = std::hash{}(s->beforeJumpNoteLineLayer);
251 | std::size_t h5 = std::hash{}(s->gameplayType);
252 | std::size_t h6 = std::hash{}(s->scoringType);
253 | std::size_t h7 = std::hash{}(s->colorType);
254 | std::size_t h8 = std::hash{}(s->cutDirection);
255 | std::size_t h9 = std::hash{}(s->timeToNextColorNote);
256 | std::size_t h10 = std::hash{}(s->timeToPrevColorNote);
257 | std::size_t h11 = std::hash{}(s->flipLineIndex);
258 | std::size_t h12 = std::hash{}(s->flipYSide);
259 | std::size_t h13 = std::hash{}(s->cutDirectionAngleOffset);
260 | std::size_t h14 = std::hash{}(s->cutSfxVolumeMultiplier);
261 |
262 | return h1 ^ h2 ^ h3 ^ h4 ^ h5 ^ h6 ^ h7 ^ h8 ^ h9 ^ h10 ^ h11 ^ h12 ^ h13 ^ h14;
263 | }
264 | };
--------------------------------------------------------------------------------
/include/Recording/NoteEventRecorder.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "GlobalNamespace/CutScoreBuffer.hpp"
5 | #include "GlobalNamespace/NoteCutInfo.hpp"
6 | #include "GlobalNamespace/NoteData.hpp"
7 | #include "fstream"
8 | #include "EventTypes.hpp"
9 | #include "Utils/SongUtils.hpp"
10 | #include "Utils/ReplayUtils.hpp"
11 | #include "Utils/FileUtils.hpp"
12 | #include
13 |
14 | using namespace Replay::NoteEventTypes;
15 | using namespace GlobalNamespace;
16 |
17 | namespace Replay {
18 | class NoteEventRecorder {
19 | private:
20 | std::vector cutEvents;
21 | std::vector missEvents;
22 |
23 | float frameTime = 0;
24 | int eventsInFrame = 0;
25 |
26 | float GetEventSaveTime(float songTime);
27 | public:
28 | std::vector> cutTimes;
29 |
30 | void AddCutEvent(CutScoreBuffer* cutScoreBuffer, float time);
31 | void AddCutEvent(NoteCutInfo noteCutInfo, float time);
32 |
33 | void AddMissEvent(NoteController* noteController);
34 |
35 | void WriteEvents(std::ofstream& output);
36 |
37 | float GetAverageCutScore();
38 | };
39 | }
--------------------------------------------------------------------------------
/include/Recording/ObstacleEventRecorder.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "GlobalNamespace/GameEnergyCounter.hpp"
5 | #include "GlobalNamespace/PlayerHeadAndObstacleInteraction.hpp"
6 | #include "GlobalNamespace/ObstacleController.hpp"
7 | #include "System/Collections/Generic/HashSet_1.hpp"
8 | #include "UnityEngine/Time.hpp"
9 | #include "fstream"
10 | #include "EventTypes.hpp"
11 | #include "Utils/SongUtils.hpp"
12 | #include "Utils/FileUtils.hpp"
13 |
14 | using namespace Replay;
15 |
16 | namespace Replay {
17 | class ObstacleEventRecorder {
18 | private:
19 | std::vector events;
20 |
21 | bool lastInteracting = false;
22 | public:
23 | void AddEvent(GlobalNamespace::GameEnergyCounter* gameEnergyCounter);
24 |
25 | void WriteEvents(std::ofstream& output);
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/include/Recording/PlayerRecorder.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "UnityEngine/Transform.hpp"
5 | #include "UnityEngine/Time.hpp"
6 | #include "GlobalNamespace/SaberType.hpp"
7 | #include "GlobalNamespace/BladeMovementDataElement.hpp"
8 | #include "fstream"
9 | #include "EventTypes.hpp"
10 | #include "Utils/SongUtils.hpp"
11 | #include "Utils/FileUtils.hpp"
12 | #include "Utils/SaberUtils.hpp"
13 | #include "Utils/ReplayUtils.hpp"
14 |
15 | using namespace Replay;
16 |
17 | namespace Replay {
18 | class PlayerRecorder {
19 | private:
20 | GlobalNamespace::BladeMovementDataElement leftSaberLastSavedMovement;
21 |
22 | GlobalNamespace::BladeMovementDataElement rightSaberLastSavedMovement;
23 |
24 | std::vector playerEvents;
25 |
26 | std::vector headEvents;
27 | std::vector leftSaberEvents;
28 | std::vector rightSaberEvents;
29 |
30 | void GetImportantEvents();// change name later
31 |
32 | void AddSaberEvent(GlobalNamespace::SaberType saberType);
33 | public:
34 | void AddEvent(PlayerEventTypes::PlayerTransforms const& playerTransforms);
35 |
36 | void AddSaberMovement(GlobalNamespace::BladeMovementDataElement bladeMovement, GlobalNamespace::SaberType saberType);
37 |
38 | void WriteEvents(std::ofstream& output);
39 | };
40 | }
--------------------------------------------------------------------------------
/include/Recording/ReplayRecorder.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "fstream"
5 | #include "vector"
6 | #include "Recording/PlayerRecorder.hpp"
7 | #include "Recording/NoteEventRecorder.hpp"
8 | #include "Recording/ObstacleEventRecorder.hpp"
9 | #include "GlobalNamespace/LevelCompletionResults.hpp"
10 | #include "Utils/ReplayUtils.hpp"
11 | #include
12 |
13 | #include "rapidjson/document.h"
14 | #include "rapidjson/stringbuffer.h"
15 | #include
16 | #include
17 |
18 | namespace Replay {
19 | class ReplayRecorder {
20 | private:
21 | void CreateClearedSpecificMetadata(GlobalNamespace::LevelCompletionResults* results, rapidjson::Document::AllocatorType& allocator);
22 |
23 | void CreateFailedSpecificMetadata(GlobalNamespace::LevelCompletionResults* results, rapidjson::Document::AllocatorType& allocator);
24 |
25 | void CreateMetadata(GlobalNamespace::LevelCompletionResults* results);
26 |
27 | bool ShouldMoveFile(GlobalNamespace::LevelCompletionResults* results, std::string_view filepath);
28 |
29 | void WriteReplayFile(std::string path);
30 | public:
31 | void Init();
32 |
33 | void StopRecording(GlobalNamespace::LevelCompletionResults* results);
34 |
35 | PlayerRecorder playerRecorder;
36 | NoteEventRecorder noteEventRecorder;
37 | ObstacleEventRecorder obstacleEventRecorder;
38 |
39 | rapidjson::Document metadata;
40 | };
41 | }
--------------------------------------------------------------------------------
/include/ReplayManager.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 | #include "Recording/ReplayRecorder.hpp"
4 | #include "Replaying/Replayer.hpp"
5 | #include "Utils/SongUtils.hpp"
6 |
7 | namespace Replay {
8 | enum ReplayState {
9 | RECORDING,
10 | REPLAYING,
11 | NONE
12 | };
13 |
14 | class ReplayManager {
15 | public:
16 | static inline Replay::ReplayState replayState;
17 |
18 | static Replay::ReplayRecorder recorder;
19 |
20 | static Replay::Replayer replayer;
21 | };
22 | }
--------------------------------------------------------------------------------
/include/Replaying/NoteEventReplayer.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "GlobalNamespace/NoteController.hpp"
5 | #include "GlobalNamespace/NoteCutInfo.hpp"
6 | #include "GlobalNamespace/NoteData.hpp"
7 | #include "GlobalNamespace/SharedCoroutineStarter.hpp"
8 | #include "fstream"
9 | #include "EventTypes.hpp"
10 | #include "Utils/SongUtils.hpp"
11 | #include "Utils/ReplayUtils.hpp"
12 | #include
13 | #include "System/Collections/IEnumerator.hpp"
14 | #include "custom-types/shared/coroutine.hpp"
15 | #include "Utils/SaberUtils.hpp"
16 |
17 | #include "UnityEngine/Resources.hpp"
18 | #include "GlobalNamespace/GameNoteController.hpp"
19 | #include "GlobalNamespace/Saber.hpp"
20 | #include "GlobalNamespace/SaberManager.hpp"
21 | #include "GlobalNamespace/SaberType.hpp"
22 | #include "GlobalNamespace/SaberTypeObject.hpp"
23 | #include "GlobalNamespace/NoteData.hpp"
24 | #include "GlobalNamespace/ColorType.hpp"
25 |
26 | // Using namespace in headers is icky
27 | using namespace Replay::NoteEventTypes;
28 |
29 | namespace Replay {
30 | struct ActiveNoteCutEvent {
31 | NoteController* note;
32 | NoteCutEvent event;
33 |
34 | constexpr ActiveNoteCutEvent() = default;
35 |
36 | constexpr ActiveNoteCutEvent(NoteController *note, const NoteCutEvent &event) : note(note), event(event) {}
37 | };
38 |
39 | struct SwingRatingData {
40 | int noteHash;
41 | SwingRating swingRating;
42 |
43 | constexpr SwingRatingData() = default;
44 |
45 | constexpr SwingRatingData(int noteHash, SwingRating swingRating) : noteHash(noteHash), swingRating(swingRating) {}
46 | };
47 |
48 | struct ActiveNoteMissEvent {
49 | NoteController* note;
50 | NoteMissEvent event;
51 |
52 | constexpr ActiveNoteMissEvent() = default;
53 |
54 | constexpr ActiveNoteMissEvent(NoteController *note, const NoteMissEvent &event) : note(note), event(event) {}
55 | };
56 |
57 | struct EventToRun {
58 | float time;
59 | bool isCutEvent;
60 | int eventIndex;
61 |
62 | constexpr EventToRun() = default;
63 |
64 | constexpr EventToRun(float time, bool isCutEvent, int eventIndex) : time(time), isCutEvent(isCutEvent), eventIndex(eventIndex) {}
65 |
66 | bool operator < (const EventToRun& str) const {
67 | return (time < str.time);
68 | }
69 |
70 | bool operator > (const EventToRun& str) const {
71 | return (eventIndex > str.eventIndex);
72 | }
73 | };
74 |
75 | class NoteEventReplayer {
76 | private:
77 | custom_types::Helpers::Coroutine Update();
78 | GlobalNamespace::SaberManager* saberManager;
79 |
80 | public:
81 | void Init();
82 |
83 | std::vector cutEvents;
84 | std::vector activeCutEvents;
85 |
86 | std::vector swingRatings;
87 |
88 | std::vector missEvents;
89 | std::vector activeMissEvents;
90 |
91 | void AddActiveEvents(GlobalNamespace::NoteController* noteController);
92 |
93 | void ReadCutEvents(std::ifstream& input, int eventsLength);
94 |
95 | void ReadMissEvents(std::ifstream& input, int eventsLength);
96 | };
97 | }
--------------------------------------------------------------------------------
/include/Replaying/ObstacleEventReplayer.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "GlobalNamespace/GameEnergyCounter.hpp"
5 | #include "GlobalNamespace/PlayerHeadAndObstacleInteraction.hpp"
6 | #include "GlobalNamespace/ObstacleController.hpp"
7 | #include "System/Collections/Generic/List_1.hpp"
8 | #include "fstream"
9 | #include "EventTypes.hpp"
10 | #include "Utils/ReplayUtils.hpp"
11 | #include "Utils/SongUtils.hpp"
12 |
13 | using namespace Replay::ObstacleEventTypes;
14 | using namespace System::Collections::Generic;
15 |
16 | namespace Replay {
17 | class ObstacleEventReplayer {
18 | private:
19 | int lastPlayerObstacleInteractionCount = 0;
20 | public:
21 | std::vector events;
22 |
23 | int lastIndex = 0;
24 |
25 | int nextEventIndex = 0;
26 |
27 | void ReadEvents(std::ifstream& input, int eventsLength);
28 |
29 | bool ShouldSetEnergy();
30 | };
31 | }
--------------------------------------------------------------------------------
/include/Replaying/PlayerReplayer.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "UnityEngine/Transform.hpp"
5 | #include "UnityEngine/Quaternion.hpp"
6 | #include "UnityEngine/Vector3.hpp"
7 | #include "GlobalNamespace/PlayerTransforms.hpp"
8 | #include "fstream"
9 | #include "EventTypes.hpp"
10 | #include "Utils/ReplayUtils.hpp"
11 |
12 | using namespace Replay::PlayerEventTypes;
13 |
14 | namespace Replay {
15 | class PlayerReplayer {
16 | private:
17 | std::vector headEvents;
18 | std::vector leftSaberEvents;
19 | std::vector rightSaberEvents;
20 |
21 | int headIndex = 0;
22 | int leftSaberIndex = 0;
23 | int rightSaberIndex = 0;
24 | public:
25 | void ReadHeadEvents(std::ifstream& input, int eventsLength);
26 | void ReadLeftSaberEvents(std::ifstream& input, int eventsLength);
27 | void ReadRightSaberEvents(std::ifstream& input, int eventsLength);
28 |
29 | void SetPlayerTransforms(GlobalNamespace::PlayerTransforms* playerTransforms);
30 | };
31 | }
--------------------------------------------------------------------------------
/include/Replaying/Replayer.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "fstream"
5 | #include "vector"
6 | #include "Replaying/PlayerReplayer.hpp"
7 | #include "Replaying/NoteEventReplayer.hpp"
8 | #include "Replaying/ObstacleEventReplayer.hpp"
9 | #include "Utils/ReplayUtils.hpp"
10 |
11 | #include "System/Collections/IEnumerator.hpp"
12 | #include "custom-types/shared/coroutine.hpp"
13 |
14 | using namespace Replay;
15 |
16 | namespace Replay {
17 | class Replayer {
18 | private:
19 | custom_types::Helpers::Coroutine WaitForSongStartToInit();
20 |
21 | public:
22 | void Init(std::string_view path);
23 |
24 | Replay::PlayerReplayer playerReplayer;
25 | Replay::NoteEventReplayer noteEventReplayer;
26 | Replay::ObstacleEventReplayer obstacleEventReplayer;
27 |
28 | void ReadReplayFile(std::string_view path);
29 | };
30 | }
--------------------------------------------------------------------------------
/include/Sprites.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 |
4 | namespace Replay::Sprites {
5 | static std::string ReplayIcon = R"(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)";
6 | }
--------------------------------------------------------------------------------
/include/UI/ReplayViewController.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "GlobalNamespace/LevelBar.hpp"
5 | #include "UnityEngine/GameObject.hpp"
6 | #include "UnityEngine/Transform.hpp"
7 | #include "UnityEngine/RectTransform.hpp"
8 | #include "TMPro/TextMeshProUGUI.hpp"
9 |
10 | #include "custom-types/shared/macros.hpp"
11 | #include "HMUI/ViewController.hpp"
12 |
13 | #include "System/Collections/IEnumerator.hpp"
14 | #include "custom-types/shared/coroutine.hpp"
15 | #include "UnityEngine/WaitForSeconds.hpp"
16 |
17 | DECLARE_CLASS_CODEGEN(Replay::UI, ReplayViewController, HMUI::ViewController,
18 |
19 | DECLARE_OVERRIDE_METHOD(void, DidActivate, il2cpp_utils::FindMethodUnsafe("HMUI", "ViewController", "DidActivate", 3), bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling);
20 |
21 | public:
22 | void Init(std::string_view filePath, bool overwriteFile);
23 |
24 | std::string path;
25 | bool overwrite;
26 |
27 | void CreateLevelBar(UnityEngine::Transform* parent);
28 | void CreateText(UnityEngine::RectTransform* parent);
29 | void CreateButtons(UnityEngine::RectTransform* parent);
30 |
31 | void SetupLevelBar();
32 | void SetText();
33 | void SetButton(bool overwrite);
34 |
35 | UnityEngine::GameObject* levelBar;
36 |
37 | TMPro::TextMeshProUGUI* dateText;
38 | TMPro::TextMeshProUGUI* scoreOrFailedText;
39 | TMPro::TextMeshProUGUI* modifiersText;
40 |
41 | TMPro::TextMeshProUGUI* averageCutScoreText;
42 | TMPro::TextMeshProUGUI* missedNotesText;
43 | TMPro::TextMeshProUGUI* maxComboText;
44 |
45 | UnityEngine::UI::Button* deleteButton;
46 | )
--------------------------------------------------------------------------------
/include/UI/UIManager.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "GlobalNamespace/SinglePlayerLevelSelectionFlowCoordinator.hpp"
5 | #include "GlobalNamespace/StandardLevelDetailView.hpp"
6 | #include "UnityEngine/RectTransform.hpp"
7 |
8 | #include "UI/ReplayViewController.hpp"
9 | #include "ReplayManager.hpp"
10 |
11 | namespace Replay::UI {
12 | class UIManager {
13 | public:
14 | static inline GlobalNamespace::SinglePlayerLevelSelectionFlowCoordinator* singlePlayerFlowCoordinator;
15 |
16 | static inline Replay::UI::ReplayViewController* replayViewController;
17 |
18 | static inline UnityEngine::Transform* buttonParent;
19 |
20 | static void SetReplayButtonOnClick(UnityEngine::Transform* buttonTransform, std::string path, bool overwrite = false);
21 | static UnityEngine::Transform* CreateReplayButton(UnityEngine::Transform* parent, UnityEngine::UI::Button* templateButton, UnityEngine::UI::Button* actionButton, std::string path, bool overwrite = false);
22 |
23 | static void CreateReplayCanvas(GlobalNamespace::StandardLevelDetailView* standardLevelDetailView, bool replayFileExists);
24 |
25 | static void SetReplayButtonCanvasActive(bool active) {
26 | buttonParent->Find(newcsstr("ReplayButtonCanvas"))->get_gameObject()->SetActive(active);//Name of canvas is defined else where, use that eventually
27 | float xpos = active ? 4.2 : -1.8;
28 | ((UnityEngine::RectTransform*) buttonParent)->set_anchoredPosition({xpos, -55});
29 | }
30 | };
31 | }
--------------------------------------------------------------------------------
/include/Utils/FileUtils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include
5 | #include "rapidjson/document.h"
6 | #include "rapidjson/stringbuffer.h"
7 | #include "rapidjson/error/error.h"
8 | #include
9 | #include
10 |
11 | namespace Replay {
12 | class FileUtils {
13 | public:
14 | template
15 | static void WriteEvents(std::vector events, byte eventID, std::ofstream& output) {
16 | unsigned int eventCount = (int)events.size();
17 |
18 | if(eventCount == 0) return;
19 |
20 | //Write events header
21 | output.write(reinterpret_cast(&eventID), sizeof(byte));
22 | output.write(reinterpret_cast(&eventCount), sizeof(unsigned int));
23 |
24 | //Write data
25 | for(T event : events) {
26 | event.Write(output);
27 | }
28 | }
29 |
30 | static rapidjson::Document GetMetadataFromReplayFile(std::string_view path) {
31 | log("Reading Replay file metadata at %s", path.data());
32 | std::ifstream input = std::ifstream(path, std::ios::binary);
33 |
34 | rapidjson::Document metadata;
35 |
36 | if(input.is_open()) {
37 | int magicBytes;
38 | input.read(reinterpret_cast(&magicBytes), sizeof(int));
39 | if(magicBytes != replayMagicBytes) {
40 | log("INCORRECT MAGIC BYTES");
41 | return metadata;
42 | }
43 |
44 | byte version;
45 | input.read(reinterpret_cast(&version), sizeof(byte));
46 |
47 | int metadataLength;
48 | input.read(reinterpret_cast(&metadataLength), sizeof(int));
49 |
50 | log("Metadata length is %zu", (size_t) metadataLength);
51 |
52 | std::string metadataString;
53 | metadataString.resize(metadataLength);
54 | input.read(metadataString.data(), (size_t) metadataLength);
55 |
56 | log("%s", metadataString.c_str());
57 |
58 | rapidjson::ParseResult ok = metadata.Parse(metadataString.c_str());
59 | if (!ok) {
60 | log("JSON parse error");
61 | }
62 | } else {
63 | log("COULD NOT FIND REPLAY FILE");
64 | }
65 |
66 | return metadata;
67 | }
68 | };
69 | }
--------------------------------------------------------------------------------
/include/Utils/FindComponentsUtils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "GlobalNamespace/SimpleDialogPromptViewController.hpp"
5 | #include "GlobalNamespace/LevelSelectionNavigationController.hpp"
6 | #include "HMUI/ScreenSystem.hpp"
7 |
8 | namespace Replay::FindComponentsUtils {
9 |
10 | #define CacheFindComponentDeclare(namespace, name) namespace::name* Get##name();
11 |
12 | CacheFindComponentDeclare(GlobalNamespace, SimpleDialogPromptViewController)
13 | CacheFindComponentDeclare(GlobalNamespace, LevelSelectionNavigationController)
14 | CacheFindComponentDeclare(HMUI, ScreenSystem)
15 |
16 | void ClearCache();
17 |
18 | }
--------------------------------------------------------------------------------
/include/Utils/MathUtils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "UnityEngine/Quaternion.hpp"
4 | #include "UnityEngine/Vector3.hpp"
5 |
6 | namespace Replay {
7 | class MathUtils {
8 | public:
9 | // TODO: Use sombrero!
10 | static UnityEngine::Quaternion LerpEulerAngles(UnityEngine::Vector3 angleA, UnityEngine::Vector3 angleB, float amount) {
11 | UnityEngine::Quaternion quaternionA = UnityEngine::Quaternion::Euler(angleA);
12 | UnityEngine::Quaternion quaternionB = UnityEngine::Quaternion::Euler(angleB);
13 |
14 | return UnityEngine::Quaternion::Lerp(quaternionA, quaternionB, amount);
15 | }
16 |
17 | // TODO: Sombrero!
18 | static constexpr UnityEngine::Vector3 Lerp(UnityEngine::Vector3 const& value1, UnityEngine::Vector3 const& value2, float amount) {
19 | return {value1.x + (value2.x - value1.x) * amount, value1.y + (value2.y - value1.y) * amount, value1.z + (value2.z - value1.z) * amount};
20 | }
21 | };
22 | }
--------------------------------------------------------------------------------
/include/Utils/ModifiersUtils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "GlobalNamespace/GameplayModifiers.hpp"
5 |
6 | namespace Replay {
7 | class ModifiersUtils {
8 | public:
9 | static std::vector ModifiersToStrings(GlobalNamespace::GameplayModifiers* gameplayModifiers) {
10 | std::vector strings;
11 |
12 | if(gameplayModifiers->energyType == GlobalNamespace::GameplayModifiers::EnergyType::Battery) strings.push_back("BatteryEnergy");
13 | if(gameplayModifiers->noFailOn0Energy) strings.push_back("NoFail");
14 | if(gameplayModifiers->instaFail) strings.push_back("InstaFail");
15 | if(gameplayModifiers->enabledObstacleType == GlobalNamespace::GameplayModifiers::EnabledObstacleType::NoObstacles) strings.push_back("NoWalls");
16 | if(gameplayModifiers->noBombs) strings.push_back("NoBombs");
17 | if(gameplayModifiers->strictAngles) strings.push_back("StrictAngles");
18 | if(gameplayModifiers->disappearingArrows) strings.push_back("DisappearingArrows");
19 | if(gameplayModifiers->songSpeed == GlobalNamespace::GameplayModifiers::SongSpeed::Slower) strings.push_back("SlowerSong");
20 | if(gameplayModifiers->songSpeed == GlobalNamespace::GameplayModifiers::SongSpeed::Faster) strings.push_back("FasterSong");
21 | if(gameplayModifiers->songSpeed == GlobalNamespace::GameplayModifiers::SongSpeed::SuperFast) strings.push_back("SuperFastSong");
22 | if(gameplayModifiers->noArrows) strings.push_back("NoArrows");
23 | if(gameplayModifiers->ghostNotes) strings.push_back("GhostNotes");
24 | if(gameplayModifiers->proMode) strings.push_back("ProMode");
25 | if(gameplayModifiers->zenMode) strings.push_back("ZenMode");
26 | if(gameplayModifiers->smallCubes) strings.push_back("SmallCubes");
27 |
28 | return strings;
29 | }
30 |
31 | static GlobalNamespace::GameplayModifiers* CreateModifiersFromStrings(std::vector strings) {
32 | GlobalNamespace::GameplayModifiers::EnergyType energyType = GlobalNamespace::GameplayModifiers::EnergyType::Bar;
33 | if(std::count(strings.begin(), strings.end(), "BatteryEnergy")) {
34 | energyType = GlobalNamespace::GameplayModifiers::EnergyType::Battery;
35 | }
36 |
37 | GlobalNamespace::GameplayModifiers::EnabledObstacleType enabledObstacleType = GlobalNamespace::GameplayModifiers::EnabledObstacleType::All;
38 | if(std::count(strings.begin(), strings.end(), "NoWalls")) {
39 | enabledObstacleType = GlobalNamespace::GameplayModifiers::EnabledObstacleType::NoObstacles;
40 | }
41 |
42 | GlobalNamespace::GameplayModifiers::SongSpeed songSpeed = GlobalNamespace::GameplayModifiers::SongSpeed::Normal;
43 | if(std::count(strings.begin(), strings.end(), "SlowerSong")) {
44 | songSpeed = GlobalNamespace::GameplayModifiers::SongSpeed::Slower;
45 | } else if(std::count(strings.begin(), strings.end(), "FasterSong")) {
46 | songSpeed = GlobalNamespace::GameplayModifiers::SongSpeed::Faster;
47 | } else if(std::count(strings.begin(), strings.end(), "SuperFastSong")) {
48 | songSpeed = GlobalNamespace::GameplayModifiers::SongSpeed::SuperFast;
49 | }
50 |
51 | GlobalNamespace::GameplayModifiers* modifiers = GlobalNamespace::GameplayModifiers::New_ctor(
52 | energyType,
53 | std::count(strings.begin(), strings.end(), "NoFail"),
54 | std::count(strings.begin(), strings.end(), "InstaFail"),
55 | false,
56 | enabledObstacleType,
57 | std::count(strings.begin(), strings.end(), "NoBombs"),
58 | false,
59 | std::count(strings.begin(), strings.end(), "StrictAngles"),
60 | std::count(strings.begin(), strings.end(), "DisappearingArrows"),
61 | songSpeed,
62 | std::count(strings.begin(), strings.end(), "NoArrows"),
63 | std::count(strings.begin(), strings.end(), "GhostNotes"),
64 | std::count(strings.begin(), strings.end(), "ProMode"),
65 | std::count(strings.begin(), strings.end(), "ZenMode"),
66 | std::count(strings.begin(), strings.end(), "SmallCubes")
67 | );
68 |
69 | return modifiers;
70 | }
71 |
72 | static std::string GetInitialsFromModifierName(std::string modifierName) {
73 | if(modifierName == "BatteryEnergy") {
74 | return "BE";
75 | } else if(modifierName == "InstaFail") {
76 | return "IF";
77 | } else if(modifierName == "NoFail") {
78 | return "NF";
79 | } else if(modifierName == "NoWalls") {
80 | return "NW";
81 | } else if(modifierName == "NoBombs") {
82 | return "NB";
83 | } else if(modifierName == "StrictAngles") {
84 | return "SA";
85 | } else if(modifierName == "DisappearingArrows") {
86 | return "DA";
87 | } else if(modifierName == "SlowerSong") {
88 | return "SS";
89 | } else if(modifierName == "FasterSong") {
90 | return "FS";
91 | } else if(modifierName == "SuperFastSong") {
92 | return "SFS";
93 | } else if(modifierName == "NoArrows") {
94 | return "NA";
95 | } else if(modifierName == "GhostNotes") {
96 | return "GN";
97 | } else if(modifierName == "ProMode") {
98 | return "PM";
99 | } else if(modifierName == "ZenMode") {
100 | return "ZM";
101 | } else if(modifierName == "SmallCubes") {
102 | return "SC";
103 | }
104 |
105 | return "No matching modifier";
106 | }
107 | };
108 | }
--------------------------------------------------------------------------------
/include/Utils/ReplayUtils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "Utils/SongUtils.hpp"
5 | #include "Utils/SaberUtils.hpp"
6 |
7 | #include "GlobalNamespace/NoteController.hpp"
8 | #include "GlobalNamespace/NoteData.hpp"
9 | #include "GlobalNamespace/NoteCutInfo.hpp"
10 | #include "GlobalNamespace/SaberSwingRatingCounter.hpp"
11 | #include "GlobalNamespace/GameplayModifiers.hpp"
12 | #include "EventTypes.hpp"
13 | #include
14 |
15 | namespace Replay {
16 | class ReplayUtils {
17 | public:
18 | static std::string GetFileName(std::string_view hash) {
19 | return hash + replayFileExtension;
20 | }
21 |
22 | static std::string GetModDirectory() {
23 | return "/sdcard/ModData/com.beatgames.beatsaber/Mods/Replay/";
24 | }
25 |
26 | static std::string GetReplaysDirectory() {
27 | return GetModDirectory() + "Replays/";
28 | }
29 |
30 | static std::string GetReplayFilePath(std::string_view hash) {
31 | return GetReplaysDirectory() + GetFileName(hash);
32 | }
33 |
34 | static std::string GetTempReplayFilePath() {
35 | return GetModDirectory() + "temp" + replayFileExtension;
36 | }
37 |
38 | template
39 | static constexpr int GetCurrentIndex(std::vector const& events, int lastIndex) {
40 | float songTime = Replay::SongUtils::GetSongTime();
41 | int eventsLength = events.size();
42 |
43 | int iterations = 0;
44 | if(songTime < events[lastIndex].time) {
45 | while(events[lastIndex + iterations - 1].time > songTime && lastIndex + iterations - 1 >= 0) {
46 | iterations--;
47 | }
48 | } else {
49 | while(events[lastIndex + iterations + 1].time < songTime && lastIndex + iterations + 1 < eventsLength) {
50 | iterations++;
51 | }
52 | }
53 |
54 | return lastIndex + iterations;
55 | }
56 |
57 | template
58 | static constexpr float LerpAmountBetweenEvents(T const& eventA, T const& eventB) {
59 | float timeA = eventA.time;
60 | float timeB = eventB.time;
61 |
62 | float songTime = Replay::SongUtils::GetSongTime();
63 |
64 | return (songTime - timeA) / (timeB - timeA);
65 | }
66 |
67 | static int GetNoteHash(GlobalNamespace::NoteData* noteData) {
68 | std::hash noteDataHash;
69 |
70 | return (int) noteDataHash(noteData);
71 | }
72 |
73 | static GlobalNamespace::NoteCutInfo CreateNoteCutInfoFromSimple(Replay::NoteEventTypes::SimpleNoteCutInfo simpleNoteCutInfo, GlobalNamespace::NoteData* noteData) {
74 | return GlobalNamespace::NoteCutInfo(
75 | noteData,
76 | simpleNoteCutInfo.speedOK,
77 | simpleNoteCutInfo.directionOK,
78 | simpleNoteCutInfo.saberTypeOK,
79 | simpleNoteCutInfo.wasCutTooSoon,
80 | simpleNoteCutInfo.saberSpeed,
81 | simpleNoteCutInfo.saberDir,
82 | simpleNoteCutInfo.saberType,
83 | simpleNoteCutInfo.timeDeviation,
84 | simpleNoteCutInfo.cutDirDeviation,
85 | simpleNoteCutInfo.cutPoint,
86 | simpleNoteCutInfo.cutNormal,
87 | simpleNoteCutInfo.cutAngle,
88 | simpleNoteCutInfo.cutDistanceToCenter,
89 | UnityEngine::Quaternion::Euler(simpleNoteCutInfo.worldRotation),
90 | UnityEngine::Quaternion::Inverse(UnityEngine::Quaternion::Euler(simpleNoteCutInfo.worldRotation)),
91 | UnityEngine::Quaternion::Euler(simpleNoteCutInfo.noteRotation),
92 | simpleNoteCutInfo.notePosition,
93 | reinterpret_cast(Replay::SaberUtils::GetSaberForType(simpleNoteCutInfo.saberType)->get_movementData())
94 | );
95 | }
96 | };
97 | }
--------------------------------------------------------------------------------
/include/Utils/SaberUtils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "GlobalNamespace/NoteController.hpp"
5 | #include "GlobalNamespace/GameNoteController.hpp"
6 | #include "GlobalNamespace/NoteData.hpp"
7 | #include "GlobalNamespace/Saber.hpp"
8 | #include "GlobalNamespace/SaberManager.hpp"
9 | #include "GlobalNamespace/SaberTypeObject.hpp"
10 | #include "GlobalNamespace/SaberType.hpp"
11 | #include "GlobalNamespace/SaberSwingRatingCounter.hpp"
12 | #include "GlobalNamespace/ISaberSwingRatingCounter.hpp"
13 |
14 | namespace Replay {
15 |
16 | struct SaberUtils {
17 | inline static GlobalNamespace::SaberManager* saberManager;
18 |
19 | static GlobalNamespace::Saber* GetSaberForType(GlobalNamespace::SaberType saberType) {
20 | return saberType == saberManager->leftSaber->saberType->saberType ? saberManager->leftSaber : saberManager->rightSaber;
21 | }
22 | };
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/include/Utils/SongUtils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "GlobalNamespace/AudioTimeSyncController.hpp"
5 | #include "GlobalNamespace/StandardLevelDetailView.hpp"
6 | #include "GlobalNamespace/BeatmapLevelSO.hpp"
7 | #include "GlobalNamespace/IDifficultyBeatmap.hpp"
8 | #include "GlobalNamespace/IDifficultyBeatmapSet.hpp"
9 | #include "GlobalNamespace/BeatmapDifficulty.hpp"
10 | #include "GlobalNamespace/BeatmapCharacteristicSO.hpp"
11 | #include "GlobalNamespace/ScoreController.hpp"
12 | #include "GlobalNamespace/PlayerSpecificSettings.hpp"
13 |
14 | using namespace il2cpp_utils;
15 |
16 | namespace Replay {
17 | class SongUtils {
18 | private:
19 | static inline std::string mapID;
20 | public:
21 | static inline GlobalNamespace::AudioTimeSyncController* audioTimeSyncController;
22 |
23 | static inline bool inSong;
24 |
25 | static float GetSongTime() {
26 | static auto const *timeSyncControllerClass = classof(GlobalNamespace::AudioTimeSyncController *);
27 | auto *timeSourceObject = reinterpret_cast(audioTimeSyncController);
28 | if (timeSourceObject->klass == timeSyncControllerClass) {
29 | auto *timeSyncController = reinterpret_cast(audioTimeSyncController);
30 | return timeSyncController->songTime;
31 | } else {
32 | return audioTimeSyncController->get_songTime();
33 | }
34 | }
35 |
36 | static std::string GetMapID();
37 |
38 | static void SetMapID(GlobalNamespace::StandardLevelDetailView* standardLevelDetailView);
39 |
40 | static inline GlobalNamespace::ScoreController* scoreController;
41 |
42 | static inline GlobalNamespace::PlayerSpecificSettings* playerSpecificSettings;
43 |
44 | static inline bool didFail;
45 | static inline float failTime;
46 |
47 | static inline GlobalNamespace::IBeatmapLevel* beatmapLevel;
48 | static inline GlobalNamespace::BeatmapDifficulty beatmapDifficulty;
49 | static inline GlobalNamespace::BeatmapCharacteristicSO* beatmapCharacteristic;
50 | static inline GlobalNamespace::IReadonlyBeatmapData* beatmapData;
51 | };
52 | }
--------------------------------------------------------------------------------
/include/Utils/TimeUtils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include
5 | #include
6 |
7 | namespace Replay {
8 | class TimeUtils {
9 | public:
10 | static std::string GetStringForTimeSince(std::time_t start, std::time_t end) {
11 | auto startTimePoint = std::chrono::system_clock::from_time_t(start);
12 | auto endTimePoint = std::chrono::system_clock::from_time_t(end);
13 |
14 | std::chrono::duration duration = endTimePoint - startTimePoint;
15 |
16 | int seconds = std::chrono::duration_cast(duration).count();
17 | int minutes = seconds / 60;
18 | int hours = minutes / 60;
19 | int days = hours / 24;
20 | int weeks = days / 7;
21 | int months = weeks / 4;
22 | int years = weeks / 52;
23 |
24 | std::string unit;
25 | int value;
26 |
27 | if(years != 0) {
28 | unit = "year";
29 | value = years;
30 | } else if(months != 0) {
31 | unit = "month";
32 | value = months;
33 | } else if(weeks != 0) {
34 | unit = "week";
35 | value = weeks;
36 | } else if(days != 0) {
37 | unit = "day";
38 | value = days;
39 | } else if(hours != 0) {
40 | unit = "hour";
41 | value = hours;
42 | } else if(minutes != 0) {
43 | unit = "minute";
44 | value = minutes;
45 | } else {
46 | unit = "second";
47 | value = seconds;
48 | }
49 |
50 | if(value != 1) {
51 | unit = unit + "s";
52 | }
53 |
54 | return std::to_string(value) + " " + unit + " ago";
55 | }
56 |
57 | static std::string SecondsToString(float value) {
58 | int minutes = (int)(value / 60.0f);
59 | int seconds = (int)(value - (float(minutes) * 60.0f));
60 |
61 | std::string minutesString = std::to_string(minutes);
62 | std::string secondsString = std::to_string(seconds);
63 | if(seconds < 10) {
64 | secondsString = "0" + std::to_string(seconds);
65 | }
66 |
67 | return minutesString+":"+secondsString;
68 | }
69 | };
70 | }
--------------------------------------------------------------------------------
/include/Utils/TypeUtils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | namespace Replay::TypeUtils {
5 | std::string FloatToString(float value, int precision = 2) {
6 | float power = (float) pow(100, precision);
7 | value = std::round(value * power) / power;
8 |
9 | std::stringstream stream;
10 | stream << std::fixed << std::setprecision(precision) << value;
11 |
12 | return stream.str();
13 | }
14 | }
--------------------------------------------------------------------------------
/include/Utils/UIUtils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | namespace Replay::UIUtils {
5 | std::string GetLayeredText(std::string label, std::string value) {
6 | return "" + label + "\n" + value + "";
7 | }
8 | }
--------------------------------------------------------------------------------
/include/Utils/UnityUtils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "static-defines.hpp"
3 |
4 | #include "UnityEngine/Transform.hpp"
5 | #include "UnityEngine/GameObject.hpp"
6 |
7 | namespace Replay::UnityUtils {
8 | void SetAllActive(UnityEngine::Transform* transform, bool active) {
9 | transform->get_gameObject()->set_active(active);
10 |
11 | int childCount = transform->get_childCount();
12 | for(int i = 0; i < childCount; i++) {
13 | SetAllActive(transform->GetChild(i), active);
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/include/hooks.hpp:
--------------------------------------------------------------------------------
1 | // Implementation by https://github.com/StackDoubleFlow
2 | // yoinked from
3 | #pragma once
4 | #include "beatsaber-hook/shared/utils/logging.hpp"
5 | #include
6 |
7 | namespace Replay {
8 | class Hooks {
9 | private:
10 | static inline std::vector installFuncs;
11 | public:
12 | static void AddInstallFunc(void(*installFunc)(Logger& logger))
13 | {
14 | installFuncs.push_back(installFunc);
15 | }
16 |
17 | static void InstallHooks(Logger& logger)
18 | {
19 | for (auto installFunc : installFuncs)
20 | {
21 | installFunc(logger);
22 | }
23 | }
24 | };
25 | }
26 |
27 | #define ReplayInstallHooks(func) \
28 | struct __ReplayRegister##func { \
29 | __ReplayRegister##func() { \
30 | Replay::Hooks::AddInstallFunc(func); \
31 | } \
32 | }; \
33 | static __ReplayRegister##func __ReplayRegisterInstance##func;
--------------------------------------------------------------------------------
/include/static-defines.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "modloader/shared/modloader.hpp"
4 | #include "beatsaber-hook/shared/utils/logging.hpp"
5 | #include "beatsaber-hook/shared/utils/hooking.hpp"
6 | #include "custom-types/shared/register.hpp"
7 | #include "questui/shared/QuestUI.hpp"
8 | #include "hooks.hpp"
9 |
10 | using byte = unsigned char;
11 |
12 | #define eventIdByteSize 1
13 | #define eventCountByteSize 4
14 |
15 | #define replayMagicBytes 0x443d3d38
16 | #define fileVersion 0b00000000
17 | #define replayFileExtension ".questplay"
18 |
19 | // My excuse for it being defined here is for universal colors across the mod, definitely belongs in this file yep
20 | #define RED std::string("#cc1818")
21 | #define GREEN std::string("#2adb44")
22 | #define TEAL std::string("#1dbcd1")
23 |
24 | #define USE_CODEGEN_FIELDS
25 |
26 | static ModInfo modInfo;
27 |
28 | static Logger& replayLogger()
29 | {
30 | modInfo.id = ID;
31 | modInfo.version = VERSION;
32 | static Logger* logger = new Logger(modInfo, LoggerOptions(false, true));
33 | return *logger;
34 | }
35 |
36 | #define log(...) replayLogger().info(__VA_ARGS__)
--------------------------------------------------------------------------------
/ndk-stack.ps1:
--------------------------------------------------------------------------------
1 | $NDKPath = Get-Content ./ndkpath.txt
2 |
3 | $stackScript = "$NDKPath/ndk-stack"
4 | if (-not ($PSVersionTable.PSEdition -eq "Core")) {
5 | $stackScript += ".cmd"
6 | }
7 |
8 | Get-Content ./log.log | & $stackScript -sym ./build/debug/ > log_unstripped.log
--------------------------------------------------------------------------------
/qpm.json:
--------------------------------------------------------------------------------
1 | {
2 | "sharedDir": "shared",
3 | "dependenciesDir": "extern",
4 | "info": {
5 | "name": "Replay",
6 | "id": "Replay",
7 | "version": "1.0.0",
8 | "url": null,
9 | "additionalData": {
10 | "overrideSoName": "libreplay.so"
11 | }
12 | },
13 | "dependencies": [
14 | {
15 | "id": "beatsaber-hook",
16 | "versionRange": "*",
17 | "additionalData": {
18 | "extraFiles": [
19 | "src/inline-hook"
20 | ]
21 | }
22 | },
23 | {
24 | "id": "questui",
25 | "versionRange": "*",
26 | "additionalData": {}
27 | },
28 | {
29 | "id": "codegen",
30 | "versionRange": "*",
31 | "additionalData": {}
32 | },
33 | {
34 | "id": "custom-types",
35 | "versionRange": "*",
36 | "additionalData": {}
37 | }
38 | ],
39 | "additionalData": {}
40 | }
--------------------------------------------------------------------------------
/qpm_defines.cmake:
--------------------------------------------------------------------------------
1 | # YOU SHOULD NOT MANUALLY EDIT THIS FILE, QPM WILL VOID ALL CHANGES
2 | # Version defines, pretty useful
3 | set(MOD_VERSION "1.0.0")
4 | # take the mod name and just remove spaces, that will be MOD_ID, if you don't like it change it after the include of this file
5 | set(MOD_ID "Replay")
6 |
7 | # derived from override .so name or just id_version
8 | set(COMPILE_ID "replay")
9 | # derived from whichever codegen package is installed, will default to just codegen
10 | set(CODEGEN_ID "codegen")
11 |
12 | # given from qpm, automatically updated from qpm.json
13 | set(EXTERN_DIR_NAME "extern")
14 | set(SHARED_DIR_NAME "shared")
15 |
16 | # if no target given, use Debug
17 | if (NOT DEFINED CMAKE_BUILD_TYPE)
18 | set(CMAKE_BUILD_TYPE "Debug")
19 | endif()
20 |
21 | # defines used in ninja / cmake ndk builds
22 | if (NOT DEFINED CMAKE_ANDROID_NDK)
23 | if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/ndkpath.txt")
24 | file (STRINGS "ndkpath.txt" CMAKE_ANDROID_NDK)
25 | else()
26 | if(EXISTS $ENV{ANDROID_NDK_HOME})
27 | set(CMAKE_ANDROID_NDK $ENV{ANDROID_NDK_HOME})
28 | elseif(EXISTS $ENV{ANDROID_NDK_LATEST_HOME})
29 | set(CMAKE_ANDROID_NDK $ENV{ANDROID_NDK_LATEST_HOME})
30 | endif()
31 | endif()
32 | endif()
33 | if (NOT DEFINED CMAKE_ANDROID_NDK)
34 | message(Big time error buddy, no NDK)
35 | endif()
36 | message(Using NDK ${CMAKE_ANDROID_NDK})
37 | string(REPLACE "\\" "/" CMAKE_ANDROID_NDK ${CMAKE_ANDROID_NDK})
38 |
39 | set(ANDROID_PLATFORM 24)
40 | set(ANDROID_ABI arm64-v8a)
41 | set(ANDROID_STL c++_static)
42 |
43 | set(CMAKE_TOOLCHAIN_FILE ${CMAKE_ANDROID_NDK}/build/cmake/android.toolchain.cmake)
44 | # define used for external data, mostly just the qpm dependencies
45 | set(EXTERN_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${EXTERN_DIR_NAME})
46 | set(SHARED_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${SHARED_DIR_NAME})
47 | # get files by filter recursively
48 | MACRO(RECURSE_FILES return_list filter)
49 | FILE(GLOB_RECURSE new_list ${filter})
50 | SET(file_list "")
51 | FOREACH(file_path ${new_list})
52 | SET(file_list ${file_list} ${file_path})
53 | ENDFOREACH()
54 | LIST(REMOVE_DUPLICATES file_list)
55 | SET(${return_list} ${file_list})
56 | ENDMACRO()
--------------------------------------------------------------------------------
/src/Hooks/AudioTimeSyncController.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/AudioTimeSyncController.hpp"
4 | #include "UnityEngine/AudioSource.hpp"
5 | #include "Utils/SongUtils.hpp"
6 | #include "ReplayManager.hpp"
7 |
8 | using namespace GlobalNamespace;
9 | using namespace Replay;
10 |
11 | MAKE_HOOK_FIND_INSTANCE(AudioTimeSyncController_ctor, classof(AudioTimeSyncController*), ".ctor", void, AudioTimeSyncController* self) {
12 | AudioTimeSyncController_ctor(self);
13 |
14 | SongUtils::audioTimeSyncController = self;
15 | SongUtils::inSong = true;
16 | }
17 |
18 | MAKE_HOOK_MATCH(SongUpdate, &AudioTimeSyncController::Update, void, AudioTimeSyncController* self) {
19 |
20 | // log("SongUpdate");
21 |
22 | if(ReplayManager::replayState == ReplayState::REPLAYING) {
23 | // UnityEngine::AudioSource* audio = self->audioSource;
24 |
25 | // float roundedReplaySpeed = float(int((0.25f)*100))/100;
26 |
27 | // self->timeScale = roundedReplaySpeed;
28 | // audio->set_pitch(roundedReplaySpeed);
29 | }
30 |
31 | SongUpdate(self);
32 | }
33 |
34 | void AudioTimeSyncControllerHook(Logger& logger) {
35 | INSTALL_HOOK(logger, AudioTimeSyncController_ctor);
36 | INSTALL_HOOK(logger, SongUpdate);
37 | }
38 |
39 | ReplayInstallHooks(AudioTimeSyncControllerHook);
--------------------------------------------------------------------------------
/src/Hooks/HapticFeedbackController.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/HapticFeedbackController.hpp"
4 | #include "Utils/SongUtils.hpp"
5 | #include "ReplayManager.hpp"
6 |
7 | using namespace GlobalNamespace;
8 | using namespace Replay;
9 |
10 | MAKE_HOOK_MATCH(HapticFeedbackController_PlayHapticFeedback, &HapticFeedbackController::PlayHapticFeedback, void, HapticFeedbackController* self, UnityEngine::XR::XRNode node, Libraries::HM::HMLib::VR::HapticPresetSO* hapticPreset) {
11 | if(SongUtils::inSong && ReplayManager::replayState == ReplayState::REPLAYING) return;
12 |
13 | HapticFeedbackController_PlayHapticFeedback(self, node, hapticPreset);
14 | }
15 |
16 | void HapticFeedbackControllerHook(Logger& logger) {
17 | INSTALL_HOOK(logger, HapticFeedbackController_PlayHapticFeedback);
18 | }
19 |
20 | ReplayInstallHooks(HapticFeedbackControllerHook);
--------------------------------------------------------------------------------
/src/Hooks/Notes/BombNoteController.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/BombNoteController.hpp"
4 | #include "ReplayManager.hpp"
5 |
6 | using namespace GlobalNamespace;
7 | using namespace Replay;
8 |
9 | MAKE_HOOK_MATCH(BombNoteController_NoteDidPassMissedMarker, &BombNoteController::NoteDidPassMissedMarker, void, BombNoteController* self) {
10 | if(ReplayManager::replayState == ReplayState::REPLAYING) {
11 | return;
12 | }
13 |
14 | BombNoteController_NoteDidPassMissedMarker(self);
15 | }
16 |
17 | void BombNoteControllerHook(Logger& logger) {
18 | INSTALL_HOOK(logger, BombNoteController_NoteDidPassMissedMarker);
19 | }
20 |
21 | ReplayInstallHooks(BombNoteControllerHook);
--------------------------------------------------------------------------------
/src/Hooks/Notes/CutScoreBuffer.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/CutScoreBuffer.hpp"
4 | #include "GlobalNamespace/NoteCutInfo.hpp"
5 | #include "GlobalNamespace/NoteData.hpp"
6 | #include "GlobalNamespace/ISaberSwingRatingCounter.hpp"
7 | #include "ReplayManager.hpp"
8 | #include "Recording/NoteEventRecorder.hpp"
9 |
10 | using namespace GlobalNamespace;
11 | using namespace Replay;
12 |
13 | MAKE_HOOK_MATCH(CutScoreBuffer_HandleSaberSwingRatingCounterDidFinish, &CutScoreBuffer::HandleSaberSwingRatingCounterDidFinish, void, CutScoreBuffer* self, ISaberSwingRatingCounter* swingRatingCounter) {
14 | CutScoreBuffer_HandleSaberSwingRatingCounterDidFinish(self, swingRatingCounter);
15 |
16 | if(ReplayManager::replayState == ReplayState::RECORDING) {
17 | auto &cutTimes = ReplayManager::recorder.noteEventRecorder.cutTimes;
18 |
19 | for (auto eventIt = cutTimes.begin(); eventIt != cutTimes.end(); eventIt++) {
20 | auto const &cutTime = *eventIt;
21 |
22 | if(cutTime.second == self) {
23 | ReplayManager::recorder.noteEventRecorder.AddCutEvent(self, cutTime.first);
24 |
25 | cutTimes.erase(eventIt);
26 |
27 | return;
28 | }
29 | }
30 | }
31 | }
32 |
33 | MAKE_HOOK_MATCH(CutScoreBuffer_Init, &CutScoreBuffer::Init, bool, CutScoreBuffer* self, ByRef noteCutInfo) {
34 | bool returnValue = CutScoreBuffer_Init(self, noteCutInfo);
35 |
36 | if(ReplayManager::replayState == ReplayState::RECORDING) {
37 | if(returnValue) {
38 | ReplayManager::recorder.noteEventRecorder.cutTimes.push_back(std::make_pair(SongUtils::GetSongTime(), self));
39 | } else {
40 | ReplayManager::recorder.noteEventRecorder.AddCutEvent(self, SongUtils::GetSongTime());
41 | }
42 | }
43 |
44 | return returnValue;
45 | }
46 |
47 | void CutScoreBufferHook(Logger& logger) {
48 | INSTALL_HOOK(logger, CutScoreBuffer_HandleSaberSwingRatingCounterDidFinish);
49 | INSTALL_HOOK(logger, CutScoreBuffer_Init);
50 | }
51 |
52 | ReplayInstallHooks(CutScoreBufferHook);
--------------------------------------------------------------------------------
/src/Hooks/Notes/FlyingScoreEffect.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/FlyingScoreEffect.hpp"
4 | #include "GlobalNamespace/NoteCutInfo.hpp"
5 | #include "GlobalNamespace/SaberSwingRatingCounter.hpp"
6 | #include "GlobalNamespace/ISaberSwingRatingCounter.hpp"
7 | #include "ReplayManager.hpp"
8 |
9 | using namespace GlobalNamespace;
10 | using namespace Replay;
11 |
12 | // MAKE_HOOK_MATCH(FlyingScoreEffect_InitAndPresent, &FlyingScoreEffect::InitAndPresent, void, FlyingScoreEffect* self, ByRef noteCutInfo, int multiplier, float duration, UnityEngine::Vector3 targetPos, UnityEngine::Quaternion rotation, UnityEngine::Color color) {
13 | // FlyingScoreEffect_InitAndPresent(self, noteCutInfo, multiplier, duration, targetPos, rotation, color);
14 |
15 | // if(ReplayManager::replayState == ReplayState::REPLAYING) {
16 | // self->HandleSaberSwingRatingCounterDidChange(reinterpret_cast(noteCutInfo->swingRatingCounter), 0);
17 | // }
18 | // }
19 |
20 | void FlyingScoreEffectHook(Logger& logger) {
21 | // INSTALL_HOOK(logger, FlyingScoreEffect_InitAndPresent);
22 | }
23 |
24 | ReplayInstallHooks(FlyingScoreEffectHook);
--------------------------------------------------------------------------------
/src/Hooks/Notes/GameNoteController.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/GameNoteController.hpp"
4 | #include "ReplayManager.hpp"
5 |
6 | using namespace GlobalNamespace;
7 | using namespace Replay;
8 |
9 | MAKE_HOOK_MATCH(GameNoteController_NoteDidPassMissedMarker, &GameNoteController::NoteDidPassMissedMarker, void, GameNoteController* self) {
10 | if(ReplayManager::replayState == ReplayState::REPLAYING) {
11 | return;
12 | }
13 |
14 | GameNoteController_NoteDidPassMissedMarker(self);
15 | }
16 |
17 | void GameNoteControllerHook(Logger& logger) {
18 | INSTALL_HOOK(logger, GameNoteController_NoteDidPassMissedMarker);
19 | }
20 |
21 | ReplayInstallHooks(GameNoteControllerHook);
--------------------------------------------------------------------------------
/src/Hooks/Notes/GoodCutScoringElement.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/CutScoreBuffer.hpp"
4 | #include "GlobalNamespace/NoteCutInfo.hpp"
5 | #include "GlobalNamespace/NoteData.hpp"
6 | #include "GlobalNamespace/GoodCutScoringElement.hpp"
7 | #include "ReplayManager.hpp"
8 | #include "Recording/NoteEventRecorder.hpp"
9 |
10 | using namespace GlobalNamespace;
11 | using namespace Replay;
12 |
13 | MAKE_HOOK_MATCH(GoodCutScoringElement_Init, &GoodCutScoringElement::Init, void, GoodCutScoringElement* self, NoteCutInfo noteCutInfo) {
14 | GoodCutScoringElement_Init(self, noteCutInfo);
15 |
16 | if(ReplayManager::replayState == ReplayState::REPLAYING) {
17 | self->cutScoreBuffer->saberSwingRatingCounter->Finish();
18 |
19 | float beforeCutScore = 0.0f;
20 | float afterCutScore = 0.0f;
21 |
22 | auto noteHash = Replay::ReplayUtils::GetNoteHash(noteCutInfo.noteData);
23 |
24 | for (auto eventIt = ReplayManager::replayer.noteEventReplayer.swingRatings.begin(); eventIt != ReplayManager::replayer.noteEventReplayer.swingRatings.end(); eventIt++) {
25 | auto const &swingRating = *eventIt;
26 |
27 | if(noteHash == swingRating.noteHash) {
28 | beforeCutScore = swingRating.swingRating.beforeCutRating;
29 | afterCutScore = swingRating.swingRating.afterCutRating;
30 |
31 | ReplayManager::replayer.noteEventReplayer.swingRatings.erase(eventIt);
32 |
33 | break;
34 | }
35 | }
36 |
37 | self->cutScoreBuffer->afterCutScore = beforeCutScore;
38 | self->cutScoreBuffer->beforeCutScore = afterCutScore;
39 | }
40 | }
41 |
42 | void GoodCutScoringElementHook(Logger& logger) {
43 | INSTALL_HOOK(logger, GoodCutScoringElement_Init);
44 | }
45 |
46 | ReplayInstallHooks(GoodCutScoringElementHook);
--------------------------------------------------------------------------------
/src/Hooks/Notes/NoteController.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/NoteController.hpp"
4 | #include "GlobalNamespace/NoteCutInfo.hpp"
5 | #include "ReplayManager.hpp"
6 | #include "Replaying/NoteEventReplayer.hpp"
7 |
8 | using namespace GlobalNamespace;
9 | using namespace Replay;
10 |
11 | MAKE_HOOK_MATCH(NoteController_Init, &NoteController::Init, void, GlobalNamespace::NoteController* self, NoteData* noteData, float worldRotation, UnityEngine::Vector3 moveStartPos, UnityEngine::Vector3 moveEndPos, UnityEngine::Vector3 jumpEndPos, float moveDuration, float jumpDuration, float jumpGravity, float endRotation, float uniformScale, bool rotateTowardsPlayer, bool useRandomRotation) {
12 | NoteController_Init(self, noteData, worldRotation, moveStartPos, moveEndPos, jumpEndPos, moveDuration, jumpDuration, jumpGravity, endRotation, uniformScale, rotateTowardsPlayer, useRandomRotation);
13 |
14 | if(ReplayManager::replayState == ReplayState::REPLAYING) {
15 | ReplayManager::replayer.noteEventReplayer.AddActiveEvents(self);
16 | }
17 | }
18 |
19 | MAKE_HOOK_MATCH(NoteController_SendNoteWasCutEvent, &NoteController::SendNoteWasCutEvent, void, NoteController* self, ByRef noteCutInfo) {
20 | NoteController_SendNoteWasCutEvent(self, noteCutInfo);
21 |
22 | if(ReplayManager::replayState == ReplayState::RECORDING && !noteCutInfo->get_allIsOK()) {
23 | ReplayManager::recorder.noteEventRecorder.AddCutEvent(noteCutInfo.heldRef, SongUtils::GetSongTime());
24 | }
25 | }
26 |
27 | MAKE_HOOK_MATCH(NoteController_SendNoteWasMissedEvent, &NoteController::SendNoteWasMissedEvent, void, NoteController* self) {
28 | NoteController_SendNoteWasMissedEvent(self);
29 |
30 | if(ReplayManager::replayState == ReplayState::RECORDING) {
31 | ReplayManager::recorder.noteEventRecorder.AddMissEvent(self);
32 | }
33 | }
34 |
35 | void NoteControllerHook(Logger& logger) {
36 | INSTALL_HOOK(logger, NoteController_Init);
37 | INSTALL_HOOK(logger, NoteController_SendNoteWasCutEvent);
38 | INSTALL_HOOK(logger, NoteController_SendNoteWasMissedEvent);
39 | }
40 |
41 | ReplayInstallHooks(NoteControllerHook);
--------------------------------------------------------------------------------
/src/Hooks/Notes/NoteCutter.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/NoteCutter.hpp"
4 | #include "GlobalNamespace/Saber.hpp"
5 | #include "ReplayManager.hpp"
6 |
7 | using namespace GlobalNamespace;
8 | using namespace Replay;
9 |
10 | MAKE_HOOK_MATCH(NoteCutter_Cut, &NoteCutter::Cut, void, NoteCutter* self, Saber* saber) {
11 | if(ReplayManager::replayState == ReplayState::REPLAYING) {
12 | return;
13 | }
14 |
15 | NoteCutter_Cut(self, saber);
16 | }
17 |
18 | void NoteCutterHook(Logger& logger) {
19 | INSTALL_HOOK(logger, NoteCutter_Cut);
20 | }
21 |
22 | ReplayInstallHooks(NoteCutterHook);
--------------------------------------------------------------------------------
/src/Hooks/Notes/RelativeScoreAndImmediateRankCounter.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/RelativeScoreAndImmediateRankCounter.hpp"
4 | #include "GlobalNamespace/ScoreModel.hpp"
5 | #include "Utils/SongUtils.hpp"
6 | #include "ReplayManager.hpp"
7 |
8 | using namespace GlobalNamespace;
9 | using namespace Replay;
10 |
11 | // MAKE_HOOK_MATCH(RelativeScoreAndImmediateRankCounter_HandleScoreControllerImmediateMaxPossibleScoreDidChange, &RelativeScoreAndImmediateRankCounter::HandleScoreControllerImmediateMaxPossibleScoreDidChange, void, RelativeScoreAndImmediateRankCounter* self, int immediateMaxPossibleScore, int immediateMaxPossibleModifiedScore) {
12 | // if(ReplayManager::replayState == ReplayState::REPLAYING) {
13 | // immediateMaxPossibleScore = ScoreModel::MaxRawScoreForNumberOfNotes(SongUtils::scoreController->cutOrMissedNotes);
14 | // immediateMaxPossibleModifiedScore = ScoreModel::GetModifiedScoreForGameplayModifiersScoreMultiplier(immediateMaxPossibleScore, SongUtils::scoreController->gameplayModifiersScoreMultiplier);
15 | // }
16 |
17 | // RelativeScoreAndImmediateRankCounter_HandleScoreControllerImmediateMaxPossibleScoreDidChange(self, immediateMaxPossibleScore, immediateMaxPossibleModifiedScore);
18 | // }
19 |
20 | // void RelativeScoreAndImmediateRankCounterHook(Logger& logger) {
21 | // INSTALL_HOOK(logger, RelativeScoreAndImmediateRankCounter_HandleScoreControllerImmediateMaxPossibleScoreDidChange);
22 | // }
23 |
24 | // ReplayInstallHooks(RelativeScoreAndImmediateRankCounterHook);
--------------------------------------------------------------------------------
/src/Hooks/Notes/SaberSwingRatingCounter.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/SaberSwingRatingCounter.hpp"
4 | #include "GlobalNamespace/BladeMovementDataElement.hpp"
5 | #include "Recording/NoteEventRecorder.hpp"
6 | #include "Recording/ReplayRecorder.hpp"
7 | #include "ReplayManager.hpp"
8 |
9 | using namespace GlobalNamespace;
10 | using namespace Replay;
11 |
12 | MAKE_HOOK_MATCH(SaberSwingRatingCounter_ProcessNewData, &SaberSwingRatingCounter::ProcessNewData, void, SaberSwingRatingCounter* self, BladeMovementDataElement newData, BladeMovementDataElement prevData, bool prevDataAreValid) {
13 | if(ReplayManager::replayState == ReplayState::REPLAYING) {
14 | self->Finish();
15 | return;
16 | }
17 |
18 | SaberSwingRatingCounter_ProcessNewData(self, newData, prevData, prevDataAreValid);
19 | }
20 |
21 | void SaberSwingRatingCounterHook(Logger& logger) {
22 | INSTALL_HOOK(logger, SaberSwingRatingCounter_ProcessNewData);
23 | }
24 |
25 | ReplayInstallHooks(SaberSwingRatingCounterHook);
--------------------------------------------------------------------------------
/src/Hooks/Obstacles/GameEnergyCounter.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/GameEnergyCounter.hpp"
4 | #include "ReplayManager.hpp"
5 | #include "Recording/ObstacleEventRecorder.hpp"
6 | #include "Replaying/ObstacleEventReplayer.hpp"
7 |
8 | using namespace GlobalNamespace;
9 | using namespace Replay;
10 |
11 | MAKE_HOOK_MATCH(GameEnergyCounter_LateUpdate, &GameEnergyCounter::LateUpdate, void, GameEnergyCounter* self) {
12 | GameEnergyCounter_LateUpdate(self);
13 |
14 | if(ReplayManager::replayState == ReplayState::RECORDING) {
15 | ReplayManager::recorder.obstacleEventRecorder.AddEvent(self);
16 | } else if(ReplayManager::replayState == ReplayState::REPLAYING) {
17 | if(ReplayManager::replayer.obstacleEventReplayer.ShouldSetEnergy()) {
18 | auto& obstacleReplayer = ReplayManager::replayer.obstacleEventReplayer;
19 | float energyChange = obstacleReplayer.events[obstacleReplayer.nextEventIndex - 1].energy - self->get_energy();
20 |
21 | self->ProcessEnergyChange(energyChange);
22 | }
23 | }
24 | }
25 |
26 | void GameEnergyCounterHook(Logger& logger) {
27 | INSTALL_HOOK(logger, GameEnergyCounter_LateUpdate);
28 | }
29 |
30 | ReplayInstallHooks(GameEnergyCounterHook);
--------------------------------------------------------------------------------
/src/Hooks/PauseController.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/PauseController.hpp"
4 | #include "Utils/SongUtils.hpp"
5 | #include "ReplayManager.hpp"
6 |
7 | using namespace GlobalNamespace;
8 | using namespace Replay;
9 |
10 | MAKE_HOOK_MATCH(PauseController_HandlePauseMenuManagerDidPressMenuButton, &PauseController::HandlePauseMenuManagerDidPressMenuButton, void, PauseController* self) {
11 | PauseController_HandlePauseMenuManagerDidPressMenuButton(self);
12 |
13 | SongUtils::inSong = false;
14 | }
15 |
16 | void PauseControllerHook(Logger& logger) {
17 | INSTALL_HOOK(logger, PauseController_HandlePauseMenuManagerDidPressMenuButton);
18 | }
19 |
20 | ReplayInstallHooks(PauseControllerHook);
--------------------------------------------------------------------------------
/src/Hooks/Player/PlayerTransforms.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/PlayerTransforms.hpp"
4 | #include "Recording/PlayerRecorder.hpp"
5 | #include "EventTypes.hpp"
6 | #include "ReplayManager.hpp"
7 | #include "fstream"
8 |
9 | using namespace GlobalNamespace;
10 | using namespace Replay;
11 |
12 | MAKE_HOOK_MATCH(PlayerTransforms_Update, &GlobalNamespace::PlayerTransforms::Update, void, GlobalNamespace::PlayerTransforms* self) {
13 | PlayerTransforms_Update(self);
14 |
15 | if(ReplayManager::replayState == ReplayState::RECORDING) {
16 | ReplayManager::recorder.playerRecorder.AddEvent(PlayerEventTypes::PlayerTransforms(self->headPseudoLocalPos, self->headPseudoLocalRot, self->leftHandPseudoLocalPos, self->leftHandPseudoLocalRot, self->rightHandPseudoLocalPos, self->rightHandPseudoLocalRot));
17 | } else if(ReplayManager::replayState == ReplayState::REPLAYING) {
18 | ReplayManager::replayer.playerReplayer.SetPlayerTransforms(self);
19 | }
20 | }
21 |
22 | void PlayerTransformsHook(Logger& logger) {
23 | INSTALL_HOOK(logger, PlayerTransforms_Update);
24 | }
25 |
26 | ReplayInstallHooks(PlayerTransformsHook);
--------------------------------------------------------------------------------
/src/Hooks/Player/SaberMovementData.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/SaberMovementData.hpp"
4 | #include "GlobalNamespace/SaberType.hpp"
5 | #include "Recording/PlayerRecorder.hpp"
6 | #include "EventTypes.hpp"
7 | #include "ReplayManager.hpp"
8 | #include "Utils/SaberUtils.hpp"
9 | #include "fstream"
10 |
11 | using namespace GlobalNamespace;
12 | using namespace Replay;
13 |
14 | MAKE_HOOK_MATCH(SaberMovementData_AddNewData, &SaberMovementData::AddNewData, void, SaberMovementData* self, UnityEngine::Vector3 topPos, UnityEngine::Vector3 bottomPos, float time) {
15 | SaberMovementData_AddNewData(self, topPos, bottomPos, time);
16 |
17 | if(ReplayManager::replayState == ReplayState::RECORDING) {
18 | SaberType saberType = self == SaberUtils::saberManager->leftSaber->get_movementData() ? SaberType::SaberA : SaberType::SaberB;
19 |
20 | ReplayManager::recorder.playerRecorder.AddSaberMovement(self->get_lastAddedData(), saberType);
21 | }
22 | }
23 |
24 | void SaberMovementDataHook(Logger& logger) {
25 | INSTALL_HOOK(logger, SaberMovementData_AddNewData);
26 | }
27 |
28 | ReplayInstallHooks(SaberMovementDataHook);
--------------------------------------------------------------------------------
/src/Hooks/ResultsViewController.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/ResultsViewController.hpp"
4 | #include "GlobalNamespace/IReadonlyBeatmapData.hpp"
5 | #include "ReplayManager.hpp"
6 | #include "UI/UIManager.hpp"
7 |
8 | using namespace GlobalNamespace;
9 | using namespace Replay;
10 |
11 | MAKE_HOOK_MATCH(ResultsViewController_Init, &ResultsViewController::Init, void, ResultsViewController* self, LevelCompletionResults* levelCompletionResults, IReadonlyBeatmapData* transformedBeatmapData, IDifficultyBeatmap* difficultyBeatmap, bool practice, bool newHighScore) {
12 | ResultsViewController_Init(self, levelCompletionResults, transformedBeatmapData, difficultyBeatmap, practice, newHighScore);
13 |
14 | SongUtils::inSong = false;
15 |
16 | if(ReplayManager::replayState == ReplayState::RECORDING && !practice) {
17 | ReplayManager::recorder.StopRecording(levelCompletionResults);
18 | }
19 | }
20 |
21 | MAKE_HOOK_MATCH(ResultsViewController_DidActivate, &ResultsViewController::DidActivate, void, ResultsViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) {
22 | ResultsViewController_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling);
23 |
24 | UnityEngine::Transform* parent = self->restartButton->get_transform()->GetParent();
25 | UnityEngine::Transform* buttonTransform = parent->Find(newcsstr("ReplayButton"));//Replay button name is defined in two other places, make better
26 |
27 | if(buttonTransform) {
28 | std::string path = fileexists(ReplayUtils::GetTempReplayFilePath()) ? ReplayUtils::GetTempReplayFilePath() : ReplayUtils::GetReplayFilePath(SongUtils::GetMapID());
29 | Replay::UI::UIManager::SetReplayButtonOnClick(buttonTransform, path, true);
30 | } else {
31 | ((UnityEngine::RectTransform*) Replay::UI::UIManager::CreateReplayButton(parent, self->restartButton, self->restartButton, ReplayUtils::GetTempReplayFilePath(), true))->set_anchoredPosition({48, 0});
32 | }
33 | }
34 |
35 | void ResultsViewControllerHook(Logger& logger) {
36 | INSTALL_HOOK(logger, ResultsViewController_Init);
37 | INSTALL_HOOK(logger, ResultsViewController_DidActivate);
38 | }
39 |
40 | ReplayInstallHooks(ResultsViewControllerHook);
--------------------------------------------------------------------------------
/src/Hooks/SaberManager.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "Utils/SaberUtils.hpp"
4 | #include "GlobalNamespace/SaberManager.hpp"
5 | #include "UnityEngine/Resources.hpp"
6 |
7 | using namespace GlobalNamespace;
8 | using namespace Replay;
9 |
10 | MAKE_HOOK_MATCH(SaberManager_Start, &SaberManager::Start, void, SaberManager* self) {
11 | SaberManager_Start(self);
12 |
13 | SaberUtils::saberManager = self;
14 | }
15 |
16 | void SaberManagerHook(Logger& logger) {
17 | INSTALL_HOOK(logger, SaberManager_Start);
18 | }
19 |
20 | ReplayInstallHooks(SaberManagerHook);
--------------------------------------------------------------------------------
/src/Hooks/ScoreController.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/ScoreController.hpp"
4 | #include "Utils/SongUtils.hpp"
5 |
6 | using namespace GlobalNamespace;
7 | using namespace Replay;
8 |
9 | MAKE_HOOK_MATCH(ScoreController_Start, &ScoreController::Start, void, ScoreController* self) {
10 | ScoreController_Start(self);
11 |
12 | SongUtils::scoreController = self;
13 | }
14 |
15 | void ScoreControllerHook(Logger& logger) {
16 | INSTALL_HOOK(logger, ScoreController_Start);
17 | }
18 |
19 | ReplayInstallHooks(ScoreControllerHook);
--------------------------------------------------------------------------------
/src/Hooks/SinglePlayerLevelSelectionFlowCoordinator.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/SinglePlayerLevelSelectionFlowCoordinator.hpp"
4 | #include "Polyglot/Localization.hpp"
5 | #include "UI/UIManager.hpp"
6 | #include "HMUI/ViewController.hpp"
7 | #include "HMUI/ViewController_AnimationType.hpp"
8 | #include "HMUI/ViewController_AnimationDirection.hpp"
9 |
10 | using namespace GlobalNamespace;
11 | using namespace Replay::UI;
12 | using namespace HMUI;
13 |
14 | MAKE_HOOK_MATCH(SinglePlayerLevelSelectionFlowCoordinator_LevelSelectionFlowCoordinatorTopViewControllerWillChange, &SinglePlayerLevelSelectionFlowCoordinator::LevelSelectionFlowCoordinatorTopViewControllerWillChange, void, SinglePlayerLevelSelectionFlowCoordinator* self, ViewController* oldViewController, ViewController* newViewController, ViewController::AnimationType animationType) {
15 | if(newViewController == UIManager::replayViewController) {
16 | self->SetLeftScreenViewController(nullptr, animationType);
17 | self->SetRightScreenViewController(nullptr, animationType);
18 | self->SetBottomScreenViewController(nullptr, animationType);
19 | self->SetTitle(Polyglot::Localization::Get(il2cpp_utils::newcsstr("REPLAY")), animationType);
20 | self->set_showBackButton(true);
21 | return;
22 | }
23 |
24 | SinglePlayerLevelSelectionFlowCoordinator_LevelSelectionFlowCoordinatorTopViewControllerWillChange(self, oldViewController, newViewController, animationType);
25 | }
26 |
27 | MAKE_HOOK_MATCH(SinglePlayerLevelSelectionFlowCoordinator_BackButtonWasPressed, &SinglePlayerLevelSelectionFlowCoordinator::BackButtonWasPressed, void, SinglePlayerLevelSelectionFlowCoordinator* self, ViewController* topViewController) {
28 | if(topViewController == UIManager::replayViewController) {
29 | self->DismissViewController(UIManager::replayViewController, ViewController::AnimationDirection::Horizontal, nullptr, false);
30 | return;
31 | }
32 |
33 | SinglePlayerLevelSelectionFlowCoordinator_BackButtonWasPressed(self, topViewController);
34 | }
35 |
36 | void SinglePlayerLevelSelectionFlowCoordinatorHook(Logger& logger) {
37 | INSTALL_HOOK(logger, SinglePlayerLevelSelectionFlowCoordinator_LevelSelectionFlowCoordinatorTopViewControllerWillChange);
38 | INSTALL_HOOK(logger, SinglePlayerLevelSelectionFlowCoordinator_BackButtonWasPressed);
39 | }
40 |
41 | ReplayInstallHooks(SinglePlayerLevelSelectionFlowCoordinatorHook);
--------------------------------------------------------------------------------
/src/Hooks/SoloFreePlayFlowController.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/SoloFreePlayFlowCoordinator.hpp"
4 | #include "GlobalNamespace/SinglePlayerLevelSelectionFlowCoordinator.hpp"
5 | #include "UI/UIManager.hpp"
6 |
7 | using namespace GlobalNamespace;
8 | using namespace Replay::UI;
9 |
10 | MAKE_HOOK_MATCH(SoloFreePlayFlowCoordinator_SinglePlayerLevelSelectionFlowCoordinatorDidActivate, &SoloFreePlayFlowCoordinator::SinglePlayerLevelSelectionFlowCoordinatorDidActivate, void, SoloFreePlayFlowCoordinator* self, bool firstActivation, bool addedToHierarchy) {
11 | SoloFreePlayFlowCoordinator_SinglePlayerLevelSelectionFlowCoordinatorDidActivate(self, firstActivation, addedToHierarchy);
12 |
13 | UIManager::singlePlayerFlowCoordinator = reinterpret_cast(self);
14 | }
15 |
16 | void SoloFreePlayFlowCoordinatorHook(Logger& logger) {
17 | INSTALL_HOOK(logger, SoloFreePlayFlowCoordinator_SinglePlayerLevelSelectionFlowCoordinatorDidActivate);
18 | }
19 |
20 | ReplayInstallHooks(SoloFreePlayFlowCoordinatorHook);
--------------------------------------------------------------------------------
/src/Hooks/StandardLevelDetailView.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/StandardLevelDetailView.hpp"
4 | #include "GlobalNamespace/BeatmapData.hpp"
5 |
6 | #include "Utils/SongUtils.hpp"
7 | #include "Utils/ReplayUtils.hpp"
8 | #include "Utils/FileUtils.hpp"
9 | #include "UI/UIManager.hpp"
10 |
11 | using namespace GlobalNamespace;
12 | using namespace Replay;
13 |
14 | MAKE_HOOK_MATCH(StandardLevelDetailView_RefreshContent, &StandardLevelDetailView::RefreshContent, void, StandardLevelDetailView* self) {
15 | StandardLevelDetailView_RefreshContent(self);
16 |
17 | SongUtils::SetMapID(self);
18 |
19 | SongUtils::beatmapLevel = self->selectedDifficultyBeatmap->get_level();
20 | SongUtils::beatmapDifficulty = self->selectedDifficultyBeatmap->get_difficulty();
21 | SongUtils::beatmapCharacteristic = self->selectedDifficultyBeatmap->get_parentDifficultyBeatmapSet()->get_beatmapCharacteristic();
22 |
23 | bool replayFileExists = fileexists(ReplayUtils::GetReplayFilePath(SongUtils::GetMapID()));
24 |
25 | Replay::UI::UIManager::CreateReplayCanvas(self, replayFileExists);
26 | }
27 |
28 | void StandardLevelDetailViewHook(Logger& logger) {
29 | INSTALL_HOOK(logger, StandardLevelDetailView_RefreshContent);
30 | }
31 |
32 | ReplayInstallHooks(StandardLevelDetailViewHook);
--------------------------------------------------------------------------------
/src/Hooks/StandardLevelGameplayManager.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/StandardLevelGameplayManager.hpp"
4 | #include "Utils/SongUtils.hpp"
5 | #include "ReplayManager.hpp"
6 |
7 | using namespace GlobalNamespace;
8 | using namespace Replay;
9 |
10 | MAKE_HOOK_MATCH(StandardLevelGameplayManager_HandleGameEnergyDidReach0, &StandardLevelGameplayManager::HandleGameEnergyDidReach0, void, StandardLevelGameplayManager* self) {
11 | if(ReplayManager::replayState == ReplayState::RECORDING) {
12 | SongUtils::didFail = true;
13 | SongUtils::failTime = SongUtils::GetSongTime();
14 | }
15 |
16 | StandardLevelGameplayManager_HandleGameEnergyDidReach0(self);
17 | }
18 |
19 | void StandardLevelGameplayManagerHook(Logger& logger) {
20 | INSTALL_HOOK(logger, StandardLevelGameplayManager_HandleGameEnergyDidReach0);
21 | }
22 |
23 | ReplayInstallHooks(StandardLevelGameplayManagerHook);
--------------------------------------------------------------------------------
/src/Hooks/StandardLevelScenesTransitionSetupDataSO.cpp:
--------------------------------------------------------------------------------
1 | #include "static-defines.hpp"
2 |
3 | #include "GlobalNamespace/StandardLevelScenesTransitionSetupDataSO.hpp"
4 | #include "Utils/SongUtils.hpp"
5 | #include "Utils/ModifiersUtils.hpp"
6 | #include "Utils/FileUtils.hpp"
7 | #include "UI/UIManager.hpp"
8 | #include "ReplayManager.hpp"
9 |
10 | #include "rapidjson/document.h"
11 | #include "rapidjson/stringbuffer.h"
12 | #include
13 | #include
14 |
15 | using namespace GlobalNamespace;
16 | using namespace Replay;
17 |
18 | MAKE_HOOK_MATCH(StandardLevelScenesTransitionSetupDataSO_Init, &StandardLevelScenesTransitionSetupDataSO::Init, void, StandardLevelScenesTransitionSetupDataSO* self, ::StringW gameMode, GlobalNamespace::IDifficultyBeatmap* difficultyBeatmap, GlobalNamespace::IPreviewBeatmapLevel* previewBeatmapLevel, GlobalNamespace::OverrideEnvironmentSettings* overrideEnvironmentSettings, GlobalNamespace::ColorScheme* overrideColorScheme, GlobalNamespace::GameplayModifiers* gameplayModifiers, GlobalNamespace::PlayerSpecificSettings* playerSpecificSettings, GlobalNamespace::PracticeSettings* practiceSettings, ::StringW backButtonText, bool useTestNoteCutSoundEffects, bool startPaused) {
19 | if(ReplayManager::replayState == ReplayState::REPLAYING) {
20 | rapidjson::Document metadata = FileUtils::GetMetadataFromReplayFile(Replay::UI::UIManager::replayViewController->path);
21 |
22 | std::vector modifierStrings;
23 | for(const auto& value : metadata["Modifiers"].GetArray()) {
24 | modifierStrings.push_back(value.GetString());
25 | }
26 |
27 | gameplayModifiers = ModifiersUtils::CreateModifiersFromStrings(modifierStrings);
28 |
29 | PlayerSpecificSettings* settings = PlayerSpecificSettings::New_ctor(
30 | metadata["PlayerSettings"]["LeftHanded"].GetBool(),
31 | metadata["PlayerSettings"]["Height"].GetFloat(),
32 | metadata["PlayerSettings"]["AutoHeight"].GetBool(),
33 | playerSpecificSettings->sfxVolume,
34 | playerSpecificSettings->reduceDebris,
35 | playerSpecificSettings->noTextsAndHuds,
36 | playerSpecificSettings->noFailEffects,
37 | playerSpecificSettings->advancedHud,
38 | playerSpecificSettings->autoRestart,
39 | playerSpecificSettings->saberTrailIntensity,
40 | playerSpecificSettings->noteJumpDurationTypeSettings,
41 | playerSpecificSettings->noteJumpFixedDuration,
42 | playerSpecificSettings->noteJumpStartBeatOffset,
43 | playerSpecificSettings->hideNoteSpawnEffect,
44 | playerSpecificSettings->adaptiveSfx,
45 | playerSpecificSettings->environmentEffectsFilterDefaultPreset,
46 | playerSpecificSettings->environmentEffectsFilterExpertPlusPreset
47 | );
48 |
49 | playerSpecificSettings = settings;
50 | }
51 |
52 | StandardLevelScenesTransitionSetupDataSO_Init(self, gameMode, difficultyBeatmap, previewBeatmapLevel, overrideEnvironmentSettings, overrideColorScheme, gameplayModifiers, playerSpecificSettings, practiceSettings, backButtonText, useTestNoteCutSoundEffects, startPaused);
53 |
54 | SongUtils::playerSpecificSettings = playerSpecificSettings;
55 | }
56 |
57 | void StandardLevelScenesTransitionSetupDataSOHook(Logger& logger) {
58 | INSTALL_HOOK(logger, StandardLevelScenesTransitionSetupDataSO_Init);
59 | }
60 |
61 | ReplayInstallHooks(StandardLevelScenesTransitionSetupDataSOHook);
--------------------------------------------------------------------------------
/src/Recording/NoteEventRecorder.cpp:
--------------------------------------------------------------------------------
1 | #include "Recording/NoteEventRecorder.hpp"
2 |
3 | #include
4 |
5 | float Replay::NoteEventRecorder::GetEventSaveTime(float songTime) {
6 | //This seems like a really bad solution for note cut race conditions, fix later?
7 | if(songTime == frameTime) {
8 | eventsInFrame++;
9 |
10 | float newTime = nextafterf(songTime, songTime + 1);
11 |
12 | // For if there are 3 or more events in a single frame (extremely unlikely except paulllssss)
13 | for(int i = 0; i < eventsInFrame - 2; i++) {
14 | newTime = nextafterf(newTime, songTime + 1);
15 | }
16 |
17 | return newTime;
18 | }
19 |
20 | frameTime = songTime;
21 | eventsInFrame = 1;
22 |
23 | return songTime;
24 | }
25 |
26 | void Replay::NoteEventRecorder::AddCutEvent(CutScoreBuffer* cutScoreBuffer, float time) {
27 | cutEvents.emplace_back(Replay::ReplayUtils::GetNoteHash(cutScoreBuffer->noteCutInfo.noteData), GetEventSaveTime(time), cutScoreBuffer);
28 | }
29 |
30 | void Replay::NoteEventRecorder::AddCutEvent(NoteCutInfo noteCutInfo, float time) {
31 | cutEvents.emplace_back(Replay::ReplayUtils::GetNoteHash(noteCutInfo.noteData), GetEventSaveTime(time), noteCutInfo);
32 | }
33 |
34 | void Replay::NoteEventRecorder::AddMissEvent(NoteController* noteController) {
35 | missEvents.emplace_back(Replay::ReplayUtils::GetNoteHash(noteController->get_noteData()), GetEventSaveTime(Replay::SongUtils::GetSongTime()));
36 | }
37 |
38 | void Replay::NoteEventRecorder::WriteEvents(std::ofstream& output) {
39 | Replay::FileUtils::WriteEvents(cutEvents, Replay::NoteEventTypes::cutEventID, output);
40 |
41 | Replay::FileUtils::WriteEvents(missEvents, Replay::NoteEventTypes::missEventID, output);
42 | }
43 |
44 | float Replay::NoteEventRecorder::GetAverageCutScore() {
45 | float total = 0;
46 | int goodCuts = 0;
47 | for(NoteEventTypes::NoteCutEvent event : cutEvents) {
48 | if(!event.noteCutInfo.AllIsOkay()) continue;
49 |
50 | // May cause issues with chains
51 | int beforeRating = std::round(event.swingRating.beforeCutRating);
52 | int afterRating = std::round(event.swingRating.afterCutRating);
53 | int accuracy = std::round((1.0f - std::clamp(event.noteCutInfo.cutDistanceToCenter / 0.3f, 0.0f, 1.0f)) * 15);
54 |
55 | int cutScore = beforeRating + afterRating + accuracy;
56 |
57 | total += cutScore;
58 | goodCuts++;
59 | }
60 | if(goodCuts == 0) return 0;
61 | return total / (float) goodCuts;
62 | }
--------------------------------------------------------------------------------
/src/Recording/ObstacleEventRecorder.cpp:
--------------------------------------------------------------------------------
1 | #include "Recording/ObstacleEventRecorder.hpp"
2 |
3 | void Replay::ObstacleEventRecorder::AddEvent(GlobalNamespace::GameEnergyCounter* gameEnergyCounter) {
4 | bool playerObstacleInteracting = (bool) gameEnergyCounter->playerHeadAndObstacleInteraction->intersectingObstacles->count;
5 | if(playerObstacleInteracting == lastInteracting) return;
6 |
7 | float songTime = SongUtils::GetSongTime();
8 | float energy = gameEnergyCounter->get_energy();
9 |
10 | events.emplace_back(songTime, energy);
11 |
12 | lastInteracting = playerObstacleInteracting;
13 | }
14 |
15 | void Replay::ObstacleEventRecorder::WriteEvents(std::ofstream& output) {
16 | Replay::FileUtils::WriteEvents(events, Replay::ObstacleEventTypes::eventID, output);
17 | }
18 |
--------------------------------------------------------------------------------
/src/Recording/PlayerRecorder.cpp:
--------------------------------------------------------------------------------
1 | #include "Recording/PlayerRecorder.hpp"
2 |
3 | #include "Utils/MathUtils.hpp"
4 |
5 | void Replay::PlayerRecorder::AddEvent(PlayerEventTypes::PlayerTransforms const& playerTransforms) {
6 | float songTime = SongUtils::GetSongTime();
7 | if(!playerEvents.empty() && songTime == playerEvents[playerEvents.size() - 1].time) return;
8 |
9 | playerEvents.emplace_back(songTime, playerTransforms, SaberUtils::saberManager->leftSaber->saberBladeTopPos, SaberUtils::saberManager->rightSaber->saberBladeTopPos);
10 | }
11 |
12 | void Replay::PlayerRecorder::AddSaberEvent(GlobalNamespace::SaberType saberType) {
13 | int lastAddedEventIndex = playerEvents.size() - 1;
14 | if(saberType == SaberType::SaberA) {
15 | leftSaberEvents.emplace_back(playerEvents[lastAddedEventIndex].time, playerEvents[lastAddedEventIndex].playerTransforms.leftSaber);
16 | } else {
17 | rightSaberEvents.emplace_back(playerEvents[lastAddedEventIndex].time, playerEvents[lastAddedEventIndex].playerTransforms.rightSaber);
18 | }
19 | }
20 |
21 | void Replay::PlayerRecorder::AddSaberMovement(GlobalNamespace::BladeMovementDataElement bladeMovement, GlobalNamespace::SaberType saberType) {
22 | //Not in the mood rn to not hard code this, forgive me
23 | float angleNeeded = 30;//These values can be optimized more
24 | float minDistance = 0.05f;
25 | float maxDistance = 1;
26 | float maxTime = 1.0f / 10.0f;
27 |
28 | float songTime = SongUtils::GetSongTime();
29 |
30 | if(saberType == SaberType::SaberA) {
31 | if(leftSaberLastSavedMovement.segmentNormal == UnityEngine::Vector3::get_zero()) {
32 | leftSaberLastSavedMovement = bladeMovement;
33 | return;
34 | }
35 |
36 | float lastEventTime = 0;
37 | if(!leftSaberEvents.empty()) lastEventTime = leftSaberEvents[leftSaberEvents.size() - 1].time;
38 |
39 | float timeSinceLastEvent = songTime - lastEventTime;
40 | float distance = UnityEngine::Vector3::Distance(bladeMovement.topPos, leftSaberLastSavedMovement.topPos);
41 | float angle = UnityEngine::Vector3::Angle(bladeMovement.segmentNormal, leftSaberLastSavedMovement.segmentNormal);
42 |
43 | bool addEvent = (angle > angleNeeded && distance > minDistance) || distance > maxDistance || timeSinceLastEvent > maxTime;
44 |
45 | if(addEvent) {
46 | leftSaberLastSavedMovement = bladeMovement;
47 | AddSaberEvent(saberType);
48 | return;
49 | }
50 | } else {
51 | if(rightSaberLastSavedMovement.segmentNormal == UnityEngine::Vector3::get_zero()) {
52 | rightSaberLastSavedMovement = bladeMovement;
53 | return;
54 | }
55 |
56 | float lastEventTime = 0;
57 | if(!rightSaberEvents.empty()) lastEventTime = rightSaberEvents[rightSaberEvents.size() - 1].time;
58 |
59 | float timeSinceLastEvent = songTime - lastEventTime;
60 | float distance = UnityEngine::Vector3::Distance(bladeMovement.topPos, rightSaberLastSavedMovement.topPos);
61 | float angle = UnityEngine::Vector3::Angle(bladeMovement.segmentNormal, rightSaberLastSavedMovement.segmentNormal);
62 |
63 | bool addEvent = (angle > angleNeeded && distance > minDistance) || distance > maxDistance || timeSinceLastEvent > maxTime;
64 |
65 | if(addEvent) {
66 | rightSaberLastSavedMovement = bladeMovement;
67 | AddSaberEvent(saberType);
68 | return;
69 | }
70 | }
71 | }
72 |
73 | void Replay::PlayerRecorder::GetImportantEvents() {
74 | headEvents.emplace_back(playerEvents[0].time, playerEvents[0].playerTransforms.head);
75 | leftSaberEvents.emplace_back(playerEvents[0].time, playerEvents[0].playerTransforms.leftSaber);
76 | rightSaberEvents.emplace_back(playerEvents[0].time, playerEvents[0].playerTransforms.rightSaber);
77 |
78 | float timePerEvent = 1.0f / 10.0f;// Can probably be lower, check after adding smooth camera (also maybe dynamic head events?)
79 | float lastSavedEventTime = 0;
80 |
81 | for(auto& event : playerEvents) {
82 | if(event.time - lastSavedEventTime > timePerEvent) {
83 | headEvents.emplace_back(event.time, event.playerTransforms.head);
84 | lastSavedEventTime = event.time;
85 | }
86 | }
87 |
88 | int lastIndex = playerEvents.size() - 1;
89 | headEvents.emplace_back(playerEvents[lastIndex].time, playerEvents[lastIndex].playerTransforms.head);
90 | leftSaberEvents.emplace_back(playerEvents[lastIndex].time, playerEvents[lastIndex].playerTransforms.leftSaber);
91 | rightSaberEvents.emplace_back(playerEvents[lastIndex].time, playerEvents[lastIndex].playerTransforms.rightSaber);
92 | }
93 |
94 | void Replay::PlayerRecorder::WriteEvents(std::ofstream& output) {
95 | GetImportantEvents();
96 |
97 | Replay::FileUtils::WriteEvents(headEvents, Replay::PlayerEventTypes::headEventID, output);
98 |
99 | Replay::FileUtils::WriteEvents(leftSaberEvents, Replay::PlayerEventTypes::leftSaberEventID, output);
100 |
101 | Replay::FileUtils::WriteEvents(rightSaberEvents, Replay::PlayerEventTypes::rightSaberEventID, output);
102 | log("Average saber events per second per hand: %f", (float)((rightSaberEvents.size() + leftSaberEvents.size()) / 2) / playerEvents[playerEvents.size() - 1].time);
103 | }
--------------------------------------------------------------------------------
/src/Recording/ReplayRecorder.cpp:
--------------------------------------------------------------------------------
1 | #include "Recording/ReplayRecorder.hpp"
2 |
3 | #include "Utils/ModifiersUtils.hpp"
4 |
5 | #include
6 | #include
7 |
8 | using namespace rapidjson;
9 |
10 | void Replay::ReplayRecorder::Init() {
11 | log("Setting up Replay Recorder");
12 | playerRecorder = Replay::PlayerRecorder();
13 | noteEventRecorder = Replay::NoteEventRecorder();
14 | obstacleEventRecorder = Replay::ObstacleEventRecorder();
15 | }
16 |
17 | void Replay::ReplayRecorder::CreateClearedSpecificMetadata(GlobalNamespace::LevelCompletionResults* results, rapidjson::Document::AllocatorType& allocator) {
18 | Value clearedInfo(kObjectType);
19 |
20 | clearedInfo.AddMember("RawScore", results->multipliedScore, allocator);
21 | clearedInfo.AddMember("ModifiedScore", results->modifiedScore, allocator);
22 |
23 | metadata.AddMember("ClearedInfo", clearedInfo, allocator);
24 | }
25 |
26 | void Replay::ReplayRecorder::CreateFailedSpecificMetadata(GlobalNamespace::LevelCompletionResults* results, rapidjson::Document::AllocatorType& allocator) {
27 | Value failedInfo(kObjectType);
28 |
29 | failedInfo.AddMember("FailedTime", SongUtils::failTime, allocator);
30 |
31 | metadata.AddMember("FailedInfo", failedInfo, allocator);
32 | }
33 |
34 | void Replay::ReplayRecorder::CreateMetadata(GlobalNamespace::LevelCompletionResults* results) {
35 | metadata.SetObject();
36 |
37 | rapidjson::Document::AllocatorType& allocator = metadata.GetAllocator();
38 |
39 | GlobalNamespace::PlayerSpecificSettings* settings = SongUtils::playerSpecificSettings;
40 |
41 | Value playerSettings(kObjectType);
42 | playerSettings.AddMember("LeftHanded", settings->leftHanded, allocator);
43 | playerSettings.AddMember("AutoHeight", settings->automaticPlayerHeight, allocator);
44 | playerSettings.AddMember("Height", settings->playerHeight, allocator);
45 | metadata.AddMember("PlayerSettings", playerSettings, allocator);
46 |
47 | std::vector modifierStrings = ModifiersUtils::ModifiersToStrings(results->gameplayModifiers);
48 | if(!modifierStrings.empty()) {
49 | Value modifiers(kArrayType);
50 | for(std::string modifierName : modifierStrings) {
51 | if(modifierName == "NoFail" && !SongUtils::didFail) continue;
52 | modifiers.PushBack(rapidjson::Value{}.SetString(modifierName.c_str(), modifierName.length(), allocator), allocator);
53 | }
54 | metadata.AddMember("Modifiers", modifiers, allocator);
55 | }
56 |
57 | int64_t timeSet = static_cast(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()));
58 |
59 | Value info(kObjectType);
60 | info.AddMember("TimeSet", timeSet, allocator);
61 | info.AddMember("AverageCutScore", noteEventRecorder.GetAverageCutScore(), allocator);
62 | info.AddMember("GoodCuts", results->goodCutsCount, allocator);
63 | info.AddMember("MissedNotes", results->badCutsCount + results->missedCount, allocator);
64 | info.AddMember("MaxCombo", results->maxCombo, allocator);
65 | metadata.AddMember("Info", info, allocator);
66 |
67 | if(!SongUtils::didFail) {
68 | CreateClearedSpecificMetadata(results, allocator);
69 | } else {
70 | CreateFailedSpecificMetadata(results, allocator);
71 | }
72 | }
73 |
74 | void Replay::ReplayRecorder::StopRecording(GlobalNamespace::LevelCompletionResults* results) {
75 | CreateMetadata(results);
76 | WriteReplayFile(ReplayUtils::GetTempReplayFilePath());
77 |
78 | std::string filepath = ReplayUtils::GetReplayFilePath(SongUtils::GetMapID());
79 | if(ShouldMoveFile(results, filepath)) {
80 | log("Moving replay file to permanent location");
81 | std::filesystem::rename(ReplayUtils::GetTempReplayFilePath(), ReplayUtils::GetReplayFilePath(SongUtils::GetMapID()));
82 | } else {
83 | log("Not permanently saving replay file, current file has higher priority");
84 | }
85 | }
86 |
87 | bool Replay::ReplayRecorder::ShouldMoveFile(GlobalNamespace::LevelCompletionResults* results, std::string_view filepath) {
88 | if(!fileexists(filepath)) return true;
89 |
90 | Document metadata = FileUtils::GetMetadataFromReplayFile(filepath);
91 | if(metadata.HasMember("ClearedInfo")) {
92 | if(SongUtils::didFail) return false;
93 |
94 | if(results->modifiedScore >= metadata["ClearedInfo"]["ModifiedScore"].GetInt()) return true;
95 |
96 | return false;
97 | } else if(metadata.HasMember("FailedInfo")) {
98 | if(!SongUtils::didFail) return true;
99 |
100 | if(SongUtils::failTime > metadata["FailedInfo"]["FailedTime"].GetFloat()) return true;
101 |
102 | return false;
103 | }
104 |
105 | return true;
106 | }
107 |
108 | void Replay::ReplayRecorder::WriteReplayFile(std::string path) {
109 | log("Writing Replay file at %s", path.c_str());
110 | std::filesystem::create_directories(Replay::ReplayUtils::GetReplaysDirectory());
111 |
112 | std::ofstream output = std::ofstream(path, std::ios::binary);
113 |
114 | int magicBytes = replayMagicBytes;
115 | output.write(reinterpret_cast(&magicBytes), sizeof(int));
116 |
117 | byte version = fileVersion;
118 | output.write(reinterpret_cast(&version), sizeof(byte));
119 |
120 | StringBuffer buffer;
121 | Writer writer(buffer);
122 | writer.SetMaxDecimalPlaces(2);
123 | metadata.Accept(writer);
124 | const char* metadataString = buffer.GetString();
125 |
126 | unsigned int metadataLength = (int) strlen(metadataString);
127 | output.write(reinterpret_cast(&metadataLength), sizeof(unsigned int));
128 |
129 | output.write(metadataString, strlen(metadataString));
130 |
131 | playerRecorder.WriteEvents(output);
132 | noteEventRecorder.WriteEvents(output);
133 | obstacleEventRecorder.WriteEvents(output);
134 |
135 | output.flush();
136 | }
--------------------------------------------------------------------------------
/src/ReplayManager.cpp:
--------------------------------------------------------------------------------
1 | #include "ReplayManager.hpp"
2 |
3 | Replay::ReplayRecorder Replay::ReplayManager::recorder;
4 |
5 | Replay::Replayer Replay::ReplayManager::replayer;
--------------------------------------------------------------------------------
/src/Replaying/NoteEventReplayer.cpp:
--------------------------------------------------------------------------------
1 | #include "Replaying/NoteEventReplayer.hpp"
2 |
3 | void Replay::NoteEventReplayer::Init() {
4 | GlobalNamespace::SharedCoroutineStarter::get_instance()->StartCoroutine(custom_types::Helpers::CoroutineHelper::New(Update()));
5 | }
6 |
7 | void Replay::NoteEventReplayer::AddActiveEvents(GlobalNamespace::NoteController* noteController) {
8 | auto noteHash = Replay::ReplayUtils::GetNoteHash(noteController->noteData);
9 |
10 | for (auto eventIt = cutEvents.begin(); eventIt != cutEvents.end(); eventIt++) {
11 | auto const ¬eCutEvent = *eventIt;
12 |
13 | if(noteHash == noteCutEvent.noteHash) {
14 | activeCutEvents.emplace_back(noteController, noteCutEvent);
15 |
16 | cutEvents.erase(eventIt);
17 |
18 | return;
19 | }
20 | }
21 |
22 | for (auto eventIt = missEvents.begin(); eventIt != missEvents.end(); eventIt++) {
23 | auto const ¬eMissEvent = *eventIt;
24 |
25 | if(noteHash == noteMissEvent.noteHash) {
26 | activeMissEvents.emplace_back(noteController, noteMissEvent);
27 |
28 | missEvents.erase(eventIt);
29 |
30 | return;
31 | }
32 | }
33 | log("Could not find event for note!");
34 | }
35 |
36 | void Replay::NoteEventReplayer::ReadCutEvents(std::ifstream& input, int eventsLength) {
37 | for(int i = 0; i < eventsLength; i++) {
38 | cutEvents.emplace_back(input);
39 | }
40 | }
41 |
42 | void Replay::NoteEventReplayer::ReadMissEvents(std::ifstream& input, int eventsLength) {
43 | for(int i = 0; i < eventsLength; i++) {
44 | missEvents.emplace_back(input);
45 | }
46 | }
47 |
48 | static const Il2CppType * NoteCutInfoT(ByRef info) {
49 | return il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_type>::get();
50 | }
51 |
52 | //GlobalNamespace::NoteController::
53 | void SendNoteWasCutEvent(GlobalNamespace::NoteController* self, ByRef noteCutInfo) {
54 | static auto ___internal__logger = ::Logger::get().WithContext("GlobalNamespace::NoteController::SendNoteWasCutEvent");
55 | static auto* ___internal__method = THROW_UNLESS((::il2cpp_utils::FindMethod(self, "SendNoteWasCutEvent", std::vector{}, ::std::vector{::NoteCutInfoT(noteCutInfo)})));
56 | ::il2cpp_utils::RunMethodRethrow(self, ___internal__method, noteCutInfo);
57 | }
58 |
59 | #pragma clang diagnostic push
60 | #pragma clang diagnostic ignored "-Winvalid-noreturn"
61 | #pragma ide diagnostic ignored "EndlessLoop"
62 | custom_types::Helpers::Coroutine Replay::NoteEventReplayer::Update() {
63 | while(SongUtils::inSong) {
64 | float songTime = Replay::SongUtils::GetSongTime();
65 |
66 | // Feel free to make this not terrible, just trying to run the events in order of time
67 | std::vector eventsToRun;
68 |
69 | for(int i = 0; i < activeCutEvents.size(); i++) {
70 | float eventTime = activeCutEvents[i].event.time;
71 | if(songTime > eventTime) {
72 | eventsToRun.emplace_back(eventTime, true, i);
73 | }
74 | }
75 |
76 | for(int i = 0; i < activeMissEvents.size(); i++) {
77 | float eventTime = activeMissEvents[i].event.time;
78 | if(songTime > eventTime) {
79 | eventsToRun.emplace_back(eventTime, false, i);
80 | }
81 | }
82 |
83 | if(eventsToRun.empty()) co_yield nullptr;
84 |
85 | // I am truly sorry to whoever reads this code next, I am just doing the first thing that comes to mind
86 | std::sort(eventsToRun.begin(), eventsToRun.end());
87 |
88 | for(auto& eventToRun : eventsToRun) {
89 | if(eventToRun.isCutEvent) {
90 | auto& activeCutEvent = activeCutEvents[eventToRun.eventIndex];
91 |
92 | GlobalNamespace::NoteCutInfo noteCutInfo = ReplayUtils::CreateNoteCutInfoFromSimple(activeCutEvent.event.noteCutInfo, activeCutEvent.note->get_noteData());
93 |
94 | if(noteCutInfo.get_allIsOK()) swingRatings.emplace_back(activeCutEvent.event.noteHash, activeCutEvent.event.swingRating);
95 |
96 | SendNoteWasCutEvent(activeCutEvent.note, byref(noteCutInfo));
97 | } else {
98 | activeMissEvents[eventToRun.eventIndex].note->SendNoteWasMissedEvent();
99 | }
100 | }
101 |
102 | // Two sorts every frame... I am gonna scrape out my eyes
103 | std::sort(eventsToRun.begin(), eventsToRun.end(), std::greater());
104 |
105 | for(auto& eventToRun : eventsToRun) {
106 | if(eventToRun.isCutEvent) {
107 | activeCutEvents.erase(activeCutEvents.begin() + eventToRun.eventIndex);
108 | } else {
109 | activeMissEvents.erase(activeMissEvents.begin() + eventToRun.eventIndex);
110 | }
111 | }
112 |
113 | co_yield nullptr;
114 | }
115 | co_return;
116 | }
117 | #pragma clang diagnostic pop
118 |
119 |
120 |
--------------------------------------------------------------------------------
/src/Replaying/ObstacleEventReplayer.cpp:
--------------------------------------------------------------------------------
1 | #include "Replaying/ObstacleEventReplayer.hpp"
2 |
3 | void Replay::ObstacleEventReplayer::ReadEvents(std::ifstream& input, int eventsLength) {
4 | for(int i = 0; i < eventsLength; i++) {
5 | events.emplace_back(input);
6 | }
7 | }
8 |
9 | bool Replay::ObstacleEventReplayer::ShouldSetEnergy() {
10 | if(nextEventIndex >= events.size()) return false;
11 |
12 | auto& event = events[nextEventIndex];
13 |
14 | if(SongUtils::GetSongTime() > event.time) {
15 | nextEventIndex++;
16 |
17 | return true;
18 | }
19 |
20 | return false;
21 | }
--------------------------------------------------------------------------------
/src/Replaying/PlayerReplayer.cpp:
--------------------------------------------------------------------------------
1 | #include "Replaying/PlayerReplayer.hpp"
2 |
3 | #include "Utils/MathUtils.hpp"
4 |
5 | void Replay::PlayerReplayer::ReadHeadEvents(std::ifstream& input, int eventsLength) {
6 | for(int i = 0; i < eventsLength; i++) {
7 | headEvents.emplace_back(input);
8 | }
9 | }
10 |
11 | void Replay::PlayerReplayer::ReadLeftSaberEvents(std::ifstream& input, int eventsLength) {
12 | for(int i = 0; i < eventsLength; i++) {
13 | leftSaberEvents.emplace_back(input);
14 | }
15 | }
16 |
17 | void Replay::PlayerReplayer::ReadRightSaberEvents(std::ifstream& input, int eventsLength) {
18 | for(int i = 0; i < eventsLength; i++) {
19 | rightSaberEvents.emplace_back(input);
20 | }
21 | }
22 |
23 | void Replay::PlayerReplayer::SetPlayerTransforms(GlobalNamespace::PlayerTransforms* playerTransforms) {
24 | // Grossly hard coded, reminder to fix in future
25 |
26 | UnityEngine::Transform* origin = playerTransforms->useOriginParentTransformForPseudoLocalCalculations ? playerTransforms->originParentTransform : playerTransforms->headTransform->GetParent();
27 |
28 | headIndex = Replay::ReplayUtils::GetCurrentIndex(headEvents, headIndex);
29 | EulerTransformEvent event = headEvents[headIndex];
30 |
31 | UnityEngine::Transform* head = playerTransforms->headTransform;
32 | head->set_rotation(origin->get_rotation() * UnityEngine::Quaternion::Euler(event.transform.rotation));
33 | head->set_position(origin->TransformPoint(event.transform.position));
34 |
35 | leftSaberIndex = Replay::ReplayUtils::GetCurrentIndex(leftSaberEvents, leftSaberIndex);
36 | event = leftSaberEvents[leftSaberIndex];
37 | EulerTransformEvent nextEvent = leftSaberEvents[std::min(leftSaberIndex + 1, (int)leftSaberEvents.size() - 1)];
38 |
39 | float lerpAmount = std::max(0.0f, std::min(1.0f, Replay::ReplayUtils::LerpAmountBetweenEvents(event, nextEvent)));
40 |
41 | UnityEngine::Transform* leftSaber = playerTransforms->leftHandTransform;
42 | leftSaber->set_rotation(origin->get_rotation() * Replay::MathUtils::LerpEulerAngles(event.transform.rotation, nextEvent.transform.rotation, lerpAmount));
43 | leftSaber->set_position(origin->TransformPoint(Replay::MathUtils::Lerp(event.transform.position, nextEvent.transform.position, lerpAmount)));
44 |
45 | rightSaberIndex = Replay::ReplayUtils::GetCurrentIndex(rightSaberEvents, rightSaberIndex);
46 | event = rightSaberEvents[rightSaberIndex];
47 | nextEvent = rightSaberEvents[std::min(rightSaberIndex + 1, (int)rightSaberEvents.size() - 1)];
48 |
49 | lerpAmount = std::max(0.0f, std::min(1.0f, Replay::ReplayUtils::LerpAmountBetweenEvents(event, nextEvent)));
50 |
51 | UnityEngine::Transform* rightSaber = playerTransforms->rightHandTransform;
52 | rightSaber->set_rotation(origin->get_rotation() * Replay::MathUtils::LerpEulerAngles(event.transform.rotation, nextEvent.transform.rotation, lerpAmount));
53 | rightSaber->set_position(origin->TransformPoint(Replay::MathUtils::Lerp(event.transform.position, nextEvent.transform.position, lerpAmount)));
54 | }
--------------------------------------------------------------------------------
/src/Replaying/Replayer.cpp:
--------------------------------------------------------------------------------
1 | #include "Replaying/Replayer.hpp"
2 |
3 | #include "rapidjson/document.h"
4 | #include
5 |
6 | using namespace rapidjson;
7 |
8 | custom_types::Helpers::Coroutine Replay::Replayer::WaitForSongStartToInit() {
9 | while(!SongUtils::inSong) {
10 | co_yield nullptr;
11 | }
12 |
13 | noteEventReplayer.Init();
14 | co_return;
15 | }
16 |
17 | void Replay::Replayer::Init(std::string_view path) {
18 | log("Setting up Replayer");
19 | playerReplayer = Replay::PlayerReplayer();
20 | noteEventReplayer = Replay::NoteEventReplayer();
21 | obstacleEventReplayer = Replay::ObstacleEventReplayer();
22 |
23 | ReadReplayFile(path);
24 |
25 | GlobalNamespace::SharedCoroutineStarter::get_instance()->StartCoroutine(custom_types::Helpers::CoroutineHelper::New(Replay::Replayer::WaitForSongStartToInit()));
26 | }
27 |
28 | void Replay::Replayer::ReadReplayFile(std::string_view path) {
29 | log("Reading Replay file at %s", path.data());
30 | std::ifstream input = std::ifstream(path, std::ios::binary);
31 |
32 | if(input.is_open()) {
33 | int magicBytes;
34 | input.read(reinterpret_cast(&magicBytes), sizeof(int));
35 | if(magicBytes != replayMagicBytes) {
36 | log("INCORRECT MAGIC BYTES");
37 | return;
38 | }
39 |
40 | byte version;
41 | input.read(reinterpret_cast(&version), sizeof(byte));
42 | log("File version is %i", version);
43 |
44 | int metadataLength;
45 | input.read(reinterpret_cast(&metadataLength), sizeof(int));
46 |
47 | char* metadataString = new char[metadataLength];
48 | input.read(metadataString, (size_t) metadataLength);
49 |
50 | Document metadata;
51 | metadata.Parse(metadataString);
52 |
53 | free(metadataString);
54 |
55 | byte eventID;
56 | while(input.read(reinterpret_cast(&eventID), sizeof(byte))) {
57 | unsigned int eventsLength;
58 | input.read(reinterpret_cast(&eventsLength), sizeof(unsigned int));
59 |
60 | log("Event %i has %i events", eventID, eventsLength); // Add log for size in bytes of events
61 |
62 | int startByte = input.tellg();
63 |
64 | switch(eventID) {
65 | case PlayerEventTypes::headEventID:
66 | playerReplayer.ReadHeadEvents(input, eventsLength);
67 | break;
68 | case PlayerEventTypes::leftSaberEventID:
69 | playerReplayer.ReadLeftSaberEvents(input, eventsLength);
70 | break;
71 | case PlayerEventTypes::rightSaberEventID:
72 | playerReplayer.ReadRightSaberEvents(input, eventsLength);
73 | break;
74 | case NoteEventTypes::cutEventID:
75 | noteEventReplayer.ReadCutEvents(input, eventsLength);
76 | break;
77 | case NoteEventTypes::missEventID:
78 | noteEventReplayer.ReadMissEvents(input, eventsLength);
79 | break;
80 | case ObstacleEventTypes::eventID:
81 | obstacleEventReplayer.ReadEvents(input, eventsLength);
82 | break;
83 | }
84 |
85 | int endByte = input.tellg();
86 | int bytes = endByte - startByte;
87 | float kiloBytes = (float) bytes / 1024.0f;
88 | float megaBytes = (float) kiloBytes / 1024.0f;
89 | log("Length of %i bytes (%f KB or %f MB)", bytes, kiloBytes, megaBytes);
90 | }
91 | } else {
92 | log("COULD NOT FIND REPLAY FILE");
93 | }
94 | }
--------------------------------------------------------------------------------
/src/UI/ReplayViewController.cpp:
--------------------------------------------------------------------------------
1 | #include "UI/ReplayViewController.hpp"
2 |
3 | #include "GlobalNamespace/ScoreModel.hpp"
4 | #include "GlobalNamespace/SimpleDialogPromptViewController.hpp"
5 | #include "UnityEngine/RectOffset.hpp"
6 | #include "UnityEngine/Object.hpp"
7 | #include "HMUI/TitleViewController.hpp"
8 | #include "HMUI/ViewController.hpp"
9 | #include "HMUI/ViewController_AnimationDirection.hpp"
10 | #include "VRUIControls/VRGraphicRaycaster.hpp"
11 | #include "System/Action_1.hpp"
12 | #include "UnityEngine/Events/UnityAction.hpp"
13 |
14 | #include "questui/shared/BeatSaberUI.hpp"
15 |
16 | #include "UI/UIManager.hpp"
17 | #include "ReplayManager.hpp"
18 | #include "Utils/ReplayUtils.hpp"
19 | #include "Utils/ModifiersUtils.hpp"
20 | #include "Utils/TypeUtils.hpp"
21 | #include "Utils/UnityUtils.hpp"
22 | #include "Utils/FindComponentsUtils.hpp"
23 | #include "Utils/TimeUtils.hpp"
24 | #include "Utils/UIUtils.hpp"
25 |
26 | using namespace Replay;
27 | using namespace Replay::UI;
28 | using namespace QuestUI;
29 | using namespace QuestUI::BeatSaberUI;
30 | using namespace VRUIControls;
31 | using namespace UnityEngine::Events;
32 |
33 | DEFINE_TYPE(Replay::UI, ReplayViewController);
34 |
35 | void Replay::UI::ReplayViewController::Init(std::string_view filePath, bool overwriteFile) {
36 | path = filePath;
37 | overwrite = overwriteFile;
38 | }
39 |
40 | void Replay::UI::ReplayViewController::DidActivate(bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) {
41 | if(firstActivation) {
42 | get_gameObject()->GetComponent()->physicsRaycaster = BeatSaberUI::GetPhysicsRaycasterWithCache();
43 | // WHy this no worky? it worky on delete view controller, why not worky here
44 |
45 | CreateLevelBar(get_transform());
46 |
47 | CreateText(get_rectTransform());
48 |
49 | CreateButtons(get_rectTransform());
50 | }
51 |
52 | if(addedToHierarchy) {
53 | SetupLevelBar();
54 |
55 | SetText();
56 |
57 | SetButton(overwrite);
58 | }
59 | }
60 |
61 | void Replay::UI::ReplayViewController::CreateLevelBar(UnityEngine::Transform* parent) {
62 | levelBar = ArrayUtil::First(UnityEngine::Resources::FindObjectsOfTypeAll(), [] (GlobalNamespace::LevelBar* x) { return to_utf8(csstrtostr(x->get_transform()->GetParent()->get_name())) == "PracticeViewController"; })->get_gameObject();
63 |
64 | levelBar->set_name(newcsstr("ReplayLevelBarSimple"));
65 | levelBar->get_transform()->SetParent(parent, false);
66 | levelBar->GetComponent()->set_anchoredPosition(UnityEngine::Vector2(0, -3.0f));
67 | }
68 |
69 | #define CreateCenteredText(text, parent, fontSize, lineSpacing) text = QuestUI::BeatSaberUI::CreateText(parent, ""); \
70 | text->set_fontSize(fontSize); \
71 | text->set_alignment(TMPro::TextAlignmentOptions::Center); \
72 | text->set_lineSpacing(lineSpacing);
73 |
74 | void Replay::UI::ReplayViewController::CreateText(UnityEngine::RectTransform* parent) {
75 | UnityEngine::UI::HorizontalLayoutGroup* horizontalLayout = CreateHorizontalLayoutGroup(parent);
76 | horizontalLayout->set_spacing(0);
77 | horizontalLayout->set_childControlWidth(false);
78 | horizontalLayout->set_childForceExpandWidth(false);
79 | horizontalLayout->GetComponent()->set_anchoredPosition(UnityEngine::Vector2(34.0f, 1));
80 |
81 | float childrenWidth = 43.5f;
82 | float childrenSpacing = 2.5f;
83 |
84 | UnityEngine::UI::VerticalLayoutGroup* layout1 = CreateVerticalLayoutGroup(horizontalLayout->get_rectTransform());
85 | layout1->set_spacing(childrenSpacing);
86 | layout1->GetComponent()->set_preferredWidth(childrenWidth);
87 |
88 | UnityEngine::UI::VerticalLayoutGroup* layout2 = CreateVerticalLayoutGroup(horizontalLayout->get_rectTransform());
89 | layout2->set_spacing(childrenSpacing);
90 | layout2->GetComponent()->set_preferredWidth(childrenWidth);
91 |
92 | float fontSize = 4.5f;
93 | float lineSpacing = -35.0f;
94 |
95 | CreateCenteredText(dateText, layout1->get_transform(), fontSize, lineSpacing);
96 | CreateCenteredText(scoreOrFailedText, layout1->get_transform(), fontSize, lineSpacing);
97 | CreateCenteredText(modifiersText, layout1->get_transform(), fontSize, lineSpacing);
98 |
99 | CreateCenteredText(averageCutScoreText, layout2->get_transform(), fontSize, lineSpacing);
100 | CreateCenteredText(missedNotesText, layout2->get_transform(), fontSize, lineSpacing);
101 | CreateCenteredText(maxComboText, layout2->get_transform(), fontSize, lineSpacing);
102 | }
103 |
104 | GlobalNamespace::SimpleDialogPromptViewController* deleteDialogPromptViewController = nullptr;
105 |
106 | GlobalNamespace::SimpleDialogPromptViewController* getDeleteDialogPromptViewController() {
107 | if(!deleteDialogPromptViewController) {
108 | deleteDialogPromptViewController = UnityEngine::Object::Instantiate(FindComponentsUtils::GetSimpleDialogPromptViewController());
109 | deleteDialogPromptViewController->GetComponent()->physicsRaycaster = BeatSaberUI::GetPhysicsRaycasterWithCache();
110 | static auto dialogViewControllerName = il2cpp_utils::newcsstr("DeleteDialogPromptViewController");
111 | deleteDialogPromptViewController->set_name(dialogViewControllerName);
112 | deleteDialogPromptViewController->get_gameObject()->SetActive(false);
113 | }
114 | return deleteDialogPromptViewController;
115 | }
116 |
117 | void Replay::UI::ReplayViewController::CreateButtons(UnityEngine::RectTransform* parent) {
118 | UnityEngine::UI::HorizontalLayoutGroup* buttonLayout = CreateHorizontalLayoutGroup(parent);
119 | buttonLayout->set_spacing(3);
120 | buttonLayout->set_childControlWidth(false);
121 | buttonLayout->set_childForceExpandWidth(false);
122 | buttonLayout->set_childControlHeight(false);
123 | buttonLayout->set_childForceExpandHeight(false);
124 | buttonLayout->set_childAlignment(UnityEngine::TextAnchor::MiddleCenter);
125 | buttonLayout->GetComponent()->set_anchoredPosition(UnityEngine::Vector2(-3, -25));
126 |
127 | UnityEngine::Vector2 size(40, 10);
128 |
129 | deleteButton = CreateUIButton(
130 | buttonLayout->get_transform(),
131 | ""
132 | );
133 | deleteButton->get_gameObject()->GetComponent()->set_sizeDelta(size);
134 |
135 | CreateUIButton(
136 | buttonLayout->get_transform(),
137 | "Watch Replay",
138 | "OkButton",
139 | [this]() {
140 | log("Replay button pressed");
141 | ReplayManager::replayState = ReplayState::REPLAYING;
142 | ReplayManager::replayer = Replayer();
143 | ReplayManager::replayer.Init(path);
144 |
145 | UIManager::singlePlayerFlowCoordinator->StartLevelOrShow360Prompt(nullptr, false);
146 | }
147 | )->GetComponent()->set_sizeDelta(size);
148 | }
149 |
150 | void Replay::UI::ReplayViewController::SetupLevelBar() {
151 | GlobalNamespace::LevelBar* levelBarComponent = levelBar->GetComponent();
152 | levelBarComponent->showDifficultyAndCharacteristic = true;
153 | levelBarComponent->Setup(reinterpret_cast(SongUtils::beatmapLevel), SongUtils::beatmapCharacteristic, SongUtils::beatmapDifficulty);
154 | }
155 |
156 | void Replay::UI::ReplayViewController::SetText() {
157 | rapidjson::Document metadata = FileUtils::GetMetadataFromReplayFile(path);
158 |
159 | auto replayTime = static_cast(metadata["Info"]["TimeSet"].GetInt64());
160 | auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
161 |
162 | dateText->set_text(newcsstr(UIUtils::GetLayeredText("Date Set", TimeUtils::GetStringForTimeSince(replayTime, now))));
163 |
164 | if(metadata.HasMember("ClearedInfo")) {
165 | int maxScore = GlobalNamespace::ScoreModel::ComputeMaxMultipliedScoreForBeatmap(SongUtils::beatmapData);
166 | int modifiedScore = metadata["ClearedInfo"]["ModifiedScore"].GetInt();
167 | float percentage = ((float) modifiedScore / (float) maxScore) * 100;
168 |
169 | scoreOrFailedText->set_text(newcsstr(UIUtils::GetLayeredText("Score", std::to_string(modifiedScore) + " (" + TypeUtils::FloatToString(percentage) + "%)")));
170 | } else {
171 | float failedSongTime = metadata["FailedInfo"]["FailedTime"].GetFloat();
172 | float songLength = reinterpret_cast(SongUtils::beatmapLevel)->get_songDuration();
173 |
174 | scoreOrFailedText->set_text(newcsstr(UIUtils::GetLayeredText("Failed", "" + TimeUtils::SecondsToString(failedSongTime) + " / " + TimeUtils::SecondsToString(songLength))));
175 | }
176 |
177 | std::string modifiersString = "";
178 |
179 | for(const auto& value : metadata["Modifiers"].GetArray()) {
180 | modifiersString = modifiersString + ModifiersUtils::GetInitialsFromModifierName(value.GetString()) + " ";
181 | }
182 |
183 | if(modifiersString.empty()) {
184 | modifiersString = "None";
185 | } else {
186 | modifiersString.pop_back(); // Remove last space
187 | }
188 |
189 | modifiersText->set_text(newcsstr(UIUtils::GetLayeredText("Modifiers", modifiersString)));
190 |
191 | float averageCutScore = metadata["Info"]["AverageCutScore"].GetFloat();
192 | float cutPercentage = (averageCutScore / 115.0f) * 100;
193 |
194 | averageCutScoreText->set_text(newcsstr(UIUtils::GetLayeredText("Average Cut Score", TypeUtils::FloatToString(averageCutScore) + " (" + TypeUtils::FloatToString(cutPercentage) + "%)")));
195 |
196 | missedNotesText->set_text(newcsstr(UIUtils::GetLayeredText("Missed Notes", "" + std::to_string(metadata["Info"]["MissedNotes"].GetInt()) + "")));
197 |
198 | maxComboText->set_text(newcsstr(UIUtils::GetLayeredText("Max Combo", std::to_string(metadata["Info"]["MaxCombo"].GetInt()))));
199 | }
200 |
201 | void Replay::UI::ReplayViewController::SetButton(bool overwriteFile) {
202 | std::string actionName = overwriteFile ? "Overwrite" : "Delete";
203 |
204 | static auto contentName = il2cpp_utils::newcsstr("Content");
205 | static auto textName = il2cpp_utils::newcsstr("Text");
206 | auto contentTransform = deleteButton->get_transform()->Find(contentName);
207 | contentTransform->Find(textName)->GetComponent()->set_text(newcsstr(actionName));
208 |
209 | std::function deleteFunction = (std::function) [actionName, overwriteFile] () {
210 | auto titleName = il2cpp_utils::newcsstr(actionName + " Replay");
211 | auto deleteName = il2cpp_utils::newcsstr(actionName);
212 | auto cancelName = il2cpp_utils::newcsstr("Cancel");
213 | auto text = u"Are you sure?";
214 |
215 | getDeleteDialogPromptViewController()->Init(titleName, il2cpp_utils::newcsstr(text), deleteName, cancelName, il2cpp_utils::MakeDelegate*>(classof(System::Action_1*),
216 | (std::function) [overwriteFile] (int selectedButton) {
217 | UIManager::singlePlayerFlowCoordinator->DismissViewController(getDeleteDialogPromptViewController(), ViewController::AnimationDirection::Horizontal, nullptr, selectedButton == 0);
218 |
219 | if(selectedButton == 0) {
220 | if(overwriteFile) {
221 | std::filesystem::rename(UIManager::replayViewController->path.c_str(), ReplayUtils::GetReplayFilePath(SongUtils::GetMapID()));
222 | } else {
223 | std::remove(UIManager::replayViewController->path.c_str());
224 | }
225 |
226 | UIManager::singlePlayerFlowCoordinator->BackButtonWasPressed(UIManager::replayViewController);
227 |
228 | UIManager::SetReplayButtonCanvasActive(overwriteFile);
229 | }
230 | }
231 | ));
232 | UIManager::singlePlayerFlowCoordinator->PresentViewController(getDeleteDialogPromptViewController(), nullptr, ViewController::AnimationDirection::Horizontal, false);
233 | };
234 |
235 | auto onClick = UnityEngine::UI::Button::ButtonClickedEvent::New_ctor();
236 | onClick->AddListener(il2cpp_utils::MakeDelegate(classof(UnityAction*), deleteFunction));
237 |
238 | deleteButton->set_onClick(onClick);
239 | }
--------------------------------------------------------------------------------
/src/UI/UIManager.cpp:
--------------------------------------------------------------------------------
1 | #include "UI/UIManager.hpp"
2 |
3 | #include "UnityEngine/Transform.hpp"
4 | #include "UnityEngine/GameObject.hpp"
5 | #include "UnityEngine/UI/LayoutElement.hpp"
6 | #include "UnityEngine/Material.hpp"
7 | #include "UnityEngine/Resources.hpp"
8 | #include "UnityEngine/Events/UnityAction.hpp"
9 |
10 | #include "TMPro/TextMeshProUGUI.hpp"
11 | #include "TMPro/TextAlignmentOptions.hpp"
12 |
13 | #include "HMUI/ViewController.hpp"
14 | #include "HMUI/ViewController_AnimationDirection.hpp"
15 |
16 | #include "questui/shared/BeatSaberUI.hpp"
17 | #include "questui/shared/ArrayUtil.hpp"
18 |
19 | #include "Utils/FileUtils.hpp"
20 | #include "Utils/TimeUtils.hpp"
21 | #include "Sprites.hpp"
22 |
23 | #include "rapidjson/document.h"
24 | #include "rapidjson/stringbuffer.h"
25 | #include
26 | #include
27 |
28 | using namespace Replay::UI;
29 | using namespace GlobalNamespace;
30 | using namespace UnityEngine;
31 | using namespace UnityEngine::UI;
32 | using namespace UnityEngine::Events;
33 | using namespace il2cpp_utils;
34 | using namespace QuestUI;
35 | using namespace HMUI;
36 |
37 | std::function getReplayFunction(std::string path, bool overwrite) {
38 | std::function replayFunction = (std::function) [path, overwrite] () {
39 | if(UIManager::replayViewController == nullptr) UIManager::replayViewController = BeatSaberUI::CreateViewController();
40 | UIManager::replayViewController->Init(path, overwrite);
41 | UIManager::singlePlayerFlowCoordinator->PresentViewController(UIManager::replayViewController, nullptr, ViewController::AnimationDirection::Horizontal, false);
42 | };
43 | return replayFunction;
44 | }
45 |
46 | Button::ButtonClickedEvent* createReplayOnClick(std::string path, bool overwrite) {
47 | auto onClick = Button::ButtonClickedEvent::New_ctor();
48 | onClick->AddListener(il2cpp_utils::MakeDelegate(classof(UnityAction*), getReplayFunction(path, overwrite)));
49 | return onClick;
50 | }
51 |
52 | std::function getPlayButtonFunction() {
53 | static std::function playButtonFunction = (std::function) [] () {
54 | log("Play button pressed");
55 | ReplayManager::replayState = ReplayState::RECORDING;
56 |
57 | SongUtils::didFail = false;
58 |
59 | ReplayManager::recorder = ReplayRecorder();
60 | ReplayManager::recorder.Init();
61 | };
62 | return playButtonFunction;
63 | }
64 |
65 | void UIManager::SetReplayButtonOnClick(UnityEngine::Transform* buttonTransform, std::string path, bool overwrite) {
66 | buttonTransform->GetComponent