Add project files (part 1/2)
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
3721eaecb2
commit
73baf8d02d
274 files changed
+83593
No files matched your search
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
@@ -0,0 +1,5 @@
|
||||
build/
|
||||
injector/build-*/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
decrypted files/
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(slinky LANGUAGES C CXX)
|
||||
|
||||
# clang-cl + lld-link without a Windows SDK: CMake still wants a manifest tool for the DLL link step. LLVM ships
|
||||
# llvm-mt, which takes the same arguments as mt.exe; without it the build fails after all objects are compiled.
|
||||
if(WIN32 AND NOT CMAKE_MT)
|
||||
find_program(SLINKY_MT NAMES llvm-mt)
|
||||
if(SLINKY_MT)
|
||||
set(CMAKE_MT "${SLINKY_MT}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# The client as one shared library that is loaded into the Minecraft JVM.
|
||||
# engine src/engine the client's own engine (JNI, JVMTI, GUI, module manager, ...), start-up in protected/
|
||||
# logic src/logic the module logic (one function per original routine)
|
||||
# src/logic/*/*.h module descriptors' callbacks (GUI, setting registration, status); each one is compiled
|
||||
# on its own (they declare the settings as opaque blobs, the logic .cpp as typed values)
|
||||
# data src/data .rdata/.data of the original image (strings, tables, module descriptors), generated
|
||||
# platform src/platform everything that depends on the operating system, NanoVG, GL loader, C runtime bindings
|
||||
if(NOT WIN32)
|
||||
message(FATAL_ERROR "Only the Windows build exists so far. Cross-compile with\n"
|
||||
" cmake -S . -B build/cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-windows-clang-cl.cmake\n"
|
||||
"(a Linux platform layer is planned: JVMTI agent, GLFW/X11 input, dlopen)")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS "" CACHE STRING "" FORCE)
|
||||
set(CMAKE_C_FLAGS "" CACHE STRING "" FORCE)
|
||||
|
||||
# ---- sources ----
|
||||
file(GLOB_RECURSE ENGINE_SRC CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/src/engine/*.cpp)
|
||||
file(GLOB_RECURSE LOGIC_SRC CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/src/logic/*.cpp)
|
||||
file(GLOB_RECURSE MODULE_SRC CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/src/logic/*.h)
|
||||
set_source_files_properties(${MODULE_SRC} PROPERTIES LANGUAGE CXX COMPILE_OPTIONS /TP)
|
||||
file(GLOB PLATFORM_SRC CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/src/platform/*.cpp ${CMAKE_SOURCE_DIR}/src/platform/win/*.cpp)
|
||||
# stack probe for large frames (clang-cl would take it from the C runtime library)
|
||||
set(CHKSTK_OBJ ${CMAKE_BINARY_DIR}/chkstk.obj)
|
||||
# NanoVG core (+ fontstash, stb_truetype, stb_image) and its GL3 backend
|
||||
set(NANOVG_SRC ${CMAKE_SOURCE_DIR}/third_party/nanovg/src/nanovg.c ${CMAKE_SOURCE_DIR}/src/platform/nanovg_impl.c)
|
||||
|
||||
# ---- the original's data, assembled (GNU syntax) ----
|
||||
find_program(SLINKY_CLANG clang REQUIRED)
|
||||
set(DATA_OBJ)
|
||||
foreach(part rdata data iat)
|
||||
set(obj ${CMAKE_BINARY_DIR}/data_${part}.obj)
|
||||
add_custom_command(OUTPUT ${obj}
|
||||
COMMAND ${SLINKY_CLANG} --target=x86_64-pc-windows-msvc -c ${CMAKE_SOURCE_DIR}/src/data/${part}.s -o ${obj}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/src/data/${part}.s VERBATIM)
|
||||
list(APPEND DATA_OBJ ${obj})
|
||||
endforeach()
|
||||
|
||||
add_custom_command(OUTPUT ${CHKSTK_OBJ}
|
||||
COMMAND ${SLINKY_CLANG} --target=x86_64-pc-windows-msvc -c ${CMAKE_SOURCE_DIR}/src/platform/win/chkstk.s -o ${CHKSTK_OBJ}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/src/platform/win/chkstk.s VERBATIM)
|
||||
list(APPEND DATA_OBJ ${CHKSTK_OBJ})
|
||||
|
||||
# ---- import libraries from the .def files (no Windows SDK needed) ----
|
||||
find_program(SLINKY_DLLTOOL NAMES llvm-dlltool REQUIRED)
|
||||
set(IMPORT_LIBS)
|
||||
foreach(dll kernel32 user32 shell32 ucrtbase)
|
||||
set(lib ${CMAKE_BINARY_DIR}/imports/${dll}.lib)
|
||||
add_custom_command(OUTPUT ${lib}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/imports
|
||||
COMMAND ${SLINKY_DLLTOOL} -m i386:x86-64 -d ${CMAKE_SOURCE_DIR}/cmake/imports/${dll}.def -l ${lib}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/cmake/imports/${dll}.def VERBATIM)
|
||||
list(APPEND IMPORT_LIBS ${lib})
|
||||
endforeach()
|
||||
|
||||
add_custom_target(slinky_imports DEPENDS ${IMPORT_LIBS})
|
||||
add_library(slinky SHARED ${ENGINE_SRC} ${LOGIC_SRC} ${MODULE_SRC} ${PLATFORM_SRC} ${NANOVG_SRC} ${DATA_OBJ})
|
||||
add_dependencies(slinky slinky_imports)
|
||||
target_link_libraries(slinky PRIVATE ${IMPORT_LIBS})
|
||||
set_target_properties(slinky PROPERTIES PREFIX "" LINKER_LANGUAGE CXX)
|
||||
set_source_files_properties(${DATA_OBJ} PROPERTIES EXTERNAL_OBJECT TRUE GENERATED TRUE)
|
||||
|
||||
# The code generation is the one the sources were verified against (tools/asmdiff.py CFLAGS): same optimiser
|
||||
# settings, frame pointers, no stack cookies, no COMDAT per function, the old compiler's inc/dec and nop choices.
|
||||
target_compile_options(slinky PRIVATE
|
||||
-O2 -GS- /Gy-
|
||||
"SHELL:-Xclang -mframe-pointer=all"
|
||||
"SHELL:-Xclang -target-feature -Xclang +slow-incdec"
|
||||
"SHELL:-Xclang -target-feature -Xclang -fast-11bytenop"
|
||||
"SHELL:-Xclang -target-feature -Xclang -fast-15bytenop"
|
||||
-w)
|
||||
target_include_directories(slinky PRIVATE include third_party/nanovg/src third_party/gl)
|
||||
# the bundled C libraries (NanoVG, stb) get a minimal libc declaration set instead of the Windows SDK's headers
|
||||
set_source_files_properties(${NANOVG_SRC} PROPERTIES COMPILE_OPTIONS "-I${CMAKE_SOURCE_DIR}/third_party/minicrt/include")
|
||||
# glcorearb.h would include <windows.h> (not available in the cross build) just to define APIENTRY
|
||||
target_compile_definitions(slinky PRIVATE APIENTRY=)
|
||||
|
||||
# some files need extra flags (`// cflags: ...` in their first lines): the compiler version that built the
|
||||
# original lowered a few constructs differently
|
||||
foreach(f ${ENGINE_SRC} ${LOGIC_SRC} ${MODULE_SRC})
|
||||
file(STRINGS ${f} line LIMIT_COUNT 1 REGEX "^// cflags:")
|
||||
if(line)
|
||||
string(REGEX REPLACE "^// cflags:[ ]*" "" extra "${line}")
|
||||
separate_arguments(extra_list NATIVE_COMMAND "${extra}")
|
||||
set_source_files_properties(${f} PROPERTIES COMPILE_OPTIONS "${extra_list}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Reached from the DLL's own entry point; no C runtime start-up code is linked (the UCRT in the process is used)
|
||||
target_link_options(slinky PRIVATE /NODEFAULTLIB /ENTRY:DllMain /SUBSYSTEM:WINDOWS /OPT:NOREF /OPT:NOICF /ERRORLIMIT:0 /MAP)
|
||||
@@ -0,0 +1,130 @@
|
||||
<p align="center">
|
||||
<img src="assets/ss.png" alt="Open Slinky Banner" width="350" />
|
||||
<br>
|
||||
<font size="6"><b>Open Slinky</b></font>
|
||||
<br>
|
||||
<font size="3">by Skid Solutions</font>
|
||||
<br>
|
||||
<font size="2">credits to cat;
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/AEwatvnvSf">https://discord.gg/AEwatvnvSf</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/jak.jpg" alt="java hooks" height="150" />
|
||||
<img src="assets/hack.jpg" alt="unload" height="150" />
|
||||
<img src="assets/deobf2.png" alt="symbol recovery" height="150" />
|
||||
<img src="assets/deobf.jpg" alt="readable names" height="150" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/proof.png" alt="build proof" width="500" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
Open Slinky is a readable C++ reconstruction of the Slinky client for Minecraft
|
||||
1.8.9. The client is injected into the game process as a native DLL; it hooks the
|
||||
game at the Java bytecode level through JVMTI, dispatches events to the module
|
||||
system, and renders its interface through NanoVG.
|
||||
|
||||
This edition is cleaned up for development:
|
||||
|
||||
- address-based file and function names have been replaced with descriptive
|
||||
ones; the original symbols are recorded in `docs/symbol-map.json` (functions)
|
||||
and `docs/global-symbol-map.json` (`data_<rva>` / `rdata_<rva>` globals and
|
||||
the `*_part` event handlers), and inferred names are listed in
|
||||
`docs/renames.md`;
|
||||
- assembly used only for byte-exact code generation has been removed; the
|
||||
remaining `asm` tokens are symbol aliases, not instructions;
|
||||
- the client's data tables remain in `src/data` because the code links against
|
||||
them (fonts, strings, module descriptors).
|
||||
|
||||
---
|
||||
|
||||
## How to build
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **LLVM** — clang-cl, lld-link, llvm-rc and llvm-mt on `PATH`
|
||||
- **CMake** 3.20 or newer
|
||||
- **Ninja**
|
||||
- **Windows x64**
|
||||
- **Java Development Kit 8** — runtime for Minecraft 1.8.9; not required to
|
||||
compile the DLL
|
||||
|
||||
### 1. Configure
|
||||
|
||||
```powershell
|
||||
cmake -S . -B build -G Ninja `
|
||||
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-windows-clang-cl.cmake
|
||||
```
|
||||
|
||||
If Ninja is not on `PATH`, add
|
||||
`-DCMAKE_MAKE_PROGRAM=C:/path/to/ninja.exe`.
|
||||
|
||||
### 2. Build
|
||||
|
||||
```powershell
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
- `build/slinky.dll`
|
||||
|
||||
### Running
|
||||
|
||||
Inject `slinky.dll` into the running Minecraft 1.8.9 process with
|
||||
`LoadLibrary`. Start-up runs on its own thread; the client interface is opened
|
||||
with its GUI key. The last interface tab unloads the client: it saves the
|
||||
settings, removes the Java hooks and frees the library.
|
||||
|
||||
---
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
src/engine/ hooks, JNI/JVMTI, GUI, module manager, math, shared services
|
||||
src/logic/ per-module behaviour (.cpp) and its descriptor callbacks / settings page (.h)
|
||||
src/platform/ Windows layer, GL loader, NanoVG integration
|
||||
src/data/ data tables (fonts, strings, descriptors) - required to link
|
||||
include/slinky/ public headers
|
||||
docs/ design notes and the symbol map
|
||||
third_party/ NanoVG and GL headers
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Start-up, teardown, the window hook and the unload thread were virtualised in
|
||||
the original image and are reconstructed in `src/engine/protected/`.
|
||||
- `docs/symbol-map.json` maps every original `fn_<rva>`, `logic_<rva>`,
|
||||
`cb_<rva>`, `reg_<rva>` and `aux_<rva>` symbol to its readable name.
|
||||
- Settings and profiles are stored in `~/.slinky` (`settings`, `profiles/`).
|
||||
- `src/data/*.s` and `src/platform/win/chkstk.s` are data/compiler-support
|
||||
sections, not client code, this is 1 to 1 logic but may have some issues that we didnt find.
|
||||
|
||||
## Credits
|
||||
|
||||
- Original client: Slinky
|
||||
- Reconstruction: Claude (VMProtect devirtualization), DeepSeek (decryption),
|
||||
GLM and Claude (symbol recovery)
|
||||
|
||||
## Legal & disclaimers
|
||||
|
||||
- **Affiliation:** This is an independent development. It is **NOT AN OFFICIAL
|
||||
SLINKY PRODUCT**, is not approved by or associated with the original authors,
|
||||
and is not affiliated with Mojang or Zelix Pty Ltd.
|
||||
- **Assets & code:** No un-obfuscated original source code is distributed
|
||||
directly within this repository. The `src/data` tables are the client's own
|
||||
data required for linking; all third-party assets and code belong to their
|
||||
respective owners.
|
||||
- **Purpose:** research and preservation. Use at your own risk.
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# Compile every module and verify it against the original slinky2 image.
|
||||
# Original image path: tools/paths.py (override with SLINKY_ORIG=...).
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
python3 tools/verify.py
|
||||
python3 tools/verify_logic.py
|
||||
python3 tools/verify_engine.py --quiet
|
||||
for f in src/engine/protected/*.cpp; do
|
||||
clang-cl --target=x86_64-pc-windows-msvc -O2 -Iinclude -c "$f" -o "build/obj/protected_$(basename "$f" .cpp).obj"
|
||||
done
|
||||
echo "protected (readable, compile-checked): $(ls src/engine/protected/*.cpp | wc -l) files"
|
||||
for f in src/engine/lib/*.cpp; do
|
||||
clang-cl --target=x86_64-pc-windows-msvc -O2 -Iinclude -c "$f" -o "build/obj/lib_$(basename "$f" .cpp).obj"
|
||||
done
|
||||
echo "lib (translated from the listing, compile-checked): $(ls src/engine/lib/*.cpp | wc -l) files"
|
||||
echo "full DLL: cmake -S . -B build/cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-windows-clang-cl.cmake && cmake --build build/cmake"
|
||||
python3 tools/verify_modules.py
|
||||
python3 tools/report_modules.py
|
||||
@@ -0,0 +1,29 @@
|
||||
LIBRARY kernel32.dll
|
||||
EXPORTS
|
||||
CloseHandle
|
||||
CreateDirectoryW
|
||||
CreateFileW
|
||||
CreateThread
|
||||
DeleteFileW
|
||||
DisableThreadLibraryCalls
|
||||
FindClose
|
||||
FindFirstFileW
|
||||
FindNextFileW
|
||||
FreeLibraryAndExitThread
|
||||
GetCurrentProcessId
|
||||
GetFileSize
|
||||
GetLastError
|
||||
GetModuleHandleA
|
||||
GetProcAddress
|
||||
GetStdHandle
|
||||
GetSystemTimeAsFileTime
|
||||
GetTempPathW
|
||||
LoadLibraryA
|
||||
OutputDebugStringA
|
||||
QueryPerformanceCounter
|
||||
QueryPerformanceFrequency
|
||||
MultiByteToWideChar
|
||||
ReadFile
|
||||
Sleep
|
||||
WideCharToMultiByte
|
||||
WriteFile
|
||||
@@ -0,0 +1,3 @@
|
||||
LIBRARY shell32.dll
|
||||
EXPORTS
|
||||
ShellExecuteW
|
||||
@@ -0,0 +1,60 @@
|
||||
LIBRARY ucrtbase.dll
|
||||
EXPORTS
|
||||
__stdio_common_vsprintf
|
||||
__stdio_common_vsscanf
|
||||
__stdio_common_vswprintf
|
||||
_hypotf
|
||||
_register_onexit_function
|
||||
_time64
|
||||
_wgetenv
|
||||
abort
|
||||
acos
|
||||
acosf
|
||||
atan2
|
||||
atan2f
|
||||
bsearch
|
||||
calloc
|
||||
ceilf
|
||||
cos
|
||||
cosf
|
||||
expf
|
||||
fclose
|
||||
feof
|
||||
floor
|
||||
floorf
|
||||
fmod
|
||||
fmodf
|
||||
fopen
|
||||
fopen_s
|
||||
fread
|
||||
free
|
||||
fseek
|
||||
ftell
|
||||
hypot
|
||||
ldexp
|
||||
localeconv
|
||||
logf
|
||||
malloc
|
||||
memcpy
|
||||
memmove
|
||||
memset
|
||||
pow
|
||||
powf
|
||||
qsort
|
||||
realloc
|
||||
roundf
|
||||
sin
|
||||
sinf
|
||||
sqrt
|
||||
sqrtf
|
||||
strcmp
|
||||
strlen
|
||||
strncmp
|
||||
strncpy
|
||||
strtod
|
||||
strtol
|
||||
tanf
|
||||
tolower
|
||||
truncf
|
||||
wcslen
|
||||
wcsncpy_s
|
||||
@@ -0,0 +1,5 @@
|
||||
LIBRARY user32.dll
|
||||
EXPORTS
|
||||
GetForegroundWindow
|
||||
GetWindowThreadProcessId
|
||||
IsWindowUnicode
|
||||
@@ -0,0 +1,26 @@
|
||||
# Cross-compile the Windows DLL from Linux / macOS with clang-cl + lld-link.
|
||||
# No Windows SDK is needed: the code declares the few Win32 / UCRT functions it uses itself and links against
|
||||
# import libraries that CMakeLists.txt generates from cmake/imports/*.def (llvm-dlltool).
|
||||
#
|
||||
# cmake -S . -B build/cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-windows-clang-cl.cmake
|
||||
# cmake --build build/cmake -> build/cmake/slinky.dll
|
||||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
set(CMAKE_SYSTEM_PROCESSOR AMD64)
|
||||
set(CMAKE_C_COMPILER clang-cl)
|
||||
set(CMAKE_CXX_COMPILER clang-cl)
|
||||
set(CMAKE_C_COMPILER_TARGET x86_64-pc-windows-msvc)
|
||||
set(CMAKE_CXX_COMPILER_TARGET x86_64-pc-windows-msvc)
|
||||
set(CMAKE_LINKER lld-link)
|
||||
set(CMAKE_AR llvm-lib)
|
||||
set(CMAKE_RC_COMPILER llvm-rc)
|
||||
# the compiler check must not need the SDK's libraries
|
||||
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||
# the flags the engine code was verified with are set explicitly in CMakeLists.txt: drop CMake's MSVC defaults
|
||||
# (/EHsc, /GR, /MD, /O2 /Ob2 /DNDEBUG ...)
|
||||
set(CMAKE_C_FLAGS_INIT "")
|
||||
set(CMAKE_CXX_FLAGS_INIT "")
|
||||
set(CMAKE_C_FLAGS_RELEASE_INIT "")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE_INIT "")
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "")
|
||||
set(CMAKE_C_STANDARD_LIBRARIES "" CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_STANDARD_LIBRARIES "" CACHE STRING "" FORCE)
|
||||
@@ -0,0 +1,5 @@
|
||||
--target=x86_64-pc-windows-msvc
|
||||
-fms-extensions
|
||||
-fms-compatibility
|
||||
-Iinclude
|
||||
-std=c++17
|
||||
@@ -0,0 +1,55 @@
|
||||
# Aim Assist — extracted implementation
|
||||
|
||||
Aim Assist is the reference example for the logic layer. Its original
|
||||
implementation is in `src/logic/combat/aim_assist.cpp` (5,024 bytes, six
|
||||
functions), extracted byte-for-byte from `slinky2.decrypted.img`.
|
||||
|
||||
## Functions
|
||||
|
||||
| function | RVA | descriptor | what it does |
|
||||
|---|---:|---|---|
|
||||
| `aim_assist_frame` | `0x2b200` | `+0x58` | main tick: checks enabled state, acquires the local player/world, evaluates the selected mode and calls the rotation/target routines |
|
||||
| `aim_assist_disable` | `0x2b5b0` | `+0x98` | disable/reset path: clears the module's runtime state and calls the engine cleanup callback |
|
||||
| `aim_assist_on_rotation_update` | `0x2b610` | — | small state helper: sets the module's active flags |
|
||||
| `aim_assist_tick` | `0x2b630` | — | condition/target pass: checks the configured requirements (target, line of sight, weapon, etc.) and iterates candidate entities |
|
||||
| `aim_assist_apply_frame_rotation` | `0x2bf40` | — | rotation math: clamps yaw/pitch, applies smoothing, prediction and randomization, then applies the rotation |
|
||||
| `aim_assist_test_entity_updating` | `0x2c3b0` | — | target selection/validation: range/FOV checks, visibility, invisibility, hitbox correction and target sorting |
|
||||
|
||||
The body of each function in the source file is the exact original
|
||||
instruction stream. The disassembly above each block shows the same code with
|
||||
RIP-relative targets resolved, so the actual aim logic (target acquisition,
|
||||
angle clamping, interpolation, FOV/range checks, prediction) is visible rather
|
||||
than hidden behind an opaque stub.
|
||||
|
||||
In total the six functions contain 1,137 instructions and 101 calls. Every
|
||||
direct call targets another function in the client `.text` (the only indirect
|
||||
call is the engine cleanup callback in the disable path); none of the aim logic
|
||||
is stubbed out.
|
||||
|
||||
## How it is wired
|
||||
|
||||
The module descriptor for Aim Assist is at `0x2e8cc0`:
|
||||
|
||||
- `+0x00` → `"Aim Assist"`
|
||||
- `+0x08` → description
|
||||
- `+0x10` → category `0` (combat)
|
||||
- `+0x58` → `0x2b200` (tick)
|
||||
- `+0x98` → `0x2b5b0` (disable)
|
||||
|
||||
The settings block at `0x2e8eb8` starts with the three callbacks used by the
|
||||
settings layer (`aim_assist_gui`, `aim_assist_register`, `aim_assist_status`) followed by the config
|
||||
objects. The logic functions read those config objects directly, including the
|
||||
current-value fields at `+0x18` (bools) and `+0x30` (numbers/enums).
|
||||
|
||||
## Runtime requirement
|
||||
|
||||
The extracted code is the real client implementation, not a placeholder. It
|
||||
expects the original runtime context: the module manager calls `descriptor
|
||||
+0x58` with the module state, and the code uses the world/player pointers in
|
||||
that state plus the client helper functions in `.text`. The Aim Assist block
|
||||
therefore behaves exactly like the original inside the original client image.
|
||||
|
||||
This repository is a source reconstruction/verification tree, not a runnable
|
||||
Minecraft client. To execute the block outside the verifier, the host must
|
||||
provide the same engine/module-manager context (or the logic has to be ported
|
||||
to a high-level API against that context).
|
||||
@@ -0,0 +1,50 @@
|
||||
# Engine / module-manager notes
|
||||
|
||||
This is the result of looking for the remaining module logic in `.slinky0` and
|
||||
in the client's own dispatcher.
|
||||
|
||||
## The module manager is in client `.text`
|
||||
|
||||
The manager is not in `.slinky0`. The constructor is at `0x28000` in the
|
||||
client `.text`:
|
||||
|
||||
- it reads the descriptor table at `0x14f810`,
|
||||
- stores the module count `0x43` (67 descriptors),
|
||||
- groups modules by category (`descriptor+0x10`),
|
||||
- builds callback lists for the event slots at
|
||||
`descriptor +0x48, +0x58, …, +0x1F8`,
|
||||
- writes `[descriptor+0x14] = 1` when a module is enabled.
|
||||
|
||||
The dispatcher at `0x285d0` walks the enabled modules, collects the callback
|
||||
for the requested event slot, and calls it. This is why the descriptor
|
||||
callbacks are the primary module logic: a module without a callback at any of
|
||||
those slots has no client-side handler for that event.
|
||||
|
||||
## The six engine-only modules
|
||||
|
||||
The descriptors for these modules were checked through all event slots:
|
||||
|
||||
| module | client callback(s) | result |
|
||||
|---|---|---|
|
||||
| No Jump Delay | `0x35d50` (`+0x158`) | extracted |
|
||||
| Ping Fix | `0x3e6d0` (`+0x68`), `0x3e700` (`+0x78`) | extracted |
|
||||
| Instant Stop | `0x42650` (`+0x148`) | extracted |
|
||||
| Item Use Fix | `0x42b80` (`+0x128`), `0x42be0` (`+0x138`) | extracted |
|
||||
| Null Move | `0x46ac0` (`+0x148`) | extracted |
|
||||
| No Hurt Cam | none | no client callback |
|
||||
|
||||
`No Hurt Cam` also has no RIP-relative reference anywhere in the client
|
||||
`.text`, and its descriptor has no callback pointer in `+0x48 … +0x1F8`.
|
||||
There is therefore no client-native implementation to copy. Its effect must
|
||||
be applied by an engine/Java-side hook (or by code in the still-packed
|
||||
`.slinky0` tail); the decrypted clean engine region contains no module names,
|
||||
no descriptor references and no module-manager code.
|
||||
|
||||
## `.slinky0`
|
||||
|
||||
`.slinky0` is a raw native runtime blob, not a PE. The clean region contains
|
||||
native code/data (a PE loader, CRT/RTTI data, networking/OpenGL support), but
|
||||
none of the module names or the descriptor table. The module table is passed
|
||||
to it at runtime, and the module dispatch is done by the client manager above.
|
||||
The remaining engine tail (`slinky0+0xAB0000 …`) is still packed, so nothing
|
||||
can be copied from it statically without defeating that transform.
|
||||
File diff suppressed because it is too large.
Load diff
+108
@@ -0,0 +1,108 @@
|
||||
# Start-up, hooks and shutdown
|
||||
|
||||
How the client gets from "DLL loaded into the JVM" to "game calls into the modules", and back. Most of the original
|
||||
start-up is protector-virtualised, so it could not be translated; it is written as new code
|
||||
(`src/engine/protected/init.cpp`, `src/platform/win/entry.cpp`) from what the surrounding readable code expects.
|
||||
Facts below come from the image unless marked **inferred**. The licence check is not part of this client.
|
||||
|
||||
## What was found where
|
||||
|
||||
| Piece | Function | State |
|
||||
|---|---|---|
|
||||
| Class-hook installer | `install_class_hooks` | **real code**, was missed by the first gap scan. Rewritten readably (tables are data), checked against the original by running both (`tools/emu_install.py`) |
|
||||
| Class-hook remover | `remove_class_hooks` | real code, translated exactly |
|
||||
| `ClassFileLoadHook` + class rewriter | `class_file_load_hook`, `rewrite_class_file`, `rewrite_instruction`, ... | real code, translated |
|
||||
| `SlinkyChannelHandler.onPacket` native | `on_packet` | real code (also missed by the scan), translated |
|
||||
| Key handlers for profile / module binds | `on_profile_bind`, `on_module_bind`, `bind_profile_to_key` | real code (missed), translated |
|
||||
| `JNI_GetCreatedJavaVMs` lookup | `get_java_vm` | real code (missed), translated |
|
||||
| Hooks on/off | `set_java_hooks` | virtualised -> `init.cpp` (**inferred**: GetEnv JVMTI, install or remove, log failure) |
|
||||
| Start-up thread body | inside the original image's start-up routine at RVA 0x1000 | virtualised -> `slinky_setup` in `init.cpp` (**inferred** order) |
|
||||
| First-frame GUI setup | inside `hook_get_mouse_over` | virtualised -> `gui_init` in `protected/hooks.cpp` (**inferred**) |
|
||||
| Global teardown | DLL detach | virtualised -> `slinky_teardown` (**inferred**) |
|
||||
|
||||
The first gap scan only accepted a function that follows `0xcc/0x90/0xc3`, so functions after multi-byte NOP padding
|
||||
or directly after a tail `jmp` were skipped; 33 real functions (7 KB) were found by fixing that
|
||||
(`tools/extract_engine.py`). 43 protector stubs remain; `empty_placeholder` is still an empty placeholder (it has no callers).
|
||||
|
||||
## Entry (Windows)
|
||||
|
||||
`DllMain` (`src/platform/win/entry.cpp`) starts a thread on `DLL_PROCESS_ATTACH`; that thread runs `slinky_setup`.
|
||||
On failure it prints `Initialization failed: <code>` and starts the unload thread (`start_unload_thread`).
|
||||
|
||||
## `slinky_setup` (init.cpp)
|
||||
|
||||
1. `get_java_vm`: JavaVM from `jvm.dll` -> `init_javavm`; attach the thread.
|
||||
2. JNI ID table: `calloc(JIds)` -> `java_natives_world_cached_jni_ids`, `init_jni_ids` (game class loader, mapping detection, every class/method/field
|
||||
ID). Mapping: `ids->variant` 0 = 1.7.10, 1 = 1.8.9; `ids->names` 0 = obfuscated, 1 = MCP, 2 = SRG.
|
||||
3. Module manager: `create_module_manager(java_natives_world_module_manager, set_module_bind)` builds the lists from the static module list (67 descriptors);
|
||||
`set_module_bind` is the key-bind setter it stores. Immediately afterwards `save_module_states` runs the modules' settings
|
||||
visitors in "remember" mode, copying the static values from `.data` into each setting's saved slot. Those slots are what the
|
||||
built-in Default profile restores and what a profile with missing fields uses as its fallback; the image's static `.data`
|
||||
only contains the live values, so without this step both paths would reset settings to zero. This is part of the inferred
|
||||
start-up order: the original's start-up lives in protector-virtualised code, so the call is not visible in `.text`.
|
||||
4. State the frame and input code read: key table `java_natives_world_key_state_table_one_byte` (one byte per key), key map `java_natives_frame_profile_key_binds_key_profile`
|
||||
(profile binds, handler `on_profile_bind`, items are strings freed with `free`), key map `java_natives_frame_module_key_binds_key_module` (module binds,
|
||||
handler `on_module_bind`, several modules per key), profile list `init_profile_list` (`load_profiles` reads `~/.slinky/profiles`).
|
||||
5. Chat commands `init_chat_commands`, GLFW entry points `init_glfw_entrypoints` (absent with LWJGL 2, not an error).
|
||||
6. Saved settings `~/.slinky/settings` (**inferred**, mirror of `save_settings`): per-module state for global modules
|
||||
through `load_module_settings`, `profile_binds` through `bind_profile_to_key`.
|
||||
7. `input_input_handler_hooked_into_game = on_input_event`: the input handler the window hook (`hook_game_window`, installed lazily every frame) calls.
|
||||
8. Java side: `install_packet_channel` defines `SlinkyChannelHandler` (class bytes in the image) and registers `onPacket = on_packet`.
|
||||
9. `install_class_hooks`: define `SlinkyHooks13`, register its 23 natives, add the `can_retransform_classes` capability, set the
|
||||
`ClassFileLoadHook` callback, enable the event, build the hook records for the detected mapping, retransform the 9
|
||||
classes, disable the event again. Errors are `12000 + jvmtiError`.
|
||||
|
||||
The hooks go last: from the first retransformation on, the game calls the natives.
|
||||
|
||||
## First frame (`hook_get_mouse_over`, hooks.cpp)
|
||||
|
||||
The first `getMouseOver` runs `gui_init` once, on the render thread: GL entry points
|
||||
(`slinky_load_gl` + `wglGetProcAddress`), NanoVG context (`nvgCreateGL3`), window object `frame_render_target` with the two
|
||||
embedded fonts (Inter Medium = slot 0, Inter Bold = slot 1). NanoVG flags are **inferred** (antialias + stencil strokes).
|
||||
|
||||
## The class hooks
|
||||
|
||||
`SlinkyHooks13` (class file in the image, 1196 bytes) declares 23 `public static native` methods; their bodies are
|
||||
`hook_get_mouse_over .. hook_update_controller` (`kNatives` in `src/engine/jni/hook_tables.inc`, same order as the class file). The game classes are
|
||||
not detoured: through JVMTI `RetransformClasses` the `ClassFileLoadHook` (`class_file_load_hook`) rewrites matching call and field
|
||||
instructions into `invokestatic SlinkyHooks13.<native>`, so the receiver becomes the first `Object` argument.
|
||||
|
||||
What is rewritten is data: for each mapping (1.7.10 / 1.8.9 x obfuscated / MCP / SRG) the 9 classes
|
||||
(Minecraft, EntityRenderer, RenderManager, RendererLivingEntity, RenderPlayer, NetHandlerPlayClient, WorldClient, Entity,
|
||||
EntityLivingBase), per class the natives it may call and per method the instructions to replace
|
||||
(`src/engine/jni/hook_tables.inc`, generated by `tools/gen_hooks.py` from an emulated run of the original installer,
|
||||
`tools/dump_hooks.py`). 16 method patterns, 23 sites per mapping.
|
||||
|
||||
Rewritten call sites, by native: `getMouseOver` (EntityRenderer.getMouseOver in updateCameraAndRender),
|
||||
`updateDisplay` (Display.update), `clickMouse`/`rightClickMouse`/`updateEntities`/`updateController` (inside Minecraft.runTick
|
||||
and the input tick), `postWorldClear_1_7/_1_8` (glClear / GlStateManager.clear), `orientCamera`, `updateActiveRenderInfo`,
|
||||
`isDisplayActivePreRender`, `setRenderX/Y` (RenderManager fields), `canRenderName` (any method of RendererLivingEntity),
|
||||
`renderPlayer`, `unloadChunk`, `setBlock_1_7/_1_8`, `invalidateChunk`, `shouldClipLedge` (Entity.isSneaking), `moveFlying`,
|
||||
`sendPacket_1_7/_1_8`.
|
||||
|
||||
**Verification:** `tools/emu_install.py` loads the built `slinky.dll`, runs `install_class_hooks` under Unicorn for all six mappings and
|
||||
compares the records it builds with the original's: identical (9 classes, 16 patterns, 23 sites, same 23 natives).
|
||||
|
||||
## Packets
|
||||
|
||||
`SlinkyChannelHandler` (netty `ChannelDuplexHandler`, class bytes in the image) is put in the connection's pipeline as
|
||||
`slinky_handler` (`register_channel_class`, every frame). It queues packets and calls `onPacket(Object packet, boolean outbound,
|
||||
long time, int id)` = `on_packet`: under the lock `frame_spinlock`, and unless shutting down (`java_natives_frame_client_shutting_down_natives_check`) it dispatches module
|
||||
event 2 (inbound) or 3 (outbound) and returns the handlers' verdict.
|
||||
|
||||
## Shutdown
|
||||
|
||||
`hook_update_display` (the `updateDisplay` hook) unloads when the GUI's confirmation was given: saves settings, notifies and disables the
|
||||
modules, removes the netty handler, `set_java_hooks(env, 0)` (hooks off), `restore_game_window` (window hook off), then `start_unload_thread` starts a
|
||||
thread that waits one second, runs `slinky_teardown` and calls `FreeLibraryAndExitThread`.
|
||||
`slinky_teardown` (**inferred**) drops the netty class and the cached class references and frees the manager, both key maps,
|
||||
the profile list, the chat command table, the profile name, the mesh cache, the key table and the ID table.
|
||||
GL objects (NanoVG context, framebuffer) are not destroyed: that needs the render thread's context.
|
||||
|
||||
## Not verified / open
|
||||
|
||||
* The whole start-up order and the object sizes (`KeyMap` 0x30, key table 0x100, profile list 0x20) are inferred from their readers;
|
||||
nothing has run inside a game yet.
|
||||
* `empty_placeholder` (decrypts a short string, only reached through `reset_key_state`, which nothing calls): empty placeholder.
|
||||
* The NanoVG creation flags, JVMTI version (1.2) and the settings loader are inferred.
|
||||
* Linux: no platform layer yet (JVMTI agent entry, input hook, file paths, GL loader).
|
||||
+741
@@ -0,0 +1,741 @@
|
||||
# Logic layer by module
|
||||
|
||||
Generated by `tools/extract_logic.py`. Each entry lists the functions in
|
||||
`src/asm/<category>/<module>.cpp` that were identified as reading the
|
||||
module's settings object or stored in its descriptor. All functions are
|
||||
byte-for-byte copies of the original `.text` (verified by
|
||||
`tools/verify_logic.py`).
|
||||
|
||||
| module | category | file | functions | bytes |
|
||||
|---|---|---|---:|---:|
|
||||
| Aim Assist | combat | [aim_assist.cpp](../src/asm/combat/aim_assist.cpp) | 6 | 5040 |
|
||||
| Anti Bot | utility | [anti_bot.cpp](../src/asm/utility/anti_bot.cpp) | 2 | 1408 |
|
||||
| Anti Debuff | visual | [anti_debuff.cpp](../src/asm/visual/anti_debuff.cpp) | 1 | 128 |
|
||||
| Auto Block | combat | [auto_block.cpp](../src/asm/combat/auto_block.cpp) | 5 | 1600 |
|
||||
| Auto Clicker | combat | [auto_clicker.cpp](../src/asm/combat/auto_clicker.cpp) | 4 | 2272 |
|
||||
| Auto Pot | utility | [auto_pot.cpp](../src/asm/utility/auto_pot.cpp) | 4 | 1264 |
|
||||
| Auto Rod | utility | [auto_rod.cpp](../src/asm/utility/auto_rod.cpp) | 7 | 2624 |
|
||||
| Auto Soup | utility | [auto_soup.cpp](../src/asm/utility/auto_soup.cpp) | 4 | 1856 |
|
||||
| Auto Tool | block | [auto_tool.cpp](../src/asm/block/auto_tool.cpp) | 2 | 864 |
|
||||
| Auto Weapon | combat | [auto_weapon.cpp](../src/asm/combat/auto_weapon.cpp) | 1 | 320 |
|
||||
| Backtrack | combat | [backtrack.cpp](../src/asm/combat/backtrack.cpp) | 3 | 1904 |
|
||||
| Blink | utility | [blink.cpp](../src/asm/utility/blink.cpp) | 5 | 464 |
|
||||
| Block ESP | visual | [block_esp.cpp](../src/asm/visual/block_esp.cpp) | 5 | 1648 |
|
||||
| Block In | block | [block_in.cpp](../src/asm/block/block_in.cpp) | 5 | 4048 |
|
||||
| Bridge Assist | block | [bridge_assist.cpp](../src/asm/block/bridge_assist.cpp) | 5 | 2800 |
|
||||
| Chams | visual | [chams.cpp](../src/asm/visual/chams.cpp) | 1 | 336 |
|
||||
| Chest ESP | visual | [chest_esp.cpp](../src/asm/visual/chest_esp.cpp) | 2 | 976 |
|
||||
| Chest Stealer | utility | [chest_stealer.cpp](../src/asm/utility/chest_stealer.cpp) | 1 | 240 |
|
||||
| Clutch | block | [clutch.cpp](../src/asm/block/clutch.cpp) | 4 | 5904 |
|
||||
| Criticals | combat | [criticals.cpp](../src/asm/combat/criticals.cpp) | 4 | 1024 |
|
||||
| Debug | utility | [debug.cpp](../src/asm/utility/debug.cpp) | 4 | 800 |
|
||||
| Fake Lag | utility | [fake_lag.cpp](../src/asm/utility/fake_lag.cpp) | 2 | 560 |
|
||||
| Fast Accel | move | [fast_accel.cpp](../src/asm/move/fast_accel.cpp) | 1 | 608 |
|
||||
| Fast Mine | block | [fast_mine.cpp](../src/asm/block/fast_mine.cpp) | 2 | 400 |
|
||||
| Fast Place | block | [fast_place.cpp](../src/asm/block/fast_place.cpp) | 1 | 800 |
|
||||
| Free Look | visual | [free_look.cpp](../src/asm/visual/free_look.cpp) | 2 | 256 |
|
||||
| Friends | utility | [friends.cpp](../src/asm/utility/friends.cpp) | 4 | 960 |
|
||||
| GUI | visual | [gui.cpp](../src/asm/visual/gui.cpp) | 7 | 752 |
|
||||
| Ghost Hand | block | [ghost_hand.cpp](../src/asm/block/ghost_hand.cpp) | 2 | 304 |
|
||||
| Hit Select | combat | [hit_select.cpp](../src/asm/combat/hit_select.cpp) | 3 | 1264 |
|
||||
| Hitboxes | combat | [hitboxes.cpp](../src/asm/combat/hitboxes.cpp) | 1 | 32 |
|
||||
| Indicators | visual | [indicators.cpp](../src/asm/visual/indicators.cpp) | 3 | 2144 |
|
||||
| Inv Manager | utility | [inv_manager.cpp](../src/asm/utility/inv_manager.cpp) | 3 | 2288 |
|
||||
| Inv Walk | move | [inv_walk.cpp](../src/asm/move/inv_walk.cpp) | 4 | 1808 |
|
||||
| Item ESP | visual | [item_esp.cpp](../src/asm/visual/item_esp.cpp) | 3 | 2336 |
|
||||
| Keep Sprint | move | [keep_sprint.cpp](../src/asm/move/keep_sprint.cpp) | 3 | 416 |
|
||||
| Knockback Delay | combat | [knockback_delay.cpp](../src/asm/combat/knockback_delay.cpp) | 2 | 1008 |
|
||||
| Knockback Displace | combat | [knockback_displace.cpp](../src/asm/combat/knockback_displace.cpp) | 6 | 1136 |
|
||||
| Lag Range | combat | [lag_range.cpp](../src/asm/combat/lag_range.cpp) | 4 | 1856 |
|
||||
| Mod Overlay | visual | [mod_overlay.cpp](../src/asm/visual/mod_overlay.cpp) | 8 | 3216 |
|
||||
| Name Tags | visual | [name_tags.cpp](../src/asm/visual/name_tags.cpp) | 3 | 5024 |
|
||||
| No Hit Delay | utility | [no_hit_delay.cpp](../src/asm/utility/no_hit_delay.cpp) | 1 | 160 |
|
||||
| No Item Release | utility | [no_item_release.cpp](../src/asm/utility/no_item_release.cpp) | 1 | 256 |
|
||||
| No Slow | move | [no_slow.cpp](../src/asm/move/no_slow.cpp) | 1 | 160 |
|
||||
| No Use Delay | utility | [no_use_delay.cpp](../src/asm/utility/no_use_delay.cpp) | 1 | 256 |
|
||||
| Notifications | visual | [notifications.cpp](../src/asm/visual/notifications.cpp) | 6 | 2992 |
|
||||
| Piercing | combat | [piercing.cpp](../src/asm/combat/piercing.cpp) | 3 | 592 |
|
||||
| Player ESP | visual | [player_esp.cpp](../src/asm/visual/player_esp.cpp) | 3 | 3152 |
|
||||
| Pointers | visual | [pointers.cpp](../src/asm/visual/pointers.cpp) | 2 | 2128 |
|
||||
| Reach | combat | [reach.cpp](../src/asm/combat/reach.cpp) | 1 | 176 |
|
||||
| Refill | utility | [refill.cpp](../src/asm/utility/refill.cpp) | 3 | 2064 |
|
||||
| Safe Walk | move | [safe_walk.cpp](../src/asm/move/safe_walk.cpp) | 1 | 288 |
|
||||
| Sprint | move | [sprint.cpp](../src/asm/move/sprint.cpp) | 2 | 368 |
|
||||
| Sprint Reset | combat | [sprint_reset.cpp](../src/asm/combat/sprint_reset.cpp) | 2 | 864 |
|
||||
| Strafe | move | [strafe.cpp](../src/asm/move/strafe.cpp) | 2 | 624 |
|
||||
| Teams | utility | [teams.cpp](../src/asm/utility/teams.cpp) | 3 | 720 |
|
||||
| Timer | utility | [timer.cpp](../src/asm/utility/timer.cpp) | 2 | 272 |
|
||||
| Timer Range | combat | [timer_range.cpp](../src/asm/combat/timer_range.cpp) | 5 | 3632 |
|
||||
| Trajectories | visual | [trajectories.cpp](../src/asm/visual/trajectories.cpp) | 3 | 3968 |
|
||||
| Velocity | combat | [velocity.cpp](../src/asm/combat/velocity.cpp) | 8 | 2512 |
|
||||
| Weapons | combat | [weapons.cpp](../src/asm/combat/weapons.cpp) | 4 | 640 |
|
||||
| No Jump Delay | move | [no_jump_delay.cpp](../src/asm/move/no_jump_delay.cpp) | 1 | 48 |
|
||||
| Ping Fix | utility | [ping_fix.cpp](../src/asm/utility/ping_fix.cpp) | 2 | 96 |
|
||||
| Instant Stop | move | [instant_stop.cpp](../src/asm/move/instant_stop.cpp) | 1 | 976 |
|
||||
| Item Use Fix | utility | [item_use_fix.cpp](../src/asm/utility/item_use_fix.cpp) | 2 | 176 |
|
||||
| Null Move | move | [null_move.cpp](../src/asm/move/null_move.cpp) | 1 | 592 |
|
||||
|
||||
## Functions
|
||||
|
||||
### Aim Assist
|
||||
|
||||
`src/asm/combat/aim_assist.cpp` - descriptor `0x2e8cc0`, settings block `0x2e8eb8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `aim_assist_frame` | `0x2b200` | 944 | descriptor +0x58 | `0x2e9020` config scalar, `0x2e9048` setting bool "silent_ignore_manual_aim" (current value +0x18) |
|
||||
| `aim_assist_disable` | `0x2b5b0` | 96 | descriptor +0x98 | |
|
||||
| `aim_assist_on_rotation_update` | `0x2b610` | 32 | descriptor +0x118 | `0x2e9068` setting bool "silent_move_correct" (current value +0x18) |
|
||||
| `aim_assist_tick` | `0x2b630` | 1200 | descriptor +0x168 | `0x2e9020` config scalar, `0x2e90d8` setting bool "require_sprint" (current value +0x18), `0x2e90f8` setting bool "require_mouse_pressed" (current value +0x18), `0x2e9118` setting bool "disable_on_block_break" (current value +0x18), ... |
|
||||
| `aim_assist_apply_frame_rotation` | `0x2bf40` | 1136 | settings logic | `0x2e8f50` setting num "hor_speed" (current value +0x30), `0x2e8f88` setting num "ver_speed" (current value +0x30), `0x2e8fa8` setting bool "lock_on_vertical" (current value +0x18), `0x2e8fc8` setting bool "require_mouse_moved" (current value +0x18), ... |
|
||||
| `aim_assist_test_entity_updating` | `0x2c3b0` | 1632 | settings logic | `0x2e9180` config scalar, `0x2e91c0` setting num "predict" (current value +0x30), `0x2e91f8` setting num "hor_hitbox_correct" (current value +0x30), `0x2e9230` setting num "ver_hitbox_correct" (current value +0x30), ... |
|
||||
|
||||
### Anti Bot
|
||||
|
||||
`src/asm/utility/anti_bot.cpp` - descriptor `0x2eacd0`, settings block `0x2eaec8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `anti_bot_on_tick` | `0x30e70` | 1184 | descriptor +0x48 | `0x2eaf10` setting num "min_age" (current value +0x30), `0x2eaf30` setting bool "always_stationary" (current value +0x18), `0x2eaf88` setting enum "invalid_uuid" (current value +0x30), `0x2eafb0` setting bool "sleeping" (current value +0x18), ... |
|
||||
| `anti_bot_on_toggle` | `0x31310` | 224 | descriptor +0x98 | |
|
||||
|
||||
### Anti Debuff
|
||||
|
||||
`src/asm/visual/anti_debuff.cpp` - descriptor `0x2f3860`, settings block `0x2f3a58`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `anti_debuff_on_late_tick` | `0x44940` | 128 | descriptor +0x168 | `0x2f3a88` setting bool "blindness" (current value +0x18), `0x2f3aa8` setting bool "nausea" (current value +0x18) |
|
||||
|
||||
### Auto Block
|
||||
|
||||
`src/asm/combat/auto_block.cpp` - descriptor `0x2eaff8`, settings block `0x2eb1f0`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `auto_block_on_tick` | `0x31570` | 1248 | descriptor +0x48 | `0x2eb238` setting num "max_hold_time" (current value +0x30), `0x2eb270` setting num "lag_chance" (current value +0x30), `0x2eb2a8` setting num "lag_duration" (current value +0x30), `0x2eb2e0` setting num "max_hurt_time" (current value +0x30), ... |
|
||||
| `auto_block_on_packet_sent` | `0x31a50` | 32 | descriptor +0x78 | |
|
||||
| `auto_block_on_toggle` | `0x31a70` | 144 | descriptor +0x98 | |
|
||||
| `auto_block_on_attack` | `0x31b00` | 96 | descriptor +0x128 | |
|
||||
| `auto_block_on_late_tick` | `0x31b60` | 80 | descriptor +0x168 | |
|
||||
|
||||
### Auto Clicker
|
||||
|
||||
`src/asm/combat/auto_clicker.cpp` - descriptor `0x2e9330`, settings block `0x2e9528`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `auto_clicker_tick` | `0x2ca10` | 128 | descriptor +0x48 | |
|
||||
| `auto_clicker_on_frame` | `0x2ca90` | 1712 | descriptor +0x58 | `0x2e9560` config scalar, `0x2e9580` setting bool "inventory_randomize" (current value +0x18), `0x2e95a0` setting bool "allow_block_break" (current value +0x18), `0x2e95c0` setting bool "require_weapon" (current value +0x18), ... |
|
||||
| `auto_clicker_on_player_update` | `0x2d140` | 80 | descriptor +0x158 | |
|
||||
| `auto_clicker_extra_burst_clicks` | `0x2d3b0` | 352 | settings logic | `0x2e96a8` setting bool "exhaust" (current value +0x18) |
|
||||
|
||||
### Auto Pot
|
||||
|
||||
`src/asm/utility/auto_pot.cpp` - descriptor `0x2ef128`, settings block `0x2ef320`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `auto_pot_on_tick` | `0x391c0` | 784 | descriptor +0x48 | `0x2ef350` setting bool "use_non_health" (current value +0x18) |
|
||||
| `auto_pot_on_input` | `0x394d0` | 80 | descriptor +0x88 | |
|
||||
| `auto_pot_on_toggle` | `0x39520` | 224 | descriptor +0x98 | `0x2ef3a8` setting bool "prevent_waste" (current value +0x18), `0x2ef3c8` setting bool "double_splash" (current value +0x18) |
|
||||
| `auto_pot_asm_r_pins` | `0x396e0` | 176 | settings logic | `0x2ef388` setting num "delay" (current value +0x30) |
|
||||
|
||||
### Auto Rod
|
||||
|
||||
`src/asm/utility/auto_rod.cpp` - descriptor `0x2f3520`, settings block `0x2f3718`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `auto_rod_on_tick` | `0x43d80` | 1472 | descriptor +0x48 | `0x2f3750` setting bool "require_not_mouse_over" (current value +0x18), `0x2f3770` setting bool "allow_throwables" (current value +0x18), `0x2f3790` setting bool "always_switch_to_weapon" (current value +0x18) |
|
||||
| `auto_rod_on_input` | `0x44340` | 80 | descriptor +0x88 | |
|
||||
| `auto_rod_on_toggle` | `0x44390` | 48 | descriptor +0x98 | |
|
||||
| `auto_rod_on_attack` | `0x443c0` | 96 | descriptor +0x128 | |
|
||||
| `auto_rod_randomised_throw_delay` | `0x445a0` | 192 | settings logic | `0x2f37c8` setting num "delay" (current value +0x30), `0x2f37e8` setting bool "randomize_delays" (current value +0x18) |
|
||||
| `auto_rod_ticks_bobber_needs` | `0x44660` | 512 | settings logic | `0x2f3820` setting num "retract_speed" (current value +0x30) |
|
||||
| `auto_rod_randomised_rod_pull` | `0x44860` | 224 | settings logic | `0x2f37e8` setting bool "randomize_delays" (current value +0x18), `0x2f3858` setting num "throw_speed" (current value +0x30) |
|
||||
|
||||
### Auto Soup
|
||||
|
||||
`src/asm/utility/auto_soup.cpp` - descriptor `0x2e96b0`, settings block `0x2e98a8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `auto_soup_on_tick` | `0x2d510` | 1488 | descriptor +0x48 | `0x2e98f0` config scalar, `0x2e9928` setting num "delay_before" (current value +0x30), `0x2e9960` config scalar, `0x2e9998` setting num "delay_between" (current value +0x30), ... |
|
||||
| `auto_soup_input_hook` | `0x2dae0` | 48 | descriptor +0x88 | |
|
||||
| `auto_soup_enable` | `0x2db10` | 64 | descriptor +0x98 | |
|
||||
| `auto_soup_asm_pins_keep` | `0x2dd50` | 256 | settings logic | `0x2e9a08` setting num "randomization" (current value +0x30) |
|
||||
|
||||
### Auto Tool
|
||||
|
||||
`src/asm/block/auto_tool.cpp` - descriptor `0x2ee158`, settings block `0x2ee350`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `auto_tool_on_inventory_tick` | `0x37180` | 816 | descriptor +0x1d8 | `0x2ee3a0` setting num "activation_time" (current value +0x30), `0x2ee3c0` setting bool "allow_using_fists" (current value +0x18), `0x2ee3e0` setting bool "allow_using_sword" (current value +0x18), `0x2ee400` setting bool "allow_using_tools" (current value +0x18), ... |
|
||||
| `auto_tool_on_shutdown` | `0x374b0` | 48 | descriptor +0x1e8 | |
|
||||
|
||||
### Auto Weapon
|
||||
|
||||
`src/asm/combat/auto_weapon.cpp` - descriptor `0x2f5af8`, settings block `0x2f5cf0`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `auto_weapon_on_inventory_tick` | `0x4a340` | 320 | descriptor +0x1d8 | `0x2f5d38` setting num "activation_time" (current value +0x30), `0x2f5d58` setting bool "require_mouse_pressed" (current value +0x18) |
|
||||
|
||||
### Backtrack
|
||||
|
||||
`src/asm/combat/backtrack.cpp` - descriptor `0x2edad0`, settings block `0x2edcc8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `backtrack_packet_hook_under` | `0x35fd0` | 320 | descriptor +0x68 | |
|
||||
| `backtrack_render_box_held` | `0x36110` | 464 | descriptor +0xa8 | `0x2edd10` setting num "indicator_line_width" (current value +0x30), `0x2edd50` setting bool "indicator_rotate" (current value +0x18), `0x2edd70` setting bool "indicator_filled" (current value +0x18) |
|
||||
| `backtrack_tick_decide_whether` | `0x362e0` | 1120 | descriptor +0x168 | `0x2edda8` setting num "max_hurt_time" (current value +0x30), `0x2ede30` config scalar, `0x2ede68` setting num "cooldown" (current value +0x30), `0x2ede88` setting bool "require_mouse_pressed" (current value +0x18), ... |
|
||||
|
||||
### Blink
|
||||
|
||||
`src/asm/utility/blink.cpp` - descriptor `0x2ec600`, settings block `0x2ec7f8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `blink_on_tick` | `0x33660` | 112 | descriptor +0x48 | `0x2ec828` setting bool "disable_on_attack" (current value +0x18) |
|
||||
| `blink_on_packet_received` | `0x336d0` | 128 | descriptor +0x68 | |
|
||||
| `blink_on_packet_sent` | `0x33750` | 32 | descriptor +0x78 | |
|
||||
| `blink_on_toggle` | `0x33770` | 80 | descriptor +0x98 | `0x2ec860` setting num "max_duration" (current value +0x30), `0x2ec8b8` current value of setting "direction" (+0x30) |
|
||||
| `blink_on_attack` | `0x337c0` | 112 | descriptor +0x128 | `0x2ec8e0` setting bool "disable_on_block_dig" (current value +0x18), `0x2ec900` setting bool "disable_on_block_interact" (current value +0x18) |
|
||||
|
||||
### Block ESP
|
||||
|
||||
`src/asm/visual/block_esp.cpp` - descriptor `0x2f1740`, settings block `0x2f1938`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `block_esp_on_tick` | `0x3f4c0` | 480 | descriptor +0x48 | |
|
||||
| `block_esp_on_toggle` | `0x3f6a0` | 272 | descriptor +0x98 | |
|
||||
| `block_esp_on_render_world` | `0x3f7b0` | 656 | descriptor +0xa8 | `0x2f19e8` setting num "line_width" (current value +0x30), `0x2f1a20` setting num "block_render_limit" (current value +0x30), `0x2f1a58` setting num "range" (current value +0x30), `0x2f1a78` config scalar |
|
||||
| `block_esp_on_world_change` | `0x3fa40` | 16 | descriptor +0x188 | |
|
||||
| `block_esp_on_shutdown` | `0x3fa50` | 224 | descriptor +0x1e8 | |
|
||||
|
||||
### Block In
|
||||
|
||||
`src/asm/block/block_in.cpp` - descriptor `0x2f6108`, settings block `0x2f6300`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `block_in_on_tick` | `0x4abf0` | 16 | descriptor +0x48 | |
|
||||
| `block_in_enable_remember_facing` | `0x4ac00` | 336 | descriptor +0x98 | |
|
||||
| `block_in_rotation_override_placement` | `0x4ad50` | 48 | descriptor +0x118 | |
|
||||
| `block_in_tick_while_placing` | `0x4ad80` | 208 | descriptor +0x148 | |
|
||||
| `block_in_tick` | `0x4ae50` | 3440 | descriptor +0x168 | `0x2f6330` setting bool "require_ground" (current value +0x18), `0x2f6368` setting num "speed" (current value +0x30) |
|
||||
|
||||
### Bridge Assist
|
||||
|
||||
`src/asm/block/bridge_assist.cpp` - descriptor `0x2e9ac0`, settings block `0x2e9cb8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `bridge_assist_disable` | `0x2de50` | 112 | descriptor +0x98 | |
|
||||
| `bridge_assist_place` | `0x2dec0` | 928 | descriptor +0x128 | |
|
||||
| `bridge_assist_tick` | `0x2e260` | 832 | descriptor +0x158 | `0x2e9ce8` setting bool "require_sneak" (current value +0x18), `0x2e9d08` setting bool "require_looking_down" (current value +0x18), `0x2e9d28` setting bool "require_not_forward" (current value +0x18), `0x2e9d78` setting enum "select_blocks" (current value +0x30), ... |
|
||||
| `bridge_assist_on_late_tick` | `0x2e5a0` | 48 | descriptor +0x168 | `0x2e9d78` setting enum "select_blocks" (current value +0x30) |
|
||||
| `bridge_assist_edge_check` | `0x2e7b0` | 880 | settings logic | `0x2e9df8` setting num "edge_offset" (current value +0x30), `0x2e9e18` setting bool "randomize" (current value +0x18) |
|
||||
|
||||
### Chams
|
||||
|
||||
`src/asm/visual/chams.cpp` - descriptor `0x2f0fe0`, settings block `0x2f11d8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `chams_render_living_entity` | `0x3e730` | 336 | settings logic | `0x2f1208` setting bool "hide_friendlies" (current value +0x18) |
|
||||
|
||||
### Chest ESP
|
||||
|
||||
`src/asm/visual/chest_esp.cpp` - descriptor `0x2ec908`, settings block `0x2ecb00`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `chest_esp_on_tick` | `0x33990` | 464 | descriptor +0x48 | `0x2ecb48` setting num "range" (current value +0x30), `0x2ecb68` config scalar, `0x2ecb88` config scalar, `0x2ecba8` config scalar, ... |
|
||||
| `chest_esp_on_render_world` | `0x33b60` | 512 | descriptor +0xa8 | `0x2ecc00` setting num "line_width" (current value +0x30), `0x2ecca0` setting bool "filled" (current value +0x18) |
|
||||
|
||||
### Chest Stealer
|
||||
|
||||
`src/asm/utility/chest_stealer.cpp` - descriptor `0x2f5288`, settings block `0x2f5480`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `chest_stealer_on_tick` | `0x48600` | 240 | descriptor +0x1d8 | `0x2f54c8` setting num "delay_after_open" (current value +0x30), `0x2f54e8` setting bool "require_vanilla_title" (current value +0x18) |
|
||||
|
||||
### Clutch
|
||||
|
||||
`src/asm/block/clutch.cpp` - descriptor `0x2f5580`, settings block `0x2f5778`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `clutch_tick` | `0x488f0` | 320 | descriptor +0x48 | `0x2f57c0` setting num "click_speed" (current value +0x30) |
|
||||
| `clutch_rotation_override_flag` | `0x48a30` | 32 | descriptor +0x118 | |
|
||||
| `clutch_on_late_tick` | `0x48a50` | 4528 | descriptor +0x168 | `0x2f57f8` setting num "snapback_duration" (current value +0x30), `0x2f5830` setting num "snapback_delay" (current value +0x30), `0x2f5868` setting num "randomization" (current value +0x30), `0x2f58a0` setting num "aim_speed" (current value +0x30), ... |
|
||||
| `clutch_pick_aim_target` | `0x49f40` | 1024 | settings logic | `0x2f5868` setting num "randomization" (current value +0x30), `0x2f5a78` setting bool "aim_multipoint" (current value +0x18) |
|
||||
|
||||
### Criticals
|
||||
|
||||
`src/asm/combat/criticals.cpp` - descriptor `0x2ec010`, settings block `0x2ec208`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `criticals_on_tick` | `0x32f10` | 368 | descriptor +0x48 | `0x2ec250` setting num "max_delay" (current value +0x30), `0x2ec288` setting num "chance" (current value +0x30), `0x2ec2a8` setting bool "require_mouse_pressed" (current value +0x18), `0x2ec2c8` setting bool "require_weapon" (current value +0x18), ... |
|
||||
| `criticals_on_frame` | `0x33080` | 288 | descriptor +0x58 | `0x2ec350` setting num "timer_speed" (current value +0x30), `0x2ec388` setting num "boost" (current value +0x30) |
|
||||
| `criticals_on_packet_sent` | `0x331a0` | 272 | descriptor +0x78 | |
|
||||
| `criticals_on_toggle` | `0x332b0` | 96 | descriptor +0x98 | |
|
||||
|
||||
### Debug
|
||||
|
||||
`src/asm/utility/debug.cpp` - descriptor `0x2f4430`, settings block `0x2f4628`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `debug_on_tick` | `0x466e0` | 368 | descriptor +0x48 | `0x2f4658` setting bool "hurt_time" (current value +0x18), `0x2f4678` setting bool "item_use_state" (current value +0x18) |
|
||||
| `debug_on_packet_sent` | `0x46850` | 112 | descriptor +0x78 | |
|
||||
| `debug_on_attack_post` | `0x468c0` | 64 | descriptor +0x138 | `0x2f4698` setting bool "clicks" (current value +0x18) |
|
||||
| `debug_on_late_tick` | `0x46900` | 256 | descriptor +0x168 | `0x2f4698` setting bool "clicks" (current value +0x18) |
|
||||
|
||||
### Fake Lag
|
||||
|
||||
`src/asm/utility/fake_lag.cpp` - descriptor `0x2ebcc0`, settings block `0x2ebeb8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `fake_lag_on_tick` | `0x32b30` | 384 | descriptor +0x48 | `0x2ebf00` setting num "inbound" (current value +0x30), `0x2ebf38` setting num "outbound" (current value +0x30), `0x2ebf58` setting bool "realtime_damage" (current value +0x18), `0x2ebfa0` current value of setting "mode" (+0x30), ... |
|
||||
| `fake_lag_on_packet_sent` | `0x32cb0` | 176 | descriptor +0x78 | |
|
||||
|
||||
### Fast Accel
|
||||
|
||||
`src/asm/move/fast_accel.cpp` - descriptor `0x2f27f8`, settings block `0x2f29f0`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `fast_accel_on_move_flying` | `0x42320` | 608 | descriptor +0x1a8 | `0x2f2a38` setting num "acceleration" (current value +0x30), `0x2f2a58` setting bool "require_weapon" (current value +0x18), `0x2f2a78` setting bool "require_not_damaged" (current value +0x18) |
|
||||
|
||||
### Fast Mine
|
||||
|
||||
`src/asm/block/fast_mine.cpp` - descriptor `0x2f1d90`, settings block `0x2f1f88`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `fast_mine_on_tick` | `0x41a90` | 160 | descriptor +0x48 | `0x2f1fd0` setting num "break_delay" (current value +0x30), `0x2f1ff0` setting bool "decr_block_hit_delay" (current value +0x18) |
|
||||
| `fast_mine_on_late_tick` | `0x41b30` | 240 | descriptor +0x168 | `0x2f2028` config scalar |
|
||||
|
||||
### Fast Place
|
||||
|
||||
`src/asm/block/fast_place.cpp` - descriptor `0x2ea2d0`, settings block `0x2ea4c8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `fast_place_tick` | `0x2fbc0` | 800 | descriptor +0x48 | `0x2ea510` config scalar, `0x2ea548` setting num "activate_delay" (current value +0x30) |
|
||||
|
||||
### Free Look
|
||||
|
||||
`src/asm/visual/free_look.cpp` - descriptor `0x2edef0`, settings block `0x2ee0e8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `free_look_on_module_toggle` | `0x37020` | 224 | descriptor +0x98 | `0x2ee148` setting enum "target_perspective" (current value +0x30) |
|
||||
| `free_look_on_rotation_update` | `0x37100` | 32 | descriptor +0x118 | |
|
||||
|
||||
### Friends
|
||||
|
||||
`src/asm/utility/friends.cpp` - descriptor `0x2f0038`, settings block `0x2f0230`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `friends_on_middle_click_add` | `0x3b200` | 384 | settings logic | `0x2f0260` setting bool "middle_click_add" (current value +0x18) |
|
||||
| `friends_on_tick` | `0x3b3c0` | 336 | descriptor +0x48 | |
|
||||
| `friends_on_toggle` | `0x3b510` | 192 | descriptor +0x98 | |
|
||||
| `friends_on_shutdown` | `0x3b5d0` | 48 | descriptor +0x1e8 | |
|
||||
|
||||
### GUI
|
||||
|
||||
`src/asm/visual/gui.cpp` - descriptor `0x2eb740`, settings block `0x2eb938`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `gui_get_scale` | `0x32490` | 32 | settings logic | `0x2eb9a0` setting enum "scale" (current value +0x30) |
|
||||
| `gui_handle_input_event` | `0x324d0` | 192 | settings logic | `0x2eb9c8` setting bool "allow_input" (current value +0x18) |
|
||||
| `gui_on_tick` | `0x325c0` | 64 | descriptor +0x48 | |
|
||||
| `gui_on_toggle` | `0x32600` | 304 | descriptor +0x98 | |
|
||||
| `gui_on_hud_tick` | `0x32730` | 16 | descriptor +0xd8 | |
|
||||
| `gui_on_hud_render` | `0x32740` | 128 | descriptor +0xe8 | `0x2eb9e8` setting bool "compact" (current value +0x18), `0x2eba08` setting bool "wide" (current value +0x18) |
|
||||
| `gui_on_shutdown` | `0x327c0` | 16 | descriptor +0x1e8 | |
|
||||
|
||||
### Ghost Hand
|
||||
|
||||
`src/asm/block/ghost_hand.cpp` - descriptor `0x2f48b0`, settings block `0x2f4aa8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `ghost_hand_should_ignore_entity` | `0x46d10` | 288 | settings logic | `0x2f4ad8` setting bool "require_mouse_pressed" (current value +0x18), `0x2f4af8` setting bool "allow_using_fists" (current value +0x18), `0x2f4b18` setting bool "allow_using_sword" (current value +0x18), `0x2f4b38` setting bool "allow_using_tools" (current value +0x18), ... |
|
||||
| `ghost_hand_on_reach_query` | `0x46e30` | 16 | descriptor +0xc8 | |
|
||||
|
||||
### Hit Select
|
||||
|
||||
`src/asm/combat/hit_select.cpp` - descriptor `0x2ee4f0`, settings block `0x2ee6e8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `hit_select_on_tick` | `0x376c0` | 80 | descriptor +0x48 | |
|
||||
| `hit_select_on_attack` | `0x37710` | 976 | descriptor +0x128 | `0x2ee730` setting num "cancel_rate_air" (current value +0x30), `0x2ee768` setting num "pause_dur" (current value +0x30), `0x2ee7a0` setting num "wait_for_damage_dur" (current value +0x30), `0x2ee7d8` setting num "trades_delay" (current value +0x30), ... |
|
||||
| `hit_select_on_player_update` | `0x37ae0` | 208 | descriptor +0x158 | `0x2ee8c8` setting bool "use_server_time" (current value +0x18) |
|
||||
|
||||
### Hitboxes
|
||||
|
||||
`src/asm/combat/hitboxes.cpp` - descriptor `0x2ecca8`, settings block `0x2ecea0`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `hitboxes_on_reach_query` | `0x33fc0` | 32 | descriptor +0xc8 | `0x2ecee8` config scalar |
|
||||
|
||||
### Indicators
|
||||
|
||||
`src/asm/visual/indicators.cpp` - descriptor `0x2f1440`, settings block `0x2f1638`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `indicators_disable` | `0x3ea40` | 144 | descriptor +0x98 | |
|
||||
| `indicators_render` | `0x3ead0` | 1104 | descriptor +0xb8 | `0x2f1680` setting num "scale" (current value +0x30), `0x2f16b8` setting num "radius" (current value +0x30) |
|
||||
| `indicators_tick` | `0x3ef20` | 896 | descriptor +0x168 | `0x2f16d8` setting bool "show_arrow" (current value +0x18), `0x2f16f8` setting bool "show_fireball" (current value +0x18), `0x2f1718` setting bool "show_pearl" (current value +0x18), `0x2f1738` setting bool "require_coming_closer" (current value +0x18) |
|
||||
|
||||
### Inv Manager
|
||||
|
||||
`src/asm/utility/inv_manager.cpp` - descriptor `0x2f4c40`, settings block `0x2f4e38`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `inv_manager_tick` | `0x47090` | 1232 | descriptor +0x1d8 | `0x2f4e80` setting num "delay_after_open" (current value +0x30), `0x2f5228` setting bool "randomize" (current value +0x18), `0x2f5260` setting num "speed" (current value +0x30), `0x2f5280` setting bool "smart_speed" (current value +0x18) |
|
||||
| `inv_manager_plan_step` | `0x479c0` | 816 | settings logic | `0x2f4fb0` config scalar |
|
||||
| `inv_manager_score_item_stack` | `0x47f90` | 240 | settings logic | `0x2f5208` setting bool "sort_hotbar_fallback" (current value +0x18) |
|
||||
|
||||
### Inv Walk
|
||||
|
||||
`src/asm/move/inv_walk.cpp` - descriptor `0x2ef3d0`, settings block `0x2ef5c8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `inv_walk_on_toggle` | `0x39790` | 48 | descriptor +0x98 | |
|
||||
| `inv_walk_tick_keep_movement` | `0x397c0` | 1312 | descriptor +0x158 | `0x2ef620` config scalar, `0x2ef678` config scalar, `0x2ef6b8` setting num "stop_duration" (current value +0x30), `0x2ef6f0` setting enum "stop_non_inv" (current value +0x30) |
|
||||
| `inv_walk_on_gui_screen` | `0x39ce0` | 336 | descriptor +0x1b8 | `0x2ef620` config scalar |
|
||||
| `inv_walk_on_inventory_tick` | `0x39e30` | 112 | descriptor +0x1d8 | |
|
||||
|
||||
### Item ESP
|
||||
|
||||
`src/asm/visual/item_esp.cpp` - descriptor `0x2f1a80`, settings block `0x2f1c78`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `item_esp_render` | `0x40de0` | 1136 | descriptor +0xa8 | `0x2f1ca8` setting bool "show_count" (current value +0x18) |
|
||||
| `item_esp_tick` | `0x41250` | 1136 | descriptor +0x168 | `0x2f1ca8` setting bool "show_count" (current value +0x18), `0x2f1ce0` setting num "range" (current value +0x30), `0x2f1ce8` config scalar, `0x2f1d20` config scalar |
|
||||
| `item_esp_shutdown` | `0x416c0` | 64 | descriptor +0x1e8 | |
|
||||
|
||||
### Keep Sprint
|
||||
|
||||
`src/asm/move/keep_sprint.cpp` - descriptor `0x2ef700`, settings block `0x2ef8f8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `keep_sprint_on_attack` | `0x3a0f0` | 128 | descriptor +0x128 | `0x2ef940` setting num "chance" (current value +0x30) |
|
||||
| `keep_sprint_on_attack_post` | `0x3a170` | 240 | descriptor +0x138 | `0x2ef978` config scalar, `0x2ef998` setting bool "require_on_ground" (current value +0x18), `0x2ef9b8` setting bool "allow_reduce" (current value +0x18) |
|
||||
| `keep_sprint_on_player_update` | `0x3a260` | 48 | descriptor +0x158 | |
|
||||
|
||||
### Knockback Delay
|
||||
|
||||
`src/asm/combat/knockback_delay.cpp` - descriptor `0x2f40f8`, settings block `0x2f42f0`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `knockback_delay_on_packet_received` | `0x46140` | 304 | descriptor +0x68 | |
|
||||
| `knockback_delay_on_late_tick` | `0x46270` | 704 | descriptor +0x168 | `0x2f4338` config scalar, `0x2f4358` setting bool "require_air" (current value +0x18), `0x2f4390` setting num "range" (current value +0x30), `0x2f43b0` setting bool "require_weapon" (current value +0x18), ... |
|
||||
|
||||
### Knockback Displace
|
||||
|
||||
`src/asm/combat/knockback_displace.cpp` - descriptor `0x2f5d60`, settings block `0x2f5f58`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `knockback_displace_on_packet_sent` | `0x4a520` | 32 | descriptor +0x78 | |
|
||||
| `knockback_displace_on_rotation_update` | `0x4a540` | 32 | descriptor +0x118 | |
|
||||
| `knockback_displace_on_attack` | `0x4a560` | 624 | descriptor +0x128 | `0x2f5fa0` setting num "cooldown" (current value +0x30), `0x2f5fd8` config scalar, `0x2f5ff8` setting bool "require_no_kb" (current value +0x18), `0x2f6018` setting bool "require_weapon" (current value +0x18), ... |
|
||||
| `knockback_displace_on_attack_post` | `0x4a7d0` | 48 | descriptor +0x138 | |
|
||||
| `knockback_displace_on_late_tick` | `0x4a800` | 304 | descriptor +0x168 | `0x2f60e0` setting bool "hide_rotation" (current value +0x18), `0x2f6100` setting bool "use_last_strafe" (current value +0x18) |
|
||||
| `knockback_displace_direction` | `0x4ab90` | 96 | settings logic | `0x2f60b8` config scalar |
|
||||
|
||||
### Lag Range
|
||||
|
||||
`src/asm/combat/lag_range.cpp` - descriptor `0x2ef9c0`, settings block `0x2efbb8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `lag_range_packet_hook_hold` | `0x3a3c0` | 592 | descriptor +0x78 | |
|
||||
| `lag_range_render_box_interpolated` | `0x3a610` | 368 | descriptor +0xa8 | `0x2efc00` setting num "indicator_line_width" (current value +0x30), `0x2efc40` setting bool "indicator_filled" (current value +0x18) |
|
||||
| `lag_range_attack_hook` | `0x3a780` | 80 | descriptor +0x138 | |
|
||||
| `lag_range_tick_decide_whether` | `0x3a7d0` | 816 | descriptor +0x168 | `0x2efc78` config scalar, `0x2efc98` setting bool "require_weapon" (current value +0x18), `0x2efcb8` setting bool "flush_splash_potion" (current value +0x18), `0x2efcd8` setting bool "flush_sprint_reset" (current value +0x18), ... |
|
||||
|
||||
### Mod Overlay
|
||||
|
||||
`src/asm/visual/mod_overlay.cpp` - descriptor `0x2ecef0`, settings block `0x2ed0e8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `mod_overlay_tick` | `0x34080` | 528 | descriptor +0x48 | `0x2ed118` setting bool "show_combat" (current value +0x18), `0x2ed138` setting bool "show_move" (current value +0x18), `0x2ed158` setting bool "show_block" (current value +0x18), `0x2ed178` setting bool "show_visual" (current value +0x18), ... |
|
||||
| `mod_overlay_module_toggled` | `0x34290` | 576 | descriptor +0x98 | `0x2ed118` setting bool "show_combat" (current value +0x18), `0x2ed138` setting bool "show_move" (current value +0x18), `0x2ed158` setting bool "show_block" (current value +0x18), `0x2ed178` setting bool "show_visual" (current value +0x18), ... |
|
||||
| `mod_overlay_on_hud_tick` | `0x344d0` | 64 | descriptor +0xd8 | |
|
||||
| `mod_overlay_render` | `0x34510` | 1248 | descriptor +0xe8 | `0x2ed230` setting num "text_size" (current value +0x30), `0x2ed268` setting key "name_color" (current value +0x30), `0x2ed2a8` setting num "bg_rounding" (current value +0x30), `0x2ed2e0` setting num "padding_x" (current value +0x30), ... |
|
||||
| `mod_overlay_on_profile_event` | `0x349f0` | 32 | descriptor +0xf8 | |
|
||||
| `mod_overlay_on_overlay_unload` | `0x34a10` | 48 | descriptor +0x1c8 | `0x2ed1b8` setting bool "require_bound" (current value +0x18) |
|
||||
| `mod_overlay_rebuild_visibility` | `0x34ce0` | 432 | settings logic | `0x2ed118` setting bool "show_combat" (current value +0x18), `0x2ed138` setting bool "show_move" (current value +0x18), `0x2ed158` setting bool "show_block" (current value +0x18), `0x2ed178` setting bool "show_visual" (current value +0x18), ... |
|
||||
| `mod_overlay_append_entry` | `0x34e90` | 288 | settings logic | `0x2ed1d8` setting bool "lowercase" (current value +0x18), `0x2ed1f8` setting bool "show_details" (current value +0x18) |
|
||||
|
||||
### Name Tags
|
||||
|
||||
`src/asm/visual/name_tags.cpp` - descriptor `0x2f02d0`, settings block `0x2f04c8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `name_tags_disable` | `0x3c210` | 112 | descriptor +0x98 | |
|
||||
| `name_tags_render` | `0x3c280` | 1952 | descriptor +0xb8 | `0x2f0510` setting num "scale" (current value +0x30), `0x2f0530` setting bool "distance_scaling" (current value +0x18), `0x2f0550` setting bool "background" (current value +0x18), `0x2f0570` setting bool "text_shadow" (current value +0x18), ... |
|
||||
| `name_tags_tick` | `0x3ca20` | 2960 | descriptor +0x168 | `0x2f05b0` setting bool "show_enchants" (current value +0x18), `0x2f05e8` setting num "range" (current value +0x30), `0x2f0608` setting bool "show_own_name_tag" (current value +0x18), `0x2f0628` setting bool "use_display_name" (current value +0x18), ... |
|
||||
|
||||
### No Hit Delay
|
||||
|
||||
`src/asm/utility/no_hit_delay.cpp` - descriptor `0x2f0b70`, settings block `0x2f0d68`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `no_hit_delay_on_attack` | `0x3e590` | 160 | descriptor +0x128 | `0x2f0dc0` current value of setting "mode" (+0x30) |
|
||||
|
||||
### No Item Release
|
||||
|
||||
`src/asm/utility/no_item_release.cpp` - descriptor `0x2ec390`, settings block `0x2ec588`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `no_item_release_on_packet_sent` | `0x334c0` | 256 | descriptor +0x78 | `0x2ec5e8` current value of setting "mode" (+0x30) |
|
||||
|
||||
### No Slow
|
||||
|
||||
`src/asm/move/no_slow.cpp` - descriptor `0x2f2c90`, settings block `0x2f2e88`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `no_slow_on_movement_input` | `0x42a20` | 160 | descriptor +0x198 | `0x2f2ed0` setting num "mult_bow" (current value +0x30), `0x2f2f08` setting num "mult_sword" (current value +0x30), `0x2f2f40` setting num "mult_consumables" (current value +0x30) |
|
||||
|
||||
### No Use Delay
|
||||
|
||||
`src/asm/utility/no_use_delay.cpp` - descriptor `0x2f1210`, settings block `0x2f1408`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `no_use_delay_on_late_tick` | `0x3e8e0` | 256 | descriptor +0x168 | `0x2f1438` setting bool "allow_desync" (current value +0x18) |
|
||||
|
||||
### Notifications
|
||||
|
||||
`src/asm/visual/notifications.cpp` - descriptor `0x2ed320`, settings block `0x2ed518`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `notifications_module_toggled_gui` | `0x35000` | 416 | descriptor +0x98 | `0x2ed548` setting bool "hide_bind_only" (current value +0x18), `0x2ed568` setting bool "hide_hold_notifs" (current value +0x18), `0x2ed588` setting bool "show_combat" (current value +0x18), `0x2ed5a8` setting bool "show_move" (current value +0x18), ... |
|
||||
| `notifications_tick_age_messages` | `0x351a0` | 784 | descriptor +0xd8 | `0x2ed628` setting bool "hide_ingame" (current value +0x18) |
|
||||
| `notifications_draw_slide_boxes` | `0x354b0` | 1200 | descriptor +0xe8 | `0x2ed628` setting bool "hide_ingame" (current value +0x18) |
|
||||
| `notifications_profile_loaded_saved` | `0x35960` | 208 | descriptor +0xf8 | |
|
||||
| `notifications_on_error_event` | `0x35a30` | 192 | descriptor +0x108 | |
|
||||
| `notifications_on_friend_event` | `0x35af0` | 192 | descriptor +0x178 | |
|
||||
|
||||
### Piercing
|
||||
|
||||
`src/asm/combat/piercing.cpp` - descriptor `0x2f3ab0`, settings block `0x2f3ca8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `piercing_decide_which_entity` | `0x44a50` | 496 | settings logic | `0x2f3cd8` setting bool "require_inside_bb" (current value +0x18), `0x2f3cf8` setting bool "require_weapon" (current value +0x18), `0x2f3d18` setting bool "non_players" (current value +0x18), `0x2f3d38` setting bool "friendlies" (current value +0x18), ... |
|
||||
| `piercing_should_pierce_blocks` | `0x44c40` | 80 | settings logic | `0x2f3cf8` setting bool "require_weapon" (current value +0x18), `0x2f3de8` setting bool "blocks" (current value +0x18) |
|
||||
| `piercing_on_reach_query` | `0x44c90` | 16 | descriptor +0xc8 | |
|
||||
|
||||
### Player ESP
|
||||
|
||||
`src/asm/visual/player_esp.cpp` - descriptor `0x2e9e80`, settings block `0x2ea078`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `player_esp_render_mode_1` | `0x2eb20` | 896 | descriptor +0xa8 | `0x2ea0c0` setting num "line_width" (current value +0x30), `0x2ea110` current value of setting "mode" (+0x30), `0x2ea138` config scalar, `0x2ea158` setting bool "hurt_effect" (current value +0x18), ... |
|
||||
| `player_esp_render_mode_0` | `0x2eea0` | 1360 | descriptor +0xb8 | `0x2ea0c0` setting num "line_width" (current value +0x30), `0x2ea110` current value of setting "mode" (+0x30), `0x2ea138` config scalar, `0x2ea158` setting bool "hurt_effect" (current value +0x18), ... |
|
||||
| `player_esp_tick` | `0x2f3f0` | 896 | descriptor +0x168 | `0x2ea1d0` setting num "range" (current value +0x30), `0x2ea208` setting key "color_target" (current value +0x30), `0x2ea228` setting key "color_friendlies" (current value +0x30), `0x2ea250` config scalar, ... |
|
||||
|
||||
### Pointers
|
||||
|
||||
`src/asm/visual/pointers.cpp` - descriptor `0x2f06c0`, settings block `0x2f08b8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `pointers_render` | `0x3da80` | 1168 | descriptor +0xb8 | `0x2f0950` setting num "scale" (current value +0x30), `0x2f0988` setting num "radius" (current value +0x30), `0x2f09c0` setting key "color_near" (current value +0x30), `0x2f0a10` current value of setting "style" (+0x30), ... |
|
||||
| `pointers_tick` | `0x3df10` | 960 | descriptor +0x168 | `0x2f0a88` config scalar, `0x2f0ac8` setting num "range" (current value +0x30), `0x2f0b00` setting num "ignore_fov" (current value +0x30), `0x2f0b38` setting key "color_friendlies" (current value +0x30), ... |
|
||||
|
||||
### Reach
|
||||
|
||||
`src/asm/combat/reach.cpp` - descriptor `0x2ea550`, settings block `0x2ea748`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `reach_on_reach_query` | `0x2ffb0` | 176 | descriptor +0xc8 | `0x2ea790` config scalar, `0x2ea7c8` setting num "activate_time" (current value +0x30), `0x2ea7e8` setting bool "require_sprint" (current value +0x18) |
|
||||
|
||||
### Refill
|
||||
|
||||
`src/asm/utility/refill.cpp` - descriptor `0x2eed90`, settings block `0x2eef88`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `refill_enable` | `0x384c0` | 48 | descriptor +0x98 | |
|
||||
| `refill_on_inventory_tick` | `0x384f0` | 1744 | descriptor +0x1d8 | `0x2eefd0` setting num "delay_before_close" (current value +0x30), `0x2ef008` setting num "delay_after_open" (current value +0x30), `0x2ef040` setting num "speed" (current value +0x30), `0x2ef0c0` setting bool "smart_speed" (current value +0x18), ... |
|
||||
| `refill_classify_27_main` | `0x38dc0` | 272 | settings logic | `0x2ef060` setting bool "refill_soup" (current value +0x18), `0x2ef080` config scalar, `0x2ef0a0` setting bool "refill_potion_non_health" (current value +0x18) |
|
||||
|
||||
### Safe Walk
|
||||
|
||||
`src/asm/move/safe_walk.cpp` - descriptor `0x2f2030`, settings block `0x2f2228`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `safe_walk_on_tick` | `0x41d30` | 288 | descriptor +0x48 | `0x2f2270` setting num "motion_multiplier" (current value +0x30), `0x2f2290` setting bool "require_looking_down" (current value +0x18), `0x2f22b0` setting bool "require_not_forward" (current value +0x18), `0x2f22d0` setting bool "require_holding_blocks" (current value +0x18) |
|
||||
|
||||
### Sprint
|
||||
|
||||
`src/asm/move/sprint.cpp` - descriptor `0x2ed840`, settings block `0x2eda38`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `sprint_on_player_update` | `0x35d80` | 176 | descriptor +0x158 | `0x2eda68` setting bool "while_in_gui" (current value +0x18) |
|
||||
| `sprint_on_movement_input` | `0x35e30` | 192 | descriptor +0x198 | `0x2eda88` setting bool "while_using_item" (current value +0x18), `0x2edaa8` setting bool "while_backwards" (current value +0x18), `0x2edac8` setting bool "while_sideways" (current value +0x18) |
|
||||
|
||||
### Sprint Reset
|
||||
|
||||
`src/asm/combat/sprint_reset.cpp` - descriptor `0x2efd00`, settings block `0x2efef8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `sprint_reset_on_player_update` | `0x3ad10` | 688 | descriptor +0x158 | `0x2eff98` setting num "delay" (current value +0x30), `0x2effb8` setting bool "randomize" (current value +0x18), `0x2effd8` setting bool "wait_for_damage" (current value +0x18), `0x2efff8` setting bool "require_weapon" (current value +0x18), ... |
|
||||
| `sprint_reset_press_key` | `0x3b150` | 176 | settings logic | `0x2eff58` current value of setting "mode" (+0x30) |
|
||||
|
||||
### Strafe
|
||||
|
||||
`src/asm/move/strafe.cpp` - descriptor `0x2f24e8`, settings block `0x2f26e0`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `strafe_on_movement_input` | `0x41f90` | 48 | descriptor +0x198 | |
|
||||
| `strafe_on_move_flying` | `0x41fc0` | 576 | descriptor +0x1a8 | `0x2f2728` setting num "max_hurt_time" (current value +0x30), `0x2f2760` setting num "strength_air" (current value +0x30), `0x2f2798` setting num "strength_jump" (current value +0x30), `0x2f27d0` setting num "strength_ground" (current value +0x30), ... |
|
||||
|
||||
### Teams
|
||||
|
||||
`src/asm/utility/teams.cpp` - descriptor `0x2eb400`, settings block `0x2eb5f8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `teams_on_tick` | `0x31e10` | 480 | descriptor +0x48 | `0x2eb628` setting bool "match_self_color" (current value +0x18) |
|
||||
| `teams_on_toggle` | `0x31ff0` | 192 | descriptor +0x98 | |
|
||||
| `teams_on_shutdown` | `0x320b0` | 48 | descriptor +0x1e8 | |
|
||||
|
||||
### Timer
|
||||
|
||||
`src/asm/utility/timer.cpp` - descriptor `0x2eba10`, settings block `0x2ebc08`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `timer_on_frame` | `0x328f0` | 192 | descriptor +0x58 | `0x2ebc50` config scalar, `0x2ebc70` setting bool "require_mouse" (current value +0x18), `0x2ebc90` setting bool "require_falling" (current value +0x18), `0x2ebcb0` setting bool "require_damage" (current value +0x18) |
|
||||
| `timer_on_toggle` | `0x329b0` | 80 | descriptor +0x98 | |
|
||||
|
||||
### Timer Range
|
||||
|
||||
`src/asm/combat/timer_range.cpp` - descriptor `0x2f3160`, settings block `0x2f3358`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `timer_range_tick` | `0x42c60` | 672 | descriptor +0x48 | `0x2f3388` setting bool "require_mouse_pressed" (current value +0x18), `0x2f33a8` setting bool "require_weapon" (current value +0x18), `0x2f33c8` setting bool "require_not_using_item" (current value +0x18), `0x2f3410` config scalar |
|
||||
| `timer_range_render_tick` | `0x42f00` | 2640 | descriptor +0x58 | `0x2f3410` config scalar, `0x2f3438` setting bool "block_while_frozen" (current value +0x18), `0x2f3470` setting num "cooldown" (current value +0x30), `0x2f3478` config scalar, ... |
|
||||
| `timer_range_on_packet_sent` | `0x43950` | 32 | descriptor +0x78 | |
|
||||
| `timer_range_on_timer_phase` | `0x43970` | 112 | descriptor +0x98 | |
|
||||
| `timer_range_hypot` | `0x43bf0` | 176 | settings logic | `0x2f3410` config scalar, `0x2f3438` setting bool "block_while_frozen" (current value +0x18) |
|
||||
|
||||
### Trajectories
|
||||
|
||||
`src/asm/visual/trajectories.cpp` - descriptor `0x2f3df0`, settings block `0x2f3fe8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `trajectories_disable` | `0x44e40` | 96 | descriptor +0x98 | |
|
||||
| `trajectories_render` | `0x44ea0` | 1232 | descriptor +0xa8 | `0x2f4030` setting num "line_width" (current value +0x30), `0x2f4068` setting key "color_hit" (current value +0x30) |
|
||||
| `trajectories_tick` | `0x45370` | 2640 | descriptor +0x168 | `0x2f4090` config scalar, `0x2f40b0` setting bool "render_uncharged_bow" (current value +0x18), `0x2f40d0` setting bool "render_pearls" (current value +0x18), `0x2f40f0` setting bool "render_throwables" (current value +0x18) |
|
||||
|
||||
### Velocity
|
||||
|
||||
`src/asm/combat/velocity.cpp` - descriptor `0x2ea7f0`, settings block `0x2ea9e8`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `velocity_mode_3_delayed` | `0x30180` | 336 | descriptor +0x48 | `0x2eaa50` config scalar, `0x2eaa90` setting num "ignore_chance" (current value +0x30), `0x2eab68` setting enum "ignore_vertical" (current value +0x30) |
|
||||
| `velocity_packet_hook_watch` | `0x302d0` | 208 | descriptor +0x68 | |
|
||||
| `velocity_disable` | `0x303a0` | 32 | descriptor +0x98 | |
|
||||
| `velocity_attack` | `0x303c0` | 80 | descriptor +0x128 | |
|
||||
| `velocity_mode_2_reduce` | `0x30410` | 272 | descriptor +0x138 | `0x2eaa50` config scalar, `0x2eaab0` setting bool "require_forward" (current value +0x18), `0x2eaaf0` setting bool "require_weapon" (current value +0x18), `0x2eab90` setting bool "reduce_multiple_per_tick" (current value +0x18) |
|
||||
| `velocity_tick_mode_0` | `0x30520` | 960 | descriptor +0x158 | `0x2eaa50` config scalar, `0x2eabc8` setting num "reduce_chance" (current value +0x30), `0x2eac00` setting num "reduce_delay" (current value +0x30), `0x2eac38` config scalar, ... |
|
||||
| `velocity_tick` | `0x308e0` | 80 | descriptor +0x168 | |
|
||||
| `velocity_shared_conditions` | `0x30c50` | 544 | settings logic | `0x2eaab0` setting bool "require_forward" (current value +0x18), `0x2eaad0` setting bool "require_mouse" (current value +0x18), `0x2eaaf0` setting bool "require_weapon" (current value +0x18), `0x2eab10` setting bool "require_target" (current value +0x18) |
|
||||
|
||||
### Weapons
|
||||
|
||||
`src/asm/combat/weapons.cpp` - descriptor `0x2ee930`, settings block `0x2eeb28`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `weapons_check_held_item_cached` | `0x37e50` | 272 | settings logic | `0x2eeb58` setting bool "fist" (current value +0x18), `0x2eeb60` config scalar, `0x2eec98` setting bool "enchant_sharpness" (current value +0x18), `0x2eecb8` setting bool "enchant_knockback" (current value +0x18), ... |
|
||||
| `weapons_is_item_allowed_by_class` | `0x38040` | 112 | settings logic | `0x2eeb58` setting bool "fist" (current value +0x18), `0x2eecf8` setting bool "axes" (current value +0x18), `0x2eed18` setting bool "swords" (current value +0x18) |
|
||||
| `weapons_find_first_weapon_slot` | `0x380c0` | 208 | settings logic | `0x2eeb78` setting bool (slot) (current value +0x18), `0x2eec98` setting bool "enchant_sharpness" (current value +0x18), `0x2eecb8` setting bool "enchant_knockback" (current value +0x18), `0x2eecd8` setting bool "enchant_fire_aspect" (current value +0x18) |
|
||||
| `weapons_on_shutdown` | `0x38190` | 48 | descriptor +0x1e8 | |
|
||||
|
||||
### No Jump Delay
|
||||
|
||||
`src/asm/move/no_jump_delay.cpp` - descriptor `0x2ed630`, settings block `None`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `no_jump_delay_on_tick` | `0x35d50` | 48 | descriptor +0x158 | |
|
||||
|
||||
### Ping Fix
|
||||
|
||||
`src/asm/utility/ping_fix.cpp` - descriptor `0x2f0dd0`, settings block `None`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `ping_fix_drop_incoming_keepalive` | `0x3e6d0` | 48 | descriptor +0x68 | |
|
||||
| `ping_fix_drop_outgoing_keepalive` | `0x3e700` | 48 | descriptor +0x78 | |
|
||||
|
||||
### Instant Stop
|
||||
|
||||
`src/asm/move/instant_stop.cpp` - descriptor `0x2f2a80`, settings block `None`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `instant_stop_on_tick` | `0x42650` | 976 | descriptor +0x148 | |
|
||||
|
||||
### Item Use Fix
|
||||
|
||||
`src/asm/utility/item_use_fix.cpp` - descriptor `0x2f2f48`, settings block `None`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `item_use_fix_clear_hitting_block` | `0x42b80` | 96 | descriptor +0x128 | |
|
||||
| `item_use_fix_restore_hitting_block` | `0x42be0` | 80 | descriptor +0x138 | |
|
||||
|
||||
### Null Move
|
||||
|
||||
`src/asm/move/null_move.cpp` - descriptor `0x2f46a0`, settings block `None`
|
||||
|
||||
| function | RVA | bytes | role | reads |
|
||||
|---|---:|---:|---|---|
|
||||
| `null_move_on_tick` | `0x46ac0` | 592 | descriptor +0x148 | |
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# Module completeness audit
|
||||
|
||||
Generated by `tools/report_modules.py` from `manifest.json`,
|
||||
`manifest_logic.json` and the decrypted image.
|
||||
|
||||
`settings` is declared config objects / registered `R_*` settings.
|
||||
`logic` is extracted functions / bytes. `descriptor` lists the
|
||||
function pointers stored in the module descriptor.
|
||||
|
||||
| module | cat | gui | settings | logic | descriptor | status |
|
||||
|---|---|---|---:|---:|---|---|
|
||||
| Aim Assist | combat | cb+reg+aux | 22 / 22 | 6 / 5040 | +0x58:0x2b200, +0x98:0x2b5b0, +0x118:0x2b610, +0x168:0x2b630, +0x1f8:0x2bae0 | complete |
|
||||
| Anti Bot | utility | cb+reg | 6 / 6 | 2 / 1408 | +0x48:0x30e70, +0x98:0x31310, +0x1f8:0x313f0 | complete |
|
||||
| Anti Debuff | visual | cb+reg | 2 / 2 | 1 / 128 | +0x168:0x44940, +0x1f8:0x449c0 | complete |
|
||||
| Auto Block | combat | cb+reg | 12 / 12 | 5 / 1600 | +0x48:0x31570, +0x78:0x31a50, +0x98:0x31a70, +0x128:0x31b00, +0x168:0x31b60, +0x1f8:0x31bb0 | complete |
|
||||
| Auto Clicker | combat | cb+reg+aux | 9 / 9 | 4 / 2272 | +0x48:0x2ca10, +0x58:0x2ca90, +0x158:0x2d140, +0x1f8:0x2d190 | complete |
|
||||
| Auto Pot | utility | cb+reg | 4 / 4 | 4 / 1264 | +0x48:0x391c0, +0x88:0x394d0, +0x98:0x39520, +0x1f8:0x39600 | complete |
|
||||
| Auto Rod | utility | cb+reg | 7 / 7 | 7 / 2624 | +0x48:0x43d80, +0x88:0x44340, +0x98:0x44390, +0x128:0x443c0, +0x1f8:0x44420 | complete |
|
||||
| Auto Soup | utility | cb+reg | 9 / 9 | 4 / 1856 | +0x48:0x2d510, +0x88:0x2dae0, +0x98:0x2db10, +0x1f8:0x2db50 | complete |
|
||||
| Auto Tool | block | cb+reg | 9 / 9 | 2 / 864 | +0x1d8:0x37180, +0x1e8:0x374b0, +0x1f8:0x374e0 | complete |
|
||||
| Auto Weapon | combat | cb+reg | 2 / 2 | 1 / 320 | +0x1d8:0x4a340, +0x1f8:0x4a480 | complete |
|
||||
| Backtrack | combat | cb+reg+aux | 12 / 12 | 3 / 1904 | +0x68:0x35fd0, +0xa8:0x36110, +0x168:0x362e0, +0x1f8:0x36740 | complete |
|
||||
| Blink | utility | cb+reg+aux | 5 / 5 | 5 / 464 | +0x48:0x33660, +0x68:0x336d0, +0x78:0x33750, +0x98:0x33770, +0x128:0x337c0, +0x1f8:0x33830 | complete |
|
||||
| Block ESP | visual | cb+reg | 5 / 5 | 5 / 1648 | +0x48:0x3f4c0, +0x98:0x3f6a0, +0xa8:0x3f7b0, +0x188:0x3fa40, +0x1e8:0x3fa50, +0x1f8:0x3fb30 | complete |
|
||||
| Block In | block | cb+reg | 2 / 2 | 5 / 4048 | +0x48:0x4abf0, +0x98:0x4ac00, +0x118:0x4ad50, +0x148:0x4ad80, +0x168:0x4ae50, +0x1f8:0x4bbc0 | complete |
|
||||
| Bridge Assist | block | cb+reg | 10 / 10 | 5 / 2800 | +0x98:0x2de50, +0x128:0x2dec0, +0x158:0x2e260, +0x168:0x2e5a0, +0x1f8:0x2e5d0 | complete |
|
||||
| Chams | visual | cb+reg | 1 / 1 | 1 / 336 | +0x1f8:0x3e880 | complete |
|
||||
| Chest ESP | visual | cb+reg | 11 / 11 | 2 / 976 | +0x48:0x33990, +0xa8:0x33b60, +0x1f8:0x33d60 | complete |
|
||||
| Chest Stealer | utility | cb+reg | 5 / 5 | 1 / 240 | +0x1d8:0x48600, +0x1f8:0x486f0 | complete |
|
||||
| Clutch | block | cb+reg | 18 / 18 | 4 / 5904 | +0x48:0x488f0, +0x118:0x48a30, +0x168:0x48a50, +0x1f8:0x49c00 | complete |
|
||||
| Criticals | combat | cb+reg+aux | 7 / 7 | 4 / 1024 | +0x48:0x32f10, +0x58:0x33080, +0x78:0x331a0, +0x98:0x332b0, +0x1f8:0x33310 | complete |
|
||||
| Debug | utility | cb+reg | 3 / 3 | 4 / 800 | +0x48:0x466e0, +0x78:0x46850, +0x138:0x468c0, +0x168:0x46900, +0x1f8:0x46a00 | complete |
|
||||
| Fake Lag | utility | cb+reg+aux | 7 / 7 | 2 / 560 | +0x48:0x32b30, +0x68:0x32cb0, +0x78:0x32cb0, +0x1f8:0x32d60 | complete |
|
||||
| Fast Accel | move | cb+reg | 3 / 3 | 1 / 608 | +0x1a8:0x42320, +0x1f8:0x42580 | complete |
|
||||
| Fast Mine | block | cb+reg+aux | 3 / 3 | 2 / 400 | +0x48:0x41a90, +0x168:0x41b30, +0x1f8:0x41c20 | complete |
|
||||
| Fast Place | block | cb+reg+aux | 2 / 2 | 1 / 800 | +0x48:0x2fbc0, +0x1f8:0x2fee0 | complete |
|
||||
| Free Look | visual | cb+reg | 1 / 1 | 2 / 256 | +0x98:0x37020, +0x118:0x37100, +0x1f8:0x37120 | complete |
|
||||
| Friends | utility | cb+reg | 2 / 2 | 4 / 960 | +0x48:0x3b3c0, +0x98:0x3b510, +0x1e8:0x3b5d0, +0x1f8:0x3b600 | complete |
|
||||
| GUI | visual | cb+reg | 5 / 5 | 7 / 752 | +0x48:0x325c0, +0x98:0x32600, +0xd8:0x32730, +0xe8:0x32740, +0x1e8:0x327c0, +0x1f8:0x327d0 | complete |
|
||||
| Ghost Hand | block | cb+reg | 12 / 12 | 2 / 304 | +0xc8:0x46e30, +0x1f8:0x46e40 | complete |
|
||||
| Hit Select | combat | cb+reg+aux | 12 / 12 | 3 / 1264 | +0x48:0x376c0, +0x128:0x37710, +0x158:0x37ae0, +0x1f8:0x37bb0 | complete |
|
||||
| Hitboxes | combat | cb+reg+aux | 1 / 1 | 1 / 32 | +0xc8:0x33fc0, +0x1f8:0x33fe0 | complete |
|
||||
| Indicators | visual | cb+reg | 6 / 6 | 3 / 2144 | +0x98:0x3ea40, +0xb8:0x3ead0, +0x168:0x3ef20, +0x1f8:0x3f2a0 | complete |
|
||||
| Inv Manager | utility | cb+reg | 16 / 16 | 3 / 2288 | +0x1d8:0x47090, +0x1f8:0x47560 | complete |
|
||||
| Inv Walk | move | cb+reg+aux | 4 / 4 | 4 / 1808 | +0x98:0x39790, +0x158:0x397c0, +0x1b8:0x39ce0, +0x1d8:0x39e30, +0x1f8:0x39ea0 | complete |
|
||||
| Item ESP | visual | cb+reg | 5 / 5 | 3 / 2336 | +0xa8:0x40de0, +0x168:0x41250, +0x1e8:0x416c0, +0x1f8:0x41700 | complete |
|
||||
| Keep Sprint | move | cb+reg+aux | 4 / 4 | 3 / 416 | +0x128:0x3a0f0, +0x138:0x3a170, +0x158:0x3a260, +0x1f8:0x3a290 | complete |
|
||||
| Knockback Delay | combat | cb+reg+aux | 7 / 7 | 2 / 1008 | +0x68:0x46140, +0x168:0x46270, +0x1f8:0x46530 | complete |
|
||||
| Knockback Displace | combat | cb+reg+aux | 9 / 9 | 6 / 1136 | +0x78:0x4a520, +0x118:0x4a540, +0x128:0x4a560, +0x138:0x4a7d0, +0x168:0x4a800, +0x1f8:0x4a930 | complete |
|
||||
| Lag Range | combat | cb+reg+aux | 8 / 8 | 4 / 1856 | +0x78:0x3a3c0, +0xa8:0x3a610, +0x138:0x3a780, +0x168:0x3a7d0, +0x1f8:0x3ab00 | complete |
|
||||
| Mod Overlay | visual | cb+reg | 14 / 14 | 8 / 3216 | +0x48:0x34080, +0x98:0x34290, +0xd8:0x344d0, +0xe8:0x34510, +0xf8:0x349f0, +0x1c8:0x34a10, +0x1f8:0x34a40 | complete |
|
||||
| Name Tags | visual | cb+reg | 12 / 12 | 3 / 5024 | +0x98:0x3c210, +0xb8:0x3c280, +0x168:0x3ca20, +0x1f8:0x3d5b0 | complete |
|
||||
| No Hit Delay | utility | cb+reg+aux | 1 / 1 | 1 / 160 | +0x128:0x3e590, +0x1f8:0x3e630 | complete |
|
||||
| No Item Release | utility | cb+reg+aux | 1 / 1 | 1 / 256 | +0x78:0x334c0, +0x1f8:0x335c0 | complete |
|
||||
| No Slow | move | cb+reg | 3 / 3 | 1 / 160 | +0x198:0x42a20, +0x1f8:0x42ac0 | complete |
|
||||
| No Use Delay | utility | cb+reg | 1 / 1 | 1 / 256 | +0x168:0x3e8e0, +0x1f8:0x3e9e0 | complete |
|
||||
| Notifications | visual | cb+reg | 8 / 8 | 6 / 2992 | +0x98:0x35000, +0xd8:0x351a0, +0xe8:0x354b0, +0xf8:0x35960, +0x108:0x35a30, +0x178:0x35af0, +0x1f8:0x35bb0 | complete |
|
||||
| Piercing | combat | cb+reg | 8 / 8 | 3 / 592 | +0xc8:0x44c90, +0x1f8:0x44ca0 | complete |
|
||||
| Player ESP | visual | cb+reg+aux | 13 / 13 | 3 / 3152 | +0xa8:0x2eb20, +0xb8:0x2eea0, +0x168:0x2f3f0, +0x1f8:0x2f770 | complete |
|
||||
| Pointers | visual | cb+reg+aux | 13 / 13 | 2 / 2128 | +0xb8:0x3da80, +0x168:0x3df10, +0x1f8:0x3e2d0 | complete |
|
||||
| Reach | combat | cb+reg+aux | 3 / 3 | 1 / 176 | +0xc8:0x2ffb0, +0x1f8:0x30060 | complete |
|
||||
| Refill | utility | cb+reg | 8 / 8 | 3 / 2064 | +0x98:0x384c0, +0x1d8:0x384f0, +0x1f8:0x38bc0 | complete |
|
||||
| Safe Walk | move | cb+reg | 4 / 4 | 1 / 288 | +0x48:0x41d30, +0x1f8:0x41e50 | complete |
|
||||
| Sprint | move | cb+reg | 4 / 4 | 2 / 368 | +0x158:0x35d80, +0x198:0x35e30, +0x1f8:0x35ef0 | complete |
|
||||
| Sprint Reset | combat | cb+reg+aux | 6 / 6 | 2 / 864 | +0x158:0x3ad10, +0x1f8:0x3afc0 | complete |
|
||||
| Strafe | move | cb+reg | 5 / 5 | 2 / 624 | +0x198:0x41f90, +0x1a8:0x41fc0, +0x1f8:0x42200 | complete |
|
||||
| Teams | utility | cb+reg | 2 / 2 | 3 / 720 | +0x48:0x31e10, +0x98:0x31ff0, +0x1e8:0x320b0, +0x1f8:0x320e0 | complete |
|
||||
| Timer | utility | cb+reg+aux | 4 / 4 | 2 / 272 | +0x58:0x328f0, +0x98:0x329b0, +0x1f8:0x32a00 | complete |
|
||||
| Timer Range | combat | cb+reg+aux | 9 / 9 | 5 / 3632 | +0x48:0x42c60, +0x58:0x42f00, +0x78:0x43950, +0x98:0x43970, +0x1f8:0x439e0 | complete |
|
||||
| Trajectories | visual | cb+reg | 7 / 7 | 3 / 3968 | +0x98:0x44e40, +0xa8:0x44ea0, +0x168:0x45370, +0x1f8:0x45dc0 | complete |
|
||||
| Velocity | combat | cb+reg+aux | 14 / 14 | 8 / 2512 | +0x48:0x30180, +0x68:0x302d0, +0x98:0x303a0, +0x128:0x303c0, +0x138:0x30410, +0x158:0x30520, +0x168:0x308e0, +0x1f8:0x30930 | complete |
|
||||
| Weapons | combat | cb+reg | 16 / 16 | 4 / 640 | +0x1e8:0x38190, +0x1f8:0x381c0 | complete |
|
||||
| No Jump Delay | move | — | 0 / 0 | 1 / 48 | +0x158:0x35d50 | no-GUI + logic |
|
||||
| Ping Fix | utility | — | 0 / 0 | 2 / 96 | +0x68:0x3e6d0, +0x78:0x3e700 | no-GUI + logic |
|
||||
| No Hurt Cam | visual | — | 0 / 0 | 0 / 0 | — | no-GUI (engine-side) |
|
||||
| Instant Stop | move | — | 0 / 0 | 1 / 976 | +0x148:0x42650 | no-GUI + logic |
|
||||
| Item Use Fix | utility | — | 0 / 0 | 2 / 176 | +0x128:0x42b80, +0x138:0x42be0 | no-GUI + logic |
|
||||
| Null Move | move | — | 0 / 0 | 1 / 592 | +0x148:0x46ac0 | no-GUI + logic |
|
||||
|
||||
**Totals:** 67 modules, 200 logic functions, 92400 logic bytes.
|
||||
|
||||
No missing module, setting, callback or logic function was found.
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# Modules by category
|
||||
|
||||
Category = descriptor field `+0x10` (0 Combat, 1 Move, 2 Visual, 3 Block,
|
||||
4 Utility), matching the GUI tab strings at `.rdata` `0x151cc8`. Paths are
|
||||
relative to the repo root. The first five sections are the 61 modules with a
|
||||
GUI callback; the six engine-only modules are listed at the end.
|
||||
|
||||
### Combat (17)
|
||||
|
||||
| module | file | functions | description |
|
||||
|---|---|---|---|
|
||||
| Aim Assist | [aim_assist.h](../src/logic/combat/aim_assist.h) | cb, reg, aux | Pulls your crosshair towards other players. |
|
||||
| Auto Block | [auto_block.h](../src/logic/combat/auto_block.h) | cb, reg | Blocks your sword for you in combat. |
|
||||
| Auto Clicker | [auto_clicker.h](../src/logic/combat/auto_clicker.h) | cb, reg, aux | Clicks for you when holding down left-click. |
|
||||
| Auto Weapon | [auto_weapon.h](../src/logic/combat/auto_weapon.h) | cb, reg | Selects your weapon when aiming on players. |
|
||||
| Backtrack | [backtrack.h](../src/logic/combat/backtrack.h) | cb, reg, aux | Uses lag to make players easier to hit. |
|
||||
| Criticals | [criticals.h](../src/logic/combat/criticals.h) | cb, reg, aux | Increases chance of landing critical hits. |
|
||||
| Hit Select | [hit_select.h](../src/logic/combat/hit_select.h) | cb, reg, aux | Perfectly times your hits. |
|
||||
| Hitboxes | [hitboxes.h](../src/logic/combat/hitboxes.h) | cb, reg, aux | Expands hitboxes of other players. |
|
||||
| Knockback Delay | [knockback_delay.h](../src/logic/combat/knockback_delay.h) | cb, reg, aux | Delays your knockback in certain scenarios. |
|
||||
| Knockback Displace | [knockback_displace.h](../src/logic/combat/knockback_displace.h) | cb, reg, aux | Allows dealing knockback at a different angle. |
|
||||
| Lag Range | [lag_range.h](../src/logic/combat/lag_range.h) | cb, reg, aux | Uses lag to stay out of target range. |
|
||||
| Piercing | [piercing.h](../src/logic/combat/piercing.h) | cb, reg | Allows hitting entities through obstructions. |
|
||||
| Reach | [reach.h](../src/logic/combat/reach.h) | cb, reg, aux | Increases your melee range. |
|
||||
| Sprint Reset | [sprint_reset.h](../src/logic/combat/sprint_reset.h) | cb, reg, aux | Resets your sprint state to deal more knockback. |
|
||||
| Timer Range | [timer_range.h](../src/logic/combat/timer_range.h) | cb, reg, aux | Abuses timer lag to teleport you. |
|
||||
| Velocity | [velocity.h](../src/logic/combat/velocity.h) | cb, reg, aux | Reduces the amount of knockback you take. |
|
||||
| Weapons | [weapons.h](../src/logic/combat/weapons.h) | cb, reg | Configure which items are considered weapons. |
|
||||
|
||||
### Move (7)
|
||||
|
||||
| module | file | functions | description |
|
||||
|---|---|---|---|
|
||||
| Fast Accel | [fast_accel.h](../src/logic/move/fast_accel.h) | cb, reg | Speeds up acceleration. |
|
||||
| Inv Walk | [inv_walk.h](../src/logic/move/inv_walk.h) | cb, reg, aux | Allows moving in inventory. |
|
||||
| Keep Sprint | [keep_sprint.h](../src/logic/move/keep_sprint.h) | cb, reg, aux | Keeps your sprint speed when attacking players. |
|
||||
| No Slow | [no_slow.h](../src/logic/move/no_slow.h) | cb, reg | Removes slowdown while using items. |
|
||||
| Safe Walk | [safe_walk.h](../src/logic/move/safe_walk.h) | cb, reg | Prevents you from walking off ledges. |
|
||||
| Sprint | [sprint.h](../src/logic/move/sprint.h) | cb, reg | Automatically sprints for you. |
|
||||
| Strafe | [strafe.h](../src/logic/move/strafe.h) | cb, reg | Allows faster strafing. |
|
||||
|
||||
### Visual (14)
|
||||
|
||||
| module | file | functions | description |
|
||||
|---|---|---|---|
|
||||
| Anti Debuff | [anti_debuff.h](../src/logic/visual/anti_debuff.h) | cb, reg | Removes negative visual effects. |
|
||||
| Block ESP | [block_esp.h](../src/logic/visual/block_esp.h) | cb, reg | Highlights blocks. |
|
||||
| Chams | [chams.h](../src/logic/visual/chams.h) | cb, reg | Renders players behind blocks. |
|
||||
| Chest ESP | [chest_esp.h](../src/logic/visual/chest_esp.h) | cb, reg | Highlights chests. |
|
||||
| Free Look | [free_look.h](../src/logic/visual/free_look.h) | cb, reg | Freely move the camera around. |
|
||||
| GUI | [gui.h](../src/logic/visual/gui.h) | cb, reg | Graphical interface for the cheat. |
|
||||
| Indicators | [indicators.h](../src/logic/visual/indicators.h) | cb, reg | Points to incoming projectiles. |
|
||||
| Item ESP | [item_esp.h](../src/logic/visual/item_esp.h) | cb, reg | Highlights dropped items. |
|
||||
| Mod Overlay | [mod_overlay.h](../src/logic/visual/mod_overlay.h) | cb, reg | Displays a list of all active mods. |
|
||||
| Name Tags | [name_tags.h](../src/logic/visual/name_tags.h) | cb, reg | Makes player name tags easier to see. |
|
||||
| Notifications | [notifications.h](../src/logic/visual/notifications.h) | cb, reg | Informs you when certain actions are performed. |
|
||||
| Player ESP | [player_esp.h](../src/logic/visual/player_esp.h) | cb, reg, aux | Highlights other players. |
|
||||
| Pointers | [pointers.h](../src/logic/visual/pointers.h) | cb, reg, aux | Points to other players. |
|
||||
| Trajectories | [trajectories.h](../src/logic/visual/trajectories.h) | cb, reg | Predicts the trajectory of the item in your hand. |
|
||||
|
||||
### Block (7)
|
||||
|
||||
| module | file | functions | description |
|
||||
|---|---|---|---|
|
||||
| Auto Tool | [auto_tool.h](../src/logic/block/auto_tool.h) | cb, reg | Selects the best tool when digging. |
|
||||
| Block In | [block_in.h](../src/logic/block/block_in.h) | cb, reg | Places blocks around you. |
|
||||
| Bridge Assist | [bridge_assist.h](../src/logic/block/bridge_assist.h) | cb, reg | Sneaks and unsneaks for you while bridging. |
|
||||
| Clutch | [clutch.h](../src/logic/block/clutch.h) | cb, reg | Clutches for you when falling. |
|
||||
| Fast Mine | [fast_mine.h](../src/logic/block/fast_mine.h) | cb, reg, aux | Increases block mining speed. |
|
||||
| Fast Place | [fast_place.h](../src/logic/block/fast_place.h) | cb, reg, aux | Allows placing blocks faster. |
|
||||
| Ghost Hand | [ghost_hand.h](../src/logic/block/ghost_hand.h) | cb, reg | Allows interacting with blocks through entities. |
|
||||
|
||||
### Utility (16)
|
||||
|
||||
| module | file | functions | description |
|
||||
|---|---|---|---|
|
||||
| Anti Bot | [anti_bot.h](../src/logic/utility/anti_bot.h) | cb, reg | Ignores NPCs for specific modules. |
|
||||
| Auto Pot | [auto_pot.h](../src/logic/utility/auto_pot.h) | cb, reg | Uses healing potions for you. |
|
||||
| Auto Rod | [auto_rod.h](../src/logic/utility/auto_rod.h) | cb, reg | Throws your fishing rod for you. |
|
||||
| Auto Soup | [auto_soup.h](../src/logic/utility/auto_soup.h) | cb, reg | Uses soups for you. |
|
||||
| Blink | [blink.h](../src/logic/utility/blink.h) | cb, reg, aux | Simulates teleporting using lag. |
|
||||
| Chest Stealer | [chest_stealer.h](../src/logic/utility/chest_stealer.h) | cb, reg | Automatically loots chests for you. |
|
||||
| Debug | [debug.h](../src/logic/utility/debug.h) | cb, reg | Prints debug information in chat. |
|
||||
| Fake Lag | [fake_lag.h](../src/logic/utility/fake_lag.h) | cb, reg, aux | Adds an artificial delay to your packets. |
|
||||
| Friends | [friends.h](../src/logic/utility/friends.h) | cb, reg | Lets you mark other players as friendly. |
|
||||
| Inv Manager | [inv_manager.h](../src/logic/utility/inv_manager.h) | cb, reg | Organises your inventory for you. |
|
||||
| No Hit Delay | [no_hit_delay.h](../src/logic/utility/no_hit_delay.h) | cb, reg, aux | Removes 1.8 swing delay. |
|
||||
| No Item Release | [no_item_release.h](../src/logic/utility/no_item_release.h) | cb, reg, aux | Hides item release from server. |
|
||||
| No Use Delay | [no_use_delay.h](../src/logic/utility/no_use_delay.h) | cb, reg | Removes vanilla delay when consuming items. |
|
||||
| Refill | [refill.h](../src/logic/utility/refill.h) | cb, reg | Automatically moves items into your hotbar. |
|
||||
| Teams | [teams.h](../src/logic/utility/teams.h) | cb, reg | Ignores teammates for specific modules. |
|
||||
| Timer | [timer.h](../src/logic/utility/timer.h) | cb, reg, aux | Modifies the speed the game runs at. |
|
||||
|
||||
### Engine-only (6)
|
||||
|
||||
These descriptors exist in the image but have no GUI callback and no settings
|
||||
pane. They are kept in `manifest.json` so the module table is exactly the 67
|
||||
descriptors from the image.
|
||||
|
||||
| module | category | file | description |
|
||||
|---|---|---|---|
|
||||
| No Jump Delay | move | [no_jump_delay.h](../src/logic/move/no_jump_delay.h) | Removes cooldown when holding jump key. |
|
||||
| Ping Fix | utility | [ping_fix.h](../src/logic/utility/ping_fix.h) | Shows your real ping while using lag mods. |
|
||||
| No Hurt Cam | visual | [no_hurt_cam.h](../src/logic/visual/no_hurt_cam.h) | Removes camera shake when taking damage. |
|
||||
| Instant Stop | move | [instant_stop.h](../src/logic/move/instant_stop.h) | Instantly stops moving when move keys are released. |
|
||||
| Item Use Fix | utility | [item_use_fix.h](../src/logic/utility/item_use_fix.h) | Allows using items while breaking blocks. |
|
||||
| Null Move | move | [null_move.h](../src/logic/move/null_move.h) | Makes it easier to switch move direction instantly. |
|
||||
@@ -0,0 +1,65 @@
|
||||
# Reverse-engineering notes
|
||||
|
||||
Carried over from `slinkyfullsrc/SRC.md`. None of this is needed to build the
|
||||
module sources.
|
||||
|
||||
## `.slinky0` — engine / module manager
|
||||
|
||||
`slinky2.decrypted/slinky0.bin` is 14 255 293 bytes (`.slinky0` of the client
|
||||
image). Region map (verified):
|
||||
|
||||
| range | content |
|
||||
|---|---|
|
||||
| `0x000000–0x77FFFF` | native code + data (MSVC CRT/RTTI strings at `0x2d000–0x3c000`, engine code; clean x86-64, ~30 % printable) |
|
||||
| `0x780000–0x8BFFFF` | high-entropy (7.99) blob — encrypted/compressed, not consumed by visible code |
|
||||
| `0x8C0000–0x9FFFFF` | sorted `(u32 rva, u16 size)` table, 213 035 records, covers `0x36300E–0x867551`, sizes multiple of 8, dense (≈50 % of the region), deltas 2–16 bytes, overlap → looks like a per-site patch/relocation or checksum map for the code region |
|
||||
| `0xA00000–0xAA0000` | table A, XOR `00 10 00 62 75 86`, 109 225 × `(u32 off, u16 size)`; offsets `0x223–0x27FFB4` fit *inside* the tail block; sizes ∈ {0,8,24,32,…,120} |
|
||||
| `0xAA0000–0xAB0000` | table B, XOR `83 A7 00 10 00 53`, same 6-byte record shape |
|
||||
| `0xAB0000–0xD80000` | `tail_high.bin` — 2 949 120 bytes, entropy 7.98, no standard container/PE; table A indexes into it |
|
||||
| `0xD80000–0xD984BD` | `tail_code.bin` (~100 KB), mixed code/data |
|
||||
|
||||
Module-manager data model (recovered from the client image, used by the
|
||||
reconstructed sources):
|
||||
|
||||
* Module descriptor in `.data` (`.rdata` string pointers + a factory/log
|
||||
function at `+0x58`). `modules_real.json` lists all 67 (61 with a GUI cb).
|
||||
* Per-module settings block in `.data`, variable stride, begins with three
|
||||
function pointers `{gui_cb, register_settings, aux}` followed by the widget
|
||||
cfg objects (`CfgBool/CfgSlider/CfgRange/CfgEnum/CfgColor/...`).
|
||||
* `R_*` registration calls are made from the client into the engine manager at
|
||||
DLL init (one `reg_<addr>` function per module), the `W_*` calls render the
|
||||
settings pane.
|
||||
|
||||
The module-side functions that read the settings and implement each feature
|
||||
are in `src/logic/` (see `docs/logic.md`); they were extracted 1:1 from `.text`
|
||||
by `tools/extract_logic.py`.
|
||||
|
||||
The manager itself is in client `.text`, not `.slinky0`: constructor `0x28000`,
|
||||
dispatch `0x285d0`, descriptor table `0x14f810`. It groups modules by category
|
||||
and by the event callback slots at `descriptor+0x48` … `+0x1F8`, and dispatches
|
||||
the enabled callbacks. See `docs/engine.md` for the full picture, including
|
||||
the six engine-only modules. `.slinky0` has **no static RIP-relative
|
||||
references** into the client's `.rdata`/`.data`/`.text` (checked: 0 refs), so
|
||||
it receives whatever it needs through runtime-installed pointers.
|
||||
|
||||
### tail_high.bin
|
||||
|
||||
`tail_high.bin` is still packed. Findings:
|
||||
|
||||
* Not a PE, not ZIP/7z/RAR/gzip/zlib/LZMA (standard headers checked).
|
||||
* Entropy 7.978; byte histogram ≈ uniform; no repeated 24-byte blocks.
|
||||
* Table A (109 225 records) and the sorted table (213 035 records) both use the
|
||||
same `(u32 offset, u16 size)` record form, sizes are multiples of 8, and both
|
||||
point *into* their subject buffers with heavy overlap — this is a
|
||||
*site map* (patch/relocation/checksum), not a container directory.
|
||||
* The runtime copy of `.slinky0` captured in `slinky.dll` is byte-identical to
|
||||
the static stream up to `0xC4B658` and zero beyond, i.e. that dump was taken
|
||||
before the engine performed the tail transformation; it gives no plaintext.
|
||||
* Therefore decryption requires either (a) implementing the protector's
|
||||
per-site transform from the `0x8C0000` table, or (b) a live Wine trace of
|
||||
the first execution that touches `slinky0+0xAB0000` (same breakpoint
|
||||
technique that recovered the `.slinky2` LZMA streams).
|
||||
|
||||
Decrypting `tail_high.bin` is **not required** for anything in this tree: the
|
||||
module callbacks/registrations are plain `.text`, and the registry data model
|
||||
is plain `.data`. It is required only for the remainder of the engine internals.
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
# Function renames (this pass)
|
||||
|
||||
Inferred from behaviour. `hitboxes_apply_hitbox_expansion_original` is the original engine copy; the descriptor uses the correctly named `hitboxes_on_reach_query` logic function. `block_esp_queue_loaded_chunks` was verified against its body (it queues the loaded chunks for scanning).
|
||||
|
||||
| old | new |
|
||||
|---|---|
|
||||
| `packet_entity_lookup` | `no_item_release_on_send_packet` |
|
||||
| `activate_hold` | `hitboxes_apply_hitbox_expansion_original` |
|
||||
| `compare_height_desc` | `compare_overlay_entries_by_height_desc` |
|
||||
| `take_pending_bool` | `take_and_clear_pending_flag` |
|
||||
| `rotation_cache_toggle` | `free_look_on_toggle` |
|
||||
| `store_cached_rotation` | `free_look_save_rotation` |
|
||||
| `enchantment_flags_item_stack` | `get_weapon_enchant_flags` |
|
||||
| `weapons_part` | `is_held_item_accepted_weapon` |
|
||||
| `click_with_helper` | `is_holding_accepted_weapon` |
|
||||
| `close_screen_and_release` | `refill_disable_and_close_screen` |
|
||||
| `find_hotbar_cell` | `refill_pick_inventory_cell` |
|
||||
| `remove_id_key` | `friend_set_remove_by_key` |
|
||||
| `clear_id_flag` | `friend_set_clear_dirty` |
|
||||
| `compare_hurt_time` | `compare_by_hurt_time_desc` |
|
||||
| `clear_list_set` | `free_block_search_section_lists` |
|
||||
| `text_filter_upper` | `search_box_append_char` |
|
||||
| `sort_float_entries` | `block_esp_queue_loaded_chunks` |
|
||||
| `clear_mesh` | `rebuild_block_esp_mesh` |
|
||||
| `free_allocator_pages` | `free_block_search_cache` |
|
||||
| `pending_action_flag` | `timer_range_is_delay_pending` |
|
||||
| `item_speed_by_type` | `no_slow_item_use_speed` |
|
||||
| `zero_motion_tick` | `anti_debuff_tick` |
|
||||
|
||||
## Context struct (`Ctx`) fields
|
||||
|
||||
Every module handler receives a `Ctx *c`. Fields were previously `a`, `b`, `c18`, ...:
|
||||
|
||||
| old | new | meaning |
|
||||
|---|---|---|
|
||||
| `a` | `jvm` | JNI/JVM handle, first argument of every `java_*` call |
|
||||
| `p08` | `module_manager` | target of `queue_module_toggle` |
|
||||
| `p10` | `profile_list` | |
|
||||
| `c18` | `minecraft` | Minecraft instance |
|
||||
| `p20` | `world` | client world |
|
||||
| `b` | `player` | local player entity |
|
||||
| `p30` | `key_state_table` | |
|
||||
| `p38` | `target` | current combat target record |
|
||||
| `f40` | `in_game` | non-zero while a world is loaded |
|
||||
|
||||
## Event handler names
|
||||
|
||||
Most handlers are `<module>_on_<event>`, where the event is the descriptor slot the handler is registered in; handlers whose purpose is clearer keep a semantic name (for example `velocity_mode_3_delayed`, `notifications_draw_slide_boxes`):
|
||||
`+0x48 tick`, `+0x58 frame`, `+0x68 packet_received`, `+0x78 packet_sent`, `+0x88 input`,
|
||||
`+0x98 toggle`, `+0xa8 render_world`, `+0xb8 render_entity_overlay`, `+0xc8 reach_query`,
|
||||
`+0xd8 hud_tick`, `+0xe8 hud_render`, `+0x118 rotation_update`, `+0x128 attack`,
|
||||
`+0x138 attack_post`, `+0x158 player_update`, `+0x168 late_tick`, `+0x188 world_change`,
|
||||
`+0x198 movement_input`, `+0x1a8 move_flying`, `+0x1b8 game_packet_sent`,
|
||||
`+0x1d8 inventory_tick`, `+0x1e8 shutdown`. These slot meanings are inferred from the
|
||||
handlers' behaviour, not from the original binary.
|
||||
|
||||
## Locals, parameters and struct fields
|
||||
|
||||
Renamed with a scope-aware AST pass (every edit is validated against the compiler and the generated object code):
|
||||
|
||||
- `Jvm *j`, `void *a` (first parameter) -> `jvm`; `Ctx *c` -> `ctx`; `*Hook *h` -> `hook`
|
||||
- JNI accessor object parameters take the class from the accessor body, e.g. `a1` -> `game_settings`, `key_binding`, `entity_player`; setter value parameters are `value`
|
||||
- locals initialised from an accessor are named after it (`mc` -> `minecraft`, `it` -> `stack`, ...)
|
||||
- NanoVG context `ig` -> `nvg`, GUI state block `g` -> `gui`, `Rot r` -> `rotation`, register-function argument `m` -> `module`
|
||||
- duplicate `HCtx` structs use the same field names as `Ctx` (`jvm`, `module_manager`, `minecraft`, `key_state_table`, `target`, `in_game`, ...)
|
||||
- event structs: `f24` -> `verdict`, `p10` -> `packet`
|
||||
|
||||
## GUI state block (`include/slinky/gui_state.h`)
|
||||
|
||||
All raw `gui + 0x...` / `gui[0x...]` accesses are now named members of `struct GuiState`
|
||||
(`toast_text`, `tabs`, `selected_tab`, `input_mode`, `edit_text`, `popup_kind`, `popup_rgb`, `popup_hsl`,
|
||||
`popup_list_*`, `mouse_scene`, `scroll`, `row_height`, `click_capture`, ...). The layout-row stack at the start of the
|
||||
block is `GuiState::rows[5]` (`GuiLayoutRow`, 0x78 bytes each). Offsets are checked with `static_assert`s; overlapping
|
||||
uses (a text buffer that doubles as a key-bind callback, the popup payload) are unions. Field names are inferred
|
||||
from how the GUI code uses them.
|
||||
|
||||
## `__N` disambiguators
|
||||
|
||||
Duplicate constants, strings and tables no longer carry `__N`: a symbol that is used from one file gets
|
||||
`_in_<file>` (for example `row_height_20_in_gui_controls`), module state that already starts with the module name gets an
|
||||
ordinal (`aim_assist_rng_states_second`), and symbols only referenced from the data tables get `_copy`.
|
||||
|
||||
## Verification note
|
||||
|
||||
The only generated-code difference from the original tree is in `gui_layout.cpp`, where the layout-row index is now
|
||||
written `&gui->rows[n - 1]`; the compiler emits the same address computation with base and index registers swapped.
|
||||
|
||||
## Widgets, padding and parameter fixes
|
||||
|
||||
- Settings widgets: the `W1`/`W2`/`W3`/`Wid` structs became `DropdownSetting`, `ColorSetting`, `ListSetting`,
|
||||
`ToggleSetting`, `SliderSetting` and `RangeSetting`, with fields such as `label`, `format`, `step`, `scale`, `min`,
|
||||
`max`, `low`, `high`, `names`, `optional`, `popup_title`, `get_candidates`. `ListSet` / `ListOps` gained `add` and
|
||||
`remove`.
|
||||
- Misnamed functions: `draw_keybind_control` -> `draw_dropdown_row`, `draw_checkbox` -> `draw_color_swatch_row`,
|
||||
`draw_colored_checkbox` -> `draw_list_editor_row`, `draw_label` -> `draw_toggle_row`, `draw_slider_label` ->
|
||||
`draw_slider_row`, `draw_slider_with_label` -> `draw_range_slider_row`, `draw_label_va` -> `draw_section_label`,
|
||||
`get_net_handler` -> `get_game_settings` (it returns `Minecraft.gameSettings`).
|
||||
- Module GUI callbacks take `(GuiState *gui, window)` and the `widget_*` functions take `(window, gui, setting)`; an earlier
|
||||
pass had called the first argument `jvm`, which was wrong for these functions and is fixed.
|
||||
- Padding fields are named by their byte offset (`padding_08`, `padding_11`, ...), computed from clang's record layouts.
|
||||
Offset-named fields with a known role got names (`on_shutdown`, `register_settings`, `fill_fraction`,
|
||||
`key_state_table`, `reach`, ...); the remaining zeroed filler bytes are `reserved_NN`.
|
||||
- `hook_*` natives: `(env, native_class, receiver, ...)`; `java_jni_is_instance_of_*` takes `candidate`;
|
||||
integer error-code locals are `error_code`.
|
||||
|
||||
## Build fix and key-binding accessors
|
||||
|
||||
- `nvg_names.cpp` failed to compile after the parameter pass (the parameter was renamed to `nvg` but the macro-free bodies
|
||||
still used `c`); fixed. The two NanoVG-dependent files were not part of my per-file compile checks, so the full CMake
|
||||
cross-build is the reference check; it builds `slinky.dll` cleanly.
|
||||
- Key-binding accessors are named by the `GameSettings.keyBindings` index they read:
|
||||
`java_get_game_settings_attack_key` (0), `_use_item_key` (1), `_forward_key` (2), `_left_key` (3), `_back_key` (4),
|
||||
`_right_key` (5), `_sneak_key` (7), `_key_8_or_9`, `_key_17_or_8` (mapping-dependent).
|
||||
- Other numbered accessors: `java_get_minecraft_object_mouse_over`, `java_get_minecraft_player_controller`,
|
||||
`java_get_entity_player_inventory`, `java_get_moving_object_position_type_ordinal` / `_entity_hit`,
|
||||
`java_increment_key_binding_press_time`.
|
||||
- `timer_range_last_forward_input` / `timer_range_last_strafe_input` were swapped in an earlier pass; fixed (they come
|
||||
from the forward/back and left/right keys respectively).
|
||||
- `CombatTarget` (engine.h) replaces raw `ctx->target + 0x18` reads with `->valid`.
|
||||
|
||||
## JNI calls, numbered names, constants, padding
|
||||
|
||||
- Raw JNI function-table calls (`(*(void ***)env)[0xb0 / 8]` and friends) are now named calls through the existing
|
||||
`JNIEnv` table: `(*env)->DeleteGlobalRef`, `IsSameObject`, `NewGlobalRef`, `NewStringUTF`, `GetObjectArrayElement`,
|
||||
`GetStringLength`, `GetStringCritical`, `ReleaseStringCritical`. `((void **)ctx->jvm)[1]` is `((Jvm *)ctx->jvm)->env`.
|
||||
- Functions that ended in `_2` / `_3` have descriptive names (`friends_on_tick`, `friends_on_toggle`,
|
||||
`bridge_assist_on_late_tick`, `piercing_decide_which_entity`, `java_get_world_chunk_provider_client`, ...). The numbers
|
||||
that remain are meaningful (`hook_*_1_7` / `_1_8` Minecraft versions, `player_esp_render_mode_0/1`, key-binding array
|
||||
indices, `java_call_world_get_block_state/500` method ids).
|
||||
- Constants used as arguments of a recognised call are named by role (`font_size_16_in_gui_popups`,
|
||||
`corner_radius_10_in_gui_text`, `colour_level_0_866667_in_gui_text`, `walk_period_0_5_in_clutch`,
|
||||
`random_range_bound_0_05_in_clutch`); constants used in arithmetic or comparisons by operator
|
||||
(`threshold_`, `factor_`, `divisor_`, `offset_`); pi and the 180 / 360 degree constants are `pi_constant`,
|
||||
`half_turn_degrees`, `full_turn_degrees`. Constants whose use is not one of these keep their value in the name.
|
||||
- Struct filler bytes are `padding_NN` (offset in hex), not `reserved_NN`: they are never read.
|
||||
|
||||
## Minecraft field / method identifiers, string names, constants
|
||||
|
||||
The Minecraft JNI identifiers (`JIds` members) are now named after what they are in the game
|
||||
(`Minecraft_theWorld`, `Minecraft_thePlayer`, `Minecraft_objectMouseOver`, `Entity_boundingBox`, `World_chunkProvider`,
|
||||
`MovingObjectPosition_hitVec`, `InventoryPlayer_itemStack`, `RenderManager_textRenderer`, ...), and their accessors
|
||||
follow (`java_get_minecraft_render_item`, `java_get_entity_riding_entity`, ...). Members whose game name I could not
|
||||
establish from the code are called `<Class>_unidentified_<offset>`; method ids are `unidentified_<offset>`.
|
||||
|
||||
**Compatibility:** only C identifiers changed. The class / field / method *name strings* that the client resolves at
|
||||
run time (the obfuscated or mapped names used for the JVM lookups, including any mapping a particular launcher needs)
|
||||
live in `src/data/rdata.s` and are untouched; the data section hashes identically before and after. Short obfuscated
|
||||
name strings are labelled `mc_obf_name_<text>`, JNI signature strings `jni_signature_<text>`, printf formats
|
||||
`fmt_<spec>` and X.509 OID strings `oid_<digits>`.
|
||||
|
||||
Also: `fn_<address>` / `logic_<address>` references in the docs now show the current function names;
|
||||
`java_get_static_field_c80` is `java_get_c16_enum_state_open_inventory`; remaining one- and two-letter locals of
|
||||
struct-pointer type are named after the struct (`entry`, `target`, `visitor`, `window`, ...); constants used as plain
|
||||
literals are named by number word (`one`, `half`, `minus_one`, ...) or by role (`range_low_*`).
|
||||
|
||||
## JNI members named from the client's own mapping tables
|
||||
|
||||
Every `JIds` member (fields and methods of the Minecraft classes the client resolves through JNI) is now named after the
|
||||
MCP name that the client's own data tables carry next to the obfuscated name for each mapping (`getBlockState`,
|
||||
`rayTrace`, `sideHit`, `renderGlobal`, `netManager`, `updateCameraAndRender`, ...), so the names are the ones the data
|
||||
itself uses and are verified against it. Earlier guesses that the tables contradicted were corrected
|
||||
(`World_playerEntities` / `World_loadedEntityList` / `World_loadedTileEntityList`, `TileEntity_blockType`,
|
||||
`NetworkPlayerInfo_displayName`, `GuiContainer_theSlot`, `RenderManager_textRenderer`, `InventoryPlayer_itemStack`).
|
||||
The string slots that hold method names and signatures for the render hooks are `RendererLivingEntity_*MethodName` /
|
||||
`*MethodSignature`. No `unidentified_*` names remain. Only identifiers changed; the name strings are unchanged.
|
||||
|
||||
## Constants
|
||||
|
||||
All remaining `k_f_*` / `k_d_*` constants are named by purpose (`ground_friction_times_air_drag`,
|
||||
`movement_input_friction`, `gui_wide_layout_max_width`, ...).
|
||||
@@ -0,0 +1,61 @@
|
||||
# Standalone build: what is left (measured 2026-10-30)
|
||||
|
||||
State of the source: every function of the client is now C++ except the 43 protector-mutated ones, which have
|
||||
readable versions in `src/engine/protected/` (17 of 20 used ones; see README there). Counts:
|
||||
|
||||
| layer | functions | in C++ | byte-exact |
|
||||
|---|---:|---:|---:|
|
||||
| module settings (cb/reg/aux) | 146 | 146 | 145 |
|
||||
| module logic | 200 | 200 | 200 |
|
||||
| engine (`manifest_engine.json`) | 880 | 837 (+17 readable protected) | 591 |
|
||||
|
||||
`tools/extract_engine.py` now also finds functions nothing calls directly (the protector's virtualised init block
|
||||
starts them through function pointers): the input handler, JVMTI unload, bytecode patcher, GLFW loader, key maps,
|
||||
JNI bootstrap, allocators, destructors.
|
||||
|
||||
## What a standalone (no original image) build still needs
|
||||
|
||||
Undefined symbols over all 440 compiled objects (`llvm-nm`):
|
||||
|
||||
| kind | count | what it is | plan |
|
||||
|---|---:|---|---|
|
||||
| `rdata_*` | 2161 | strings, float/double constants, lookup tables, vtable-like arrays | generate `src/data/rdata_*.cpp` from the image, typed by use (strings as literals, floats, structs where known) |
|
||||
| `data_*` | 1352 | globals: settings values, module descriptors, state | generate definitions with initial values from the image's `.data` |
|
||||
| `fn_*` | 97 | NanoVG + its GL3 backend (0x62850+; not ImGui), MSVC CRT (0xab000+), import thunks | link upstream NanoVG (nanovg_gl, GL3), the real CRT; map thunks to real APIs |
|
||||
| `W_*`/`R_*`/`log_fmt` | 48 | the widget/registration API the module code calls | already written as headers in include/slinky/api.h; needs real bodies (they are `fn_*` in the image) |
|
||||
| `__imp_*` | - | Win32 imports used by src/engine/protected | link kernel32 (Windows) / replace in a platform layer (Linux) |
|
||||
|
||||
Platform layer (needed for Linux): the Windows-only pieces are the window-procedure input hook and click injection
|
||||
(`protected/input.cpp`), file/path helpers (`protected/files.cpp`), `GetModuleHandle/GetProcAddress`, DLL entry/unload.
|
||||
Everything else goes through JNI/JVMTI (portable) and OpenGL/NanoVG (portable). On Linux: load as a JVMTI agent
|
||||
(`-agentpath`), take input from GLFW callbacks (`init_glfw_entrypoints` already resolves them) or X11, use `dlopen/dlsym`.
|
||||
|
||||
## Order
|
||||
1. generate data/rdata definitions (largest mechanical step, enables linking the C++ without the image)
|
||||
2. real NanoVG + CRT, widget API bodies
|
||||
3. CMake: Windows DLL first (comparable against the original), then Linux `.so` with the platform layer
|
||||
4. rewrite the unrecovered virtualised blocks (one-time init in `hook_get_mouse_over`, hook on/off `set_java_hooks`) with our own design
|
||||
|
||||
## Result of the data step (tools/gen_data.py, tools/gen_aliases.py, tools/link_probe.sh)
|
||||
|
||||
* `src/data/rdata.s` (1.6 MB, 4430 relocated pointers, 6937 strings) and `src/data/data.s` (2595 pointers) are generated
|
||||
from the image and verified byte-for-byte by `tools/check_data.py` (0 differing bytes, every pointer target correct).
|
||||
Strings are `.asciz`, pointers are symbol references, 4/8-byte constants carry their float/double value as a comment.
|
||||
* `src/engine/aliases.cpp` maps engine calls of logic functions (`gui_handle_input_event_entry` -> `gui_handle_input_event`) and the module API names
|
||||
(`W_bool` -> `widget_bool`, ...).
|
||||
* `tools/link_probe.sh` links every C++ object with these into a DLL (no CRT, no imports). Before: 3513 data symbols and
|
||||
~700 functions undefined. Now: 300 undefined symbols, 0 duplicates, and all of them are library code or platform API:
|
||||
199 MSVC CRT functions, 42 NanoVG functions, 23 import thunks, ~25 Win32 imports, ~9 libc/runtime symbols, and
|
||||
one piece of client code (`set_java_hooks`, the unrecovered hook on/off).
|
||||
* So the client's own code is closed: everything it needs besides NanoVG, the C runtime and the OS is in this tree.
|
||||
|
||||
## Status update: the DLL builds (CMakeLists.txt)
|
||||
|
||||
* `cmake -S . -B build/cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-windows-clang-cl.cmake && cmake --build build/cmake`
|
||||
produces `build/cmake/slinky.dll` without the original image and without a Windows SDK.
|
||||
* What stood open in the table above is now bound: the C runtime and NanoVG by `src/platform/` (CRT shim, NanoVG
|
||||
names, GL slots), the OS imports by import libraries generated from `cmake/imports/*.def`, the few libc declarations
|
||||
the bundled C code needs by `third_party/minicrt/`.
|
||||
* Start-up, hook control, teardown and the first-frame GUI setup are new code (`docs/init.md`).
|
||||
* Still open: running it in a game (nothing has been run), the Linux platform layer, and the items marked inferred in
|
||||
`docs/init.md`.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,47 @@
|
||||
// Slinky client - shared declarations for every module.
|
||||
// Symbols keep the names they had in the original binary (fn_<rva>, data_<rva>,
|
||||
// rdata_<rva>), so the code can be compared with the decompiled image one
|
||||
// function at a time.
|
||||
#pragma once
|
||||
|
||||
// ---- widget API (GUI drawing), original RVAs ----
|
||||
extern "C" void W_bool(void *a, void *ctx, void *cfg); // 0x0cef0
|
||||
extern "C" void W_slider(void *a, void *ctx, void *cfg); // 0x0cf30
|
||||
extern "C" void W_range(void *a, void *ctx, void *cfg); // 0x0cfb0
|
||||
extern "C" void W_enumw(void *a, void *ctx, void *cfg); // 0x0d040
|
||||
extern "C" void W_color(void *a, void *ctx, void *cfg); // 0x0d0a0
|
||||
extern "C" void W_items(void *a, void *ctx, void *cfg); // 0x0d0e0
|
||||
extern "C" void W_label(void *ctx, void *a, const char *s); // 0x13e90
|
||||
|
||||
// ---- settings registration API ----
|
||||
extern "C" void R_bool(void *mgr, const char *name, void *cfg); // 0x0c3b0
|
||||
extern "C" void R_num(void *mgr, const char *name, void *cfg); // 0x0c450
|
||||
extern "C" void R_rng(void *mgr, const char *name, void *cfg); // 0x0c550
|
||||
extern "C" void R_enum(void *mgr, const char *name, void *cfg); // 0x0c6f0
|
||||
extern "C" void R_key(void *mgr, const char *name, void *cfg); // 0x0c7e0
|
||||
extern "C" void R_kind2(void *mgr, const char *name, void *cfg); // 0x0ca00
|
||||
|
||||
// ---- original .rdata strings (symbol = RVA) ----
|
||||
#define S(n) extern "C" const char n[]
|
||||
#define D(n) extern "C" unsigned char n[]
|
||||
|
||||
// The original compiler (an older clang-cl) emitted a 1-byte `nop` between a
|
||||
// tail-call `jmp` and a following SEH epilogue; clang 23 no longer does. Put
|
||||
// EPILOG_NOP() on the fall-through path that reaches that second epilogue to
|
||||
// reproduce it (compiler-version shim, not original source).
|
||||
#define EPILOG_NOP() __asm__ __volatile__("nop")
|
||||
|
||||
// Same story for if/else that rejoins at one epilogue: clang 23 tail-duplicates
|
||||
// the epilogue into both arms, the original kept a single shared one. An empty
|
||||
// asm-goto is not duplicable, so it pins the join block. Emits no code.
|
||||
#define SHARED_EPILOG() do { __asm__ __volatile__("" ::: "memory"); \
|
||||
__asm__ goto("" :::: shared_epilog_); shared_epilog_:; } while (0)
|
||||
|
||||
// Same trick for any mid-function join (usable several times per function):
|
||||
// stops clang 23 from tail-duplicating the following block into each predecessor.
|
||||
#define JOIN_AT_(n) do { __asm__ goto("" :::: join_##n##_); join_##n##_:; } while (0)
|
||||
#define JOIN_AT(n) JOIN_AT_(n)
|
||||
#define JOIN() JOIN_AT(__COUNTER__)
|
||||
|
||||
// module logging/format helper (0x0ab710): log_fmt(self, len, fmt, arg)
|
||||
extern "C" void log_fmt(void *self, int len, const char *fmt, ...);
|
||||
@@ -0,0 +1,174 @@
|
||||
// cJSON internals (upstream cJSON 1.7.x, MIT) mapped onto the image's globals.
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
typedef int cJSON_bool;
|
||||
#define false 0
|
||||
#define true 1
|
||||
#define cJSON_Invalid (0)
|
||||
#define cJSON_False (1 << 0)
|
||||
#define cJSON_True (1 << 1)
|
||||
#define cJSON_NULL (1 << 2)
|
||||
#define cJSON_Number (1 << 3)
|
||||
#define cJSON_String (1 << 4)
|
||||
#define cJSON_Array (1 << 5)
|
||||
#define cJSON_Object (1 << 6)
|
||||
#define cJSON_Raw (1 << 7)
|
||||
#define cJSON_IsReference 256
|
||||
#define cJSON_StringIsConst 512
|
||||
typedef struct cJSON {
|
||||
struct cJSON *next, *prev, *child;
|
||||
int type;
|
||||
char *valuestring;
|
||||
int valueint;
|
||||
double valuedouble;
|
||||
char *string;
|
||||
} cJSON;
|
||||
typedef struct internal_hooks {
|
||||
void *(*allocate)(size_t size);
|
||||
void (*deallocate)(void *pointer);
|
||||
void *(*reallocate)(void *pointer, size_t size);
|
||||
} internal_hooks;
|
||||
typedef struct { const unsigned char *json; size_t position; } error;
|
||||
typedef struct {
|
||||
const unsigned char *content; size_t length; size_t offset; size_t depth; internal_hooks hooks;
|
||||
} parse_buffer;
|
||||
typedef struct {
|
||||
unsigned char *buffer; size_t length; size_t offset; size_t depth; cJSON_bool noalloc; cJSON_bool format;
|
||||
internal_hooks hooks;
|
||||
} printbuffer;
|
||||
extern "C" internal_hooks block_in_speed_setting_field_0x38;
|
||||
extern "C" error cjson_state;
|
||||
extern "C" double not_a_number; // NAN
|
||||
#define global_hooks block_in_speed_setting_field_0x38
|
||||
#define global_error cjson_state
|
||||
#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length))
|
||||
#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index))
|
||||
#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset)
|
||||
#define cjson_min(a, b) (((a) < (b)) ? (a) : (b))
|
||||
extern "C" __attribute__((pure)) size_t libc_strlen(const char *s); // strlen
|
||||
extern "C" __attribute__((pure)) int libc_strncmp(const char *a, const char *b, size_t n); // strncmp
|
||||
extern "C" __attribute__((pure)) int libc_strcmp(const char *a, const char *b); // strcmp
|
||||
extern "C" void *libc_memcpy_alt(void *d, const void *s, size_t n); // memcpy
|
||||
#define strlen libc_strlen
|
||||
#define strncmp libc_strncmp
|
||||
#define strcmp libc_strcmp
|
||||
#define memcpy libc_memcpy_alt
|
||||
#define memset __builtin_memset
|
||||
static inline __attribute__((always_inline)) cJSON_bool cJSON_IsString(const cJSON *const item) {
|
||||
if (item == NULL) return false;
|
||||
return (item->type & 0xFF) == cJSON_String;
|
||||
}
|
||||
static inline __attribute__((always_inline)) cJSON_bool cJSON_IsNumber(const cJSON *const item) {
|
||||
if (item == NULL) return false;
|
||||
return (item->type & 0xFF) == cJSON_Number;
|
||||
}
|
||||
static inline __attribute__((always_inline)) cJSON *cJSON_New_Item(const internal_hooks *const hooks) {
|
||||
cJSON *node = (cJSON *)hooks->allocate(sizeof(cJSON));
|
||||
if (node) memset(node, '\0', sizeof(cJSON));
|
||||
return node;
|
||||
}
|
||||
static inline __attribute__((always_inline)) void update_offset(printbuffer *const buffer) {
|
||||
const unsigned char *buffer_pointer = NULL;
|
||||
if ((buffer == NULL) || (buffer->buffer == NULL)) return;
|
||||
buffer_pointer = buffer->buffer + buffer->offset;
|
||||
buffer->offset += strlen((const char *)buffer_pointer);
|
||||
}
|
||||
|
||||
// ---- shared static helpers (inlined into every public function, as in the original) ----
|
||||
#define INLINE static inline __attribute__((always_inline))
|
||||
extern "C" double threshold_2_14748e09, threshold_neg_2_14748e09; // INT_MAX, INT_MIN as doubles
|
||||
#define INT_MAX 2147483647
|
||||
#define INT_MIN (-INT_MAX - 1)
|
||||
extern "C" void cjson_delete(cJSON *item); // cJSON_Delete
|
||||
INLINE unsigned char *cJSON_strdup(const unsigned char *string, const internal_hooks *const hooks) {
|
||||
size_t length = 0;
|
||||
unsigned char *copy = NULL;
|
||||
if (string == NULL) return NULL;
|
||||
length = strlen((const char *)string) + sizeof("");
|
||||
copy = (unsigned char *)hooks->allocate(length);
|
||||
if (copy == NULL) return NULL;
|
||||
memcpy(copy, string, length);
|
||||
return copy;
|
||||
}
|
||||
INLINE void suffix_object(cJSON *prev, cJSON *item) { prev->next = item; item->prev = prev; }
|
||||
INLINE cJSON_bool add_item_to_array(cJSON *array, cJSON *item) {
|
||||
cJSON *child = NULL;
|
||||
if ((item == NULL) || (array == NULL) || (array == item)) return false;
|
||||
child = array->child;
|
||||
if (child == NULL) {
|
||||
array->child = item;
|
||||
item->prev = item;
|
||||
item->next = NULL;
|
||||
} else {
|
||||
if (child->prev) {
|
||||
suffix_object(child->prev, item);
|
||||
child->prev = item;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
INLINE cJSON_bool add_item_to_object(cJSON *const object, const char *const string, cJSON *const item,
|
||||
const internal_hooks *const hooks, const cJSON_bool constant_key) {
|
||||
char *new_key = NULL;
|
||||
int new_type = cJSON_Invalid;
|
||||
if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) return false;
|
||||
if (constant_key) {
|
||||
new_key = (char *)string;
|
||||
new_type = item->type | cJSON_StringIsConst;
|
||||
} else {
|
||||
new_key = (char *)cJSON_strdup((const unsigned char *)string, hooks);
|
||||
if (new_key == NULL) return false;
|
||||
new_type = item->type & ~cJSON_StringIsConst;
|
||||
}
|
||||
if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) hooks->deallocate(item->string);
|
||||
item->string = new_key;
|
||||
item->type = new_type;
|
||||
return add_item_to_array(object, item);
|
||||
}
|
||||
INLINE cJSON *cJSON_CreateBool(cJSON_bool boolean) {
|
||||
cJSON *item = cJSON_New_Item(&global_hooks);
|
||||
if (item) item->type = boolean ? cJSON_True : cJSON_False;
|
||||
return item;
|
||||
}
|
||||
INLINE cJSON *cJSON_CreateNumber(double num) {
|
||||
cJSON *item = cJSON_New_Item(&global_hooks);
|
||||
if (item) {
|
||||
item->type = cJSON_Number;
|
||||
item->valuedouble = num;
|
||||
if (num >= threshold_2_14748e09) item->valueint = INT_MAX;
|
||||
else if (num <= threshold_neg_2_14748e09) item->valueint = INT_MIN;
|
||||
else item->valueint = (int)num;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
INLINE cJSON *cJSON_CreateString(const char *string) {
|
||||
cJSON *item = cJSON_New_Item(&global_hooks);
|
||||
if (item) {
|
||||
item->type = cJSON_String;
|
||||
item->valuestring = (char *)cJSON_strdup((const unsigned char *)string, &global_hooks);
|
||||
if (!item->valuestring) {
|
||||
cjson_delete(item);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
INLINE cJSON *cJSON_CreateObject(void) {
|
||||
cJSON *item = cJSON_New_Item(&global_hooks);
|
||||
if (item) item->type = cJSON_Object;
|
||||
return item;
|
||||
}
|
||||
INLINE cJSON *cJSON_CreateArray(void) {
|
||||
cJSON *item = cJSON_New_Item(&global_hooks);
|
||||
if (item) item->type = cJSON_Array;
|
||||
return item;
|
||||
}
|
||||
#define CJSON_NESTING_LIMIT 1000
|
||||
extern "C" void buffer_skip_whitespace_unused_return(parse_buffer *const buffer); // buffer_skip_whitespace (void-returning copy)
|
||||
INLINE parse_buffer *buffer_skip_whitespace_i(parse_buffer *const buffer) {
|
||||
if ((buffer == NULL) || (buffer->content == NULL)) return NULL;
|
||||
if (cannot_access_at_index(buffer, 0)) return buffer;
|
||||
while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) buffer->offset++;
|
||||
if (buffer->offset == buffer->length) buffer->offset--;
|
||||
return buffer;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Java class-file constant pool access (the client patches classes in ClassFileLoadHook).
|
||||
// Error codes: 0x2af8 truncated, 0x2afd bad index, 0x2afe wrong tag.
|
||||
#pragma once
|
||||
struct CP { const unsigned char **e; unsigned short n; char pad[6]; const unsigned char *end; };
|
||||
struct Utf8 { const unsigned char *p; unsigned short len; };
|
||||
struct NameType { Utf8 name, desc; };
|
||||
struct MemberRef { Utf8 cls; NameType nt; };
|
||||
static inline __attribute__((always_inline)) unsigned short be16(const unsigned char *p) {
|
||||
unsigned short v = *(const unsigned short *)p;
|
||||
return (unsigned short)((v << 8) | (v >> 8));
|
||||
}
|
||||
// entry `idx` (1-based) and the end of its bytes (start of the next entry, or the pool end)
|
||||
#define CP_ENTRY(cp, idx, ent, lim, fail) \
|
||||
do { \
|
||||
if ((idx) == 0) goto fail; \
|
||||
unsigned short i_ = (unsigned short)((idx) - 1); \
|
||||
if (i_ >= (cp)->n) goto fail; \
|
||||
ent = (cp)->e[i_]; \
|
||||
if (!ent) goto fail; \
|
||||
const unsigned char **nx_ = &(cp)->e[i_]; \
|
||||
\
|
||||
lim = *((idx) == (cp)->n ? &(cp)->end : nx_ + 1); \
|
||||
} while (0)
|
||||
@@ -0,0 +1,33 @@
|
||||
// Engine-side types and helpers shared by the recoded module logic.
|
||||
// Everything is named after its original RVA until its purpose is understood.
|
||||
#pragma once
|
||||
#include "slinky/api.h"
|
||||
|
||||
// Per-hook context handed to every module callback (rcx at +0x08 points here).
|
||||
struct Ctx {
|
||||
void *jvm; // +0x00 JNI/JVM handle passed as the first argument of every java_* call
|
||||
void *module_manager; // +0x08 target of queue_module_toggle
|
||||
void *profile_list; // +0x10
|
||||
void *minecraft; // +0x18 Minecraft instance
|
||||
void *world; // +0x20 client world
|
||||
void *player; // +0x28 local player entity
|
||||
void *key_state_table; // +0x30
|
||||
void *target; // +0x38 current combat target record (entity, squared distance, ...)
|
||||
unsigned char in_game; // +0x40 non-zero while a world is loaded
|
||||
};
|
||||
|
||||
// Current combat target record (Ctx::target)
|
||||
struct CombatTarget {
|
||||
void *padding_00;
|
||||
void *ent; // +0x08 target entity
|
||||
double dist2; // +0x10 squared distance to it
|
||||
unsigned char valid; // +0x18
|
||||
};
|
||||
|
||||
// Hook argument block (rcx of every descriptor callback).
|
||||
struct Hook {
|
||||
char pad0[8];
|
||||
Ctx *ctx; // +0x08
|
||||
};
|
||||
|
||||
#define FN(ret, name, args) extern "C" ret name args
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,117 @@
|
||||
// Layout of the client GUI state block (the `gui` argument of every GUI helper).
|
||||
// Field offsets match the original image; overlapping uses are expressed as unions.
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
|
||||
struct GuiTab { int kind; int pad; const char *label; }; // one entry of the tab bar (kind 6 = unload)
|
||||
struct GuiVec2 { float x, y; };
|
||||
struct GuiColor { float r, g, b, a; };
|
||||
struct GuiRgb { float r, g, b; };
|
||||
struct GuiHsl { float h, s, l; };
|
||||
|
||||
struct GuiCell { float x, y, w, h; };
|
||||
// one entry of the layout-row stack (see push_layout_row / add_cell)
|
||||
struct GuiLayoutRow {
|
||||
char pad[4]; float top; float width; int padw; float y0; int pad14; unsigned char clip; char pad2[7];
|
||||
float bottom; int pad3; long long cols; GuiCell cell[4]; unsigned long long items;
|
||||
};
|
||||
static_assert(sizeof(GuiLayoutRow) == 0x78, "layout row size");
|
||||
|
||||
struct GuiState {
|
||||
GuiLayoutRow rows[5]; // 0x000 layout-row stack
|
||||
long long layout_row_depth; // 0x258 number of rows currently pushed
|
||||
int layout_column_count; // 0x260
|
||||
char pad_264[4];
|
||||
unsigned char toast_visible; // 0x268
|
||||
char pad_269;
|
||||
char toast_text[0x66]; // 0x26a message shown by the toast at the top of the window
|
||||
float toast_x; // 0x2d0
|
||||
float toast_y; // 0x2d4
|
||||
float toast_fade; // 0x2d8 0 -> 1 while the toast fades in
|
||||
char pad_2dc[4];
|
||||
GuiTab tabs[7]; // 0x2e0
|
||||
unsigned long long selected_tab; // 0x350
|
||||
float tab_highlight_pos; // 0x358 animated position of the tab highlight
|
||||
char pad_35c[4];
|
||||
GuiColor accent_color; // 0x360
|
||||
union {
|
||||
int input_mode; // 0x370 0 idle, 1 key-bind capture, 2/3 text field being edited
|
||||
unsigned input_mode_u;
|
||||
};
|
||||
char pad_374[4];
|
||||
union { // 0x378
|
||||
char edit_text[0x68]; // text being edited (input_mode 2/3)
|
||||
struct {
|
||||
void *keybind_callback; // key-bind capture (input_mode 1)
|
||||
void *keybind_target; // 0x380
|
||||
};
|
||||
};
|
||||
void *edit_char_filter; // 0x3e0 per-character filter of the text field
|
||||
void *edit_filter_arg; // 0x3e8
|
||||
unsigned char edit_confirmed; // 0x3f0
|
||||
char pad_3f1[7];
|
||||
union {
|
||||
long long popup_kind; // 0x3f8 0 none, 1 callback, 2 colour picker, 3 confirm, 4 list picker
|
||||
int popup_kind_i;
|
||||
unsigned popup_kind_u;
|
||||
};
|
||||
void *popup_context; // 0x400
|
||||
union {
|
||||
void *popup_title; // 0x408
|
||||
const char *popup_title_text;
|
||||
const char ***popup_title_items;
|
||||
};
|
||||
double popup_open_time; // 0x410
|
||||
float popup_opacity; // 0x418
|
||||
float popup_height; // 0x41c
|
||||
unsigned char popup_animating; // 0x420
|
||||
char pad_421[7];
|
||||
void *popup_callback; // 0x428
|
||||
union { // 0x430 payload, depends on popup_kind
|
||||
char popup_message[0x68]; // confirm: formatted message
|
||||
struct { // colour picker
|
||||
GuiRgb popup_rgb; // 0x430
|
||||
GuiHsl popup_hsl; // 0x43c
|
||||
};
|
||||
struct { // list picker
|
||||
void *popup_list_callback; // 0x430
|
||||
void *popup_list_items; // 0x438
|
||||
union { void *popup_list_extra; unsigned long long popup_list_extra_u; }; // 0x440
|
||||
double popup_list_state; // 0x448
|
||||
void *popup_list_view; // 0x450
|
||||
unsigned long long popup_list_count; // 0x458
|
||||
};
|
||||
};
|
||||
double card_anim_start_time; // 0x498
|
||||
double profile_refresh_time; // 0x4a0
|
||||
GuiVec2 mouse_scene; // 0x4a8 mouse position in scaled window space
|
||||
GuiVec2 mouse_raw; // 0x4b0
|
||||
union {
|
||||
float card_open_progress; // 0x4b8
|
||||
int card_open_progress_i;
|
||||
};
|
||||
float scroll; // 0x4bc
|
||||
float scroll_max; // 0x4c0
|
||||
float row_height; // 0x4c4
|
||||
unsigned char click_capture; // 0x4c8
|
||||
unsigned char click_consumed; // 0x4c9
|
||||
unsigned char numeric_drag_enabled; // 0x4ca
|
||||
unsigned char unload_requested; // 0x4cb
|
||||
unsigned char card_layout_mode; // 0x4cc
|
||||
unsigned char wide_layout; // 0x4cd
|
||||
};
|
||||
|
||||
#define GUI_CHECK(field, off) static_assert(offsetof(GuiState, field) == off, #field)
|
||||
GUI_CHECK(layout_row_depth, 0x258); GUI_CHECK(toast_visible, 0x268); GUI_CHECK(toast_text, 0x26a);
|
||||
GUI_CHECK(toast_x, 0x2d0); GUI_CHECK(toast_fade, 0x2d8); GUI_CHECK(tabs, 0x2e0); GUI_CHECK(selected_tab, 0x350);
|
||||
GUI_CHECK(tab_highlight_pos, 0x358); GUI_CHECK(accent_color, 0x360); GUI_CHECK(input_mode, 0x370);
|
||||
GUI_CHECK(edit_text, 0x378); GUI_CHECK(keybind_target, 0x380); GUI_CHECK(edit_char_filter, 0x3e0);
|
||||
GUI_CHECK(edit_filter_arg, 0x3e8); GUI_CHECK(edit_confirmed, 0x3f0); GUI_CHECK(popup_kind, 0x3f8);
|
||||
GUI_CHECK(popup_context, 0x400); GUI_CHECK(popup_title, 0x408); GUI_CHECK(popup_open_time, 0x410);
|
||||
GUI_CHECK(popup_opacity, 0x418); GUI_CHECK(popup_height, 0x41c); GUI_CHECK(popup_animating, 0x420);
|
||||
GUI_CHECK(popup_callback, 0x428); GUI_CHECK(popup_message, 0x430); GUI_CHECK(popup_rgb, 0x430);
|
||||
GUI_CHECK(popup_hsl, 0x43c); GUI_CHECK(popup_list_extra, 0x440); GUI_CHECK(popup_list_state, 0x448);
|
||||
GUI_CHECK(popup_list_view, 0x450); GUI_CHECK(popup_list_count, 0x458); GUI_CHECK(card_anim_start_time, 0x498);
|
||||
GUI_CHECK(profile_refresh_time, 0x4a0); GUI_CHECK(mouse_scene, 0x4a8); GUI_CHECK(mouse_raw, 0x4b0);
|
||||
GUI_CHECK(card_open_progress, 0x4b8); GUI_CHECK(scroll, 0x4bc); GUI_CHECK(row_height, 0x4c4);
|
||||
GUI_CHECK(click_capture, 0x4c8); GUI_CHECK(wide_layout, 0x4cd);
|
||||
@@ -0,0 +1,16 @@
|
||||
// JNI bridge call in the shape the original compiler emitted: ids and env loaded first, then the
|
||||
// JNIEnv function pointer, then the call (the barrier keeps the pointer load from folding into the jmp/call).
|
||||
#pragma once
|
||||
#include "slinky/jids.h"
|
||||
#define JCALL(j, Fn, ...) ({ JIds *ids = (j)->ids; JNIEnv *env = (j)->env; auto f_ = (*env)->Fn; \
|
||||
asm("" : "+r"(f_)); f_(env, ##__VA_ARGS__); })
|
||||
// Non-tail call: the compiler folds the function-pointer load into the call.
|
||||
#define JCALLN(j, Fn, ...) ({ JIds *ids = (j)->ids; JNIEnv *env = (j)->env; (*env)->Fn(env, ##__VA_ARGS__); })
|
||||
// Call whose first two arguments after env are cached IDs (class+method, NewObject/CallStatic*):
|
||||
// the original loads both IDs before evaluating the remaining arguments.
|
||||
#define JCALL2(j, Fn, A, B, ...) ({ JIds *ids = (j)->ids; JNIEnv *env = (j)->env; auto vt_ = *env; \
|
||||
asm("" : "+r"(vt_)); auto a_ = (A); auto b_ = (B); \
|
||||
asm("" : "+r"(a_), "+r"(b_)); vt_->Fn(env, a_, b_, ##__VA_ARGS__); })
|
||||
// Call<T>Method(obj, ID, args...) with extra arguments: vtable and ID loaded before the arguments.
|
||||
#define JCALL1(j, Fn, O, A, ...) ({ JIds *ids = (j)->ids; JNIEnv *env = (j)->env; auto vt_ = *env; \
|
||||
asm("" : "+r"(vt_)); auto a_ = (A); asm("" : "+r"(a_)); vt_->Fn(env, O, a_, ##__VA_ARGS__); })
|
||||
@@ -0,0 +1,429 @@
|
||||
// Cached JNI IDs of the engine (filled once by load_all_jni_ids). Generated by tools/gen_jids.py.
|
||||
// Member = <Class>_<member>; the comment gives the kind (cls/m/f/sm/sf) and the JVM signature.
|
||||
#pragma once
|
||||
#include "slinky/jnienv.h"
|
||||
struct JIds {
|
||||
int variant; // 0x0 non-zero: alternate mappings
|
||||
int names; // 0x4 mapping names: 0 obfuscated, 1 MCP, 2 SRG
|
||||
void *hasBlockPos;
|
||||
void *gameClassLoader;
|
||||
void *gameClassLoaderLoadClass;
|
||||
jclass List; // 0x20 cls
|
||||
jmethodID List_size; // 0x28 m ()I
|
||||
jmethodID List_get; // 0x30 m (I)Ljava/lang/Object;
|
||||
jmethodID List_add; // 0x38 m (Ljava/lang/Object;)Z
|
||||
jmethodID List_remove; // 0x40 m (Ljava/lang/Object;)Z
|
||||
jclass Map; // 0x48 cls
|
||||
jmethodID Map_containsKey; // 0x50 m (Ljava/lang/Object;)Z
|
||||
jmethodID Map_get; // 0x58 m (Ljava/lang/Object;)Ljava/lang/Object;
|
||||
jclass Keyboard; // 0x60 cls
|
||||
jmethodID Keyboard_isKeyDown; // 0x68 sm (I)Z
|
||||
jmethodID Keyboard_next; // 0x70 sm ()Z
|
||||
jmethodID Keyboard_getEventKey; // 0x78 sm ()I
|
||||
jmethodID Keyboard_getEventKeyState; // 0x80 sm ()Z
|
||||
jclass Mouse; // 0x88 cls
|
||||
jmethodID Mouse_isGrabbed; // 0x90 sm ()Z
|
||||
jmethodID Mouse_setGrabbed; // 0x98 sm (Z)V
|
||||
jmethodID Mouse_isButtonDown; // 0xa0 sm (I)Z
|
||||
jmethodID Mouse_next; // 0xa8 sm ()Z
|
||||
jmethodID Mouse_setCursorPosition; // 0xb0 sm (II)V
|
||||
jclass Display; // 0xb8 cls
|
||||
jmethodID Display_update; // 0xc0 sm ()V
|
||||
jmethodID Display_isActive; // 0xc8 sm ()Z
|
||||
jfieldID Display_width; // 0xd0 sf I
|
||||
jfieldID Display_height; // 0xd8 sf I
|
||||
jfieldID Display_framebufferWidth; // 0xe0 sf I
|
||||
jfieldID Display_framebufferHeight; // 0xe8 sf I
|
||||
jclass Object; // 0xf0 cls
|
||||
jmethodID Object_hashCode; // 0xf8 m ()I
|
||||
jclass SecureClassLoader; // 0x100 cls
|
||||
jmethodID SecureClassLoader__init_; // 0x108 m (Ljava/lang/ClassLoader;)V
|
||||
jclass Channel; // 0x110 cls
|
||||
jmethodID Channel_pipeline; // 0x118 m ()Lio/netty/channel/ChannelPipeline;
|
||||
jclass ChannelPipeline; // 0x120 cls
|
||||
jmethodID ChannelPipeline_addBefore; // 0x128 m
|
||||
jmethodID ChannelPipeline_get; // 0x130 m (Ljava/lang/Class;)Lio/netty/channel/ChannelHandler;
|
||||
jmethodID ChannelPipeline_remove; // 0x138 m (Ljava/lang/Class;)Lio/netty/channel/ChannelHandler;
|
||||
jclass Enum; // 0x140 cls
|
||||
jmethodID Enum_ordinal; // 0x148 m ()I
|
||||
jclass Minecraft; // 0x150 cls
|
||||
jmethodID Minecraft_getNetHandler; // 0x158 m
|
||||
void *unused_slot_160;
|
||||
jmethodID Minecraft_addScheduledTask; // 0x168 m (Ljava/lang/Runnable;)Lcom/google/common/util/concurrent/ListenableFuture;
|
||||
jmethodID Minecraft_clickMouse; // 0x170 m ()V
|
||||
jmethodID Minecraft_rightClickMouse; // 0x178 m ()V
|
||||
jfieldID Minecraft_theMinecraft; // 0x180 sf
|
||||
void *Minecraft_thePlayer;
|
||||
jfieldID Minecraft_theWorld; // 0x190 f
|
||||
jfieldID Minecraft_timer; // 0x198 f
|
||||
jfieldID Minecraft_gameSettings; // 0x1a0 f
|
||||
jfieldID Minecraft_inGameHasFocus; // 0x1a8 f Z
|
||||
jfieldID Minecraft_objectMouseOver; // 0x1b0 f
|
||||
jfieldID Minecraft_leftClickCounter; // 0x1b8 f I
|
||||
jfieldID Minecraft_rightClickDelayTimer; // 0x1c0 f I
|
||||
jfieldID Minecraft_currentScreen; // 0x1c8 f
|
||||
jfieldID Minecraft_ingameGUI; // 0x1d0 f
|
||||
jfieldID Minecraft_displayWidth; // 0x1d8 f I
|
||||
jfieldID Minecraft_displayHeight; // 0x1e0 f I
|
||||
jfieldID Minecraft_renderEngine; // 0x1e8 f
|
||||
jfieldID Minecraft_renderManager; // 0x1f0 f
|
||||
jfieldID Minecraft_renderViewEntity; // 0x1f8 f
|
||||
jfieldID Minecraft_renderGlobal; // 0x200 f
|
||||
jfieldID Minecraft_renderItem; // 0x208 f
|
||||
jfieldID Minecraft_entityRenderer; // 0x210 f
|
||||
jfieldID Minecraft_playerController; // 0x218 f
|
||||
jclass Entity; // 0x220 cls
|
||||
jmethodID Entity_isSprinting; // 0x228 m ()Z
|
||||
jmethodID Entity_setSprinting; // 0x230 m (Z)V
|
||||
jmethodID Entity_isSneaking; // 0x238 m ()Z
|
||||
jmethodID Entity_getFormattedCommandSenderName; // 0x240 m
|
||||
jmethodID Entity_getCommandSenderName; // 0x248 m ()Ljava/lang/String;
|
||||
jmethodID Entity_setAngles; // 0x250 m (FF)V
|
||||
jmethodID Entity_moveFlying; // 0x258 m (FFF)V
|
||||
jmethodID Entity_canBeCollidedWith; // 0x260 m ()Z
|
||||
jfieldID Entity_posX; // 0x268 f D
|
||||
jfieldID Entity_posY; // 0x270 f D
|
||||
jfieldID Entity_posZ; // 0x278 f D
|
||||
jfieldID Entity_prevPosX; // 0x280 f D
|
||||
jfieldID Entity_prevPosY; // 0x288 f D
|
||||
jfieldID Entity_prevPosZ; // 0x290 f D
|
||||
jfieldID Entity_rotationYaw; // 0x298 f F
|
||||
jfieldID Entity_rotationPitch; // 0x2a0 f F
|
||||
jfieldID Entity_prevRotationYaw; // 0x2a8 f F
|
||||
jfieldID Entity_prevRotationPitch; // 0x2b0 f F
|
||||
jfieldID Entity_motionX; // 0x2b8 f D
|
||||
jfieldID Entity_motionY; // 0x2c0 f D
|
||||
jfieldID Entity_motionZ; // 0x2c8 f D
|
||||
jfieldID Entity_boundingBox; // 0x2d0 f
|
||||
jfieldID Entity_ticksExisted; // 0x2d8 f I
|
||||
jfieldID Entity_entityId; // 0x2e0 f I
|
||||
jfieldID Entity_uniqueId; // 0x2e8 f Ljava/util/UUID;
|
||||
jfieldID Entity_onGround; // 0x2f0 f Z
|
||||
jfieldID Entity_isDead; // 0x2f8 f Z
|
||||
jfieldID Entity_fallDistance; // 0x300 f F
|
||||
jfieldID Entity_ridingEntity; // 0x308 f
|
||||
jclass World; // 0x310 cls
|
||||
jmethodID World_getEntityByID; // 0x318 m
|
||||
void *World_getBlock;
|
||||
void *World_getBlockState;
|
||||
jmethodID World_rayTraceBlocks; // 0x330 m
|
||||
jmethodID World_updateEntities; // 0x338 m ()V
|
||||
jmethodID World_getChunkFromChunkCoords; // 0x340 m
|
||||
jmethodID World_markBlockRangeForRenderUpdate; // 0x348 m (IIIIII)V
|
||||
jmethodID World_setBlock; // 0x350 m
|
||||
jmethodID World_setBlockState; // 0x358 m
|
||||
jfieldID World_playerEntities; // 0x360 f Ljava/util/List;
|
||||
jfieldID World_loadedEntityList; // 0x368 f Ljava/util/List;
|
||||
jfieldID World_loadedTileEntityList; // 0x370 f Ljava/util/List;
|
||||
jfieldID World_chunkProvider; // 0x378 f
|
||||
jclass Timer; // 0x380 cls
|
||||
jfieldID Timer_renderPartialTicks; // 0x388 f F
|
||||
jfieldID Timer_timerSpeed; // 0x390 f F
|
||||
jfieldID Timer_elapsedPartialTicks; // 0x398 f F
|
||||
jfieldID Timer_elapsedTicks; // 0x3a0 f I
|
||||
jclass GameSettings; // 0x3a8 cls
|
||||
jfieldID GameSettings_mouseSensitivity; // 0x3b0 f F
|
||||
jfieldID GameSettings_keyBindings; // 0x3b8 f
|
||||
jfieldID GameSettings_thirdPersonView; // 0x3c0 f I
|
||||
jclass MovingObjectPosition; // 0x3c8 cls
|
||||
jmethodID MovingObjectPosition__init_; // 0x3d0 m (Lnet/minecraft/entity/Entity;Lnet/minecraft/util/Vec3;)V
|
||||
jfieldID MovingObjectPosition_typeOfHit; // 0x3d8 f
|
||||
jfieldID MovingObjectPosition_hitVec; // 0x3e0 f
|
||||
void *MovingObjectPosition_sideHitIndex;
|
||||
void *MovingObjectPosition_sideHit;
|
||||
jfieldID MovingObjectPosition_blockX; // 0x3f8 f
|
||||
jfieldID MovingObjectPosition_blockY; // 0x400 f
|
||||
jfieldID MovingObjectPosition_blockZ; // 0x408 f
|
||||
jfieldID MovingObjectPosition_blockPos; // 0x410 f
|
||||
jfieldID MovingObjectPosition_entityHit; // 0x418 f
|
||||
jclass _init_; // 0x420 cls
|
||||
jmethodID _init__getBlock; // 0x428 m
|
||||
jclass Block; // 0x430 cls
|
||||
jmethodID Block_getIdFromBlock; // 0x438 sm
|
||||
jmethodID Block_getBlockFromItem; // 0x440 sm
|
||||
jclass BlockPos; // 0x448 cls
|
||||
jmethodID BlockPos__init_; // 0x450 m (III)V
|
||||
jfieldID BlockPos_a; // 0x458 f
|
||||
jfieldID BlockPos_c; // 0x460 f
|
||||
jfieldID BlockPos_d; // 0x468 f
|
||||
jclass KeyBinding; // 0x470 cls
|
||||
jfieldID KeyBinding_pressed; // 0x478 f Z
|
||||
jfieldID KeyBinding_pressTime; // 0x480 f I
|
||||
jfieldID KeyBinding_keyCode; // 0x488 f I
|
||||
jclass AxisAlignedBB; // 0x490 cls
|
||||
jmethodID AxisAlignedBB__init_; // 0x498 m (DDDDDD)V
|
||||
jmethodID AxisAlignedBB_calculateIntercept; // 0x4a0 m
|
||||
jfieldID AxisAlignedBB_minX; // 0x4a8 f D
|
||||
jfieldID AxisAlignedBB_minY; // 0x4b0 f D
|
||||
jfieldID AxisAlignedBB_minZ; // 0x4b8 f D
|
||||
jfieldID AxisAlignedBB_maxX; // 0x4c0 f D
|
||||
jfieldID AxisAlignedBB_maxY; // 0x4c8 f D
|
||||
jfieldID AxisAlignedBB_maxZ; // 0x4d0 f D
|
||||
jclass GuiContainer; // 0x4d8 cls
|
||||
jmethodID GuiContainer_handleMouseClick; // 0x4e0 m
|
||||
jfieldID GuiContainer_theSlot; // 0x4e8 f
|
||||
jfieldID GuiContainer_lastClickTime; // 0x4f0 f J
|
||||
jclass EntityLivingBase; // 0x4f8 cls
|
||||
void *EntityLivingBase_rayTrace;
|
||||
jmethodID EntityLivingBase_getPosition; // 0x508 m
|
||||
jmethodID EntityLivingBase_getHealth; // 0x510 m ()F
|
||||
jmethodID EntityLivingBase_getTeam; // 0x518 m
|
||||
jmethodID EntityLivingBase_swingItem; // 0x520 m ()V
|
||||
jmethodID EntityLivingBase_getMaxHealth; // 0x528 m ()F
|
||||
jmethodID EntityLivingBase_getActivePotionEffect; // 0x530 m
|
||||
jmethodID EntityLivingBase_jump; // 0x538 m ()V
|
||||
jfieldID EntityLivingBase_moveForward; // 0x540 f F
|
||||
jfieldID EntityLivingBase_moveStrafing; // 0x548 f F
|
||||
jfieldID EntityLivingBase_maxHurtTime; // 0x550 f I
|
||||
jfieldID EntityLivingBase_hurtTime; // 0x558 f I
|
||||
jfieldID EntityLivingBase_lastAttackerTime; // 0x560 f I
|
||||
jfieldID EntityLivingBase_landMovementFactor; // 0x568 f F
|
||||
jfieldID EntityLivingBase_jumpTicks; // 0x570 f I
|
||||
jclass EntityPlayer; // 0x578 cls
|
||||
jmethodID EntityPlayer_isInvisibleToPlayer; // 0x580 m
|
||||
jmethodID EntityPlayer_isUsingItem; // 0x588 m ()Z
|
||||
jmethodID EntityPlayer_getAbsorptionAmount; // 0x590 m ()F
|
||||
jmethodID EntityPlayer_getEquipmentInSlot; // 0x598 m
|
||||
jfieldID EntityPlayer_inventory; // 0x5a0 f
|
||||
jfieldID EntityPlayer_itemInUseCount; // 0x5a8 f I
|
||||
jfieldID EntityPlayer_inventoryContainer; // 0x5b0 f
|
||||
jfieldID EntityPlayer_sleeping; // 0x5b8 f Z
|
||||
jfieldID EntityPlayer_speedOnGround; // 0x5c0 f F
|
||||
jclass InventoryPlayer; // 0x5c8 cls
|
||||
jfieldID InventoryPlayer_mainInventory; // 0x5d0 f
|
||||
jfieldID InventoryPlayer_currentItem; // 0x5d8 f I
|
||||
jfieldID InventoryPlayer_itemStack; // 0x5e0 f
|
||||
jclass ItemStack; // 0x5e8 cls
|
||||
jmethodID ItemStack__init_; // 0x5f0 m
|
||||
jmethodID ItemStack_getItem; // 0x5f8 m
|
||||
jmethodID ItemStack_getStrVsBlock; // 0x600 m
|
||||
jmethodID ItemStack_getEnchantmentTagList; // 0x608 m
|
||||
jmethodID ItemStack_getMaxStackSize; // 0x610 m ()I
|
||||
jmethodID ItemStack_areItemStackTagsEqual; // 0x618 sm
|
||||
jmethodID ItemStack_hasDisplayName; // 0x620 m ()Z
|
||||
jfieldID ItemStack_stackSize; // 0x628 f I
|
||||
jfieldID ItemStack_metadata; // 0x630 f I
|
||||
jclass Item; // 0x638 cls
|
||||
jmethodID Item_getIdFromItem; // 0x640 sm
|
||||
jmethodID Item_getItemById; // 0x648 sm
|
||||
jclass Vec3; // 0x650 cls
|
||||
jmethodID Vec3__init_; // 0x658 m (DDD)V
|
||||
jfieldID Vec3_xCoord; // 0x660 f D
|
||||
jfieldID Vec3_yCoord; // 0x668 f D
|
||||
jfieldID Vec3_zCoord; // 0x670 f D
|
||||
jclass ScorePlayerTeam; // 0x678 cls
|
||||
jfieldID ScorePlayerTeam_Ljava_lang_String_; // 0x680 f Ljava/lang/String;
|
||||
jclass NetHandlerPlayClient; // 0x688 cls
|
||||
jfieldID NetHandlerPlayClient_playerInfoMap; // 0x690 f Ljava/util/Map;
|
||||
jfieldID NetHandlerPlayClient_netManager; // 0x698 f
|
||||
jclass IChatComponent; // 0x6a0 cls
|
||||
jmethodID IChatComponent_getFormattedText; // 0x6a8 m ()Ljava/lang/String;
|
||||
jclass IChatComponent_Serializer; // 0x6b0 cls
|
||||
jmethodID IChatComponent_Serializer_jsonToComponent; // 0x6b8 sm
|
||||
jclass Slot; // 0x6c0 cls
|
||||
jfieldID Slot_slotNumber; // 0x6c8 f I
|
||||
jmethodID Slot_getStack; // 0x6d0 m
|
||||
jclass IInventory; // 0x6d8 cls
|
||||
jmethodID IInventory_getStackInSlot; // 0x6e0 m
|
||||
jclass NetworkManager; // 0x6e8 cls
|
||||
jfieldID NetworkManager_Lio_netty_channel_Channel_; // 0x6f0 f Lio/netty/channel/Channel;
|
||||
void *NetworkManager_scheduleOutboundPacket;
|
||||
void *NetworkManager_sendPacket;
|
||||
jclass C03PacketPlayer; // 0x708 cls
|
||||
jfieldID C03PacketPlayer_moving; // 0x710 f Z
|
||||
jfieldID C03PacketPlayer_onGround; // 0x718 f Z
|
||||
jfieldID C03PacketPlayer_a; // 0x720 f D
|
||||
jfieldID C03PacketPlayer_b; // 0x728 f D
|
||||
jfieldID C03PacketPlayer_c; // 0x730 f D
|
||||
jclass C07PacketPlayerDigging; // 0x738 cls
|
||||
jfieldID C07PacketPlayerDigging_status; // 0x740 f
|
||||
jclass C08PacketPlayerBlockPlacement; // 0x748 cls
|
||||
jfieldID C08PacketPlayerBlockPlacement_stack; // 0x750 f
|
||||
jclass TileEntity; // 0x758 cls
|
||||
jfieldID TileEntity_blockType; // 0x760 f
|
||||
void *TileEntity_pos;
|
||||
void *TileEntity_xCoord;
|
||||
jfieldID TileEntity_yCoord; // 0x778 f
|
||||
jfieldID TileEntity_zCoord; // 0x780 f
|
||||
jclass S12PacketEntityVelocity; // 0x788 cls
|
||||
jfieldID S12PacketEntityVelocity_entityID; // 0x790 f I
|
||||
jfieldID S12PacketEntityVelocity_motionX; // 0x798 f I
|
||||
jfieldID S12PacketEntityVelocity_motionY; // 0x7a0 f I
|
||||
jfieldID S12PacketEntityVelocity_motionZ; // 0x7a8 f I
|
||||
jclass RenderManager; // 0x7b0 cls
|
||||
jfieldID RenderManager_instance; // 0x7b8 sf
|
||||
jfieldID RenderManager_playerViewX; // 0x7c0 f F
|
||||
jfieldID RenderManager_playerViewY; // 0x7c8 f F
|
||||
jfieldID RenderManager_textRenderer; // 0x7d0 f
|
||||
jclass S14PacketEntity; // 0x7d8 cls
|
||||
jfieldID S14PacketEntity_entityId; // 0x7e0 f I
|
||||
jfieldID S14PacketEntity_posX; // 0x7e8 f B
|
||||
jfieldID S14PacketEntity_posY; // 0x7f0 f B
|
||||
jfieldID S14PacketEntity_posZ; // 0x7f8 f B
|
||||
jfieldID S14PacketEntity_yaw; // 0x800 f B
|
||||
jfieldID S14PacketEntity_pitch; // 0x808 f B
|
||||
jfieldID S14PacketEntity_g; // 0x810 f Z
|
||||
jclass S18PacketEntityTeleport; // 0x818 cls
|
||||
jfieldID S18PacketEntityTeleport_entityId; // 0x820 f I
|
||||
jfieldID S18PacketEntityTeleport_posX; // 0x828 f I
|
||||
jfieldID S18PacketEntityTeleport_posY; // 0x830 f I
|
||||
jfieldID S18PacketEntityTeleport_posZ; // 0x838 f I
|
||||
jfieldID S18PacketEntityTeleport_yaw; // 0x840 f B
|
||||
jfieldID S18PacketEntityTeleport_pitch; // 0x848 f B
|
||||
jclass S19PacketEntityStatus; // 0x850 cls
|
||||
jfieldID S19PacketEntityStatus_entityId; // 0x858 f I
|
||||
jclass S29PacketSoundEffect; // 0x860 cls
|
||||
jclass S06PacketUpdateHealth; // 0x868 cls
|
||||
jclass EntityOtherPlayerMP; // 0x870 cls
|
||||
jfieldID EntityOtherPlayerMP_otherPlayerMPPosRotationIncrements; // 0x878 f I
|
||||
jfieldID EntityOtherPlayerMP_otherPlayerMPX; // 0x880 f D
|
||||
jfieldID EntityOtherPlayerMP_otherPlayerMPY; // 0x888 f D
|
||||
jfieldID EntityOtherPlayerMP_otherPlayerMPZ; // 0x890 f D
|
||||
jfieldID EntityOtherPlayerMP_otherPlayerMPYaw; // 0x898 f D
|
||||
jclass GuiInventory; // 0x8a0 cls
|
||||
jclass EntityPlayerSP; // 0x8a8 cls
|
||||
jfieldID EntityPlayerSP_renderArmYaw; // 0x8b0 f F
|
||||
jfieldID EntityPlayerSP_renderArmPitch; // 0x8b8 f F
|
||||
jfieldID EntityPlayerSP_prevRenderArmYaw; // 0x8c0 f F
|
||||
jfieldID EntityPlayerSP_prevRenderArmPitch; // 0x8c8 f F
|
||||
jfieldID EntityPlayerSP_timeInPortal; // 0x8d0 f F
|
||||
jfieldID EntityPlayerSP_prevTimeInPortal; // 0x8d8 f F
|
||||
jmethodID EntityPlayerSP_addChatMessage; // 0x8e0 m
|
||||
jclass EntityRenderer; // 0x8e8 cls
|
||||
jmethodID EntityRenderer_getMouseOver; // 0x8f0 m (F)V
|
||||
jmethodID EntityRenderer_renderHand; // 0x8f8 m (FI)V
|
||||
jmethodID EntityRenderer_orientCamera; // 0x900 m (F)V
|
||||
jmethodID EntityRenderer_getFOVModifier; // 0x908 m (FZ)F
|
||||
void *EntityRenderer_updateCameraAndRender;
|
||||
jclass Framebuffer; // 0x918 cls
|
||||
jmethodID Framebuffer_bindFramebuffer; // 0x920 m (Z)V
|
||||
jclass RenderGlobal; // 0x928 cls
|
||||
jfieldID RenderGlobal_displayListEntitiesDirty; // 0x930 f Z
|
||||
jclass ActiveRenderInfo; // 0x938 cls
|
||||
jmethodID ActiveRenderInfo_updateRenderInfo; // 0x940 sm
|
||||
jclass RendererLivingEntity; // 0x948 cls
|
||||
void *RendererLivingEntity_renderNameMethodName;
|
||||
void *RendererLivingEntity_renderNameMethodSignature;
|
||||
jmethodID RendererLivingEntity_func_177067_a; // 0x960 m (Lpr;DDD)V
|
||||
void *RendererLivingEntity_canRenderNameMethodName;
|
||||
void *RendererLivingEntity_canRenderNameMethodSignature;
|
||||
jmethodID RendererLivingEntity_canRenderName; // 0x978 m
|
||||
void *RendererLivingEntity_doRenderMethodName;
|
||||
void *RendererLivingEntity_doRenderMethodSignature;
|
||||
jmethodID RendererLivingEntity___2; // 0x990 m
|
||||
jclass FontRenderer; // 0x998 cls
|
||||
jmethodID FontRenderer_getStringWidth; // 0x9a0 m (Ljava/lang/String;)I
|
||||
void *FontRenderer_drawString;
|
||||
jclass GlStateManager; // 0x9b0 cls
|
||||
jmethodID GlStateManager_enableDepth; // 0x9b8 sm
|
||||
jmethodID GlStateManager_disableDepth; // 0x9c0 sm
|
||||
jmethodID GlStateManager_enableLighting; // 0x9c8 sm
|
||||
jmethodID GlStateManager_disableLighting; // 0x9d0 sm
|
||||
jmethodID GlStateManager_enableTexture2D; // 0x9d8 sm
|
||||
jmethodID GlStateManager_disableTexture2D; // 0x9e0 sm
|
||||
jmethodID GlStateManager_enableBlend; // 0x9e8 sm
|
||||
jmethodID GlStateManager_disableBlend; // 0x9f0 sm
|
||||
jmethodID GlStateManager_enableCull; // 0x9f8 sm
|
||||
jmethodID GlStateManager_disableCull; // 0xa00 sm
|
||||
jmethodID GlStateManager_enableAlpha; // 0xa08 sm
|
||||
jmethodID GlStateManager_disableAlpha; // 0xa10 sm
|
||||
jmethodID GlStateManager_blendFunc; // 0xa18 sm
|
||||
jmethodID GlStateManager_color; // 0xa20 sm
|
||||
jclass C02PacketUseEntity; // 0xa28 cls
|
||||
jclass C0BPacketEntityAction; // 0xa30 cls
|
||||
jfieldID C0BPacketEntityAction_Lis_a_; // 0xa38 f
|
||||
jclass S08PacketPlayerPosLook; // 0xa40 cls
|
||||
jclass S27PacketExplosion; // 0xa48 cls
|
||||
jfieldID S27PacketExplosion_f; // 0xa50 f F
|
||||
jfieldID S27PacketExplosion_g; // 0xa58 f F
|
||||
jfieldID S27PacketExplosion_h; // 0xa60 f F
|
||||
jclass S00PacketKeepAlive; // 0xa68 cls
|
||||
jclass C00PacketKeepAlive; // 0xa70 cls
|
||||
jclass Potion; // 0xa78 cls
|
||||
jfieldID Potion_potionTypes; // 0xa80 sf
|
||||
jclass RenderPlayer; // 0xa88 cls
|
||||
void *RenderArrow_doRenderMethodName;
|
||||
void *RenderArrow_doRenderMethodSignature;
|
||||
jmethodID RenderPlayer__L_s_DDDFF_V; // 0xaa0 m
|
||||
jclass RenderItem; // 0xaa8 cls
|
||||
jfieldID RenderItem_zLevel; // 0xab0 f F
|
||||
void *RenderItem_init;
|
||||
jmethodID RenderItem__init_; // 0xac0 m
|
||||
jmethodID RenderItem_renderItemOverlayIntoGUI; // 0xac8 m
|
||||
jclass RenderHelper; // 0xad0 cls
|
||||
jmethodID RenderHelper_enableStandardItemLighting; // 0xad8 sm ()V
|
||||
jmethodID RenderHelper_disableStandardItemLighting; // 0xae0 sm ()V
|
||||
jclass EntityList; // 0xae8 cls
|
||||
jmethodID EntityList_getEntityID; // 0xaf0 sm
|
||||
jclass Chunk; // 0xaf8 cls
|
||||
jfieldID Chunk_storageArrays; // 0xb00 f
|
||||
jfieldID Chunk_xPosition; // 0xb08 f I
|
||||
jfieldID Chunk_zPosition; // 0xb10 f I
|
||||
jclass ExtendedBlockStorage; // 0xb18 cls
|
||||
void *ExtendedBlockStorage_blockLSBArray;
|
||||
jclass WorldClient; // 0xb28 cls
|
||||
jclass ChunkProviderClient; // 0xb30 cls
|
||||
jfieldID ChunkProviderClient_chunkMapping; // 0xb38 f Ljava/util/List;
|
||||
jmethodID ChunkProviderClient_unloadChunk; // 0xb40 m (II)V
|
||||
jclass EntityItem; // 0xb48 cls
|
||||
jmethodID EntityItem_getEntityItem; // 0xb50 m
|
||||
jmethodID EntityItem_searchForOtherItemsNearby; // 0xb58 m ()V
|
||||
jclass S3FPacketCustomPayload; // 0xb60 cls
|
||||
jfieldID S3FPacketCustomPayload_Ljava_lang_String_; // 0xb68 f Ljava/lang/String;
|
||||
jfieldID S3FPacketCustomPayload_data; // 0xb70 f
|
||||
jclass PacketBuffer; // 0xb78 cls
|
||||
jmethodID PacketBuffer_readableBytes; // 0xb80 m ()I
|
||||
jmethodID PacketBuffer_readerIndex; // 0xb88 m ()I
|
||||
jmethodID PacketBuffer_getBytes; // 0xb90 m (I[B)Lio/netty/buffer/ByteBuf;
|
||||
jclass UUID; // 0xb98 cls
|
||||
jmethodID UUID_getLeastSignificantBits; // 0xba0 m ()J
|
||||
jmethodID UUID_getMostSignificantBits; // 0xba8 m ()J
|
||||
jclass ChatComponentText; // 0xbb0 cls
|
||||
jmethodID ChatComponentText__init_; // 0xbb8 m (Ljava/lang/String;)V
|
||||
jclass PlayerControllerMP; // 0xbc0 cls
|
||||
jfieldID PlayerControllerMP_curBlockDamageMP; // 0xbc8 f F
|
||||
jfieldID PlayerControllerMP_blockHitDelay; // 0xbd0 f I
|
||||
jfieldID PlayerControllerMP_isHittingBlock; // 0xbd8 f Z
|
||||
jmethodID PlayerControllerMP_updateController; // 0xbe0 m ()V
|
||||
jclass C0EPacketClickWindow; // 0xbe8 cls
|
||||
jclass NBTTagList; // 0xbf0 cls
|
||||
jmethodID NBTTagList_tagCount; // 0xbf8 m ()I
|
||||
jmethodID NBTTagList_getCompoundTagAt; // 0xc00 m
|
||||
jclass NBTTagCompound; // 0xc08 cls
|
||||
jmethodID NBTTagCompound_getShort; // 0xc10 m (Ljava/lang/String;)S
|
||||
jclass EntityArrow; // 0xc18 cls
|
||||
void *unused_slot_c20;
|
||||
jfieldID EntityArrow_inGround; // 0xc28 f Z
|
||||
jclass PotionEffect; // 0xc30 cls
|
||||
jfieldID PotionEffect_duration; // 0xc38 f I
|
||||
jclass NetworkPlayerInfo; // 0xc40 cls
|
||||
jfieldID NetworkPlayerInfo_displayName; // 0xc48 f
|
||||
jmethodID NetworkPlayerInfo_getPlayerTeam; // 0xc50 m
|
||||
jclass GenericFutureListener; // 0xc58 cls
|
||||
jclass C16PacketClientStatus; // 0xc60 cls
|
||||
jfieldID C16PacketClientStatus_status; // 0xc68 f
|
||||
void *C16PacketClientStatus_init;
|
||||
jclass C16PacketClientStatus_EnumState; // 0xc78 cls
|
||||
void *C16PacketClientStatus_EnumState_openInventory;
|
||||
jclass c; // 0xc88 cls
|
||||
jmethodID c__init_; // 0xc90 m (I)V
|
||||
jclass ContainerPlayer; // 0xc98 cls
|
||||
jfieldID ContainerPlayer_craftMatrix; // 0xca0 f
|
||||
jclass GuiScreen; // 0xca8 cls
|
||||
jmethodID GuiScreen_keyTyped; // 0xcb0 m (CI)V
|
||||
jclass GuiChest; // 0xcb8 cls
|
||||
jfieldID GuiChest_lowerChestInventory; // 0xcc0 f
|
||||
jclass InventoryBasic; // 0xcc8 cls
|
||||
jfieldID InventoryBasic_Ljava_lang_String_; // 0xcd0 f Ljava/lang/String;
|
||||
jclass StatCollector; // 0xcd8 cls
|
||||
jmethodID StatCollector_translateToLocal; // 0xce0 sm (Ljava/lang/String;)Ljava/lang/String;
|
||||
jclass C01PacketChatMessage; // 0xce8 cls
|
||||
jfieldID C01PacketChatMessage_Ljava_lang_String_; // 0xcf0 f Ljava/lang/String;
|
||||
jclass C14PacketTabComplete; // 0xcf8 cls
|
||||
jfieldID C14PacketTabComplete_Ljava_lang_String_; // 0xd00 f Ljava/lang/String;
|
||||
jclass S2APacketParticles; // 0xd08 cls
|
||||
};
|
||||
|
||||
// The handle every engine call receives: ID table + JNIEnv.
|
||||
struct Jvm { JIds *ids; JNIEnv *env; };
|
||||
#define JNI(j) (*(j)->env)
|
||||
@@ -0,0 +1,252 @@
|
||||
// JNIEnv function table in JNI ABI order (index = offset / 8), generated from a JDK jni.h.
|
||||
// Object handles are plain pointers; only the layout matters to the recoded engine.
|
||||
#pragma once
|
||||
typedef void *jobject, *jclass, *jstring, *jarray, *jobjectArray, *jthrowable, *jweak, *jmethodID, *jfieldID;
|
||||
typedef void *jbooleanArray, *jbyteArray, *jcharArray, *jshortArray, *jintArray, *jlongArray, *jfloatArray, *jdoubleArray;
|
||||
typedef unsigned char jboolean; typedef signed char jbyte; typedef unsigned short jchar; typedef short jshort;
|
||||
typedef int jint; typedef long long jlong; typedef float jfloat; typedef double jdouble; typedef jint jsize;
|
||||
typedef union jvalue { jboolean z; jbyte b; jchar c; jshort s; jint i; jlong j; jfloat f; jdouble d; jobject l; } jvalue;
|
||||
typedef struct { char *name; char *signature; void *fnPtr; } JNINativeMethod;
|
||||
typedef int jobjectRefType;
|
||||
struct JNIEnv_;
|
||||
typedef const struct JNINativeInterface_ *JNIEnv;
|
||||
struct JavaVM_; typedef struct JavaVM_ JavaVM;
|
||||
#include <stdarg.h>
|
||||
struct JNINativeInterface_ {
|
||||
void *reserved0; // 0x0
|
||||
void *reserved1; // 0x8
|
||||
void *reserved2; // 0x10
|
||||
void *reserved3; // 0x18
|
||||
jint (*GetVersion)(JNIEnv *env); // 0x20
|
||||
jclass (*DefineClass)(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len); // 0x28
|
||||
jclass (*FindClass)(JNIEnv *env, const char *name); // 0x30
|
||||
jmethodID (*FromReflectedMethod)(JNIEnv *env, jobject method); // 0x38
|
||||
jfieldID (*FromReflectedField)(JNIEnv *env, jobject field); // 0x40
|
||||
jobject (*ToReflectedMethod)(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic); // 0x48
|
||||
jclass (*GetSuperclass)(JNIEnv *env, jclass sub); // 0x50
|
||||
jboolean (*IsAssignableFrom)(JNIEnv *env, jclass sub, jclass sup); // 0x58
|
||||
jobject (*ToReflectedField)(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic); // 0x60
|
||||
jint (*Throw)(JNIEnv *env, jthrowable obj); // 0x68
|
||||
jint (*ThrowNew)(JNIEnv *env, jclass clazz, const char *msg); // 0x70
|
||||
jthrowable (*ExceptionOccurred)(JNIEnv *env); // 0x78
|
||||
void (*ExceptionDescribe)(JNIEnv *env); // 0x80
|
||||
void (*ExceptionClear)(JNIEnv *env); // 0x88
|
||||
void (*FatalError)(JNIEnv *env, const char *msg); // 0x90
|
||||
jint (*PushLocalFrame)(JNIEnv *env, jint capacity); // 0x98
|
||||
jobject (*PopLocalFrame)(JNIEnv *env, jobject result); // 0xa0
|
||||
jobject (*NewGlobalRef)(JNIEnv *env, jobject lobj); // 0xa8
|
||||
void (*DeleteGlobalRef)(JNIEnv *env, jobject gref); // 0xb0
|
||||
void (*DeleteLocalRef)(JNIEnv *env, jobject obj); // 0xb8
|
||||
jboolean (*IsSameObject)(JNIEnv *env, jobject obj1, jobject obj2); // 0xc0
|
||||
jobject (*NewLocalRef)(JNIEnv *env, jobject ref); // 0xc8
|
||||
jint (*EnsureLocalCapacity)(JNIEnv *env, jint capacity); // 0xd0
|
||||
jobject (*AllocObject)(JNIEnv *env, jclass clazz); // 0xd8
|
||||
jobject (*NewObject)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); // 0xe0
|
||||
jobject (*NewObjectV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); // 0xe8
|
||||
jobject (*NewObjectA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); // 0xf0
|
||||
jclass (*GetObjectClass)(JNIEnv *env, jobject obj); // 0xf8
|
||||
jboolean (*IsInstanceOf)(JNIEnv *env, jobject obj, jclass clazz); // 0x100
|
||||
jmethodID (*GetMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig); // 0x108
|
||||
jobject (*CallObjectMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); // 0x110
|
||||
jobject (*CallObjectMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); // 0x118
|
||||
jobject (*CallObjectMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); // 0x120
|
||||
jboolean (*CallBooleanMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); // 0x128
|
||||
jboolean (*CallBooleanMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); // 0x130
|
||||
jboolean (*CallBooleanMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); // 0x138
|
||||
jbyte (*CallByteMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); // 0x140
|
||||
jbyte (*CallByteMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); // 0x148
|
||||
jbyte (*CallByteMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); // 0x150
|
||||
jchar (*CallCharMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); // 0x158
|
||||
jchar (*CallCharMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); // 0x160
|
||||
jchar (*CallCharMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); // 0x168
|
||||
jshort (*CallShortMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); // 0x170
|
||||
jshort (*CallShortMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); // 0x178
|
||||
jshort (*CallShortMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); // 0x180
|
||||
jint (*CallIntMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); // 0x188
|
||||
jint (*CallIntMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); // 0x190
|
||||
jint (*CallIntMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); // 0x198
|
||||
jlong (*CallLongMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); // 0x1a0
|
||||
jlong (*CallLongMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); // 0x1a8
|
||||
jlong (*CallLongMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); // 0x1b0
|
||||
jfloat (*CallFloatMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); // 0x1b8
|
||||
jfloat (*CallFloatMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); // 0x1c0
|
||||
jfloat (*CallFloatMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); // 0x1c8
|
||||
jdouble (*CallDoubleMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); // 0x1d0
|
||||
jdouble (*CallDoubleMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); // 0x1d8
|
||||
jdouble (*CallDoubleMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); // 0x1e0
|
||||
void (*CallVoidMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); // 0x1e8
|
||||
void (*CallVoidMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); // 0x1f0
|
||||
void (*CallVoidMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); // 0x1f8
|
||||
jobject (*CallNonvirtualObjectMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); // 0x200
|
||||
jobject (*CallNonvirtualObjectMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); // 0x208
|
||||
jobject (*CallNonvirtualObjectMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue * args); // 0x210
|
||||
jboolean (*CallNonvirtualBooleanMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); // 0x218
|
||||
jboolean (*CallNonvirtualBooleanMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); // 0x220
|
||||
jboolean (*CallNonvirtualBooleanMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue * args); // 0x228
|
||||
jbyte (*CallNonvirtualByteMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); // 0x230
|
||||
jbyte (*CallNonvirtualByteMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); // 0x238
|
||||
jbyte (*CallNonvirtualByteMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); // 0x240
|
||||
jchar (*CallNonvirtualCharMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); // 0x248
|
||||
jchar (*CallNonvirtualCharMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); // 0x250
|
||||
jchar (*CallNonvirtualCharMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); // 0x258
|
||||
jshort (*CallNonvirtualShortMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); // 0x260
|
||||
jshort (*CallNonvirtualShortMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); // 0x268
|
||||
jshort (*CallNonvirtualShortMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); // 0x270
|
||||
jint (*CallNonvirtualIntMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); // 0x278
|
||||
jint (*CallNonvirtualIntMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); // 0x280
|
||||
jint (*CallNonvirtualIntMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); // 0x288
|
||||
jlong (*CallNonvirtualLongMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); // 0x290
|
||||
jlong (*CallNonvirtualLongMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); // 0x298
|
||||
jlong (*CallNonvirtualLongMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); // 0x2a0
|
||||
jfloat (*CallNonvirtualFloatMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); // 0x2a8
|
||||
jfloat (*CallNonvirtualFloatMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); // 0x2b0
|
||||
jfloat (*CallNonvirtualFloatMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); // 0x2b8
|
||||
jdouble (*CallNonvirtualDoubleMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); // 0x2c0
|
||||
jdouble (*CallNonvirtualDoubleMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); // 0x2c8
|
||||
jdouble (*CallNonvirtualDoubleMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); // 0x2d0
|
||||
void (*CallNonvirtualVoidMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); // 0x2d8
|
||||
void (*CallNonvirtualVoidMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); // 0x2e0
|
||||
void (*CallNonvirtualVoidMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue * args); // 0x2e8
|
||||
jfieldID (*GetFieldID)(JNIEnv *env, jclass clazz, const char *name, const char *sig); // 0x2f0
|
||||
jobject (*GetObjectField)(JNIEnv *env, jobject obj, jfieldID fieldID); // 0x2f8
|
||||
jboolean (*GetBooleanField)(JNIEnv *env, jobject obj, jfieldID fieldID); // 0x300
|
||||
jbyte (*GetByteField)(JNIEnv *env, jobject obj, jfieldID fieldID); // 0x308
|
||||
jchar (*GetCharField)(JNIEnv *env, jobject obj, jfieldID fieldID); // 0x310
|
||||
jshort (*GetShortField)(JNIEnv *env, jobject obj, jfieldID fieldID); // 0x318
|
||||
jint (*GetIntField)(JNIEnv *env, jobject obj, jfieldID fieldID); // 0x320
|
||||
jlong (*GetLongField)(JNIEnv *env, jobject obj, jfieldID fieldID); // 0x328
|
||||
jfloat (*GetFloatField)(JNIEnv *env, jobject obj, jfieldID fieldID); // 0x330
|
||||
jdouble (*GetDoubleField)(JNIEnv *env, jobject obj, jfieldID fieldID); // 0x338
|
||||
void (*SetObjectField)(JNIEnv *env, jobject obj, jfieldID fieldID, jobject val); // 0x340
|
||||
void (*SetBooleanField)(JNIEnv *env, jobject obj, jfieldID fieldID, jboolean val); // 0x348
|
||||
void (*SetByteField)(JNIEnv *env, jobject obj, jfieldID fieldID, jbyte val); // 0x350
|
||||
void (*SetCharField)(JNIEnv *env, jobject obj, jfieldID fieldID, jchar val); // 0x358
|
||||
void (*SetShortField)(JNIEnv *env, jobject obj, jfieldID fieldID, jshort val); // 0x360
|
||||
void (*SetIntField)(JNIEnv *env, jobject obj, jfieldID fieldID, jint val); // 0x368
|
||||
void (*SetLongField)(JNIEnv *env, jobject obj, jfieldID fieldID, jlong val); // 0x370
|
||||
void (*SetFloatField)(JNIEnv *env, jobject obj, jfieldID fieldID, jfloat val); // 0x378
|
||||
void (*SetDoubleField)(JNIEnv *env, jobject obj, jfieldID fieldID, jdouble val); // 0x380
|
||||
jmethodID (*GetStaticMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig); // 0x388
|
||||
jobject (*CallStaticObjectMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); // 0x390
|
||||
jobject (*CallStaticObjectMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); // 0x398
|
||||
jobject (*CallStaticObjectMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); // 0x3a0
|
||||
jboolean (*CallStaticBooleanMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); // 0x3a8
|
||||
jboolean (*CallStaticBooleanMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); // 0x3b0
|
||||
jboolean (*CallStaticBooleanMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); // 0x3b8
|
||||
jbyte (*CallStaticByteMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); // 0x3c0
|
||||
jbyte (*CallStaticByteMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); // 0x3c8
|
||||
jbyte (*CallStaticByteMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); // 0x3d0
|
||||
jchar (*CallStaticCharMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); // 0x3d8
|
||||
jchar (*CallStaticCharMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); // 0x3e0
|
||||
jchar (*CallStaticCharMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); // 0x3e8
|
||||
jshort (*CallStaticShortMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); // 0x3f0
|
||||
jshort (*CallStaticShortMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); // 0x3f8
|
||||
jshort (*CallStaticShortMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); // 0x400
|
||||
jint (*CallStaticIntMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); // 0x408
|
||||
jint (*CallStaticIntMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); // 0x410
|
||||
jint (*CallStaticIntMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); // 0x418
|
||||
jlong (*CallStaticLongMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); // 0x420
|
||||
jlong (*CallStaticLongMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); // 0x428
|
||||
jlong (*CallStaticLongMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); // 0x430
|
||||
jfloat (*CallStaticFloatMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); // 0x438
|
||||
jfloat (*CallStaticFloatMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); // 0x440
|
||||
jfloat (*CallStaticFloatMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); // 0x448
|
||||
jdouble (*CallStaticDoubleMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); // 0x450
|
||||
jdouble (*CallStaticDoubleMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); // 0x458
|
||||
jdouble (*CallStaticDoubleMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); // 0x460
|
||||
void (*CallStaticVoidMethod)(JNIEnv *env, jclass cls, jmethodID methodID, ...); // 0x468
|
||||
void (*CallStaticVoidMethodV)(JNIEnv *env, jclass cls, jmethodID methodID, va_list args); // 0x470
|
||||
void (*CallStaticVoidMethodA)(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue * args); // 0x478
|
||||
jfieldID (*GetStaticFieldID)(JNIEnv *env, jclass clazz, const char *name, const char *sig); // 0x480
|
||||
jobject (*GetStaticObjectField)(JNIEnv *env, jclass clazz, jfieldID fieldID); // 0x488
|
||||
jboolean (*GetStaticBooleanField)(JNIEnv *env, jclass clazz, jfieldID fieldID); // 0x490
|
||||
jbyte (*GetStaticByteField)(JNIEnv *env, jclass clazz, jfieldID fieldID); // 0x498
|
||||
jchar (*GetStaticCharField)(JNIEnv *env, jclass clazz, jfieldID fieldID); // 0x4a0
|
||||
jshort (*GetStaticShortField)(JNIEnv *env, jclass clazz, jfieldID fieldID); // 0x4a8
|
||||
jint (*GetStaticIntField)(JNIEnv *env, jclass clazz, jfieldID fieldID); // 0x4b0
|
||||
jlong (*GetStaticLongField)(JNIEnv *env, jclass clazz, jfieldID fieldID); // 0x4b8
|
||||
jfloat (*GetStaticFloatField)(JNIEnv *env, jclass clazz, jfieldID fieldID); // 0x4c0
|
||||
jdouble (*GetStaticDoubleField)(JNIEnv *env, jclass clazz, jfieldID fieldID); // 0x4c8
|
||||
void (*SetStaticObjectField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jobject value); // 0x4d0
|
||||
void (*SetStaticBooleanField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jboolean value); // 0x4d8
|
||||
void (*SetStaticByteField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jbyte value); // 0x4e0
|
||||
void (*SetStaticCharField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jchar value); // 0x4e8
|
||||
void (*SetStaticShortField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jshort value); // 0x4f0
|
||||
void (*SetStaticIntField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jint value); // 0x4f8
|
||||
void (*SetStaticLongField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jlong value); // 0x500
|
||||
void (*SetStaticFloatField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jfloat value); // 0x508
|
||||
void (*SetStaticDoubleField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jdouble value); // 0x510
|
||||
jstring (*NewString)(JNIEnv *env, const jchar *unicode, jsize len); // 0x518
|
||||
jsize (*GetStringLength)(JNIEnv *env, jstring str); // 0x520
|
||||
const jchar * (*GetStringChars)(JNIEnv *env, jstring str, jboolean *isCopy); // 0x528
|
||||
void (*ReleaseStringChars)(JNIEnv *env, jstring str, const jchar *chars); // 0x530
|
||||
jstring (*NewStringUTF)(JNIEnv *env, const char *utf); // 0x538
|
||||
jsize (*GetStringUTFLength)(JNIEnv *env, jstring str); // 0x540
|
||||
const char* (*GetStringUTFChars)(JNIEnv *env, jstring str, jboolean *isCopy); // 0x548
|
||||
void (*ReleaseStringUTFChars)(JNIEnv *env, jstring str, const char* chars); // 0x550
|
||||
jsize (*GetArrayLength)(JNIEnv *env, jarray array); // 0x558
|
||||
jobjectArray (*NewObjectArray)(JNIEnv *env, jsize len, jclass clazz, jobject init); // 0x560
|
||||
jobject (*GetObjectArrayElement)(JNIEnv *env, jobjectArray array, jsize index); // 0x568
|
||||
void (*SetObjectArrayElement)(JNIEnv *env, jobjectArray array, jsize index, jobject val); // 0x570
|
||||
jbooleanArray (*NewBooleanArray)(JNIEnv *env, jsize len); // 0x578
|
||||
jbyteArray (*NewByteArray)(JNIEnv *env, jsize len); // 0x580
|
||||
jcharArray (*NewCharArray)(JNIEnv *env, jsize len); // 0x588
|
||||
jshortArray (*NewShortArray)(JNIEnv *env, jsize len); // 0x590
|
||||
jintArray (*NewIntArray)(JNIEnv *env, jsize len); // 0x598
|
||||
jlongArray (*NewLongArray)(JNIEnv *env, jsize len); // 0x5a0
|
||||
jfloatArray (*NewFloatArray)(JNIEnv *env, jsize len); // 0x5a8
|
||||
jdoubleArray (*NewDoubleArray)(JNIEnv *env, jsize len); // 0x5b0
|
||||
jboolean * (*GetBooleanArrayElements)(JNIEnv *env, jbooleanArray array, jboolean *isCopy); // 0x5b8
|
||||
jbyte * (*GetByteArrayElements)(JNIEnv *env, jbyteArray array, jboolean *isCopy); // 0x5c0
|
||||
jchar * (*GetCharArrayElements)(JNIEnv *env, jcharArray array, jboolean *isCopy); // 0x5c8
|
||||
jshort * (*GetShortArrayElements)(JNIEnv *env, jshortArray array, jboolean *isCopy); // 0x5d0
|
||||
jint * (*GetIntArrayElements)(JNIEnv *env, jintArray array, jboolean *isCopy); // 0x5d8
|
||||
jlong * (*GetLongArrayElements)(JNIEnv *env, jlongArray array, jboolean *isCopy); // 0x5e0
|
||||
jfloat * (*GetFloatArrayElements)(JNIEnv *env, jfloatArray array, jboolean *isCopy); // 0x5e8
|
||||
jdouble * (*GetDoubleArrayElements)(JNIEnv *env, jdoubleArray array, jboolean *isCopy); // 0x5f0
|
||||
void (*ReleaseBooleanArrayElements)(JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode); // 0x5f8
|
||||
void (*ReleaseByteArrayElements)(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode); // 0x600
|
||||
void (*ReleaseCharArrayElements)(JNIEnv *env, jcharArray array, jchar *elems, jint mode); // 0x608
|
||||
void (*ReleaseShortArrayElements)(JNIEnv *env, jshortArray array, jshort *elems, jint mode); // 0x610
|
||||
void (*ReleaseIntArrayElements)(JNIEnv *env, jintArray array, jint *elems, jint mode); // 0x618
|
||||
void (*ReleaseLongArrayElements)(JNIEnv *env, jlongArray array, jlong *elems, jint mode); // 0x620
|
||||
void (*ReleaseFloatArrayElements)(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode); // 0x628
|
||||
void (*ReleaseDoubleArrayElements)(JNIEnv *env, jdoubleArray array, jdouble *elems, jint mode); // 0x630
|
||||
void (*GetBooleanArrayRegion)(JNIEnv *env, jbooleanArray array, jsize start, jsize l, jboolean *buf); // 0x638
|
||||
void (*GetByteArrayRegion)(JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf); // 0x640
|
||||
void (*GetCharArrayRegion)(JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf); // 0x648
|
||||
void (*GetShortArrayRegion)(JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf); // 0x650
|
||||
void (*GetIntArrayRegion)(JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf); // 0x658
|
||||
void (*GetLongArrayRegion)(JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf); // 0x660
|
||||
void (*GetFloatArrayRegion)(JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf); // 0x668
|
||||
void (*GetDoubleArrayRegion)(JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf); // 0x670
|
||||
void (*SetBooleanArrayRegion)(JNIEnv *env, jbooleanArray array, jsize start, jsize l, const jboolean *buf); // 0x678
|
||||
void (*SetByteArrayRegion)(JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte *buf); // 0x680
|
||||
void (*SetCharArrayRegion)(JNIEnv *env, jcharArray array, jsize start, jsize len, const jchar *buf); // 0x688
|
||||
void (*SetShortArrayRegion)(JNIEnv *env, jshortArray array, jsize start, jsize len, const jshort *buf); // 0x690
|
||||
void (*SetIntArrayRegion)(JNIEnv *env, jintArray array, jsize start, jsize len, const jint *buf); // 0x698
|
||||
void (*SetLongArrayRegion)(JNIEnv *env, jlongArray array, jsize start, jsize len, const jlong *buf); // 0x6a0
|
||||
void (*SetFloatArrayRegion)(JNIEnv *env, jfloatArray array, jsize start, jsize len, const jfloat *buf); // 0x6a8
|
||||
void (*SetDoubleArrayRegion)(JNIEnv *env, jdoubleArray array, jsize start, jsize len, const jdouble *buf); // 0x6b0
|
||||
jint (*RegisterNatives)(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods); // 0x6b8
|
||||
jint (*UnregisterNatives)(JNIEnv *env, jclass clazz); // 0x6c0
|
||||
jint (*MonitorEnter)(JNIEnv *env, jobject obj); // 0x6c8
|
||||
jint (*MonitorExit)(JNIEnv *env, jobject obj); // 0x6d0
|
||||
jint (*GetJavaVM)(JNIEnv *env, JavaVM **vm); // 0x6d8
|
||||
void (*GetStringRegion)(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf); // 0x6e0
|
||||
void (*GetStringUTFRegion)(JNIEnv *env, jstring str, jsize start, jsize len, char *buf); // 0x6e8
|
||||
void * (*GetPrimitiveArrayCritical)(JNIEnv *env, jarray array, jboolean *isCopy); // 0x6f0
|
||||
void (*ReleasePrimitiveArrayCritical)(JNIEnv *env, jarray array, void *carray, jint mode); // 0x6f8
|
||||
const jchar * (*GetStringCritical)(JNIEnv *env, jstring string, jboolean *isCopy); // 0x700
|
||||
void (*ReleaseStringCritical)(JNIEnv *env, jstring string, const jchar *cstring); // 0x708
|
||||
jweak (*NewWeakGlobalRef)(JNIEnv *env, jobject obj); // 0x710
|
||||
void (*DeleteWeakGlobalRef)(JNIEnv *env, jweak ref); // 0x718
|
||||
jboolean (*ExceptionCheck)(JNIEnv *env); // 0x720
|
||||
jobject (*NewDirectByteBuffer)(JNIEnv* env, void* address, jlong capacity); // 0x728
|
||||
void* (*GetDirectBufferAddress)(JNIEnv* env, jobject buf); // 0x730
|
||||
jlong (*GetDirectBufferCapacity)(JNIEnv* env, jobject buf); // 0x738
|
||||
jobjectRefType (*GetObjectRefType)(JNIEnv* env, jobject obj); // 0x740
|
||||
jobject (*GetModule)(JNIEnv* env, jclass clazz); // 0x748
|
||||
jboolean (*IsVirtualThread)(JNIEnv* env, jobject obj); // 0x750
|
||||
jlong (*GetStringUTFLengthAsLong)(JNIEnv *env, jstring str); // 0x758
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// What the portable client code needs from the operating system (implemented in src/platform/<os>/).
|
||||
#pragma once
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// ---- implemented by the platform layer ----
|
||||
void *slinky_gl_get_proc(const char *name); // OpenGL entry point by name (needs a current context)
|
||||
void slinky_log_write(const char *line); // one line of diagnostics (stderr / debug output), no newline
|
||||
// ---- implemented by the client (src/engine/protected/init.cpp), called by the platform layer ----
|
||||
int slinky_setup(void *self); // start-up; runs on its own thread. 0 = ok, else an error code
|
||||
int slinky_teardown(void); // frees everything slinky_setup built; 0 = hooks still live, keep the DLL loaded
|
||||
void slinky_logf(const char *fmt, ...); // printf-style diagnostics via slinky_log_write
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,82 @@
|
||||
// Minimal Win32 declarations for the readable reimplementations in src/engine/protected/.
|
||||
// (The image calls these through the protector's encrypted import slots; here they are ordinary imports.)
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
|
||||
typedef void *HANDLE, *HWND, *HMODULE, *HINSTANCE, *LPVOID;
|
||||
typedef int BOOL;
|
||||
typedef unsigned long DWORD;
|
||||
typedef long long LONG_PTR, LRESULT;
|
||||
typedef unsigned long long ULONG_PTR, WPARAM;
|
||||
typedef long long LPARAM;
|
||||
typedef unsigned int UINT;
|
||||
typedef unsigned short WCHAR;
|
||||
typedef long long LARGE_INTEGER_QUAD;
|
||||
typedef LRESULT (*WNDPROC)(HWND, UINT, WPARAM, LPARAM);
|
||||
typedef DWORD (*THREAD_START)(LPVOID);
|
||||
|
||||
struct FILETIME { DWORD lo, hi; };
|
||||
struct WIN32_FIND_DATAW {
|
||||
DWORD attributes;
|
||||
FILETIME created, accessed, written;
|
||||
DWORD size_hi, size_lo, reserved0, reserved1;
|
||||
wchar_t name[260];
|
||||
wchar_t alt_name[14];
|
||||
};
|
||||
|
||||
#define WINAPI __stdcall
|
||||
#define INVALID_HANDLE ((HANDLE)(LONG_PTR)-1)
|
||||
#define GENERIC_READ 0x80000000u
|
||||
#define GENERIC_WRITE 0x40000000u
|
||||
#define FILE_SHARE_READ 1u
|
||||
#define CREATE_NEW 1u
|
||||
#define CREATE_ALWAYS 2u
|
||||
#define OPEN_EXISTING 3u
|
||||
#define FILE_ATTRIBUTE_NORMAL 0x80u
|
||||
#define FILE_ATTRIBUTE_DIRECTORY 0x10u
|
||||
#define INVALID_FILE_SIZE 0xffffffffu
|
||||
#define ERROR_FILE_NOT_FOUND 2u
|
||||
#define ERROR_NO_MORE_FILES 0x12u
|
||||
#define ERROR_FILE_EXISTS 0x50u
|
||||
#define ERROR_ALREADY_EXISTS 0xb7u
|
||||
#define GWLP_WNDPROC (-4)
|
||||
#define SW_SHOWNORMAL 1
|
||||
|
||||
extern "C" {
|
||||
__declspec(dllimport) BOOL WINAPI QueryPerformanceCounter(long long *count);
|
||||
__declspec(dllimport) BOOL WINAPI QueryPerformanceFrequency(long long *freq);
|
||||
__declspec(dllimport) DWORD WINAPI GetLastError();
|
||||
__declspec(dllimport) BOOL WINAPI CreateDirectoryW(const wchar_t *path, void *security);
|
||||
__declspec(dllimport) BOOL WINAPI DeleteFileW(const wchar_t *path);
|
||||
__declspec(dllimport) HANDLE WINAPI CreateFileW(const wchar_t *path, DWORD access, DWORD share, void *security,
|
||||
DWORD disposition, DWORD flags, HANDLE templ);
|
||||
__declspec(dllimport) DWORD WINAPI GetFileSize(HANDLE h, DWORD *high);
|
||||
__declspec(dllimport) BOOL WINAPI ReadFile(HANDLE h, void *buf, DWORD n, DWORD *done, void *overlapped);
|
||||
__declspec(dllimport) BOOL WINAPI WriteFile(HANDLE h, const void *buf, DWORD n, DWORD *done, void *overlapped);
|
||||
__declspec(dllimport) BOOL WINAPI CloseHandle(HANDLE h);
|
||||
__declspec(dllimport) HANDLE WINAPI FindFirstFileW(const wchar_t *pattern, WIN32_FIND_DATAW *fd);
|
||||
__declspec(dllimport) BOOL WINAPI FindNextFileW(HANDLE h, WIN32_FIND_DATAW *fd);
|
||||
__declspec(dllimport) BOOL WINAPI FindClose(HANDLE h);
|
||||
__declspec(dllimport) void WINAPI GetSystemTimeAsFileTime(FILETIME *ft);
|
||||
__declspec(dllimport) HINSTANCE WINAPI ShellExecuteW(HWND hwnd, const wchar_t *op, const wchar_t *file,
|
||||
const wchar_t *params, const wchar_t *dir, int show);
|
||||
__declspec(dllimport) HMODULE WINAPI GetModuleHandleA(const char *name);
|
||||
__declspec(dllimport) void *WINAPI GetProcAddress(HMODULE m, const char *name);
|
||||
__declspec(dllimport) HWND WINAPI GetForegroundWindow();
|
||||
__declspec(dllimport) BOOL WINAPI IsWindowUnicode(HWND hwnd);
|
||||
__declspec(dllimport) DWORD WINAPI GetWindowThreadProcessId(HWND hwnd, DWORD *pid);
|
||||
__declspec(dllimport) DWORD WINAPI GetCurrentProcessId();
|
||||
__declspec(dllimport) HANDLE WINAPI CreateThread(void *security, size_t stack, THREAD_START start, LPVOID param,
|
||||
DWORD flags, DWORD *id);
|
||||
__declspec(dllimport) void WINAPI Sleep(DWORD ms);
|
||||
__declspec(dllimport) void WINAPI FreeLibraryAndExitThread(HMODULE m, DWORD code);
|
||||
__declspec(dllimport) int WINAPI WideCharToMultiByte(UINT codepage, DWORD flags, const wchar_t *src, int n, char *dst,
|
||||
int dst_size, const char *def, BOOL *used_def);
|
||||
__declspec(dllimport) int WINAPI MultiByteToWideChar(UINT codepage, DWORD flags, const char *src, int n, wchar_t *dst,
|
||||
int dst_size);
|
||||
__declspec(dllimport) HANDLE WINAPI GetStdHandle(DWORD which);
|
||||
__declspec(dllimport) void WINAPI OutputDebugStringA(const char *text);
|
||||
__declspec(dllimport) HMODULE WINAPI LoadLibraryA(const char *name);
|
||||
__declspec(dllimport) BOOL WINAPI DisableThreadLibraryCalls(HMODULE m);
|
||||
wchar_t *_wgetenv(const wchar_t *name);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(slinky-inject LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
|
||||
endif()
|
||||
|
||||
add_executable(slinky-inject
|
||||
main.cpp
|
||||
tui.cpp
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_sources(slinky-inject PRIVATE inject_windows.cpp)
|
||||
target_compile_definitions(slinky-inject PRIVATE _WIN32_WINNT=0x0601)
|
||||
# A standalone .exe: no MinGW runtime DLLs to copy beside it.
|
||||
target_link_options(slinky-inject PRIVATE -static -static-libgcc -static-libstdc++)
|
||||
else()
|
||||
target_sources(slinky-inject PRIVATE inject_linux.cpp)
|
||||
target_link_libraries(slinky-inject PRIVATE ${CMAKE_DL_LIBS} pthread)
|
||||
endif()
|
||||
|
||||
# Copy the client library next to the injector when it is built in the same
|
||||
# tree (the top-level CMakeLists.txt does this via add_subdirectory).
|
||||
# A separate always-run target: a POST_BUILD step on slinky-inject only ran when
|
||||
# the injector itself was relinked, so the sibling copy went stale whenever only
|
||||
# the client changed.
|
||||
if(TARGET slinky)
|
||||
add_custom_target(slinky-copy-beside-injector ALL
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
$<TARGET_FILE:slinky>
|
||||
$<TARGET_FILE_DIR:slinky-inject>/$<TARGET_FILE_NAME:slinky>
|
||||
DEPENDS slinky slinky-inject
|
||||
VERBATIM)
|
||||
endif()
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
# Build slinky-inject for the host Linux.
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -j"$(nproc)"
|
||||
echo "built: $(pwd)/build/slinky-inject"
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
# Cross-build slinky-inject.exe with MinGW-w64.
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
cmake -S . -B build-windows \
|
||||
-DCMAKE_SYSTEM_NAME=Windows \
|
||||
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
|
||||
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build-windows -j"$(nproc)"
|
||||
echo "built: $(pwd)/build-windows/slinky-inject.exe"
|
||||
@@ -0,0 +1,35 @@
|
||||
// Process discovery and library injection, with no opinion about the UI.
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Injector {
|
||||
|
||||
struct Process {
|
||||
unsigned long pid = 0;
|
||||
std::string name;
|
||||
std::string command_line;
|
||||
};
|
||||
|
||||
using LogFn = std::function<void(const std::string &)>;
|
||||
|
||||
std::vector<Process> ListProcesses();
|
||||
|
||||
// Finds the running Minecraft JVM. On Linux that is a java process whose
|
||||
// command line names the game; on Windows it is the LWJGL/GLFW window's owner.
|
||||
bool FindMinecraft(Process &out, std::string &error);
|
||||
|
||||
// Finds a process by pid, exact name, or case-insensitive substring of the
|
||||
// name/command line. Accepts a decimal pid in `target`.
|
||||
bool FindTarget(const std::string &target, Process &out, std::string &error);
|
||||
|
||||
// Loads `library` into `pid` through ptrace+dlopen (Linux) or
|
||||
// CreateRemoteThread+LoadLibraryW (Windows). Returns false on failure.
|
||||
bool Inject(unsigned long pid, const std::string &library, const LogFn &log);
|
||||
|
||||
// Why injection may fail before it starts, for the error message.
|
||||
std::string EnvironmentHint();
|
||||
|
||||
} // namespace Injector
|
||||
@@ -0,0 +1,527 @@
|
||||
// Linux injection: ptrace the target and make it call its own dlopen().
|
||||
//
|
||||
// This is the same technique as the Catware injector: attach to the process,
|
||||
// save its registers, resolve dlopen in the target (preferring the target's own
|
||||
// libc ELF), point rip at it with the library path on a scratch stack, and use
|
||||
// a return address of 0 as the completion signal. The client library's ELF
|
||||
// constructor then starts the client, exactly as DllMain does on Windows.
|
||||
//
|
||||
// The library is injected from a private copy under a fresh name so that a
|
||||
// re-injection after Unload starts a new copy with fresh statics; the old
|
||||
// mapping is never unloaded (-z nodelete).
|
||||
#include "inject.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <dlfcn.h>
|
||||
#include <elf.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Injector {
|
||||
namespace {
|
||||
|
||||
std::string lower(std::string text) {
|
||||
std::transform(text.begin(), text.end(), text.begin(),
|
||||
[](unsigned char c) { return (char)std::tolower(c); });
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string trim(std::string text) {
|
||||
while (!text.empty() && (text.back() == '\n' || text.back() == '\r' || text.back() == ' ')) text.pop_back();
|
||||
size_t start = text.find_first_not_of(" \t");
|
||||
return start == std::string::npos ? std::string() : text.substr(start);
|
||||
}
|
||||
|
||||
std::string read_small_file(const std::string &path) {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
if (!in) return {};
|
||||
std::ostringstream out;
|
||||
out << in.rdbuf();
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string command_line_of(unsigned long pid) {
|
||||
std::string data = read_small_file("/proc/" + std::to_string(pid) + "/cmdline");
|
||||
for (char &c : data) {
|
||||
if (c == '\0') c = ' ';
|
||||
}
|
||||
return trim(data);
|
||||
}
|
||||
|
||||
std::string name_of(unsigned long pid) {
|
||||
return trim(read_small_file("/proc/" + std::to_string(pid) + "/comm"));
|
||||
}
|
||||
|
||||
// ---- ELF / maps helpers ----
|
||||
struct MappedRegion {
|
||||
uintptr_t start = 0;
|
||||
uintptr_t end = 0;
|
||||
uintptr_t offset = 0;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
std::vector<MappedRegion> read_maps(unsigned long pid) {
|
||||
std::vector<MappedRegion> regions;
|
||||
std::ifstream maps("/proc/" + std::to_string(pid) + "/maps");
|
||||
std::string line;
|
||||
while (std::getline(maps, line)) {
|
||||
uintptr_t start = 0, end = 0, offset = 0;
|
||||
char path[512] = {0};
|
||||
int parsed = std::sscanf(line.c_str(), "%lx-%lx %*4s %lx %*x:%*x %*d %511[^\n]",
|
||||
&start, &end, &offset, path);
|
||||
if (parsed < 3) continue;
|
||||
MappedRegion region;
|
||||
region.start = start;
|
||||
region.end = end;
|
||||
region.offset = offset;
|
||||
region.path = parsed == 4 ? path : "";
|
||||
size_t first = region.path.find_first_not_of(' ');
|
||||
if (first == std::string::npos) region.path.clear();
|
||||
else region.path.erase(0, first);
|
||||
regions.push_back(region);
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> read_target_file(unsigned long pid, const std::string &path) {
|
||||
// The target's mount namespace first: in a Flatpak or other container the same path on the host is a
|
||||
// different libc build, and its symbol offsets would point into the wrong place.
|
||||
std::string through_root = "/proc/" + std::to_string(pid) + "/root" + path;
|
||||
for (const std::string &candidate : {through_root, path}) {
|
||||
std::ifstream in(candidate, std::ios::binary);
|
||||
if (!in) continue;
|
||||
return std::vector<uint8_t>((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
uint64_t elf_function_value(const std::vector<uint8_t> &bytes, const char *name) {
|
||||
if (bytes.size() < sizeof(Elf64_Ehdr)) return 0;
|
||||
const auto *header = reinterpret_cast<const Elf64_Ehdr *>(bytes.data());
|
||||
if (std::memcmp(header->e_ident, ELFMAG, SELFMAG) != 0 ||
|
||||
header->e_ident[EI_CLASS] != ELFCLASS64 ||
|
||||
header->e_shoff == 0 || header->e_shnum == 0 ||
|
||||
header->e_shoff + (uint64_t)header->e_shnum * sizeof(Elf64_Shdr) > bytes.size()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto *sections = reinterpret_cast<const Elf64_Shdr *>(bytes.data() + header->e_shoff);
|
||||
for (uint32_t want : {(uint32_t)SHT_DYNSYM, (uint32_t)SHT_SYMTAB}) {
|
||||
for (int i = 0; i < header->e_shnum; i++) {
|
||||
const Elf64_Shdr §ion = sections[i];
|
||||
if (section.sh_type != want || section.sh_entsize == 0 || section.sh_link >= header->e_shnum ||
|
||||
section.sh_offset + section.sh_size > bytes.size()) {
|
||||
continue;
|
||||
}
|
||||
const auto *symbols = reinterpret_cast<const Elf64_Sym *>(bytes.data() + section.sh_offset);
|
||||
const char *names = reinterpret_cast<const char *>(bytes.data() + sections[section.sh_link].sh_offset);
|
||||
size_t count = section.sh_size / section.sh_entsize;
|
||||
for (size_t s = 0; s < count; s++) {
|
||||
if (ELF64_ST_TYPE(symbols[s].st_info) != STT_FUNC || symbols[s].st_value == 0) continue;
|
||||
if (std::strcmp(names + symbols[s].st_name, name) == 0) return symbols[s].st_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::optional<uintptr_t> resolve_dlopen_from_elf(unsigned long pid, const std::vector<MappedRegion> ®ions) {
|
||||
for (const MappedRegion &header : regions) {
|
||||
if (header.offset != 0) continue;
|
||||
bool is_libc = header.path.find("libc.so.6") != std::string::npos ||
|
||||
header.path.find("libdl.so.2") != std::string::npos;
|
||||
if (!is_libc) continue;
|
||||
|
||||
uint64_t value = elf_function_value(read_target_file(pid, header.path), "dlopen");
|
||||
if (!value) continue;
|
||||
|
||||
for (const MappedRegion &segment : regions) {
|
||||
if (segment.path == header.path && value >= segment.offset &&
|
||||
value < segment.offset + (segment.end - segment.start)) {
|
||||
return segment.start + (uintptr_t)(value - segment.offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<uintptr_t> resolve_remote_dlopen(unsigned long pid, const LogFn &log) {
|
||||
void *local_dlopen = dlsym(RTLD_DEFAULT, "dlopen");
|
||||
if (!local_dlopen) {
|
||||
log(std::string("dlsym(dlopen) failed: ") + dlerror());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto local_regions = read_maps(getpid());
|
||||
const MappedRegion *owner = nullptr;
|
||||
for (const auto ®ion : local_regions) {
|
||||
if ((uintptr_t)local_dlopen >= region.start && (uintptr_t)local_dlopen < region.end &&
|
||||
!region.path.empty() && region.path[0] == '/') {
|
||||
owner = ®ion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!owner) {
|
||||
log("could not find dlopen in the injector's own maps");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto remote_regions = read_maps(pid);
|
||||
|
||||
// The target's own libc first. A Flatpak/container runtime is a different
|
||||
// build at the same path, where our offset would land in the wrong place.
|
||||
if (auto from_elf = resolve_dlopen_from_elf(pid, remote_regions)) return from_elf;
|
||||
|
||||
// Otherwise match the same file and the same segment offset.
|
||||
uintptr_t within = (uintptr_t)local_dlopen - owner->start;
|
||||
for (const auto ®ion : remote_regions) {
|
||||
if (region.path == owner->path && region.offset == owner->offset) {
|
||||
return region.start + within;
|
||||
}
|
||||
}
|
||||
|
||||
log("could not resolve dlopen in the target (no matching libc segment)");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// ---- ptrace plumbing ----
|
||||
bool wait_stopped(pid_t pid, int &status) {
|
||||
for (;;) {
|
||||
pid_t r = waitpid(pid, &status, 0);
|
||||
if (r == pid) return true;
|
||||
if (r < 0 && errno == EINTR) continue;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool write_remote(pid_t pid, uintptr_t address, const void *data, size_t size) {
|
||||
iovec local{const_cast<void *>(data), size};
|
||||
iovec remote{reinterpret_cast<void *>(address), size};
|
||||
if (process_vm_writev(pid, &local, 1, &remote, 1, 0) == (ssize_t)size) return true;
|
||||
|
||||
// Fallback for kernels without process_vm_writev.
|
||||
const uint8_t *bytes = static_cast<const uint8_t *>(data);
|
||||
for (size_t done = 0; done < size;) {
|
||||
long word = 0;
|
||||
size_t n = std::min(sizeof(word), size - done);
|
||||
std::memcpy(&word, bytes + done, n);
|
||||
if (ptrace(PTRACE_POKEDATA, pid, reinterpret_cast<void *>(address + done), reinterpret_cast<void *>(word)) != 0) {
|
||||
return false;
|
||||
}
|
||||
done += n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inject_path(pid_t pid, const std::string &path, const LogFn &log) {
|
||||
if (ptrace(PTRACE_ATTACH, pid, nullptr, nullptr) != 0) {
|
||||
log(std::string("PTRACE_ATTACH failed: ") + std::strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
if (!wait_stopped(pid, status)) {
|
||||
log("waitpid after attach failed");
|
||||
ptrace(PTRACE_DETACH, pid, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
user_regs_struct saved{};
|
||||
if (ptrace(PTRACE_GETREGS, pid, nullptr, &saved) != 0) {
|
||||
log(std::string("PTRACE_GETREGS failed: ") + std::strerror(errno));
|
||||
ptrace(PTRACE_DETACH, pid, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto remote_dlopen = resolve_remote_dlopen(pid, log);
|
||||
if (!remote_dlopen) {
|
||||
ptrace(PTRACE_SETREGS, pid, nullptr, &saved);
|
||||
ptrace(PTRACE_DETACH, pid, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The stack grows down from rsp. The path must sit above the call stack,
|
||||
// or dlopen overwrites its own filename argument while loading.
|
||||
constexpr size_t scratch_offset = 256;
|
||||
constexpr size_t call_stack_offset = 16384;
|
||||
uintptr_t scratch = (saved.rsp - scratch_offset) & ~(uintptr_t)0xF;
|
||||
uintptr_t call_rsp = (saved.rsp - call_stack_offset) & ~(uintptr_t)0xF;
|
||||
|
||||
std::string padded = path;
|
||||
padded.resize(((padded.size() / 8) + 2) * 8, '\0');
|
||||
if (!write_remote(pid, scratch, padded.data(), padded.size())) {
|
||||
log(std::string("failed to write library path into target: ") + std::strerror(errno));
|
||||
ptrace(PTRACE_SETREGS, pid, nullptr, &saved);
|
||||
ptrace(PTRACE_DETACH, pid, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// A return address of 0 makes dlopen's `ret` fault at rip == 0. That
|
||||
// private fault is the completion signal; no shared code is patched, so
|
||||
// the other JVM threads can keep running untouched.
|
||||
constexpr uintptr_t return_address = 0;
|
||||
user_regs_struct call = saved;
|
||||
call.rdi = scratch;
|
||||
call.rsi = RTLD_NOW;
|
||||
call.rip = *remote_dlopen;
|
||||
call.orig_rax = (unsigned long long)-1; // no syscall to restart
|
||||
call.rsp = call_rsp - 8;
|
||||
|
||||
auto restore = [&]() {
|
||||
ptrace(PTRACE_SETREGS, pid, nullptr, &saved);
|
||||
ptrace(PTRACE_DETACH, pid, nullptr, nullptr);
|
||||
};
|
||||
|
||||
if (ptrace(PTRACE_POKEDATA, pid, reinterpret_cast<void *>(call.rsp),
|
||||
reinterpret_cast<void *>(return_address)) != 0) {
|
||||
log(std::string("failed to write completion address: ") + std::strerror(errno));
|
||||
restore();
|
||||
return false;
|
||||
}
|
||||
if (ptrace(PTRACE_SETREGS, pid, nullptr, &call) != 0) {
|
||||
log(std::string("PTRACE_SETREGS failed: ") + std::strerror(errno));
|
||||
restore();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
int pending_signal = 0;
|
||||
user_regs_struct fault{};
|
||||
|
||||
for (int attempt = 0; attempt < (1 << 20); attempt++) {
|
||||
if (ptrace(PTRACE_CONT, pid, nullptr, reinterpret_cast<void *>((uintptr_t)pending_signal)) != 0) {
|
||||
log(std::string("PTRACE_CONT failed: ") + std::strerror(errno));
|
||||
restore();
|
||||
return false;
|
||||
}
|
||||
pending_signal = 0;
|
||||
|
||||
if (!wait_stopped(pid, status)) {
|
||||
log("target vanished while injecting");
|
||||
return false;
|
||||
}
|
||||
if (!WIFSTOPPED(status)) {
|
||||
log("target exited while injecting");
|
||||
return false;
|
||||
}
|
||||
|
||||
int signal = WSTOPSIG(status);
|
||||
if (signal == SIGSEGV) {
|
||||
ptrace(PTRACE_GETREGS, pid, nullptr, &fault);
|
||||
if (fault.rip == return_address) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
pending_signal = SIGSEGV; // HotSpot's own fault: deliver it
|
||||
continue;
|
||||
}
|
||||
pending_signal = signal; // timers, SIGQUIT, ...: the target's own
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
log("dlopen returned handle 0x" + [&] {
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof buf, "%llx", (unsigned long long)fault.rax);
|
||||
return std::string(buf);
|
||||
}());
|
||||
if (fault.rax == 0) {
|
||||
ok = false;
|
||||
log("dlopen returned NULL -- the library failed to load in the target");
|
||||
}
|
||||
} else {
|
||||
log("dlopen did not return through the completion address");
|
||||
}
|
||||
|
||||
if (ptrace(PTRACE_SETREGS, pid, nullptr, &saved) != 0) {
|
||||
log(std::string("warning: failed to restore registers: ") + std::strerror(errno));
|
||||
}
|
||||
if (ptrace(PTRACE_DETACH, pid, nullptr, nullptr) != 0) {
|
||||
log(std::string("warning: PTRACE_DETACH failed: ") + std::strerror(errno));
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// The copy is written through /proc/<pid>/root/tmp so that a target with its own /tmp (PrivateTmp, Flatpak,
|
||||
// pressure-vessel) can open it; `host_path` is where the injector sees it, `target_path` what dlopen gets.
|
||||
bool private_copy(const std::string &library, unsigned long pid, std::string &host_path, std::string &target_path,
|
||||
const LogFn &log) {
|
||||
char source[PATH_MAX];
|
||||
if (!realpath(library.c_str(), source)) {
|
||||
log(std::string("cannot resolve ") + library + ": " + std::strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ifstream in(source, std::ios::binary);
|
||||
if (!in) {
|
||||
log(std::string("cannot open ") + source);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string pattern = "/proc/" + std::to_string(pid) + "/root/tmp/slinky-inject-XXXXXX";
|
||||
int fd = mkstemp(pattern.data());
|
||||
if (fd >= 0) {
|
||||
target_path = pattern.substr(pattern.find("/tmp/"));
|
||||
} else {
|
||||
pattern = "/tmp/slinky-inject-XXXXXX";
|
||||
fd = mkstemp(pattern.data());
|
||||
target_path = pattern;
|
||||
}
|
||||
if (fd < 0) {
|
||||
log(std::string("cannot create a private library copy: ") + std::strerror(errno));
|
||||
return false;
|
||||
}
|
||||
fchmod(fd, 0644);
|
||||
|
||||
std::vector<char> buffer(64 * 1024);
|
||||
bool ok = true;
|
||||
while (in) {
|
||||
in.read(buffer.data(), (std::streamsize)buffer.size());
|
||||
std::streamsize got = in.gcount();
|
||||
for (std::streamsize done = 0; done < got;) {
|
||||
ssize_t n = write(fd, buffer.data() + done, (size_t)(got - done));
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
done += n;
|
||||
}
|
||||
if (!ok) break;
|
||||
}
|
||||
if (close(fd) != 0) ok = false;
|
||||
if (!ok) {
|
||||
unlink(pattern.c_str());
|
||||
log("failed to write the private library copy");
|
||||
return false;
|
||||
}
|
||||
host_path = pattern;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<Process> ListProcesses() {
|
||||
std::vector<Process> out;
|
||||
DIR *proc = opendir("/proc");
|
||||
if (!proc) return out;
|
||||
|
||||
while (dirent *entry = readdir(proc)) {
|
||||
if (entry->d_name[0] < '0' || entry->d_name[0] > '9') continue;
|
||||
unsigned long pid = std::strtoul(entry->d_name, nullptr, 10);
|
||||
if (!pid) continue;
|
||||
std::string cmdline = command_line_of(pid);
|
||||
if (cmdline.empty()) continue; // kernel thread
|
||||
out.push_back(Process{pid, name_of(pid), cmdline});
|
||||
}
|
||||
closedir(proc);
|
||||
|
||||
std::sort(out.begin(), out.end(), [](const Process &a, const Process &b) {
|
||||
if (a.name != b.name) return a.name < b.name;
|
||||
return a.pid < b.pid;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
bool FindMinecraft(Process &out, std::string &error) {
|
||||
int best_score = 0;
|
||||
for (const Process &process : ListProcesses()) {
|
||||
std::string cmd = lower(process.command_line);
|
||||
std::string name = lower(process.name);
|
||||
bool java = name.find("java") != std::string::npos || cmd.find("/java ") != std::string::npos;
|
||||
if (!java) continue;
|
||||
|
||||
int score = 0;
|
||||
if (cmd.find("net.minecraft") != std::string::npos) score += 100;
|
||||
if (cmd.find("minecraft") != std::string::npos) score += 50;
|
||||
if (cmd.find("fabric") != std::string::npos) score += 10;
|
||||
if (cmd.find("forge") != std::string::npos) score += 10;
|
||||
if (cmd.find("--gamedir") != std::string::npos) score += 10;
|
||||
if (score > best_score) {
|
||||
best_score = score;
|
||||
out = process;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_score > 0) return true;
|
||||
error = "Minecraft is not running (no java process with minecraft in its command line)";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FindTarget(const std::string &target, Process &out, std::string &error) {
|
||||
if (target.empty()) {
|
||||
error = "no target given";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool numeric = std::all_of(target.begin(), target.end(), [](unsigned char c) { return std::isdigit(c); });
|
||||
if (numeric) {
|
||||
out.pid = std::strtoul(target.c_str(), nullptr, 10);
|
||||
out.name = name_of(out.pid);
|
||||
out.command_line = command_line_of(out.pid);
|
||||
if (out.pid && !out.name.empty()) return true;
|
||||
error = "no process with pid " + target;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string needle = lower(target);
|
||||
for (const Process &process : ListProcesses()) {
|
||||
if (lower(process.name) == needle) {
|
||||
out = process;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const Process &process : ListProcesses()) {
|
||||
if (lower(process.name).find(needle) != std::string::npos ||
|
||||
lower(process.command_line).find(needle) != std::string::npos) {
|
||||
out = process;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
error = "no process matching '" + target + "'";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Inject(unsigned long pid, const std::string &library, const LogFn &log) {
|
||||
std::string host_copy, target_copy;
|
||||
if (!private_copy(library, pid, host_copy, target_copy, log)) return false;
|
||||
|
||||
log("injecting " + library + " as " + target_copy + " into pid " + std::to_string(pid));
|
||||
bool ok = inject_path((pid_t)pid, target_copy, log);
|
||||
// The loaded mapping keeps the inode alive, so a later copy can never alias it.
|
||||
unlink(host_copy.c_str());
|
||||
return ok;
|
||||
}
|
||||
|
||||
std::string EnvironmentHint() {
|
||||
std::ifstream scope_file("/proc/sys/kernel/yama/ptrace_scope");
|
||||
int scope = -1;
|
||||
if (scope_file >> scope && scope > 0 && geteuid() != 0) {
|
||||
return "kernel.yama.ptrace_scope is " + std::to_string(scope) +
|
||||
"; run the injector with sudo, or set it to 0 (sudo sysctl kernel.yama.ptrace_scope=0), "
|
||||
"or give the binary CAP_SYS_PTRACE (sudo setcap cap_sys_ptrace+ep <injector>)";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Injector
|
||||
@@ -0,0 +1,213 @@
|
||||
// Windows injection: CreateRemoteThread(LoadLibraryW) into the game's process.
|
||||
// The client DLL's DllMain starts the client, so loading it is the whole job.
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "inject.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
|
||||
namespace Injector {
|
||||
namespace {
|
||||
|
||||
std::string narrow(const std::wstring &text) {
|
||||
if (text.empty()) return {};
|
||||
int n = WideCharToMultiByte(CP_UTF8, 0, text.data(), (int)text.size(), nullptr, 0, nullptr, nullptr);
|
||||
std::string out((size_t)n, '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, text.data(), (int)text.size(), out.data(), n, nullptr, nullptr);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::wstring widen(const std::string &text) {
|
||||
if (text.empty()) return {};
|
||||
int n = MultiByteToWideChar(CP_UTF8, 0, text.data(), (int)text.size(), nullptr, 0);
|
||||
std::wstring out((size_t)n, L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, text.data(), (int)text.size(), out.data(), n);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string lower(std::string text) {
|
||||
std::transform(text.begin(), text.end(), text.begin(),
|
||||
[](unsigned char c) { return (char)std::tolower(c); });
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string process_name(DWORD pid) {
|
||||
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
||||
if (!process) return {};
|
||||
wchar_t path[1024];
|
||||
DWORD size = (DWORD)(sizeof path / sizeof path[0]);
|
||||
std::string result;
|
||||
if (QueryFullProcessImageNameW(process, 0, path, &size)) {
|
||||
std::wstring full(path, size);
|
||||
size_t slash = full.find_last_of(L"\\/");
|
||||
result = narrow(slash == std::wstring::npos ? full : full.substr(slash + 1));
|
||||
}
|
||||
CloseHandle(process);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<Process> ListProcesses() {
|
||||
std::vector<Process> out;
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snapshot == INVALID_HANDLE_VALUE) return out;
|
||||
|
||||
PROCESSENTRY32W entry{};
|
||||
entry.dwSize = sizeof entry;
|
||||
if (Process32FirstW(snapshot, &entry)) {
|
||||
do {
|
||||
out.push_back(Process{entry.th32ProcessID, narrow(entry.szExeFile), {}});
|
||||
} while (Process32NextW(snapshot, &entry));
|
||||
}
|
||||
CloseHandle(snapshot);
|
||||
|
||||
std::sort(out.begin(), out.end(), [](const Process &a, const Process &b) {
|
||||
if (a.name != b.name) return a.name < b.name;
|
||||
return a.pid < b.pid;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
bool FindMinecraft(Process &out, std::string &error) {
|
||||
const wchar_t *classes[] = {L"LWJGL", L"GLFW30"};
|
||||
for (const wchar_t *cls : classes) {
|
||||
HWND window = FindWindowW(cls, nullptr);
|
||||
if (!window) continue;
|
||||
DWORD pid = 0;
|
||||
GetWindowThreadProcessId(window, &pid);
|
||||
if (!pid) continue;
|
||||
out.pid = pid;
|
||||
out.name = process_name(pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
// No window class match: fall back to the first javaw.exe/java.exe, which
|
||||
// is right when the game is still starting or the window class differs.
|
||||
for (const Process &process : ListProcesses()) {
|
||||
std::string name = lower(process.name);
|
||||
if (name == "javaw.exe" || name == "java.exe") {
|
||||
out = process;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
error = "Minecraft is not running (no LWJGL/GLFW30 window and no javaw.exe)";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FindTarget(const std::string &target, Process &out, std::string &error) {
|
||||
if (target.empty()) {
|
||||
error = "no target given";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool numeric = std::all_of(target.begin(), target.end(), [](unsigned char c) { return std::isdigit(c); });
|
||||
if (numeric) {
|
||||
out.pid = std::strtoul(target.c_str(), nullptr, 10);
|
||||
out.name = process_name((DWORD)out.pid);
|
||||
if (out.pid && !out.name.empty()) return true;
|
||||
error = "no process with pid " + target;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string needle = lower(target);
|
||||
auto all = ListProcesses();
|
||||
for (const Process &process : all) {
|
||||
if (lower(process.name) == needle) {
|
||||
out = process;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const Process &process : all) {
|
||||
if (lower(process.name).find(needle) != std::string::npos) {
|
||||
out = process;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
error = "no process matching '" + target + "'";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Inject(unsigned long pid, const std::string &library, const LogFn &log) {
|
||||
std::wstring wide_library = widen(library);
|
||||
|
||||
DWORD full_size = GetFullPathNameW(wide_library.c_str(), 0, nullptr, nullptr);
|
||||
if (full_size) {
|
||||
std::wstring full(full_size, L'\0');
|
||||
DWORD written = GetFullPathNameW(wide_library.c_str(), full_size, full.data(), nullptr);
|
||||
if (written && written < full_size) {
|
||||
full.resize(written);
|
||||
wide_library = full;
|
||||
}
|
||||
}
|
||||
|
||||
log("injecting " + narrow(wide_library) + " into pid " + std::to_string(pid));
|
||||
|
||||
HANDLE process = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION |
|
||||
PROCESS_VM_WRITE | PROCESS_VM_READ,
|
||||
FALSE, (DWORD)pid);
|
||||
if (!process) {
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_ACCESS_DENIED) log("access denied -- try running the injector as administrator");
|
||||
else log("OpenProcess failed (" + std::to_string(err) + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
SIZE_T bytes = (wide_library.size() + 1) * sizeof(wchar_t);
|
||||
void *remote = VirtualAllocEx(process, nullptr, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!remote) {
|
||||
log("VirtualAllocEx failed (" + std::to_string(GetLastError()) + ")");
|
||||
CloseHandle(process);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
SIZE_T written = 0;
|
||||
if (!WriteProcessMemory(process, remote, wide_library.c_str(), bytes, &written)) {
|
||||
log("WriteProcessMemory failed (" + std::to_string(GetLastError()) + ")");
|
||||
} else {
|
||||
HMODULE kernel = GetModuleHandleW(L"kernel32.dll");
|
||||
auto load_library = reinterpret_cast<LPTHREAD_START_ROUTINE>(
|
||||
GetProcAddress(kernel, "LoadLibraryW"));
|
||||
if (!load_library) {
|
||||
log("could not find LoadLibraryW");
|
||||
} else {
|
||||
HANDLE thread = CreateRemoteThread(process, nullptr, 0, load_library, remote, 0, nullptr);
|
||||
if (!thread) {
|
||||
log("CreateRemoteThread failed (" + std::to_string(GetLastError()) + ")");
|
||||
} else {
|
||||
WaitForSingleObject(thread, INFINITE);
|
||||
DWORD exit_code = 0;
|
||||
if (GetExitCodeThread(thread, &exit_code)) {
|
||||
// The HMODULE return is truncated to a DWORD here; only
|
||||
// zero (failure) is meaningful.
|
||||
ok = exit_code != 0;
|
||||
if (!ok) log("LoadLibraryW returned NULL in the target");
|
||||
} else {
|
||||
log("GetExitCodeThread failed (" + std::to_string(GetLastError()) + ")");
|
||||
}
|
||||
CloseHandle(thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
|
||||
CloseHandle(process);
|
||||
return ok;
|
||||
}
|
||||
|
||||
std::string EnvironmentHint() {
|
||||
return "if injection fails with access denied, run the injector as administrator";
|
||||
}
|
||||
|
||||
} // namespace Injector
|
||||
|
||||
#endif // _WIN32
|
||||
@@ -0,0 +1,327 @@
|
||||
// slinky-inject: the loader's terminal UI and a cross-platform injector.
|
||||
//
|
||||
// slinky-inject find Minecraft, inject libslinky.so /
|
||||
// slinky.dll from beside the injector
|
||||
// slinky-inject <pid|name> same, but choose the target
|
||||
// slinky-inject <pid|name> <library> inject that library
|
||||
// slinky-inject --lib <library> choose the library only
|
||||
// slinky-inject --pid <pid> choose the target only
|
||||
// slinky-inject --list list visible processes
|
||||
//
|
||||
// Linux uses ptrace + the target's own dlopen; Windows uses
|
||||
// CreateRemoteThread(LoadLibraryW). The animation is the one from the Go
|
||||
// loader (animation/animation.go).
|
||||
#include "inject.hpp"
|
||||
#include "tui.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
struct Options {
|
||||
bool list = false;
|
||||
bool help = false;
|
||||
bool no_anim = false;
|
||||
bool pid_given = false;
|
||||
unsigned long pid = 0;
|
||||
std::string target;
|
||||
std::string library;
|
||||
};
|
||||
|
||||
void usage(const char *program) {
|
||||
std::cout <<
|
||||
"usage: " << program << " [options] [target] [library]\n"
|
||||
"\n"
|
||||
" target pid or process name (default: the running Minecraft)\n"
|
||||
" library path to libslinky.so / slinky.dll (default: beside this program)\n"
|
||||
"\n"
|
||||
"options:\n"
|
||||
" -p, --pid <pid> target a pid\n"
|
||||
" -n, --name <name> target a process name (substring match)\n"
|
||||
" -l, --lib <path> library to inject\n"
|
||||
" --list list processes and exit\n"
|
||||
" --no-anim plain output, no animation\n"
|
||||
" -h, --help this text\n";
|
||||
}
|
||||
|
||||
bool parse_options(int argc, char **argv, Options &options, std::string &error) {
|
||||
std::vector<std::string> positional;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string arg = argv[i];
|
||||
auto value = [&](const char *what) -> bool {
|
||||
if (i + 1 >= argc) {
|
||||
error = std::string(what) + " needs a value";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (arg == "-h" || arg == "--help") {
|
||||
options.help = true;
|
||||
} else if (arg == "--list") {
|
||||
options.list = true;
|
||||
} else if (arg == "--no-anim") {
|
||||
options.no_anim = true;
|
||||
} else if (arg == "-p" || arg == "--pid") {
|
||||
if (!value("--pid")) return false;
|
||||
char *end = nullptr;
|
||||
options.pid = std::strtoul(argv[++i], &end, 10);
|
||||
options.pid_given = true;
|
||||
if (!end || *end || !options.pid) {
|
||||
error = "invalid pid '" + std::string(argv[i]) + "'";
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "-n" || arg == "--name") {
|
||||
if (!value("--name")) return false;
|
||||
options.target = argv[++i];
|
||||
} else if (arg == "-l" || arg == "--lib") {
|
||||
if (!value("--lib")) return false;
|
||||
options.library = argv[++i];
|
||||
} else if (!arg.empty() && arg[0] == '-') {
|
||||
error = "unknown option '" + arg + "'";
|
||||
return false;
|
||||
} else {
|
||||
positional.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
// One positional that names a library is a library, not a target.
|
||||
if (positional.size() == 1 && options.target.empty() && !options.pid_given) {
|
||||
std::string lower = positional[0];
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](unsigned char c) { return (char)std::tolower(c); });
|
||||
if (lower.size() > 3 && (lower.substr(lower.size() - 3) == ".so" ||
|
||||
lower.substr(lower.size() - 4) == ".dll")) {
|
||||
options.library = positional[0];
|
||||
positional.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (positional.size() > 2) {
|
||||
error = "too many positional arguments";
|
||||
return false;
|
||||
}
|
||||
if (positional.size() >= 1 && options.target.empty() && !options.pid_given) options.target = positional[0];
|
||||
if (positional.size() >= 2 && options.library.empty()) options.library = positional[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
std::filesystem::path executable_path() {
|
||||
#ifdef _WIN32
|
||||
wchar_t buffer[MAX_PATH];
|
||||
DWORD n = GetModuleFileNameW(nullptr, buffer, MAX_PATH);
|
||||
return std::filesystem::path(std::wstring(buffer, n));
|
||||
#else
|
||||
char buffer[PATH_MAX];
|
||||
ssize_t n = readlink("/proc/self/exe", buffer, sizeof buffer - 1);
|
||||
if (n <= 0) return {};
|
||||
buffer[n] = 0;
|
||||
return std::filesystem::path(buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string default_library() {
|
||||
#ifdef _WIN32
|
||||
const char *names[] = {"slinky.dll", "libslinky.dll"};
|
||||
const char *build[] = {"build/cmake/slinky.dll", "../build/cmake/slinky.dll"};
|
||||
#else
|
||||
const char *names[] = {"libslinky.so", "slinky.so"};
|
||||
const char *build[] = {"build/linux/libslinky.so", "../build/linux/libslinky.so"};
|
||||
#endif
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::path exe = executable_path();
|
||||
std::vector<std::filesystem::path> candidates;
|
||||
|
||||
if (!exe.empty()) {
|
||||
for (const char *name : names) candidates.push_back(exe.parent_path() / name);
|
||||
}
|
||||
for (const char *name : names) candidates.push_back(std::filesystem::current_path(ec) / name);
|
||||
for (const char *path : build) candidates.push_back(std::filesystem::current_path(ec) / path);
|
||||
#ifndef _WIN32
|
||||
candidates.push_back("/usr/local/lib/libslinky.so");
|
||||
candidates.push_back("/usr/lib/libslinky.so");
|
||||
#endif
|
||||
|
||||
for (const auto &candidate : candidates) {
|
||||
if (std::filesystem::exists(candidate, ec)) {
|
||||
auto canonical = std::filesystem::weakly_canonical(candidate, ec);
|
||||
return ec ? candidate.string() : canonical.string();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void sleep_ms(int ms) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||
}
|
||||
|
||||
void render_for(Tui::Screen &screen, int milliseconds, const std::string &title, const std::string &message,
|
||||
bool error = false) {
|
||||
auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(milliseconds);
|
||||
do {
|
||||
screen.Render(title, message, error);
|
||||
sleep_ms(16);
|
||||
} while (std::chrono::steady_clock::now() < end);
|
||||
}
|
||||
|
||||
void print_list(const std::vector<Injector::Process> &processes) {
|
||||
std::cout << " PID NAME COMMAND\n";
|
||||
for (const auto &process : processes) {
|
||||
std::string command = process.command_line;
|
||||
if (command.size() > 100) command = command.substr(0, 97) + "...";
|
||||
std::cout << std::setw(6) << process.pid << " " << std::setw(20) << std::left << process.name << " "
|
||||
<< command << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Options options;
|
||||
std::string error;
|
||||
if (!parse_options(argc, argv, options, error)) {
|
||||
std::cerr << "slinky-inject: " << error << "\n\n";
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
if (options.help) {
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (options.list) {
|
||||
print_list(Injector::ListProcesses());
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool interactive = Tui::EnableAnsi() && Tui::StdoutIsTty() && !options.no_anim;
|
||||
Tui::Screen screen(interactive);
|
||||
|
||||
if (!options.library.empty()) {
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(options.library, ec)) {
|
||||
std::cerr << "slinky-inject: library not found: " << options.library << "\n";
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
options.library = default_library();
|
||||
if (options.library.empty()) {
|
||||
if (interactive) {
|
||||
screen.Render("ERROR",
|
||||
"Library not found. Put libslinky.so/slinky.dll beside the injector, "
|
||||
"or pass its path as the second argument.",
|
||||
true);
|
||||
sleep_ms(3000);
|
||||
} else {
|
||||
std::cerr << "slinky-inject: library not found (pass its path as the second argument)\n";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- find the target ----
|
||||
Injector::Process target;
|
||||
if (interactive) {
|
||||
render_for(screen, 450, "FINDING MINECRAFT", "Looking for the game process...");
|
||||
} else {
|
||||
std::cout << "Finding Minecraft...\n";
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
if (options.pid_given) {
|
||||
target.pid = options.pid;
|
||||
found = true;
|
||||
} else if (!options.target.empty()) {
|
||||
found = Injector::FindTarget(options.target, target, error);
|
||||
} else {
|
||||
found = Injector::FindMinecraft(target, error);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
std::string hint = Injector::EnvironmentHint();
|
||||
std::string message = error;
|
||||
if (!hint.empty()) message += "\n\n" + hint;
|
||||
if (interactive) {
|
||||
screen.Render("ERROR", message, true);
|
||||
sleep_ms(4000);
|
||||
} else {
|
||||
std::cerr << "slinky-inject: " << message << "\n";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---- inject, with the animation still running ----
|
||||
std::string last_log = "Injecting into Minecraft...";
|
||||
std::mutex log_mutex;
|
||||
std::atomic<bool> done{false};
|
||||
std::atomic<bool> injected{false};
|
||||
|
||||
auto started = std::chrono::steady_clock::now();
|
||||
std::thread worker([&]() {
|
||||
injected.store(Injector::Inject(
|
||||
target.pid, options.library,
|
||||
[&](const std::string &line) {
|
||||
std::lock_guard<std::mutex> lock(log_mutex);
|
||||
last_log = line;
|
||||
}));
|
||||
done.store(true);
|
||||
});
|
||||
|
||||
if (interactive) {
|
||||
for (;;) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(log_mutex);
|
||||
screen.Render("INJECTING", last_log);
|
||||
}
|
||||
sleep_ms(16);
|
||||
bool finished = done.load();
|
||||
auto elapsed = std::chrono::steady_clock::now() - started;
|
||||
if (finished && elapsed > std::chrono::milliseconds(1200)) break;
|
||||
}
|
||||
}
|
||||
worker.join();
|
||||
|
||||
if (!injected.load()) {
|
||||
std::string hint = Injector::EnvironmentHint();
|
||||
std::string message = "Injection failed. " + last_log;
|
||||
if (!hint.empty()) message += "\n\n" + hint;
|
||||
if (interactive) {
|
||||
screen.Render("ERROR", message, true);
|
||||
sleep_ms(5000);
|
||||
} else {
|
||||
std::cerr << "slinky-inject: " << message << "\n";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---- success countdown, exactly the Go loader's five seconds ----
|
||||
if (interactive) {
|
||||
for (int i = 5; i > 0; --i) {
|
||||
std::string title = "Injected successfully. Loader will close in " + std::to_string(i) + "s.";
|
||||
render_for(screen, 1000, title, "DONE");
|
||||
}
|
||||
} else {
|
||||
std::cout << "Injected successfully into pid " << target.pid << ".\n";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
#include "tui.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Tui {
|
||||
namespace {
|
||||
|
||||
// Exact frames from the Go loader. The trailing spaces are part of the art:
|
||||
// they keep the right edge of the box aligned between frames.
|
||||
const char *kLogo[4] = {
|
||||
" _ \n"
|
||||
"(( ____ \n"
|
||||
" \\\\________________( ( e\\____o\n"
|
||||
" ( _____/ \n"
|
||||
" \\___________________/ \n"
|
||||
" / \\ \\ / ",
|
||||
|
||||
" _ \n"
|
||||
" (( ____ \n"
|
||||
" ||________________( ( e\\____o\n"
|
||||
" ( _____/ \n"
|
||||
" \\___________________/ \n"
|
||||
" ) | | ) ",
|
||||
|
||||
" _ \n"
|
||||
" // ____ \n"
|
||||
" //________________( ( e\\____o\n"
|
||||
" ( _____/ \n"
|
||||
" \\___________________/ \n"
|
||||
" \\ / / \\ ",
|
||||
|
||||
" _ \n"
|
||||
" (( ____ \n"
|
||||
" ||________________( ( e\\____o\n"
|
||||
" ( _____/ \n"
|
||||
" \\___________________/ \n"
|
||||
" | ) ) | ",
|
||||
};
|
||||
|
||||
const char *kErrorLogo =
|
||||
"\n"
|
||||
" ____ \n"
|
||||
" _________________( ( x\\____o\n"
|
||||
"// _____/ \n"
|
||||
"||____________________/ \n"
|
||||
" | | | | ";
|
||||
|
||||
const char *kOrange = "\x1b[38;5;208m";
|
||||
const char *kRed = "\x1b[91m";
|
||||
const char *kReset = "\x1b[0m";
|
||||
|
||||
std::string make_line() {
|
||||
std::random_device device;
|
||||
std::mt19937 rng(device());
|
||||
std::string line;
|
||||
line.reserve(25);
|
||||
for (int i = 0; i < 25; i++) line.push_back(rng() & 1 ? '_' : '-');
|
||||
return line;
|
||||
}
|
||||
|
||||
const std::string &base_line() {
|
||||
static const std::string line = make_line();
|
||||
return line;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool EnableAnsi() {
|
||||
#ifdef _WIN32
|
||||
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
DWORD mode = 0;
|
||||
if (!out || out == INVALID_HANDLE_VALUE || !GetConsoleMode(out, &mode)) return false;
|
||||
return SetConsoleMode(out, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool StdoutIsTty() {
|
||||
#ifdef _WIN32
|
||||
return _isatty(_fileno(stdout)) != 0;
|
||||
#else
|
||||
return isatty(STDOUT_FILENO) != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Logo(int frame) {
|
||||
return kLogo[((frame % 4) + 4) % 4];
|
||||
}
|
||||
|
||||
std::string ErrorLogo() { return kErrorLogo; }
|
||||
|
||||
// The line is the base pattern shifted one character per frame, exactly as the
|
||||
// Go loader's init() builds its 30 frames.
|
||||
std::string SlinkyLine(int frame) {
|
||||
const std::string &base = base_line();
|
||||
if (base.empty()) return {};
|
||||
int shift = ((frame % (int)base.size()) + (int)base.size()) % (int)base.size();
|
||||
return base.substr(shift) + base.substr(0, shift);
|
||||
}
|
||||
|
||||
Screen::Screen(bool animate) : animate_(animate && StdoutIsTty()) {
|
||||
if (animate_) {
|
||||
std::fputs("\x1b[?1049h\x1b[?25l", stdout);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
Screen::~Screen() {
|
||||
if (animate_) {
|
||||
std::fputs("\x1b[?25h\x1b[?1049l", stdout);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
void Screen::Render(const std::string &title, const std::string &message, bool error) {
|
||||
if (!animate_) {
|
||||
// Non-interactive output: one line per state change is enough.
|
||||
std::fputs(error ? "ERROR: " : "", stdout);
|
||||
std::fputs(title.c_str(), stdout);
|
||||
if (!message.empty()) {
|
||||
std::fputs(" - ", stdout);
|
||||
std::fputs(message.c_str(), stdout);
|
||||
}
|
||||
std::fputc('\n', stdout);
|
||||
std::fflush(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the whole frame first. Clearing the screen before every 16 ms redraw made the
|
||||
// console blank between frames (the flicker); instead the cursor goes home and each line is
|
||||
// overwritten in one write, with erase-to-end-of-line cleaning up shorter content.
|
||||
const int frame_index = Frame();
|
||||
|
||||
std::string body;
|
||||
body.reserve(1024);
|
||||
if (error) {
|
||||
body += kRed;
|
||||
body += kErrorLogo;
|
||||
} else {
|
||||
body += kLogo[frame_index % 4];
|
||||
}
|
||||
body += '\n';
|
||||
body += SlinkyLine(frame_index);
|
||||
body += "\n\n> ";
|
||||
body += (error ? kRed : kOrange);
|
||||
body += title;
|
||||
body += kReset;
|
||||
body += "\n\n";
|
||||
body += message;
|
||||
body += '\n';
|
||||
|
||||
// Nothing changed since the last call: leave the terminal alone.
|
||||
if (body == last_body_) return;
|
||||
last_body_ = body;
|
||||
|
||||
std::string out;
|
||||
out.reserve(body.size() + 64);
|
||||
// Synchronized output: supporting terminals swap the completed frame atomically.
|
||||
out += "\x1b[?2026h";
|
||||
out += "\x1b[H";
|
||||
for (char c : body) {
|
||||
if (c == '\n') {
|
||||
out += "\x1b[K\n";
|
||||
} else {
|
||||
out += c;
|
||||
}
|
||||
}
|
||||
out += "\x1b[K\x1b[J"; // clear the rest of the last line and anything below the frame
|
||||
out += "\x1b[?2026l";
|
||||
|
||||
std::fwrite(out.data(), 1, out.size(), stdout);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
int Screen::Frame() const {
|
||||
auto elapsed = std::chrono::steady_clock::now() - start_;
|
||||
return (int)(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count() / kFrameMs);
|
||||
}
|
||||
|
||||
} // namespace Tui
|
||||
@@ -0,0 +1,40 @@
|
||||
// The loader's terminal UI: the four-frame Slinky logo and the sliding
|
||||
// underscore line, ported from the Go loader (animation/animation.go) so the
|
||||
// injector looks and moves exactly like it.
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
namespace Tui {
|
||||
|
||||
bool EnableAnsi(); // Windows: turn on VT processing; Linux: no-op
|
||||
bool StdoutIsTty();
|
||||
|
||||
std::string Logo(int frame);
|
||||
std::string ErrorLogo();
|
||||
std::string SlinkyLine(int frame);
|
||||
|
||||
class Screen {
|
||||
public:
|
||||
explicit Screen(bool animate);
|
||||
~Screen();
|
||||
|
||||
Screen(const Screen&) = delete;
|
||||
Screen& operator=(const Screen&) = delete;
|
||||
|
||||
void Render(const std::string& title, const std::string& message, bool error = false);
|
||||
private:
|
||||
// The animation advances with time, not with redraws: the caller redraws every ~16 ms so status
|
||||
// messages show up at once, which made a frame-per-redraw animation run at 60 fps.
|
||||
static constexpr int kFrameMs = 100;
|
||||
int Frame() const;
|
||||
|
||||
bool animate_;
|
||||
std::chrono::steady_clock::time_point start_ = std::chrono::steady_clock::now();
|
||||
// Last rendered content: the caller redraws at 60 Hz, but the frame only changes every
|
||||
// kFrameMs (or on a status change), so identical redraws are skipped.
|
||||
std::string last_body_;
|
||||
};
|
||||
|
||||
} // namespace Tui
|
||||
+10450
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,3 @@
|
||||
# generated by tools/gen_data.py: import-table slots used by code that calls the image's thunks
|
||||
.section .data,"dw"
|
||||
.balign 8
|
||||
@@ -0,0 +1,29 @@
|
||||
// Aliases for logic functions the engine refers to by their address name.
|
||||
#include "slinky/engine.h"
|
||||
#pragma comment(linker, "/alternatename:R_bool=visit_bool_setting")
|
||||
#pragma comment(linker, "/alternatename:R_enum=visit_choice_setting")
|
||||
#pragma comment(linker, "/alternatename:R_key=visit_color_setting")
|
||||
#pragma comment(linker, "/alternatename:R_kind2=visit_list_setting")
|
||||
#pragma comment(linker, "/alternatename:R_num=visit_float_setting")
|
||||
#pragma comment(linker, "/alternatename:R_rng=visit_range_setting")
|
||||
#pragma comment(linker, "/alternatename:W_bool=widget_bool")
|
||||
#pragma comment(linker, "/alternatename:W_color=widget_color")
|
||||
#pragma comment(linker, "/alternatename:W_enumw=widget_enum")
|
||||
#pragma comment(linker, "/alternatename:W_items=widget_items")
|
||||
#pragma comment(linker, "/alternatename:W_label=draw_section_label")
|
||||
#pragma comment(linker, "/alternatename:W_range=widget_range")
|
||||
#pragma comment(linker, "/alternatename:W_slider=widget_slider")
|
||||
#pragma comment(linker, "/alternatename:velocity_shared_conditions_entry=velocity_shared_conditions")
|
||||
#pragma comment(linker, "/alternatename:gui_get_scale_entry=gui_get_scale")
|
||||
#pragma comment(linker, "/alternatename:gui_handle_input_event_entry=gui_handle_input_event")
|
||||
#pragma comment(linker, "/alternatename:mod_overlay_rebuild_visibility_entry=mod_overlay_rebuild_visibility")
|
||||
#pragma comment(linker, "/alternatename:mod_overlay_append_entry_entry=mod_overlay_append_entry")
|
||||
#pragma comment(linker, "/alternatename:is_held_item_accepted_weapon=weapons_check_held_item_cached")
|
||||
#pragma comment(linker, "/alternatename:weapons_is_item_allowed_by_class_entry=weapons_is_item_allowed_by_class")
|
||||
#pragma comment(linker, "/alternatename:weapons_find_first_weapon_slot_entry=weapons_find_first_weapon_slot")
|
||||
#pragma comment(linker, "/alternatename:friends_friends_friend_list=friends_on_middle_click_add")
|
||||
#pragma comment(linker, "/alternatename:chams_render_living_entity_entry=chams_render_living_entity")
|
||||
#pragma comment(linker, "/alternatename:piercing_decide_which_entity_entry=piercing_decide_which_entity")
|
||||
#pragma comment(linker, "/alternatename:piercing_should_pierce_blocks_entry=piercing_should_pierce_blocks")
|
||||
#pragma comment(linker, "/alternatename:ghost_hand_should_ignore_entity_entry=ghost_hand_should_ignore_entity")
|
||||
#pragma comment(linker, "/alternatename:log_fmt=libc_snprintf")
|
||||
@@ -0,0 +1,428 @@
|
||||
// 0x7000: Java native entry points (SlinkyHooks13): wrap the JNIEnv in a Jvm handle and forward
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jids.h"
|
||||
extern "C" JIds *java_natives_world_cached_jni_ids;
|
||||
extern "C" void slinky_logf(const char *fmt, ...);
|
||||
extern "C" char *ptr_to_frame_module_settings; // -> module settings block
|
||||
FN(void, render_world_events, (JNIEnv *env, int key, bool down));
|
||||
FN(void, tick_rotation, (Jvm *jvm));
|
||||
FN(void, set_local_player_max_hurt_time, (Jvm *jvm));
|
||||
FN(unsigned char, world_is_loaded, (Jvm *jvm));
|
||||
FN(void, slinky_gui_init_once, ()); // one-time NanoVG/GUI setup (protected/hooks.cpp)
|
||||
FN(void, slinky_try_pending_class_hooks, ()); // Java 9+: deferred WorldClient retransform (protected/init.cpp)
|
||||
// The unload sequence sets java_natives_frame_client_shutting_down_natives_check ("client is shutting down") before slinky_teardown frees every
|
||||
// global about a second later. The JVM can still dispatch any of these natives in that window (the
|
||||
// retransform is not guaranteed to have unwired every patched call site: see the java_set_render_manager_player_view_y/serialize_settings
|
||||
// unload crashes in hs_err pid6708/pid2260), so every entry reachable through the hooks checks the flag
|
||||
// first and does nothing.
|
||||
extern "C" unsigned char java_natives_frame_client_shutting_down_natives_check;
|
||||
extern "C" void hook_post_world_clear_1_7(JNIEnv *env, jclass, int key) { if (!java_natives_frame_client_shutting_down_natives_check) render_world_events(env, key, false); }
|
||||
extern "C" void hook_post_world_clear_1_8(JNIEnv *env, jclass, int key) { if (!java_natives_frame_client_shutting_down_natives_check) render_world_events(env, key, true); }
|
||||
extern "C" void hook_is_display_active_prerender(JNIEnv *env, jclass) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
tick_rotation(&jvm);
|
||||
if (ptr_to_frame_module_settings[0x14] == 1) set_local_player_max_hurt_time(&jvm);
|
||||
world_is_loaded(&jvm);
|
||||
}
|
||||
|
||||
struct Rot { float yaw, pitch; };
|
||||
FN(unsigned char, is_silent_rotation_active, ());
|
||||
FN(Rot, get_silent_rotation, ());
|
||||
FN(void *, get_minecraft, (Jvm *jvm));
|
||||
FN(void *, get_local_player, (Jvm *jvm, void *minecraft));
|
||||
FN(void *, get_minecraft_render_global, (Jvm *jvm, void *minecraft));
|
||||
FN(void, set_render_manager_boolean, (Jvm *jvm, void *render_manager, bool on));
|
||||
FN(void, apply_rotation_to_player, (Jvm *jvm, void *player));
|
||||
FN(void, set_silent_rotation_active, (Jvm *jvm, void *player, bool on));
|
||||
FN(void, call_orient_camera, (Jvm *jvm, void *object, float t));
|
||||
FN(Rot, get_entity_rotation, (Jvm *jvm, void *entity));
|
||||
FN(Rot, get_entity_prev_rotation, (Jvm *jvm, void *entity));
|
||||
FN(void, set_entity_rotation, (Jvm *jvm, void *entity, Rot rotation));
|
||||
FN(void, set_entity_prev_rotation, (Jvm *jvm, void *entity, Rot rotation));
|
||||
FN(void, set_active_render_info, (Jvm *jvm, void *active_render_info, bool b));
|
||||
// camera orientation hook: with silent rotations active, orient using the server rotation
|
||||
extern "C" void hook_orient_camera(JNIEnv *env, jclass, void *receiver, float t) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
if (is_silent_rotation_active()) {
|
||||
void *minecraft = get_minecraft(&jvm);
|
||||
void *player = get_local_player(&jvm, minecraft);
|
||||
set_render_manager_boolean(&jvm, get_minecraft_render_global(&jvm, minecraft), true);
|
||||
apply_rotation_to_player(&jvm, player);
|
||||
set_silent_rotation_active(&jvm, player, true);
|
||||
call_orient_camera(&jvm, receiver, t);
|
||||
set_silent_rotation_active(&jvm, player, false);
|
||||
} else {
|
||||
call_orient_camera(&jvm, receiver, t);
|
||||
}
|
||||
}
|
||||
// same for the other hook: temporarily swap in the silent rotation around the call
|
||||
extern "C" void hook_update_active_render_info(JNIEnv *env, jclass, void *receiver, unsigned char b) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
if (is_silent_rotation_active()) {
|
||||
void *minecraft = get_minecraft(&jvm);
|
||||
void *player = get_local_player(&jvm, minecraft);
|
||||
Rot rotation = get_entity_rotation(&jvm, player);
|
||||
Rot entity_prev_rotation = get_entity_prev_rotation(&jvm, player);
|
||||
Rot silent_rotation = get_silent_rotation();
|
||||
set_entity_rotation(&jvm, player, silent_rotation);
|
||||
set_entity_prev_rotation(&jvm, player, silent_rotation);
|
||||
set_active_render_info(&jvm, receiver, b != 0);
|
||||
set_entity_rotation(&jvm, player, rotation);
|
||||
set_entity_prev_rotation(&jvm, player, entity_prev_rotation);
|
||||
} else {
|
||||
set_active_render_info(&jvm, receiver, b != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- tick hooks: build the module Ctx, dispatch pre-event (0xe), run the game method, dispatch post (0xf) ----
|
||||
extern "C" char java_natives_world_module_manager[], java_natives_frame_module_manager_target_state[];
|
||||
extern "C" void *init_profile_list, *java_natives_world_key_state_table_one_byte;
|
||||
extern "C" char *ptr_to_gui_module_descriptor;
|
||||
struct HCtx { Jvm *jvm; void *module_manager; void *profile_list; void *minecraft; void *world; void *player; void *key_state_table; void *target;
|
||||
unsigned char in_game; unsigned char padding_41[3]; int padding_44; };
|
||||
struct HEvent { int type; int padding_04; HCtx *ctx; unsigned char flag, cancel; char rest[0x36]; };
|
||||
FN(void *, get_world, (Jvm *jvm, void *minecraft));
|
||||
FN(unsigned char, is_in_game, (Jvm *jvm, void *minecraft));
|
||||
FN(void, tick_pre_click, (HCtx *ctx, void *state));
|
||||
FN(void, dispatch_event, (void *mgr, HEvent *e));
|
||||
FN(void, java_call_minecraft_click_mouse, (Jvm *jvm, void *minecraft));
|
||||
FN(void, java_call_minecraft_right_click_mouse, (Jvm *jvm, void *minecraft));
|
||||
FN(void, tick_click, (HCtx *ctx));
|
||||
FN(void, tick_click_done, (HCtx *ctx));
|
||||
static inline __attribute__((always_inline)) void make_ctx(HCtx *ctx, Jvm *jvm, void *minecraft) {
|
||||
void *world = get_world(jvm, minecraft);
|
||||
void *player = get_local_player(jvm, minecraft);
|
||||
ctx->jvm = jvm;
|
||||
ctx->module_manager = java_natives_world_module_manager;
|
||||
ctx->profile_list = init_profile_list;
|
||||
ctx->minecraft = minecraft;
|
||||
ctx->world = world;
|
||||
ctx->player = player;
|
||||
ctx->key_state_table = java_natives_world_key_state_table_one_byte;
|
||||
ctx->target = java_natives_frame_module_manager_target_state;
|
||||
ctx->in_game = ptr_to_gui_module_descriptor[0x14] ? 0 : is_in_game(jvm, minecraft);
|
||||
ctx->padding_41[0] = 0; ctx->padding_41[1] = 0; ctx->padding_41[2] = 0;
|
||||
*(int *)&ctx->padding_41[0] = 0;
|
||||
ctx->padding_44 = 0;
|
||||
}
|
||||
extern "C" void hook_click_mouse(JNIEnv *env, jclass, void *minecraft) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
HCtx ctx;
|
||||
make_ctx(&ctx, &jvm, minecraft);
|
||||
tick_pre_click(&ctx, java_natives_frame_module_manager_target_state);
|
||||
HEvent e = {};
|
||||
e.type = 0xe;
|
||||
e.ctx = &ctx;
|
||||
e.flag = 1;
|
||||
dispatch_event(java_natives_world_module_manager, &e);
|
||||
if (e.cancel) return;
|
||||
java_call_minecraft_click_mouse(&jvm, minecraft);
|
||||
e.type = 0xf;
|
||||
dispatch_event(java_natives_world_module_manager, &e);
|
||||
}
|
||||
extern "C" void hook_right_click_mouse(JNIEnv *env, jclass, void *minecraft) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
HCtx ctx;
|
||||
make_ctx(&ctx, &jvm, minecraft);
|
||||
HEvent e = {};
|
||||
e.type = 0xe;
|
||||
e.ctx = &ctx;
|
||||
|
||||
e.cancel = 0;
|
||||
dispatch_event(java_natives_world_module_manager, &e);
|
||||
tick_click(&ctx);
|
||||
if (e.cancel) return;
|
||||
java_call_minecraft_right_click_mouse(&jvm, minecraft);
|
||||
e.type = 0xf;
|
||||
dispatch_event(java_natives_world_module_manager, &e);
|
||||
tick_click_done(&ctx);
|
||||
}
|
||||
|
||||
// ---- main per-frame hook: context, GUI frame (events 9/10), module ticks, unload ----
|
||||
FN(void, hook_game_window, ());
|
||||
FN(void *, get_timer, (Jvm *jvm, void *minecraft));
|
||||
FN(void, invoke_callback, (void *x));
|
||||
FN(void, apply_module_state, (void *mgr, void *mod, int v));
|
||||
FN(double, time_now, ());
|
||||
FN(int, display_width, (Jvm *jvm));
|
||||
FN(int, display_height, (Jvm *jvm));
|
||||
FN(unsigned char, is_event_handled, (void *mgr, int type));
|
||||
FN(unsigned char, resize_render_target, (void *gui, int w, int h));
|
||||
FN(double, gui_get_scale_entry, ());
|
||||
FN(void, set_render_scale, (void *gui, double t));
|
||||
FN(void, begin_frame_framebuffer, (void *gui, double t));
|
||||
FN(void, clear_frame, (void *gui));
|
||||
FN(void, begin_frame, (void *gui, double t));
|
||||
FN(void, draw_framebuffer_texture_over_screen, (void *gui));
|
||||
FN(void, end_frame, (void *gui));
|
||||
FN(unsigned char, has_queued_modules, (void *mgr));
|
||||
FN(void, apply_module_toggles, (HCtx *ctx));
|
||||
FN(void, tick_modules, (HCtx *ctx, void *timer, double now));
|
||||
FN(void, tick_netty, (Jvm *jvm));
|
||||
FN(unsigned char, is_unload_requested_flag, ());
|
||||
FN(int, save_settings, (void *module_manager, void *profile_list));
|
||||
FN(void, queue_module_toggle, (void *module_manager, void *module, unsigned char enabled));
|
||||
struct Pair16 { long long a, b; };
|
||||
FN(int, unregister_channel_class, (Jvm *jvm, Pair16 *p));
|
||||
FN(void, tick_post_frame, (HCtx *ctx, void *state));
|
||||
FN(int, set_java_hooks, (JNIEnv *env, int x));
|
||||
extern "C" char gui_state_block[]; // GUI state; [0x4cb] = unload requested
|
||||
FN(void, restore_game_window, ());
|
||||
FN(void, start_unload_thread, (void *x));
|
||||
typedef void (*GlPushAttrib)(unsigned);
|
||||
typedef void (*GlPopAttrib)();
|
||||
extern "C" GlPushAttrib *ptr_to_pGlPushAttrib;
|
||||
extern "C" GlPopAttrib *ptr_to_pGlPopAttrib;
|
||||
extern "C" const double threshold_0_00833333;
|
||||
extern "C" double frame_start_time;
|
||||
extern "C" unsigned char frame_prev_module_state, frame_input_flag, frame_repass_needed, frame_spinlock, java_natives_frame_client_shutting_down_natives_check;
|
||||
extern "C" int frame_resize_cooldown, render_target_width, render_target_height;
|
||||
extern "C" void *java_natives_frame_module_key_binds_key_module, *frame_render_target, *init_our_module_handle;
|
||||
extern "C" Pair16 init_slinkychannelhandler_class_constructor;
|
||||
struct ModEnt { char padding_00[0x1e8]; void (*on_shutdown)(HEvent *); };
|
||||
struct Mgr { ModEnt **data; unsigned long long count; };
|
||||
struct GuiEvent { int type; int padding_04; HCtx *ctx; int flags; float dt; char rest[0x30]; };
|
||||
struct GuiEvent2 { long long type; HCtx *ctx; void *gui; float dt; char rest[0x2c]; };
|
||||
static inline __attribute__((always_inline)) void spin_lock() {
|
||||
while (__atomic_exchange_n(&frame_spinlock, 1, __ATOMIC_SEQ_CST)) __builtin_ia32_pause();
|
||||
}
|
||||
extern "C" void hook_update_display(JNIEnv *env) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return; // the client is shutting down; this frame must not touch anything the
|
||||
// unload thread is about to free (save_settings of a second pass crashed here)
|
||||
hook_game_window();
|
||||
slinky_gui_init_once(); // updateDisplay runs on the client thread with the GL context current, also in the menu
|
||||
slinky_try_pending_class_hooks();
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
void *minecraft = get_minecraft(&jvm);
|
||||
void *player = get_local_player(&jvm, minecraft);
|
||||
void *world = get_world(&jvm, minecraft);
|
||||
void *timer = get_timer(&jvm, minecraft);
|
||||
HCtx ctx;
|
||||
ctx.jvm = &jvm; ctx.module_manager = java_natives_world_module_manager; ctx.profile_list = init_profile_list; ctx.minecraft = minecraft; ctx.world = world; ctx.player = player;
|
||||
ctx.key_state_table = java_natives_world_key_state_table_one_byte; ctx.target = java_natives_frame_module_manager_target_state;
|
||||
char *cfg = ptr_to_gui_module_descriptor;
|
||||
ctx.in_game = cfg[0x14] ? 0 : is_in_game(&jvm, minecraft);
|
||||
*(int *)&ctx.padding_41[0] = 0;
|
||||
ctx.padding_44 = 0;
|
||||
if (!ctx.in_game && frame_prev_module_state) {
|
||||
invoke_callback(java_natives_frame_module_key_binds_key_module);
|
||||
if (!world_is_loaded(&jvm)) __builtin_memset(java_natives_world_key_state_table_one_byte, 0, 0x68);
|
||||
}
|
||||
frame_prev_module_state = ctx.in_game;
|
||||
if (*(int *)(cfg + 0x1c) == 0) apply_module_state(java_natives_world_module_manager, ptr_to_gui_module_descriptor, 0x63);
|
||||
double now = time_now();
|
||||
int W = display_width(&jvm);
|
||||
int H = display_height(&jvm);
|
||||
if (0.0 > frame_start_time) frame_start_time = now;
|
||||
if (frame_render_target && !is_event_handled(java_natives_world_module_manager, 0xa)) {
|
||||
render_target_width = W;
|
||||
render_target_height = H;
|
||||
if (resize_render_target(frame_render_target, W, H)) {
|
||||
frame_resize_cooldown = 0xa;
|
||||
} else if (frame_resize_cooldown > 0) {
|
||||
frame_resize_cooldown = frame_resize_cooldown - 1;
|
||||
}
|
||||
void *gui = frame_render_target;
|
||||
set_render_scale(gui, gui_get_scale_entry());
|
||||
double dt = now - frame_start_time;
|
||||
if (dt > threshold_0_00833333) {
|
||||
float fdt = (float)dt;
|
||||
GuiEvent e = {};
|
||||
e.type = 9;
|
||||
e.ctx = &ctx;
|
||||
e.dt = fdt;
|
||||
dispatch_event(java_natives_world_module_manager, (HEvent *)&e);
|
||||
int flags = e.flags;
|
||||
frame_input_flag = flags & 1;
|
||||
if (flags & 1) {
|
||||
if ((flags & 2) || frame_repass_needed || frame_resize_cooldown > 0) {
|
||||
unsigned char again = (unsigned)(flags & 2) >> 1;
|
||||
(*ptr_to_pGlPushAttrib)(0xffffffff);
|
||||
begin_frame_framebuffer(frame_render_target, now);
|
||||
GuiEvent2 e2 = {0xa, &ctx, frame_render_target, fdt, {}};
|
||||
dispatch_event(java_natives_world_module_manager, (HEvent *)&e2);
|
||||
clear_frame(frame_render_target);
|
||||
(*ptr_to_pGlPopAttrib)();
|
||||
frame_repass_needed = again;
|
||||
}
|
||||
}
|
||||
frame_start_time = now;
|
||||
}
|
||||
if (frame_input_flag) {
|
||||
(*ptr_to_pGlPushAttrib)(0xffffffff);
|
||||
begin_frame(frame_render_target, now);
|
||||
draw_framebuffer_texture_over_screen(frame_render_target);
|
||||
end_frame(frame_render_target);
|
||||
(*ptr_to_pGlPopAttrib)();
|
||||
} else {
|
||||
frame_repass_needed = 1;
|
||||
}
|
||||
}
|
||||
if (has_queued_modules(java_natives_world_module_manager)) {
|
||||
spin_lock();
|
||||
apply_module_toggles(&ctx);
|
||||
frame_spinlock = 0;
|
||||
}
|
||||
tick_modules(&ctx, timer, now);
|
||||
tick_netty(&jvm);
|
||||
if (!is_unload_requested_flag()) return;
|
||||
gui_state_block[0x4cb] = 0; // consume the click immediately: one sequence per click, no repass if
|
||||
// the user clicks again while this one is still running
|
||||
// Take the Java hooks out before touching any state. If removal fails the game will keep calling
|
||||
// these natives; the old sequence still freed the module manager from the unload thread and the
|
||||
// next frame crashed in save_settings -> serialize_settings on the freed module list. In that case keep the client
|
||||
// resident (everything stays allocated) and cancel the unload request instead. The native entries
|
||||
// check java_natives_frame_client_shutting_down_natives_check, so a partial removal followed by the real teardown can no longer fault
|
||||
// either: everything the game still reaches goes inert the moment this branch commits.
|
||||
Pair16 p = init_slinkychannelhandler_class_constructor;
|
||||
int channel_off = p.a ? unregister_channel_class(&jvm, &p) : 0;
|
||||
slinky_logf("unload: channel-off=%d", channel_off);
|
||||
// 0x3fd means there is no live network pipeline; there is nothing to remove in that case.
|
||||
if (channel_off && channel_off != 0x3fd) return;
|
||||
int hoff = set_java_hooks(env, 0);
|
||||
slinky_logf("unload: hooks-off=%d", hoff);
|
||||
if (hoff) return;
|
||||
// unload: save settings, notify and disable every module, tear down
|
||||
int saved = save_settings(java_natives_world_module_manager, init_profile_list);
|
||||
slinky_logf("unload: settings saved=%d", saved);
|
||||
HEvent e3 = {};
|
||||
e3.type = 0x1a;
|
||||
e3.ctx = &ctx;
|
||||
Mgr *mgr = (Mgr *)java_natives_world_module_manager;
|
||||
for (unsigned long long i = 0; i < mgr->count; i++)
|
||||
if (mgr->data[i]->on_shutdown) mgr->data[i]->on_shutdown(&e3);
|
||||
for (unsigned long long i = 0; i < mgr->count; i++) queue_module_toggle(java_natives_world_module_manager, mgr->data[i], 0);
|
||||
spin_lock();
|
||||
java_natives_frame_client_shutting_down_natives_check = 1;
|
||||
frame_spinlock = 0;
|
||||
apply_module_toggles(&ctx);
|
||||
tick_post_frame(&ctx, java_natives_frame_module_manager_target_state);
|
||||
restore_game_window();
|
||||
slinky_logf("unload: starting unload thread");
|
||||
start_unload_thread(init_our_module_handle);
|
||||
}
|
||||
|
||||
// ---- set the key bind of a module: re-register it in the bind map, then tell the modules (event 0x18) ----
|
||||
FN(void, key_map_remove, (void *map, int key, void *mod)); // unregister
|
||||
FN(void, key_map_add, (void *map, int key, void *mod)); // register
|
||||
struct BindEvent { int type; int padding_04; void *mgr; void *mod; char rest[0x30]; int tail; };
|
||||
extern "C" void set_module_bind(void *mod, int key) {
|
||||
int *slot = (int *)((char *)mod + 0x1c);
|
||||
key_map_remove(java_natives_frame_module_key_binds_key_module, *slot, mod);
|
||||
*slot = key;
|
||||
key_map_add(java_natives_frame_module_key_binds_key_module, key, mod);
|
||||
BindEvent ev = {};
|
||||
ev.type = 0x18;
|
||||
ev.mgr = java_natives_world_module_manager;
|
||||
ev.mod = mod;
|
||||
dispatch_event(java_natives_world_module_manager, (HEvent *)&ev);
|
||||
}
|
||||
|
||||
// ---- key handlers of the two key maps (java_natives_frame_profile_key_binds_key_profile: key -> profile name, java_natives_frame_module_key_binds_key_module: key -> module) ----
|
||||
// Both are called from the input handler on_input_event through key_map_dispatch as handler(item, key, down).
|
||||
FN(int, load_profile_name, (const char *name, void *mgr)); // load profile `name`; 0 = ok
|
||||
FN(void *, find_profile, (void *profiles, const char *name)); // profile entry by name (+0x38: key bind)
|
||||
FN(void *, key_map_get, (void *map, int key, void *val)); // item registered under `key`
|
||||
FN(void *, libc_calloc, (unsigned long long n, unsigned long long size));
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
FN(void *, libc_memcpy, (void *dest, const void *src, unsigned long long n));
|
||||
extern "C" void *java_natives_frame_profile_key_binds_key_profile;
|
||||
extern "C" unsigned char frame_spinlock, java_natives_frame_client_shutting_down_natives_check;
|
||||
extern "C" const char str_failed_to_load_profile[]; // "Failed to load profile"
|
||||
struct Ev48 { int type; int padding_04; union { const char *name; struct { int err; int pad2; const char *msg; }; }; char rest[0x38]; };
|
||||
extern "C" const char ptr_to_str_failed_to_set_bind[]; // prepared "Failed to set bind" event (0x48 bytes)
|
||||
// profile bind pressed: load the profile; a failure unbinds it and is reported (event 0xc), success is event 0xb
|
||||
extern "C" unsigned char on_profile_bind(const char *name, int key, bool down) {
|
||||
if (!down) return 0;
|
||||
int err = load_profile_name(name, java_natives_world_module_manager);
|
||||
if (!err) {
|
||||
Ev48 e = {};
|
||||
e.type = 0xb;
|
||||
e.name = name;
|
||||
dispatch_event(java_natives_world_module_manager, (HEvent *)&e);
|
||||
return 1;
|
||||
}
|
||||
char *prof = (char *)find_profile(init_profile_list, name);
|
||||
if (prof) *(int *)(prof + 0x38) = 0;
|
||||
Ev48 e = {};
|
||||
e.type = 0xc;
|
||||
e.err = err;
|
||||
e.msg = str_failed_to_load_profile;
|
||||
dispatch_event(java_natives_world_module_manager, (HEvent *)&e);
|
||||
key_map_remove(java_natives_frame_profile_key_binds_key_profile, key, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// module bind pressed (hold mode: enabled while the key is down; otherwise toggles on press)
|
||||
extern "C" unsigned char on_module_bind(char *mod, int key, bool down) {
|
||||
if (mod[0x20] == 1) {
|
||||
queue_module_toggle(java_natives_world_module_manager, mod, down);
|
||||
mod[0x21] = down;
|
||||
} else if (down) {
|
||||
queue_module_toggle(java_natives_world_module_manager, mod, mod[0x14] ^ 1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// bind profile `p` to `key` (0 = unbind): a key serves one profile, the previous owner loses its bind
|
||||
extern "C" void bind_profile_to_key(char *p, int key) {
|
||||
void *map = java_natives_frame_profile_key_binds_key_profile;
|
||||
|
||||
key_map_remove(map, *(int *)(p + 0x38), 0);
|
||||
void *other = key_map_get(java_natives_frame_profile_key_binds_key_profile, key, 0);
|
||||
if (other) {
|
||||
char *o = (char *)find_profile(init_profile_list, (const char *)other);
|
||||
if (o) {
|
||||
*(int *)(o + 0x38) = 0;
|
||||
key_map_remove(java_natives_frame_profile_key_binds_key_profile, key, 0);
|
||||
}
|
||||
}
|
||||
if (key) {
|
||||
const char *name = *(const char **)p;
|
||||
unsigned long long n = libc_strlen(name);
|
||||
char *copy = (char *)libc_calloc(n + 1, 1);
|
||||
if (!copy) {
|
||||
Ev48 e;
|
||||
__builtin_memcpy(&e, ptr_to_str_failed_to_set_bind, 0x48);
|
||||
dispatch_event(java_natives_world_module_manager, (HEvent *)&e);
|
||||
return;
|
||||
}
|
||||
libc_memcpy(copy, name, n);
|
||||
key_map_add(java_natives_frame_profile_key_binds_key_profile, key, copy);
|
||||
}
|
||||
*(int *)(p + 0x38) = key;
|
||||
}
|
||||
|
||||
// ---- SlinkyChannelHandler.onPacket(Object packet, boolean outbound, long time, int id): int ----
|
||||
// Netty thread -> client: serialised with the frame code through the lock at frame_spinlock, ignored once the
|
||||
// client is shutting down (java_natives_frame_client_shutting_down_natives_check). The modules see it as event 2 (inbound) / 3 (outbound) and may change
|
||||
// `result` (checked at +0x24 by the handler).
|
||||
struct PktEv { int type; int padding_04; Jvm *jvm; void *pkt; long long time; int id; int result; char rest[0x20]; int tail; };
|
||||
extern "C" int on_packet(JNIEnv *env, jobject, void *pkt, unsigned char outbound, long long time, int id) {
|
||||
bool busy;
|
||||
for (;;) {
|
||||
busy = __atomic_exchange_n(&frame_spinlock, 1, __ATOMIC_SEQ_CST) != 0;
|
||||
|
||||
if (!busy) break;
|
||||
__builtin_ia32_pause();
|
||||
}
|
||||
int result = 0;
|
||||
if (!java_natives_frame_client_shutting_down_natives_check) {
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
PktEv e = {};
|
||||
e.type = outbound ? 3 : 2;
|
||||
e.jvm = &jvm;
|
||||
e.pkt = pkt;
|
||||
e.time = time;
|
||||
e.id = id;
|
||||
dispatch_event(java_natives_world_module_manager, (HEvent *)&e);
|
||||
result = e.result;
|
||||
}
|
||||
frame_spinlock = 0;
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
// 0x8000: Java native entry points (SlinkyHooks13): wrap the JNIEnv in a Jvm handle and forward
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jids.h"
|
||||
extern "C" JIds *java_natives_world_cached_jni_ids;
|
||||
extern "C" unsigned char java_natives_frame_client_shutting_down_natives_check; // set by the unload sequence: every entry below is inert once it is 1
|
||||
extern "C" void slinky_logf(const char *fmt, ...);
|
||||
struct Rot { float yaw, pitch; };
|
||||
FN(unsigned char, is_silent_rotation_active, ());
|
||||
FN(Rot, get_silent_rotation, ());
|
||||
FN(void *, should_render_player_name, (Jvm *jvm, void *render_player, void *other_object));
|
||||
FN(void, java_set_render_manager_player_view_x, (Jvm *jvm, void *render_manager, float value));
|
||||
FN(void, java_set_render_manager_player_view_y, (Jvm *jvm, void *render_manager, float value));
|
||||
FN(unsigned char, handle_chat_packet, (Jvm *jvm, void *object, void *other_object));
|
||||
FN(void, send_packet_with_id, (Jvm *jvm, void *object, void *other_object, void *c));
|
||||
FN(void, send_packet, (Jvm *jvm, void *object, void *other_object));
|
||||
// renderPlayer is a static native with the class-file descriptor (Object,Object,DDDFF)V: the JVM passes
|
||||
// the three doubles and two floats in registers. The module receives them unchanged and forwards them
|
||||
// to RendererLivingEntity.doRender.
|
||||
FN(void, chams_render_living_entity_entry, (Jvm *jvm, void *entity, void *other_object, double x, double d0, double d1, float f0, float f1));
|
||||
extern "C" void *hook_can_render_name(JNIEnv *env, jclass, void *receiver, void *b) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return 0;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
return should_render_player_name(&jvm, receiver, b);
|
||||
}
|
||||
extern "C" void hook_render_player(JNIEnv *env, jclass, void *receiver, void *b, double x, double d0, double d1, float f0, float f1) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
static int logged;
|
||||
if (!logged) {
|
||||
logged = 1;
|
||||
slinky_logf("frame: first render_player");
|
||||
}
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
chams_render_living_entity_entry(&jvm, receiver, b, x, d0, d1, f0, f1);
|
||||
}
|
||||
// silent-rotation aware pitch / yaw setters
|
||||
extern "C" void hook_set_render_x(JNIEnv *env, jclass, void *receiver, float pitch) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
if (is_silent_rotation_active()) pitch = get_silent_rotation().pitch;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
java_set_render_manager_player_view_x(&jvm, receiver, pitch);
|
||||
}
|
||||
extern "C" void hook_set_render_y(JNIEnv *env, jclass, void *receiver, float yaw) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
if (is_silent_rotation_active()) yaw = get_silent_rotation().yaw;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
java_set_render_manager_player_view_y(&jvm, receiver, yaw);
|
||||
}
|
||||
extern "C" void hook_send_packet_1_7(JNIEnv *env, jclass, void *receiver, void *b, void *c) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
if (!handle_chat_packet(&jvm, receiver, b)) send_packet_with_id(&jvm, receiver, b, c);
|
||||
}
|
||||
extern "C" void hook_send_packet_1_8(JNIEnv *env, jclass, void *receiver, void *b) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
if (!handle_chat_packet(&jvm, receiver, b)) send_packet(&jvm, receiver, b);
|
||||
}
|
||||
|
||||
FN(void *, get_minecraft, (Jvm *jvm));
|
||||
FN(void *, get_world, (Jvm *jvm, void *minecraft));
|
||||
FN(void, set_block_meta, (Jvm *jvm, void *block, int a, int b));
|
||||
struct Event { long long type; Jvm *jvm; void *world; int z; int a; int b; int zz[9]; };
|
||||
extern "C" char java_natives_world_module_manager[];
|
||||
FN(void, dispatch_event, (void *mgr, Event *e));
|
||||
extern "C" void hook_unload_chunk(JNIEnv *env, jclass, void *receiver, int a, int b) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
void *world = get_world(&jvm, get_minecraft(&jvm));
|
||||
set_block_meta(&jvm, receiver, a, b);
|
||||
Event e = {0x14, &jvm, world, 0, a, b, {}};
|
||||
dispatch_event(java_natives_world_module_manager, &e);
|
||||
}
|
||||
|
||||
// ---- world-change hooks: report the change to the modules (event 0x14), then call the game method ----
|
||||
FN(int, get_block_id, (Jvm *jvm, void *blockOrState));
|
||||
FN(void *, set_block_state, (Jvm *jvm, void *block, int a, int b, int c, void *blk, int d, int e));
|
||||
struct Pos3 { int x, y, z; };
|
||||
FN(Pos3, get_block_position, (Jvm *jvm, void *pos));
|
||||
FN(void *, get_block_from_pos, (Jvm *jvm, void *blk));
|
||||
FN(void *, set_block_at_pos, (Jvm *jvm, void *block, void *pos, void *blk, int f));
|
||||
FN(void, update_chunk, (Jvm *jvm, void *chunk, int p4, int p5, int p6, int p7, int p8, int p9));
|
||||
FN(int, chunk_coordinate, (int v));
|
||||
struct Ev14 { long long type; Jvm *jvm; void *o; int kind; int v[4]; int z[7]; };
|
||||
extern "C" void *hook_set_block_1_7(JNIEnv *env, jclass, void *receiver, int a, int b, int c, void *blk, int d, int e) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return 0;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
int block_id = get_block_id(&jvm, blk);
|
||||
Ev14 ev = {0x14, &jvm, receiver, 2, {a, b, c, block_id}, {}};
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&ev);
|
||||
return set_block_state(&jvm, receiver, a, b, c, blk, d, e);
|
||||
}
|
||||
extern "C" void *hook_set_block_1_8(JNIEnv *env, jclass, void *receiver, void *pos, void *blk, int f) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return 0;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
Pos3 block_position = get_block_position(&jvm, pos);
|
||||
int block_id = get_block_id(&jvm, get_block_from_pos(&jvm, blk));
|
||||
Ev14 ev = {0x14, &jvm, receiver, 2, {block_position.x, block_position.y, block_position.z, block_id}, {}};
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&ev);
|
||||
return set_block_at_pos(&jvm, receiver, pos, blk, f);
|
||||
}
|
||||
extern "C" void hook_invalidate_chunk(JNIEnv *env, jclass, void *receiver, int p4, int p5, int p6, int p7, int p8, int p9) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
update_chunk(&jvm, receiver, p4, p5, p6, p7, p8, p9);
|
||||
int cx = chunk_coordinate(p4);
|
||||
int cz = chunk_coordinate(p6);
|
||||
Ev14 ev = {0x14, &jvm, receiver, 1, {cx, cz, 0, 0}, {}};
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&ev);
|
||||
}
|
||||
extern "C" char *ptr_to_safe_walk_module_descriptor;
|
||||
FN(void *, get_local_player, (Jvm *jvm, void *minecraft));
|
||||
FN(unsigned char, get_render_flag, ());
|
||||
FN(unsigned char, is_sneaking, (Jvm *jvm, void *object));
|
||||
// isSneaking hook: report true for the local player while the "sneak" module asks for it
|
||||
extern "C" unsigned char hook_should_clip_ledge(JNIEnv *env, jclass, void *receiver) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return 0;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
if (ptr_to_safe_walk_module_descriptor[0x14] == 1) {
|
||||
void *player = get_local_player(&jvm, get_minecraft(&jvm));
|
||||
if (player && (*jvm.env)->IsSameObject(jvm.env, (jobject)player, (jobject)receiver) && get_render_flag()) return 1;
|
||||
}
|
||||
return is_sneaking(&jvm, receiver);
|
||||
}
|
||||
|
||||
// ---- player update / velocity hooks ----
|
||||
extern "C" char java_natives_frame_module_manager_target_state[];
|
||||
extern "C" void *init_profile_list, *java_natives_world_key_state_table_one_byte;
|
||||
extern "C" char *ptr_to_gui_module_descriptor;
|
||||
struct HCtx { Jvm *jvm; void *module_manager; void *profile_list; void *minecraft; void *world; void *player; void *key_state_table; void *target;
|
||||
unsigned char in_game; unsigned char padding_41[3]; int padding_44; };
|
||||
struct HEvent { int type; int padding_04; HCtx *ctx; char rest[0x38]; };
|
||||
struct RotEvent { int type; int padding_04; unsigned char a, b, d, e; Rot rot; char rest[0x34]; }; // event 0xd
|
||||
FN(unsigned char, is_in_game, (Jvm *jvm, void *minecraft));
|
||||
FN(void, request_rotation_reset_if_flagged, (HCtx *ctx));
|
||||
FN(void, update_entities, (Jvm *jvm, void *object));
|
||||
FN(Rot, get_entity_rotation, (Jvm *jvm, void *entity));
|
||||
FN(unsigned char, is_event_handled, (void *mgr, int type));
|
||||
FN(void, apply_player_rotation, (HCtx *ctx, unsigned char a, unsigned char b, unsigned char d, Rot rotation));
|
||||
FN(void, set_rotation_active, (Jvm *jvm, void *player, bool on));
|
||||
extern "C" void hook_update_entities(JNIEnv *env, jclass, void *receiver) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
void *minecraft = get_minecraft(&jvm);
|
||||
void *player = get_local_player(&jvm, minecraft);
|
||||
if (!player) {
|
||||
// updateEntities also runs in the menu / after disconnect. Keep the game's world tick intact;
|
||||
// only the module/rotation part below needs a player.
|
||||
if (receiver) update_entities(&jvm, receiver);
|
||||
return;
|
||||
}
|
||||
HCtx ctx;
|
||||
ctx.jvm = &jvm; ctx.module_manager = java_natives_world_module_manager; ctx.profile_list = init_profile_list; ctx.minecraft = minecraft; ctx.world = receiver; ctx.player = player;
|
||||
ctx.key_state_table = java_natives_world_key_state_table_one_byte; ctx.target = java_natives_frame_module_manager_target_state;
|
||||
ctx.in_game = ptr_to_gui_module_descriptor[0x14] ? 0 : is_in_game(&jvm, minecraft);
|
||||
*(int *)&ctx.padding_41[0] = 0;
|
||||
ctx.padding_44 = 0;
|
||||
static int entities_logged;
|
||||
if (!entities_logged) {
|
||||
entities_logged = 1;
|
||||
slinky_logf("frame: first update_entities in_game=%d gui_open=%d", (int)ctx.in_game, (int)ptr_to_gui_module_descriptor[0x14]);
|
||||
}
|
||||
HEvent e = {};
|
||||
e.type = 0x10;
|
||||
e.ctx = &ctx;
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&e);
|
||||
request_rotation_reset_if_flagged(&ctx);
|
||||
e.type = 0x11;
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&e);
|
||||
update_entities(&jvm, ctx.world);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
(*env)->ExceptionClear(env);
|
||||
return; // do not call more JNI with an exception pending
|
||||
}
|
||||
e.type = 0x12;
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&e);
|
||||
Rot rotation = is_silent_rotation_active() ? get_silent_rotation() : get_entity_rotation(&jvm, player);
|
||||
unsigned char a, b, d;
|
||||
if (is_event_handled(java_natives_world_module_manager, 0xd)) {
|
||||
a = 0; b = 0; d = 0;
|
||||
} else {
|
||||
RotEvent m;
|
||||
__builtin_memset(&m, 0, sizeof m);
|
||||
|
||||
m.type = 0xd;
|
||||
m.rot = rotation;
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&m);
|
||||
a = m.a; b = m.b; d = m.d;
|
||||
rotation = m.rot;
|
||||
}
|
||||
apply_player_rotation(&ctx, a, b, d, rotation);
|
||||
set_rotation_active(&jvm, player, true);
|
||||
}
|
||||
|
||||
FN(void, set_motion, (Jvm *jvm, void *object, float x, float y, float z));
|
||||
FN(void *, get_game_settings, (Jvm *jvm, void *minecraft));
|
||||
FN(void *, get_held_item, (Jvm *jvm, void *item));
|
||||
FN(int, get_hurt_time, (Jvm *jvm, void *player));
|
||||
FN(unsigned char, is_key_down, (Jvm *jvm, void *object));
|
||||
struct VelEvent { int type; int padding_04; HCtx *ctx; float x, y, z; unsigned char flag; char rest[0x2b]; };
|
||||
extern "C" void hook_move_flying(JNIEnv *env, jclass, void *receiver, float x, float y, float z) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
if (!is_event_handled(java_natives_world_module_manager, 0x15) || !is_event_handled(java_natives_world_module_manager, 0x16)) {
|
||||
void *minecraft = get_minecraft(&jvm);
|
||||
void *player = get_local_player(&jvm, minecraft);
|
||||
if ((*jvm.env)->IsSameObject(jvm.env, (jobject)receiver, (jobject)player)) {
|
||||
void *world = get_world(&jvm, minecraft);
|
||||
HCtx ctx;
|
||||
ctx.jvm = &jvm; ctx.module_manager = java_natives_world_module_manager; ctx.profile_list = init_profile_list; ctx.minecraft = minecraft; ctx.world = world; ctx.player = player;
|
||||
ctx.key_state_table = java_natives_world_key_state_table_one_byte; ctx.target = java_natives_frame_module_manager_target_state;
|
||||
ctx.in_game = ptr_to_gui_module_descriptor[0x14] ? 0 : is_in_game(&jvm, minecraft);
|
||||
*(int *)&ctx.padding_41[0] = 0;
|
||||
ctx.padding_44 = 0;
|
||||
void *held_item = get_held_item(&jvm, get_game_settings(&jvm, minecraft));
|
||||
unsigned char flag = get_hurt_time(&jvm, player) > 0 ? is_key_down(&jvm, held_item) : 0;
|
||||
VelEvent e = {0x15, 0, &ctx, x, y, z, flag, {}};
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&e);
|
||||
set_motion(&jvm, player, e.x, e.y, e.z);
|
||||
e.type = 0x16;
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
set_motion(&jvm, receiver, x, y, z);
|
||||
}
|
||||
|
||||
// GUI tick: let modules ask for key pass-through, then forward movement keys into the open screen
|
||||
FN(void, gui_key_passthrough, (Jvm *jvm, void *object));
|
||||
FN(void *, get_current_screen, (Jvm *jvm, void *minecraft));
|
||||
FN(void *, get_game_screen, (Jvm *jvm, void *object));
|
||||
FN(int, get_event_key, (Jvm *jvm, void *object));
|
||||
FN(unsigned char, keyboard_has_events, (Jvm *jvm));
|
||||
FN(unsigned char, keyboard_has_next, (Jvm *jvm));
|
||||
FN(unsigned char, keyboard_can_repeat, (Jvm *jvm));
|
||||
FN(int, keyboard_next_key, (Jvm *jvm));
|
||||
FN(void, keyboard_send_key, (Jvm *jvm, void *scr, int x, int y));
|
||||
struct FlagEvent { int type; int padding_04; HCtx *ctx; unsigned char flag; char rest[0x37]; };
|
||||
extern "C" void hook_update_controller(JNIEnv *env, jclass, void *receiver) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
gui_key_passthrough(&jvm, receiver);
|
||||
void *minecraft = get_minecraft(&jvm);
|
||||
void *player = get_local_player(&jvm, minecraft);
|
||||
void *world = get_world(&jvm, minecraft);
|
||||
HCtx ctx;
|
||||
ctx.jvm = &jvm; ctx.module_manager = java_natives_world_module_manager; ctx.profile_list = init_profile_list; ctx.minecraft = minecraft; ctx.world = world; ctx.player = player;
|
||||
ctx.key_state_table = java_natives_world_key_state_table_one_byte; ctx.target = java_natives_frame_module_manager_target_state;
|
||||
ctx.in_game = ptr_to_gui_module_descriptor[0x14] ? 0 : is_in_game(&jvm, minecraft);
|
||||
*(int *)&ctx.padding_41[0] = 0;
|
||||
ctx.padding_44 = 0;
|
||||
FlagEvent e = {};
|
||||
e.type = 0x19;
|
||||
e.ctx = &ctx;
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&e);
|
||||
if (!(e.flag & 1)) return;
|
||||
void *scr = get_current_screen(&jvm, minecraft);
|
||||
int key = get_event_key(&jvm, get_game_screen(&jvm, get_game_settings(&jvm, minecraft)));
|
||||
while (keyboard_has_events(&jvm)) {
|
||||
}
|
||||
while (keyboard_has_next(&jvm)) {
|
||||
if (!scr || !keyboard_can_repeat(&jvm)) continue;
|
||||
int k = keyboard_next_key(&jvm);
|
||||
if (k == 1 || k == key) keyboard_send_key(&jvm, scr, 0, k);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- glClear(GL_DEPTH_BUFFER_BIT) hook: fire the 3D (6) and 2D (7) render events with GL state set up ----
|
||||
struct Vec3 { double x, y, z; };
|
||||
FN(void *, get_render_view_entity, (Jvm *jvm, void *minecraft));
|
||||
FN(void *, get_timer, (Jvm *jvm, void *minecraft));
|
||||
FN(float, get_partial_ticks, (Jvm *jvm, void *timer));
|
||||
FN(Vec3, java_get_entity_pos_x, (Jvm *jvm, void *entity));
|
||||
FN(Vec3, java_get_entity_prev_pos_x, (Jvm *jvm, void *entity));
|
||||
FN(Vec3, vec3_lerp, (Vec3 a, Vec3 b, double t));
|
||||
FN(void, gl_push_state, (Jvm *jvm));
|
||||
FN(void, gl_clear_color, (Jvm *jvm));
|
||||
FN(void, gl_disable_lighting, (Jvm *jvm));
|
||||
FN(void, gl_pop_state, (Jvm *jvm));
|
||||
FN(void, gl_blend_mode, (Jvm *jvm, unsigned src, unsigned dst));
|
||||
FN(void, gl_bind_texture, (Jvm *jvm));
|
||||
FN(void, gl_enable_depth, (Jvm *jvm));
|
||||
FN(void, gl_begin_2d, (Jvm *jvm));
|
||||
FN(void, gl_depth_mask, (Jvm *jvm));
|
||||
FN(void, gl_set_color, (Jvm *jvm, float r, float g, float b, float a));
|
||||
FN(void, set_silent_rotation_active, (Jvm *jvm, void *player, bool on));
|
||||
typedef void (*GlCap)(unsigned);
|
||||
typedef void (*GlBlend)(unsigned, unsigned);
|
||||
typedef void (*GlGetD)(unsigned, double *);
|
||||
typedef void (*GlVoid)();
|
||||
typedef void (*GlOrtho)(double, double, double, double, double, double);
|
||||
typedef void (*GlColor4)(float, float, float, float);
|
||||
extern "C" GlCap *ptr_to_pGlDisable, *ptr_to_pGlEnable, *ptr_to_pGlClear, *ptr_to_pGlMatrixMode;
|
||||
extern "C" GlBlend *ptr_to_pGlBlendFunc;
|
||||
extern "C" GlGetD *ptr_to_pGlGetFloatv;
|
||||
extern "C" GlVoid *ptr_to_pGlLoadIdentity;
|
||||
extern "C" GlOrtho *ptr_to_pGlOrtho;
|
||||
extern "C" GlColor4 *ptr_to_pGlColor4f;
|
||||
extern "C" const float one_b;
|
||||
extern "C" void *frame_render_target;
|
||||
extern "C" int render_target_width, render_target_height;
|
||||
extern "C" double gl_modelview_matrix[16], gl_projection_matrix[16];
|
||||
struct R3D { long long type; Vec3 cam; HCtx *ctx; float pt; unsigned char flag; char rest[0x1b]; };
|
||||
struct R2D { long long type; Vec3 cam; HCtx *ctx; float w, h; double *proj, *mv; float pt; unsigned char flag, padding_5, padding_6, padding_7; };
|
||||
extern "C" void render_world_events(JNIEnv *env, unsigned mask, unsigned char flag) {
|
||||
if (java_natives_frame_client_shutting_down_natives_check) return;
|
||||
if (!frame_render_target) return;
|
||||
static int events_logged;
|
||||
if (!events_logged) {
|
||||
events_logged = 1;
|
||||
slinky_logf("frame: first post_world_clear mask=%x flag=%d target=%p", mask, (int)flag, frame_render_target);
|
||||
}
|
||||
if (mask == 0x100) {
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, env};
|
||||
void *minecraft = get_minecraft(&jvm);
|
||||
void *player = get_local_player(&jvm, minecraft);
|
||||
unsigned char silent = is_silent_rotation_active();
|
||||
if (silent) set_silent_rotation_active(&jvm, player, true);
|
||||
unsigned char no3d = is_event_handled(java_natives_world_module_manager, 6);
|
||||
unsigned char no2d = is_event_handled(java_natives_world_module_manager, 7);
|
||||
if (!(no3d & no2d)) {
|
||||
void *world = get_world(&jvm, minecraft);
|
||||
void *view = get_render_view_entity(&jvm, minecraft);
|
||||
static int worldrender_logged;
|
||||
if (!worldrender_logged) {
|
||||
worldrender_logged = 1;
|
||||
slinky_logf("worldrender: no3d=%d no2d=%d player=%p world=%p view=%p", (int)no3d, (int)no2d, player, world, view);
|
||||
}
|
||||
if (player && world && view) {
|
||||
HCtx ctx;
|
||||
ctx.jvm = &jvm; ctx.module_manager = java_natives_world_module_manager; ctx.profile_list = init_profile_list; ctx.minecraft = minecraft; ctx.world = world; ctx.player = player;
|
||||
ctx.key_state_table = java_natives_world_key_state_table_one_byte; ctx.target = java_natives_frame_module_manager_target_state;
|
||||
ctx.in_game = ptr_to_gui_module_descriptor[0x14] ? 0 : is_in_game(&jvm, minecraft);
|
||||
*(int *)&ctx.padding_41[0] = 0;
|
||||
ctx.padding_44 = 0;
|
||||
float partial_ticks = get_partial_ticks(&jvm, get_timer(&jvm, minecraft));
|
||||
Vec3 cur = java_get_entity_pos_x(&jvm, view);
|
||||
Vec3 prev = java_get_entity_prev_pos_x(&jvm, view);
|
||||
Vec3 cam = vec3_lerp(prev, cur, (double)partial_ticks);
|
||||
if (flag) {
|
||||
gl_push_state(&jvm); gl_clear_color(&jvm); gl_disable_lighting(&jvm); gl_pop_state(&jvm);
|
||||
gl_blend_mode(&jvm, 0x302, 0x303);
|
||||
}
|
||||
GlCap *dis = ptr_to_pGlDisable;
|
||||
(*dis)(0xb71); (*dis)(0xde1); (*dis)(0xb50);
|
||||
GlCap *en = ptr_to_pGlEnable;
|
||||
(*en)(0xbe2);
|
||||
(*ptr_to_pGlBlendFunc)(0x302, 0x303);
|
||||
(*en)(0xb20);
|
||||
if (!no3d) {
|
||||
R3D e = {6, cam, &ctx, partial_ticks, flag, {}};
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&e);
|
||||
}
|
||||
if (!(no2d & 1)) {
|
||||
GlGetD *get = ptr_to_pGlGetFloatv;
|
||||
(*get)(0xba6, gl_modelview_matrix);
|
||||
(*get)(0xba7, gl_projection_matrix);
|
||||
GlCap *mode = ptr_to_pGlMatrixMode;
|
||||
(*mode)(0x1701);
|
||||
GlVoid *push = ptr_to_pGlLoadIdentity;
|
||||
(*push)();
|
||||
(*ptr_to_pGlOrtho)(0.0, (double)render_target_width, (double)render_target_height, 0.0, -3000.0, 3000.0);
|
||||
(*mode)(0x1700);
|
||||
(*push)();
|
||||
R2D e = {7, cam, &ctx, (float)render_target_width, (float)render_target_height, gl_modelview_matrix, gl_projection_matrix, partial_ticks, flag, 0, 0, 0};
|
||||
dispatch_event(java_natives_world_module_manager, (Event *)&e);
|
||||
}
|
||||
if (flag) {
|
||||
gl_bind_texture(&jvm); gl_enable_depth(&jvm); gl_begin_2d(&jvm); gl_depth_mask(&jvm);
|
||||
float one = one_b;
|
||||
gl_set_color(&jvm, one, one, one, 1.0f);
|
||||
}
|
||||
(*en)(0xde1); (*en)(0xb71); (*en)(0xb44); (*en)(0xbc0);
|
||||
float one = one_b;
|
||||
(*ptr_to_pGlColor4f)(one, one, one, one);
|
||||
}
|
||||
}
|
||||
if (silent) set_silent_rotation_active(&jvm, player, false);
|
||||
}
|
||||
(*ptr_to_pGlClear)(mask);
|
||||
}
|
||||
|
||||
// ---- the client's input handler (installed in input_input_handler_hooked_into_game for the game-window hook, see protected/input.cpp) ----
|
||||
// Keeps the key-state table (byte per key: bit0 = down, bit1 = toggled by synthetic events), attaches to the JVM,
|
||||
// lets the key-bind logic handle the event, and passes keys/buttons on to the registered key listeners.
|
||||
// Returns true when the event was consumed.
|
||||
struct InEvt { int type; int code; int padding_08; unsigned char down, synthetic; };
|
||||
struct VmFns { void *fn[8]; };
|
||||
extern "C" VmFns **init_javavm; // JavaVM*
|
||||
extern "C" void *java_natives_world_key_state_table_one_byte, *java_natives_frame_profile_key_binds_key_profile, *java_natives_frame_module_key_binds_key_module; // key states, listener maps (typed / always)
|
||||
extern "C" char java_natives_world_module_manager[]; // module manager
|
||||
extern "C" char *ptr_to_gui_module_descriptor, *ptr_to_friends_module_descriptor; // settings blocks (+0x14: flag)
|
||||
FN(unsigned char, gui_handle_input_event_entry, (InEvt *e, unsigned char unchanged, void *mgr));
|
||||
FN(void *, get_minecraft, (Jvm *j));
|
||||
FN(void, friends_friends_friend_list, (Jvm *jvm, void *mgr));
|
||||
FN(bool, key_map_dispatch, (void *map, int id, int arg));
|
||||
extern "C" bool on_input_event(InEvt *e) {
|
||||
bool unchanged = true;
|
||||
if (e->type == 2) {
|
||||
unsigned char *keys = (unsigned char *)java_natives_world_key_state_table_one_byte;
|
||||
unsigned code = e->code;
|
||||
unsigned char cur = keys[code];
|
||||
unsigned char was = cur & 1;
|
||||
unsigned char down = e->down;
|
||||
if (!(e->synthetic == 1 && was == down)) {
|
||||
if (e->synthetic == 1) down = (down | (cur & 2)) ^ 2;
|
||||
keys[code] = down;
|
||||
unchanged = was == e->down;
|
||||
}
|
||||
}
|
||||
void *env;
|
||||
if (((int (*)(void *, void **, int))(*init_javavm)->fn[6])(init_javavm, &env, 0x10008)) return false;
|
||||
Jvm jvm = {java_natives_world_cached_jni_ids, (JNIEnv *)env};
|
||||
bool r = gui_handle_input_event_entry(e, unchanged, java_natives_world_module_manager);
|
||||
if (r) return r;
|
||||
if (e->type != 2 || unchanged) return r;
|
||||
if (e->code == 0) return r;
|
||||
if (!is_in_game(&jvm, get_minecraft(&jvm))) return r;
|
||||
if (ptr_to_gui_module_descriptor[0x14] != 0) return r;
|
||||
if (ptr_to_friends_module_descriptor[0x14] == 1 && e->type == 2 && e->down == 1 && e->code == 3) friends_friends_friend_list(&jvm, java_natives_world_module_manager);
|
||||
if (key_map_dispatch(java_natives_frame_profile_key_binds_key_profile, e->code, e->down)) return r;
|
||||
key_map_dispatch(java_natives_frame_module_key_binds_key_module, e->code, e->down);
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// 0x6000: per-frame tick: rotation-delta tracking and the timed "tick" event (type 1)
|
||||
#include "slinky/engine.h"
|
||||
struct Rot { float yaw, pitch; };
|
||||
extern "C" double tick_rotation_last_time;
|
||||
extern "C" Rot tick_rotation_previous;
|
||||
extern "C" const double threshold_0_002;
|
||||
extern "C" char java_natives_world_module_manager[];
|
||||
FN(void, update_frame_time, ());
|
||||
FN(Rot, get_entity_rotation, (void *jvm, void *entity));
|
||||
FN(Rot, rot_sub, (Rot a, Rot b));
|
||||
FN(void, add_rotation_delta, (Rot rotation));
|
||||
FN(float, get_partial_ticks, (void *jvm, void *object));
|
||||
FN(void, set_rotation_active, (void *jvm, void *player, bool on));
|
||||
struct TickEvent { long long type; Ctx *ctx; float pt; int z; double dt; char rest[0x28]; };
|
||||
FN(void, dispatch_event, (void *mgr, TickEvent *e));
|
||||
extern "C" void tick_modules(Ctx *ctx, void *object, double now) {
|
||||
if (tick_rotation_last_time == 0.0) tick_rotation_last_time = now;
|
||||
update_frame_time();
|
||||
if (!ctx->world || !ctx->player) {
|
||||
tick_rotation_last_time = now;
|
||||
return;
|
||||
}
|
||||
Rot d = rot_sub(get_entity_rotation(ctx->jvm, ctx->player), tick_rotation_previous);
|
||||
if (__builtin_bit_cast(unsigned long long, d) & 0x7fffffff7fffffffull) add_rotation_delta(d);
|
||||
double dt = now - tick_rotation_last_time;
|
||||
if (threshold_0_002 > dt) return;
|
||||
float partial_ticks = get_partial_ticks(ctx->jvm, object);
|
||||
TickEvent e = {1, ctx, partial_ticks, 0, dt, {}};
|
||||
dispatch_event(java_natives_world_module_manager, &e);
|
||||
set_rotation_active(ctx->jvm, ctx->player, false);
|
||||
tick_rotation_last_time = now;
|
||||
tick_rotation_previous = get_entity_rotation(ctx->jvm, ctx->player);
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
// 0xf000: chat command check, GUI font/style setup
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jids.h"
|
||||
FN(jstring, java_get_c14_packet_tab_complete, (Jvm *jvm, void *c14_packet_tab_complete));
|
||||
// does the text of obj start with '!'?
|
||||
extern "C" bool text_starts_with_bang(Jvm **c, void *obj) {
|
||||
JNIEnv *env = (*c)->env;
|
||||
jstring c14_packet_tab_complete = java_get_c14_packet_tab_complete(*c, obj);
|
||||
bool r;
|
||||
if ((*env)->GetStringLength(env, c14_packet_tab_complete)) {
|
||||
const jchar *p = (const jchar *)(*env)->GetStringCritical(env, c14_packet_tab_complete, 0);
|
||||
r = *p == '!';
|
||||
(*env)->ReleaseStringCritical(env, c14_packet_tab_complete, p);
|
||||
} else {
|
||||
r = false;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// ---- chat commands: "!word word arg arg" walks the command tree, parses up to 3 typed arguments and runs the
|
||||
// node's handler; prints help (sub-commands) or usage on errors. "!!text" sends "!text" as a normal message. ----
|
||||
struct CBuf { char *p; unsigned long long cap, len; };
|
||||
union CVal { struct { const char *p; unsigned long long n; } s; unsigned long long i; unsigned char b; };
|
||||
struct CArg { const char *name; int type; unsigned char opt; char padding_0d[3]; CVal dflt; }; // type 1 str, 2 uint, 3 bool
|
||||
struct CNode { const char *name, *desc; CArg args[3]; void (*run)(Jvm **c, CVal *v); CNode *sub; };
|
||||
extern "C" CNode chat_fonts_root; // root
|
||||
FN(void *, java_get_c01_packet_chat_message, (Jvm *jvm, void *msg));
|
||||
FN(void, java_set_c01_packet_chat_message, (Jvm *jvm, void *msg, jstring s));
|
||||
FN(void, buffer_init, (CBuf *b, char *p, unsigned long long cap));
|
||||
FN(bool, buffer_printf, (CBuf *b, const char *fmt, ...));
|
||||
FN(bool, buffer_push_char, (CBuf *b, char character));
|
||||
FN(bool, buffer_terminate, (CBuf *b));
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
FN(void *, libc_memcpy_alt, (void *dest, const void *src, unsigned long long n));
|
||||
FN(int, libc_strncmp_alt, (const void *left, const void *right, unsigned long long n));
|
||||
extern "C" void jni_throw_format(Jvm *jvm, const char *fmt, ...);
|
||||
extern "C" const char str_s_in_chat_fonts[], str_sect_7_s[], str_sect_f_s_s_sect_7_s[], str_sect_f_message_sect_7_send_sec[], str_sect_cmissing_argument_sect_o[], str_sect_cargument_sect_o_ssect_c[],
|
||||
str_sect_cargument_sect_o_ssect_c_in_chat_fonts[], str_sect_ctoo_many_arguments_provi[], str_sect_c_s[], str_csect_o_ssect_c_s_c[];
|
||||
extern "C" const char *const ptr_to_str_string[]; // type names
|
||||
extern "C" bool handle_chat_message(Jvm **c, void *msg) {
|
||||
JNIEnv *env = (*c)->env;
|
||||
jstring c01_packet_chat_message = (jstring)java_get_c01_packet_chat_message(*c, msg);
|
||||
if (!c01_packet_chat_message) return false;
|
||||
const char *utf = (*env)->GetStringUTFChars(env, c01_packet_chat_message, 0);
|
||||
if (!utf) return false;
|
||||
char buf[0x1a0];
|
||||
unsigned long long ulen = libc_strlen(utf);
|
||||
if (ulen >= sizeof buf) ulen = sizeof buf - 1;
|
||||
libc_memcpy_alt(buf, utf, ulen);
|
||||
buf[ulen] = 0;
|
||||
(*env)->ReleaseStringUTFChars(env, c01_packet_chat_message, utf);
|
||||
if (!buf[0] || buf[0] != '!') return false;
|
||||
char *p = buf + 1;
|
||||
if (buf[1] == '!') {
|
||||
java_set_c01_packet_chat_message(*c, msg, (*env)->NewStringUTF(env, p));
|
||||
return false;
|
||||
}
|
||||
char pb[0x100], ubuf[0x100];
|
||||
CBuf path;
|
||||
buffer_init(&path, pb, 0x100);
|
||||
buffer_push_char(&path, '!');
|
||||
unsigned long long L = libc_strlen(p), pos = 0;
|
||||
CNode *node = &chat_fonts_root;
|
||||
Jvm *jvm;
|
||||
const char *err;
|
||||
CVal v[3];
|
||||
unsigned long long i;
|
||||
// command words
|
||||
while (node->sub) {
|
||||
if (pos == L) goto help;
|
||||
unsigned long long we = pos;
|
||||
while (we < L && p[we] != ' ') we++;
|
||||
unsigned long long nx = we;
|
||||
while (nx < L && p[nx] == ' ') nx++;
|
||||
unsigned long long wl = we - pos;
|
||||
CNode *sub = node->sub;
|
||||
for (; sub->name; sub++)
|
||||
if (libc_strlen(sub->name) == wl && libc_strncmp_alt(sub->name, p + pos, wl) == 0) break;
|
||||
if (!sub->name) goto help;
|
||||
buffer_printf(&path, str_s_in_chat_fonts /* "%s " */, sub->name);
|
||||
pos = nx;
|
||||
node = sub;
|
||||
}
|
||||
// arguments
|
||||
jvm = *c;
|
||||
for (i = 0; i != 3; i++) {
|
||||
CArg *a = &node->args[i];
|
||||
if (!a->name) break;
|
||||
if (pos == L) {
|
||||
if (!(a->opt & 1)) {
|
||||
err = str_sect_cmissing_argument_sect_o; // missing argument
|
||||
goto usage;
|
||||
}
|
||||
if (a->type == 3) v[i].b = a->dflt.b & 1;
|
||||
else if (a->type == 2) v[i].i = a->dflt.i;
|
||||
else if (a->type == 1) v[i] = a->dflt;
|
||||
continue;
|
||||
}
|
||||
unsigned long long we = pos;
|
||||
while (we < L && p[we] != ' ') we++;
|
||||
unsigned long long nx = we;
|
||||
while (nx < L && p[nx] == ' ') nx++;
|
||||
const char *w = p + pos;
|
||||
unsigned long long wl = we - pos;
|
||||
if (a->type == 3) {
|
||||
unsigned d = (unsigned char)w[0] - 0x46u;
|
||||
if (d > 0x33) goto badbool;
|
||||
if ((0x10100000101ull >> d) & 1) v[i].b = 0; // F N f n
|
||||
else if ((0x8400000084000ull >> d) & 1) v[i].b = 1; // T Y t y
|
||||
else goto badbool;
|
||||
} else if (a->type == 2) {
|
||||
unsigned long long x = 0;
|
||||
for (unsigned long long k = 0; k < wl; k++) {
|
||||
unsigned char ch = (unsigned char)w[k];
|
||||
if ((unsigned char)(ch - ':') < 0xf6) goto badint;
|
||||
unsigned long long y = x * 10 + (ch & 0xf);
|
||||
if (y < x) goto badint;
|
||||
x = y;
|
||||
}
|
||||
v[i].i = x;
|
||||
} else if (a->type == 1) {
|
||||
v[i].s.p = w;
|
||||
v[i].s.n = wl;
|
||||
}
|
||||
pos = nx;
|
||||
}
|
||||
if (pos < L) {
|
||||
jni_throw_format(jvm, str_sect_ctoo_many_arguments_provi); // too many arguments
|
||||
goto usage_line;
|
||||
}
|
||||
node->run(c, v);
|
||||
return true;
|
||||
badbool:
|
||||
err = str_sect_cargument_sect_o_ssect_c_in_chat_fonts;
|
||||
goto usage;
|
||||
badint:
|
||||
err = str_sect_cargument_sect_o_ssect_c;
|
||||
usage:
|
||||
jni_throw_format(jvm, err);
|
||||
usage_line : {
|
||||
buffer_terminate(&path);
|
||||
CBuf u;
|
||||
buffer_init(&u, ubuf, 0x100);
|
||||
buffer_printf(&u, str_sect_c_s, path.p);
|
||||
for (int k = 0; k < 3; k++) {
|
||||
CArg *a = &node->args[k];
|
||||
if (!a->name) continue;
|
||||
bool o = a->opt & 1;
|
||||
buffer_printf(&u, str_csect_o_ssect_c_s_c, o ? '[' : '<', a->name, ptr_to_str_string[(unsigned)a->type], o ? ']' : '>');
|
||||
}
|
||||
buffer_terminate(&u);
|
||||
jni_throw_format(jvm, ubuf);
|
||||
return true;
|
||||
}
|
||||
help : {
|
||||
buffer_terminate(&path);
|
||||
jvm = *c;
|
||||
jni_throw_format(jvm, str_sect_7_s, node->desc);
|
||||
for (CNode *ch = node->sub; ch->name; ch++) jni_throw_format(jvm, str_sect_f_s_s_sect_7_s, path.p, ch->name, ch->desc);
|
||||
if (node == &chat_fonts_root) jni_throw_format(jvm, str_sect_f_message_sect_7_send_sec);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- GLFW key code -> client key code (0 = unmapped). Counterpart of windows_key_to_client_key (Windows virtual keys). ----
|
||||
extern "C" int glfw_key_to_client_key(int key) {
|
||||
switch (key) {
|
||||
case 0x20: return 0x58; // space
|
||||
case 0x27: return 0x3e; // '
|
||||
case 0x2c: return 0x36; // ,
|
||||
case 0x2d: case 0x14d: return 0x37; // - (also keypad -)
|
||||
case 0x2e: case 0x14a: return 0x38; // . (also keypad .)
|
||||
case 0x2f: case 0x14b: return 0x39; // / (also keypad /)
|
||||
case 0x30 ... 0x39: return key - 0x2a; // 0-9
|
||||
case 0x3b: return 0x34; // ;
|
||||
case 0x3d: case 0x14e: case 0x150: return 0x35; // = (also keypad)
|
||||
case 0x41 ... 0x5a: return key - 0x27; // A-Z
|
||||
case 0x5b: return 0x3b;
|
||||
case 0x5c: return 0x3c;
|
||||
case 0x5d: return 0x3d;
|
||||
case 0x60: return 0x3a;
|
||||
case 0x100: return 0x57; // escape
|
||||
case 0x101: case 0x14f: return 0x55; // enter (also keypad)
|
||||
case 0x102: return 0x54; // tab
|
||||
case 0x103: return 0x53; // backspace
|
||||
case 0x104: return 0x5e; // insert
|
||||
case 0x105: return 0x5f; // delete
|
||||
case 0x106: return 0x41; // right
|
||||
case 0x107: return 0x3f; // left
|
||||
case 0x108: return 0x42; // down
|
||||
case 0x109: return 0x40; // up
|
||||
case 0x10a: return 0x59; // page up
|
||||
case 0x10b: return 0x5a; // page down
|
||||
case 0x10c: return 0x5c; // home
|
||||
case 0x10d: return 0x5b; // end
|
||||
case 0x118: return 0x56; // caps lock
|
||||
case 0x119: return 0x61; // scroll lock
|
||||
case 0x11a: return 0x60; // num lock
|
||||
case 0x11b: return 0x5d; // print screen
|
||||
case 0x122 ... 0x131: return key - 0xdf; // F1-F16
|
||||
case 0x140 ... 0x149: return key - 0x130; // keypad 0-9
|
||||
case 0x154: return 0x62; // left shift
|
||||
case 0x155: return 0x64; // left control
|
||||
case 0x156: return 0x66; // left alt
|
||||
case 0x158: return 0x63; // right shift
|
||||
case 0x159: return 0x65; // right control
|
||||
case 0x15a: return 0x67; // right alt
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// copy the two built-in chat commands (descriptors in .rdata) into the root command's sub-command array
|
||||
extern "C" const CNode *ptr_to_rdata_1531c0, *ptr_to_rdata_151518;
|
||||
FN(void *, libc_calloc, (unsigned long long n, unsigned long long size));
|
||||
extern "C" int init_chat_commands() {
|
||||
CNode *cmds = (CNode *)libc_calloc(3, sizeof(CNode)); // two commands + a zero terminator
|
||||
if (!cmds) return 0x3ed;
|
||||
cmds[0] = *ptr_to_rdata_1531c0;
|
||||
cmds[1] = *ptr_to_rdata_151518;
|
||||
chat_fonts_root.sub = cmds;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// next token of a text reader: returns a pointer to it and its length in *len, then skips the delimiters after it
|
||||
struct TextReader { const char *data; unsigned long long end, pos; };
|
||||
extern "C" const char *text_reader_next(TextReader *r, char delim, unsigned long long *len) {
|
||||
if (r->pos == r->end) return 0;
|
||||
unsigned long long start = r->pos, p = start;
|
||||
const char *data = r->data;
|
||||
while (p < r->end && data[p] != delim) r->pos = ++p;
|
||||
*len = p - start;
|
||||
while (r->pos < r->end && data[r->pos] == delim) r->pos++;
|
||||
return data + start;
|
||||
}
|
||||
|
||||
// load a font file into slot `slot` of the font table (0x1772 on failure)
|
||||
FN(int, nvg_create_font_mem, (void *nvg, const char *path, const char *name, int size, int flags));
|
||||
extern "C" int find_glfw_entrypoint(void **ctx, unsigned slot, const char *path, const char *name, int size) {
|
||||
int id = nvg_create_font_mem(ctx[0], path, name, size, 0);
|
||||
if (id == -1) return 0x1772;
|
||||
((int *)ctx)[0x10 + slot] = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- small helpers the first gap scan missed ----
|
||||
extern "C" void *chat_font_context;
|
||||
FN(void, libc_free, (void *ptr));
|
||||
FN(unsigned long long, libc_strlen, (const char *text));
|
||||
FN(void *, libc_memcpy_alt, (void *dest, const void *src, unsigned long long n));
|
||||
extern "C" void destroy_chat_commands() {
|
||||
libc_free(chat_fonts_root.sub);
|
||||
chat_fonts_root.sub = 0;
|
||||
}
|
||||
|
||||
// bounded memory cursor {data, size, pos}: the sink of the output helpers (fwrite-like mem_cursor_write)
|
||||
struct MemCursor { char *data; unsigned long long size, pos; };
|
||||
extern "C" MemCursor *mem_cursor_init(MemCursor *c, char *data, unsigned long long size) {
|
||||
c->data = data;
|
||||
c->size = size;
|
||||
c->pos = 0;
|
||||
return c;
|
||||
}
|
||||
extern "C" MemCursor *text_reader_init(MemCursor *c, char *text) {
|
||||
unsigned long long n = libc_strlen(text);
|
||||
c->data = text;
|
||||
c->size = n;
|
||||
c->pos = 0;
|
||||
return c;
|
||||
}
|
||||
extern "C" bool text_reader_has_more(const MemCursor *c) { return c->pos < c->size; }
|
||||
// fwrite onto the cursor: append `count` records of `size` bytes if they all fit, returns the bytes written
|
||||
extern "C" unsigned long long mem_cursor_write(const void *src, unsigned long long size, unsigned long long count, MemCursor *c) {
|
||||
unsigned long long n = size * count;
|
||||
if (c->pos + n > c->size) return 0;
|
||||
libc_memcpy_alt(c->data + c->pos, src, n);
|
||||
c->pos += n;
|
||||
return n;
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
// 0x1f000: class-file constant pool getters (single exit, like the original)
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/classfile.h"
|
||||
// CONSTANT_Utf8
|
||||
extern "C" __attribute__((noinline)) int constant_utf8(CP *constant_pool, unsigned short idx, Utf8 *out) {
|
||||
const unsigned char *end, *lim;
|
||||
int error_code = 0x2afd;
|
||||
CP_ENTRY(constant_pool, idx, end, lim, out_);
|
||||
if (end + 1 > lim) goto trunc;
|
||||
error_code = 0x2afe;
|
||||
if (*end != 1) goto out_;
|
||||
{
|
||||
const unsigned char *cursor = end + 3;
|
||||
if (cursor > lim) goto trunc;
|
||||
unsigned short len = be16(end + 1);
|
||||
error_code = 0x2af8;
|
||||
if (cursor + len > lim) goto out_;
|
||||
error_code = 0;
|
||||
if (out) {
|
||||
out->len = len;
|
||||
out->p = cursor;
|
||||
}
|
||||
}
|
||||
goto out_;
|
||||
trunc:
|
||||
error_code = 0x2af8;
|
||||
out_:
|
||||
|
||||
return error_code;
|
||||
}
|
||||
// CONSTANT_Class -> its name
|
||||
extern "C" __attribute__((noinline)) int constant_class_name(CP *constant_pool, unsigned short idx, Utf8 *out) {
|
||||
const unsigned char *end, *lim, *u, *ulim;
|
||||
int error_code = 0x2afd;
|
||||
CP_ENTRY(constant_pool, idx, end, lim, out_);
|
||||
if (end + 1 > lim) goto trunc;
|
||||
if (*end != 7) goto tag;
|
||||
if (end + 3 > lim) goto trunc;
|
||||
{
|
||||
unsigned short ni = be16(end + 1);
|
||||
CP_ENTRY(constant_pool, ni, u, ulim, out_);
|
||||
if (u + 1 > ulim) goto trunc;
|
||||
error_code = 0x2afe;
|
||||
if (*u != 1) goto out_;
|
||||
const unsigned char *cursor = u + 3;
|
||||
if (cursor > ulim) goto trunc;
|
||||
unsigned short len = be16(u + 1);
|
||||
error_code = 0x2af8;
|
||||
if (cursor + len > ulim) goto out_;
|
||||
error_code = 0;
|
||||
if (out) {
|
||||
out->len = len;
|
||||
out->p = cursor;
|
||||
}
|
||||
}
|
||||
goto out_;
|
||||
tag:
|
||||
error_code = 0x2afe;
|
||||
goto out_;
|
||||
trunc:
|
||||
error_code = 0x2af8;
|
||||
out_:
|
||||
|
||||
return error_code;
|
||||
}
|
||||
// CONSTANT_NameAndType -> name + descriptor
|
||||
extern "C" __attribute__((noinline)) int constant_name_and_type(CP *constant_pool, unsigned short idx, NameType *out) {
|
||||
const unsigned char *end, *lim, *u, *ulim;
|
||||
int error_code = 0x2afd;
|
||||
CP_ENTRY(constant_pool, idx, end, lim, out_);
|
||||
if (end + 1 > lim) goto trunc;
|
||||
if (*end != 0xc) goto tag;
|
||||
if (end + 3 > lim || end + 5 > lim) goto trunc;
|
||||
{
|
||||
unsigned short ni = be16(end + 1);
|
||||
CP_ENTRY(constant_pool, ni, u, ulim, out_);
|
||||
if (u + 1 > ulim) goto trunc;
|
||||
error_code = 0x2afe;
|
||||
if (*u != 1) goto out_;
|
||||
const unsigned char *cursor = u + 3;
|
||||
if (cursor > ulim) goto trunc;
|
||||
unsigned short len = be16(u + 1);
|
||||
error_code = 0x2af8;
|
||||
if (cursor + len > ulim) goto out_;
|
||||
unsigned short di = be16(end + 3);
|
||||
if (out) {
|
||||
out->name.len = len;
|
||||
out->name.p = cursor;
|
||||
}
|
||||
return constant_utf8(constant_pool, di, &out->desc);
|
||||
}
|
||||
tag:
|
||||
error_code = 0x2afe;
|
||||
goto out_;
|
||||
trunc:
|
||||
error_code = 0x2af8;
|
||||
out_:
|
||||
|
||||
return error_code;
|
||||
}
|
||||
|
||||
struct WBuf { unsigned char *p, *end; };
|
||||
extern "C" int classfile_write_u1(WBuf *w, unsigned char b) {
|
||||
unsigned char *cursor = w->p;
|
||||
if (cursor + 1 > w->end) return 0x2af9;
|
||||
*cursor = b;
|
||||
w->p = cursor + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct RBuf { const unsigned char *p, *end; };
|
||||
extern "C" int classfile_read_u1(RBuf *r, unsigned char *out) {
|
||||
const unsigned char *cursor = r->p;
|
||||
if (cursor + 1 > r->end) return 0x2af8;
|
||||
if (out) *out = *cursor;
|
||||
r->p = cursor + 1;
|
||||
return 0;
|
||||
}
|
||||
extern "C" int classfile_read_u2(RBuf *r, unsigned short *out) {
|
||||
const unsigned char *cursor = r->p;
|
||||
if (cursor + 2 > r->end) return 0x2af8;
|
||||
if (out) *out = be16(cursor);
|
||||
r->p = cursor + 2;
|
||||
return 0;
|
||||
}
|
||||
extern "C" int classfile_write_u2(WBuf *w, unsigned short v) {
|
||||
unsigned char *cursor = w->p;
|
||||
if (cursor + 2 > w->end) return 0x2af9;
|
||||
cursor[0] = (unsigned char)(v >> 8);
|
||||
w->p[1] = (unsigned char)v;
|
||||
w->p = cursor + 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include "slinky/jnienv.h"
|
||||
extern "C" const char str_loadclass[], str_ljava_lang_string_ljava_lang_c[]; // "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"
|
||||
struct LoaderRef { jobject loader; jmethodID loadClass; };
|
||||
extern "C" int make_secure_class_loader(LoaderRef *o, JNIEnv *env, jobject loader) {
|
||||
jclass cls = (*env)->GetObjectClass(env, loader);
|
||||
jmethodID mid = (*env)->GetMethodID(env, cls, str_loadclass, str_ljava_lang_string_ljava_lang_c);
|
||||
if (!mid) return 0x7d4;
|
||||
jobject ref = (*env)->NewGlobalRef(env, loader);
|
||||
if (!ref) return 0x7d5;
|
||||
o->loader = ref;
|
||||
o->loadClass = mid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
FN(int, constant_class_name, (CP *cp, unsigned short idx, Utf8 *out));
|
||||
// CONSTANT_Methodref / InterfaceMethodref (tags 10, 11) -> class, name, descriptor
|
||||
extern "C" int constant_pool_member_ref2(CP *constant_pool, unsigned short idx, MemberRef *out) {
|
||||
const unsigned char *end, *lim;
|
||||
int error_code = 0x2afd;
|
||||
CP_ENTRY(constant_pool, idx, end, lim, out_);
|
||||
if (end + 1 > lim) goto trunc;
|
||||
error_code = 0x2afe;
|
||||
if ((*end & 0xfe) != 10) goto out_;
|
||||
if (end + 3 > lim) goto trunc;
|
||||
error_code = 0x2af8;
|
||||
if (end + 5 > lim) goto out_;
|
||||
{
|
||||
unsigned short ci = be16(end + 1);
|
||||
unsigned char hi = end[3], lo = end[4];
|
||||
error_code = constant_class_name(constant_pool, ci, &out->cls);
|
||||
if (error_code) goto out_;
|
||||
return constant_name_and_type(constant_pool, (unsigned short)((hi << 8) | lo), &out->nt);
|
||||
}
|
||||
trunc:
|
||||
error_code = 0x2af8;
|
||||
out_:
|
||||
return error_code;
|
||||
}
|
||||
|
||||
FN(jclass, find_class, (jobject loader, JNIEnv *env, const char *name));
|
||||
extern "C" const char str_net_minecraft_client_minecraft_in_classfile_constants[], str_field_71439_g_in_classfile_constants[], str_lnet_minecraft_client_entity_e_in_classfile_constants[], str_lnet_minecraft_client_entity_e_in_classfile_constants_a[], str_theplayer_in_classfile_constants[],
|
||||
str_bao_in_classfile_constants[], str_i_in_classfile_constants[], str_ljava_io_file[], str_ave_in_classfile_constants[], str_n_in_classfile_constants[];
|
||||
struct Mapping { int player; int names; unsigned char padding_08; }; // player: 0 = ClientPlayerMP, 1 = PlayerSP; names: 0/1 obf, 1 MCP, 2 SRG
|
||||
// detect which mapping set the game uses (SRG / MCP / obfuscated 1.7 / 1.8)
|
||||
extern "C" int fill_jni_ids(Mapping *out, jobject loader, JNIEnv *env) {
|
||||
jclass mc = find_class(loader, env, str_net_minecraft_client_minecraft_in_classfile_constants); // net.minecraft.client.Minecraft
|
||||
if (mc) {
|
||||
if ((*env)->GetFieldID(env, mc, str_field_71439_g_in_classfile_constants, str_lnet_minecraft_client_entity_e_in_classfile_constants)) { *(long long *)out = 0x200000000ll; return 0; }
|
||||
(*env)->ExceptionClear(env);
|
||||
if ((*env)->GetFieldID(env, mc, str_field_71439_g_in_classfile_constants, str_lnet_minecraft_client_entity_e_in_classfile_constants_a)) { *(long long *)out = 0x200000001ll; return 0; }
|
||||
(*env)->ExceptionClear(env);
|
||||
if ((*env)->GetFieldID(env, mc, str_theplayer_in_classfile_constants, str_lnet_minecraft_client_entity_e_in_classfile_constants)) { *(long long *)out = 0x100000000ll; out->padding_08 = 0; return 0; }
|
||||
(*env)->ExceptionClear(env);
|
||||
if ((*env)->GetFieldID(env, mc, str_theplayer_in_classfile_constants, str_lnet_minecraft_client_entity_e_in_classfile_constants_a)) { *(long long *)out = 0x100000001ll; out->padding_08 = 0; return 0; }
|
||||
(*env)->ExceptionClear(env);
|
||||
return 0x3eb;
|
||||
}
|
||||
(*env)->ExceptionClear(env);
|
||||
jclass c = find_class(loader, env, str_bao_in_classfile_constants); // "bao" (1.7 obf)
|
||||
if (c && (*env)->GetFieldID(env, c, str_i_in_classfile_constants, str_ljava_io_file)) { *(long long *)out = 0; return 0; }
|
||||
(*env)->ExceptionClear(env);
|
||||
c = find_class(loader, env, str_ave_in_classfile_constants); // "ave" (1.8 obf)
|
||||
if (c && (*env)->GetFieldID(env, c, str_n_in_classfile_constants, str_ljava_io_file)) { *(long long *)out = 1; return 0; }
|
||||
(*env)->ExceptionClear(env);
|
||||
return 0x3eb;
|
||||
}
|
||||
|
||||
extern "C" const char str_java_lang_thread[], str_getallstacktraces[], str_ljava_util_map_in_classfile_constants[], str_java_util_map_in_classfile_constants[], str_keyset[], str_ljava_util_set[],
|
||||
str_java_util_collection[], str_toarray[], str_ljava_lang_object[], str_getname_in_classfile_constants[], str_ljava_lang_string_in_classfile_constants[], str_client_thread[], str_getcontextclassloader[], str_ljava_lang_classloader[];
|
||||
extern "C" int libc_strcmp(const char *left, const char *right); // strcmp
|
||||
// find the game's "Client thread" and keep a global ref to its context class loader (+ loadClass)
|
||||
extern "C" int find_game_class_loader(LoaderRef *out, JNIEnv *env) {
|
||||
jclass T = (*env)->FindClass(env, str_java_lang_thread);
|
||||
if (!T) return 0x7d0;
|
||||
jmethodID all = (*env)->GetStaticMethodID(env, T, str_getallstacktraces, str_ljava_util_map_in_classfile_constants);
|
||||
if (!all) return 0x7d0;
|
||||
jclass M = (*env)->FindClass(env, str_java_util_map_in_classfile_constants);
|
||||
if (!M) return 0x7d0;
|
||||
jmethodID keySet = (*env)->GetMethodID(env, M, str_keyset, str_ljava_util_set);
|
||||
if (!keySet) return 0x7d0;
|
||||
jobject map = (*env)->CallStaticObjectMethod(env, T, all);
|
||||
jobject set = (*env)->CallObjectMethod(env, map, keySet);
|
||||
jclass C = (*env)->FindClass(env, str_java_util_collection);
|
||||
if (!C) return 0x7d0;
|
||||
jmethodID toArray = (*env)->GetMethodID(env, C, str_toarray, str_ljava_lang_object);
|
||||
if (!toArray) return 0x7d0;
|
||||
jmethodID getName = (*env)->GetMethodID(env, T, str_getname_in_classfile_constants, str_ljava_lang_string_in_classfile_constants);
|
||||
if (!getName) return 0x7d0;
|
||||
jobjectArray arr = (jobjectArray)(*env)->CallObjectMethod(env, set, toArray);
|
||||
jsize n = (*env)->GetArrayLength(env, arr);
|
||||
if (n <= 0) return 0x3ea;
|
||||
jobject th;
|
||||
for (int i = 0;; i++) {
|
||||
th = (*env)->GetObjectArrayElement(env, arr, i);
|
||||
jstring name = (jstring)(*env)->CallObjectMethod(env, th, getName);
|
||||
const char *s = (*env)->GetStringUTFChars(env, name, 0);
|
||||
int c = libc_strcmp(s, str_client_thread); // "Client thread"
|
||||
(*env)->ReleaseStringUTFChars(env, name, s);
|
||||
if (c == 0) break;
|
||||
if (n == i + 1) return 0x3ea;
|
||||
}
|
||||
jmethodID gccl = (*env)->GetMethodID(env, T, str_getcontextclassloader, str_ljava_lang_classloader);
|
||||
if (!gccl) return 0x7d0;
|
||||
jobject loader = (*env)->CallObjectMethod(env, th, gccl);
|
||||
jclass lc = (*env)->GetObjectClass(env, loader);
|
||||
jmethodID load = (*env)->GetMethodID(env, lc, str_loadclass, str_ljava_lang_string_ljava_lang_c);
|
||||
if (!load) return 0x7d4;
|
||||
jobject ref = (*env)->NewGlobalRef(env, loader);
|
||||
if (!ref) return 0x7d5;
|
||||
out->loader = ref;
|
||||
out->loadClass = load;
|
||||
return 0;
|
||||
}
|
||||
extern "C" void *class_loader_ref(void **p) { return *p; }
|
||||
// load class `name` (slashes or dots) through the game's class loader: loader.loadClass(name with dots)
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
FN(void *, libc_malloc, (unsigned long long n)); // malloc
|
||||
FN(void, libc_free, (void *ptr)); // free
|
||||
extern "C" __attribute__((noinline)) jclass find_class(jobject lr, JNIEnv *env, const char *name) {
|
||||
LoaderRef *o = (LoaderRef *)lr;
|
||||
unsigned long long n = libc_strlen(name);
|
||||
char *buf = (char *)libc_malloc(n + 1);
|
||||
if (!buf) return 0;
|
||||
#pragma clang loop vectorize(disable) interleave(disable)
|
||||
for (unsigned long long i = 0; i < n; i++) buf[i] = name[i] == '/' ? '.' : name[i];
|
||||
buf[n] = 0;
|
||||
jstring s = (*env)->NewStringUTF(env, buf);
|
||||
libc_free(buf);
|
||||
if (!s) return 0;
|
||||
jclass c = (jclass)(*env)->CallObjectMethod(env, o->loader, o->loadClass, s);
|
||||
(*env)->DeleteLocalRef(env, s);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env); // class not found: the caller decides what to do
|
||||
return 0;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// ---- gap-scan functions of the class-file reader block ----
|
||||
// release a global ref: DeleteGlobalRef(env, *ref); *ref = 0
|
||||
extern "C" void class_file_scan(jobject *ref, JNIEnv *env) {
|
||||
if (*ref) {
|
||||
(*env)->DeleteGlobalRef(env, *ref);
|
||||
*ref = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// which Minecraft is it? the obfuscated main class "bao" (field I: File) or "ave" (field N: File) -> *kind 0 / 1
|
||||
// (0x3eb: neither found)
|
||||
extern "C" const char str_bao_in_classfile_constants[], str_i_in_classfile_constants[], str_ljava_io_file[], str_ave_in_classfile_constants[], str_n_in_classfile_constants[];
|
||||
extern "C" int class_define(long long *kind, jobject loader, JNIEnv *env) {
|
||||
jclass c = find_class(loader, env, str_bao_in_classfile_constants /* "bao" */);
|
||||
if (c && (*env)->GetFieldID(env, c, str_i_in_classfile_constants /* "I" */, str_ljava_io_file /* "Ljava/io/File;" */)) {
|
||||
*kind = 0;
|
||||
return 0;
|
||||
}
|
||||
(*env)->ExceptionClear(env);
|
||||
c = find_class(loader, env, str_ave_in_classfile_constants /* "ave" */);
|
||||
if (c && (*env)->GetFieldID(env, c, str_n_in_classfile_constants /* "N" */, str_ljava_io_file)) {
|
||||
*kind = 1;
|
||||
return 0;
|
||||
}
|
||||
(*env)->ExceptionClear(env);
|
||||
return 0x3eb;
|
||||
}
|
||||
|
||||
FN(void *, libc_memcpy_alt, (void *dest, const void *src, unsigned long long n));
|
||||
// read n bytes into dst (may be NULL to skip) / a big-endian u32
|
||||
extern "C" int classfile_read_bytes(RBuf *r, unsigned n, void *dst) {
|
||||
const unsigned char *cursor = r->p, *end = cursor + n;
|
||||
if (end > r->end) return 0x2af8;
|
||||
if (dst) libc_memcpy_alt(dst, cursor, n);
|
||||
r->p = end;
|
||||
return 0;
|
||||
}
|
||||
extern "C" int classfile_read_constant(RBuf *r, unsigned *out) {
|
||||
const unsigned char *cursor = r->p, *end = cursor + 4;
|
||||
if (end > r->end) return 0x2af8;
|
||||
if (out) *out = __builtin_bswap32(*(const unsigned *)cursor);
|
||||
r->p = end;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fieldref/Methodref-style entry whose tag satisfies `pred` -> class, name, descriptor (general form of constant_pool_member_ref/constant_pool_member_ref2)
|
||||
FN(int, constant_name_and_type, (CP *cp, unsigned short idx, NameType *out));
|
||||
extern "C" int constant_pool_find_if(CP *constant_pool, bool (*pred)(unsigned char), unsigned short idx, MemberRef *out) {
|
||||
const unsigned char *end, *lim;
|
||||
int error_code = 0x2afd;
|
||||
CP_ENTRY(constant_pool, idx, end, lim, out_);
|
||||
if (end + 1 > lim) goto trunc;
|
||||
{
|
||||
unsigned char tag = *end;
|
||||
error_code = 0x2afe;
|
||||
if (!pred(tag)) goto out_;
|
||||
}
|
||||
if (end + 3 > lim) goto trunc;
|
||||
error_code = 0x2af8;
|
||||
if (end + 5 > lim) goto out_;
|
||||
{
|
||||
unsigned short ci = be16(end + 1);
|
||||
unsigned char hi = end[3], lo = end[4];
|
||||
error_code = constant_class_name(constant_pool, ci, &out->cls);
|
||||
if (error_code) goto out_;
|
||||
return constant_name_and_type(constant_pool, (unsigned short)((hi << 8) | lo), &out->nt);
|
||||
}
|
||||
trunc:
|
||||
error_code = 0x2af8;
|
||||
out_:
|
||||
return error_code;
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
// 0xe000: comparators, static tables
|
||||
#include "slinky/engine.h"
|
||||
extern "C" int libc_strcmp(const char *left, const char *right);
|
||||
extern "C" int compare_cstr_ptr(const char **left, const char **right) {
|
||||
const char *x = *left;
|
||||
|
||||
return libc_strcmp(x, *right);
|
||||
}
|
||||
extern "C" int compare_int_field(const int *left, const char *right) { return *left - *(const int *)(right + 8); }
|
||||
extern "C" int compare_int(const int *left, const int *right) { return *left - *right; }
|
||||
extern "C" int compare_cstr_desc(const char **left, const char **right) {
|
||||
const char *x = *left;
|
||||
|
||||
return libc_strcmp(x, *right);
|
||||
}
|
||||
extern "C" char default_name_list_data[], default_key_list_data[];
|
||||
extern "C" void default_name_list(void **p, unsigned long long *n) { *p = default_name_list_data; *n = 0x181; }
|
||||
extern "C" void default_key_list(void **p, unsigned long long *n) { *p = default_key_list_data; *n = 0xc6; }
|
||||
|
||||
extern "C" int compare_long_field(const char **left, const char **right) {
|
||||
long long y = *(const long long *)((const char *)right + 0x30);
|
||||
if (*(const long long *)((const char *)left + 0x30) < y) return -1;
|
||||
if (*(const long long *)((const char *)left + 0x30) > y) return 1;
|
||||
const char *x = *left;
|
||||
|
||||
return libc_strcmp(x, *right);
|
||||
}
|
||||
|
||||
FN(void *, libc_bsearch, (const void *key, const void *base, unsigned long long n, unsigned long long size, int (*cmp)(const void *, const void *)));
|
||||
struct MEnt { int id; int padding_04; unsigned long long n; void *capacity; void **list; };
|
||||
struct IdMap { char padding_00[0x10]; unsigned long long count; char padding_18[8]; MEnt *data; unsigned char multi; };
|
||||
// lookup id -> value (multi-maps: test membership of val)
|
||||
extern "C" void *key_map_get(IdMap *m, int id, void *val) {
|
||||
if (!id) return 0;
|
||||
MEnt k = {};
|
||||
k.id = id;
|
||||
if (!m->data) return 0;
|
||||
MEnt *map_entry = (MEnt *)libc_bsearch(&k, m->data, m->count, 0x20, (int (*)(const void *, const void *))compare_int);
|
||||
if (!map_entry) return 0;
|
||||
unsigned long long n = map_entry->n;
|
||||
if (m->multi == 1) {
|
||||
for (unsigned long long i = 0; i < n; i++) {
|
||||
void *x = map_entry->list[i];
|
||||
if (x == val) return x;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (!n) return 0;
|
||||
return map_entry->list[0];
|
||||
}
|
||||
|
||||
struct CbMap { unsigned char (*fn)(void *item, int id, int arg); char padding_08[8]; unsigned long long count; char padding_18[8]; MEnt *data; };
|
||||
// call fn on every item registered under id; true if any returned true
|
||||
extern "C" bool key_map_dispatch(CbMap *m, int id, int arg) {
|
||||
MEnt k = {};
|
||||
k.id = id;
|
||||
unsigned char r = 0;
|
||||
if (m->data) {
|
||||
MEnt *map_entry = (MEnt *)libc_bsearch(&k, m->data, m->count, 0x20, (int (*)(const void *, const void *))compare_int);
|
||||
if (map_entry && map_entry->n) {
|
||||
for (unsigned long long i = 0; i < map_entry->n; i++) r |= m->fn(map_entry->list[i], id, arg);
|
||||
}
|
||||
}
|
||||
return r & 1;
|
||||
}
|
||||
|
||||
// ---- id -> value(s) map (sorted 32-byte entries; "multi" maps keep a list per id) ----
|
||||
FN(unsigned char, vector_grow, (void *data, unsigned long long elem, unsigned long long n, void *cap));
|
||||
FN(void, libc_free, (void *ptr));
|
||||
FN(void *, libc_memmove, (void *dest, const void *src, unsigned long long n));
|
||||
struct IdMap2 { void *fn; void (*release)(void *v); unsigned long long count, cap; MEnt *data; unsigned char multi; };
|
||||
extern "C" bool key_map_add(IdMap2 *m, int id, void *val) {
|
||||
if (!id) return true;
|
||||
MEnt k = {};
|
||||
k.id = id;
|
||||
MEnt *map_entry = m->data ? (MEnt *)libc_bsearch(&k, m->data, m->count, 0x20, (int (*)(const void *, const void *))compare_int) : 0;
|
||||
if (!map_entry) {
|
||||
unsigned long long idx = 0;
|
||||
if (m->count) {
|
||||
for (idx = 0; idx < m->count; idx++)
|
||||
if (id - m->data[idx].id <= 0) break;
|
||||
}
|
||||
if (!vector_grow(&m->data, 0x20, m->count, &m->cap)) return false;
|
||||
MEnt *base = m->data;
|
||||
libc_memmove(base + idx + 1, base + idx, (m->count - idx) * 0x20);
|
||||
base[idx] = MEnt{};
|
||||
base[idx].id = id;
|
||||
m->count++;
|
||||
if (!m->data) return false;
|
||||
map_entry = m->data + idx;
|
||||
}
|
||||
unsigned long long n = map_entry->n;
|
||||
if (m->multi == 1) {
|
||||
for (unsigned long long i = 0; i < n; i++)
|
||||
if (map_entry->list[i] == val) return true;
|
||||
} else if (n) {
|
||||
if (m->release) m->release(map_entry->list[0]);
|
||||
map_entry->n = 0;
|
||||
map_entry->capacity = 0;
|
||||
libc_free(map_entry->list);
|
||||
map_entry->list = 0;
|
||||
n = map_entry->n;
|
||||
}
|
||||
if (!vector_grow(&map_entry->list, 8, n, &map_entry->capacity)) return false;
|
||||
map_entry->list[map_entry->n] = val;
|
||||
map_entry->n++;
|
||||
return true;
|
||||
}
|
||||
extern "C" void key_map_remove(IdMap2 *m, int id, void *val) {
|
||||
if (!id) return;
|
||||
MEnt k = {};
|
||||
k.id = id;
|
||||
if (!m->data) return;
|
||||
MEnt *map_entry = (MEnt *)libc_bsearch(&k, m->data, m->count, 0x20, (int (*)(const void *, const void *))compare_int);
|
||||
if (!map_entry) return;
|
||||
unsigned long long n0 = map_entry->n;
|
||||
if (!n0) return;
|
||||
if (m->multi == 1) {
|
||||
void **list = map_entry->list;
|
||||
unsigned long long i;
|
||||
for (i = 0; i < n0; i++)
|
||||
if (list[i] == val) break;
|
||||
if (i == n0) return;
|
||||
unsigned long long n = n0;
|
||||
if (m->release) {
|
||||
m->release(list[i]);
|
||||
n = map_entry->n;
|
||||
list = map_entry->list;
|
||||
}
|
||||
map_entry->n = --n;
|
||||
libc_memmove(&list[i], &list[i + 1], (n - i) * 8);
|
||||
if (n0 != 1) return;
|
||||
} else {
|
||||
if (m->release) m->release(map_entry->list[0]);
|
||||
}
|
||||
map_entry->n = 0;
|
||||
map_entry->capacity = 0;
|
||||
libc_free(map_entry->list);
|
||||
map_entry->list = 0;
|
||||
unsigned long long idx = (unsigned long long)(map_entry - m->data);
|
||||
unsigned long long c = m->count;
|
||||
m->count = c - 1;
|
||||
libc_memmove(map_entry, map_entry + 1, (c - idx - 1) * 0x20);
|
||||
}
|
||||
|
||||
// ---- profile list (names + "modified" label + bind), refreshed from the profiles folder ----
|
||||
FN(void *, libc_calloc, (unsigned long long n, unsigned long long size)); // calloc
|
||||
FN(void *, libc_memcpy, (void *dest, const void *src, unsigned long long n));
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
FN(int, libc_snprintf, (char *buf, unsigned long long n, const char *fmt, ...)); // snprintf
|
||||
FN(void, libc_qsort, (void *base, unsigned long long n, unsigned long long size, int (*cmp)(const void *, const void *)));
|
||||
FN(int, list_profiles, (void *ctx, int (*callback)(void *fi, void *ctx)));
|
||||
extern "C" const char str_unknown_modification_date[], str_modified_1_day_ago[], str_modified_today[], str_modified_lld_days_ago[];
|
||||
struct ProfEnt { char *name; char *end; char desc[0x20]; long long age; int bind; int padding_3c; };
|
||||
struct ProfList { unsigned long long count, cap; ProfEnt *data; };
|
||||
struct OldEnt { char *name; int bind; int padding_0c; };
|
||||
struct OldList { unsigned long long count, cap; OldEnt *data; };
|
||||
struct FileInfo { const char *name; long long age; };
|
||||
struct ScanCtx { ProfList *list; OldList *old; };
|
||||
extern "C" int scan_next_file(FileInfo *fi, ScanCtx *ctx) {
|
||||
const char *name = fi->name;
|
||||
unsigned long long n = libc_strlen(name);
|
||||
char *s = (char *)libc_calloc(n + 1, 1);
|
||||
if (!s) return 0x3ed;
|
||||
libc_memcpy(s, name, n);
|
||||
ProfEnt e = {};
|
||||
e.name = s;
|
||||
e.end = s + (n < 0xf ? n : 0xf);
|
||||
e.age = fi->age;
|
||||
OldEnt key = {s, 0, 0};
|
||||
OldList *old = ctx->old;
|
||||
if (old->data) {
|
||||
OldEnt *f = (OldEnt *)libc_bsearch(&key, old->data, old->count, 0x10, (int (*)(const void *, const void *))compare_cstr_desc);
|
||||
if (f) e.bind = f->bind;
|
||||
}
|
||||
long long age = fi->age;
|
||||
if (age == -1) {
|
||||
__builtin_memcpy(e.desc, str_unknown_modification_date, 0x18);
|
||||
} else {
|
||||
long long days = age / 86400;
|
||||
if (days == 1) __builtin_memcpy(e.desc, str_modified_1_day_ago, 0x18);
|
||||
else if (days == 0) { __builtin_memcpy(e.desc, str_modified_today, 0x10); *(long long *)(e.desc + 0x10) = 0; }
|
||||
else libc_snprintf(e.desc, 0x19, str_modified_lld_days_ago, days);
|
||||
}
|
||||
ProfList *l = ctx->list;
|
||||
if (!vector_grow(&l->data, 0x40, l->count, &l->cap)) {
|
||||
libc_free(s);
|
||||
return 0x3ed;
|
||||
}
|
||||
l->data[l->count] = e;
|
||||
l->count++;
|
||||
return 0;
|
||||
}
|
||||
extern "C" int load_profiles(ProfList *l) {
|
||||
OldList old = {0, 0, 0};
|
||||
int error_code;
|
||||
if (l->count) {
|
||||
unsigned long long left = l->count;
|
||||
for (ProfEnt *e = l->data; ; ) {
|
||||
if (e->bind) {
|
||||
const char *nm = e->name;
|
||||
unsigned long long n = libc_strlen(nm);
|
||||
char *s = (char *)libc_calloc(n + 1, 1);
|
||||
if (!s) { error_code = 0x3ed; goto cleanup; }
|
||||
libc_memcpy(s, nm, n);
|
||||
int bind = e->bind;
|
||||
if (vector_grow(&old.data, 0x10, old.count, &old.cap)) {
|
||||
old.data[old.count].name = s;
|
||||
old.data[old.count].bind = bind;
|
||||
old.data[old.count].padding_0c = 0;
|
||||
old.count++;
|
||||
}
|
||||
}
|
||||
if (!--left) break;
|
||||
e = l->data + (l->count - left);
|
||||
}
|
||||
if (old.data) libc_qsort(old.data, old.count, 0x10, (int (*)(const void *, const void *))compare_cstr_desc);
|
||||
for (unsigned long long i = 0; i < l->count; i++) libc_free(l->data[i].name);
|
||||
l->count = 0;
|
||||
l->cap = 0;
|
||||
libc_free(l->data);
|
||||
l->data = 0;
|
||||
}
|
||||
{
|
||||
ScanCtx ctx = {l, &old};
|
||||
error_code = list_profiles(&ctx, (int (*)(void *, void *))scan_next_file);
|
||||
if (l->count && l->data) libc_qsort(l->data, l->count, 0x40, (int (*)(const void *, const void *))compare_long_field);
|
||||
}
|
||||
cleanup:
|
||||
for (unsigned long long i = 0; i < old.count; i++) libc_free(old.data[i].name);
|
||||
old.count = 0;
|
||||
old.cap = 0;
|
||||
libc_free(old.data);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
// ---- generic dynamic arrays (as 0xdd0..0xdf90): {u32 cap, u32 count} header, element size passed in ----
|
||||
struct AHdr { unsigned cap, count; };
|
||||
typedef int (*ACmp)(void *ctx, const void *key, const void *elem);
|
||||
FN(void *, libc_realloc, (void *ptr, unsigned long long n)); // realloc
|
||||
extern "C" __attribute__((noreturn)) void libc_abort(); // abort
|
||||
FN(void *, libc_memcpy_alt, (void *dest, const void *src, unsigned long long n)); // memcpy
|
||||
// sorted find-or-insert: the existing element equal to key, or key inserted in order
|
||||
extern "C" void *vector_find_insert(void **data, unsigned elem, AHdr *header, const void *key, ACmp cmp) {
|
||||
unsigned hi = header->count, idx = 0, cnt = 0;
|
||||
if (hi) {
|
||||
char *base = (char *)*data;
|
||||
unsigned lo = 0;
|
||||
for (;;) {
|
||||
unsigned mid = lo + ((hi - lo) >> 1);
|
||||
int result = cmp(0, key, base + mid * elem);
|
||||
if (result < 0) {
|
||||
hi = mid;
|
||||
if (lo >= hi) break;
|
||||
} else if (result == 0) {
|
||||
return (char *)*data + mid * elem;
|
||||
} else {
|
||||
lo = mid + 1;
|
||||
if (lo >= hi) break;
|
||||
}
|
||||
}
|
||||
cnt = header->count;
|
||||
idx = hi;
|
||||
}
|
||||
char *base = (char *)*data;
|
||||
if (cnt >= header->cap) {
|
||||
unsigned cap = header->cap ? header->cap * 2 : 8;
|
||||
header->cap = cap;
|
||||
void *p = libc_realloc(base, cap * elem);
|
||||
if (!p) libc_abort();
|
||||
*data = p;
|
||||
base = (char *)p;
|
||||
cnt = header->count;
|
||||
}
|
||||
char *at = base + idx * elem;
|
||||
libc_memmove(base + (idx + 1) * elem, at, (cnt - idx) * elem);
|
||||
libc_memcpy_alt(at, key, elem);
|
||||
header->count++;
|
||||
return at;
|
||||
}
|
||||
// erase index i
|
||||
extern "C" void *vector_erase(void *base, unsigned elem, AHdr *header, unsigned i) {
|
||||
unsigned n = --header->count;
|
||||
return libc_memmove((char *)base + i * elem, (char *)base + (i + 1) * elem, (n - i) * elem);
|
||||
}
|
||||
// erase the element at p
|
||||
extern "C" void *vector_erase_value(void *base, unsigned elem, AHdr *header, void *p) {
|
||||
unsigned i = (unsigned)((unsigned long long)((char *)p - (char *)base) / elem);
|
||||
unsigned n = --header->count;
|
||||
return libc_memmove((char *)base + i * elem, (char *)base + (i + 1) * elem, (n - i) * elem);
|
||||
}
|
||||
// make room for element n (64-bit sizes): starts at 8, doubles
|
||||
extern "C" unsigned char vector_grow(void *datav, unsigned long long elem, unsigned long long n, void *capv) {
|
||||
void **data = (void **)datav;
|
||||
unsigned long long *cap = (unsigned long long *)capv;
|
||||
unsigned long long c = *cap;
|
||||
if (n < c) return true;
|
||||
void *p;
|
||||
if (*data) {
|
||||
c += c;
|
||||
p = libc_realloc(*data, elem * c);
|
||||
} else {
|
||||
c = 8;
|
||||
p = libc_calloc(8, elem);
|
||||
}
|
||||
if (!p) return false;
|
||||
*data = p;
|
||||
*cap = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- block name <-> id tables (sorted by name / by id, 0x181 entries) ----
|
||||
struct NameId { const char *name; int id; int padding_0c; };
|
||||
extern "C" NameId name_id_table_sorted_by_name[], name_id_table_sorted_by_id[];
|
||||
extern "C" int key_name_to_code(const char *name) {
|
||||
const char *k = name;
|
||||
NameId *e = (NameId *)libc_bsearch(&k, name_id_table_sorted_by_name, 0x181, 0x10, (int (*)(const void *, const void *))compare_cstr_ptr);
|
||||
int result;
|
||||
if (e) result = e->id;
|
||||
else result = -1;
|
||||
SHARED_EPILOG();
|
||||
return result;
|
||||
}
|
||||
extern "C" const char *key_code_to_index(int id) {
|
||||
int k = id;
|
||||
NameId *e = (NameId *)libc_bsearch(&k, name_id_table_sorted_by_id, 0x181, 0x10, (int (*)(const void *, const void *))compare_int_field);
|
||||
const char *r;
|
||||
if (e) r = e->name;
|
||||
else r = 0;
|
||||
SHARED_EPILOG();
|
||||
return r;
|
||||
}
|
||||
// call fn(item, id, 0) for every registered item
|
||||
extern "C" void invoke_callback(CbMap *m) {
|
||||
for (unsigned long long i = 0; i < m->count; i++) {
|
||||
MEnt *map_entry = &m->data[i];
|
||||
if (!map_entry->n) continue;
|
||||
unsigned long long j = 0;
|
||||
do {
|
||||
void *it = map_entry->list[j];
|
||||
|
||||
m->fn(it, map_entry->id, 0);
|
||||
} while (++j < map_entry->n);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- profile list accessors ----
|
||||
struct Profs { unsigned long long count, cap; ProfEnt *data; void *bind; };
|
||||
extern "C" void *profile_set_bind(Profs *p) { return p->bind; }
|
||||
extern "C" ProfEnt *profile_array(Profs *p, unsigned long long *n) {
|
||||
*n = p->count;
|
||||
return p->count ? p->data : 0;
|
||||
}
|
||||
extern "C" ProfEnt *find_profile(Profs *p, const char *name) {
|
||||
unsigned long long n = p->count;
|
||||
if (!n) return 0;
|
||||
ProfEnt *e = p->data;
|
||||
for (; n; n--, e++)
|
||||
if (libc_strcmp(name, e->name) == 0) return e;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- gap-scan functions of the 0xe000 block ----
|
||||
// grow a ring buffer when full: capacity doubles (16 first); if the contents wrap around, the wrapped part
|
||||
// [0, tail) is copied behind the old end. Indices are free-running, taken modulo the (power of two) capacity.
|
||||
struct Ring { unsigned long long cap, head, tail; };
|
||||
extern "C" void vector_reserve(void **data, unsigned long long elem, Ring *r) {
|
||||
unsigned long long cap = r->cap;
|
||||
if (r->tail - r->head < cap) return;
|
||||
unsigned long long ncap = cap ? cap * 2 : 16;
|
||||
r->cap = ncap;
|
||||
void *p = libc_realloc(*data, ncap * elem);
|
||||
if (!p) libc_abort();
|
||||
*data = p;
|
||||
unsigned long long mask = cap - 1;
|
||||
unsigned long long h = r->head & mask, t = r->tail & mask;
|
||||
if (h < t) return;
|
||||
libc_memcpy_alt((char *)p + cap * elem, p, t * elem);
|
||||
}
|
||||
|
||||
// small zeroed records
|
||||
struct Node30 { void *a, *b; char padding_10[0x18]; unsigned char flag; char padding_29[7]; }; // 0x30
|
||||
extern "C" Node30 *find_entry_index(void *jvm, void *object, bool flag) {
|
||||
Node30 *n = (Node30 *)libc_calloc(1, sizeof(Node30));
|
||||
if (n) {
|
||||
n->a = jvm;
|
||||
n->b = object;
|
||||
n->flag = flag;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
struct Node20 { char padding_00[0x18]; void *p; }; // 0x20
|
||||
extern "C" Node20 *count_matching(void *p) {
|
||||
Node20 *n = (Node20 *)libc_calloc(1, sizeof(Node20));
|
||||
n->p = p; // (the original does not check the allocation)
|
||||
return n;
|
||||
}
|
||||
|
||||
// destroy a callback map (see CbMap): run dtor on every registered item, free the lists, the entry array and the map
|
||||
struct DtorMap { void *fn; void (*dtor)(void *item); unsigned long long count; unsigned long long cap; MEnt *data; };
|
||||
extern "C" void key_map_destroy(DtorMap *m) {
|
||||
if (!m) return;
|
||||
if (m->count) {
|
||||
void (*dtor)(void *) = m->dtor;
|
||||
for (unsigned long long i = 0; i < m->count; i++) {
|
||||
MEnt *map_entry = &m->data[i];
|
||||
if (dtor && map_entry->n)
|
||||
#pragma clang loop unroll(disable)
|
||||
for (unsigned long long j = 0; j < map_entry->n; j++) dtor(map_entry->list[j]);
|
||||
map_entry->n = 0;
|
||||
map_entry->capacity = 0;
|
||||
libc_free(map_entry->list);
|
||||
map_entry->list = 0;
|
||||
}
|
||||
}
|
||||
m->count = 0;
|
||||
m->cap = 0;
|
||||
libc_free(m->data);
|
||||
libc_free(m);
|
||||
}
|
||||
|
||||
// free a profile list: every entry's name, the entry array and the list itself
|
||||
extern "C" void destroy_profiles(ProfList *l) {
|
||||
if (!l) return;
|
||||
#pragma clang loop unroll(disable)
|
||||
for (unsigned long long i = 0; i < l->count; i++) libc_free(l->data[i].name);
|
||||
l->count = 0;
|
||||
l->cap = 0;
|
||||
libc_free(l->data);
|
||||
libc_free(l);
|
||||
}
|
||||
|
||||
// look up the GLFW entry points the client hooks from the already loaded "glfw" module
|
||||
// (0x3e8: module not loaded, 0x3e9: a function is missing)
|
||||
FN(void *, kernel32_lookups, (const char *module));
|
||||
FN(void *, get_proc_address, (void *module, const char *name));
|
||||
extern "C" void *pfn_glfwgetcurrentcontext, *pfn_glfwgetframebuffersize, *pfn_glfwsetframebuffersizecallback, *pfn_glfwsetmousebuttoncallback, *pfn_glfwsetkeycallback, *pfn_glfwsetcharcallback, *pfn_glfwsetcursorposcallback, *pfn_glfwsetscrollcallback;
|
||||
extern "C" const char str_glfw[], str_glfwgetcurrentcontext[], str_glfwgetframebuffersize[], str_glfwsetframebuffersizecallback[], str_glfwsetmousebuttoncallback[], str_glfwsetkeycallback[],
|
||||
str_glfwsetcharcallback[], str_glfwsetcursorposcallback[], str_glfwsetscrollcallback[];
|
||||
extern "C" int init_glfw_entrypoints() {
|
||||
void *glfw = kernel32_lookups(str_glfw /* "glfw" */);
|
||||
if (!glfw) return 0x3e8;
|
||||
if (!(pfn_glfwgetcurrentcontext = get_proc_address(glfw, str_glfwgetcurrentcontext /* glfwGetCurrentContext */))) return 0x3e9;
|
||||
if (!(pfn_glfwgetframebuffersize = get_proc_address(glfw, str_glfwgetframebuffersize /* glfwGetFramebufferSize */))) return 0x3e9;
|
||||
if (!(pfn_glfwsetframebuffersizecallback = get_proc_address(glfw, str_glfwsetframebuffersizecallback /* glfwSetFramebufferSizeCallback */))) return 0x3e9;
|
||||
if (!(pfn_glfwsetmousebuttoncallback = get_proc_address(glfw, str_glfwsetmousebuttoncallback /* glfwSetMouseButtonCallback */))) return 0x3e9;
|
||||
if (!(pfn_glfwsetkeycallback = get_proc_address(glfw, str_glfwsetkeycallback /* glfwSetKeyCallback */))) return 0x3e9;
|
||||
if (!(pfn_glfwsetcharcallback = get_proc_address(glfw, str_glfwsetcharcallback /* glfwSetCharCallback */))) return 0x3e9;
|
||||
if (!(pfn_glfwsetcursorposcallback = get_proc_address(glfw, str_glfwsetcursorposcallback /* glfwSetCursorPosCallback */))) return 0x3e9;
|
||||
if (!(pfn_glfwsetscrollcallback = get_proc_address(glfw, str_glfwsetscrollcallback /* glfwSetScrollCallback */))) return 0x3e9;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// GUI: module cards - header, settings body, per-card open/close animation.
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/gui_state.h"
|
||||
FN(void, close_popup, (void *x));
|
||||
extern "C" void close_popup_now(void *x) { close_popup(x); }
|
||||
extern "C" void reset_drag_state(GuiState *gui) { gui->input_mode = 0; gui->click_capture = 0; }
|
||||
|
||||
struct R16f { float x, y, w, h; };
|
||||
struct V4f { float x, y, z, w; };
|
||||
FN(float, libc_powf, (float x, float y)); // powf
|
||||
FN(void, nvg_GlobalAlpha, (void *nvg, float alpha));
|
||||
FN(V4f, nvg_rgba_f, (float r, float g, float b, float a));
|
||||
FN(V4f, nvg_trans_rgba_f, (V4f c, float k));
|
||||
FN(void, nvg_FillColor, (void *nvg, V4f *c));
|
||||
FN(R16f, make_rect, (float x, float y, float w, float h));
|
||||
FN(void, draw_rect_filled, (void *window, R16f *r));
|
||||
FN(void, nvg_Translate, (void *nvg, float a, float b));
|
||||
extern "C" const float one_in_gui_cards, five_in_gui_cards, gui_card_header_grey_level, colour_alpha_0_75, gui_card_slide_offset, factor_50;
|
||||
struct WinA { void *ig; char padding_08[0x10]; float w, h; };
|
||||
// open/close animation: ease-out alpha, dimmed backdrop, slide offset
|
||||
extern "C" void card_open_animation(WinA *window, float t) {
|
||||
float one = one_in_gui_cards;
|
||||
float alpha = one - libc_powf(one - t, five_in_gui_cards);
|
||||
void *nvg = window->ig;
|
||||
nvg_GlobalAlpha(nvg, alpha);
|
||||
float k = gui_card_header_grey_level;
|
||||
V4f c = nvg_rgba_f(k, k, k, 1.0f);
|
||||
V4f c2 = nvg_trans_rgba_f(c, colour_alpha_0_75);
|
||||
nvg_FillColor(nvg, &c2);
|
||||
R16f r = make_rect(0.0f, 0.0f, window->w, window->h);
|
||||
draw_rect_filled(window, &r);
|
||||
nvg_Translate(nvg, 0.0f, gui_card_slide_offset);
|
||||
nvg_Translate(nvg, 0.0f, alpha * factor_50);
|
||||
}
|
||||
|
||||
// ---- module cards of one category: header (name, bind button, toggle), description, settings ----
|
||||
struct Reg2 { void **list; unsigned long long n; };
|
||||
struct Mgr2 { void *mods; unsigned long long count; Reg2 cat[5]; char padding_60[0x308 - 0x60]; void *set308; };
|
||||
struct ModC { const char *name; const char *desc; char padding_10[4]; unsigned char enabled; char padding_15[3]; unsigned flags;
|
||||
int bind; unsigned char hold; char padding_21[7]; char anim[0x10]; float height; char padding_3c[4];
|
||||
unsigned char expanded; char padding_41[0x1b7]; void (*settings)(GuiState *gui, void *w); };
|
||||
FN(void, choose_columns, (GuiState *gui, float w));
|
||||
FN(R16f, add_cell, (GuiState *gui, float h));
|
||||
FN(bool, rects_intersect, (const R16f *a, const R16f *b));
|
||||
FN(void, draw_rect_rounded, (void *window, const R16f *r, float round));
|
||||
FN(void, nvg_Scissor, (void *nvg, float x, float y, float w, float h));
|
||||
FN(void, begin_content, (void *window, GuiState *gui, R16f *r, float pad, float f));
|
||||
FN(void, pop_layout_row, (GuiState *gui, int n));
|
||||
FN(float, draw_module_header, (GuiState *gui, void *object, void *other_object, const R16f *r, float fsz, const V4f *col, const V4f *hcol, const char *text));
|
||||
FN(void, nvg_FontSize, (void *nvg, float size));
|
||||
FN(float, draw_text, (void *window, R16f r, int align, int col, const char *text));
|
||||
FN(void, draw_module_description, (GuiState *gui, void *object, void *cbArg, void *target, int key, unsigned char *hold, const R16f *r));
|
||||
FN(bool, draw_animated_description, (GuiState *gui, void *object, R16f *r, void *anim, unsigned char *val));
|
||||
FN(void, queue_module_toggle, (void *module_manager, void *module, unsigned char enabled));
|
||||
FN(float, end_layout_row, (void *window, GuiState *gui));
|
||||
FN(float, lerp_clamped_float, (float a, float b, float t));
|
||||
extern "C" const float gui_cards_column_width, gui_card_background_grey_level, row_height_20_in_gui_cards, content_padding_8_in_gui_cards, font_size_16_in_gui_cards, colour_level_0_866667_in_gui_cards, colour_level_1_in_gui_cards_a,
|
||||
rect_extent_5_in_gui_cards_a, rect_extent_neg_40, rect_extent_40, colour_level_0_6_in_gui_cards, colour_level_0_733333, offset_9_22337e18;
|
||||
extern "C" const double divisor_0_05_in_gui_cards;
|
||||
static inline __attribute__((always_inline)) V4f grey12(float k) { return nvg_rgba_f(k, k, k, 1.0f); }
|
||||
extern "C" void draw_module_cards(GuiState *gui, WinA *window, Mgr2 *mgr, unsigned cat, float dt) {
|
||||
void *nvg = window->ig;
|
||||
choose_columns(gui, gui_cards_column_width);
|
||||
Reg2 *list = &mgr->cat[cat];
|
||||
if (!list->n) return;
|
||||
float speed = (float)((double)dt / divisor_0_05_in_gui_cards);
|
||||
for (unsigned long long i = 0; i < list->n; i++) {
|
||||
ModC *module = (ModC *)list->list[i];
|
||||
R16f card = add_cell(gui, module->height);
|
||||
// the content height grows through the per-card end_layout_row finalizers alone, exactly like the
|
||||
// original: a card outside the viewport is only measured once it scrolls into view
|
||||
R16f view = make_rect(0.0f, gui->scroll, window->w, window->h);
|
||||
if (!rects_intersect(&view, &card)) continue;
|
||||
V4f cc;
|
||||
volatile float ck = gui_card_background_grey_level;
|
||||
cc.x = cc.y = cc.z = ck;
|
||||
cc.w = 1.0f;
|
||||
nvg_FillColor(nvg, &cc);
|
||||
draw_rect_rounded(window, &card, row_height_20_in_gui_cards);
|
||||
R16f inner = card;
|
||||
begin_content(window, gui, &inner, content_padding_8_in_gui_cards, 12.0f);
|
||||
pop_layout_row(gui, 1);
|
||||
void *ig2 = window->ig;
|
||||
R16f hdr = add_cell(gui, row_height_20_in_gui_cards);
|
||||
float x;
|
||||
if (gui->card_layout_mode == 1) {
|
||||
V4f c1 = grey12(colour_level_0_866667_in_gui_cards), c2 = grey12(colour_level_1_in_gui_cards_a);
|
||||
x = draw_module_header(gui, window, module, &hdr, 16.0f, &c1, &c2, module->name);
|
||||
} else {
|
||||
nvg_FontSize(ig2, font_size_16_in_gui_cards);
|
||||
V4f c = grey12(colour_level_0_866667_in_gui_cards);
|
||||
nvg_FillColor(ig2, &c);
|
||||
x = draw_text(window, hdr, 0, 0, module->name);
|
||||
}
|
||||
unsigned f = module->flags;
|
||||
if (!(f & 1)) {
|
||||
R16f kb = make_rect(x + rect_extent_5_in_gui_cards_a, hdr.y, hdr.w - x + hdr.x, hdr.h);
|
||||
draw_module_description(gui, window, mgr->set308, module, module->bind, (module->flags & 8) ? 0 : &module->hold, &kb);
|
||||
f = module->flags;
|
||||
}
|
||||
if (!(f & 2)) {
|
||||
R16f tg = make_rect(hdr.x + hdr.w + rect_extent_neg_40, hdr.y, rect_extent_40, hdr.h);
|
||||
unsigned char on = module->enabled;
|
||||
draw_animated_description(gui, window, &tg, module->anim, &on);
|
||||
if (on != module->enabled) queue_module_toggle(mgr, module, on);
|
||||
}
|
||||
if (gui->card_layout_mode == 0) {
|
||||
R16f d = add_cell(gui, row_height_20_in_gui_cards);
|
||||
V4f c1 = grey12(colour_level_0_6_in_gui_cards), c2 = grey12(colour_level_0_733333);
|
||||
draw_module_header(gui, window, module, &d, 14.0f, &c1, &c2, module->desc);
|
||||
}
|
||||
if (module->expanded == 1 && module->settings) module->settings(gui, window);
|
||||
float used = end_layout_row(window, gui);
|
||||
long long ia = (long long)used;
|
||||
long long ib = (long long)(used - offset_9_22337e18); // float -> uint64 like the original (no constant pool)
|
||||
unsigned long long u = (unsigned long long)((ib & (ia >> 63)) | ia);
|
||||
float target = (float)u;
|
||||
if (module->height != 0.0f) target = lerp_clamped_float(module->height, target, speed);
|
||||
module->height = target;
|
||||
}
|
||||
}
|
||||
extern "C" unsigned char is_unload_requested(GuiState *gui) { return (unsigned char)gui->unload_requested; }
|
||||
|
||||
// is rect r inside the scrolled viewport (0, scroll, width, height)?
|
||||
extern "C" bool rect_in_viewport(GuiState *gui, WinA *w, const R16f *r) {
|
||||
R16f view = make_rect(0.0f, gui->scroll, w->w, w->h);
|
||||
R16f rr = *r;
|
||||
return rects_intersect(&view, &rr);
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
// GUI: controls - sliders, ranges, numeric fields, key pickers.
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/gui_state.h"
|
||||
|
||||
FN(void *, libc_memcpy, (void *dest, const void *src, unsigned long long n));
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
extern "C" void set_slider_text(char *o, void *, const char *src, int a, int b) {
|
||||
o[0x268] = 1;
|
||||
unsigned long long n = libc_strlen(src);
|
||||
if (n > 0x65) n = 0x65;
|
||||
libc_memcpy(o + 0x26a, src, n);
|
||||
o[0x26a + n] = 0;
|
||||
float *f = (float *)(o + 0x2d0);
|
||||
f[0] = (float)a;
|
||||
f[1] = (float)b;
|
||||
}
|
||||
|
||||
// ---- slider: drag along the track or type a value (after a double click), rounded to step, clamped ----
|
||||
struct R16g { float x, y, w, h; };
|
||||
FN(unsigned char, clicked_inside_r_frame, (GuiState *gui, R16g r));
|
||||
FN(void, start_dragging_item_b, (GuiState *gui, void *item, void *other_object));
|
||||
FN(bool, dragged_item_is, (GuiState *gui, void *item));
|
||||
FN(const char *, take_dragged_item, (GuiState *gui));
|
||||
FN(void, numeric_text_filter, (char *buf, char character));
|
||||
FN(float, libc_roundf, (float x)); // roundf
|
||||
extern "C" const float tenth_b;
|
||||
extern "C" const double factor_0_1_in_gui_controls;
|
||||
extern "C" unsigned char draw_slider_track(GuiState *gui, const R16g *track, const R16g *hit, float lo, float hi, float step,
|
||||
float scale, unsigned char *active, float *val) {
|
||||
if (clicked_inside_r_frame(gui, *hit)) {
|
||||
*active = 1;
|
||||
if (gui->numeric_drag_enabled == 1) start_dragging_item_b(gui, (void *)numeric_text_filter, val);
|
||||
} else if (gui->click_capture == 0 && !dragged_item_is(gui, val)) {
|
||||
*active = 0;
|
||||
}
|
||||
unsigned char a = *active;
|
||||
if (a != 1) return a;
|
||||
float v;
|
||||
if (dragged_item_is(gui, val)) {
|
||||
const char *s = take_dragged_item(gui);
|
||||
if (!s) return a;
|
||||
int neg = *s == '-';
|
||||
const char *p = s + neg;
|
||||
int sign = -neg | 1;
|
||||
int ip = 0;
|
||||
char c = *p;
|
||||
while ((unsigned)(c - '0') <= 9) {
|
||||
ip = ip * 10 + (c - '0');
|
||||
c = *++p;
|
||||
}
|
||||
if ((unsigned char)(c | 2) == '.') {
|
||||
float f = 0.0f;
|
||||
float m = tenth_b;
|
||||
++p;
|
||||
for (int d; (unsigned)(d = *p - '0') < 10; p++) {
|
||||
f += (float)d * m;
|
||||
m = (float)((double)m * factor_0_1_in_gui_controls);
|
||||
}
|
||||
v = ((float)ip + f) * (float)sign;
|
||||
} else {
|
||||
v = (float)(ip * sign);
|
||||
}
|
||||
if (scale != 0.0f) v /= scale;
|
||||
} else {
|
||||
float t = (gui->mouse_scene.x - track->x) / track->w;
|
||||
v = (hi - lo) * t + lo;
|
||||
}
|
||||
if (step != 0.0f) v = libc_roundf(v / step) * step;
|
||||
*val = v;
|
||||
if (lo >= v) *val = lo;
|
||||
else if (!(v < hi)) *val = hi;
|
||||
return a;
|
||||
}
|
||||
|
||||
// ---- range slider: two knobs (each grabs on its side of the midpoint), they push each other ----
|
||||
struct V4g { float x, y, z, w; };
|
||||
struct GW14 { void *ig; double t; };
|
||||
struct RAnim { unsigned char low_knob_active, high_knob_active; char padding_02[2]; float p0, p1; int padding_0c; double last; };
|
||||
FN(R16g, add_cell, (GuiState *gui, float h));
|
||||
FN(V4g, nvg_rgba_f, (float r, float g, float b, float a));
|
||||
FN(void, nvg_FillColor, (void *nvg, V4g *c));
|
||||
FN(void, nvg_FontSize, (void *nvg, float size));
|
||||
FN(float, draw_text, (GW14 *window, R16g r, int align, int col, const char *text));
|
||||
FN(R16g, make_rect, (float x, float y, float w, float h));
|
||||
FN(bool, mouse_over_rect_r, (GuiState *gui, R16g r));
|
||||
FN(void, draw_rect_rounded, (GW14 *window, R16g r, float round));
|
||||
FN(float, limit_rotation_delta, (float lo, float hi, float x));
|
||||
FN(float, lerp_clamped_float, (float a, float b, float t));
|
||||
FN(float, lerp_unclamped_float, (float a, float b, float t));
|
||||
FN(void, nvg_BeginPath, (void *nvg));
|
||||
FN(void, nvg_Circle, (void *nvg, float x, float y, float rad));
|
||||
FN(void, nvg_Fill, (void *nvg));
|
||||
FN(unsigned char, draw_slider_track_local, (GuiState *gui, R16g track, R16g hit, float lo, float hi, float step, float scale,
|
||||
unsigned char *active, float *val)) __asm__("draw_slider_track");
|
||||
FN(int, libc_snprintf, (char *buf, unsigned long long n, const char *fmt, ...));
|
||||
extern "C" const float row_height_20_in_gui_controls, colour_level_0_866667_in_gui_controls, font_size_15_in_gui_controls, offset_10_in_gui_controls, offset_neg_2_in_gui_controls_b, gui_control_idle_grey_level,
|
||||
gui_control_hover_grey_level, two_in_gui_controls, clamp_bound_1_in_gui_controls, gui_range_slider_knob_radius, rect_extent_neg_6, gui_range_slider_padding;
|
||||
extern "C" const double factor_0_5_in_gui_controls, offset_neg_2_in_gui_controls, divisor_0_1_in_gui_controls_a;
|
||||
extern "C" const char fmt_float_2dp[], fmt_float_2dp_float_2dp[]; // "%0.2f", "%0.2f - %0.2f"
|
||||
extern "C" char value_label_buffer[], range_label_buffer[];
|
||||
static inline __attribute__((always_inline)) const char *val_text(GuiState *gui, float *item, const char *fmt, float scale) {
|
||||
if (dragged_item_is(gui, item)) return gui->edit_text;
|
||||
float x = *item;
|
||||
float s = scale != 0.0f ? scale * x : x;
|
||||
libc_snprintf(value_label_buffer, 0x40, fmt ? fmt : fmt_float_2dp, (double)s);
|
||||
return value_label_buffer;
|
||||
}
|
||||
extern "C" bool draw_range_slider_row(GuiState *gui, GW14 *window, const char *label, const char *fmt, const char *rfmt, RAnim *an, float lo,
|
||||
float hi, float step, float scale, float *vlo, float *vhi) {
|
||||
void *nvg = window->ig;
|
||||
R16g r = add_cell(gui, row_height_20_in_gui_controls);
|
||||
V4g tc = nvg_rgba_f(colour_level_0_866667_in_gui_controls, colour_level_0_866667_in_gui_controls, colour_level_0_866667_in_gui_controls, 1.0f);
|
||||
nvg_FillColor(nvg, &tc);
|
||||
nvg_FontSize(nvg, font_size_15_in_gui_controls);
|
||||
draw_text(window, r, 0, 0, label);
|
||||
float y = r.y + offset_10_in_gui_controls + offset_neg_2_in_gui_controls_b;
|
||||
double half = (double)r.w * factor_0_5_in_gui_controls;
|
||||
float x0 = (float)((double)r.x + half);
|
||||
float tw = (float)(half + offset_neg_2_in_gui_controls);
|
||||
R16g track = make_rect(x0, y, tw, 4.0f);
|
||||
float k = mouse_over_rect_r(gui, r) ? gui_control_hover_grey_level : gui_control_idle_grey_level;
|
||||
V4g kc = nvg_rgba_f(k, k, k, 1.0f);
|
||||
nvg_FillColor(nvg, &kc);
|
||||
float rnd = two_in_gui_controls;
|
||||
draw_rect_rounded(window, track, rnd);
|
||||
float range = hi - lo;
|
||||
float t0 = limit_rotation_delta(0.0f, clamp_bound_1_in_gui_controls, (*vlo - lo) / range);
|
||||
float t1 = limit_rotation_delta(0.0f, clamp_bound_1_in_gui_controls, (*vhi - lo) / range);
|
||||
float dt = (float)((double)(float)(window->t - an->last) / divisor_0_1_in_gui_controls_a);
|
||||
an->p0 = lerp_clamped_float(an->p0, t0, dt);
|
||||
an->p1 = lerp_clamped_float(an->p1, t1, dt);
|
||||
an->last = window->t;
|
||||
float x1 = x0 + tw;
|
||||
float a = lerp_unclamped_float(x0, x1, an->p0);
|
||||
float b = lerp_unclamped_float(x0, x1, an->p1);
|
||||
R16g fill = make_rect(a, y, b - a, 4.0f);
|
||||
V4g acc = *(V4g *)&gui->accent_color;
|
||||
nvg_FillColor(nvg, &acc);
|
||||
draw_rect_rounded(window, fill, rnd);
|
||||
float ky = rnd + y, rad = gui_range_slider_knob_radius;
|
||||
nvg_BeginPath(nvg);
|
||||
nvg_Circle(nvg, a, ky, rad);
|
||||
nvg_Fill(nvg);
|
||||
nvg_BeginPath(nvg);
|
||||
nvg_Circle(nvg, b, ky, rad);
|
||||
nvg_Fill(nvg);
|
||||
float mid = (float)(int)((double)(a + b) * factor_0_5_in_gui_controls);
|
||||
float d = mid - x0;
|
||||
float pad = gui_range_slider_padding;
|
||||
R16g hl = make_rect(rect_extent_neg_6 + x0, r.y, d + pad, r.h);
|
||||
R16g hh = make_rect(mid, r.y, tw - d + pad, r.h);
|
||||
const char *text;
|
||||
int tx;
|
||||
bool changed;
|
||||
if (draw_slider_track_local(gui, track, hl, lo, hi, step, scale, &an->low_knob_active, vlo)) {
|
||||
if (*vlo > *vhi) *vhi = *vlo;
|
||||
text = val_text(gui, vlo, fmt, scale);
|
||||
tx = (int)a;
|
||||
changed = true;
|
||||
} else if (draw_slider_track_local(gui, track, hh, lo, hi, step, scale, &an->high_knob_active, vhi)) {
|
||||
if (*vlo > *vhi) *vlo = *vhi;
|
||||
text = val_text(gui, vhi, fmt, scale);
|
||||
tx = (int)b;
|
||||
changed = true;
|
||||
} else {
|
||||
if (!mouse_over_rect_r(gui, r)) return false;
|
||||
if (gui->click_capture == 1 && !an->low_knob_active && an->high_knob_active != 1) return false;
|
||||
float l = *vlo, h = *vhi;
|
||||
float ls = scale == 0.0f ? l : scale * l;
|
||||
float hs = scale == 0.0f ? h : scale * h;
|
||||
libc_snprintf(range_label_buffer, 0x40, rfmt ? rfmt : fmt_float_2dp_float_2dp, (double)ls, (double)hs);
|
||||
text = range_label_buffer;
|
||||
tx = (int)((double)(hl.x + hl.w + hh.x) * factor_0_5_in_gui_controls);
|
||||
changed = false;
|
||||
}
|
||||
gui->toast_visible = 1;
|
||||
unsigned long long tn = libc_strlen(text);
|
||||
if (tn > 0x65) tn = 0x65;
|
||||
libc_memcpy(gui->toast_text, text, tn);
|
||||
gui->toast_text[tn] = 0;
|
||||
gui->toast_x = (float)tx;
|
||||
gui->toast_y = (float)(int)y;
|
||||
return changed;
|
||||
}
|
||||
|
||||
// ---- dropdown: expands in place (< 6 items) or opens the "Select item" search popup; optional dropdowns
|
||||
// (index 0 = none) get a clear cross ----
|
||||
struct DDrop { char padding_00[8]; double last; float h; unsigned char open; };
|
||||
FN(void, nvg_StrokeColor, (void *nvg, V4g *c));
|
||||
FN(void, nvg_StrokeWidth, (void *nvg, float w));
|
||||
FN(void, draw_rect_stroked, (GW14 *window, R16g r, float round));
|
||||
FN(V4g, nvg_trans_rgba_f, (V4g c, float k));
|
||||
FN(void, nvg_MoveTo, (void *nvg, float x, float y));
|
||||
FN(void, nvg_LineTo, (void *nvg, float x, float y));
|
||||
FN(void, nvg_Stroke, (void *nvg));
|
||||
FN(void, open_list_popup, (GuiState *gui, const char *title, void *sel, void *filter, void *pick, void *items, unsigned long long n));
|
||||
FN(void, text_filter_alnum, (char *buf, char character));
|
||||
FN(void, select_index, (int *out, int i, void *sel));
|
||||
extern "C" const float twenty_in_gui_controls_a, colour_level_0_866667_in_gui_controls_a, font_size_15_in_gui_controls_a, colour_alpha_0_5_in_gui_controls, colour_level_0_25098_in_gui_controls_a, stroke_width_2_in_gui_controls_a,
|
||||
corner_radius_10_in_gui_controls_a, colour_level_0_933333_in_gui_controls, colour_level_0_345098_in_gui_controls, colour_level_1_in_gui_controls_a, colour_level_0_6_in_gui_controls, colour_level_0_733333_in_gui_controls;
|
||||
extern "C" const double divisor_0_1_in_gui_controls_b, factor_0_5_in_gui_controls_a, factor_0_3_in_gui_controls;
|
||||
extern "C" const char str_select_item[]; // "Select item"
|
||||
static inline __attribute__((always_inline)) V4g grey14(float k) { return nvg_rgba_f(k, k, k, 1.0f); }
|
||||
extern "C" unsigned char draw_dropdown_row(GuiState *gui, GW14 *window, const char *label, DDrop *dd, unsigned n, const char **items,
|
||||
int *sel, bool opt) {
|
||||
void *nvg = window->ig;
|
||||
float dt = (float)(window->t - dd->last);
|
||||
float target = dd->open ? (float)(28 * n) : twenty_in_gui_controls_a;
|
||||
float h = lerp_clamped_float(dd->h, target, (float)((double)dt / divisor_0_1_in_gui_controls_b));
|
||||
dd->h = h;
|
||||
dd->last = window->t;
|
||||
R16g r = add_cell(gui, h);
|
||||
R16g lr = r;
|
||||
lr.h = 20.0f;
|
||||
V4g tc = grey14(colour_level_0_866667_in_gui_controls_a);
|
||||
nvg_FillColor(nvg, &tc);
|
||||
nvg_FontSize(nvg, font_size_15_in_gui_controls_a);
|
||||
draw_text(window, lr, 0, 0, label);
|
||||
R16g box = make_rect((float)((double)r.w * factor_0_5_in_gui_controls_a + (double)r.x), r.y, r.w * colour_alpha_0_5_in_gui_controls, r.h);
|
||||
V4g oc = grey14(colour_level_0_25098_in_gui_controls_a);
|
||||
nvg_StrokeColor(nvg, &oc);
|
||||
nvg_StrokeWidth(nvg, stroke_width_2_in_gui_controls_a);
|
||||
draw_rect_stroked(window, box, corner_radius_10_in_gui_controls_a);
|
||||
if (dd->open) {
|
||||
float rh = box.h;
|
||||
V4g sc = nvg_trans_rgba_f(grey14(colour_level_0_25098_in_gui_controls_a), colour_alpha_0_5_in_gui_controls);
|
||||
nvg_StrokeColor(nvg, &sc);
|
||||
if (!n) return 0;
|
||||
rh /= (float)n;
|
||||
bool notopt = !opt;
|
||||
unsigned char done = 0;
|
||||
for (unsigned long long i = 0; i != n; i++) {
|
||||
R16g row = make_rect(box.x, (float)(unsigned)i * rh + box.y, box.w, rh);
|
||||
const char *name = items[i];
|
||||
bool hov = mouse_over_rect_r(gui, row);
|
||||
unsigned s = (unsigned)*sel;
|
||||
V4g c;
|
||||
if (i == s) c = *(V4g *)&gui->accent_color;
|
||||
else if (hov) c = grey14(i == 0 || notopt ? colour_level_1_in_gui_controls_a : colour_level_0_733333_in_gui_controls);
|
||||
else c = grey14(i != 0 || notopt ? colour_level_0_866667_in_gui_controls_a : colour_level_0_6_in_gui_controls);
|
||||
nvg_FillColor(nvg, &c);
|
||||
draw_text(window, row, 1, 0, name);
|
||||
if (!(done & 1)) {
|
||||
if (clicked_inside_r_frame(gui, row)) {
|
||||
*sel = (int)i;
|
||||
dd->open = 0;
|
||||
done = 1;
|
||||
} else {
|
||||
done = 0;
|
||||
}
|
||||
} else {
|
||||
done = 1;
|
||||
}
|
||||
if (i) {
|
||||
nvg_BeginPath(nvg);
|
||||
nvg_MoveTo(nvg, row.x, row.y);
|
||||
nvg_LineTo(nvg, row.x + row.w, row.y);
|
||||
nvg_Stroke(nvg);
|
||||
}
|
||||
}
|
||||
return done;
|
||||
}
|
||||
bool small = n < 6;
|
||||
bool hov = mouse_over_rect_r(gui, box);
|
||||
int s = *sel;
|
||||
if (opt && !small) {
|
||||
if (s) {
|
||||
float y = box.y, xh = box.h, x = box.w - xh + box.x;
|
||||
R16g xr = {x, y, xh, xh};
|
||||
bool xhov = mouse_over_rect_r(gui, xr);
|
||||
V4g xc = xhov ? nvg_rgba_f(colour_level_0_933333_in_gui_controls, colour_level_0_345098_in_gui_controls, colour_level_0_345098_in_gui_controls, 1.0f) : grey14(colour_level_0_25098_in_gui_controls_a);
|
||||
nvg_StrokeColor(nvg, &xc);
|
||||
float in = (float)((double)xh * factor_0_3_in_gui_controls);
|
||||
nvg_BeginPath(nvg);
|
||||
float x0 = x + in, y0 = y + in, x1 = xh + x - in, y1 = y + xh - in;
|
||||
nvg_MoveTo(nvg, x0, y0);
|
||||
nvg_LineTo(nvg, x1, y1);
|
||||
nvg_MoveTo(nvg, x1, y0);
|
||||
nvg_LineTo(nvg, x0, y1);
|
||||
nvg_Stroke(nvg);
|
||||
if (clicked_inside_r_frame(gui, xr)) {
|
||||
*sel = 0;
|
||||
s = 0;
|
||||
} else {
|
||||
s = *sel;
|
||||
}
|
||||
hov = hov & !xhov;
|
||||
} else {
|
||||
s = 0;
|
||||
}
|
||||
}
|
||||
if (!n) return 0;
|
||||
V4g c;
|
||||
if (opt && s == 0) c = grey14(hov ? colour_level_0_733333_in_gui_controls : colour_level_0_6_in_gui_controls);
|
||||
else c = grey14(hov ? colour_level_1_in_gui_controls_a : colour_level_0_866667_in_gui_controls_a);
|
||||
nvg_FillColor(nvg, &c);
|
||||
if ((unsigned)s >= n) s = 0;
|
||||
draw_text(window, box, 1, 0, items[(unsigned)s]);
|
||||
if (clicked_inside_r_frame(gui, box)) {
|
||||
if (n >= 6) open_list_popup(gui, str_select_item, sel, (void *)text_filter_alnum, (void *)select_index, items + 1, n - 1);
|
||||
else dd->open = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// GUI: small helpers - text buffers and formatting.
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/gui_state.h"
|
||||
extern "C" void select_index(int *out, int i, void *sel) { if (sel) *out = i + 1; }
|
||||
struct C12 { long long a; int b; };
|
||||
extern "C" void copy_pair12(C12 *o, const C12 *i) { o->b = i->b; o->a = i->a; }
|
||||
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
extern "C" void text_filter_alnum(char *buf, char character) {
|
||||
unsigned long long n = libc_strlen(buf);
|
||||
if (n > 99) return;
|
||||
unsigned char u = (unsigned char)((character & 0xdf) - 0x41);
|
||||
asm("" : "+r"(u));
|
||||
if (u >= 0x1a) {
|
||||
unsigned char d = (unsigned char)(character - 0x30);
|
||||
asm("" : "+r"(d));
|
||||
unsigned char notdigit = d >= 10;
|
||||
asm("" : "+r"(notdigit));
|
||||
unsigned char notspace = character != 0x20;
|
||||
asm("" : "+r"(notspace));
|
||||
if (notdigit & notspace) return;
|
||||
}
|
||||
buf[n] = character;
|
||||
}
|
||||
|
||||
// ---- colour setting row: label, swatch (colour fading to 25 % alpha), click opens the colour picker ----
|
||||
struct R15 { float x, y, w, h; };
|
||||
struct V15 { float x, y, z, w; };
|
||||
struct Grad15 { float v[19]; };
|
||||
struct Col15 { float r, g, b; };
|
||||
struct GW15 { void *ig; double t; };
|
||||
FN(R15, add_cell, (GuiState *gui, float h));
|
||||
FN(V15, nvg_rgba_f, (float r, float g, float b, float a));
|
||||
FN(V15, nvg_rgb_f, (float r, float g, float b));
|
||||
FN(void, nvg_FillColor, (void *nvg, V15 *c));
|
||||
FN(void, nvg_StrokeColor, (void *nvg, V15 *c));
|
||||
FN(void, nvg_StrokeWidth, (void *nvg, float w));
|
||||
FN(void, nvg_FontSize, (void *nvg, float size));
|
||||
FN(float, draw_text, (GW15 *window, R15 r, int align, int col, const char *text));
|
||||
FN(R15, make_rect, (float x, float y, float w, float h));
|
||||
FN(void, draw_rect_stroked, (GW15 *window, R15 r, float round));
|
||||
FN(void, draw_rect_rounded, (GW15 *window, R15 r, float round));
|
||||
FN(Grad15, nvg_linear_gradient, (void *nvg, float x0, float y0, float x1, float y1, V15 c0, V15 c1));
|
||||
FN(void, nvg_FillPaint, (void *nvg, Grad15 g));
|
||||
FN(unsigned char, clicked_inside_r_frame, (GuiState *gui, R15 r));
|
||||
FN(void, open_colour_picker_colour_c, (GuiState *gui, const char *label, Col15 *col, const Col15 *c, void *cb));
|
||||
extern "C" const float row_height_20_in_gui_helpers, colour_level_0_866667_in_gui_helpers, font_size_15_in_gui_helpers, rect_extent_neg_40_in_gui_helpers, rect_extent_40_in_gui_helpers, colour_level_0_25098_in_gui_helpers,
|
||||
stroke_width_2_in_gui_helpers, ten_in_gui_helpers, factor_0_5_in_gui_helpers_b;
|
||||
extern "C" const double factor_0_5_in_gui_helpers_a;
|
||||
extern "C" unsigned char draw_color_swatch_row(GuiState *gui, GW15 *window, const char *label, Col15 *col) {
|
||||
void *nvg = window->ig;
|
||||
R15 r = add_cell(gui, row_height_20_in_gui_helpers);
|
||||
R15 lr = r;
|
||||
lr.h = 20.0f;
|
||||
V15 tc = nvg_rgba_f(colour_level_0_866667_in_gui_helpers, colour_level_0_866667_in_gui_helpers, colour_level_0_866667_in_gui_helpers, 1.0f);
|
||||
nvg_FillColor(nvg, &tc);
|
||||
nvg_FontSize(nvg, font_size_15_in_gui_helpers);
|
||||
draw_text(window, lr, 0, 0, label);
|
||||
R15 sw = make_rect(r.x + r.w + rect_extent_neg_40_in_gui_helpers, r.y, rect_extent_40_in_gui_helpers, 20.0f);
|
||||
V15 oc = nvg_rgba_f(colour_level_0_25098_in_gui_helpers, colour_level_0_25098_in_gui_helpers, colour_level_0_25098_in_gui_helpers, 1.0f);
|
||||
nvg_StrokeColor(nvg, &oc);
|
||||
nvg_StrokeWidth(nvg, stroke_width_2_in_gui_helpers);
|
||||
float rnd = ten_in_gui_helpers;
|
||||
draw_rect_stroked(window, sw, rnd);
|
||||
float x0 = sw.x, y0 = sw.y, x1 = sw.w + sw.x;
|
||||
V15 c0 = nvg_rgb_f(col->r, col->g, col->b);
|
||||
V15 c1 = nvg_rgba_f(col->r, col->g, col->b, 0.25f);
|
||||
nvg_FillPaint(nvg, nvg_linear_gradient(nvg, x0, y0, x1, y0, c0, c1));
|
||||
draw_rect_rounded(window, sw, rnd);
|
||||
R15 cr = make_rect((float)((double)r.w * factor_0_5_in_gui_helpers_a + (double)r.x), r.y, r.w * factor_0_5_in_gui_helpers_b, r.h);
|
||||
unsigned char clicked = clicked_inside_r_frame(gui, cr);
|
||||
if (clicked) {
|
||||
Col15 c = *col;
|
||||
open_colour_picker_colour_c(gui, label, col, &c, (void *)copy_pair12);
|
||||
}
|
||||
return clicked;
|
||||
}
|
||||
|
||||
// ---- list setting: one row per entry (name, colour swatch for colour lists, remove cross), "Click to add" ----
|
||||
struct ListOps {
|
||||
void *ctx;
|
||||
unsigned long long (*count)(void *ctx);
|
||||
const char *(*name)(void *ctx, unsigned long long i);
|
||||
Col15 *(*color)(void *ctx, unsigned long long i);
|
||||
void (*add)(void *ctx, const char *name, Col15 *color);
|
||||
void (*remove)(void *ctx, unsigned long long i);
|
||||
};
|
||||
FN(float, draw_text_sized, (GW15 *window, R15 r, unsigned align, unsigned col, const char *text, const char *end));
|
||||
FN(R15, inset_rect, (R15 r, float d));
|
||||
FN(V15, nvg_trans_rgba_f, (V15 c, float k));
|
||||
FN(bool, mouse_over_rect_r, (GuiState *gui, R15 r));
|
||||
FN(void, nvg_BeginPath, (void *nvg));
|
||||
FN(void, nvg_MoveTo, (void *nvg, float x, float y));
|
||||
FN(void, nvg_LineTo, (void *nvg, float x, float y));
|
||||
FN(void, nvg_Stroke, (void *nvg));
|
||||
FN(void, pick_add_highlight, (void *p, void *, void *key));
|
||||
FN(void, pick_add_shadow, (void *p, void *key));
|
||||
FN(void, open_list_popup, (GuiState *gui, void *object, void *other_object, void *c, void *d0, void *d1, void *e));
|
||||
FN(void, start_timed_popup_drag_payload, (GuiState *gui, void *object, void *other_object, void *c, void *d));
|
||||
extern "C" const float colour_level_0_866667_in_gui_helpers_a, font_size_15_in_gui_helpers_a, colour_alpha_0_5_in_gui_helpers_a, colour_level_0_25098_in_gui_helpers_a, stroke_width_2_in_gui_helpers_a, corner_radius_10_in_gui_helpers_a,
|
||||
inset_4, corner_radius_5_in_gui_helpers, colour_level_0_933333_in_gui_helpers, colour_level_0_345098_in_gui_helpers, gui_list_add_idle_grey_level, gui_list_add_hover_grey_level;
|
||||
extern "C" const double factor_0_5_in_gui_helpers, factor_0_3_in_gui_helpers;
|
||||
extern "C" const char str_click_to_add[]; // "Click to add"
|
||||
extern "C" bool draw_list_editor_row(GuiState *gui, GW15 *window, const char *label, void *title, void *filter, int kind, ListOps *sel, void *items,
|
||||
void *p9) {
|
||||
void *nvg = window->ig;
|
||||
unsigned long long n = sel->count(sel->ctx);
|
||||
R15 r = add_cell(gui, (float)(n + 27 * (n + 1) + 1));
|
||||
R15 lr = r;
|
||||
lr.h = 20.0f;
|
||||
V15 tc = nvg_rgba_f(colour_level_0_866667_in_gui_helpers_a, colour_level_0_866667_in_gui_helpers_a, colour_level_0_866667_in_gui_helpers_a, 1.0f);
|
||||
nvg_FillColor(nvg, &tc);
|
||||
nvg_FontSize(nvg, font_size_15_in_gui_helpers_a);
|
||||
draw_text(window, lr, 0, 0, label);
|
||||
R15 box = make_rect((float)((double)r.w * factor_0_5_in_gui_helpers + (double)r.x), r.y, r.w * colour_alpha_0_5_in_gui_helpers_a, r.h);
|
||||
V15 oc = nvg_rgba_f(colour_level_0_25098_in_gui_helpers_a, colour_level_0_25098_in_gui_helpers_a, colour_level_0_25098_in_gui_helpers_a, 1.0f);
|
||||
nvg_StrokeColor(nvg, &oc);
|
||||
nvg_StrokeWidth(nvg, stroke_width_2_in_gui_helpers_a);
|
||||
draw_rect_stroked(window, box, corner_radius_10_in_gui_helpers_a);
|
||||
float rh = box.h / (float)(n + 1);
|
||||
bool changed = false;
|
||||
if (n) {
|
||||
unsigned long long del = ~0ull;
|
||||
float dim = colour_level_0_25098_in_gui_helpers_a;
|
||||
for (unsigned long long i = 0; i != n; i++) {
|
||||
R15 row = make_rect(box.x, (float)i * rh + box.y, box.w, rh);
|
||||
const char *name = sel->name(sel->ctx, i);
|
||||
unsigned long long len = libc_strlen(name);
|
||||
if (len >= 0x14) len = 0x14;
|
||||
V15 nc = nvg_rgba_f(colour_level_0_866667_in_gui_helpers_a, colour_level_0_866667_in_gui_helpers_a, colour_level_0_866667_in_gui_helpers_a, 1.0f);
|
||||
nvg_FillColor(nvg, &nc);
|
||||
draw_text_sized(window, row, 1, 0, name, name + len);
|
||||
if (kind == 1) {
|
||||
R15 sw = inset_rect(R15{row.x, row.y, row.h, row.h}, inset_4);
|
||||
Col15 *col = sel->color(sel->ctx, i);
|
||||
V15 cc = nvg_rgb_f(col->r, col->g, col->b);
|
||||
nvg_FillColor(nvg, &cc);
|
||||
draw_rect_rounded(window, sw, corner_radius_5_in_gui_helpers);
|
||||
if (clicked_inside_r_frame(gui, sw)) {
|
||||
Col15 c = *col;
|
||||
open_colour_picker_colour_c(gui, name, col, &c, (void *)copy_pair12);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
float y = row.y, h = row.h, x = row.w - h + row.x;
|
||||
R15 xr = {x, y, h, h};
|
||||
V15 xc = mouse_over_rect_r(gui, xr) ? nvg_rgba_f(colour_level_0_933333_in_gui_helpers, colour_level_0_345098_in_gui_helpers, colour_level_0_345098_in_gui_helpers, 1.0f)
|
||||
: nvg_rgba_f(dim, dim, dim, 1.0f);
|
||||
nvg_StrokeColor(nvg, &xc);
|
||||
float in = (float)((double)h * factor_0_3_in_gui_helpers);
|
||||
nvg_BeginPath(nvg);
|
||||
float x0 = x + in, y0 = y + in, x1 = h + x - in, y1 = y + h - in;
|
||||
nvg_MoveTo(nvg, x0, y0);
|
||||
nvg_LineTo(nvg, x1, y1);
|
||||
nvg_MoveTo(nvg, x1, y0);
|
||||
nvg_LineTo(nvg, x0, y1);
|
||||
nvg_Stroke(nvg);
|
||||
if (clicked_inside_r_frame(gui, xr)) del = i;
|
||||
V15 sc = nvg_trans_rgba_f(nvg_rgba_f(dim, dim, dim, 1.0f), colour_alpha_0_5_in_gui_helpers_a);
|
||||
nvg_StrokeColor(nvg, &sc);
|
||||
nvg_BeginPath(nvg);
|
||||
nvg_MoveTo(nvg, row.x, row.y + row.h);
|
||||
nvg_LineTo(nvg, row.x + row.w, row.y + row.h);
|
||||
nvg_Stroke(nvg);
|
||||
}
|
||||
if (del != ~0ull) {
|
||||
sel->remove(sel->ctx, del);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
R15 add = make_rect(box.x, (float)n * rh + box.y, box.w, rh);
|
||||
float k = mouse_over_rect_r(gui, add) ? gui_list_add_hover_grey_level : gui_list_add_idle_grey_level;
|
||||
V15 ac = nvg_rgba_f(k, k, k, 1.0f);
|
||||
nvg_FillColor(nvg, &ac);
|
||||
draw_text(window, add, 1, 0, str_click_to_add);
|
||||
if (clicked_inside_r_frame(gui, add)) {
|
||||
if (items) open_list_popup(gui, title, sel, filter, (void *)pick_add_highlight, items, p9);
|
||||
else start_timed_popup_drag_payload(gui, title, sel, filter, (void *)pick_add_shadow);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
// 0x10000: GUI window helpers
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/gui_state.h"
|
||||
FN(void, nvg_FontFaceId, (void *imgui, unsigned col));
|
||||
struct GCol { void *imgui; char padding_08[0x38]; unsigned col[1]; };
|
||||
extern "C" void set_draw_color(GCol *g, unsigned i) {
|
||||
void *nvg = g->imgui;
|
||||
|
||||
nvg_FontFaceId(nvg, g->col[i]);
|
||||
}
|
||||
extern "C" void reset_layout_columns(GuiState *gui) { gui->layout_row_depth = 0; gui->layout_column_count = 0; }
|
||||
|
||||
FN(void, nvg_BeginPath, (void *nvg));
|
||||
FN(void, nvg_Rect, (void *nvg, float x, float y, float w, float h));
|
||||
FN(void, nvg_Fill, (void *nvg));
|
||||
extern "C" void draw_rect_filled(void **w, const float *r) {
|
||||
void *nvg = w[0];
|
||||
nvg_BeginPath(nvg);
|
||||
float x = r[0];
|
||||
float y = r[1];
|
||||
float w2 = r[2];
|
||||
float h = r[3];
|
||||
nvg_Rect(nvg, x, y, w2, h);
|
||||
nvg_Fill(nvg);
|
||||
}
|
||||
|
||||
FN(void, nvg_RoundedRect, (void *nvg, float x, float y, float w, float h, float round));
|
||||
FN(void, nvg_Stroke, (void *nvg));
|
||||
extern "C" void draw_rect_rounded(void **w, const float *r, float round) {
|
||||
float x = r[0], y = r[1], ww = r[2], h = r[3];
|
||||
void *nvg = w[0];
|
||||
nvg_BeginPath(nvg);
|
||||
nvg_RoundedRect(nvg, x, y, ww, h, round);
|
||||
nvg_Fill(nvg);
|
||||
}
|
||||
extern "C" void draw_rect_stroked(void **w, const float *r, float round) {
|
||||
float x = r[0], y = r[1], ww = r[2], h = r[3];
|
||||
void *nvg = w[0];
|
||||
nvg_BeginPath(nvg);
|
||||
nvg_RoundedRect(nvg, x, y, ww, h, round);
|
||||
nvg_Stroke(nvg);
|
||||
}
|
||||
|
||||
struct R16e { float x, y, w, h; };
|
||||
struct V4e { float x, y, z, w; };
|
||||
FN(void, push_layout_row, (GuiState *gui, R16e *r, float sp, float pad));
|
||||
FN(R16e, round_rect, (R16e r));
|
||||
FN(void, nvg_Save, (void *nvg));
|
||||
FN(void, nvg_Scissor, (void *nvg, float x, float y, float w, float h));
|
||||
typedef int v4ie __attribute__((vector_size(16)));
|
||||
typedef float v4fe __attribute__((vector_size(16)));
|
||||
extern "C" const v4ie gui_layout_sign_mask; // sign mask
|
||||
// begin a child region: push it, round the rect to whole pixels and clip to its padded inside
|
||||
extern "C" void begin_content(void **w, GuiState *gui, R16e *r, float pad, float f) {
|
||||
void *nvg = w[0];
|
||||
// the original round-trips a stack copy of the rect: push_layout_row clips and insets the copy to build
|
||||
// the pushed row while the caller's rect stays raw, then the raw rect is rounded and reused for
|
||||
// the scissor - whose left/top edge sits at the card edge, not at the already-inset row. Scissoring
|
||||
// the inset row shaved the first glyph off every label ("mode" -> "ode").
|
||||
R16e local = *r;
|
||||
push_layout_row(gui, &local, pad, f);
|
||||
gui->rows[gui->layout_row_depth - 1].clip = 1;
|
||||
*r = round_rect(*r);
|
||||
nvg_Save(nvg);
|
||||
v4fe v = {pad, 0, 0, 0};
|
||||
float np = ((v4fe)((v4ie)v ^ gui_layout_sign_mask))[0] - pad;
|
||||
nvg_Scissor(nvg, r->x + pad, r->y + pad, r->w + np, np + r->h);
|
||||
}
|
||||
FN(float, libc_sinf, (float x)); // sinf
|
||||
FN(V4e, nvg_trans_rgba_f, (V4e c, float k));
|
||||
FN(void, nvg_StrokeColor, (void *nvg, V4e *c));
|
||||
FN(void, nvg_StrokeWidth, (void *nvg, float w));
|
||||
FN(void, nvg_MoveTo, (void *nvg, float x, float y));
|
||||
FN(void, nvg_LineTo, (void *nvg, float x, float y));
|
||||
extern "C" const float half_constant;
|
||||
struct WT { void *ig; double t; };
|
||||
// pulsing vertical accent line at the left edge of r
|
||||
extern "C" void draw_accent_line(WT *w, R16e *r, V4e *col, float speed, float width) {
|
||||
void *nvg = w->ig;
|
||||
float s = libc_sinf((float)((double)speed * w->t));
|
||||
float k = half_constant;
|
||||
k = s * k + k;
|
||||
nvg_BeginPath(nvg);
|
||||
V4e c = nvg_trans_rgba_f(*col, k);
|
||||
nvg_StrokeColor(nvg, &c);
|
||||
nvg_StrokeWidth(nvg, width);
|
||||
nvg_MoveTo(nvg, r->x, r->y);
|
||||
nvg_LineTo(nvg, r->x, r->y + r->h);
|
||||
nvg_Stroke(nvg);
|
||||
}
|
||||
|
||||
extern "C" const float minus_one_in_gui_layout_a;
|
||||
// set the column count of the current layout row (finishing the previous row's height)
|
||||
typedef GuiCell Cell;
|
||||
typedef GuiLayoutRow Row;
|
||||
extern "C" void pop_layout_row(GuiState *gui, int n) {
|
||||
Row *row = &gui->rows[gui->layout_row_depth - 1];
|
||||
unsigned long long k = row->items;
|
||||
if (k) {
|
||||
k--;
|
||||
// the compiled binary here reloads only one cell, (items-1)&3 i.e. the last added one: its
|
||||
// "max over the last visual row" loop re-maxes that same value into a register constant and
|
||||
// moves it - dead straight-lining. Two columns therefore never cross-balance their bottoms.
|
||||
Cell *c = &row->cell[k & 3];
|
||||
float m = c->y + c->h;
|
||||
m = m > minus_one_in_gui_layout_a ? m : minus_one_in_gui_layout_a;
|
||||
if (m != minus_one_in_gui_layout_a) row->bottom = m + row->y0;
|
||||
}
|
||||
int cols = n < 4 ? n : 4;
|
||||
if (!n) cols = 1;
|
||||
row->cols = cols;
|
||||
row->items = 0;
|
||||
}
|
||||
FN(void, nvg_TextAlign, (void *nvg, unsigned font));
|
||||
FN(float, nvg_text, (void *nvg, float x, float y, void *text, void *end));
|
||||
extern "C" const unsigned text_align_flags_table[];
|
||||
extern "C" const float offset_1_in_gui_layout_c;
|
||||
extern "C" const double factor_0_5_in_gui_layout_a; // 0.5
|
||||
// text in rect r: align 0 = left, 1 = centre, 2 = right; vertically centred
|
||||
extern "C" void draw_text_sized(GCol *window, R16e *r, unsigned align, unsigned col, void *text, void *end) {
|
||||
set_draw_color(window, col);
|
||||
void *nvg = window->imgui;
|
||||
nvg_TextAlign(nvg, text_align_flags_table[align]);
|
||||
float x = r->x;
|
||||
float y = r->y + offset_1_in_gui_layout_c;
|
||||
r->y = y;
|
||||
if (align != 0) {
|
||||
if (align == 2) x = x + r->w;
|
||||
else x = (float)((double)r->w * factor_0_5_in_gui_layout_a + (double)x);
|
||||
}
|
||||
float yy = (float)((double)r->h * factor_0_5_in_gui_layout_a + (double)y);
|
||||
nvg_text(nvg, x, yy, text, end);
|
||||
}
|
||||
|
||||
extern "C" const float minus_one_in_gui_layout_b;
|
||||
// pick the column count so that cells of width w fit the row
|
||||
extern "C" void choose_columns(GuiState *gui, float w) {
|
||||
Row *row = &gui->rows[gui->layout_row_depth - 1];
|
||||
float avail = row->width, sp = row->y0;
|
||||
unsigned long long k = row->items;
|
||||
if (k) {
|
||||
k--;
|
||||
// same single-cell finalizer as end_layout_row/pop_layout_row, see the note there
|
||||
Cell *c = &row->cell[k & 3];
|
||||
float m = c->y + c->h;
|
||||
m = m > minus_one_in_gui_layout_b ? m : minus_one_in_gui_layout_b;
|
||||
if (m != minus_one_in_gui_layout_b) row->bottom = m + sp;
|
||||
}
|
||||
int n = (int)((avail + sp) / (w + sp));
|
||||
int cols = n < 4 ? n : 4;
|
||||
if (!n) cols = 1;
|
||||
row->cols = cols;
|
||||
row->items = 0;
|
||||
}
|
||||
extern "C" const float one_in_gui_layout_b;
|
||||
extern "C" const double factor_0_5_in_gui_layout;
|
||||
// text in rect r (read-only), returns its width
|
||||
extern "C" float draw_text(GCol *window, const R16e *r, unsigned align, unsigned col, void *text) {
|
||||
float x = r->x, y = r->y, w = r->w, h = r->h;
|
||||
set_draw_color(window, col);
|
||||
void *nvg = window->imgui;
|
||||
nvg_TextAlign(nvg, text_align_flags_table[align]);
|
||||
if (align != 0) {
|
||||
if (align == 2) x = x + w;
|
||||
else x = (float)((double)w * factor_0_5_in_gui_layout + (double)x);
|
||||
}
|
||||
y += one_in_gui_layout_b;
|
||||
float yy = (float)((double)h * factor_0_5_in_gui_layout + (double)y);
|
||||
return nvg_text(nvg, x, yy, text, 0);
|
||||
}
|
||||
|
||||
extern "C" const float minus_one_in_gui_layout;
|
||||
FN(void, nvg_Restore, (void *nvg));
|
||||
// end the current layout row: finish its height, pop its clip, grow the window height; returns the used height
|
||||
extern "C" float end_layout_row(GCol *w, GuiState *gui) {
|
||||
Row *row = &gui->rows[gui->layout_row_depth - 1];
|
||||
unsigned long long k = row->items;
|
||||
if (k) {
|
||||
k--;
|
||||
// only the last added cell ((items-1)&3) reaches the finalizer in the compiled original
|
||||
Cell *c = &row->cell[k & 3];
|
||||
float m = c->y + c->h;
|
||||
m = m > minus_one_in_gui_layout ? m : minus_one_in_gui_layout;
|
||||
if (m != minus_one_in_gui_layout) row->bottom = m + row->y0;
|
||||
}
|
||||
if (row->clip == 1) nvg_Restore(w->imgui);
|
||||
float bottom = row->bottom;
|
||||
if (bottom > (float)gui->layout_column_count) gui->layout_column_count = (int)bottom;
|
||||
gui->layout_row_depth -= 1;
|
||||
return (row->y0 + row->y0) + (bottom - row->top);
|
||||
}
|
||||
|
||||
FN(R16e, inset_rect, (R16e r, float d));
|
||||
FN(unsigned long long, offset_rect, (R16e r));
|
||||
// push a layout row: clip r to the parent row, inset it by pad, start the cursor at its corner
|
||||
extern "C" void push_layout_row(GuiState *gui, R16e *r, float sp, float pad) {
|
||||
long long n = gui->layout_row_depth;
|
||||
if (n < 0) n = 0;
|
||||
if (n >= 5) n = 4; // rows[5] is the fixed stack size
|
||||
if (n) {
|
||||
float *p = (float *)&gui->rows[n - 1];
|
||||
float px = p[0], py = p[1], pw = p[2], ph = p[3];
|
||||
float x = r->x;
|
||||
if (px > x) { r->x = px; x = px; }
|
||||
float y = r->y;
|
||||
if (py > y) { r->y = py; y = py; }
|
||||
float right = px + pw;
|
||||
if (r->w + x > right) {
|
||||
r->w = right - x;
|
||||
if (0.0f > r->w) r->w = 0.0f;
|
||||
}
|
||||
float bottom = py + ph;
|
||||
if (r->h + y > bottom) {
|
||||
r->h = bottom - y;
|
||||
if (0.0f > r->h) r->h = 0.0f;
|
||||
}
|
||||
}
|
||||
char *row = (char *)&gui->rows[n];
|
||||
gui->layout_row_depth = n + 1;
|
||||
*r = inset_rect(*r, pad);
|
||||
__builtin_memset(row, 0, 0x78);
|
||||
*(R16e *)row = *r;
|
||||
*(float *)(row + 0x10) = sp;
|
||||
*(float *)(row + 0x14) = pad;
|
||||
*(unsigned long long *)(row + 0x1c) = offset_rect(*r);
|
||||
}
|
||||
struct Img { char b[0x4c]; };
|
||||
struct Off2 { float x, y; };
|
||||
FN(Img, nvg_image_pattern, (void *nvg, float x, float y, float w, float h, int z, int tex, float f));
|
||||
FN(void, nvg_FillPaint, (void *nvg, Img im));
|
||||
extern "C" const float twenty;
|
||||
extern "C" Off2 *ptr_to_unreferenced_rodata;
|
||||
struct GImg { void *imgui; char padding_08[0x40]; int tex; float tw, th; };
|
||||
// draw texture tile `idx` scaled into r, clipped to r
|
||||
extern "C" void draw_texture(GImg *w, R16e *r, unsigned idx, float alpha) {
|
||||
void *nvg = w->imgui;
|
||||
float k = twenty;
|
||||
float sx = r->w / k, sy = r->h / k;
|
||||
Off2 *t = ptr_to_unreferenced_rodata;
|
||||
float ox = r->x - t[idx].x * sx;
|
||||
float oy = r->y - t[idx].y * sy;
|
||||
Img im = nvg_image_pattern(nvg, ox, oy, sx * w->tw, sy * w->th, 0, w->tex, alpha);
|
||||
nvg_BeginPath(nvg);
|
||||
nvg_Rect(nvg, r->x, r->y, r->w, r->h);
|
||||
nvg_FillPaint(nvg, im);
|
||||
nvg_Fill(nvg);
|
||||
}
|
||||
|
||||
// next cell of the current layout row (height h), rounded to whole pixels
|
||||
extern "C" R16e add_cell(GuiState *gui, float h) {
|
||||
long long top = gui->layout_row_depth;
|
||||
if (top <= 0) return R16e{0.0f, 0.0f, 0.0f, h};
|
||||
Row *row = &gui->rows[top - 1];
|
||||
unsigned long long cols = (unsigned long long)row->cols;
|
||||
if (!cols) {
|
||||
row->cols = 1;
|
||||
cols = 1;
|
||||
}
|
||||
unsigned long long n = row->items;
|
||||
row->items = n + 1;
|
||||
Cell *above = &row->cell[(n - cols) & 3];
|
||||
Cell *c = &row->cell[n & 3];
|
||||
if (n % cols) {
|
||||
Cell *prev = &row->cell[(n - 1) & 3];
|
||||
float pw = prev->w;
|
||||
c->x = prev->x + pw + row->y0;
|
||||
c->w = pw;
|
||||
c->y = n < cols ? row->bottom : above->y + above->h + row->y0;
|
||||
} else {
|
||||
c->x = *(float *)((char *)row + 0x1c);
|
||||
if (n < cols) {
|
||||
c->y = row->bottom;
|
||||
c->w = (row->width - (float)(cols - 1) * row->y0) / (float)cols;
|
||||
} else {
|
||||
c->y = above->y + above->h + row->y0;
|
||||
c->w = above->w;
|
||||
}
|
||||
}
|
||||
c->h = h;
|
||||
return round_rect(*(R16e *)c);
|
||||
}
|
||||
|
||||
// ---- input events: 1 mouse move, 2 key/button (+ up to 4 typed chars), 3 scroll; returns "consumed" ----
|
||||
FN(bool, popup_is_open, (GuiState *gui));
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
FN(void, close_popup, (GuiState *gui));
|
||||
extern "C" const float factor_neg_30;
|
||||
struct InEv { int type; union { int key; float wheel; }; union { int y; unsigned char ch[4]; }; unsigned char down; };
|
||||
typedef void (*CharFilter)(char *buf, char c, void *arg);
|
||||
extern "C" bool handle_gui_input(GuiState *gui, InEv *in) {
|
||||
int st;
|
||||
switch (in->type) {
|
||||
case 1:
|
||||
gui->mouse_raw.x = (float)in->key;
|
||||
gui->mouse_raw.y = (float)in->y;
|
||||
return false;
|
||||
case 3:
|
||||
if (!popup_is_open(gui)) gui->scroll += in->wheel * factor_neg_30;
|
||||
return false;
|
||||
case 2:
|
||||
if (in->down != 1) break;
|
||||
st = gui->input_mode;
|
||||
if ((unsigned)(st - 2) < 2) { // text field active
|
||||
int k = in->key;
|
||||
if (k > 0x54) {
|
||||
if (k == 0x55) { // enter
|
||||
if (gui->edit_text[0]) gui->edit_confirmed = 1;
|
||||
return true;
|
||||
}
|
||||
if (k == 0x57) { // escape
|
||||
gui->input_mode = 0;
|
||||
return true;
|
||||
}
|
||||
} else if (k == 1) {
|
||||
if (st != 3) {
|
||||
gui->input_mode = 0;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
} else if (k == 0x53) { // backspace
|
||||
unsigned long long n = libc_strlen(gui->edit_text);
|
||||
if (n) gui->edit_text[n - 1] = 0;
|
||||
return true;
|
||||
}
|
||||
CharFilter edit_char_filter = ((CharFilter)gui->edit_char_filter);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
char c = (char)in->ch[i];
|
||||
if (!c) return true;
|
||||
edit_char_filter = ((CharFilter)gui->edit_char_filter);
|
||||
edit_char_filter(gui->edit_text, c, gui->edit_filter_arg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (st == 1) { // key-bind capture
|
||||
int k = in->key;
|
||||
int key = (k == 1 || k == 0x57) ? 0 : k;
|
||||
((void (*)(void *, int))gui->keybind_callback)(gui->keybind_target, key);
|
||||
gui->input_mode = 0;
|
||||
return true;
|
||||
}
|
||||
if (st == 0 && popup_is_open(gui) && in->key == 0x57) {
|
||||
close_popup(gui);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
{
|
||||
int k = in->key;
|
||||
if ((unsigned)(k - 100) < 2) gui->numeric_drag_enabled = in->down;
|
||||
else if (k == 1) gui->click_capture = in->down;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- GUI render target: ImGui frame into an offscreen framebuffer scaled by `scale` ----
|
||||
struct FBuf { char padding_00[0x14]; int tex; };
|
||||
struct Rend { void *ig; double t; int w, h; float sw, sh; float scale; int padding_24; FBuf *fb; float clear[4]; };
|
||||
FN(void, nvg_wrap_delete_framebuffer, (FBuf *fb)); // destroy
|
||||
FN(FBuf *, nvglu_create_framebuffer, (void *nvg, int w, int h, int flags)); // create
|
||||
FN(void, nvg_wrap_bind_framebuffer, (FBuf *fb)); // bind (0 = default)
|
||||
FN(void, nvg_BeginFrame, (void *nvg, float w, float h, float dpi)); // begin frame
|
||||
FN(void, nvg_EndFrame, (void *nvg)); // end frame
|
||||
FN(void, nvg_Scale, (void *nvg, float x, float y));
|
||||
extern "C" void (**const ptr_to_pGlGetFloatv)(unsigned pname, float *out); // glGetFloatv
|
||||
extern "C" void (**const ptr_to_pGlClearColor)(float r, float g, float b, float a); // glClearColor
|
||||
extern "C" void (**const ptr_to_pGlClear)(unsigned mask); // glClear
|
||||
extern "C" float frame_ratio_1_in_gui_layout, frame_ratio_1_in_gui_layout_a;
|
||||
extern "C" void set_render_scale(Rend *renderer, float scale) {
|
||||
renderer->scale = scale;
|
||||
renderer->sw = (float)renderer->w / scale;
|
||||
renderer->sh = (float)renderer->h / scale;
|
||||
}
|
||||
// (re)create the framebuffer when the window size changed
|
||||
extern "C" bool resize_render_target(Rend *renderer, int w, int h) {
|
||||
bool changed;
|
||||
if (w == renderer->w && h == renderer->h) {
|
||||
changed = false;
|
||||
} else {
|
||||
if (renderer->fb) nvg_wrap_delete_framebuffer(renderer->fb);
|
||||
renderer->fb = nvglu_create_framebuffer(renderer->ig, w, h, 0);
|
||||
changed = true;
|
||||
}
|
||||
renderer->w = w;
|
||||
renderer->h = h;
|
||||
return changed;
|
||||
}
|
||||
extern "C" void begin_frame(Rend *renderer, double t) {
|
||||
float fw = (float)renderer->w;
|
||||
|
||||
void *nvg = renderer->ig;
|
||||
float fh = (float)renderer->h;
|
||||
|
||||
nvg_BeginFrame(nvg, fw, fh, frame_ratio_1_in_gui_layout);
|
||||
nvg_Scale(renderer->ig, renderer->scale, renderer->scale);
|
||||
renderer->t = t;
|
||||
}
|
||||
// begin a frame into the framebuffer (saves and clears the GL clear colour)
|
||||
extern "C" void begin_frame_framebuffer(Rend *renderer, double t) {
|
||||
nvg_wrap_bind_framebuffer(renderer->fb);
|
||||
(*ptr_to_pGlGetFloatv)(0xc22, renderer->clear); // GL_COLOR_CLEAR_VALUE
|
||||
(*ptr_to_pGlClearColor)(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
(*ptr_to_pGlClear)(0x4400); // colour | stencil
|
||||
float fw = (float)renderer->w;
|
||||
|
||||
void *nvg = renderer->ig;
|
||||
float fh = (float)renderer->h;
|
||||
|
||||
nvg_BeginFrame(nvg, fw, fh, frame_ratio_1_in_gui_layout_a);
|
||||
nvg_Scale(renderer->ig, renderer->scale, renderer->scale);
|
||||
renderer->t = t;
|
||||
}
|
||||
// draw the framebuffer texture over the screen
|
||||
extern "C" void draw_framebuffer_texture_over_screen(Rend *renderer) {
|
||||
void *nvg = renderer->ig;
|
||||
float sw = renderer->sw, sh = renderer->sh;
|
||||
|
||||
Img g = nvg_image_pattern(nvg, 0.0f, 0.0f, sw, sh, 0, renderer->fb->tex, 1.0f);
|
||||
nvg_BeginPath(nvg);
|
||||
nvg_Rect(nvg, 0.0f, 0.0f, renderer->sw, renderer->sh);
|
||||
nvg_FillPaint(nvg, g);
|
||||
nvg_Fill(nvg);
|
||||
}
|
||||
extern "C" void end_frame(Rend *renderer) { nvg_EndFrame(renderer->ig); }
|
||||
extern "C" void clear_frame(Rend *renderer) {
|
||||
nvg_EndFrame(renderer->ig);
|
||||
nvg_wrap_bind_framebuffer(0);
|
||||
void (*cc)(float, float, float, float) = *ptr_to_pGlClearColor;
|
||||
|
||||
float c0 = renderer->clear[0], c1 = renderer->clear[1], c2 = renderer->clear[2], c3 = renderer->clear[3];
|
||||
|
||||
cc(c0, c1, c2, c3);
|
||||
}
|
||||
@@ -0,0 +1,631 @@
|
||||
// 0x16000
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/gui_state.h"
|
||||
FN(void, set_popup_open, (void *x, bool b));
|
||||
extern "C" void close_popup(void *x) { set_popup_open(x, true); }
|
||||
extern "C" bool popup_is_open(GuiState *gui) { return gui->popup_kind_i != 0; }
|
||||
extern "C" unsigned char popup_is_animating(GuiState *gui) { return (unsigned char)gui->popup_animating; }
|
||||
|
||||
struct Col3 { float r, g, b; };
|
||||
struct Pick { void *ctx; char padding_08[0x18]; void (*add)(void *ctx, void *key, Col3 *c); };
|
||||
extern "C" const float one_in_gui_popups, one_in_gui_popups_a;
|
||||
extern "C" void pick_add_highlight(Pick *p, void *, void *key) {
|
||||
if (!key) return;
|
||||
void *ctx = p->ctx;
|
||||
auto add = p->add;
|
||||
|
||||
alignas(16) Col3 c = {one_in_gui_popups, 0, 0};
|
||||
add(ctx, key, &c);
|
||||
}
|
||||
extern "C" void pick_add_shadow(Pick *p, void *key) {
|
||||
if (!key) return;
|
||||
void *ctx = p->ctx;
|
||||
auto add = p->add;
|
||||
|
||||
alignas(16) Col3 c = {one_in_gui_popups_a, 0, 0};
|
||||
add(ctx, key, &c);
|
||||
}
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
// numeric text filter: digits and , - .
|
||||
extern "C" void numeric_text_filter(char *buf, char character) {
|
||||
unsigned long long n = libc_strlen(buf);
|
||||
if (n == 100) return;
|
||||
unsigned char d = (unsigned char)(character - 0x30);
|
||||
|
||||
unsigned char nd = d >= 10;
|
||||
|
||||
unsigned char e = (unsigned char)(character - 0x2c);
|
||||
|
||||
unsigned char np = e >= 3;
|
||||
|
||||
if (np & nd) return;
|
||||
buf[n] = character;
|
||||
}
|
||||
struct R16 { long long a, b; };
|
||||
FN(unsigned char, clicked_inside_r_frame, (void *gui, R16 r));
|
||||
FN(void, start_dragging_item_b, (void *gui, void *item, void *other_object));
|
||||
FN(bool, dragged_item_is, (GuiState *gui, void *item));
|
||||
extern "C" __attribute__((noinline)) bool drag_item(GuiState *gui, void *, R16 *r, void *item, void *b) {
|
||||
if (clicked_inside_r_frame(gui, *r)) start_dragging_item_b(gui, item, b);
|
||||
return dragged_item_is(gui, b);
|
||||
}
|
||||
|
||||
struct R16d { float x, y, w, h; };
|
||||
struct V4b { float x, y, z, w; };
|
||||
FN(float, draw_text, (void *window, R16d r, int a, int b, void *x));
|
||||
FN(V4b, nvg_rgba_f, (float r, float g, float b, float a));
|
||||
FN(void, draw_accent_line, (void *window, R16d r, V4b c, float a, float b));
|
||||
extern "C" const float offset_1_in_gui_popups_b, gui_popup_title_grey_level, gui_popup_title_inset_a;
|
||||
extern "C" __attribute__((noinline)) void draw_tracked_text(GuiState *gui, void *object, const R16d *rp) {
|
||||
R16d r = *rp;
|
||||
float f = draw_text(object, *rp, 1, 0, gui->edit_text) + offset_1_in_gui_popups_b;
|
||||
float k = gui_popup_title_grey_level;
|
||||
V4b c = nvg_rgba_f(k, k, k, 1.0f);
|
||||
R16d n = {f, r.y, r.w, r.h};
|
||||
draw_accent_line(object, n, c, gui_popup_title_inset_a, 1.0f);
|
||||
}
|
||||
|
||||
extern "C" const float one_in_gui_popups_c, gui_popup_text_grey_level, gui_popup_title_inset_b;
|
||||
extern "C" __attribute__((noinline)) void draw_clipped_text(GuiState *gui, void *object, const R16d *rp, int align) {
|
||||
float f = draw_text(object, *rp, align, 0, gui->edit_text);
|
||||
R16d r = *rp;
|
||||
f += one_in_gui_popups_c;
|
||||
float k = gui_popup_text_grey_level;
|
||||
V4b c = nvg_rgba_f(k, k, k, 1.0f);
|
||||
R16d n = {f, r.y, r.w, r.h};
|
||||
draw_accent_line(object, n, c, gui_popup_title_inset_b, 1.0f);
|
||||
}
|
||||
FN(void, set_popup_open, (void *x, bool b));
|
||||
FN(double, time_now, ());
|
||||
FN(void, start_dragging_item_b, (void *g, void *a, void *b));
|
||||
// start a timed popup/drag payload
|
||||
extern "C" void start_timed_popup_drag_payload(GuiState *gui, void *object, void *other_object, void *item, void *d) {
|
||||
set_popup_open(gui, true);
|
||||
double now = time_now();
|
||||
gui->popup_kind = 1;
|
||||
gui->popup_context = other_object;
|
||||
gui->popup_title = object;
|
||||
gui->popup_open_time = now;
|
||||
__builtin_memset(&gui->popup_opacity, 0, 0x10);
|
||||
gui->popup_callback = d;
|
||||
gui->popup_list_callback = item;
|
||||
__builtin_memset(&gui->popup_rgb.b, 0, 0x60);
|
||||
start_dragging_item_b(gui, item, &gui->popup_kind);
|
||||
}
|
||||
|
||||
#include <stdarg.h>
|
||||
FN(int, libc_vsnprintf, (char *buf, unsigned long long n, const char *fmt, va_list ap));
|
||||
FN(void, end_drag, (void *gui));
|
||||
extern "C" const char str_are_you_sure[]; // "Are you sure?"
|
||||
// confirmation popup with a formatted message
|
||||
extern "C" void confirmation_popup_formatted_message(GuiState *gui, void *object, void *other_object, const char *fmt, ...) {
|
||||
set_popup_open(gui, true);
|
||||
double now = time_now();
|
||||
gui->popup_kind = 3;
|
||||
gui->popup_context = object;
|
||||
gui->popup_title_text = str_are_you_sure;
|
||||
gui->popup_open_time = now;
|
||||
__builtin_memset(&gui->popup_opacity, 0, 0x10);
|
||||
gui->popup_callback = other_object;
|
||||
__builtin_memset(gui->popup_message, 0, 0x68);
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
libc_vsnprintf(gui->popup_message, 100, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
extern "C" void open_list_popup(GuiState *gui, void *object, void *other_object, void *item, void *d0, void *d1, void *e) {
|
||||
set_popup_open(gui, true);
|
||||
double now = time_now();
|
||||
gui->popup_kind = 4;
|
||||
gui->popup_context = other_object;
|
||||
gui->popup_title = object;
|
||||
gui->popup_open_time = now;
|
||||
__builtin_memset(&gui->popup_opacity, 0, 0x10);
|
||||
gui->popup_callback = item;
|
||||
gui->popup_list_callback = d0;
|
||||
gui->popup_list_items = d1;
|
||||
gui->popup_list_extra = e;
|
||||
__builtin_memset(&gui->popup_list_state, 0, 0x50);
|
||||
start_dragging_item_b(gui, item, &gui->popup_kind);
|
||||
end_drag(gui);
|
||||
}
|
||||
|
||||
// ---- popups: 1 = callback, 2 = colour picker, 3 = confirm, 4 = list picker ----
|
||||
struct Col3b { float r, g, b; };
|
||||
extern "C" unsigned long long gui_popups_recent_colour_ring_position; // recent-colour ring position
|
||||
extern "C" Col3b recent_colors_ring[8];
|
||||
struct ListV { unsigned long long n, cap; void *data; };
|
||||
static inline __attribute__((always_inline)) bool same(const Col3b &a, const Col3b &b) {
|
||||
return a.r == b.r && a.g == b.g && a.b == b.b;
|
||||
}
|
||||
// close the open popup (apply = run its callback with the result)
|
||||
FN(void, libc_free, (void *ptr));
|
||||
extern "C" __attribute__((noinline)) void set_popup_open(void *gv, bool apply) {
|
||||
GuiState *gui = (GuiState *)gv;
|
||||
gui->input_mode = 0;
|
||||
switch (gui->popup_kind_u) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
if (apply) ((void (*)(void *, void *))gui->popup_callback)(gui->popup_context, 0);
|
||||
break;
|
||||
case 2: {
|
||||
if (apply) {
|
||||
Col3b c = *(Col3b *)&gui->popup_rgb;
|
||||
((void (*)(void *, Col3b *))gui->popup_callback)(gui->popup_context, &c);
|
||||
}
|
||||
Col3b c = *(Col3b *)&gui->popup_rgb;
|
||||
unsigned long long p = gui_popups_recent_colour_ring_position;
|
||||
for (unsigned j = 0; j < 8; j++)
|
||||
if (same(c, recent_colors_ring[(p + 8 - j) & 7])) goto out;
|
||||
p = p ? p - 1 : 7;
|
||||
gui_popups_recent_colour_ring_position = p;
|
||||
recent_colors_ring[p] = c;
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
break;
|
||||
case 4: {
|
||||
if (apply) ((void (*)(void *, void *, void *))gui->popup_list_callback)(gui->popup_context, 0, 0);
|
||||
ListV *popup_list_view = ((ListV *)gui->popup_list_view);
|
||||
if (popup_list_view) {
|
||||
popup_list_view->n = 0;
|
||||
popup_list_view->cap = 0;
|
||||
libc_free(popup_list_view->data);
|
||||
popup_list_view->data = 0;
|
||||
libc_free(gui->popup_list_view);
|
||||
gui->popup_list_view = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
out:
|
||||
gui->popup_kind = 0;
|
||||
}
|
||||
extern "C" const float half_in_gui_popups, offset_2_in_gui_popups, offset_4_in_gui_popups, divisor_6_in_gui_popups; // 0.5, 2, 4, 6
|
||||
// open the colour picker for colour c (keeps the RGB and its HSL)
|
||||
extern "C" void open_colour_picker_colour_c(GuiState *gui, void *object, void *other_object, const Col3b *c, void *cb) {
|
||||
set_popup_open(gui, true);
|
||||
double now = time_now();
|
||||
Col3b in = *c;
|
||||
float r = c->r, gg = c->g, bb = c->b;
|
||||
float mx = __builtin_fmaxf(r, __builtin_fmaxf(gg, bb));
|
||||
float mn = __builtin_fminf(r, __builtin_fminf(gg, bb));
|
||||
float sum = mx + mn;
|
||||
float l = half_in_gui_popups * sum;
|
||||
float h = 0.0f, s = 0.0f;
|
||||
if (mx != mn) {
|
||||
float d = mx - mn;
|
||||
float denom = half_in_gui_popups < l ? offset_2_in_gui_popups - mx - mn : sum;
|
||||
if (mx == r) h = (gg - bb) / d + (gg < bb ? divisor_6_in_gui_popups : 0.0f);
|
||||
else if (mx == gg) h = (bb - r) / d + offset_2_in_gui_popups;
|
||||
else if (mx == bb) h = (r - gg) / d + offset_4_in_gui_popups;
|
||||
else h = l;
|
||||
s = d / denom;
|
||||
h = h / divisor_6_in_gui_popups;
|
||||
}
|
||||
gui->popup_kind = 2;
|
||||
gui->popup_context = other_object;
|
||||
gui->popup_title = object;
|
||||
gui->popup_open_time = now;
|
||||
__builtin_memset(&gui->popup_opacity, 0, 0x10);
|
||||
gui->popup_callback = cb;
|
||||
*(Col3b *)&gui->popup_rgb = in;
|
||||
gui->popup_hsl.h = h;
|
||||
gui->popup_hsl.s = s;
|
||||
gui->popup_hsl.l = l;
|
||||
__builtin_memset(&gui->popup_list_state, 0, 0x50);
|
||||
}
|
||||
|
||||
// ---- draw the open popup: title bar with close cross, then by kind (popup_kind): 1 text prompt, 2 colour
|
||||
// picker (hex field, HSL sliders, recent colours), 3 confirm, 4 searchable list. Closes it when done. ----
|
||||
struct GW16 { void *ig; double t; };
|
||||
struct Grad { float v[19]; };
|
||||
struct Hit { unsigned long long idx; const char *name; };
|
||||
FN(void, pop_layout_row, (GuiState *gui, int n));
|
||||
FN(R16d, add_cell, (GuiState *gui, float h));
|
||||
FN(void, nvg_FillColor, (void *nvg, V4b *c));
|
||||
FN(void, nvg_StrokeColor, (void *nvg, V4b *c));
|
||||
FN(void, nvg_StrokeWidth, (void *nvg, float w));
|
||||
FN(void, nvg_FontSize, (void *nvg, float size));
|
||||
FN(void, draw_rect_rounded, (GW16 *window, R16d r, float round));
|
||||
FN(void, draw_rect_stroked, (GW16 *window, R16d r, float round));
|
||||
FN(void, begin_content, (GW16 *window, GuiState *gui, R16d r, float pad, float f));
|
||||
FN(bool, mouse_over_rect_r, (GuiState *gui, R16d r));
|
||||
FN(unsigned char, point_in_rect_local, (void *gui, R16d r)) __asm__("clicked_inside_r_frame");
|
||||
FN(void, nvg_BeginPath, (void *nvg));
|
||||
FN(void, nvg_MoveTo, (void *nvg, float x, float y));
|
||||
FN(void, nvg_LineTo, (void *nvg, float x, float y));
|
||||
FN(void, nvg_Stroke, (void *nvg));
|
||||
FN(void, nvg_Fill, (void *nvg));
|
||||
FN(R16d, inset_rect, (R16d r, float d));
|
||||
FN(void *, take_dragged_item, (GuiState *gui));
|
||||
FN(char *, dragged_payload, (GuiState *gui));
|
||||
FN(R16d, make_rect, (float x, float y, float w, float h));
|
||||
FN(V4b, nvg_rgb_f, (float r, float g, float b));
|
||||
FN(V4b, nvg_hsl, (float h, float s, float l));
|
||||
FN(V4b, nvg_lerp_rgba, (V4b a, V4b b, float t));
|
||||
FN(Grad, nvg_linear_gradient, (void *nvg, float x0, float y0, float x1, float y1, V4b c0, V4b c1));
|
||||
FN(void, nvg_FillPaint, (void *nvg, Grad g));
|
||||
FN(void, nvg_RoundedRectVarying, (void *nvg, float x, float y, float w, float h, float r0, float r1, float r2, float r3));
|
||||
FN(void, nvg_Rect, (void *nvg, float x, float y, float w, float h));
|
||||
FN(void, nvg_RoundedRect, (void *nvg, float x, float y, float w, float h, float r));
|
||||
FN(float, libc_ceilf, (float x));
|
||||
FN(float, libc_floorf, (float x));
|
||||
FN(float, draw_section_label, (GuiState *gui, GW16 *window, const char *label));
|
||||
FN(bool, is_packet_cancelled, (GuiState *gui, GW16 *window, R16d r, unsigned idx, V4b *col));
|
||||
FN(void, hex_colour_text_filter, (char *buf, char character));
|
||||
FN(float, end_layout_row, (GW16 *window, GuiState *gui));
|
||||
FN(void *, libc_calloc, (unsigned long long n, unsigned long long size));
|
||||
FN(unsigned char, vector_grow, (void *vec, unsigned long long elem, unsigned long long n, void *cap));
|
||||
FN(int, libc_snprintf, (char *buf, unsigned long long n, const char *fmt, ...));
|
||||
extern "C" const float colour_level_1_in_gui_popups_d, colour_level_0_0784314_in_gui_popups, twenty_in_gui_popups, corner_radius_8_in_gui_popups_c, font_size_16_in_gui_popups, colour_level_0_866667_in_gui_popups_c, colour_level_0_6_in_gui_popups_a,
|
||||
colour_level_0_933333_in_gui_popups, colour_level_0_345098_in_gui_popups, stroke_width_2_in_gui_popups_a, row_height_30, font_size_15_in_gui_popups, colour_level_0_25098_in_gui_popups_a, inset_4_in_gui_popups_a, row_height_25,
|
||||
factor_0_5_in_gui_popups_a, colour_level_0_278431_in_gui_popups, corner_radius_5_in_gui_popups, row_height_24, colour_byte_max, gui_popup_button_hover_grey_level, divisor_6_in_gui_popups_a, offset_neg_56,
|
||||
factor_0_125_b, gui_popup_hover_grey_level;
|
||||
extern "C" const double divisor_0_3_in_gui_popups, factor_0_2_in_gui_popups, threshold_0_25, divisor_255;
|
||||
extern "C" const char str_hue[], str_saturation[], str_lightness[], str_recently_used[], str_str[], fmt_hex2_hex2_hex2[],
|
||||
str_continue[], str_d_more[];
|
||||
extern "C" char color_hex_string_buffer[];
|
||||
static inline __attribute__((always_inline)) unsigned char lower16(unsigned char c) {
|
||||
return (unsigned char)(c - 'A') < 26 ? (unsigned char)(c | 0x20) : c;
|
||||
}
|
||||
static inline __attribute__((always_inline)) unsigned char hexval(signed char c) {
|
||||
return (unsigned char)(c + (c >= 'a' ? 0xa9 : 0xd0));
|
||||
}
|
||||
// popup_rgb -> popup_hsl
|
||||
static inline __attribute__((always_inline)) void rgb_to_hsl(GuiState *gui) {
|
||||
float r = gui->popup_rgb.r, gg = gui->popup_rgb.g, bb = gui->popup_rgb.b;
|
||||
float mx = __builtin_fmaxf(__builtin_fmaxf(bb, gg), r);
|
||||
float mn = __builtin_fminf(__builtin_fminf(bb, gg), r);
|
||||
float sum = mx + mn;
|
||||
float l = factor_0_5_in_gui_popups_a * sum;
|
||||
float h = 0.0f, s = 0.0f;
|
||||
if (mx != mn) {
|
||||
float d = mx - mn;
|
||||
float denom = factor_0_5_in_gui_popups_a < l ? stroke_width_2_in_gui_popups_a - mx - mn : sum;
|
||||
if (mx == r) h = (gg - bb) / d + (gg < bb ? divisor_6_in_gui_popups_a : 0.0f);
|
||||
else if (mx == gg) h = (bb - r) / d + stroke_width_2_in_gui_popups_a;
|
||||
else if (mx == bb) h = (r - gg) / d + inset_4_in_gui_popups_a;
|
||||
else h = l;
|
||||
s = d / denom;
|
||||
h = h / divisor_6_in_gui_popups_a;
|
||||
}
|
||||
gui->popup_hsl.h = h;
|
||||
gui->popup_hsl.s = s;
|
||||
gui->popup_hsl.l = l;
|
||||
}
|
||||
// one HSL slider: gradient from c(t=0) to c(t=1) with the knob at the current value
|
||||
static inline __attribute__((always_inline)) bool hsl_bar(GuiState *gui, GW16 *window, unsigned idx, V4b c0, V4b c1, float t) {
|
||||
V4b mid = nvg_lerp_rgba(c0, c1, t);
|
||||
void *nvg = window->ig;
|
||||
R16d r = add_cell(gui, font_size_15_in_gui_popups);
|
||||
nvg_FillPaint(nvg, nvg_linear_gradient(nvg, r.x, r.y, r.w + r.x, r.h + r.y, c0, c1));
|
||||
draw_rect_rounded(window, r, corner_radius_5_in_gui_popups);
|
||||
return is_packet_cancelled(gui, window, r, idx, &mid);
|
||||
}
|
||||
extern "C" void close_popup_animated(GuiState *gui, GW16 *window) {
|
||||
gui->popup_animating = 1;
|
||||
float op = (float)((double)gui->popup_opacity + (window->t - gui->popup_open_time) / divisor_0_3_in_gui_popups);
|
||||
gui->popup_opacity = colour_level_1_in_gui_popups_d < op ? colour_level_1_in_gui_popups_d : op;
|
||||
pop_layout_row(gui, 1);
|
||||
R16d box = add_cell(gui, gui->popup_height);
|
||||
void *nvg = window->ig;
|
||||
V4b bg = nvg_rgba_f(colour_level_0_0784314_in_gui_popups, colour_level_0_0784314_in_gui_popups, colour_level_0_0784314_in_gui_popups, 1.0f);
|
||||
nvg_FillColor(nvg, &bg);
|
||||
draw_rect_rounded(window, box, twenty_in_gui_popups);
|
||||
begin_content(window, gui, box, corner_radius_8_in_gui_popups_c, 12.0f);
|
||||
pop_layout_row(gui, 1);
|
||||
void *tig = window->ig;
|
||||
R16d t = add_cell(gui, twenty_in_gui_popups);
|
||||
nvg_FontSize(tig, font_size_16_in_gui_popups);
|
||||
V4b tc = nvg_rgba_f(colour_level_0_866667_in_gui_popups_c, colour_level_0_866667_in_gui_popups_c, colour_level_0_866667_in_gui_popups_c, 1.0f);
|
||||
nvg_FillColor(tig, &tc);
|
||||
draw_text(window, t, 0, 0, gui->popup_title);
|
||||
// close cross in the title bar's right square
|
||||
float cx = t.w - t.h + t.x;
|
||||
R16d xr = {cx, t.y, t.h, t.h};
|
||||
V4b xc = mouse_over_rect_r(gui, xr) ? nvg_rgba_f(colour_level_0_933333_in_gui_popups, colour_level_0_345098_in_gui_popups, colour_level_0_345098_in_gui_popups, 1.0f)
|
||||
: nvg_rgba_f(colour_level_0_6_in_gui_popups_a, colour_level_0_6_in_gui_popups_a, colour_level_0_6_in_gui_popups_a, 1.0f);
|
||||
nvg_StrokeColor(tig, &xc);
|
||||
nvg_StrokeWidth(tig, stroke_width_2_in_gui_popups_a);
|
||||
float in = (float)((double)t.h * factor_0_2_in_gui_popups);
|
||||
nvg_BeginPath(tig);
|
||||
float x0 = cx + in, y0 = t.y + in;
|
||||
nvg_MoveTo(tig, x0, y0);
|
||||
float x1 = t.h + cx - in, y1 = t.y + t.h - in;
|
||||
nvg_LineTo(tig, x1, y1);
|
||||
nvg_MoveTo(tig, x1, y0);
|
||||
nvg_LineTo(tig, x0, y1);
|
||||
nvg_Stroke(tig);
|
||||
bool closed = point_in_rect_local(gui, R16d{cx, t.y, t.h, t.h});
|
||||
int res = 1; // 0 keep open, 1 close (cancel), 2 close (done)
|
||||
void *key = &gui->popup_kind;
|
||||
switch (gui->popup_kind_i) {
|
||||
case 1: { // text prompt -> cb(ctx, text)
|
||||
void *fig = window->ig;
|
||||
R16d f = add_cell(gui, row_height_30);
|
||||
nvg_FontSize(fig, font_size_15_in_gui_popups);
|
||||
V4b oc = nvg_rgba_f(colour_level_0_25098_in_gui_popups_a, colour_level_0_25098_in_gui_popups_a, colour_level_0_25098_in_gui_popups_a, 1.0f);
|
||||
nvg_StrokeColor(fig, &oc);
|
||||
nvg_StrokeWidth(fig, stroke_width_2_in_gui_popups_a);
|
||||
draw_rect_stroked(window, f, corner_radius_8_in_gui_popups_c);
|
||||
res = 1;
|
||||
if (!dragged_item_is(gui, key)) break;
|
||||
f = inset_rect(f, inset_4_in_gui_popups_a);
|
||||
draw_tracked_text(gui, window, &f);
|
||||
void *s = take_dragged_item(gui);
|
||||
if (!s) {
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
((void (*)(void *, void *))gui->popup_callback)(gui->popup_context, s);
|
||||
res = 2;
|
||||
break;
|
||||
}
|
||||
case 2: { // colour picker
|
||||
void *pig = window->ig;
|
||||
R16d f = add_cell(gui, row_height_24);
|
||||
bool hov = mouse_over_rect_r(gui, f);
|
||||
float fh = f.h;
|
||||
V4b sw = nvg_rgb_f(gui->popup_rgb.r, gui->popup_rgb.g, gui->popup_rgb.b);
|
||||
nvg_FillColor(pig, &sw);
|
||||
draw_rect_rounded(window, R16d{f.x, f.y, fh, fh}, corner_radius_5_in_gui_popups);
|
||||
V4b oc = nvg_rgba_f(colour_level_0_25098_in_gui_popups_a, colour_level_0_25098_in_gui_popups_a, colour_level_0_25098_in_gui_popups_a, 1.0f);
|
||||
nvg_StrokeColor(pig, &oc);
|
||||
nvg_StrokeWidth(pig, stroke_width_2_in_gui_popups_a);
|
||||
nvg_Stroke(pig);
|
||||
nvg_FontSize(pig, twenty_in_gui_popups);
|
||||
V4b hc = nvg_rgba_f(colour_level_0_6_in_gui_popups_a, colour_level_0_6_in_gui_popups_a, colour_level_0_6_in_gui_popups_a, 1.0f);
|
||||
nvg_FillColor(pig, &hc);
|
||||
float hx = f.x + fh + corner_radius_8_in_gui_popups_c;
|
||||
float hw = f.w - (f.x - hx);
|
||||
float ex = draw_text(window, R16d{hx, f.y, hw, f.h}, 0, 0, (void *)str_str /* "#" */);
|
||||
hx -= ex;
|
||||
hw -= hx;
|
||||
R16d hr = {ex, f.y, hw, f.h};
|
||||
R16 ed;
|
||||
__builtin_memcpy(&ed, &f, sizeof ed);
|
||||
if (drag_item(gui, window, &ed, (void *)hex_colour_text_filter, &gui->popup_callback)) {
|
||||
V4b wc = nvg_rgba_f(colour_level_1_in_gui_popups_d, colour_level_1_in_gui_popups_d, colour_level_1_in_gui_popups_d, 1.0f);
|
||||
nvg_FillColor(pig, &wc);
|
||||
draw_clipped_text(gui, window, &hr, 0);
|
||||
const char *s = (const char *)take_dragged_item(gui);
|
||||
if (s) {
|
||||
if (libc_strlen(s) > 5) {
|
||||
gui->popup_rgb.r =
|
||||
(float)((double)(hexval(s[0]) << 4 | hexval(s[1])) / divisor_255);
|
||||
gui->popup_rgb.g =
|
||||
(float)((double)(hexval(s[2]) << 4 | hexval(s[3])) / divisor_255);
|
||||
gui->popup_rgb.b =
|
||||
(float)((double)(hexval(s[4]) << 4 | hexval(s[5])) / divisor_255);
|
||||
}
|
||||
rgb_to_hsl(gui);
|
||||
}
|
||||
} else {
|
||||
float k = colour_byte_max;
|
||||
libc_snprintf(color_hex_string_buffer, 7, fmt_hex2_hex2_hex2 /* "%02x%02x%02x" */,
|
||||
(unsigned char)(long long)(gui->popup_rgb.r * k),
|
||||
(unsigned char)(long long)(gui->popup_rgb.g * k),
|
||||
(unsigned char)(long long)(k * gui->popup_rgb.b));
|
||||
float c = hov ? gui_popup_button_hover_grey_level : colour_level_0_6_in_gui_popups_a;
|
||||
V4b tc2 = nvg_rgba_f(c, c, c, 1.0f);
|
||||
nvg_FillColor(pig, &tc2);
|
||||
draw_text(window, hr, 0, 0, color_hex_string_buffer);
|
||||
}
|
||||
// hue: six gradient segments, rounded at the ends
|
||||
draw_section_label(gui, window, str_hue /* "Hue" */);
|
||||
void *hig = window->ig;
|
||||
R16d hb = add_cell(gui, font_size_15_in_gui_popups);
|
||||
float seg = hb.w / divisor_6_in_gui_popups_a;
|
||||
float sw2 = libc_ceilf(seg);
|
||||
for (unsigned long long k = 0; k != 6; k++) {
|
||||
float sx = libc_floorf((float)k * seg + hb.x);
|
||||
V4b c0 = nvg_hsl((float)k / divisor_6_in_gui_popups_a, gui->popup_hsl.s, gui->popup_hsl.l);
|
||||
V4b c1 = nvg_hsl((float)(k + 1) / divisor_6_in_gui_popups_a, gui->popup_hsl.s, gui->popup_hsl.l);
|
||||
Grad gr = nvg_linear_gradient(hig, sx, hb.y, seg + sx, hb.y, c0, c1);
|
||||
nvg_BeginPath(hig);
|
||||
if (k == 5) nvg_RoundedRectVarying(hig, sx, hb.y, sw2, hb.h, 0.0f, 5.0f, 5.0f, 0.0f);
|
||||
else if (k == 0) nvg_RoundedRectVarying(hig, sx, hb.y, sw2, hb.h, 5.0f, 0.0f, 0.0f, 5.0f);
|
||||
else nvg_Rect(hig, sx, hb.y, sw2, hb.h);
|
||||
nvg_FillPaint(hig, gr);
|
||||
nvg_Fill(hig);
|
||||
}
|
||||
V4b cur = nvg_hsl(gui->popup_hsl.h, gui->popup_hsl.s, gui->popup_hsl.l);
|
||||
bool ch = is_packet_cancelled(gui, window, hb, 0, &cur);
|
||||
draw_section_label(gui, window, str_saturation /* "Saturation" */);
|
||||
bool cs = hsl_bar(gui, window, 1, nvg_hsl(gui->popup_hsl.h, 0.0f, gui->popup_hsl.l),
|
||||
nvg_hsl(gui->popup_hsl.h, colour_level_1_in_gui_popups_d, gui->popup_hsl.l), gui->popup_hsl.s);
|
||||
draw_section_label(gui, window, str_lightness /* "Lightness" */);
|
||||
bool cl = hsl_bar(gui, window, 2, nvg_hsl(gui->popup_hsl.h, gui->popup_hsl.s, 0.0f),
|
||||
nvg_hsl(gui->popup_hsl.h, gui->popup_hsl.s, colour_level_1_in_gui_popups_d), gui->popup_hsl.l);
|
||||
if (ch || cs || cl) {
|
||||
V4b c = nvg_hsl(gui->popup_hsl.h, gui->popup_hsl.s, gui->popup_hsl.l);
|
||||
__builtin_memcpy(gui->popup_message, &c, 8);
|
||||
gui->popup_rgb.b = c.z;
|
||||
}
|
||||
// recently used colours (ring buffer, newest first)
|
||||
draw_section_label(gui, window, str_recently_used /* "Recently used" */);
|
||||
void *rig = window->ig;
|
||||
R16d rr = add_cell(gui, row_height_25);
|
||||
float x = rr.x;
|
||||
float sz = (rr.w + offset_neg_56) * factor_0_125_b;
|
||||
nvg_StrokeWidth(rig, stroke_width_2_in_gui_popups_a);
|
||||
float step = corner_radius_8_in_gui_popups_c + sz;
|
||||
unsigned long long pick = 8;
|
||||
for (long long i = -8; i; i++) {
|
||||
unsigned long long p = gui_popups_recent_colour_ring_position + i + 8;
|
||||
unsigned long long idx = p < 8 ? p : gui_popups_recent_colour_ring_position + i;
|
||||
R16d cell = make_rect(x, rr.y, sz, rr.h);
|
||||
float k = mouse_over_rect_r(gui, cell) ? gui_popup_hover_grey_level : colour_level_0_25098_in_gui_popups_a;
|
||||
V4b oc2 = nvg_rgba_f(k, k, k, 1.0f);
|
||||
nvg_StrokeColor(rig, &oc2);
|
||||
Col3b *e = &recent_colors_ring[idx];
|
||||
if (point_in_rect_local(gui, cell)) pick = idx;
|
||||
nvg_BeginPath(rig);
|
||||
nvg_RoundedRect(rig, cell.x, cell.y, cell.w, cell.h, 5.0f);
|
||||
V4b fc = nvg_rgb_f(e->r, e->g, e->b);
|
||||
nvg_FillColor(rig, &fc);
|
||||
nvg_Fill(rig);
|
||||
nvg_Stroke(rig);
|
||||
x += step;
|
||||
}
|
||||
if (pick != 8) {
|
||||
*(Col3b *)&gui->popup_rgb = recent_colors_ring[pick];
|
||||
rgb_to_hsl(gui);
|
||||
}
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
case 3: { // confirm -> cb(ctx)
|
||||
void *cig = window->ig;
|
||||
R16d f = add_cell(gui, row_height_30);
|
||||
nvg_FontSize(cig, font_size_15_in_gui_popups);
|
||||
V4b mc = nvg_rgba_f(colour_level_0_866667_in_gui_popups_c, colour_level_0_866667_in_gui_popups_c, colour_level_0_866667_in_gui_popups_c, 1.0f);
|
||||
nvg_FillColor(cig, &mc);
|
||||
draw_text(window, f, 0, 0, gui->popup_message);
|
||||
R16d b = add_cell(gui, row_height_30);
|
||||
V4b bc = mouse_over_rect_r(gui, b) ? nvg_rgba_f(colour_level_0_933333_in_gui_popups, colour_level_0_345098_in_gui_popups, colour_level_0_345098_in_gui_popups, 1.0f)
|
||||
: nvg_rgba_f(colour_level_0_866667_in_gui_popups_c, colour_level_0_278431_in_gui_popups, colour_level_0_278431_in_gui_popups, 1.0f);
|
||||
nvg_FillColor(cig, &bc);
|
||||
draw_rect_rounded(window, b, corner_radius_5_in_gui_popups);
|
||||
V4b wc = nvg_rgba_f(colour_level_1_in_gui_popups_d, colour_level_1_in_gui_popups_d, colour_level_1_in_gui_popups_d, 1.0f);
|
||||
nvg_FillColor(cig, &wc);
|
||||
draw_text(window, b, 1, 0, (void *)str_continue /* "Continue" */);
|
||||
res = 0;
|
||||
if (!point_in_rect_local(gui, b)) break;
|
||||
((void (*)(void *))gui->popup_callback)(gui->popup_context);
|
||||
res = 2;
|
||||
break;
|
||||
}
|
||||
case 4: { // searchable list -> cb(ctx, index, name)
|
||||
R16d f = add_cell(gui, row_height_30);
|
||||
void *lig = window->ig;
|
||||
nvg_FontSize(lig, font_size_15_in_gui_popups);
|
||||
V4b oc = nvg_rgba_f(colour_level_0_25098_in_gui_popups_a, colour_level_0_25098_in_gui_popups_a, colour_level_0_25098_in_gui_popups_a, 1.0f);
|
||||
nvg_StrokeColor(lig, &oc);
|
||||
nvg_StrokeWidth(lig, stroke_width_2_in_gui_popups_a);
|
||||
draw_rect_stroked(window, f, corner_radius_8_in_gui_popups_c);
|
||||
if (!dragged_item_is(gui, key)) break;
|
||||
f = inset_rect(f, inset_4_in_gui_popups_a);
|
||||
draw_tracked_text(gui, window, &f);
|
||||
const char *q = dragged_payload(gui);
|
||||
double now = window->t;
|
||||
if (!(threshold_0_25 > now - gui->popup_list_state)) { // rebuild the matches at most every 0.25 s
|
||||
ListV *popup_list_view = ((ListV *)gui->popup_list_view);
|
||||
if (!popup_list_view) {
|
||||
popup_list_view = (ListV *)libc_calloc(1, 0x18);
|
||||
gui->popup_list_view = popup_list_view;
|
||||
if (!popup_list_view) goto shown;
|
||||
}
|
||||
popup_list_view->n = 0;
|
||||
popup_list_view->cap = 0;
|
||||
libc_free(popup_list_view->data);
|
||||
popup_list_view->data = 0;
|
||||
gui->popup_list_count = 0;
|
||||
for (unsigned long long k = 0; k < gui->popup_list_extra_u; k++) {
|
||||
const char *it = (((const char **)gui->popup_list_items))[k];
|
||||
unsigned long long il = libc_strlen(it), ql = libc_strlen(q);
|
||||
if (il < ql) continue;
|
||||
if (ql) { // case-insensitive substring
|
||||
for (unsigned long long st = 0;; st++) {
|
||||
unsigned long long p = 0;
|
||||
while (lower16(it[st + p]) == lower16(q[p]))
|
||||
if (++p == ql) goto match;
|
||||
if (st + 1 > il - ql) goto next;
|
||||
}
|
||||
}
|
||||
match : {
|
||||
ListV *m = ((ListV *)gui->popup_list_view);
|
||||
if (m->n >= 0x1e) {
|
||||
(gui->popup_list_count)++;
|
||||
continue;
|
||||
}
|
||||
if (!vector_grow(&m->data, 0x10, m->n, &m->cap)) continue;
|
||||
Hit *h = &((Hit *)m->data)[m->n];
|
||||
h->idx = k;
|
||||
h->name = it;
|
||||
m->n++;
|
||||
}
|
||||
next:;
|
||||
}
|
||||
gui->popup_list_state = now;
|
||||
}
|
||||
shown : {
|
||||
if (!gui->popup_list_view) {
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
unsigned long long cnt = (((ListV *)gui->popup_list_view))->n;
|
||||
if (!cnt) {
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
void *tig2 = window->ig;
|
||||
nvg_FontSize(tig2, font_size_16_in_gui_popups);
|
||||
const char *chosen = 0;
|
||||
unsigned long long cidx = 0;
|
||||
R16d row = {};
|
||||
for (unsigned long long i = 0; i != cnt; i++) { // two columns
|
||||
float cx2, cw;
|
||||
if (i & 1) {
|
||||
cw = row.w * factor_0_5_in_gui_popups_a;
|
||||
cx2 = row.x + cw;
|
||||
} else {
|
||||
row = add_cell(gui, row_height_25);
|
||||
cw = row.w * factor_0_5_in_gui_popups_a;
|
||||
cx2 = row.x;
|
||||
}
|
||||
R16d cell = {cx2, row.y, cw, row.h};
|
||||
float k = mouse_over_rect_r(gui, cell) ? colour_level_1_in_gui_popups_d : colour_level_0_866667_in_gui_popups_c;
|
||||
V4b ic = nvg_rgba_f(k, k, k, 1.0f);
|
||||
nvg_FillColor(tig2, &ic);
|
||||
Hit *h = &((Hit *)(((ListV *)gui->popup_list_view))->data)[i];
|
||||
unsigned long long hi = h->idx;
|
||||
const char *hn = h->name;
|
||||
draw_text(window, cell, 0, 0, (void *)hn);
|
||||
if (point_in_rect_local(gui, cell)) {
|
||||
chosen = hn;
|
||||
cidx = hi;
|
||||
}
|
||||
}
|
||||
unsigned long long more = gui->popup_list_count;
|
||||
if (more) {
|
||||
char buf[0xe];
|
||||
libc_snprintf(buf, 0xe, str_d_more /* "+%d more..." */, more < 0x270f ? more : 0x270f);
|
||||
R16d mr = add_cell(gui, row_height_25);
|
||||
V4b mc = nvg_rgba_f(colour_level_0_6_in_gui_popups_a, colour_level_0_6_in_gui_popups_a, colour_level_0_6_in_gui_popups_a, 1.0f);
|
||||
nvg_FillColor(tig2, &mc);
|
||||
nvg_FontSize(tig2, font_size_15_in_gui_popups);
|
||||
draw_text(window, mr, 0, 0, buf);
|
||||
}
|
||||
res = 0;
|
||||
if (!chosen) break;
|
||||
((void (*)(void *, unsigned long long, const char *))gui->popup_list_callback)(gui->popup_context, cidx, chosen);
|
||||
res = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
gui->popup_height = end_layout_row(window, gui);
|
||||
gui->popup_animating = 0;
|
||||
gui->popup_open_time = window->t;
|
||||
if (res) set_popup_open(gui, res == 1);
|
||||
else if (closed) set_popup_open(gui, true);
|
||||
}
|
||||
|
||||
// ---- selectable label: rounded box (highlighted when `hot`), text centred; returns whether it was clicked ----
|
||||
extern "C" const float colour_level_0_25098_in_gui_popups, corner_radius_8_in_gui_popups, gui_popup_disabled_grey_level, gui_popup_enabled_grey_level, font_size_15_in_gui_popups_a;
|
||||
extern "C" bool draw_icon_button(GuiState *gui, GW16 *window, const char *text, bool selectable, const R16d *rect) {
|
||||
void *nvg = window->ig;
|
||||
bool clicked = selectable ? point_in_rect_local(gui, *rect) : false;
|
||||
V4b bg = nvg_rgba_f(colour_level_0_25098_in_gui_popups, colour_level_0_25098_in_gui_popups, colour_level_0_25098_in_gui_popups, 1.0f);
|
||||
nvg_FillColor(nvg, &bg);
|
||||
draw_rect_rounded(window, *rect, corner_radius_8_in_gui_popups);
|
||||
float k = selectable ? gui_popup_enabled_grey_level : gui_popup_disabled_grey_level;
|
||||
V4b fg = nvg_rgba_f(k, k, k, 1.0f);
|
||||
nvg_FillColor(nvg, &fg);
|
||||
nvg_FontSize(nvg, font_size_15_in_gui_popups_a);
|
||||
draw_text(window, *rect, 1, 0, (void *)text);
|
||||
return clicked;
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
// 0x11000: GUI state helpers (drag state at +0x370, flags at +0x4c8..)
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/gui_state.h"
|
||||
extern "C" __attribute__((noinline)) void mark_click_consumed(GuiState *gui) {
|
||||
if (gui->click_capture == 1 && gui->click_consumed == 0) { gui->click_consumed = 1; }
|
||||
|
||||
|
||||
SHARED_EPILOG();
|
||||
}
|
||||
extern "C" void begin_drag(GuiState *gui, void *object, void *other_object) {
|
||||
gui->input_mode = 1;
|
||||
gui->keybind_callback = object;
|
||||
gui->keybind_target = other_object;
|
||||
}
|
||||
extern "C" bool drag_owner_is(GuiState *gui, void *object) {
|
||||
if (gui->input_mode != 1) return false;
|
||||
return gui->keybind_target == object;
|
||||
}
|
||||
extern "C" void end_drag(GuiState *gui) {
|
||||
if (gui->input_mode == 2) gui->input_mode = 3;
|
||||
}
|
||||
extern "C" void *dragged_payload(GuiState *gui) { return (gui->input_mode_u & ~1u) == 2 ? gui->edit_text : 0; }
|
||||
extern "C" void request_unload(GuiState *gui) { gui->unload_requested = 1; }
|
||||
extern "C" void set_tab_animation(GuiState *gui, double t) {
|
||||
gui->card_open_progress_i = 0;
|
||||
gui->card_anim_start_time = t;
|
||||
}
|
||||
|
||||
extern "C" bool dragged_item_is(GuiState *gui, void *item) {
|
||||
if ((gui->input_mode_u & ~1u) != 2) return false;
|
||||
return gui->edit_filter_arg == item;
|
||||
}
|
||||
extern "C" __attribute__((noinline)) void *take_dragged_item(GuiState *gui) {
|
||||
if ((gui->input_mode_u & ~1u) != 2 || gui->edit_confirmed != 1) return 0;
|
||||
void *edit_text = gui->edit_text;
|
||||
gui->input_mode = 0;
|
||||
gui->edit_confirmed = 0;
|
||||
return edit_text;
|
||||
}
|
||||
|
||||
struct R16b { long long a, b; };
|
||||
FN(bool, popup_is_open, (GuiState *gui));
|
||||
FN(unsigned char, popup_is_animating, (GuiState *gui));
|
||||
FN(bool, point_in_rect, (R16b r, unsigned long long mouse));
|
||||
// is the mouse over rect r (and no drag in progress)?
|
||||
extern "C" __attribute__((noinline)) bool mouse_over_rect_r(GuiState *gui, const R16b *r) {
|
||||
if (popup_is_open(gui) && !popup_is_animating(gui)) return false;
|
||||
if (!point_in_rect(*r, *(unsigned long long *)&gui->mouse_scene)) return false;
|
||||
int st = gui->input_mode;
|
||||
return (st == 0) | (st == 3);
|
||||
}
|
||||
// clicked inside r this frame (consumes the click)
|
||||
extern "C" bool clicked_inside_r_frame(GuiState *gui, R16b r) {
|
||||
R16b c = r;
|
||||
|
||||
if (popup_is_open(gui) && !popup_is_animating(gui)) return false;
|
||||
if (!point_in_rect(c, *(unsigned long long *)&gui->mouse_scene)) return false;
|
||||
int st = gui->input_mode;
|
||||
if (st != 3 && st != 0) return false;
|
||||
if (gui->click_capture != 1 || gui->click_consumed != 0) return false;
|
||||
gui->click_consumed = 1;
|
||||
return true;
|
||||
}
|
||||
// start dragging item b (owner a)
|
||||
extern "C" void start_dragging_item_b(GuiState *gui, void *item, void *other_object) {
|
||||
if ((gui->input_mode_u & ~1u) == 2 && gui->edit_filter_arg == other_object) return;
|
||||
gui->input_mode = 2;
|
||||
__builtin_memset(gui->edit_text, 0, 0x80);
|
||||
gui->edit_char_filter = item;
|
||||
gui->edit_filter_arg = other_object;
|
||||
}
|
||||
|
||||
// ---- category tab bar: 7 tabs with a sliding highlight; tab kind 6 = "Unload Slinky?" ----
|
||||
struct R16t { float x, y, w, h; };
|
||||
struct V4t { float x, y, z, w; };
|
||||
struct WT3 { void *ig; double t; };
|
||||
FN(R16t, add_cell, (GuiState *gui, float h));
|
||||
FN(V4t, nvg_rgba_f, (float r, float g, float b, float a));
|
||||
FN(void, nvg_FillColor, (void *nvg, V4t *c));
|
||||
FN(void, draw_rect_rounded, (WT3 *w, const R16t *r, float round));
|
||||
FN(float, lerp_clamped_float, (float a, float b, float t));
|
||||
FN(R16t, make_rect, (float x, float y, float w, float h));
|
||||
FN(R16t, inset_rect, (R16t r, float d));
|
||||
FN(void, nvg_FontSize, (void *nvg, float size));
|
||||
FN(unsigned char, clicked_inside_r_frame_local, (GuiState *gui, R16t r)) __asm__("clicked_inside_r_frame");
|
||||
FN(float, draw_text, (WT3 *w, R16t r, int align, int col, void *text));
|
||||
FN(void, confirmation_popup_formatted_message, (void *gui, void *object, void *other_object, const char *fmt, ...));
|
||||
FN(void, request_unload, (GuiState *gui));
|
||||
extern "C" const float row_height_42, gui_panel_grey_level, corner_radius_20_in_gui_state, divisor_7, gui_selection_inset, gui_tab_highlight_grey_level, corner_radius_14,
|
||||
font_size_16, colour_level_0_866667, colour_level_1_in_gui_state, colour_level_0_278431, colour_level_0_933333, colour_level_0_345098;
|
||||
extern "C" const double divisor_0_05;
|
||||
extern "C" const char str_unload_slinky[]; // "Unload Slinky?"
|
||||
struct Tab { int kind; int padding_04; const char *label; };
|
||||
extern "C" void draw_tab_bar(GuiState *gui, WT3 *window, float dt) {
|
||||
void *nvg = window->ig;
|
||||
R16t bar = add_cell(gui, row_height_42);
|
||||
float k = gui_panel_grey_level;
|
||||
V4t c = nvg_rgba_f(k, k, k, 1.0f);
|
||||
nvg_FillColor(nvg, &c);
|
||||
draw_rect_rounded(window, &bar, corner_radius_20_in_gui_state);
|
||||
float tw = bar.w / divisor_7;
|
||||
float pos = lerp_clamped_float(gui->tab_highlight_pos, (float)gui->selected_tab,
|
||||
(float)((double)dt / divisor_0_05));
|
||||
gui->tab_highlight_pos = pos;
|
||||
float inset = gui_selection_inset;
|
||||
R16t sel = inset_rect(make_rect(bar.x + pos * tw, bar.y, tw, bar.h), inset);
|
||||
float ks = gui_tab_highlight_grey_level;
|
||||
V4t sc = nvg_rgba_f(ks, ks, ks, 1.0f);
|
||||
nvg_FillColor(window->ig, &sc);
|
||||
draw_rect_rounded(window, &sel, corner_radius_14);
|
||||
nvg_FontSize(nvg, font_size_16);
|
||||
Tab *tab = (Tab *)(gui->tabs);
|
||||
for (unsigned long long i = 0; i != 7; i++, tab++) {
|
||||
float cw = bar.w / divisor_7;
|
||||
R16t tr = make_rect((float)(long long)i * cw + bar.x, bar.y, cw, bar.h);
|
||||
if (clicked_inside_r_frame_local(gui, tr)) {
|
||||
if (tab->kind == 6) {
|
||||
confirmation_popup_formatted_message(gui, gui, (void *)request_unload, str_unload_slinky);
|
||||
} else {
|
||||
gui->selected_tab = i;
|
||||
gui->input_mode = 0;
|
||||
}
|
||||
}
|
||||
R16t in = inset_rect(tr, inset);
|
||||
V4t tc;
|
||||
bool hov = mouse_over_rect_r(gui, (const R16b *)&in);
|
||||
if (tab->kind == 6) tc = hov ? nvg_rgba_f(colour_level_0_933333, colour_level_0_345098, colour_level_0_345098, 1.0f)
|
||||
: nvg_rgba_f(colour_level_0_866667, colour_level_0_278431, colour_level_0_278431, 1.0f);
|
||||
else tc = hov ? nvg_rgba_f(colour_level_1_in_gui_state, colour_level_1_in_gui_state, colour_level_1_in_gui_state, 1.0f)
|
||||
: nvg_rgba_f(colour_level_0_866667, colour_level_0_866667, colour_level_0_866667, 1.0f);
|
||||
nvg_FillColor(window->ig, &tc);
|
||||
draw_text(window, in, 1, 0, (void *)tab->label);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- click GUI frame: window rect + open animation, scroll, tab bar, category page (module cards or the
|
||||
// profile list), toast, modal popup ----
|
||||
struct GWin { void *ig; double t; void *padding_10; float w, h, scale; };
|
||||
struct R2f { float x, y; };
|
||||
struct Prof { const char *name, *end; char desc[0x28]; int key; int padding_3c; };
|
||||
struct GEv { int type, padding_04; union { void *p; int i; } a, b; char rest[0x30]; };
|
||||
typedef void (*SetBindF)(void *mod, int key);
|
||||
typedef int v4i11 __attribute__((vector_size(16)));
|
||||
typedef float v4f11 __attribute__((vector_size(16)));
|
||||
FN(void, nvg_Save, (void *nvg));
|
||||
FN(void, nvg_Restore, (void *nvg));
|
||||
FN(void, nvg_GlobalAlpha, (void *nvg, float a));
|
||||
FN(void, nvg_Translate, (void *nvg, float x, float y));
|
||||
FN(void, nvg_Scale, (void *nvg, float x, float y));
|
||||
FN(R2f, rot_scale, (R2f a, float s));
|
||||
FN(void, card_open_animation, (GWin *window, float t));
|
||||
FN(void, reset_layout_columns, (GuiState *gui));
|
||||
FN(void, push_layout_row, (GuiState *gui, R16t r, float sp, float pad));
|
||||
FN(void, pop_layout_row, (GuiState *gui, int n));
|
||||
FN(void, draw_module_cards, (GuiState *gui, GWin *window, void *mgr, unsigned cat, float dt));
|
||||
FN(int, load_profiles, (void *profiles));
|
||||
FN(void, dispatch_event, (void *mgr, GEv *e));
|
||||
FN(void, choose_columns, (GuiState *gui, float w));
|
||||
FN(Prof *, profile_array, (void *profiles, unsigned long long *n));
|
||||
FN(bool, rects_intersect, (R16t a, R16t b));
|
||||
FN(void, begin_content, (GWin *window, GuiState *gui, R16t r, float pad, float f));
|
||||
FN(float, draw_text_sized, (GWin *window, R16t r, unsigned align, unsigned col, const char *text, const char *end));
|
||||
FN(SetBindF, profile_set_bind, (void *profiles));
|
||||
FN(void, draw_module_description, (GuiState *gui, GWin *window, SetBindF cb, void *target, int key, unsigned char *hold, R16t r));
|
||||
FN(void, ask_sure_before_running_action, (GuiState *gui, void *mgr, void *profiles, const char *name, int kind));
|
||||
FN(void, draw_texture, (GWin *window, R16t r, unsigned icon, float alpha));
|
||||
FN(void, set_slider_text, (GuiState *gui, GWin *window, const char *tip, int x, int y));
|
||||
FN(bool, get_profile_dirty, ());
|
||||
FN(const char *, get_saved_string, ());
|
||||
FN(int, libc_strcmp, (const char *left, const char *right)); // strcmp
|
||||
FN(int, load_profile_name, (const char *name, void *mgr)); // load profile
|
||||
FN(int, save_profile, (const char *name, bool text, void *mgr)); // save profile
|
||||
FN(V4t, nvg_trans_rgba_f, (V4t c, float k));
|
||||
FN(void, file_name_text_filter, (char *buf, char character));
|
||||
FN(bool, drag_item, (GuiState *gui, GWin *window, R16t r, void (*filter)(char *, char), void *b));
|
||||
FN(void, draw_tracked_text, (GuiState *gui, GWin *window, const R16t *r));
|
||||
FN(float, end_layout_row, (GWin *window, GuiState *gui));
|
||||
FN(float, ease_curve, (float t));
|
||||
FN(float, nvg_text_bounds, (void *nvg, float x, float y, const char *text, const char *end, const float *bounds)); // nvgTextBounds
|
||||
FN(void, close_popup_animated, (GuiState *gui, GWin *window));
|
||||
FN(bool, mouse_over_rect_r_local, (GuiState *gui, R16t r)) __asm__("mouse_over_rect_r");
|
||||
FN(void, draw_rect_rounded_local, (WT3 *w, R16t r, float round)) __asm__("draw_rect_rounded");
|
||||
extern "C" const float offset_neg_16, gui_wide_layout_max_width, gui_narrow_layout_max_width, half_in_gui_state, one_in_gui_state_a, row_padding_8,
|
||||
row_padding_12, gui_profiles_column_width, colour_level_0_0784314_in_gui_state, row_height_20_in_gui_state_a, font_size_16_in_gui_state, colour_level_0_866667_in_gui_state, rect_extent_5, font_size_14_in_gui_state,
|
||||
colour_level_0_6, font_size_15, gui_tab_font_size, offset_10, gui_tab_bar_vertical_offset, colour_level_0_25098_in_gui_state, threshold_600, offset_neg_600;
|
||||
extern "C" const double divisor_0_3, factor_neg_0_1, offset_2, factor_0_5_in_gui_state, offset_neg_8, divisor_0_1_in_gui_state,
|
||||
factor_neg_0_5, offset_neg_5;
|
||||
extern "C" const v4i11 gui_state_sign_abs_masks, gui_state_sign_abs_masks_second; // sign / abs masks
|
||||
extern "C" const char str_default[], str_contains_the_default_configura[], str_default_profile_cannot_be_dele[], str_default_profile_cannot_be_over[], str_profile_name_is_taken[], str_create_new_profile[];
|
||||
extern "C" const int gui_state_tooltip_strings_as_offsets_from[]; // tooltip strings as offsets from the table ("Click to load", ...)
|
||||
extern "C" const unsigned gui_state_button_icons[]; // button icons
|
||||
static inline __attribute__((always_inline)) float mask11(float x, v4i11 m, bool x_or) {
|
||||
v4f11 v = {x, 0, 0, 0};
|
||||
return ((v4f11)(x_or ? ((v4i11)v ^ m) : ((v4i11)v & m)))[0];
|
||||
}
|
||||
static inline __attribute__((always_inline)) void prof_event(void *mgr, int type, GEv *ev) {
|
||||
ev->type = type;
|
||||
dispatch_event(mgr, ev);
|
||||
}
|
||||
FN(void, nvg_Scissor, (void *nvg, float x, float y, float w, float h));
|
||||
extern "C" void draw_gui_frame(GuiState *gui, GWin *window, void *mgr, void *profiles) {
|
||||
void *nvg = window->ig;
|
||||
nvg_Save(nvg);
|
||||
card_open_animation(window, gui->card_open_progress);
|
||||
float ww = window->w;
|
||||
float pw = offset_neg_16 + ww;
|
||||
float lim = gui->wide_layout ? gui_wide_layout_max_width : gui_narrow_layout_max_width;
|
||||
pw = pw < lim ? pw : lim;
|
||||
float px = (ww - pw) * half_in_gui_state;
|
||||
float *scroll = (float *)(&gui->scroll);
|
||||
float s = *scroll;
|
||||
if (0.0f > s) *scroll = 0.0f;
|
||||
else if (s > gui->scroll_max) *scroll = gui->scroll_max;
|
||||
float one = one_in_gui_state_a;
|
||||
R2f m = rot_scale(*(R2f *)&gui->mouse_raw, one / window->scale);
|
||||
*(R2f *)&gui->mouse_scene = m;
|
||||
gui->mouse_scene.y = m.y + *scroll;
|
||||
nvg_Translate(window->ig, 0.0f, mask11(*scroll, gui_state_sign_abs_masks, true));
|
||||
reset_layout_columns(gui);
|
||||
R16t win = make_rect(px, 0.0f, pw, window->h);
|
||||
float dt = (float)(window->t - gui->card_anim_start_time);
|
||||
double dtd = dt;
|
||||
float op = (float)((double)gui->card_open_progress + dtd / divisor_0_3);
|
||||
gui->card_open_progress = one < op ? one : op;
|
||||
push_layout_row(gui, win, row_padding_8, row_padding_12);
|
||||
pop_layout_row(gui, 1);
|
||||
draw_tab_bar(gui, (WT3 *)window, dt);
|
||||
unsigned long long cat = gui->selected_tab;
|
||||
float off = (float)cat - gui->tab_highlight_pos;
|
||||
if (off != 0.0f) { // page slides while the tab highlight moves
|
||||
void *sig = window->ig;
|
||||
float d = mask11(off, gui_state_sign_abs_masks_second, false);
|
||||
nvg_GlobalAlpha(sig, half_in_gui_state < d ? 0.0f : one - (d + d));
|
||||
nvg_Translate(sig, (float)((double)off * ((double)window->w * factor_neg_0_1)), 0.0f);
|
||||
cat = gui->selected_tab;
|
||||
}
|
||||
if (cat >= 7) cat = 0; // tabs[7] would run into selected_tab
|
||||
switch ((unsigned)gui->tabs[cat].kind) {
|
||||
case 0: draw_module_cards(gui, window, mgr, 0, dt); break;
|
||||
case 4: draw_module_cards(gui, window, mgr, 4, dt); break;
|
||||
case 2: draw_module_cards(gui, window, mgr, 2, dt); break;
|
||||
case 3: draw_module_cards(gui, window, mgr, 3, dt); break;
|
||||
case 1: draw_module_cards(gui, window, mgr, 1, dt); break;
|
||||
case 5: { // profiles
|
||||
if (gui->input_mode == 0 && window->t > gui->profile_refresh_time + offset_2) {
|
||||
int e = load_profiles(profiles);
|
||||
if (e) {
|
||||
GEv ev = {};
|
||||
ev.a.i = e;
|
||||
prof_event(mgr, 0xc, &ev);
|
||||
}
|
||||
gui->profile_refresh_time = window->t;
|
||||
}
|
||||
void *pig = window->ig;
|
||||
choose_columns(gui, gui_profiles_column_width);
|
||||
unsigned long long n;
|
||||
Prof *list = profile_array(profiles, &n);
|
||||
for (unsigned long long i = 0; i <= n; i++) {
|
||||
R16t row = add_cell(gui, gui->row_height);
|
||||
if (!rects_intersect(make_rect(0.0f, gui->scroll, window->w, window->h), row)) continue;
|
||||
Prof *ent = i ? &list[i - 1] : 0;
|
||||
V4t bg = nvg_rgba_f(colour_level_0_0784314_in_gui_state, colour_level_0_0784314_in_gui_state, colour_level_0_0784314_in_gui_state, 1.0f);
|
||||
nvg_FillColor(pig, &bg);
|
||||
draw_rect_rounded_local((WT3 *)window, row, row_height_20_in_gui_state_a);
|
||||
begin_content(window, gui, row, row_padding_8, 12.0f);
|
||||
pop_layout_row(gui, 1);
|
||||
void *tig = window->ig;
|
||||
R16t t = add_cell(gui, row_height_20_in_gui_state_a);
|
||||
nvg_FontSize(tig, font_size_16_in_gui_state);
|
||||
V4t tc = nvg_rgba_f(colour_level_0_866667_in_gui_state, colour_level_0_866667_in_gui_state, colour_level_0_866667_in_gui_state, 1.0f);
|
||||
nvg_FillColor(tig, &tc);
|
||||
if (ent) {
|
||||
float tw = draw_text_sized(window, t, 0, 0, ent->name, ent->end);
|
||||
R16t br = make_rect(tw + rect_extent_5, t.y, t.w - tw + t.x, t.h);
|
||||
draw_module_description(gui, window, profile_set_bind(profiles), ent, ent->key, 0, br);
|
||||
} else {
|
||||
draw_text_sized(window, t, 0, 0, str_default /* "Default" */, 0);
|
||||
}
|
||||
for (unsigned j = 0; j != 4; j++) { // load, save, open folder, delete
|
||||
R16t b = make_rect(t.x + t.w - (float)(int)(20 * (4 - j)) - (float)(int)(12 * (3 - j)), t.y,
|
||||
row_height_20_in_gui_state_a, 20.0f);
|
||||
float alpha = half_in_gui_state;
|
||||
if (ent || !j) {
|
||||
alpha = one;
|
||||
if (mouse_over_rect_r_local(gui, b)) {
|
||||
set_slider_text(gui, window, (const char *)gui_state_tooltip_strings_as_offsets_from + gui_state_tooltip_strings_as_offsets_from[j],
|
||||
(int)((double)b.x + (double)b.w * factor_0_5_in_gui_state), (int)b.y);
|
||||
if (clicked_inside_r_frame_local(gui, b)) {
|
||||
const char *name = ent ? ent->name : 0;
|
||||
int error_code;
|
||||
const char *msg;
|
||||
switch (j) {
|
||||
case 0:
|
||||
if (get_profile_dirty()) {
|
||||
ask_sure_before_running_action(gui, mgr, profiles, name, 0);
|
||||
goto icon;
|
||||
}
|
||||
error_code = load_profile_name(name, mgr);
|
||||
goto result;
|
||||
case 1:
|
||||
if (!ent) {
|
||||
error_code = 0x1773;
|
||||
msg = str_default_profile_cannot_be_over; // "Default profile cannot be overwritten"
|
||||
goto error;
|
||||
}
|
||||
if (get_saved_string() && libc_strcmp(name, get_saved_string()) == 0) {
|
||||
error_code = save_profile(name, true, mgr);
|
||||
goto result;
|
||||
}
|
||||
ask_sure_before_running_action(gui, mgr, profiles, name, 1);
|
||||
goto icon;
|
||||
case 2:
|
||||
ask_sure_before_running_action(gui, mgr, profiles, name, 2);
|
||||
goto icon;
|
||||
case 3:
|
||||
if (ent) {
|
||||
ask_sure_before_running_action(gui, mgr, profiles, name, 3);
|
||||
goto icon;
|
||||
}
|
||||
error_code = 0x1774;
|
||||
msg = str_default_profile_cannot_be_dele; // "Default profile cannot be deleted"
|
||||
goto error;
|
||||
}
|
||||
result:
|
||||
if (!error_code) {
|
||||
GEv ev = {};
|
||||
ev.a.p = (void *)name;
|
||||
ev.b.i = (int)j;
|
||||
prof_event(mgr, 0xb, &ev);
|
||||
gui->profile_refresh_time = -1.0;
|
||||
goto icon;
|
||||
}
|
||||
msg = error_code == 0x1774 ? str_default_profile_cannot_be_dele : error_code == 0x1773 ? str_default_profile_cannot_be_over : 0;
|
||||
error : {
|
||||
GEv ev = {};
|
||||
ev.a.i = error_code;
|
||||
ev.b.p = (void *)msg;
|
||||
prof_event(mgr, 0xc, &ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
icon:
|
||||
draw_texture(window, b, gui_state_button_icons[j], alpha);
|
||||
}
|
||||
if (!gui->card_layout_mode) { // description line
|
||||
R16t d = add_cell(gui, row_height_20_in_gui_state_a);
|
||||
nvg_FontSize(tig, font_size_14_in_gui_state);
|
||||
V4t dc = nvg_rgba_f(colour_level_0_6, colour_level_0_6, colour_level_0_6, 1.0f);
|
||||
nvg_FillColor(tig, &dc);
|
||||
draw_text((WT3 *)window, d, 0, 0, ent ? (void *)ent->desc : (void *)str_contains_the_default_configura);
|
||||
}
|
||||
gui->row_height = end_layout_row(window, gui);
|
||||
}
|
||||
// "CREATE NEW PROFILE": a text field once clicked
|
||||
R16t cr = add_cell(gui, gui->row_height);
|
||||
void *cig = window->ig;
|
||||
V4t c0 = nvg_rgba_f(colour_level_0_0784314_in_gui_state, colour_level_0_0784314_in_gui_state, colour_level_0_0784314_in_gui_state, 1.0f);
|
||||
V4t c1 = nvg_trans_rgba_f(c0, half_in_gui_state);
|
||||
nvg_FillColor(cig, &c1);
|
||||
draw_rect_rounded_local((WT3 *)window, cr, row_height_20_in_gui_state_a);
|
||||
if (drag_item(gui, window, cr, file_name_text_filter, 0)) {
|
||||
nvg_FontSize(cig, font_size_16_in_gui_state);
|
||||
V4t wc = nvg_rgba_f(one, one, one, 1.0f);
|
||||
nvg_FillColor(cig, &wc);
|
||||
R16t ir = {cr.x, (float)((double)cr.h * factor_0_5_in_gui_state + (double)cr.y + offset_neg_8), cr.w, gui_tab_font_size};
|
||||
draw_tracked_text(gui, window, &ir);
|
||||
const char *nm = (const char *)take_dragged_item(gui);
|
||||
if (nm) {
|
||||
int r = save_profile(nm, false, mgr);
|
||||
GEv ev = {};
|
||||
if (!r) {
|
||||
ev.a.p = (void *)nm;
|
||||
ev.b.i = 1;
|
||||
prof_event(mgr, 0xb, &ev);
|
||||
gui->profile_refresh_time = -1.0;
|
||||
} else {
|
||||
ev.a.i = r;
|
||||
ev.b.p = r == 0x3f4 ? (void *)str_profile_name_is_taken : 0; // "Profile name is taken."
|
||||
prof_event(mgr, 0xc, &ev);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nvg_FontSize(cig, font_size_15);
|
||||
float k = mouse_over_rect_r_local(gui, cr) ? one : colour_level_0_866667_in_gui_state;
|
||||
V4t tc2 = nvg_rgba_f(k, k, k, 1.0f);
|
||||
nvg_FillColor(cig, &tc2);
|
||||
draw_text((WT3 *)window, cr, 1, 1, (void *)str_create_new_profile); // "CREATE NEW PROFILE"
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
end_layout_row(window, gui);
|
||||
if (gui->toast_visible) { // toast at the top, fades in
|
||||
gui->toast_fade = (float)((double)gui->toast_fade + dtd / divisor_0_1_in_gui_state);
|
||||
void *tig = window->ig;
|
||||
nvg_Save(tig);
|
||||
nvg_Translate(tig, gui->toast_x, gui->toast_y);
|
||||
if (gui->toast_fade > one) {
|
||||
gui->toast_fade = 1.0f;
|
||||
} else {
|
||||
float e = ease_curve(gui->toast_fade);
|
||||
nvg_GlobalAlpha(tig, e);
|
||||
nvg_Scale(tig, e, e);
|
||||
}
|
||||
nvg_FontSize(tig, font_size_14_in_gui_state);
|
||||
float tw = nvg_text_bounds(tig, 0.0f, 0.0f, gui->toast_text, 0, (const float *)0);
|
||||
R16t tr = make_rect((float)((double)tw * factor_neg_0_5 + offset_neg_5), gui_tab_bar_vertical_offset, tw + offset_10, 20.0f);
|
||||
V4t bc = nvg_rgba_f(colour_level_0_25098_in_gui_state, colour_level_0_25098_in_gui_state, colour_level_0_25098_in_gui_state, 1.0f);
|
||||
nvg_FillColor(tig, &bc);
|
||||
draw_rect_rounded_local((WT3 *)window, tr, rect_extent_5);
|
||||
V4t fc = nvg_rgba_f(colour_level_0_866667_in_gui_state, colour_level_0_866667_in_gui_state, colour_level_0_866667_in_gui_state, 1.0f);
|
||||
nvg_FillColor(tig, &fc);
|
||||
draw_text((WT3 *)window, tr, 1, 0, gui->toast_text);
|
||||
nvg_Restore(tig);
|
||||
gui->toast_visible = 0;
|
||||
} else {
|
||||
gui->toast_fade = 0.0f;
|
||||
}
|
||||
nvg_Restore(nvg);
|
||||
float mx = (float)gui->layout_column_count - window->h + row_padding_12;
|
||||
gui->scroll_max = 0.0f > mx ? 0.0f : mx;
|
||||
if (popup_is_open(gui)) { // modal popup over everything (unscrolled)
|
||||
nvg_Save(nvg);
|
||||
gui->mouse_scene.y -= gui->scroll;
|
||||
card_open_animation(window, gui->popup_opacity);
|
||||
reset_layout_columns(gui);
|
||||
R16t pr = win;
|
||||
if (pr.w > threshold_600) {
|
||||
pr.x = (float)((double)(pr.w + offset_neg_600) * factor_0_5_in_gui_state + (double)pr.x);
|
||||
pr.w = threshold_600;
|
||||
}
|
||||
push_layout_row(gui, pr, row_padding_8, row_padding_12);
|
||||
close_popup_animated(gui, window);
|
||||
end_layout_row(window, gui);
|
||||
nvg_Restore(nvg);
|
||||
}
|
||||
mark_click_consumed(gui);
|
||||
gui->click_consumed = gui->click_capture;
|
||||
gui->card_anim_start_time = window->t;
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
// GUI: text rendering helpers - labels, wrapped descriptions, numeric filters.
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/gui_state.h"
|
||||
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
// file-name text filter: printable, no slashes, max 15 chars
|
||||
extern "C" void file_name_text_filter(char *buf, char character) {
|
||||
unsigned long long n = libc_strlen(buf);
|
||||
unsigned char lng = n >= 0xf;
|
||||
|
||||
unsigned char np = (unsigned char)(character - 0x20) >= 0x5f;
|
||||
|
||||
if (np | lng) return;
|
||||
if (character == 0x2f || character == 0x5c) return;
|
||||
buf[n] = character;
|
||||
}
|
||||
|
||||
struct R16c { float x, y, w, h; };
|
||||
struct V4 { float x, y, z, w; };
|
||||
FN(R16c, add_cell, (const void *src, float f));
|
||||
FN(V4, nvg_rgba_f, (float r, float g, float b, float a));
|
||||
FN(void, nvg_FillColor, (void *nvg, V4 *c));
|
||||
FN(void, nvg_FontSize, (void *nvg, float f));
|
||||
FN(float, draw_text, (void *window, R16c r, int a, int b, void *x));
|
||||
extern "C" const float row_height_19, gui_section_label_top_margin, gui_section_label_grey_level, font_size_15_in_gui_text_a;
|
||||
extern "C" float draw_section_label(const void *src, void **window, void *other_object) {
|
||||
void *nvg = window[0];
|
||||
R16c r = add_cell(src, row_height_19);
|
||||
r.y += gui_section_label_top_margin;
|
||||
float g = gui_section_label_grey_level;
|
||||
V4 c = nvg_rgba_f(g, g, g, 1.0f);
|
||||
nvg_FillColor(nvg, &c);
|
||||
nvg_FontSize(nvg, font_size_15_in_gui_text_a);
|
||||
return draw_text(window, r, 0, 0, other_object);
|
||||
}
|
||||
|
||||
FN(void *, libc_malloc, (unsigned long long n));
|
||||
FN(void, libc_free, (void *ptr));
|
||||
FN(void *, libc_memcpy, (void *dest, const void *src, unsigned long long n));
|
||||
FN(void, confirmation_popup_formatted_message, (void *gui, void *object, void *other_object, const char *fmt, ...));
|
||||
FN(void, run_confirmed_profile_action, (void *p));
|
||||
extern "C" const int format_string_offsets[];
|
||||
extern "C" const char str_default[]; // "Default"
|
||||
struct Confirm { GuiState *gui; void *module_manager; void *profile_list; char *name; int kind; };
|
||||
// ask "Are you sure?" before running action `kind` on profile `name`
|
||||
extern "C" void ask_sure_before_running_action(GuiState *gui, void *module_manager, void *profile_list, const char *name, int kind) {
|
||||
Confirm *c = (Confirm *)libc_malloc(0x28);
|
||||
if (!c) return;
|
||||
c->gui = gui; c->module_manager = module_manager; c->profile_list = profile_list; c->name = 0; c->kind = kind;
|
||||
if (name) {
|
||||
unsigned long long n = libc_strlen(name);
|
||||
char *s = (char *)libc_malloc(n + 1);
|
||||
if (!s) { libc_free(c); return; }
|
||||
libc_memcpy(s, name, n);
|
||||
s[n] = 0;
|
||||
c->name = s;
|
||||
} else {
|
||||
name = str_default;
|
||||
}
|
||||
const char *fmt = (const char *)format_string_offsets + format_string_offsets[(unsigned)kind];
|
||||
confirmation_popup_formatted_message(gui, c, (void *)run_confirmed_profile_action, fmt, name);
|
||||
}
|
||||
|
||||
FN(int, load_profile_name, (const char *name, void *mods));
|
||||
FN(int, save_profile, (const char *name, bool text, void *mods));
|
||||
FN(int, open_profiles_folder, ());
|
||||
FN(int, delete_profile_file, (const char *name));
|
||||
FN(void, dispatch_event, (void *mgr, void *object));
|
||||
struct ProfEvent { int type; int padding_04; union { int code; const char *name; }; int kind; char rest[0x2c]; };
|
||||
// run the confirmed profile action (0 load, 1 save, 2 open folder, 3 delete) and report it
|
||||
extern "C" void run_confirmed_profile_action(void *cv) {
|
||||
Confirm *c = (Confirm *)cv;
|
||||
int error_code;
|
||||
switch ((unsigned)c->kind) {
|
||||
case 0: error_code = load_profile_name(c->name, c->module_manager); break;
|
||||
case 1: error_code = save_profile(c->name, true, c->module_manager); break;
|
||||
case 2: error_code = open_profiles_folder(); break;
|
||||
case 3: error_code = delete_profile_file(c->name); break;
|
||||
default: error_code = 0; goto ok;
|
||||
}
|
||||
if (error_code) {
|
||||
ProfEvent e = {};
|
||||
e.type = 0xc;
|
||||
e.code = error_code;
|
||||
dispatch_event(c->module_manager, &e);
|
||||
goto done;
|
||||
}
|
||||
ok:
|
||||
{
|
||||
ProfEvent e = {};
|
||||
e.type = 0xb;
|
||||
e.name = c->name;
|
||||
e.kind = c->kind;
|
||||
dispatch_event(c->module_manager, &e);
|
||||
}
|
||||
done:
|
||||
c->gui->profile_refresh_time = -1.0;
|
||||
libc_free(c->name);
|
||||
libc_free(c);
|
||||
}
|
||||
|
||||
// ---- animated toggle switch; returns true when clicked ----
|
||||
struct Anim { float pos; int padding_04; double last; };
|
||||
struct WT2 { void *ig; double t; };
|
||||
FN(float, ease_curve, (float t)); // smoothstep
|
||||
FN(float, lerp_unclamped_float, (float a, float b, float t));
|
||||
FN(bool, mouse_over_rect_r, (GuiState *gui, const R16c *r));
|
||||
FN(unsigned char, clicked_inside_r_frame, (GuiState *gui, R16c r));
|
||||
FN(V4, nvg_lerp_rgba, (V4 a, V4 b, float t));
|
||||
FN(void, draw_rect_rounded, (WT2 *window, const R16c *r, float round));
|
||||
FN(R16c, inset_rect, (R16c r, float d));
|
||||
FN(void, nvg_BeginPath, (void *nvg));
|
||||
FN(void, nvg_Circle, (void *nvg, float x, float y, float rad));
|
||||
FN(void, nvg_Fill, (void *nvg));
|
||||
extern "C" const double tenth_in_gui_text, divisor_neg_0_1_in_gui_text;
|
||||
extern "C" const float threshold_1_in_gui_text_b, gui_toggle_idle_grey_level, gui_toggle_hover_grey_level, corner_radius_10_in_gui_text, inset_6_in_gui_text_a, gui_text_background_grey_level, factor_0_5_in_gui_text_a;
|
||||
extern "C" bool draw_animated_description(GuiState *gui, WT2 *window, R16c *r, Anim *an, unsigned char *val) {
|
||||
double t = window->t;
|
||||
double dt = (double)(float)(t - an->last);
|
||||
unsigned char v = *val;
|
||||
float p;
|
||||
if (dt > tenth_in_gui_text) {
|
||||
p = (float)v;
|
||||
an->pos = p;
|
||||
} else {
|
||||
p = an->pos;
|
||||
if (v) {
|
||||
if (threshold_1_in_gui_text_b > p) {
|
||||
p = (float)(dt / tenth_in_gui_text + (double)p);
|
||||
p = p < threshold_1_in_gui_text_b ? p : threshold_1_in_gui_text_b;
|
||||
an->pos = p;
|
||||
}
|
||||
} else if (p > 0.0f) {
|
||||
p = (float)(dt / divisor_neg_0_1_in_gui_text + (double)p);
|
||||
p = p > 0.0f ? p : 0.0f;
|
||||
an->pos = p;
|
||||
}
|
||||
}
|
||||
an->last = t;
|
||||
void *nvg = window->ig;
|
||||
float e = ease_curve(p);
|
||||
float k = mouse_over_rect_r(gui, r) ? gui_toggle_hover_grey_level : gui_toggle_idle_grey_level;
|
||||
V4 col = nvg_rgba_f(k, k, k, 1.0f);
|
||||
V4 track = nvg_lerp_rgba(col, *(V4 *)&gui->accent_color, e);
|
||||
nvg_FillColor(nvg, &track);
|
||||
draw_rect_rounded(window, r, corner_radius_10_in_gui_text);
|
||||
bool clicked = clicked_inside_r_frame(gui, *r);
|
||||
if (clicked) *val ^= 1;
|
||||
*r = inset_rect(*r, inset_6_in_gui_text_a);
|
||||
float h = r->h;
|
||||
float kk = gui_text_background_grey_level;
|
||||
V4 knob = nvg_rgba_f(kk, kk, kk, 1.0f);
|
||||
nvg_FillColor(nvg, &knob);
|
||||
nvg_BeginPath(nvg);
|
||||
float rad = (float)(int)h;
|
||||
float off = rad * factor_0_5_in_gui_text_a;
|
||||
float x = lerp_unclamped_float(off + r->x, r->x + r->w - off, e);
|
||||
nvg_Circle(nvg, x, off + r->y, rad);
|
||||
nvg_Fill(nvg);
|
||||
return clicked;
|
||||
}
|
||||
|
||||
// ---- module header row: [+]/[-] expander box and name; returns the right edge of the clickable area ----
|
||||
FN(R16c, make_rect, (float x, float y, float w, float h));
|
||||
FN(float, nvg_text_bounds, (void *nvg, float x, float y, const char *text, const char *end, const float *bounds)); // nvgTextBounds
|
||||
FN(void, nvg_StrokeColor, (void *nvg, V4 *c));
|
||||
FN(void, nvg_MoveTo, (void *nvg, float x, float y));
|
||||
FN(void, nvg_LineTo, (void *nvg, float x, float y));
|
||||
FN(void, nvg_StrokeWidth, (void *nvg, float w));
|
||||
FN(void, nvg_Stroke, (void *nvg));
|
||||
extern "C" const float gui_text_gap, gui_text_idle_grey_level, gui_text_hover_grey_level, corner_radius_5_in_gui_text, inset_6_in_gui_text, gui_toggle_text_grey_level, one_in_gui_text,
|
||||
half_in_gui_text, stroke_width_2;
|
||||
struct ModHdr { char padding_00[0x40]; unsigned char expanded; char padding_41[0x1b7]; void *settings; };
|
||||
extern "C" float draw_module_header(GuiState *gui, WT2 *window, ModHdr *m, const R16c *r, float fsz, const V4 *col, const V4 *hcol,
|
||||
const char *text) {
|
||||
void *nvg = window->ig;
|
||||
if (!m->settings) {
|
||||
nvg_FontSize(nvg, fsz);
|
||||
V4 c = *col;
|
||||
nvg_FillColor(nvg, &c);
|
||||
return draw_text(window, *r, 0, 0, (void *)text);
|
||||
}
|
||||
R16c box = make_rect(r->x, r->y, r->h, r->h);
|
||||
float gap = gui_text_gap;
|
||||
float tx = box.x + box.w + gap;
|
||||
float tw = nvg_text_bounds(nvg, tx, r->y, text, 0, (const float *)0);
|
||||
R16c lbl = make_rect(tx, r->y, tw, r->h);
|
||||
R16c hit = make_rect(r->x, r->y, box.w + lbl.w + gap, r->h);
|
||||
if (clicked_inside_r_frame(gui, hit)) m->expanded ^= 1;
|
||||
bool hov = mouse_over_rect_r(gui, &hit);
|
||||
nvg_FontSize(nvg, fsz);
|
||||
V4 c = *(hov ? hcol : col);
|
||||
nvg_FillColor(nvg, &c);
|
||||
draw_text(window, lbl, 0, 0, (void *)text);
|
||||
float k = hov ? gui_text_hover_grey_level : gui_text_idle_grey_level;
|
||||
V4 bc = nvg_rgba_f(k, k, k, 1.0f);
|
||||
nvg_FillColor(nvg, &bc);
|
||||
draw_rect_rounded(window, &box, corner_radius_5_in_gui_text);
|
||||
R16c in = inset_rect(box, inset_6_in_gui_text);
|
||||
float k2 = hov ? one_in_gui_text : gui_toggle_text_grey_level;
|
||||
V4 sc = nvg_rgba_f(k2, k2, k2, 1.0f);
|
||||
nvg_StrokeColor(nvg, &sc);
|
||||
nvg_BeginPath(nvg);
|
||||
float half = half_in_gui_text;
|
||||
nvg_MoveTo(nvg, in.x, in.h * half + in.y);
|
||||
nvg_LineTo(nvg, in.x + in.w, in.h * half + in.y);
|
||||
if (!m->expanded) {
|
||||
nvg_MoveTo(nvg, in.w * half + in.x, in.y);
|
||||
nvg_LineTo(nvg, half * in.w + in.x, in.y + in.h);
|
||||
}
|
||||
nvg_StrokeWidth(nvg, stroke_width_2);
|
||||
nvg_Stroke(nvg);
|
||||
return hit.x + hit.w;
|
||||
}
|
||||
|
||||
// ---- key-bind button ("PRESS A KEY..." while capturing) and optional "HOLD" toggle ----
|
||||
FN(void, set_draw_color, (void *window, unsigned col));
|
||||
FN(bool, drag_owner_is, (GuiState *gui, void *object));
|
||||
FN(void, begin_drag, (GuiState *gui, void *object, void *other_object));
|
||||
FN(float, libc_ceilf, (float x)); // ceilf
|
||||
FN(const char *, mouse_button_name, (int key));
|
||||
FN(V4, nvg_trans_rgba_f, (V4 c, float k));
|
||||
extern "C" float text_width_cache[];
|
||||
extern "C" const char str_press_a_key[], str_hold_in_gui_text[]; // "PRESS A KEY...", "HOLD"
|
||||
extern "C" const float rect_extent_12_in_gui_text, colour_alpha_0_15, colour_level_0_6_in_gui_text, corner_radius_5_in_gui_text_a, colour_level_1_in_gui_text_a, colour_level_0_866667_in_gui_text_a;
|
||||
static inline __attribute__((always_inline)) V4 grey(float k) { return nvg_rgba_f(k, k, k, 1.0f); }
|
||||
extern "C" void draw_module_description(GuiState *gui, WT2 *window, void *cbArg, void *target, int key, unsigned char *hold, const R16c *r) {
|
||||
void *nvg = window->ig;
|
||||
nvg_FontSize(nvg, rect_extent_12_in_gui_text);
|
||||
set_draw_color(window, 1);
|
||||
bool capturing = drag_owner_is(gui, target);
|
||||
float tw;
|
||||
if (capturing) {
|
||||
tw = libc_ceilf(nvg_text_bounds(nvg, 0.0f, 0.0f, str_press_a_key, 0, (const float *)0));
|
||||
} else {
|
||||
tw = text_width_cache[(unsigned)key];
|
||||
if (tw == 0.0f) {
|
||||
tw = libc_ceilf(nvg_text_bounds(nvg, 0.0f, 0.0f, mouse_button_name(key), 0, (const float *)0));
|
||||
text_width_cache[(unsigned)key] = tw;
|
||||
}
|
||||
}
|
||||
R16c btn = make_rect(r->x, r->y, tw + rect_extent_12_in_gui_text, r->h);
|
||||
V4 bg = key ? nvg_trans_rgba_f(*(V4 *)&gui->accent_color, colour_alpha_0_15) : nvg_trans_rgba_f(grey(colour_level_0_6_in_gui_text), colour_alpha_0_15);
|
||||
nvg_FillColor(nvg, &bg);
|
||||
draw_rect_rounded(window, &btn, corner_radius_5_in_gui_text_a);
|
||||
V4 tc;
|
||||
if (mouse_over_rect_r(gui, &btn)) tc = grey(colour_level_1_in_gui_text_a);
|
||||
else if (key) tc = *(V4 *)&gui->accent_color;
|
||||
else tc = grey(colour_level_0_6_in_gui_text);
|
||||
nvg_FillColor(nvg, &tc);
|
||||
draw_text(window, btn, 1, 1, (void *)(capturing ? str_press_a_key : mouse_button_name(key)));
|
||||
if (clicked_inside_r_frame(gui, btn)) begin_drag(gui, cbArg, target);
|
||||
if (!key) {
|
||||
if (hold) *hold = 0;
|
||||
return;
|
||||
}
|
||||
if (!hold) return;
|
||||
float hw = libc_ceilf(nvg_text_bounds(nvg, 0.0f, 0.0f, str_hold_in_gui_text, 0, (const float *)0));
|
||||
R16c hb = make_rect(btn.x + btn.w + corner_radius_5_in_gui_text_a, btn.y, hw + rect_extent_12_in_gui_text, btn.h);
|
||||
V4 hc;
|
||||
if (*hold) {
|
||||
hc = nvg_trans_rgba_f(grey(colour_level_0_866667_in_gui_text_a), colour_alpha_0_15);
|
||||
} else {
|
||||
if (!mouse_over_rect_r(gui, r)) return;
|
||||
hc = *hold == 1 ? nvg_trans_rgba_f(grey(colour_level_0_866667_in_gui_text_a), colour_alpha_0_15) : nvg_trans_rgba_f(grey(colour_level_0_6_in_gui_text), colour_alpha_0_15);
|
||||
}
|
||||
nvg_FillColor(nvg, &hc);
|
||||
draw_rect_rounded(window, &hb, corner_radius_5_in_gui_text_a);
|
||||
V4 ht = mouse_over_rect_r(gui, &hb) ? grey(colour_level_0_866667_in_gui_text_a) : grey(colour_level_0_6_in_gui_text);
|
||||
nvg_FillColor(nvg, &ht);
|
||||
draw_text(window, hb, 1, 1, (void *)str_hold_in_gui_text);
|
||||
if (clicked_inside_r_frame(gui, hb)) *hold ^= 1;
|
||||
}
|
||||
// labelled toggle row: label text, then the switch right of it
|
||||
extern "C" const float row_height_20_in_gui_text, gui_label_text_grey_level, font_size_15_in_gui_text, rect_extent_neg_40_in_gui_text, rect_extent_40_in_gui_text;
|
||||
FN(R16c, make_rect, (float x, float y, float w, float h));
|
||||
FN(bool, draw_animated_description_local, (GuiState *gui, WT2 *window, R16c r, Anim *an, unsigned char *val)) __asm__("draw_animated_description");
|
||||
extern "C" bool draw_toggle_row(GuiState *gui, WT2 *window, const char *label, Anim *an, unsigned char *val) {
|
||||
void *nvg = window->ig;
|
||||
R16c r = add_cell(gui, row_height_20_in_gui_text);
|
||||
float k = gui_label_text_grey_level;
|
||||
V4 c = nvg_rgba_f(k, k, k, 1.0f);
|
||||
nvg_FillColor(nvg, &c);
|
||||
nvg_FontSize(nvg, font_size_15_in_gui_text);
|
||||
draw_text(window, r, 0, 0, (void *)label);
|
||||
return draw_animated_description_local(gui, window, make_rect(r.x + r.w + rect_extent_neg_40_in_gui_text, r.y, rect_extent_40_in_gui_text, r.h), an, val);
|
||||
}
|
||||
|
||||
// ---- float slider: label, track, animated fill + knob, drag/typing via draw_slider_track, value tooltip ----
|
||||
struct SAnim { unsigned char active; char padding_01[3]; float pos; double last; };
|
||||
FN(unsigned char, draw_slider_track, (GuiState *gui, R16c track, R16c hit, float lo, float hi, float step, float scale, SAnim *an,
|
||||
float *val));
|
||||
FN(float, limit_rotation_delta, (float lo, float hi, float x)); // clamp
|
||||
FN(float, lerp_clamped_float, (float a, float b, float t));
|
||||
FN(bool, dragged_item_is, (GuiState *gui, void *item));
|
||||
FN(int, libc_snprintf, (char *buf, unsigned long long n, const char *fmt, ...)); // snprintf
|
||||
extern "C" const float row_height_20_in_gui_text_a, colour_level_0_866667_in_gui_text_c, font_size_15_in_gui_text_b, offset_10_in_gui_text_a, offset_neg_2_in_gui_text, gui_description_idle_grey_level,
|
||||
gui_slider_hover_grey_level, two_in_gui_text, clamp_bound_1_in_gui_text_c, gui_slider_knob_radius;
|
||||
extern "C" const double factor_0_5_in_gui_text, offset_neg_2, divisor_0_1_in_gui_text_a;
|
||||
extern "C" const char fmt_float_2dp[]; // "%0.2f"
|
||||
extern "C" char value_label_buffer[];
|
||||
extern "C" unsigned char draw_slider_row(GuiState *gui, WT2 *window, const char *label, const char *fmt, SAnim *active, float lo, float hi,
|
||||
float step, float scale, float *val) {
|
||||
void *nvg = window->ig;
|
||||
R16c r = add_cell(gui, row_height_20_in_gui_text_a);
|
||||
V4 tc = nvg_rgba_f(colour_level_0_866667_in_gui_text_c, colour_level_0_866667_in_gui_text_c, colour_level_0_866667_in_gui_text_c, 1.0f);
|
||||
nvg_FillColor(nvg, &tc);
|
||||
nvg_FontSize(nvg, font_size_15_in_gui_text_b);
|
||||
draw_text(window, r, 0, 0, (void *)label);
|
||||
float y = r.y + offset_10_in_gui_text_a + offset_neg_2_in_gui_text;
|
||||
double half = (double)r.w * factor_0_5_in_gui_text;
|
||||
float x0 = (float)((double)r.x + half);
|
||||
float tw = (float)(half + offset_neg_2);
|
||||
R16c track = make_rect(x0, y, tw, 4.0f);
|
||||
R16c hr = r;
|
||||
float k = mouse_over_rect_r(gui, &hr) ? gui_slider_hover_grey_level : gui_description_idle_grey_level;
|
||||
V4 kc = nvg_rgba_f(k, k, k, 1.0f);
|
||||
nvg_FillColor(nvg, &kc);
|
||||
float rnd = two_in_gui_text;
|
||||
R16c tr = track;
|
||||
draw_rect_rounded(window, &tr, rnd);
|
||||
float t = limit_rotation_delta(0.0f, clamp_bound_1_in_gui_text_c, (*val - lo) / (hi - lo));
|
||||
float pos = lerp_clamped_float(active->pos, t, (float)((double)(float)(window->t - active->last) / divisor_0_1_in_gui_text_a));
|
||||
active->pos = pos;
|
||||
active->last = window->t;
|
||||
float fx = lerp_unclamped_float(x0, tw + x0, pos);
|
||||
R16c fill = make_rect(x0, y, fx - x0, 4.0f);
|
||||
V4 acc = *(V4 *)&gui->accent_color;
|
||||
nvg_FillColor(nvg, &acc);
|
||||
draw_rect_rounded(window, &fill, rnd);
|
||||
nvg_BeginPath(nvg);
|
||||
nvg_Circle(nvg, fx, rnd + y, gui_slider_knob_radius);
|
||||
nvg_Fill(nvg);
|
||||
unsigned char res = draw_slider_track(gui, track, r, lo, hi, step, scale, active, val);
|
||||
if (!active->active) {
|
||||
R16c h2 = r;
|
||||
if (!mouse_over_rect_r(gui, &h2) || gui->click_capture) return res;
|
||||
}
|
||||
const char *text;
|
||||
if (dragged_item_is(gui, val)) {
|
||||
text = gui->edit_text;
|
||||
} else {
|
||||
float v = *val;
|
||||
float shown = scale != 0.0f ? scale * v : v;
|
||||
text = value_label_buffer;
|
||||
libc_snprintf(value_label_buffer, 0x40, fmt ? fmt : fmt_float_2dp, (double)shown);
|
||||
}
|
||||
gui->toast_visible = 1;
|
||||
unsigned long long tn = libc_strlen(text);
|
||||
if (tn > 0x65) tn = 0x65;
|
||||
libc_memcpy(gui->toast_text, text, tn);
|
||||
gui->toast_text[tn] = 0;
|
||||
gui->toast_x = (float)(int)fx;
|
||||
gui->toast_y = (float)(int)y;
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
// 0xd000: ImGui widget wrappers (mark profile dirty on change), bounded string builder
|
||||
#include "slinky/engine.h"
|
||||
#include <stdarg.h>
|
||||
FN(void, set_profile_dirty, (unsigned char v));
|
||||
FN(unsigned char, draw_dropdown_row, (void *gui, void *window, void *label, void *setting, int count, void *names, void *value, unsigned char optional));
|
||||
FN(unsigned char, draw_color_swatch_row, (void *gui, void *window, void *label, void *color));
|
||||
FN(unsigned char, draw_list_editor_row, (void *gui, void *window, void *label, void *popup_title, void *char_filter, int kind, void *list_ops,
|
||||
void *candidates, void *candidate_count));
|
||||
|
||||
// enum setting: label, option count, option names, current value (+ saved copy); `optional` adds a "none" entry with a clear cross
|
||||
struct DropdownSetting { char padding_00[0x18]; void *label; int count; int padding_24; void *names; char value_and_saved[8]; unsigned char optional; };
|
||||
extern "C" void widget_enum(void *window, void *gui, DropdownSetting *setting) {
|
||||
if (draw_dropdown_row(gui, window, setting->label, setting, setting->count, setting->names, setting->value_and_saved, setting->optional)) set_profile_dirty(1);
|
||||
}
|
||||
// colour setting: label, then the colour (current + saved)
|
||||
struct ColorSetting { void *label; char color[8]; };
|
||||
extern "C" void widget_color(void *window, void *gui, ColorSetting *setting) {
|
||||
if (draw_color_swatch_row(gui, window, setting->label, setting->color)) set_profile_dirty(1);
|
||||
}
|
||||
// list setting: label, "add" popup title and character filter, entry kind (0 names, 1 names + colour), list callbacks,
|
||||
// and an optional provider of the candidate items offered by the "add" popup
|
||||
struct ListSetting {
|
||||
void *label, *popup_title, *char_filter; int kind; int padding_1c; char list_ops[0x38];
|
||||
void (*get_candidates)(void **items, void **count); unsigned char changed;
|
||||
};
|
||||
extern "C" void widget_items(void *window, void *gui, ListSetting *setting) {
|
||||
void *candidates = 0, *candidate_count = 0;
|
||||
if (setting->get_candidates) setting->get_candidates(&candidates, &candidate_count);
|
||||
if (draw_list_editor_row(gui, window, setting->label, setting->popup_title, setting->char_filter, setting->kind, setting->list_ops, candidates, candidate_count)) {
|
||||
setting->changed = 1;
|
||||
set_profile_dirty(1);
|
||||
}
|
||||
}
|
||||
|
||||
struct Buf { char *p; unsigned long long cap, len; };
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
FN(void *, libc_memcpy_alt, (void *dest, const void *src, unsigned long long n));
|
||||
FN(int, libc_vsnprintf, (char *buf, unsigned long long n, const char *fmt, va_list ap)); // vsnprintf
|
||||
extern "C" void buffer_init(Buf *b, char *p, unsigned long long cap) { b->p = p; b->cap = cap; b->len = 0; }
|
||||
extern "C" bool buffer_append(Buf *b, const char *s) {
|
||||
unsigned long long len = b->len;
|
||||
if (b->cap <= len) return false;
|
||||
unsigned long long avail = b->cap - len;
|
||||
unsigned long long n = libc_strlen(s);
|
||||
if (n >= avail) return false;
|
||||
libc_memcpy_alt(b->p + len, s, n);
|
||||
b->len += n;
|
||||
return true;
|
||||
}
|
||||
extern "C" bool buffer_printf(Buf *b, const char *fmt, ...) {
|
||||
if (b->cap <= b->len) return false;
|
||||
unsigned long long avail = b->cap - b->len;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int result = libc_vsnprintf(b->p + b->len, avail, fmt, ap);
|
||||
unsigned long long nl;
|
||||
bool ok;
|
||||
if (result < 0 || avail <= (unsigned)result) { nl = b->cap; ok = false; }
|
||||
else { nl = (unsigned)result + b->len; ok = true; }
|
||||
b->len = nl;
|
||||
return ok;
|
||||
}
|
||||
extern "C" bool buffer_push_char(Buf *b, char character) {
|
||||
unsigned long long len = b->len, cap = b->cap;
|
||||
if (len < cap) {
|
||||
char *p = b->p;
|
||||
b->len = len + 1;
|
||||
p[len] = character;
|
||||
}
|
||||
return len < cap;
|
||||
}
|
||||
extern "C" bool buffer_terminate(Buf *b) {
|
||||
unsigned long long len = b->len, cap = b->cap;
|
||||
if (len < cap) {
|
||||
b->p[len] = 0;
|
||||
b->len++;
|
||||
}
|
||||
return len < cap;
|
||||
}
|
||||
|
||||
// ---- generic dynamic array: data pointer + {u32 cap, u32 count} header, element size passed in ----
|
||||
struct AHdr { unsigned cap, count; };
|
||||
typedef int (*ACmp)(void *ctx, const void *key, const void *elem);
|
||||
FN(void *, libc_realloc, (void *ptr, unsigned long long n)); // realloc
|
||||
extern "C" __attribute__((noreturn)) void libc_abort(); // abort
|
||||
FN(void *, libc_memmove, (void *dest, const void *src, unsigned long long n)); // memmove
|
||||
extern "C" void *element_at(void *base, unsigned elem, AHdr *, unsigned i) { return (char *)base + elem * i; }
|
||||
static inline __attribute__((always_inline)) void agrow(void **data, unsigned elem, AHdr *header) {
|
||||
if (header->cap > 0x7fffffffu) libc_abort();
|
||||
unsigned cap = header->cap ? header->cap * 2 : 8;
|
||||
if (elem && cap > 0xffffffffu / elem) libc_abort();
|
||||
header->cap = cap;
|
||||
void *p = libc_realloc(*data, (unsigned long long)cap * elem);
|
||||
if (!p) libc_abort();
|
||||
*data = p;
|
||||
}
|
||||
// push_back
|
||||
extern "C" void *vector_push_back(void **data, unsigned elem, AHdr *header, const void *src) {
|
||||
if (header->count >= header->cap) agrow(data, elem, header);
|
||||
unsigned i = header->count;
|
||||
header->count = i + 1;
|
||||
return libc_memcpy_alt((char *)*data + i * elem, src, elem);
|
||||
}
|
||||
// insert at index (shares the manifest range of vector_push_back)
|
||||
extern "C" void *insert_index(void **data, unsigned elem, AHdr *header, const void *src, unsigned idx) {
|
||||
if (idx > header->count) return 0;
|
||||
if (header->count >= header->cap) agrow(data, elem, header);
|
||||
char *base = (char *)*data;
|
||||
char *at = base + idx * elem;
|
||||
libc_memmove(base + (idx + 1) * elem, at, (header->count - idx) * elem);
|
||||
libc_memcpy_alt(at, src, elem);
|
||||
header->count++;
|
||||
return at;
|
||||
}
|
||||
// binary search
|
||||
extern "C" void *binary_search(void *base, unsigned elem, AHdr *header, const void *key, ACmp cmp) {
|
||||
unsigned hi = header->count;
|
||||
if (!hi) return 0;
|
||||
unsigned lo = 0;
|
||||
do {
|
||||
unsigned mid = lo + ((hi - lo) >> 1);
|
||||
char *p = (char *)base + mid * elem;
|
||||
int result = cmp(0, key, p);
|
||||
if (result < 0) hi = mid;
|
||||
else if (result == 0) return p;
|
||||
else lo = mid + 1;
|
||||
} while (lo < hi);
|
||||
return 0;
|
||||
}
|
||||
// sorted insert (returns the new element)
|
||||
extern "C" void *sorted_insert(void **data, unsigned elem, AHdr *header, const void *key, ACmp cmp) {
|
||||
unsigned hi = header->count, idx;
|
||||
if (!hi) {
|
||||
idx = 0;
|
||||
} else {
|
||||
char *base = (char *)*data;
|
||||
unsigned lo = 0;
|
||||
for (;;) {
|
||||
unsigned mid = lo + ((hi - lo) >> 1);
|
||||
int result = cmp(0, key, base + mid * elem);
|
||||
if (result < 0) {
|
||||
hi = mid;
|
||||
if (lo >= hi) break;
|
||||
} else if (result == 0) {
|
||||
hi = mid;
|
||||
break;
|
||||
} else {
|
||||
lo = mid + 1;
|
||||
if (lo >= hi) break;
|
||||
}
|
||||
}
|
||||
idx = hi;
|
||||
}
|
||||
if (header->count >= header->cap) agrow(data, elem, header);
|
||||
char *base = (char *)*data;
|
||||
char *at = base + idx * elem;
|
||||
libc_memmove(base + (idx + 1) * elem, at, (header->count - idx) * elem);
|
||||
libc_memcpy_alt(at, key, elem);
|
||||
header->count++;
|
||||
return at;
|
||||
}
|
||||
|
||||
// ---- install / remove "slinky_handler" in the client's netty pipeline ----
|
||||
#include "slinky/jids.h"
|
||||
FN(void *, get_minecraft, (Jvm *jvm));
|
||||
FN(void *, java_call_minecraft_get_net_handler, (Jvm *jvm, void *minecraft));
|
||||
FN(void *, java_get_net_handler_play_client_net_manager, (Jvm *jvm, void *net_handler_play_client));
|
||||
FN(void *, java_get_network_manager, (Jvm *jvm, void *network_manager));
|
||||
FN(void *, java_call_channel_pipeline, (Jvm *jvm, void *channel_pipeline));
|
||||
FN(void *, java_call_channel_pipeline_get, (Jvm *jvm, void *pipe, void *cls));
|
||||
FN(void, java_call_channel_pipeline_add_before, (Jvm *jvm, void *pipe, const char *before, const char *name, jobject h));
|
||||
FN(void, java_call_channel_pipeline_remove, (Jvm *jvm, void *pipe, void *cls));
|
||||
extern "C" const char str_packet_handler[], str_slinky_handler[];
|
||||
struct HClass { jclass cls; jmethodID ctor; };
|
||||
static inline __attribute__((always_inline)) int pipeline(Jvm *jvm, void **out) {
|
||||
void *minecraft = get_minecraft(jvm);
|
||||
if (!minecraft) return 0x3fc;
|
||||
void *minecraft_get_net_handler = java_call_minecraft_get_net_handler(jvm, minecraft);
|
||||
if (!minecraft_get_net_handler) return 0x3fd;
|
||||
void *network_manager = java_get_network_manager(jvm, java_get_net_handler_play_client_net_manager(jvm, minecraft_get_net_handler));
|
||||
if (!network_manager) return 0x3fd;
|
||||
void *pipe = java_call_channel_pipeline(jvm, network_manager);
|
||||
if (!pipe) return 0x3fd;
|
||||
*out = pipe;
|
||||
return 0;
|
||||
}
|
||||
extern "C" int register_channel_class(Jvm *jvm, HClass *h) {
|
||||
void *pipe;
|
||||
int error_code = pipeline(jvm, &pipe);
|
||||
if (error_code) return error_code;
|
||||
if (java_call_channel_pipeline_get(jvm, pipe, h->cls)) return 0;
|
||||
JNIEnv *env = jvm->env;
|
||||
jobject object = (*env)->NewObject(env, h->cls, h->ctor);
|
||||
if (!object) return 0x7d5;
|
||||
java_call_channel_pipeline_add_before(jvm, pipe, str_packet_handler, str_slinky_handler, object);
|
||||
return (*env)->ExceptionCheck(env) ? 0x7d4 : 0;
|
||||
}
|
||||
extern "C" int unregister_channel_class(Jvm *jvm, HClass *h) {
|
||||
void *pipe;
|
||||
int error_code = pipeline(jvm, &pipe);
|
||||
if (error_code) return error_code;
|
||||
if (!java_call_channel_pipeline_get(jvm, pipe, h->cls)) return 0;
|
||||
JNIEnv *env = jvm->env;
|
||||
java_call_channel_pipeline_remove(jvm, pipe, h->cls);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env);
|
||||
return 0x7d4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- reach: entity mouse-over with the module's reach / hitbox expansion (piercing, ghost hand aware) ----
|
||||
struct V3d { double x, y, z; };
|
||||
struct Box6 { double v[6]; };
|
||||
struct ReachA { Ctx *c; void *padding_08; double reach, expand; };
|
||||
FN(bool, piercing_should_pierce_blocks_entry, (Ctx *ctx)); // piercing: blocks
|
||||
FN(bool, ghost_hand_should_ignore_entity_entry, (Ctx *ctx, jobject entity)); // ghost hand: may hit through e
|
||||
FN(int, piercing_decide_which_entity_entry, (Ctx *ctx, jobject e1, jobject e2, bool inside)); // piercing: 0/1 preferred, 2 none
|
||||
FN(jobject, java_call_entity_ray_trace, (Jvm *jvm, jobject object, double d, float pt)); // rayTrace
|
||||
FN(jobject, java_call_entity_living_base_get_position, (Jvm *jvm, jobject entity_living_base)); // eye position
|
||||
FN(V3d, java_get_vec3_x_coord, (Jvm *jvm, jobject vec3));
|
||||
FN(jobject, java_get_moving_object_position_hit_vec, (Jvm *jvm, jobject mop)); // hitVec
|
||||
FN(double, vec3_distance, (V3d a, V3d b));
|
||||
FN(V3d, java_get_entity_rotation_pitch, (Jvm *jvm, jobject entity)); // look vector
|
||||
FN(V3d, vec3_scale, (V3d a, double s));
|
||||
FN(V3d, vec3_add, (V3d a, V3d b));
|
||||
FN(jobject, java_new_vec3_init, (Jvm *jvm, V3d v));
|
||||
FN(jobject, java_get_world_loaded_entity_list, (Jvm *jvm, void *world)); // loaded entity list
|
||||
FN(int, java_call_list_size, (Jvm *jvm, jobject l));
|
||||
FN(int, java_get_moving_object_position_type_ordinal, (Jvm *jvm, jobject mop)); // typeOfHit ordinal
|
||||
FN(jobject, java_call_list_get, (Jvm *jvm, jobject l, int i));
|
||||
FN(bool, java_call_entity_can_be_collided_with, (Jvm *jvm, jobject entity)); // canBeCollidedWith
|
||||
FN(jobject, java_get_entity_bounding_box, (Jvm *jvm, jobject entity)); // bounding box
|
||||
FN(Box6, java_get_axis_aligned_bb_min_x, (Jvm *jvm, jobject bb));
|
||||
FN(Box6, aabb_expand, (Box6 b, double d)); // expand
|
||||
FN(jobject, java_new_axis_aligned_bb_init, (Jvm *jvm, Box6 b));
|
||||
FN(jobject, java_axis_aligned_bb_calculate_intercept, (Jvm *jvm, jobject bb, jobject from, jobject to)); // calculateIntercept
|
||||
FN(bool, aabb_contains_point, (Box6 b, V3d p)); // isVecInside
|
||||
FN(V3d, aabb_clamp_point, (Box6 b, V3d p)); // clamp into box
|
||||
FN(jobject, java_moving_object_position_init, (Jvm *jvm, jobject moving_object_position, jobject v)); // new MovingObjectPosition
|
||||
FN(void, java_set_minecraft, (Jvm *jvm, void *minecraft, jobject mop)); // objectMouseOver =
|
||||
extern "C" const double offset_1_5, tenth;
|
||||
extern "C" const float one_in_imgui_widgets;
|
||||
#define RJ ((Jvm *)ctx->jvm)
|
||||
extern "C" void reach_distance_check(ReachA *a) {
|
||||
Ctx *ctx = a->c;
|
||||
double reach = a->reach;
|
||||
double d = offset_1_5 + reach;
|
||||
bool pierce = piercing_should_pierce_blocks_entry(ctx);
|
||||
jobject mop = java_call_entity_ray_trace(RJ, ctx->player, d, one_in_imgui_widgets);
|
||||
jobject eyes = java_call_entity_living_base_get_position(RJ, ctx->player);
|
||||
V3d eye = java_get_vec3_x_coord(RJ, eyes);
|
||||
if (!pierce && mop) d = vec3_distance(eye, java_get_vec3_x_coord(RJ, java_get_moving_object_position_hit_vec(RJ, mop)));
|
||||
V3d endv = vec3_add(eye, vec3_scale(java_get_entity_rotation_pitch(RJ, ctx->player), d));
|
||||
jobject endo = java_new_vec3_init(RJ, endv);
|
||||
jobject list = java_get_world_loaded_entity_list(RJ, ctx->world);
|
||||
int n = java_call_list_size(RJ, list);
|
||||
bool blockHit = mop && java_get_moving_object_position_type_ordinal(RJ, mop) == 1;
|
||||
if (n <= 0) {
|
||||
if (blockHit) java_set_minecraft(RJ, ctx->minecraft, mop);
|
||||
return;
|
||||
}
|
||||
jobject pointed = 0, hitv = 0;
|
||||
double closest = reach;
|
||||
bool found = false;
|
||||
for (int i = 0; i != n; i++) {
|
||||
jobject element = java_call_list_get(RJ, list, i);
|
||||
JNIEnv *env = RJ->env;
|
||||
if ((*env)->IsSameObject(env, (jobject)ctx->player, element)) continue;
|
||||
if (!java_call_entity_can_be_collided_with(RJ, element)) continue;
|
||||
Box6 bb = aabb_expand(java_get_axis_aligned_bb_min_x(RJ, java_get_entity_bounding_box(RJ, element)), a->expand);
|
||||
jobject ic = java_axis_aligned_bb_calculate_intercept(RJ, java_new_axis_aligned_bb_init(RJ, bb), eyes, endo);
|
||||
jobject entity;
|
||||
double nd;
|
||||
if (aabb_contains_point(bb, eye)) { // eye inside the (expanded) box
|
||||
found = true;
|
||||
entity = pointed;
|
||||
nd = closest;
|
||||
if (closest >= 0.0 && !(pointed && piercing_decide_which_entity_entry(ctx, pointed, element, true) == 1)) {
|
||||
hitv = ic ? java_get_moving_object_position_hit_vec(RJ, ic) : eyes;
|
||||
entity = element;
|
||||
nd = 0.0;
|
||||
}
|
||||
} else {
|
||||
if (!ic) continue;
|
||||
jobject moving_object_position = java_get_moving_object_position_hit_vec(RJ, ic);
|
||||
double dist = vec3_distance(eye, java_get_vec3_x_coord(RJ, moving_object_position));
|
||||
int result = piercing_decide_which_entity_entry(ctx, pointed, element, found);
|
||||
bool take = result == 2 ? closest == 0.0 || closest > dist : result == 0 && reach > dist;
|
||||
entity = pointed;
|
||||
nd = closest;
|
||||
if (take) {
|
||||
hitv = moving_object_position;
|
||||
entity = element;
|
||||
nd = dist;
|
||||
}
|
||||
}
|
||||
bool through = ghost_hand_should_ignore_entity_entry(ctx, entity);
|
||||
blockHit = blockHit & through;
|
||||
closest = nd;
|
||||
pointed = entity;
|
||||
}
|
||||
if (blockHit) {
|
||||
java_set_minecraft(RJ, ctx->minecraft, mop);
|
||||
return;
|
||||
}
|
||||
if (!pointed) return;
|
||||
if (!(pierce | !mop | !(closest > d))) return;
|
||||
Box6 bb = aabb_expand(java_get_axis_aligned_bb_min_x(RJ, java_get_entity_bounding_box(RJ, pointed)), tenth);
|
||||
V3d p = aabb_clamp_point(bb, java_get_vec3_x_coord(RJ, hitv));
|
||||
java_set_minecraft(RJ, ctx->minecraft, java_moving_object_position_init(RJ, pointed, java_new_vec3_init(RJ, p)));
|
||||
}
|
||||
#undef RJ
|
||||
|
||||
// ---- counting allocator (the allocation counter at allocation_counter is checked for leaks on unload) ----
|
||||
FN(void *, libc_malloc, (unsigned long long n)); // malloc
|
||||
FN(void, libc_free, (void *ptr)); // free
|
||||
FN(void *, libc_memset, (void *ptr, int c, unsigned long long n)); // memset
|
||||
extern "C" long long allocation_counter;
|
||||
extern "C" void *debug_alloc(unsigned long long n) { // allocate n bytes filled with 0xaa
|
||||
void *p = libc_malloc(n);
|
||||
if (!p) return 0;
|
||||
libc_memset(p, 0xaa, n);
|
||||
__atomic_fetch_add(&allocation_counter, 1, __ATOMIC_SEQ_CST);
|
||||
return p;
|
||||
}
|
||||
extern "C" void debug_free(void *p) {
|
||||
if (p) {
|
||||
__atomic_fetch_sub(&allocation_counter, 1, __ATOMIC_SEQ_CST);
|
||||
libc_free(p);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- define the netty channel handler class "SlinkyChannelHandler" from the embedded class bytes ----
|
||||
// registers its native `onPacket(Object, boolean, long, int)` (implemented by `onPacket`), looks up the
|
||||
// constructor and keeps a global ref of the class. 0x7d0: no constructor, 0x7d5: no global ref.
|
||||
FN(int, define_class, (JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jint len, const JNINativeMethod *methods,
|
||||
jclass *out));
|
||||
extern "C" const jbyte *ptr_to_str_str; // class file bytes of SlinkyChannelHandler
|
||||
extern "C" const jint *ptr_to_rdata_2b4bf8; // their length
|
||||
extern "C" const char str_onpacket[], str_ljava_lang_object_zji_i[], str_slinkychannelhandler[], str_init[], str_v_in_imgui_widgets[];
|
||||
extern "C" int install_packet_channel(Jvm *jvm, jobject loader, HClass *out, void *onPacket) {
|
||||
JNINativeMethod native = {(char *)str_onpacket /* "onPacket" */, (char *)str_ljava_lang_object_zji_i /* "(Ljava/lang/Object;ZJI)I" */,
|
||||
onPacket};
|
||||
JNIEnv *env = jvm->env;
|
||||
jclass cls;
|
||||
int error_code = define_class(env, str_slinkychannelhandler /* "SlinkyChannelHandler" */, loader, ptr_to_str_str, *ptr_to_rdata_2b4bf8, &native, &cls);
|
||||
if (error_code) return error_code;
|
||||
out->ctor = (*env)->GetMethodID(env, cls, str_init /* "<init>" */, str_v_in_imgui_widgets /* "()V" */);
|
||||
if (!out->ctor) return 0x7d0;
|
||||
out->cls = (jclass)(*env)->NewGlobalRef(env, cls);
|
||||
return out->cls ? 0 : 0x7d5;
|
||||
}
|
||||
// drop the class again: unregister its natives and release the global ref
|
||||
extern "C" void release_packet_channel(JNIEnv *env, jclass *cls) {
|
||||
if (!*cls) return;
|
||||
(*env)->UnregisterNatives(env, *cls);
|
||||
(*env)->DeleteGlobalRef(env, *cls);
|
||||
*cls = 0;
|
||||
}
|
||||
|
||||
// grow a dynamic array when it is full (the capacity step of vector_push_back)
|
||||
extern "C" void grow_dynamic_array_full(void **data, unsigned elem, AHdr *header) {
|
||||
if (__builtin_expect(header->count >= header->cap, 0)) agrow(data, elem, header);
|
||||
}
|
||||
|
||||
// calloc / realloc with the same allocation counter
|
||||
FN(void *, libc_calloc, (unsigned long long n, unsigned long long size));
|
||||
FN(void *, libc_realloc, (void *p, unsigned long long n));
|
||||
extern "C" void *debug_calloc(unsigned long long n, unsigned long long size) {
|
||||
void *p = libc_calloc(n, size);
|
||||
if (p) __atomic_fetch_add(&allocation_counter, 1, __ATOMIC_SEQ_CST);
|
||||
return p;
|
||||
}
|
||||
extern "C" void *debug_realloc(void *old, unsigned long long n) {
|
||||
bool had = old != 0;
|
||||
void *p = libc_realloc(old, n);
|
||||
if (!(p == 0 || had)) __atomic_fetch_add(&allocation_counter, 1, __ATOMIC_SEQ_CST);
|
||||
return p;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,115 @@
|
||||
// 0x1e000: JNI ID table teardown
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jids.h"
|
||||
|
||||
// Release the global ref of every class cached by load_all_jni_ids. (The original skips S27PacketExplosion and EntityArrow.)
|
||||
#define RELEASE(f) \
|
||||
do { \
|
||||
if (ids->f) { \
|
||||
(*env)->DeleteGlobalRef(env, ids->f); \
|
||||
ids->f = 0; \
|
||||
} \
|
||||
} while (0)
|
||||
extern "C" void release_jni_ids(JIds *ids, JNIEnv *env) {
|
||||
if (ids->gameClassLoader) {
|
||||
(*env)->DeleteGlobalRef(env, (jobject)ids->gameClassLoader);
|
||||
ids->gameClassLoader = 0;
|
||||
}
|
||||
RELEASE(List);
|
||||
RELEASE(Map);
|
||||
RELEASE(Keyboard);
|
||||
RELEASE(Mouse);
|
||||
RELEASE(Display);
|
||||
RELEASE(Object);
|
||||
RELEASE(SecureClassLoader);
|
||||
RELEASE(Channel);
|
||||
RELEASE(ChannelPipeline);
|
||||
RELEASE(Enum);
|
||||
RELEASE(Minecraft);
|
||||
RELEASE(Entity);
|
||||
RELEASE(World);
|
||||
RELEASE(Timer);
|
||||
RELEASE(GameSettings);
|
||||
RELEASE(MovingObjectPosition);
|
||||
RELEASE(_init_);
|
||||
RELEASE(Block);
|
||||
RELEASE(BlockPos);
|
||||
RELEASE(KeyBinding);
|
||||
RELEASE(AxisAlignedBB);
|
||||
RELEASE(GuiContainer);
|
||||
RELEASE(EntityLivingBase);
|
||||
RELEASE(EntityPlayer);
|
||||
RELEASE(InventoryPlayer);
|
||||
RELEASE(ItemStack);
|
||||
RELEASE(Item);
|
||||
RELEASE(Vec3);
|
||||
RELEASE(ScorePlayerTeam);
|
||||
RELEASE(NetHandlerPlayClient);
|
||||
RELEASE(IChatComponent);
|
||||
RELEASE(IChatComponent_Serializer);
|
||||
RELEASE(Slot);
|
||||
RELEASE(IInventory);
|
||||
RELEASE(NetworkManager);
|
||||
RELEASE(C03PacketPlayer);
|
||||
RELEASE(C07PacketPlayerDigging);
|
||||
RELEASE(C08PacketPlayerBlockPlacement);
|
||||
RELEASE(TileEntity);
|
||||
RELEASE(S12PacketEntityVelocity);
|
||||
RELEASE(RenderManager);
|
||||
RELEASE(S14PacketEntity);
|
||||
RELEASE(S18PacketEntityTeleport);
|
||||
RELEASE(S19PacketEntityStatus);
|
||||
RELEASE(S06PacketUpdateHealth);
|
||||
RELEASE(S29PacketSoundEffect);
|
||||
RELEASE(EntityOtherPlayerMP);
|
||||
RELEASE(GuiInventory);
|
||||
RELEASE(EntityPlayerSP);
|
||||
RELEASE(EntityRenderer);
|
||||
RELEASE(Framebuffer);
|
||||
RELEASE(RenderGlobal);
|
||||
RELEASE(ActiveRenderInfo);
|
||||
RELEASE(RendererLivingEntity);
|
||||
RELEASE(FontRenderer);
|
||||
RELEASE(GlStateManager);
|
||||
RELEASE(C02PacketUseEntity);
|
||||
RELEASE(C0BPacketEntityAction);
|
||||
RELEASE(S08PacketPlayerPosLook);
|
||||
RELEASE(S00PacketKeepAlive);
|
||||
RELEASE(C00PacketKeepAlive);
|
||||
RELEASE(Potion);
|
||||
RELEASE(RenderPlayer);
|
||||
RELEASE(RenderItem);
|
||||
RELEASE(RenderHelper);
|
||||
RELEASE(EntityList);
|
||||
RELEASE(Chunk);
|
||||
RELEASE(ExtendedBlockStorage);
|
||||
RELEASE(WorldClient);
|
||||
RELEASE(ChunkProviderClient);
|
||||
RELEASE(EntityItem);
|
||||
RELEASE(S3FPacketCustomPayload);
|
||||
RELEASE(PacketBuffer);
|
||||
RELEASE(UUID);
|
||||
RELEASE(ChatComponentText);
|
||||
RELEASE(PlayerControllerMP);
|
||||
RELEASE(C0EPacketClickWindow);
|
||||
RELEASE(NBTTagList);
|
||||
RELEASE(NBTTagCompound);
|
||||
RELEASE(PotionEffect);
|
||||
RELEASE(NetworkPlayerInfo);
|
||||
RELEASE(GenericFutureListener);
|
||||
RELEASE(C16PacketClientStatus);
|
||||
RELEASE(C16PacketClientStatus_EnumState);
|
||||
RELEASE(c);
|
||||
RELEASE(ContainerPlayer);
|
||||
RELEASE(GuiScreen);
|
||||
RELEASE(GuiChest);
|
||||
RELEASE(InventoryBasic);
|
||||
RELEASE(StatCollector);
|
||||
RELEASE(C01PacketChatMessage);
|
||||
RELEASE(C14PacketTabComplete);
|
||||
if (ids->S2APacketParticles) { // last one is a tail call, function pointer loaded first
|
||||
auto f = (*env)->DeleteGlobalRef;
|
||||
|
||||
f(env, ids->S2APacketParticles);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
// 0xc000: named profiles (non-global modules) load/save via cJSON
|
||||
#include "slinky/engine.h"
|
||||
struct cJSON;
|
||||
struct Mod { char padding_00[0x18]; unsigned char flags; };
|
||||
struct Mods { Mod **data; unsigned long long count; };
|
||||
FN(cJSON *, cjson_parse, (const char *text)); // cJSON_Parse
|
||||
FN(void, cjson_delete, (cJSON *object)); // cJSON_Delete
|
||||
FN(void, cjson_free_string, (void *p)); // cJSON_free
|
||||
FN(cJSON *, cjson_create_object, ()); // cJSON_CreateObject
|
||||
FN(char *, cjson_print_unformatted, (const cJSON *object)); // cJSON_PrintUnformatted
|
||||
FN(unsigned char, load_module_settings, (cJSON *root, Mods *mods, Mod *module));
|
||||
FN(unsigned char, save_module_settings, (cJSON *root, Mod *module));
|
||||
FN(int, settings_path, (char **path));
|
||||
FN(int, settings_path_append, (char **path, const char *suffix));
|
||||
FN(int, settings_path_ensure, (char **path));
|
||||
FN(void, free_path, (char **path));
|
||||
FN(int, write_text_file, (char **path, const char *data, bool text));
|
||||
FN(void, set_saved_string, (const char *s));
|
||||
extern "C" const char str_slinky_in_profiles[], str_profiles[];
|
||||
extern "C" unsigned char settings_dirty_flag;
|
||||
|
||||
extern "C" bool load_module_states(const char *text, Mods *mods) {
|
||||
cJSON *root = cjson_parse(text);
|
||||
if (!root) return false;
|
||||
bool ok = true;
|
||||
for (unsigned long long i = 0; i < mods->count; i++) {
|
||||
Mod *module = mods->data[i];
|
||||
if (module->flags & 0x10) continue;
|
||||
if (!load_module_settings(root, mods, module)) { ok = false; break; }
|
||||
}
|
||||
cjson_delete(root);
|
||||
return ok;
|
||||
}
|
||||
extern "C" char *serialize_module_states(Mods *mods) {
|
||||
cJSON *root = cjson_create_object();
|
||||
char *out = 0;
|
||||
if (!root) goto done;
|
||||
for (unsigned long long i = 0; i < mods->count; i++) {
|
||||
Mod *module = mods->data[i];
|
||||
if (module->flags & 0x10) continue;
|
||||
if (!save_module_settings(root, module)) goto done;
|
||||
}
|
||||
out = cjson_print_unformatted(root);
|
||||
done:
|
||||
cjson_delete(root);
|
||||
return out;
|
||||
}
|
||||
extern "C" int save_profile(const char *name, bool text, Mods *mods) {
|
||||
char *path;
|
||||
int error_code = settings_path(&path);
|
||||
if (error_code) return error_code;
|
||||
if ((error_code = settings_path_append(&path, str_slinky_in_profiles)) != 0) goto clean;
|
||||
if ((error_code = settings_path_ensure(&path)) != 0) goto clean;
|
||||
if ((error_code = settings_path_append(&path, str_profiles)) != 0) goto clean;
|
||||
if ((error_code = settings_path_ensure(&path)) != 0) goto clean;
|
||||
if ((error_code = settings_path_append(&path, name)) != 0) goto clean;
|
||||
{
|
||||
char *json = serialize_module_states(mods);
|
||||
if (!json) {
|
||||
error_code = 0x1388;
|
||||
} else {
|
||||
error_code = write_text_file(&path, json, text);
|
||||
if (!error_code) {
|
||||
set_saved_string(name);
|
||||
settings_dirty_flag = 0;
|
||||
}
|
||||
cjson_free_string(json);
|
||||
}
|
||||
}
|
||||
clean:
|
||||
free_path(&path);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
// ---- setting visitors: mode 0 = load from json, 1 = save to json, 2 = remember, 3 = restore ----
|
||||
// cflags-free: the original used a 4-entry jump table for the mode switch
|
||||
struct Visit { cJSON *json; int mode; unsigned char failed; };
|
||||
FN(cJSON *, cjson_get_object_item, (const cJSON *object, const char *name)); // cJSON_GetObjectItemCaseSensitive
|
||||
FN(int, cjson_is_bool, (const cJSON *object)); // cJSON_IsBool
|
||||
FN(int, cjson_is_true, (const cJSON *object)); // cJSON_IsTrue
|
||||
FN(int, cjson_is_number, (const cJSON *object)); // cJSON_IsNumber
|
||||
FN(double, cjson_get_number_value, (const cJSON *object)); // cJSON_GetNumberValue
|
||||
FN(int, cjson_is_object, (const cJSON *object)); // cJSON_IsObject
|
||||
FN(cJSON *, cjson_add_item_to_object, (cJSON *object, const char *name, int b)); // cJSON_AddBoolToObject
|
||||
FN(cJSON *, cjson_add_number_to_object, (cJSON *object, const char *name, double v)); // cJSON_AddNumberToObject
|
||||
FN(cJSON *, cjson_add_bool_to_object, (cJSON *object, const char *name)); // cJSON_AddObjectToObject
|
||||
extern "C" const char str_min[], str_max[]; // "min", "max"
|
||||
struct BoolSet { char padding_00[0x18]; unsigned char value, saved; };
|
||||
extern "C" void visit_bool_setting(Visit *visitor, const char *name, BoolSet *s) {
|
||||
if (visitor->failed) return;
|
||||
switch (visitor->mode) {
|
||||
case 0: {
|
||||
unsigned char b = s->saved;
|
||||
cJSON *json = cjson_get_object_item(visitor->json, name);
|
||||
if (cjson_is_bool(json)) b = cjson_is_true(json) != 0;
|
||||
s->value = b;
|
||||
break;
|
||||
}
|
||||
case 2: s->saved = s->value; break;
|
||||
case 3: s->value = s->saved; break;
|
||||
case 1: {
|
||||
cJSON *j = visitor->json;
|
||||
|
||||
visitor->failed |= cjson_add_item_to_object(j, name, s->value) == 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
struct FloatSet { char padding_00[0x28]; float min, max, value, saved; };
|
||||
extern "C" void visit_float_setting(Visit *visitor, const char *name, FloatSet *s) {
|
||||
if (visitor->failed) return;
|
||||
int mode = visitor->mode;
|
||||
if (mode == 0) {
|
||||
cJSON *json = cjson_get_object_item(visitor->json, name);
|
||||
float r = s->saved, lo = s->min, hi = s->max;
|
||||
if (cjson_is_number(json)) {
|
||||
float d = (float)cjson_get_number_value(json);
|
||||
if (d == d) {
|
||||
r = lo;
|
||||
if (!(lo > d)) {
|
||||
r = d;
|
||||
if (d > hi) r = hi;
|
||||
}
|
||||
}
|
||||
}
|
||||
s->value = r;
|
||||
} else {
|
||||
|
||||
if (mode == 1) {
|
||||
cJSON *j = visitor->json;
|
||||
|
||||
visitor->failed |= cjson_add_number_to_object(j, name, (double)s->value) == 0;
|
||||
} else if (mode == 2) {
|
||||
s->saved = s->value;
|
||||
} else if (mode == 3) {
|
||||
s->value = s->saved;
|
||||
}
|
||||
}
|
||||
}
|
||||
struct Range { float min, max; };
|
||||
struct RangeSet { char padding_00[0x38]; float lo, hi; Range cur, saved; };
|
||||
static inline __attribute__((always_inline)) float clampf(float x, float lo, float hi) {
|
||||
float r = lo;
|
||||
if (!(lo > x)) {
|
||||
r = x;
|
||||
if (x > hi) r = hi;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
extern "C" void visit_range_setting(Visit *visitor, const char *name, RangeSet *s) {
|
||||
if (visitor->failed) return;
|
||||
int mode = visitor->mode;
|
||||
if (mode == 0) {
|
||||
cJSON *json = cjson_get_object_item(visitor->json, name);
|
||||
Range r = s->saved;
|
||||
float lo = s->lo, hi = s->hi;
|
||||
if (cjson_is_object(json)) {
|
||||
float mn = (float)cjson_get_number_value(cjson_get_object_item(json, str_min));
|
||||
if (mn == mn) {
|
||||
float mx = (float)cjson_get_number_value(cjson_get_object_item(json, str_max));
|
||||
if (mx == mx) {
|
||||
float a = mx < mn ? mx : mn;
|
||||
r.min = clampf(a, lo, hi);
|
||||
r.max = clampf(mx, lo, hi);
|
||||
}
|
||||
}
|
||||
}
|
||||
s->cur = r;
|
||||
} else {
|
||||
|
||||
if (mode == 1) {
|
||||
cJSON *o = cjson_add_bool_to_object(visitor->json, name);
|
||||
if (!o) { visitor->failed = 1; return; }
|
||||
visitor->failed |= cjson_add_number_to_object(o, str_min, (double)s->cur.min) == 0;
|
||||
visitor->failed |= cjson_add_number_to_object(o, str_max, (double)s->cur.max) == 0;
|
||||
} else if (mode == 2) {
|
||||
s->saved = s->cur;
|
||||
} else if (mode == 3) {
|
||||
s->cur = s->saved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- current profile name / dirty flag, profile file helpers ----
|
||||
extern "C" char *settings_buffer;
|
||||
extern "C" char *get_saved_string() { return settings_buffer; }
|
||||
extern "C" unsigned char get_profile_dirty() { return settings_dirty_flag; }
|
||||
extern "C" __attribute__((noinline)) void set_profile_dirty(unsigned char v) { settings_dirty_flag = v; }
|
||||
FN(int, delete_file_path, (char **path));
|
||||
FN(int, open_path_explorer, (char **path));
|
||||
FN(int, find_files, (char **path, void *object, void *other_object));
|
||||
extern "C" int delete_profile_file(const char *name) { // delete profile file
|
||||
char *path;
|
||||
int error_code = settings_path(&path);
|
||||
if (error_code) return error_code;
|
||||
if ((error_code = settings_path_append(&path, str_slinky_in_profiles)) == 0 && (error_code = settings_path_append(&path, str_profiles)) == 0 &&
|
||||
(error_code = settings_path_append(&path, name)) == 0)
|
||||
error_code = delete_file_path(&path);
|
||||
free_path(&path);
|
||||
return error_code;
|
||||
}
|
||||
extern "C" int open_profiles_folder() { // open the profiles folder
|
||||
char *path;
|
||||
int error_code = settings_path(&path);
|
||||
if (error_code) return error_code;
|
||||
if ((error_code = settings_path_append(&path, str_slinky_in_profiles)) == 0 && (error_code = settings_path_append(&path, str_profiles)) == 0)
|
||||
error_code = open_path_explorer(&path);
|
||||
free_path(&path);
|
||||
return error_code;
|
||||
}
|
||||
extern "C" int list_profiles(void *ctx, void *callback) { // list profiles
|
||||
char *path;
|
||||
int error_code = settings_path(&path);
|
||||
if (error_code) return error_code;
|
||||
if ((error_code = settings_path_append(&path, str_slinky_in_profiles)) == 0 && (error_code = settings_path_ensure(&path)) == 0 &&
|
||||
(error_code = settings_path_append(&path, str_profiles)) == 0 && (error_code = settings_path_ensure(&path)) == 0)
|
||||
error_code = find_files(&path, ctx, callback);
|
||||
free_path(&path);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
FN(int, cjson_is_string, (const cJSON *object)); // cJSON_IsString
|
||||
FN(cJSON *, cjson_add_string_to_object, (cJSON *object, const char *name, const char *s));
|
||||
extern "C" int libc_strcmp(const char *left, const char *right); // strcmp
|
||||
struct JStr { char padding_00[0x20]; const char *valuestring; };
|
||||
struct ChoiceSet { char padding_00[0x20]; unsigned count; char padding_24[4]; const char **names; unsigned value, saved; };
|
||||
extern "C" void visit_choice_setting(Visit *visitor, const char *name, ChoiceSet *s) {
|
||||
if (visitor->failed) return;
|
||||
int mode = visitor->mode;
|
||||
if (mode == 0) {
|
||||
unsigned r = s->saved;
|
||||
cJSON *json = cjson_get_object_item(visitor->json, name);
|
||||
if (cjson_is_string(json)) {
|
||||
unsigned long long n = s->count;
|
||||
for (unsigned long long i = 0; i < n; i++)
|
||||
if (libc_strcmp(s->names[i], ((JStr *)json)->valuestring) == 0) { r = (unsigned)i; break; }
|
||||
}
|
||||
s->value = r;
|
||||
} else {
|
||||
|
||||
if (mode == 1) {
|
||||
if (!s->count) {
|
||||
visitor->failed = 1;
|
||||
return;
|
||||
}
|
||||
unsigned v = s->value < s->count ? s->value : 0;
|
||||
visitor->failed |= cjson_add_string_to_object(visitor->json, name, s->names[v]) == 0;
|
||||
} else if (mode == 2) s->saved = s->value;
|
||||
else if (mode == 3) s->value = s->saved;
|
||||
}
|
||||
}
|
||||
struct Color { float r, g, b; };
|
||||
extern "C" const float threshold_1_in_profiles; // 1.0
|
||||
extern "C" const char str_r_in_profiles[], str_g_in_profiles[], str_b_in_profiles[];
|
||||
static inline __attribute__((always_inline)) bool unit(float x) {
|
||||
|
||||
return x >= 0.0f && !(x > threshold_1_in_profiles);
|
||||
}
|
||||
extern "C" Color parse_color(const cJSON *json, const Color *def) {
|
||||
if (cjson_is_object(json)) {
|
||||
float r = (float)cjson_get_number_value(cjson_get_object_item(json, str_r_in_profiles));
|
||||
if (unit(r)) {
|
||||
float g = (float)cjson_get_number_value(cjson_get_object_item(json, str_g_in_profiles));
|
||||
if (unit(g)) {
|
||||
float b = (float)cjson_get_number_value(cjson_get_object_item(json, str_b_in_profiles));
|
||||
if (unit(b)) return Color{r, g, b};
|
||||
}
|
||||
}
|
||||
}
|
||||
return *def;
|
||||
}
|
||||
struct ColorSet { char padding_00[8]; Color cur, saved; };
|
||||
extern "C" void visit_color_setting(Visit *visitor, const char *name, ColorSet *s) {
|
||||
if (visitor->failed) return;
|
||||
int mode = visitor->mode;
|
||||
if (mode == 0) {
|
||||
cJSON *json = cjson_get_object_item(visitor->json, name);
|
||||
Color def = s->saved;
|
||||
s->cur = parse_color(json, &def);
|
||||
} else {
|
||||
|
||||
if (mode == 1) {
|
||||
cJSON *o = cjson_add_bool_to_object(visitor->json, name);
|
||||
if (!o) { visitor->failed = 1; return; }
|
||||
visitor->failed |= cjson_add_number_to_object(o, str_r_in_profiles, (double)s->cur.r) == 0;
|
||||
visitor->failed |= cjson_add_number_to_object(o, str_g_in_profiles, (double)s->cur.g) == 0;
|
||||
visitor->failed |= cjson_add_number_to_object(o, str_b_in_profiles, (double)s->cur.b) == 0;
|
||||
} else if (mode == 2) {
|
||||
s->saved = s->cur;
|
||||
} else if (mode == 3) {
|
||||
s->cur = s->saved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- per-module json: "__on", "__bind", "__hold" and the module's own settings visitor ----
|
||||
struct MCfg { const char *name; char padding_08[0xc]; unsigned char enabled; char padding_15[3]; unsigned flags; unsigned bind;
|
||||
unsigned char hold; char padding_21[0x1df]; void (*visit)(Visit *); };
|
||||
FN(void, queue_module_toggle, (Mods *module_manager, MCfg *module, unsigned char enabled));
|
||||
FN(void, apply_module_state, (Mods *mods, MCfg *m, int key));
|
||||
extern "C" const char mc_obf_name_on[], str_bind_in_profiles[], str_hold[];
|
||||
extern "C" const double offset_0_5_in_profiles; // 0.5
|
||||
struct JNum { char padding_00[0x30]; double vd; };
|
||||
extern "C" unsigned char load_module_settings(cJSON *root, Mods *mods, Mod *mm) {
|
||||
MCfg *m = (MCfg *)mm;
|
||||
cJSON *json = cjson_get_object_item(root, m->name);
|
||||
unsigned f = m->flags;
|
||||
if (!(f & 2)) {
|
||||
queue_module_toggle(mods, m, cjson_is_true(cjson_get_object_item(json, mc_obf_name_on)) != 0);
|
||||
f = m->flags;
|
||||
}
|
||||
if (!(f & 1)) {
|
||||
cJSON *item = cjson_get_object_item(json, str_bind_in_profiles);
|
||||
int key;
|
||||
if (cjson_is_number(item)) {
|
||||
int k = (int)(((JNum *)item)->vd + offset_0_5_in_profiles);
|
||||
key = 0;
|
||||
if ((unsigned)(k - 0x68) >= (unsigned)-0x67) key = k;
|
||||
} else {
|
||||
key = 0;
|
||||
}
|
||||
apply_module_state(mods, m, key);
|
||||
m->hold = (m->flags & 8) ? 0 : cjson_is_true(cjson_get_object_item(json, str_hold)) != 0;
|
||||
}
|
||||
if (!m->visit) return true;
|
||||
Visit vv = {json, 0, 0};
|
||||
m->visit(&vv);
|
||||
return !vv.failed;
|
||||
}
|
||||
extern "C" unsigned char save_module_settings(cJSON *root, Mod *mm) {
|
||||
MCfg *m = (MCfg *)mm;
|
||||
cJSON *json = cjson_add_bool_to_object(root, m->name);
|
||||
unsigned f = m->flags;
|
||||
if (!(f & 2) && m->enabled == 1) {
|
||||
if (!cjson_add_item_to_object(json, mc_obf_name_on, 1)) return false;
|
||||
f = m->flags;
|
||||
}
|
||||
if (!(f & 1)) {
|
||||
if (m->bind) {
|
||||
if (!cjson_add_number_to_object(json, str_bind_in_profiles, (double)m->bind)) return false;
|
||||
f = m->flags;
|
||||
}
|
||||
if (!(f & 8) && m->hold == 1 && !cjson_add_item_to_object(json, str_hold, 1)) return false;
|
||||
}
|
||||
if (!m->visit) return true;
|
||||
Visit vv = {json, 1, 0};
|
||||
m->visit(&vv);
|
||||
return !vv.failed;
|
||||
}
|
||||
|
||||
// ---- ImGui widget wrappers: mark the profile dirty when a widget changed its setting ----
|
||||
FN(unsigned char, draw_toggle_row, (void *gui, void *window, const char *label, void *setting, void *value));
|
||||
FN(unsigned char, draw_slider_row, (void *gui, void *window, void *label, void *format, void *setting, float min, float max, float step, float scale,
|
||||
void *value));
|
||||
FN(unsigned char, draw_range_slider_row, (void *gui, void *window, void *label, void *format, void *range_format, void *setting, float min, float max,
|
||||
float step, float scale, void *low, void *high));
|
||||
// bool setting: label, then the switch value
|
||||
struct ToggleSetting { char padding_00[0x10]; void *label; unsigned char value; };
|
||||
extern "C" void widget_bool(void *window, void *gui, ToggleSetting *setting) {
|
||||
if (draw_toggle_row(gui, window, (const char *)setting->label, setting, &setting->value)) set_profile_dirty(1);
|
||||
}
|
||||
// float setting: label, display format, step, display scale, bounds, current value
|
||||
struct SliderSetting { char padding_00[0x10]; void *label, *format; float step, scale, min, max, value; };
|
||||
extern "C" void widget_slider(void *window, void *gui, SliderSetting *setting) {
|
||||
if (draw_slider_row(gui, window, setting->label, setting->format, setting, setting->min, setting->max, setting->step, setting->scale, &setting->value)) set_profile_dirty(1);
|
||||
}
|
||||
// range setting: label, display formats, step, display scale, bounds, current low / high
|
||||
struct RangeSetting { char padding_00[0x18]; void *label, *format, *range_format; float step, scale, min, max, low, high; };
|
||||
extern "C" void widget_range(void *window, void *gui, RangeSetting *setting) {
|
||||
if (draw_range_slider_row(gui, window, setting->label, setting->format, setting->range_format, setting, setting->min, setting->max, setting->step, setting->scale, &setting->low, &setting->high)) set_profile_dirty(1);
|
||||
}
|
||||
|
||||
// ---- list setting (names, optionally with a colour each) ----
|
||||
FN(cJSON *, cjson_add_array_to_object, (cJSON *object, const char *name)); // cJSON_AddArrayToObject
|
||||
FN(cJSON *, cjson_create_string, (const char *text)); // cJSON_CreateString
|
||||
FN(int, cjson_add_item_to_array, (cJSON *arr, cJSON *item)); // cJSON_AddItemToArray
|
||||
FN(int, cjson_is_array, (const cJSON *object)); // cJSON_IsArray
|
||||
extern "C" const char str_name[], str_color[]; // "name", "color"
|
||||
extern "C" const double default_colour_rg_one_as_double; // {1.0f, 1.0f}
|
||||
struct JNode2 { JNode2 *next, *prev, *child; int type; const char *vs; };
|
||||
struct ListSet {
|
||||
char padding_00[0x18]; int kind; int padding_1c; void *ctx;
|
||||
unsigned long long (*count)(void *ctx);
|
||||
const char *(*name)(void *ctx, unsigned long long i);
|
||||
const float *(*color)(void *ctx, unsigned long long i);
|
||||
void (*add)(void *ctx, const char *name, Color *c);
|
||||
void (*remove)(void *ctx, unsigned long long i);
|
||||
void (*clear)(void *ctx);
|
||||
};
|
||||
extern "C" void visit_list_setting(Visit *visitor, const char *name, ListSet *s) {
|
||||
if (visitor->failed) return;
|
||||
int mode = visitor->mode;
|
||||
if (mode == 0) {
|
||||
s->clear(s->ctx);
|
||||
JNode2 *arr = (JNode2 *)cjson_get_object_item(visitor->json, name);
|
||||
unsigned char notarr = !cjson_is_array((cJSON *)arr);
|
||||
if ((arr == 0) | notarr) return;
|
||||
for (JNode2 *e = arr->child; e; e = e->next) {
|
||||
if (s->kind == 1) {
|
||||
if (!cjson_is_object((cJSON *)e)) continue;
|
||||
JNode2 *item = (JNode2 *)cjson_get_object_item((cJSON *)e, str_name);
|
||||
if (!cjson_is_string((cJSON *)item)) continue;
|
||||
cJSON *col = cjson_get_object_item((cJSON *)e, str_color);
|
||||
void *ctx = s->ctx;
|
||||
auto add = s->add;
|
||||
const char *str = item->vs;
|
||||
Color def;
|
||||
*(double *)&def = default_colour_rg_one_as_double;
|
||||
def.b = 1.0f;
|
||||
Color c = parse_color(col, &def);
|
||||
add(ctx, str, &c);
|
||||
} else if (s->kind == 0) {
|
||||
if (!cjson_is_string((cJSON *)e)) continue;
|
||||
Color c = {0, 0, 0};
|
||||
s->add(s->ctx, e->vs, &c);
|
||||
}
|
||||
}
|
||||
} else if (mode == 1) {
|
||||
cJSON *arr = cjson_add_array_to_object(visitor->json, name);
|
||||
if (!arr) { visitor->failed = 1; return; }
|
||||
if (!s->count(s->ctx)) return;
|
||||
for (unsigned long long i = 0; i < s->count(s->ctx); i++) {
|
||||
unsigned char bad;
|
||||
if (s->kind == 1) {
|
||||
bad = 1;
|
||||
cJSON *json = cjson_create_object();
|
||||
if (!json) goto fail;
|
||||
if (!cjson_add_string_to_object(json, str_name, s->name(s->ctx, i))) goto fail;
|
||||
{
|
||||
cJSON *c = cjson_add_bool_to_object(json, str_color);
|
||||
if (!c) goto fail;
|
||||
const float *col = s->color(s->ctx, i);
|
||||
if (!cjson_add_number_to_object(c, str_r_in_profiles, (double)col[0]) || !cjson_add_number_to_object(c, str_g_in_profiles, (double)col[1]) ||
|
||||
!cjson_add_number_to_object(c, str_b_in_profiles, (double)col[2]))
|
||||
goto fail;
|
||||
bad = cjson_add_item_to_array(arr, json) == 0;
|
||||
}
|
||||
} else if (s->kind == 0) {
|
||||
cJSON *str = cjson_create_string(s->name(s->ctx, i));
|
||||
bad = str ? cjson_add_item_to_array(arr, str) == 0 : 1;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
fail:
|
||||
visitor->failed |= bad;
|
||||
}
|
||||
} else if (mode == 3) {
|
||||
s->clear(s->ctx);
|
||||
}
|
||||
}
|
||||
|
||||
FN(void, libc_free, (void *ptr)); // free
|
||||
extern "C" void clear_saved_string() {
|
||||
if (settings_buffer) libc_free(settings_buffer);
|
||||
settings_buffer = 0;
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
// JNI bridge 0x22000
|
||||
// cflags: /Ob1 /clang:-fno-slp-vectorize
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jcall.h"
|
||||
struct Rot { float yaw, pitch; };
|
||||
struct Vec3 { double x, y, z; };
|
||||
|
||||
extern "C" jobject get_minecraft(Jvm *jvm) {
|
||||
return JCALL(jvm, GetStaticObjectField, ids->Minecraft, ids->Minecraft_theMinecraft);
|
||||
}
|
||||
|
||||
// Lunar and other modern remappers keep the MCP class names but rename the
|
||||
// members (Mojang names such as f_71439_g). The generated JIds tables only
|
||||
// know the vanilla MCP/SRG spellings, so resolve the two fields the frame hooks
|
||||
// need on first use instead of passing a null jfieldID to GetObjectField.
|
||||
static jfieldID resolve_minecraft_field(Jvm *jvm, jfieldID *slot,
|
||||
const char *type1, const char *type2,
|
||||
const char *const *names, int count) {
|
||||
if (*slot) return *slot;
|
||||
JNIEnv *env = jvm->env;
|
||||
jclass cls = (jclass)jvm->ids->Minecraft;
|
||||
for (int t = 0; t < 2; t++) {
|
||||
const char *sig = t == 0 ? type1 : type2;
|
||||
if (!sig) continue;
|
||||
for (int i = 0; i < count; i++) {
|
||||
jfieldID f = (*env)->GetFieldID(env, cls, names[i], sig);
|
||||
if (f) {
|
||||
*slot = f;
|
||||
return f;
|
||||
}
|
||||
(*env)->ExceptionClear(env);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject get_local_player(Jvm *jvm, void *minecraft) {
|
||||
static const char *const names[] = { "thePlayer", "field_71439_g", "f_71439_g", "player" };
|
||||
jfieldID id = resolve_minecraft_field(jvm, &jvm->ids->Minecraft_thePlayer,
|
||||
"Lnet/minecraft/client/entity/EntityPlayerSP;",
|
||||
"Lnet/minecraft/client/entity/EntityClientPlayerMP;",
|
||||
names, 4);
|
||||
if (!id) return nullptr;
|
||||
return JCALL(jvm, GetObjectField, minecraft, id);
|
||||
}
|
||||
|
||||
extern "C" jobject get_world(Jvm *jvm, void *minecraft) {
|
||||
static const char *const names[] = { "theWorld", "field_71441_e", "f_71441_e", "world" };
|
||||
jfieldID id = resolve_minecraft_field(jvm, &jvm->ids->Minecraft_theWorld,
|
||||
"Lnet/minecraft/world/World;",
|
||||
"Lnet/minecraft/client/multiplayer/WorldClient;",
|
||||
names, 4);
|
||||
if (!id) return nullptr;
|
||||
return JCALL(jvm, GetObjectField, minecraft, id);
|
||||
}
|
||||
|
||||
extern "C" jobject get_timer(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetObjectField, minecraft, ids->Minecraft_timer);
|
||||
}
|
||||
|
||||
extern "C" jobject get_game_settings(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetObjectField, minecraft, ids->Minecraft_gameSettings);
|
||||
}
|
||||
|
||||
extern "C" jobject get_minecraft_render_global(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetObjectField, minecraft, ids->Minecraft_renderGlobal);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_minecraft(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetObjectField, minecraft, ids->Minecraft_entityRenderer);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_minecraft_render_item(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetObjectField, minecraft, ids->Minecraft_renderItem);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_minecraft_render_engine(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetObjectField, minecraft, ids->Minecraft_renderEngine);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_minecraft_player_controller(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetObjectField, minecraft, ids->Minecraft_playerController);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_minecraft_object_mouse_over(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetObjectField, minecraft, ids->Minecraft_objectMouseOver);
|
||||
}
|
||||
|
||||
extern "C" void java_set_minecraft(Jvm *jvm, void *minecraft, void *value) {
|
||||
JCALL(jvm, SetObjectField, minecraft, ids->Minecraft_objectMouseOver, value);
|
||||
}
|
||||
|
||||
extern "C" bool is_in_game(Jvm *jvm, void *minecraft) {
|
||||
return JCALLN(jvm, GetBooleanField, minecraft, ids->Minecraft_inGameHasFocus) != 0;
|
||||
}
|
||||
|
||||
extern "C" jint java_get_minecraft_left_click_counter(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetIntField, minecraft, ids->Minecraft_leftClickCounter);
|
||||
}
|
||||
|
||||
extern "C" void java_set_minecraft_left_click_counter(Jvm *jvm, void *minecraft, int value) {
|
||||
JCALL(jvm, SetIntField, minecraft, ids->Minecraft_leftClickCounter, value);
|
||||
}
|
||||
|
||||
extern "C" jobject get_render_view_entity(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetObjectField, minecraft, ids->Minecraft_renderViewEntity);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_minecraft_get_net_handler(Jvm *jvm, void *minecraft) {
|
||||
return JCALLN(jvm, CallObjectMethod, minecraft, ids->Minecraft_getNetHandler);
|
||||
}
|
||||
|
||||
extern "C" void java_call_minecraft_click_mouse(Jvm *jvm, void *minecraft) {
|
||||
JCALLN(jvm, CallVoidMethod, minecraft, ids->Minecraft_clickMouse);
|
||||
}
|
||||
|
||||
extern "C" void java_call_minecraft_right_click_mouse(Jvm *jvm, void *minecraft) {
|
||||
JCALLN(jvm, CallVoidMethod, minecraft, ids->Minecraft_rightClickMouse);
|
||||
}
|
||||
|
||||
// ---- hand-written
|
||||
FN(jobject, java_get_render_manager_instance, (Jvm *jvm, jobject render_manager));
|
||||
|
||||
extern "C" jobject get_current_screen(Jvm *jvm, jobject object) {
|
||||
jobject x = JCALLN(jvm, GetObjectField, object, (jfieldID)ids->Minecraft_currentScreen);
|
||||
return JCALLN(jvm, IsInstanceOf, x, (jclass)ids->GuiContainer) ? x : nullptr;
|
||||
}
|
||||
extern "C" jobject java_get_minecraft_render_manager(Jvm *jvm, jobject minecraft) {
|
||||
if (jvm->ids->variant != 0)
|
||||
return JCALL(jvm, GetObjectField, minecraft, (jfieldID)ids->Minecraft_renderManager);
|
||||
return java_get_render_manager_instance(jvm, minecraft);
|
||||
}
|
||||
|
||||
// ---- ClassFileLoadHook: rewrite the classes listed in the hook table, injecting "SlinkyHooks13"
|
||||
struct MethodKey { char b[32]; };
|
||||
struct MethodPat { long long a, b; };
|
||||
struct HookTab { char padding_00[0x18]; MethodPat *pats; unsigned long long n; };
|
||||
struct HookCls { jobject cls; void *data; int len; char padding_14[0x14]; }; // 0x28 bytes each
|
||||
|
||||
extern "C" volatile int classfile_hooks_first_error; // first error (sticky)
|
||||
extern "C" volatile long classfile_hooks_sites_rewritten = 0; // instructions replaced by the class currently being rewritten (diagnostics)
|
||||
extern "C" unsigned char classfile_hooks_1_while_hooks_are_installed; // 1 while the hooks are installed
|
||||
extern "C" unsigned char classfile_hooks_world_client_pending = 0; // WorldClient retransform skipped at install on Java 9+
|
||||
extern "C" unsigned char classfile_hooks_world_client_patched = 0; // ... and has since been applied
|
||||
extern "C" unsigned char java_natives_frame_client_shutting_down_natives_check; // the client is unloading: the transformer must go inert
|
||||
extern "C" void slinky_logf(const char *fmt, ...);
|
||||
static unsigned char g_disarming; // 1 while remove_class_hooks restores the original bytecode (atomic)
|
||||
extern "C" HookCls hooked_class_minecraft, hooked_class_entity_renderer, hooked_class_render_manager, hooked_class_renderer_living_entity, hooked_class_render_player,
|
||||
hooked_class_net_handler_play_client, hooked_class_world_client, hooked_class_entity, hooked_class_entity_living_base;
|
||||
extern "C" const char str_slinkyhooks13[]; // "SlinkyHooks13"
|
||||
FN(bool, compare_member_name, (MethodKey left, MethodPat right));
|
||||
struct HookTable; struct CP; struct RBuf22; struct WBuf22;
|
||||
FN(int, rewrite_instruction, (HookTable *t, CP *constant_pool, RBuf22 *in, WBuf22 *out)); // instruction patcher (below)
|
||||
FN(int, rewrite_class_file, (const unsigned char *data, int len, unsigned char **out, int *outLen, const char *hookCls,
|
||||
void *hookData, int hookLen, void *match, HookCls *h));
|
||||
FN(void, libc_free, (void *ptr)); // free
|
||||
FN(void *, libc_memcpy_alt, (void *dest, const void *src, long long n)); // memcpy
|
||||
|
||||
struct JvmtiEnv_ { void *fn[64]; };
|
||||
typedef int (*JvmtiAllocate)(JvmtiEnv_ **, long long, unsigned char **);
|
||||
|
||||
// method matcher passed to the class rewriter: (-1,-1) entries match everything
|
||||
extern "C" int match_method(HookTab *t, MethodKey *k, void **outFn, MethodPat **outPat) {
|
||||
static int pat_logs;
|
||||
for (unsigned long long i = 0; i < t->n; i++) {
|
||||
MethodPat *p = &t->pats[i * 2];
|
||||
if (p->a == -1 && p->b == -1) {
|
||||
*outFn = (void *)rewrite_instruction;
|
||||
*outPat = p;
|
||||
break;
|
||||
}
|
||||
unsigned long long na = (unsigned long long)p->a, da = (unsigned long long)p->b;
|
||||
if (pat_logs < 12) { pat_logs++; slinky_logf("patcmp: i=%llu name=%p desc=%p", i, p->a, p->b); }
|
||||
if (na < 0x10000 || da < 0x10000) {
|
||||
slinky_logf("patcmp: bad pattern name=%llx desc=%llx (skipped)", na, da);
|
||||
continue;
|
||||
}
|
||||
if (compare_member_name(*k, *p)) {
|
||||
*outFn = (void *)rewrite_instruction;
|
||||
*outPat = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" void class_file_load_hook(JvmtiEnv_ **jvmti, JNIEnv *env, jclass redefined, jobject loader, const char *name,
|
||||
jobject pd, int len, const unsigned char *data, int *newLen, unsigned char **newData) {
|
||||
if (classfile_hooks_first_error != 0) return;
|
||||
// remove_class_hooks sets g_disarming before it retransforms: during that call the JVM may replay this hook
|
||||
// for the classes being restored, and re-patching them would silently undo the disarm. Handing the
|
||||
// bytes back untouched makes the retransform a true restore. (The install also runs with the hook
|
||||
// active and g_disarming clear, so it still patches.)
|
||||
if (__atomic_load_n(&g_disarming, __ATOMIC_SEQ_CST) || java_natives_frame_client_shutting_down_natives_check) return;
|
||||
// JVMTI passes class_being_redefined = NULL when a class is loaded for the first time. IsSameObject(NULL,
|
||||
// NULL) is true, so an empty table entry (all of them before build_records ran, or one whose class did
|
||||
// not resolve) would match every class load and rewrite it with the wrong hook record. The nine hooked
|
||||
// classes are all loaded by init_jni_ids; install patches them with RetransformClasses.
|
||||
if (!redefined) return;
|
||||
HookCls *pctx;
|
||||
#define TRY(x) if (x.cls && (*env)->IsSameObject(env, x.cls, redefined)) { pctx = &x; goto found; }
|
||||
TRY(hooked_class_minecraft) TRY(hooked_class_entity_renderer) TRY(hooked_class_render_manager) TRY(hooked_class_renderer_living_entity) TRY(hooked_class_render_player)
|
||||
TRY(hooked_class_net_handler_play_client) TRY(hooked_class_world_client) TRY(hooked_class_entity) TRY(hooked_class_entity_living_base)
|
||||
#undef TRY
|
||||
return;
|
||||
found:
|
||||
slinky_logf("classhook-seen: %s", name ? name : "?");
|
||||
unsigned char *buf = nullptr;
|
||||
int outLen;
|
||||
__atomic_store_n(&classfile_hooks_sites_rewritten, 0, __ATOMIC_RELAXED);
|
||||
int err = rewrite_class_file(data, len, &buf, &outLen, str_slinkyhooks13, pctx->data, pctx->len, (void *)match_method, pctx);
|
||||
long sites = __atomic_load_n(&classfile_hooks_sites_rewritten, __ATOMIC_RELAXED);
|
||||
if (err == 0)
|
||||
slinky_logf("classhook: %s sites=%ld %d->%d", name ? name : "?", sites, len, outLen);
|
||||
else
|
||||
slinky_logf("classhook: %s sites=%ld err=%d", name ? name : "?", sites, err);
|
||||
if (err == 0) {
|
||||
unsigned char *mem = nullptr;
|
||||
JvmtiAllocate alloc = (JvmtiAllocate)(*jvmti)->fn[45];
|
||||
int alloc_error = alloc(jvmti, outLen, &mem);
|
||||
if (alloc_error != 0 || !mem) {
|
||||
err = (alloc_error ? alloc_error : 100) + 12000; // JVMTI_ERROR_NULL_POINTER
|
||||
libc_free(buf);
|
||||
__sync_val_compare_and_swap(&classfile_hooks_first_error, 0, err);
|
||||
return;
|
||||
}
|
||||
libc_memcpy_alt(mem, buf, outLen);
|
||||
*newData = mem;
|
||||
*newLen = outLen;
|
||||
libc_free(buf);
|
||||
return;
|
||||
}
|
||||
__sync_val_compare_and_swap(&classfile_hooks_first_error, 0, err);
|
||||
}
|
||||
|
||||
// ---- JNI bridge functions found by the gap scan (no caller outside the virtualised init) ----
|
||||
extern "C" jint java_get_minecraft_right_click_delay_timer(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetIntField, minecraft, ids->Minecraft_rightClickDelayTimer);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_minecraft_add_scheduled_task(Jvm *jvm, void *minecraft, void *value) {
|
||||
return JCALLN(jvm, CallObjectMethod, minecraft, ids->Minecraft_addScheduledTask, value);
|
||||
}
|
||||
|
||||
// ---- JVMTI: switch the class hooks off again (undo of the install) ----
|
||||
// Disables the ClassFileLoadHook event, retransforms the 9 hooked classes so the game gets its original bytecode
|
||||
// back, gives the capabilities up and drops the global ref of the transformer class. Errors are JVMTI error + 12000.
|
||||
struct JvmtiTable { void *fn[160]; };
|
||||
typedef int (*JvmtiSetEventMode)(JvmtiTable **, int mode, int event, void *thread, ...);
|
||||
typedef int (*JvmtiRetransform)(JvmtiTable **, int count, jclass *classes);
|
||||
typedef int (*JvmtiRelinquish)(JvmtiTable **, const void *caps);
|
||||
#define JVMTI(env, idx, T) ((T)(*(env))->fn[idx])
|
||||
extern "C" unsigned char classfile_hooks_1_while_hooks_are_installed; // 1 while the hooks are installed
|
||||
extern "C" jobject classfile_hooks_global_ref_transformer_class; // global ref of the transformer class
|
||||
extern "C" const char ptr_to_hook_installer_class_file_slinkyhooks13[]; // JVMTI capabilities that were added
|
||||
#define JVMTI_CHECK(call) \
|
||||
do { \
|
||||
int check_error = (call); \
|
||||
if (check_error) { \
|
||||
err = check_error + 0x2ee0; \
|
||||
goto out; \
|
||||
} \
|
||||
} while (0)
|
||||
// Deferred half of the Java 9+ install: the installer skips RetransformClasses(WorldClient) because
|
||||
// Lunar/Badlion transformers can deadlock in it. Retry from the game thread after the installer has
|
||||
// completed; if it succeeds the class is patched and must also be restored by remove_class_hooks.
|
||||
extern "C" int retransform_pending_world_client(void *jvmti_env) {
|
||||
if (!__atomic_load_n(&classfile_hooks_world_client_pending, __ATOMIC_SEQ_CST)) return 0;
|
||||
__atomic_store_n(&classfile_hooks_world_client_pending, 0, __ATOMIC_SEQ_CST);
|
||||
jclass one = hooked_class_world_client.cls;
|
||||
if (!one) return 12000 + 49 /* JVMTI_ERROR_NOT_AVAILABLE */;
|
||||
JvmtiTable **jvmti = (JvmtiTable **)jvmti_env;
|
||||
int transform_error = JVMTI(jvmti, 151, JvmtiRetransform)(jvmti, 1, &one);
|
||||
if (transform_error) {
|
||||
slinky_logf("classhooks: deferred WorldClient retransform err=%d", transform_error);
|
||||
} else {
|
||||
__atomic_store_n(&classfile_hooks_world_client_patched, 1, __ATOMIC_SEQ_CST);
|
||||
slinky_logf("classhooks: deferred WorldClient retransform ok");
|
||||
}
|
||||
return transform_error;
|
||||
}
|
||||
extern "C" int remove_class_hooks(JNIEnv *env, JvmtiTable **jvmti) {
|
||||
int err = 0;
|
||||
// Not armed = the hooks were never installed or another pass already consumed the flag (a failed
|
||||
// pass left the game's patched calls live). Reporting success here made the unload sequence tear
|
||||
// everything down while the JVM was still dispatching natives into this DLL: hs_err pid6708
|
||||
// (java_set_render_manager_player_view_y via setRenderY) and pid2260 (serialize_settings via a second hook_update_display pass) look exactly like that.
|
||||
// Set the callback guard before clearing the armed flag: a class-load callback racing with this
|
||||
// CAS must not re-patch a class between the two operations.
|
||||
__atomic_store_n(&g_disarming, 1, __ATOMIC_SEQ_CST);
|
||||
if (!__sync_bool_compare_and_swap(&classfile_hooks_1_while_hooks_are_installed, 1, 0)) {
|
||||
__atomic_store_n(&g_disarming, 0, __ATOMIC_SEQ_CST);
|
||||
// A failed install may still have defined SlinkyHooks13 and kept its global ref.
|
||||
if (env && classfile_hooks_global_ref_transformer_class) {
|
||||
(*env)->DeleteGlobalRef(env, classfile_hooks_global_ref_transformer_class);
|
||||
classfile_hooks_global_ref_transformer_class = 0;
|
||||
}
|
||||
return 12000 + 49 /* JVMTI_ERROR_NOT_AVAILABLE */;
|
||||
}
|
||||
JVMTI_CHECK(JVMTI(jvmti, 1, JvmtiSetEventMode)(jvmti, 0 /* JVMTI_DISABLE */, 0x36 /* CLASS_FILE_LOAD_HOOK */, 0));
|
||||
{
|
||||
jclass all[9] = {hooked_class_minecraft.cls, hooked_class_entity_renderer.cls, hooked_class_render_manager.cls, hooked_class_renderer_living_entity.cls, hooked_class_render_player.cls,
|
||||
hooked_class_net_handler_play_client.cls, hooked_class_world_client.cls, hooked_class_entity.cls, hooked_class_entity_living_base.cls};
|
||||
// Same Java 9+ deadlock as install: WorldClient is index 6. Skip it only if the
|
||||
// deferred retransform never patched it; a patched WorldClient must be restored
|
||||
// before the DLL is unloaded.
|
||||
bool java9plus = false;
|
||||
if (env) java9plus = ((unsigned)(*env)->GetVersion(env) >> 16) >= 9;
|
||||
bool restore_world_client = !java9plus || __atomic_load_n(&classfile_hooks_world_client_patched, __ATOMIC_SEQ_CST);
|
||||
jclass classes[9];
|
||||
int n = 0;
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (i == 6 && !restore_world_client) continue;
|
||||
if (all[i]) classes[n++] = all[i];
|
||||
}
|
||||
if (n) JVMTI_CHECK(JVMTI(jvmti, 151, JvmtiRetransform)(jvmti, n, classes));
|
||||
if (restore_world_client) __atomic_store_n(&classfile_hooks_world_client_patched, 0, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
__atomic_store_n(&g_disarming, 0, __ATOMIC_SEQ_CST);
|
||||
JVMTI_CHECK(JVMTI(jvmti, 142, JvmtiRelinquish)(jvmti, ptr_to_hook_installer_class_file_slinkyhooks13));
|
||||
err = 0;
|
||||
out:
|
||||
__atomic_store_n(&g_disarming, 0, __ATOMIC_SEQ_CST);
|
||||
if (classfile_hooks_global_ref_transformer_class) {
|
||||
if (env) {
|
||||
if (!err) (*env)->UnregisterNatives(env, classfile_hooks_global_ref_transformer_class);
|
||||
(*env)->DeleteGlobalRef(env, classfile_hooks_global_ref_transformer_class);
|
||||
}
|
||||
classfile_hooks_global_ref_transformer_class = 0;
|
||||
}
|
||||
if (!err) __atomic_store_n(&classfile_hooks_world_client_pending, 0, __ATOMIC_SEQ_CST);
|
||||
// A failed step leaves the client resident (the unload sequence cancels on errors): re-arm the flag
|
||||
// so the next click gets a whole new pass. Retransform is idempotent, so resuming is safe.
|
||||
slinky_logf("disarm: err=%d", err);
|
||||
if (err) __sync_bool_compare_and_swap(&classfile_hooks_1_while_hooks_are_installed, 0, 1);
|
||||
return err;
|
||||
}
|
||||
|
||||
// ---- class-file patcher: rewrites a call/field instruction that matches a hook entry into `invokestatic hook` ----
|
||||
// Instruction callback of the class transformer (rewrite_span/copy_methods_table_constant_pool `pick`). `t` lists the hooks; each has the
|
||||
// owner class, member name + descriptor to catch, the kind of instruction and where its new Methodref index is.
|
||||
#include "slinky/classfile.h"
|
||||
struct RBuf22 { const unsigned char *p, *end; };
|
||||
struct WBuf22 { unsigned char *p, *end; };
|
||||
struct HookEnt { const char *cls; const char *name, *desc; int kind; int padding_1c; unsigned short *ref; }; // 0x28
|
||||
struct HookTable { char padding_00[0x10]; HookEnt *e; unsigned long long n; };
|
||||
struct MemberName { const char *name, *desc; };
|
||||
FN(int, classfile_read_u1, (RBuf22 *r, unsigned char *out));
|
||||
FN(int, classfile_read_u2, (RBuf22 *r, unsigned short *out));
|
||||
FN(int, classfile_write_u1, (WBuf22 *w, unsigned char b));
|
||||
FN(int, classfile_write_u2, (WBuf22 *w, unsigned short v));
|
||||
FN(int, constant_pool_member_ref, (CP *constant_pool, unsigned short idx, MemberRef *out));
|
||||
FN(int, constant_pool_member_ref2, (CP *constant_pool, unsigned short idx, MemberRef *out));
|
||||
extern "C" int compare_utf8_local(Utf8 left, const char *right) __asm__("compare_utf8");
|
||||
extern "C" bool compare_member_name_local(NameType left, MemberName right) __asm__("compare_member_name");
|
||||
extern "C" int rewrite_instruction(HookTable *t, CP *cp, RBuf22 *in, WBuf22 *out) {
|
||||
unsigned char op;
|
||||
int error_code = classfile_read_u1(in, &op);
|
||||
if (error_code) return error_code;
|
||||
int kind;
|
||||
bool field = true;
|
||||
switch (op) {
|
||||
case 0xb2: kind = 4; break; // getstatic
|
||||
case 0xb3: kind = 6; break; // putstatic
|
||||
case 0xb4: kind = 3; break; // getfield
|
||||
case 0xb5: kind = 5; break; // putfield
|
||||
case 0xb6: case 0xb7: kind = 1; field = false; break; // invokevirtual / invokespecial
|
||||
case 0xb8: kind = 2; field = false; break; // invokestatic
|
||||
default: return 0;
|
||||
}
|
||||
unsigned short idx;
|
||||
error_code = classfile_read_u2(in, &idx);
|
||||
if (error_code) return error_code;
|
||||
MemberRef ref;
|
||||
error_code = field ? constant_pool_member_ref(cp, idx, &ref) : constant_pool_member_ref2(cp, idx, &ref);
|
||||
if (error_code) return error_code;
|
||||
for (unsigned long long i = 0; i < t->n; i++) {
|
||||
HookEnt *h = &t->e[i];
|
||||
if (h->kind != kind) continue;
|
||||
if (compare_utf8_local(ref.cls, h->cls) != 0) continue;
|
||||
unsigned long long np = (unsigned long long)h->name, dp = (unsigned long long)h->desc;
|
||||
static int site_logs;
|
||||
if (site_logs < 30) { site_logs++; slinky_logf("sitecmp: cls=%s kind=%d name=%p desc=%p", h->cls, h->kind, h->name, h->desc); }
|
||||
if (np < 0x10000 || dp < 0x10000) {
|
||||
slinky_logf("sitecmp: bad site name=%llx desc=%llx (skipped)", np, dp);
|
||||
continue;
|
||||
}
|
||||
if (!compare_member_name_local(ref.nt, MemberName{h->name, h->desc})) continue;
|
||||
error_code = classfile_write_u1(out, 0xb8); // invokestatic
|
||||
if (error_code) return error_code;
|
||||
error_code = classfile_write_u2(out, *h->ref);
|
||||
if (error_code == 0) __atomic_add_fetch(&classfile_hooks_sites_rewritten, 1, __ATOMIC_RELAXED);
|
||||
return error_code;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- functions the first gap scan missed (padding / tail-jump neighbours) ----
|
||||
extern "C" void java_set_minecraft_right_click_delay_timer(Jvm *jvm, void *minecraft, int value) {
|
||||
JCALL(jvm, SetIntField, minecraft, ids->Minecraft_rightClickDelayTimer, value);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_minecraft_display_width(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetIntField, minecraft, ids->Minecraft_displayWidth);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_minecraft_display_height(Jvm *jvm, void *minecraft) {
|
||||
return JCALL(jvm, GetIntField, minecraft, ids->Minecraft_displayHeight);
|
||||
}
|
||||
@@ -0,0 +1,542 @@
|
||||
// 0x20000: class-file patching helpers (member refs, name compares)
|
||||
// cflags: /Ob1 /clang:-fno-slp-vectorize -mllvm -switch-to-lookup=false
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/classfile.h"
|
||||
FN(int, constant_class_name, (CP *constant_pool, unsigned short idx, Utf8 *out));
|
||||
FN(int, constant_name_and_type, (CP *constant_pool, unsigned short idx, NameType *out));
|
||||
FN(int, libc_strncmp_alt, (const void *left, const void *right, unsigned long long n));
|
||||
// CONSTANT_Fieldref/Methodref (tag 9) -> class, name, descriptor
|
||||
extern "C" int constant_pool_member_ref(CP *constant_pool, unsigned short idx, MemberRef *out) {
|
||||
const unsigned char *end, *lim;
|
||||
int error_code = 0x2afd;
|
||||
CP_ENTRY(constant_pool, idx, end, lim, out_);
|
||||
if (end + 1 > lim) goto trunc;
|
||||
error_code = 0x2afe;
|
||||
if (*end != 9) goto out_;
|
||||
if (end + 3 > lim) goto trunc;
|
||||
error_code = 0x2af8;
|
||||
if (end + 5 > lim) goto out_;
|
||||
{
|
||||
unsigned short ci = be16(end + 1);
|
||||
unsigned char hi = end[3], lo = end[4];
|
||||
error_code = constant_class_name(constant_pool, ci, &out->cls);
|
||||
if (error_code) goto out_;
|
||||
return constant_name_and_type(constant_pool, (unsigned short)((hi << 8) | lo), &out->nt);
|
||||
}
|
||||
trunc:
|
||||
error_code = 0x2af8;
|
||||
out_:
|
||||
return error_code;
|
||||
}
|
||||
// compare a constant-pool string with a C string (strncmp semantics, bounded by len + 1);
|
||||
// `k` is the compile-time copy of `s` used for the length (lets a literal fold like the original)
|
||||
static inline __attribute__((always_inline)) int utf8_cmpk(const Utf8 *u, const char *s, const char *k) {
|
||||
unsigned long long n;
|
||||
if (!k[0]) {
|
||||
n = 0;
|
||||
} else {
|
||||
unsigned long long lim = (unsigned long long)u->len + 1;
|
||||
unsigned long long i = 0;
|
||||
for (;;) {
|
||||
if (lim == i) { n = lim; break; }
|
||||
if (!k[i + 1]) { n = i + 1; break; }
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return libc_strncmp_alt(u->p, s, n);
|
||||
}
|
||||
static inline __attribute__((always_inline)) int utf8_cmp(const Utf8 *u, const char *s) {
|
||||
unsigned long long n;
|
||||
if (!s[0]) {
|
||||
n = 0;
|
||||
} else {
|
||||
unsigned long long lim = (unsigned long long)u->len + 1;
|
||||
unsigned long long i = 0;
|
||||
for (;;) {
|
||||
if (lim == i) { n = lim; break; }
|
||||
if (!s[i + 1]) { n = i + 1; break; }
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return libc_strncmp_alt(u->p, s, n);
|
||||
}
|
||||
extern "C" int compare_utf8(const Utf8 *left, const char *right) { return utf8_cmp(left, right); }
|
||||
// does the NameAndType match {name, desc}?
|
||||
extern "C" bool compare_member_name(const NameType *left, const char *const *right) {
|
||||
if (utf8_cmp(&left->name, right[0]) != 0) return false;
|
||||
return utf8_cmp(&left->desc, right[1]) == 0;
|
||||
}
|
||||
|
||||
// ---- class-file writer: bounded output cursor, every put fails with 0x2af9 when it would overflow ----
|
||||
struct W { unsigned char *p, *end; };
|
||||
extern "C" __attribute__((pure)) unsigned long long libc_strlen(const char *str); // strlen
|
||||
FN(void *, libc_memcpy_alt, (void *dest, const void *src, unsigned long long n));
|
||||
#define W_PUT8(w, v) \
|
||||
do { \
|
||||
unsigned char *q_ = (w)->p + 1; \
|
||||
if (q_ > (w)->end) return 0x2af9; \
|
||||
*(w)->p = (unsigned char)(v); \
|
||||
(w)->p = q_; \
|
||||
} while (0)
|
||||
#define W_PUT16(w, v) \
|
||||
do { \
|
||||
unsigned char *q_ = (w)->p + 2; \
|
||||
if (q_ > (w)->end) return 0x2af9; \
|
||||
*(w)->p = (unsigned char)((v) >> 8); \
|
||||
(w)->p[1] = (unsigned char)(v); \
|
||||
(w)->p = q_; \
|
||||
} while (0)
|
||||
// append CONSTANT_Utf8 `s` and a CONSTANT_Class naming it; *cls = the class entry's index
|
||||
extern "C" int append_constant_utf8_s_constant(W *w, const char *s, unsigned short *count, unsigned short *cls) {
|
||||
unsigned short u = (*count)++;
|
||||
W_PUT8(w, 1);
|
||||
unsigned char *q = w->p + 2;
|
||||
if (q > w->end) return 0x2af9;
|
||||
unsigned short n = (unsigned short)libc_strlen(s);
|
||||
*w->p = (unsigned char)(n >> 8);
|
||||
w->p[1] = (unsigned char)n;
|
||||
w->p = q;
|
||||
|
||||
unsigned char *e = q + n;
|
||||
if (e > w->end) return 0x2af9;
|
||||
libc_memcpy_alt(q, s, n);
|
||||
w->p = e;
|
||||
unsigned c = *count;
|
||||
*count = (unsigned short)(c + 1);
|
||||
*cls = (unsigned short)c;
|
||||
unsigned char *t = e + 1;
|
||||
if (t > w->end) return 0x2af9;
|
||||
*e = 7;
|
||||
w->p = t;
|
||||
q = e + 3;
|
||||
if (q > w->end) return 0x2af9;
|
||||
*t = (unsigned char)(u >> 8);
|
||||
w->p[1] = (unsigned char)u;
|
||||
w->p = q;
|
||||
return 0;
|
||||
}
|
||||
// resolve two CONSTANT_Utf8 entries into {name, desc} and hand them to cb(a, &nt, b, c)
|
||||
typedef int (*NtCb)(void *a, NameType *nt, void *b, void *c);
|
||||
extern "C" int constant_pool_name_and_type(CP *constant_pool, unsigned short ni, unsigned short di, NtCb cb, void *a, void *b, void *c) {
|
||||
const unsigned char *end, *lim, *u, *ulim;
|
||||
int error_code = 0x2afd;
|
||||
CP_ENTRY(constant_pool, ni, end, lim, out_);
|
||||
if (end + 1 > lim) goto trunc;
|
||||
if (*end != 1) goto tag;
|
||||
|
||||
{
|
||||
const unsigned char *cursor = end + 3;
|
||||
if (cursor > lim) goto trunc;
|
||||
unsigned short len = be16(end + 1);
|
||||
if (cursor + len > lim) goto trunc;
|
||||
CP_ENTRY(constant_pool, di, u, ulim, out_);
|
||||
if (u + 1 > ulim) goto trunc;
|
||||
error_code = 0x2afe;
|
||||
if (*u != 1) goto out_;
|
||||
|
||||
const unsigned char *q = u + 3;
|
||||
if (q > ulim) goto trunc;
|
||||
unsigned short dl = be16(u + 1);
|
||||
error_code = 0x2af8;
|
||||
if (q + dl > ulim) goto out_;
|
||||
NameType nt = {{cursor, len}, {q, dl}};
|
||||
return cb(a, &nt, b, c);
|
||||
}
|
||||
tag:
|
||||
error_code = 0x2afe;
|
||||
goto out_;
|
||||
trunc:
|
||||
error_code = 0x2af8;
|
||||
out_:
|
||||
return error_code;
|
||||
}
|
||||
// is constant-pool entry idx the Utf8 "Code"?
|
||||
extern "C" const char str_code[]; // "Code"
|
||||
extern "C" int constant_pool_utf8_member(CP *constant_pool, unsigned short idx, bool *out) {
|
||||
const unsigned char *end, *lim;
|
||||
int error_code = 0x2afd;
|
||||
CP_ENTRY(constant_pool, idx, end, lim, out_);
|
||||
if (end + 1 > lim) goto trunc;
|
||||
error_code = 0x2afe;
|
||||
if (*end != 1) goto out_;
|
||||
|
||||
{
|
||||
const unsigned char *cursor = end + 3;
|
||||
if (cursor > lim) goto trunc;
|
||||
unsigned short len = be16(end + 1);
|
||||
error_code = 0x2af8;
|
||||
if (cursor + len > lim) goto out_;
|
||||
Utf8 u = {cursor, len};
|
||||
*out = utf8_cmpk(&u, str_code, "Code") == 0;
|
||||
error_code = 0;
|
||||
goto out_;
|
||||
}
|
||||
trunc:
|
||||
error_code = 0x2af8;
|
||||
out_:
|
||||
|
||||
return error_code;
|
||||
}
|
||||
|
||||
// ---- bytecode walker: copies each instruction's span to `out` and hands both spans to cb ----
|
||||
struct Span { const unsigned char *p, *end; };
|
||||
typedef int (*InsCb)(void *ctx, void *a, Span *ins, Span *dst);
|
||||
extern "C" const unsigned char classfile_patch_helpers_opcode_instruction_length[]; // opcode -> instruction length (0 = invalid)
|
||||
#define IN_SKIP(in, n) \
|
||||
do { \
|
||||
const unsigned char *q_ = (in)->p + (n); \
|
||||
if (q_ > (in)->end) return 0x2af8; \
|
||||
(in)->p = q_; \
|
||||
} while (0)
|
||||
static inline __attribute__((always_inline)) unsigned be32(const unsigned char *p) {
|
||||
return __builtin_bswap32(*(const unsigned *)p);
|
||||
}
|
||||
extern "C" int rewrite_span(Span *in, W *out, const unsigned char *code, void *a, InsCb cb, void *ctx) {
|
||||
for (;;) {
|
||||
const unsigned char *cursor = in->p;
|
||||
if (cursor >= in->end) return 0;
|
||||
unsigned char *dst = out->p;
|
||||
unsigned char op = *cursor;
|
||||
in->p = cursor + 1;
|
||||
const unsigned char *cur, *nx;
|
||||
switch (op) {
|
||||
case 0xc4: { // wide: opcode + u16 index (+ s16 constant for iinc)
|
||||
const unsigned char *s = in->p;
|
||||
cur = s + 1;
|
||||
if (cur > in->end) return 0x2af8;
|
||||
nx = cur + (*s == 0x84 ? 4 : 2);
|
||||
goto skip;
|
||||
}
|
||||
case 0xab: { // lookupswitch: pad, default, npairs, npairs * {match, offset}
|
||||
unsigned pad = (unsigned)(in->p - code) & 3;
|
||||
if (pad) IN_SKIP(in, 4 - pad);
|
||||
IN_SKIP(in, 4);
|
||||
const unsigned char *l = in->p;
|
||||
cur = l + 4;
|
||||
if (cur > in->end) return 0x2af8;
|
||||
nx = cur + (unsigned)(be32(l) << 3);
|
||||
goto skip;
|
||||
}
|
||||
case 0xaa: { // tableswitch: pad, default, low, high, (high - low + 1) offsets
|
||||
unsigned pad = (unsigned)(in->p - code) & 3;
|
||||
if (pad) IN_SKIP(in, 4 - pad);
|
||||
IN_SKIP(in, 4);
|
||||
const unsigned char *l = in->p;
|
||||
if (l + 4 > in->end) return 0x2af8;
|
||||
unsigned lo = (unsigned)l[0] << 24 | (unsigned)l[1] << 16 | (unsigned)l[2] << 8 | l[3];
|
||||
in->p = l + 4;
|
||||
cur = l + 8;
|
||||
if (cur > in->end) return 0x2af8;
|
||||
nx = cur + (unsigned)((be32(l + 4) - lo) * 4 + 4);
|
||||
goto skip;
|
||||
}
|
||||
default:
|
||||
if (op > 0xc9) return 0x2aff;
|
||||
IN_SKIP(in, (unsigned)(classfile_patch_helpers_opcode_instruction_length[op] - 1));
|
||||
break;
|
||||
skip:
|
||||
in->p = nx <= in->end ? nx : cur;
|
||||
if (nx > in->end) return 0x2af8;
|
||||
break;
|
||||
}
|
||||
const unsigned char *end = in->p;
|
||||
unsigned n = (unsigned)(end - cursor);
|
||||
unsigned char *o = out->p + n;
|
||||
if (o > out->end) return 0x2af9;
|
||||
out->p = o;
|
||||
Span ins = {cursor, end}, os = {dst, o};
|
||||
int error_code = cb(ctx, a, &ins, &os);
|
||||
if (error_code) return error_code;
|
||||
}
|
||||
}
|
||||
// copy a Code attribute's u4 code_length + code through the walker (the length word is copied verbatim)
|
||||
extern "C" int copy_code_attribute_s_u4(Span *in, W *out, void *other_object, InsCb cb, void *ctx) {
|
||||
const unsigned char *cursor = in->p, *iend = in->end;
|
||||
const unsigned char *l = cursor + 4;
|
||||
if (l > iend) return 0x2af8;
|
||||
in->p = l;
|
||||
const unsigned char *c = cursor + 8;
|
||||
if (c > iend) return 0x2af8;
|
||||
unsigned char b0 = l[0], b1 = l[1], b2 = l[2], b3 = l[3];
|
||||
in->p = c;
|
||||
unsigned char *op = out->p, *oend = out->end;
|
||||
unsigned char *o = op + 8;
|
||||
if (o > oend) return 0x2af9;
|
||||
unsigned long long n = (unsigned)(b1 << 16 | b0 << 24 | b2 << 8) | (unsigned long long)b3;
|
||||
out->p = o;
|
||||
Span src = {c, c + n};
|
||||
W dst = {o, o + n};
|
||||
if (src.end > iend) return 0x2af8;
|
||||
in->p = src.end;
|
||||
if (dst.end > oend) return 0x2af9;
|
||||
out->p = dst.end;
|
||||
return rewrite_span(&src, &dst, c, other_object, cb, ctx);
|
||||
}
|
||||
|
||||
// copy the methods table; when a constant pool is given, `pick` chooses per method an instruction
|
||||
// callback (+ ctx) and the method's Code attribute is rewritten through it
|
||||
extern "C" int copy_methods_table_constant_pool(Span *in, W *out, CP *constant_pool, NtCb pick, void *pctx) {
|
||||
const unsigned char *cursor = in->p;
|
||||
if (cursor + 2 > in->end) return 0x2af8;
|
||||
unsigned char c0 = cursor[0], c1 = cursor[1];
|
||||
in->p = cursor + 2;
|
||||
unsigned char *o = out->p;
|
||||
if (o + 2 > out->end) return 0x2af9;
|
||||
*o = c0;
|
||||
out->p[1] = c1;
|
||||
out->p = o + 2;
|
||||
unsigned short cnt = (unsigned short)(c0 << 8 | c1);
|
||||
if (!cnt) return 0;
|
||||
unsigned short m = 0;
|
||||
do {
|
||||
const unsigned char *h = in->p, *hend = in->end;
|
||||
if (h + 2 > hend) return 0x2af8;
|
||||
in->p = h + 2;
|
||||
if (h + 4 > hend) return 0x2af8;
|
||||
unsigned short ni = *(const unsigned short *)(h + 2);
|
||||
in->p = h + 4;
|
||||
if (h + 6 > hend) return 0x2af8;
|
||||
unsigned short di = *(const unsigned short *)(h + 4);
|
||||
in->p = h + 6;
|
||||
if (h + 8 > hend) return 0x2af8;
|
||||
unsigned char high_byte = h[6], low_byte = h[7];
|
||||
in->p = h + 8;
|
||||
unsigned char *w = out->p;
|
||||
if (w + 8 > out->end) return 0x2af9;
|
||||
__builtin_memcpy(w, h, 8);
|
||||
out->p = w + 8;
|
||||
InsCb cb = 0;
|
||||
void *cbctx = 0;
|
||||
if (constant_pool) {
|
||||
int error_code = constant_pool_name_and_type(constant_pool, (unsigned short)(ni << 8 | ni >> 8), (unsigned short)(di << 8 | di >> 8), pick,
|
||||
pctx, &cb, &cbctx);
|
||||
if (error_code) return error_code;
|
||||
}
|
||||
unsigned short na = (unsigned short)((high_byte << 8) + low_byte);
|
||||
for (; na; na--) {
|
||||
const unsigned char *q = in->p, *qend = in->end;
|
||||
if (q + 2 > qend) return 0x2af8;
|
||||
InsCb acb = cb;
|
||||
void *actx = cbctx;
|
||||
unsigned short an = *(const unsigned short *)q;
|
||||
in->p = q + 2;
|
||||
if (q + 6 > qend) return 0x2af8;
|
||||
unsigned long long len = (unsigned)((unsigned)q[2] << 24 | (unsigned)q[3] << 16 | (unsigned)q[4] << 8) | (unsigned long long)q[5];
|
||||
in->p = q + 6;
|
||||
unsigned char *ah = out->p;
|
||||
unsigned char *body = ah + 6;
|
||||
if (body > out->end) return 0x2af9;
|
||||
__builtin_memcpy(ah, q, 6);
|
||||
out->p = body;
|
||||
const unsigned char *src = in->p;
|
||||
if (src + len > in->end) return 0x2af8;
|
||||
in->p = src + len;
|
||||
unsigned char *bend = body + len;
|
||||
if (bend > out->end) return 0x2af9;
|
||||
if (src) libc_memcpy_alt(body, src, len);
|
||||
out->p = bend;
|
||||
if (!acb) continue;
|
||||
bool code;
|
||||
int r = constant_pool_utf8_member(constant_pool, (unsigned short)(an << 8 | an >> 8), &code);
|
||||
if (!r && code) {
|
||||
Span s2 = {src, in->p};
|
||||
W w2 = {body, bend};
|
||||
r = copy_code_attribute_s_u4(&s2, &w2, constant_pool, acb, actx);
|
||||
}
|
||||
if (r) return r;
|
||||
}
|
||||
} while (++m != cnt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- class transformer: rewrite a class file, adding per hook a Utf8 name/desc, NameAndType and a
|
||||
// Methodref on the class itself (hooks[i].ref = its index), and rewriting methods through `pick` ----
|
||||
struct MHook { const char *name, *desc; unsigned short ref; };
|
||||
FN(void *, libc_malloc, (long long n)); // malloc
|
||||
FN(void *, libc_calloc, (unsigned long long n, unsigned long long size)); // calloc
|
||||
FN(void, libc_free, (void *ptr)); // free
|
||||
extern "C" int rewrite_class_file(const unsigned char *cls, int len, unsigned char **outBuf, int *outLen, const char *clsName,
|
||||
MHook *hooks, int nh, NtCb pick, void *pctx) {
|
||||
long long cap = len + 0x1000;
|
||||
unsigned char *buf = (unsigned char *)libc_malloc(cap);
|
||||
if (!buf) return 0x3ed;
|
||||
const unsigned char *end = cls + len;
|
||||
unsigned char *oend = buf + cap;
|
||||
const unsigned char **ents;
|
||||
int error_code = 0x2af8;
|
||||
if (len < 4) goto free_buf;
|
||||
error_code = 0x2afa;
|
||||
if (be32(cls) != 0xcafebabe) goto free_buf;
|
||||
error_code = 0x2af8;
|
||||
if ((unsigned)len < 8) goto free_buf;
|
||||
error_code = 0x2afb;
|
||||
if (be16(cls + 6) > 0x45) goto free_buf; // newer than Java 1 (major 69)
|
||||
__builtin_memcpy(buf, cls, 8);
|
||||
error_code = 0x2af8;
|
||||
if ((unsigned)len < 10) goto free_buf;
|
||||
{
|
||||
unsigned short cpn = be16(cls + 8);
|
||||
Span in = {cls + 10, end};
|
||||
unsigned nc = cpn + nh * 4 + 2;
|
||||
buf[8] = (unsigned char)(nc >> 8);
|
||||
buf[9] = (unsigned char)nc;
|
||||
W out = {buf + 10, oend};
|
||||
unsigned short n1 = cpn - 1;
|
||||
ents = (const unsigned char **)libc_calloc(n1, 8);
|
||||
if (!ents) {
|
||||
error_code = 0x3ed;
|
||||
goto free_buf;
|
||||
}
|
||||
// index the constant pool (long/double take two slots)
|
||||
const unsigned char *cursor = cls + 10;
|
||||
for (unsigned short i = 0; i < n1;) {
|
||||
ents[i] = cursor;
|
||||
const unsigned char *q = cursor + 1;
|
||||
if (q > end) { error_code = 0x2af8; goto free_both; }
|
||||
unsigned char tag = *cursor;
|
||||
in.p = q;
|
||||
error_code = 0x2afc;
|
||||
unsigned long long sz;
|
||||
switch (tag) {
|
||||
case 3: case 4: case 9: case 10: case 11: case 12: case 17: case 18: sz = 4; break;
|
||||
case 7: case 8: case 16: case 19: case 20: sz = 2; break;
|
||||
case 5: case 6: sz = 8; break;
|
||||
case 15: sz = 3; break;
|
||||
case 1:
|
||||
q = cursor + 3;
|
||||
if (q > end) { error_code = 0x2af8; goto free_both; }
|
||||
q += (cursor[1] << 8) + cursor[2];
|
||||
error_code = 0x2af8;
|
||||
if (q > end) goto free_both;
|
||||
in.p = q;
|
||||
i++;
|
||||
cursor = q;
|
||||
continue;
|
||||
default: goto free_both;
|
||||
}
|
||||
q += sz;
|
||||
error_code = 0x2af8;
|
||||
if (q > end) goto free_both;
|
||||
in.p = q;
|
||||
i += (unsigned char)(tag - 5) < 2;
|
||||
i++;
|
||||
cursor = q;
|
||||
}
|
||||
unsigned cpl = (unsigned)(cursor - (cls + 10));
|
||||
unsigned char *ce = buf + 10 + cpl;
|
||||
if (ce > oend) { error_code = 0x2af9; goto free_both; }
|
||||
libc_memcpy_alt(buf + 10, cls + 10, cpl);
|
||||
out.p = ce;
|
||||
unsigned short count = cpn, clsIdx;
|
||||
error_code = append_constant_utf8_s_constant(&out, clsName, &count, &clsIdx);
|
||||
if (error_code) goto free_both;
|
||||
if (nh > 0) {
|
||||
unsigned char *w = out.p;
|
||||
if (w + 1 > oend) { error_code = 0x2af9; goto free_both; }
|
||||
unsigned c = count;
|
||||
for (unsigned long long k = 0;;) {
|
||||
const char *nm = hooks[k].name;
|
||||
w[0] = 1;
|
||||
unsigned char *s = w + 3;
|
||||
if (s > oend) { error_code = 0x2af9; goto free_both; }
|
||||
unsigned short l = (unsigned short)libc_strlen(nm);
|
||||
w[1] = (unsigned char)(l >> 8);
|
||||
w[2] = (unsigned char)l;
|
||||
unsigned char *t = s + l;
|
||||
if (t > oend) { error_code = 0x2af9; goto free_both; }
|
||||
libc_memcpy_alt(s, nm, l);
|
||||
if (t + 1 > oend) { error_code = 0x2af9; goto free_both; }
|
||||
const char *desc = hooks[k].desc;
|
||||
t[0] = 1;
|
||||
s = t + 3;
|
||||
if (s > oend) { error_code = 0x2af9; goto free_both; }
|
||||
l = (unsigned short)libc_strlen(desc);
|
||||
*(unsigned short *)(t + 1) = __builtin_bswap16(l);
|
||||
unsigned char *u = s + l;
|
||||
if (u > oend) { error_code = 0x2af9; goto free_both; }
|
||||
libc_memcpy_alt(s, desc, l);
|
||||
if (u + 1 > oend) { error_code = 0x2af9; goto free_both; }
|
||||
u[0] = 0xc; // NameAndType {c, c + 1}
|
||||
if (u + 3 > oend) { error_code = 0x2af9; goto free_both; }
|
||||
*(unsigned short *)(u + 1) = __builtin_bswap16((unsigned short)c);
|
||||
if (u + 5 > oend) { error_code = 0x2af9; goto free_both; }
|
||||
u[3] = (unsigned char)((c + 1) >> 8);
|
||||
u[4] = (unsigned char)(c + 1);
|
||||
hooks[k].ref = (unsigned short)(c + 3);
|
||||
if (u + 6 > oend) { error_code = 0x2af9; goto free_both; }
|
||||
u[5] = 0xa; // Methodref {clsIdx, c + 2}
|
||||
if (u + 8 > oend) { error_code = 0x2af9; goto free_both; }
|
||||
u[6] = (unsigned char)(clsIdx >> 8);
|
||||
u[7] = (unsigned char)clsIdx;
|
||||
w = u + 10;
|
||||
if (w > oend) { error_code = 0x2af9; goto free_both; }
|
||||
u[8] = (unsigned char)((c + 2) >> 8);
|
||||
u[9] = (unsigned char)(c + 2);
|
||||
if (++k == (unsigned)nh) break;
|
||||
c += 4;
|
||||
if (w + 1 > oend) { error_code = 0x2af9; goto free_both; }
|
||||
}
|
||||
out.p = w;
|
||||
}
|
||||
CP cp;
|
||||
cp.e = ents;
|
||||
cp.n = n1;
|
||||
cp.end = cursor;
|
||||
// access, this, super, interfaces
|
||||
const unsigned char *h = cursor;
|
||||
error_code = 0x2af8;
|
||||
if (h + 6 > end) goto free_cp;
|
||||
in.p = h + 6;
|
||||
if (h + 8 > end) goto free_cp;
|
||||
{
|
||||
unsigned long long ic = (unsigned long long)(h[6] << 9) + (unsigned long long)(h[7] * 2);
|
||||
const unsigned char *ie = h + 8 + ic;
|
||||
in.p = ie > end ? h + 8 : ie;
|
||||
if (ie > end) goto free_cp;
|
||||
unsigned char *we = out.p + ic + 8;
|
||||
error_code = 0x2af9;
|
||||
if (we > oend) goto free_cp;
|
||||
libc_memcpy_alt(out.p, h, ic + 8);
|
||||
out.p = we;
|
||||
}
|
||||
{
|
||||
int e2 = copy_methods_table_constant_pool(&in, &out, 0, 0, 0); // fields
|
||||
if (e2) { error_code = e2; goto free_cp; }
|
||||
e2 = copy_methods_table_constant_pool(&in, &out, &cp, pick, pctx); // methods
|
||||
if (e2) { error_code = e2; goto free_cp; }
|
||||
}
|
||||
{
|
||||
const unsigned char *rest = in.p;
|
||||
unsigned rn = (unsigned)(end - rest);
|
||||
unsigned char *fe = out.p + rn;
|
||||
if (fe > oend) {
|
||||
error_code = 0x2af9;
|
||||
*outBuf = nullptr;
|
||||
*outLen = 0;
|
||||
} else {
|
||||
error_code = 0;
|
||||
if (rest) libc_memcpy_alt(out.p, rest, rn);
|
||||
*outBuf = buf;
|
||||
*outLen = (int)(fe - buf);
|
||||
}
|
||||
}
|
||||
free_cp:
|
||||
libc_free(cp.e);
|
||||
if (!error_code) return 0;
|
||||
goto free_buf;
|
||||
}
|
||||
free_both:
|
||||
libc_free(ents);
|
||||
free_buf:
|
||||
libc_free(buf);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
// ---- gap-scan functions of the class transformer ----
|
||||
// size of what rewrite_class_file appends to the constant pool: the class entries plus, per hook, a Utf8 name, a Utf8 descriptor,
|
||||
// a NameAndType and a Methodref
|
||||
extern "C" int gap_scan_functions_class_transformer(const char *clsName, MHook *hooks, int nh) {
|
||||
int n = (int)libc_strlen(clsName) + 6;
|
||||
for (int i = 0; i < nh; i++) n += (int)libc_strlen(hooks[i].name) + (int)libc_strlen(hooks[i].desc) + 0x10;
|
||||
return n;
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// 0x21000: class-hook installer (JVMTI) and the table of native hook bodies
|
||||
// Defines SlinkyHooks13 in the game's class loader, registers its natives and rewrites the nine hooked game classes
|
||||
// through the ClassFileLoadHook (class_file_load_hook, see classfile_hooks.cpp). The tables of what gets rewritten are data
|
||||
// (hook_tables.inc, generated from the image by tools/gen_hooks.py): the original assembled them on its stack.
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jnienv.h"
|
||||
#include "slinky/jids.h"
|
||||
FN(void *, libc_memcpy_alt, (void *dest, const void *src, unsigned long long n));
|
||||
extern "C" void slinky_logf(const char *fmt, ...);
|
||||
static inline const char *cs(const void *p) { return p ? (const char *)p : "?"; }
|
||||
extern "C" char hook_native_pointer_table[];
|
||||
// Copies the 23 native pointers to hook_native_pointer_table (the original installer read them back from there; this
|
||||
// implementation takes them from kNatives, so nothing calls this any more)
|
||||
extern "C" void *copies_23_native_pointers_data(const void *natives) { return libc_memcpy_alt(hook_native_pointer_table, natives, 0xb8); }
|
||||
|
||||
// ---- run-time layouts shared with the ClassFileLoadHook code (classfile_hooks.cpp, classfile_patch_helpers.cpp) ----
|
||||
struct MHook { const char *name, *desc; unsigned short ref; }; // a native of SlinkyHooks13; ref = its Methodref
|
||||
struct HookEnt { const char *owner, *name, *desc; long long kind; unsigned short *ref; }; // instruction to replace
|
||||
struct MethodPat { const char *name, *desc; HookEnt *ents; unsigned long long n; }; // method to rewrite (-1, -1 = all)
|
||||
struct HookRec { jclass cls; MHook *natives; unsigned long long nnat; MethodPat *pats; unsigned long long npats; };
|
||||
|
||||
// ---- compile-time description of the hooks ----
|
||||
enum { INVOKE_VIRTUAL = 1, INVOKE_STATIC = 2, GETFIELD = 3, GETSTATIC = 4, PUTFIELD = 5, PUTSTATIC = 6 };
|
||||
#define ANY nullptr
|
||||
struct NativeRef { const char *name, *desc; };
|
||||
struct Site { const char *owner, *name, *desc; int kind; int native; }; // `native` indexes the class's NativeRef list
|
||||
struct Method { const char *name, *desc; const Site *sites; int nsites; };
|
||||
struct HookedClass { jclass JIds::*cls; const NativeRef *natives; int nnatives; const Method *methods; int nmethods; };
|
||||
#include "hook_tables.inc"
|
||||
|
||||
// ---- JVMTI (function table indices) ----
|
||||
struct JvmtiTable { void *fn[160]; };
|
||||
typedef int (*SetEventMode)(JvmtiTable **, int mode, int event, void *thread);
|
||||
typedef int (*AddCaps)(JvmtiTable **, const void *caps);
|
||||
typedef int (*SetCallbacks)(JvmtiTable **, const void *callbacks, int size);
|
||||
typedef int (*Retransform)(JvmtiTable **, int count, jclass *classes);
|
||||
#define JVMTI(env, idx, T) ((T)(*(env))->fn[idx])
|
||||
enum { JVMTI_DISABLE = 0, JVMTI_ENABLE = 1, EV_CLASS_FILE_LOAD_HOOK = 0x36 };
|
||||
enum { SET_EVENT_NOTIFICATION_MODE = 1, SET_EVENT_CALLBACKS = 121, ADD_CAPABILITIES = 141, RETRANSFORM_CLASSES = 151 };
|
||||
enum { JVMTI_ERROR_BASE = 12000 }; // client error code = 12000 + jvmtiError
|
||||
|
||||
extern "C" unsigned char classfile_hooks_1_while_hooks_are_installed; // 1 while the hooks are installed
|
||||
extern "C" unsigned char classfile_hooks_world_client_pending; // WorldClient retransform still to do (Java 9+)
|
||||
extern "C" jobject classfile_hooks_global_ref_transformer_class; // global ref of SlinkyHooks13
|
||||
extern "C" int classfile_hooks_first_error; // first error seen by the ClassFileLoadHook (sticky)
|
||||
extern "C" HookRec hooked_class_minecraft[9]; // the hooked classes, read by the ClassFileLoadHook
|
||||
extern "C" const char str_slinkyhooks13[]; // "SlinkyHooks13"
|
||||
extern "C" const char ptr_to_hook_installer_class_file_slinkyhooks13[]; // jvmtiCapabilities: can_retransform_classes
|
||||
extern "C" const char ptr_to_class_file_load_hook[]; // jvmtiEventCallbacks (0x138 bytes): ClassFileLoadHook = class_file_load_hook
|
||||
extern "C" const jbyte hook_installer_class_file_slinkyhooks13[]; // class file of SlinkyHooks13
|
||||
extern "C" const int hook_installer_its_length; // its length
|
||||
FN(int, define_class_with_natives, (JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, const JNINativeMethod *methods, jint n,
|
||||
jclass *out));
|
||||
|
||||
// static storage for the records of the chosen mapping (the ClassFileLoadHook reads them for as long as the hooks
|
||||
// are installed)
|
||||
static MHook g_natives[9][6];
|
||||
static MethodPat g_pats[9][3];
|
||||
static HookEnt g_ents[32];
|
||||
static unsigned long long g_nents;
|
||||
|
||||
// The original installer assembled parts of this table at run time from the JIds method name /
|
||||
// signature slots (RendererLivingEntity/RenderPlayer doRender + canRenderName): tools/gen_hooks.py
|
||||
// could not recover those values statically, because dump_hooks.py had filled every JIds pointer
|
||||
// with a fake 0x10000000+offset when it decoded the table - the generated table therefore carries
|
||||
// "BAD@<ids offset>" placeholders. Resolve them against the live JIds exactly like the original.
|
||||
static const char *hook_string(JIds *ids, const char *s) {
|
||||
if (!s || s[0] != 'B' || s[1] != 'A' || s[2] != 'D' || s[3] != '@') return s;
|
||||
unsigned long long off = 0;
|
||||
for (const char *p = s + 4; *p; p++)
|
||||
off = off * 16 + (unsigned long long)(*p <= '9' ? *p - '0' : (*p | 0x20) - 'a' + 10);
|
||||
// The generator wrote the dumper's fake pointer base into the placeholder: "BAD@10000968" is
|
||||
// JIds+0x968, not JIds+0x10000968. Using the raw value read 256 MB past the table, which crashed
|
||||
// wherever that address was unmapped (vanilla 1.8.9: access violation in this function) and
|
||||
// silently produced garbage pointers where it happened to be mapped.
|
||||
if (off >= 0x10000000) off -= 0x10000000;
|
||||
if (off + sizeof(const char *) > sizeof(JIds)) return 0; // malformed placeholder
|
||||
return *(const char **)((char *)ids + off);
|
||||
}
|
||||
|
||||
static void build_records(JIds *ids, const HookedClass *tab) {
|
||||
g_nents = 0;
|
||||
for (int c = 0; c < 9; c++) {
|
||||
const HookedClass &hc = tab[c];
|
||||
HookRec &rec = hooked_class_minecraft[c];
|
||||
for (int i = 0; i < hc.nnatives; i++) g_natives[c][i] = MHook{hook_string(ids, hc.natives[i].name), hook_string(ids, hc.natives[i].desc), 0};
|
||||
for (int m = 0; m < hc.nmethods; m++) {
|
||||
const Method &meth = hc.methods[m];
|
||||
HookEnt *first = &g_ents[g_nents];
|
||||
for (int s = 0; s < meth.nsites; s++) {
|
||||
const Site &site = meth.sites[s];
|
||||
g_ents[g_nents++] = HookEnt{hook_string(ids, site.owner), hook_string(ids, site.name), hook_string(ids, site.desc), site.kind, &g_natives[c][site.native].ref};
|
||||
}
|
||||
const char *any = (const char *)-1;
|
||||
g_pats[c][m] = MethodPat{meth.name ? hook_string(ids, meth.name) : any, meth.desc ? hook_string(ids, meth.desc) : any, first,
|
||||
(unsigned long long)meth.nsites};
|
||||
}
|
||||
rec = HookRec{ids->*hc.cls, g_natives[c], (unsigned long long)hc.nnatives, g_pats[c], (unsigned long long)hc.nmethods};
|
||||
}
|
||||
}
|
||||
|
||||
static bool java9_or_newer(JNIEnv *env) {
|
||||
if (!env) return false;
|
||||
return ((unsigned)(*env)->GetVersion(env) >> 16) >= 9;
|
||||
}
|
||||
|
||||
// Install the hooks: 0 or an error (JNI/loader errors as the client defines them, 12000 + jvmtiError)
|
||||
extern "C" int install_class_hooks(Jvm *jvm, JvmtiTable **jvmti) {
|
||||
if (!jvm || !jvm->ids) {
|
||||
slinky_logf("classhooks: install called with bad jvm=%p ids=%p", jvm, jvm ? jvm->ids : 0);
|
||||
return 0x7d0;
|
||||
}
|
||||
slinky_logf("classhooks: install jvm=%p ids=%p env=%p jvmti=%p installed=%d", jvm, jvm->ids, jvm->env, jvmti, (int)classfile_hooks_1_while_hooks_are_installed);
|
||||
if (classfile_hooks_1_while_hooks_are_installed) return 0;
|
||||
JNIEnv *env = jvm->env;
|
||||
JIds *ids = jvm->ids;
|
||||
// Build the per-class hook records before the ClassFileLoadHook is enabled: the callback matches a
|
||||
// load against these entries, and an entry that is still zero (or a class that failed to resolve)
|
||||
// would match JVMTI's class_being_redefined == NULL for a first-time load and rewrite every class.
|
||||
build_records(ids, kHooks[ids->variant][ids->names]);
|
||||
|
||||
JNINativeMethod natives[23];
|
||||
for (int i = 0; i < 23; i++) natives[i] = JNINativeMethod{(char *)kNatives[i].name, (char *)kNatives[i].desc, kNatives[i].fn};
|
||||
jclass cls;
|
||||
int err = define_class_with_natives(env, str_slinkyhooks13, (jobject)((char *)ids + 0x10 /* game loader + loadClass */), hook_installer_class_file_slinkyhooks13,
|
||||
hook_installer_its_length, natives, 23, &cls);
|
||||
if (err) return err;
|
||||
classfile_hooks_global_ref_transformer_class = (*env)->NewGlobalRef(env, cls);
|
||||
|
||||
if ((err = JVMTI(jvmti, ADD_CAPABILITIES, AddCaps)(jvmti, ptr_to_hook_installer_class_file_slinkyhooks13))) return JVMTI_ERROR_BASE + err;
|
||||
char callbacks[0x138];
|
||||
__builtin_memcpy(callbacks, ptr_to_class_file_load_hook, sizeof callbacks);
|
||||
if ((err = JVMTI(jvmti, SET_EVENT_CALLBACKS, SetCallbacks)(jvmti, callbacks, sizeof callbacks)))
|
||||
return JVMTI_ERROR_BASE + err;
|
||||
if ((err = JVMTI(jvmti, SET_EVENT_NOTIFICATION_MODE, SetEventMode)(jvmti, JVMTI_ENABLE, EV_CLASS_FILE_LOAD_HOOK, 0)))
|
||||
return JVMTI_ERROR_BASE + err;
|
||||
|
||||
slinky_logf("classhooks: variant=%d names=%d canRenderName=%s%s doRender=%s%s playerDoRender=%s%s",
|
||||
(int)ids->variant, (int)ids->names,
|
||||
cs(ids->RendererLivingEntity_canRenderNameMethodName), cs(ids->RendererLivingEntity_canRenderNameMethodSignature),
|
||||
cs(ids->RendererLivingEntity_doRenderMethodName), cs(ids->RendererLivingEntity_doRenderMethodSignature),
|
||||
cs(ids->RenderArrow_doRenderMethodName), cs(ids->RenderArrow_doRenderMethodSignature));
|
||||
for (unsigned long long k = 0; k < g_nents; k++)
|
||||
slinky_logf("ent[%llu] owner=%s kind=%d name=%p desc=%p", k, g_ents[k].owner, (int)g_ents[k].kind, (void *)g_ents[k].name, (void *)g_ents[k].desc);
|
||||
__atomic_exchange_n(&classfile_hooks_first_error, 0, __ATOMIC_SEQ_CST);
|
||||
// Retransform one class at a time: classes the game has not loaded yet make the
|
||||
// all-at-once call fail, and they will be patched later by the still-enabled
|
||||
// ClassFileLoadHook when the game loads them (RenderPlayer/WorldClient on world join).
|
||||
// Java 9+ class transformers (Lunar/Badlion) can deadlock inside
|
||||
// RetransformClasses(WorldClient) while their transformer is active. The
|
||||
// world hooks are the only ones affected; skip that one retransform so
|
||||
// the installer completes and the client stays responsive. The
|
||||
// ClassFileLoadHook remains enabled for classes loaded later.
|
||||
const bool java9plus = java9_or_newer(env);
|
||||
if (java9plus)
|
||||
slinky_logf("classhooks: Java 9+ detected; skipping WorldClient retransform");
|
||||
for (int i = 0; i < 9; i++) {
|
||||
jclass one = hooked_class_minecraft[i].cls;
|
||||
slinky_logf("classhooks: retransform[%d]=%p", i, one);
|
||||
if (!one) continue;
|
||||
if (java9plus && i == 6) {
|
||||
slinky_logf("classhooks: retransform[%d] skipped on Java 9+ (deferred)", i);
|
||||
__atomic_store_n(&classfile_hooks_world_client_pending, 1, __ATOMIC_SEQ_CST);
|
||||
continue;
|
||||
}
|
||||
err = JVMTI(jvmti, RETRANSFORM_CLASSES, Retransform)(jvmti, 1, &one);
|
||||
slinky_logf("classhooks: retransform[%d] err=%d first_error=%d", i, err, classfile_hooks_first_error);
|
||||
if (err) __sync_val_compare_and_swap(&classfile_hooks_first_error, 0, JVMTI_ERROR_BASE + err);
|
||||
}
|
||||
if (classfile_hooks_first_error) __atomic_store_n(&classfile_hooks_world_client_pending, 0, __ATOMIC_SEQ_CST);
|
||||
__atomic_exchange_n(&classfile_hooks_1_while_hooks_are_installed, 1, __ATOMIC_SEQ_CST);
|
||||
// Keep the ClassFileLoadHook enabled: classes that were not loaded yet (RenderPlayer,
|
||||
// WorldClient, ...) are only retransformed when the game loads them after joining a world.
|
||||
slinky_logf("classhooks: installed, hook stays enabled, first_error=%d", classfile_hooks_first_error);
|
||||
return classfile_hooks_first_error;
|
||||
}
|
||||
@@ -0,0 +1,688 @@
|
||||
// generated by tools/gen_hooks.py from the image's class-hook installer - do not edit
|
||||
// Which instructions of the game are rewritten into calls of SlinkyHooks13 natives, per Minecraft mapping.
|
||||
|
||||
extern "C" void hook_get_mouse_over(), hook_is_display_active_prerender(), hook_update_display(), hook_post_world_clear_1_7(), hook_post_world_clear_1_8(), hook_click_mouse(), hook_right_click_mouse(), hook_orient_camera(), hook_update_active_render_info(), hook_set_render_x(), hook_set_render_y(), hook_update_entities(), hook_can_render_name(), hook_render_player(), hook_unload_chunk(), hook_set_block_1_7(), hook_set_block_1_8(), hook_invalidate_chunk(), hook_should_clip_ledge(), hook_move_flying(), hook_send_packet_1_7(), hook_send_packet_1_8(), hook_update_controller(); // bodies: base/e_7.cpp, e_8.cpp, protected/hooks.cpp
|
||||
static const struct { const char *name, *desc; void *fn; } kNatives[23] = {
|
||||
{"getMouseOver", "(Ljava/lang/Object;F)V", (void *)hook_get_mouse_over},
|
||||
{"isDisplayActivePreRender", "()Z", (void *)hook_is_display_active_prerender},
|
||||
{"updateDisplay", "()V", (void *)hook_update_display},
|
||||
{"postWorldClear_1_7", "(I)V", (void *)hook_post_world_clear_1_7},
|
||||
{"postWorldClear_1_8", "(I)V", (void *)hook_post_world_clear_1_8},
|
||||
{"clickMouse", "(Ljava/lang/Object;)V", (void *)hook_click_mouse},
|
||||
{"rightClickMouse", "(Ljava/lang/Object;)V", (void *)hook_right_click_mouse},
|
||||
{"orientCamera", "(Ljava/lang/Object;F)V", (void *)hook_orient_camera},
|
||||
{"updateActiveRenderInfo", "(Ljava/lang/Object;Z)V", (void *)hook_update_active_render_info},
|
||||
{"setRenderX", "(Ljava/lang/Object;F)V", (void *)hook_set_render_x},
|
||||
{"setRenderY", "(Ljava/lang/Object;F)V", (void *)hook_set_render_y},
|
||||
{"updateEntities", "(Ljava/lang/Object;)V", (void *)hook_update_entities},
|
||||
{"canRenderName", "(Ljava/lang/Object;Ljava/lang/Object;)Z", (void *)hook_can_render_name},
|
||||
{"renderPlayer", "(Ljava/lang/Object;Ljava/lang/Object;DDDFF)V", (void *)hook_render_player},
|
||||
{"unloadChunk", "(Ljava/lang/Object;II)V", (void *)hook_unload_chunk},
|
||||
{"setBlock_1_7", "(Ljava/lang/Object;IIILjava/lang/Object;II)Z", (void *)hook_set_block_1_7},
|
||||
{"setBlock_1_8", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)Z", (void *)hook_set_block_1_8},
|
||||
{"invalidateChunk", "(Ljava/lang/Object;IIIIII)V", (void *)hook_invalidate_chunk},
|
||||
{"shouldClipLedge", "(Ljava/lang/Object;)Z", (void *)hook_should_clip_ledge},
|
||||
{"moveFlying", "(Ljava/lang/Object;FFF)V", (void *)hook_move_flying},
|
||||
{"sendPacket_1_7", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", (void *)hook_send_packet_1_7},
|
||||
{"sendPacket_1_8", "(Ljava/lang/Object;Ljava/lang/Object;)V", (void *)hook_send_packet_1_8},
|
||||
{"updateController", "(Ljava/lang/Object;)V", (void *)hook_update_controller},
|
||||
};
|
||||
|
||||
static const NativeRef natives_Minecraft[] = {
|
||||
{"getMouseOver", "(Ljava/lang/Object;F)V"},
|
||||
{"updateDisplay", "()V"},
|
||||
{"clickMouse", "(Ljava/lang/Object;)V"},
|
||||
{"rightClickMouse", "(Ljava/lang/Object;)V"},
|
||||
{"updateEntities", "(Ljava/lang/Object;)V"},
|
||||
{"updateController", "(Ljava/lang/Object;)V"},
|
||||
};
|
||||
static const NativeRef natives_EntityRenderer[] = {
|
||||
{"postWorldClear_1_7", "(I)V"},
|
||||
{"postWorldClear_1_8", "(I)V"},
|
||||
{"orientCamera", "(Ljava/lang/Object;F)V"},
|
||||
{"updateActiveRenderInfo", "(Ljava/lang/Object;Z)V"},
|
||||
{"isDisplayActivePreRender", "()Z"},
|
||||
};
|
||||
static const NativeRef natives_RenderManager[] = {
|
||||
{"setRenderX", "(Ljava/lang/Object;F)V"},
|
||||
{"setRenderY", "(Ljava/lang/Object;F)V"},
|
||||
};
|
||||
static const NativeRef natives_RendererLivingEntity[] = {
|
||||
{"canRenderName", "(Ljava/lang/Object;Ljava/lang/Object;)Z"},
|
||||
};
|
||||
static const NativeRef natives_RenderPlayer[] = {
|
||||
{"renderPlayer", "(Ljava/lang/Object;Ljava/lang/Object;DDDFF)V"},
|
||||
};
|
||||
static const NativeRef natives_NetHandlerPlayClient[] = {
|
||||
{"invalidateChunk", "(Ljava/lang/Object;IIIIII)V"},
|
||||
{"sendPacket_1_7", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V"},
|
||||
{"sendPacket_1_8", "(Ljava/lang/Object;Ljava/lang/Object;)V"},
|
||||
};
|
||||
static const NativeRef natives_WorldClient[] = {
|
||||
{"unloadChunk", "(Ljava/lang/Object;II)V"},
|
||||
{"setBlock_1_7", "(Ljava/lang/Object;IIILjava/lang/Object;II)Z"},
|
||||
{"setBlock_1_8", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)Z"},
|
||||
};
|
||||
static const NativeRef natives_Entity[] = {
|
||||
{"shouldClipLedge", "(Ljava/lang/Object;)Z"},
|
||||
};
|
||||
static const NativeRef natives_EntityLivingBase[] = {
|
||||
{"moveFlying", "(Ljava/lang/Object;FFF)V"},
|
||||
};
|
||||
|
||||
// ---- Minecraft 1.7.10, obfuscated names ----
|
||||
static const Site v0_0_Minecraft_m0[] = { // in Minecraft.p()V
|
||||
{"blt", "a", "(F)V", INVOKE_VIRTUAL, 0}, // -> getMouseOver
|
||||
{"bao", "al", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"bao", "am", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
{"bjf", "h", "()V", INVOKE_VIRTUAL, 4}, // -> updateEntities
|
||||
{"bje", "e", "()V", INVOKE_VIRTUAL, 5}, // -> updateController
|
||||
};
|
||||
static const Site v0_0_Minecraft_m1[] = { // in Minecraft.runInputTick()V
|
||||
{"bao", "al", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"bao", "am", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
};
|
||||
static const Site v0_0_Minecraft_m2[] = { // in Minecraft.g()V
|
||||
{"org/lwjgl/opengl/Display", "update", "()V", INVOKE_STATIC, 1}, // -> updateDisplay
|
||||
};
|
||||
static const Method v0_0_Minecraft[] = {
|
||||
{"p", "()V", v0_0_Minecraft_m0, 5},
|
||||
{"runInputTick", "()V", v0_0_Minecraft_m1, 2},
|
||||
{"g", "()V", v0_0_Minecraft_m2, 1},
|
||||
};
|
||||
static const Site v0_0_EntityRenderer_m0[] = { // in EntityRenderer.a(FJ)V
|
||||
{"org/lwjgl/opengl/GL11", "glClear", "(I)V", INVOKE_STATIC, 0}, // -> postWorldClear_1_7
|
||||
{"baj", "a", "(Lyz;Z)V", INVOKE_STATIC, 3}, // -> updateActiveRenderInfo
|
||||
};
|
||||
static const Site v0_0_EntityRenderer_m1[] = { // in EntityRenderer.a(FI)V
|
||||
{"blt", "h", "(F)V", INVOKE_VIRTUAL, 2}, // -> orientCamera
|
||||
};
|
||||
static const Site v0_0_EntityRenderer_m2[] = { // in EntityRenderer.b(F)V
|
||||
{"org/lwjgl/opengl/Display", "isActive", "()Z", INVOKE_STATIC, 4}, // -> isDisplayActivePreRender
|
||||
};
|
||||
static const Method v0_0_EntityRenderer[] = {
|
||||
{"a", "(FJ)V", v0_0_EntityRenderer_m0, 2},
|
||||
{"a", "(FI)V", v0_0_EntityRenderer_m1, 1},
|
||||
{"b", "(F)V", v0_0_EntityRenderer_m2, 1},
|
||||
};
|
||||
static const Site v0_0_RenderManager_m0[] = { // in RenderManager.a(Lahb;Lbqf;Lbbu;Lsv;Lsa;Lbbj;F)V
|
||||
{"bnn", "k", "F", PUTFIELD, 0}, // -> setRenderX
|
||||
{"bnn", "j", "F", PUTFIELD, 1}, // -> setRenderY
|
||||
};
|
||||
static const Method v0_0_RenderManager[] = {
|
||||
{"a", "(Lahb;Lbqf;Lbbu;Lsv;Lsa;Lbbj;F)V", v0_0_RenderManager_m0, 2},
|
||||
};
|
||||
static const Site v0_0_RendererLivingEntity_m0[] = { // in RendererLivingEntity.*
|
||||
{"boh", "BAD@10000968", "BAD@10000970", INVOKE_VIRTUAL, 0}, // -> canRenderName
|
||||
};
|
||||
static const Method v0_0_RendererLivingEntity[] = {
|
||||
{ANY, ANY, v0_0_RendererLivingEntity_m0, 1},
|
||||
};
|
||||
static const Site v0_0_RenderPlayer_m0[] = { // in RenderPlayer.BAD@10000a90BAD@10000a98
|
||||
{"boh", "BAD@10000980", "BAD@10000988", INVOKE_VIRTUAL, 0}, // -> renderPlayer
|
||||
};
|
||||
static const Method v0_0_RenderPlayer[] = {
|
||||
{"BAD@10000a90", "BAD@10000a98", v0_0_RenderPlayer_m0, 1},
|
||||
};
|
||||
static const Site v0_0_NetHandlerPlayClient_m0[] = { // in NetHandlerPlayClient.a(Lgx;)V
|
||||
{"bjf", "c", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v0_0_NetHandlerPlayClient_m1[] = { // in NetHandlerPlayClient.a(Lgz;)V
|
||||
{"bjf", "c", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v0_0_NetHandlerPlayClient_m2[] = { // in NetHandlerPlayClient.a(Lft;)V
|
||||
{"ej", "a", "(Lft;[Lio/netty/util/concurrent/GenericFutureListener;)V", INVOKE_VIRTUAL, 1}, // -> sendPacket_1_7
|
||||
};
|
||||
static const Method v0_0_NetHandlerPlayClient[] = {
|
||||
{"a", "(Lgx;)V", v0_0_NetHandlerPlayClient_m0, 1},
|
||||
{"a", "(Lgz;)V", v0_0_NetHandlerPlayClient_m1, 1},
|
||||
{"a", "(Lft;)V", v0_0_NetHandlerPlayClient_m2, 1},
|
||||
};
|
||||
static const Site v0_0_WorldClient_m0[] = { // in WorldClient.c(IIILaji;I)Z
|
||||
{"ahb", "d", "(IIILaji;II)Z", INVOKE_VIRTUAL, 1}, // -> setBlock_1_7
|
||||
};
|
||||
static const Site v0_0_WorldClient_m1[] = { // in WorldClient.a(IIZ)V
|
||||
{"bjd", "b", "(II)V", INVOKE_VIRTUAL, 0}, // -> unloadChunk
|
||||
};
|
||||
static const Method v0_0_WorldClient[] = {
|
||||
{"c", "(IIILaji;I)Z", v0_0_WorldClient_m0, 1},
|
||||
{"a", "(IIZ)V", v0_0_WorldClient_m1, 1},
|
||||
};
|
||||
static const Site v0_0_Entity_m0[] = { // in Entity.d(DDD)V
|
||||
{"sa", "an", "()Z", INVOKE_VIRTUAL, 0}, // -> shouldClipLedge
|
||||
};
|
||||
static const Method v0_0_Entity[] = {
|
||||
{"d", "(DDD)V", v0_0_Entity_m0, 1},
|
||||
};
|
||||
static const Site v0_0_EntityLivingBase_m0[] = { // in EntityLivingBase.e(FF)V
|
||||
{"sv", "a", "(FFF)V", INVOKE_VIRTUAL, 0}, // -> moveFlying
|
||||
};
|
||||
static const Method v0_0_EntityLivingBase[] = {
|
||||
{"e", "(FF)V", v0_0_EntityLivingBase_m0, 1},
|
||||
};
|
||||
static const HookedClass v0_0[9] = {
|
||||
{&JIds::Minecraft, natives_Minecraft, 6, v0_0_Minecraft, 3},
|
||||
{&JIds::EntityRenderer, natives_EntityRenderer, 5, v0_0_EntityRenderer, 3},
|
||||
{&JIds::RenderManager, natives_RenderManager, 2, v0_0_RenderManager, 1},
|
||||
{&JIds::RendererLivingEntity, natives_RendererLivingEntity, 1, v0_0_RendererLivingEntity, 1},
|
||||
{&JIds::RenderPlayer, natives_RenderPlayer, 1, v0_0_RenderPlayer, 1},
|
||||
{&JIds::NetHandlerPlayClient, natives_NetHandlerPlayClient, 3, v0_0_NetHandlerPlayClient, 3},
|
||||
{&JIds::WorldClient, natives_WorldClient, 3, v0_0_WorldClient, 2},
|
||||
{&JIds::Entity, natives_Entity, 1, v0_0_Entity, 1},
|
||||
{&JIds::EntityLivingBase, natives_EntityLivingBase, 1, v0_0_EntityLivingBase, 1},
|
||||
};
|
||||
|
||||
// ---- Minecraft 1.7.10, MCP names ----
|
||||
static const Site v0_1_Minecraft_m0[] = { // in Minecraft.runTick()V
|
||||
{"net/minecraft/client/renderer/EntityRenderer", "getMouseOver", "(F)V", INVOKE_VIRTUAL, 0}, // -> getMouseOver
|
||||
{"net/minecraft/client/Minecraft", "clickMouse", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"net/minecraft/client/Minecraft", "rightClickMouse", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "updateEntities", "()V", INVOKE_VIRTUAL, 4}, // -> updateEntities
|
||||
{"net/minecraft/client/multiplayer/PlayerControllerMP", "updateController", "()V", INVOKE_VIRTUAL, 5}, // -> updateController
|
||||
};
|
||||
static const Site v0_1_Minecraft_m1[] = { // in Minecraft.runInputTick()V
|
||||
{"net/minecraft/client/Minecraft", "clickMouse", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"net/minecraft/client/Minecraft", "rightClickMouse", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
};
|
||||
static const Site v0_1_Minecraft_m2[] = { // in Minecraft.resetSize()V
|
||||
{"org/lwjgl/opengl/Display", "update", "()V", INVOKE_STATIC, 1}, // -> updateDisplay
|
||||
};
|
||||
static const Method v0_1_Minecraft[] = {
|
||||
{"runTick", "()V", v0_1_Minecraft_m0, 5},
|
||||
{"runInputTick", "()V", v0_1_Minecraft_m1, 2},
|
||||
{"resetSize", "()V", v0_1_Minecraft_m2, 1},
|
||||
};
|
||||
static const Site v0_1_EntityRenderer_m0[] = { // in EntityRenderer.renderWorld(FJ)V
|
||||
{"org/lwjgl/opengl/GL11", "glClear", "(I)V", INVOKE_STATIC, 0}, // -> postWorldClear_1_7
|
||||
{"net/minecraft/client/renderer/ActiveRenderInfo", "updateRenderInfo", "(Lnet/minecraft/entity/player/EntityPlayer;Z)V", INVOKE_STATIC, 3}, // -> updateActiveRenderInfo
|
||||
};
|
||||
static const Site v0_1_EntityRenderer_m1[] = { // in EntityRenderer.setupCameraTransform(FI)V
|
||||
{"net/minecraft/client/renderer/EntityRenderer", "orientCamera", "(F)V", INVOKE_VIRTUAL, 2}, // -> orientCamera
|
||||
};
|
||||
static const Site v0_1_EntityRenderer_m2[] = { // in EntityRenderer.updateCameraAndRender(F)V
|
||||
{"org/lwjgl/opengl/Display", "isActive", "()Z", INVOKE_STATIC, 4}, // -> isDisplayActivePreRender
|
||||
};
|
||||
static const Method v0_1_EntityRenderer[] = {
|
||||
{"renderWorld", "(FJ)V", v0_1_EntityRenderer_m0, 2},
|
||||
{"setupCameraTransform", "(FI)V", v0_1_EntityRenderer_m1, 1},
|
||||
{"updateCameraAndRender", "(F)V", v0_1_EntityRenderer_m2, 1},
|
||||
};
|
||||
static const Site v0_1_RenderManager_m0[] = { // in RenderManager.cacheActiveRenderInfo(Lnet/minecraft/world/World;Lnet/minecraft/client/renderer/texture/TextureManager;Lnet/minecraft/client/gui/FontRenderer;Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/entity/Entity;Lnet/minecr
|
||||
{"net/minecraft/client/renderer/entity/RenderManager", "playerViewX", "F", PUTFIELD, 0}, // -> setRenderX
|
||||
{"net/minecraft/client/renderer/entity/RenderManager", "playerViewY", "F", PUTFIELD, 1}, // -> setRenderY
|
||||
};
|
||||
static const Method v0_1_RenderManager[] = {
|
||||
{"cacheActiveRenderInfo", "(Lnet/minecraft/world/World;Lnet/minecraft/client/renderer/texture/TextureManager;Lnet/minecraft/client/gui/FontRenderer;Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/entity/Entity;Lnet/minecr", v0_1_RenderManager_m0, 2},
|
||||
};
|
||||
static const Site v0_1_RendererLivingEntity_m0[] = { // in RendererLivingEntity.*
|
||||
{"net/minecraft/client/renderer/entity/RendererLivingEntity", "BAD@10000968", "BAD@10000970", INVOKE_VIRTUAL, 0}, // -> canRenderName
|
||||
};
|
||||
static const Method v0_1_RendererLivingEntity[] = {
|
||||
{ANY, ANY, v0_1_RendererLivingEntity_m0, 1},
|
||||
};
|
||||
static const Site v0_1_RenderPlayer_m0[] = { // in RenderPlayer.BAD@10000a90BAD@10000a98
|
||||
{"net/minecraft/client/renderer/entity/RendererLivingEntity", "BAD@10000980", "BAD@10000988", INVOKE_VIRTUAL, 0}, // -> renderPlayer
|
||||
};
|
||||
static const Method v0_1_RenderPlayer[] = {
|
||||
{"BAD@10000a90", "BAD@10000a98", v0_1_RenderPlayer_m0, 1},
|
||||
};
|
||||
static const Site v0_1_NetHandlerPlayClient_m0[] = { // in NetHandlerPlayClient.handleChunkData(Lnet/minecraft/network/play/server/S21PacketChunkData;)V
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "markBlockRangeForRenderUpdate", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v0_1_NetHandlerPlayClient_m1[] = { // in NetHandlerPlayClient.handleMapChunkBulk(Lnet/minecraft/network/play/server/S26PacketMapChunkBulk;)V
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "markBlockRangeForRenderUpdate", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v0_1_NetHandlerPlayClient_m2[] = { // in NetHandlerPlayClient.addToSendQueue(Lnet/minecraft/network/Packet;)V
|
||||
{"net/minecraft/network/NetworkManager", "scheduleOutboundPacket", "(Lnet/minecraft/network/Packet;[Lio/netty/util/concurrent/GenericFutureListener;)V", INVOKE_VIRTUAL, 1}, // -> sendPacket_1_7
|
||||
};
|
||||
static const Method v0_1_NetHandlerPlayClient[] = {
|
||||
{"handleChunkData", "(Lnet/minecraft/network/play/server/S21PacketChunkData;)V", v0_1_NetHandlerPlayClient_m0, 1},
|
||||
{"handleMapChunkBulk", "(Lnet/minecraft/network/play/server/S26PacketMapChunkBulk;)V", v0_1_NetHandlerPlayClient_m1, 1},
|
||||
{"addToSendQueue", "(Lnet/minecraft/network/Packet;)V", v0_1_NetHandlerPlayClient_m2, 1},
|
||||
};
|
||||
static const Site v0_1_WorldClient_m0[] = { // in WorldClient.func_147492_c(IIILnet/minecraft/block/Block;I)Z
|
||||
{"net/minecraft/world/World", "setBlock", "(IIILnet/minecraft/block/Block;II)Z", INVOKE_VIRTUAL, 1}, // -> setBlock_1_7
|
||||
};
|
||||
static const Site v0_1_WorldClient_m1[] = { // in WorldClient.doPreChunk(IIZ)V
|
||||
{"net/minecraft/client/multiplayer/ChunkProviderClient", "unloadChunk", "(II)V", INVOKE_VIRTUAL, 0}, // -> unloadChunk
|
||||
};
|
||||
static const Method v0_1_WorldClient[] = {
|
||||
{"func_147492_c", "(IIILnet/minecraft/block/Block;I)Z", v0_1_WorldClient_m0, 1},
|
||||
{"doPreChunk", "(IIZ)V", v0_1_WorldClient_m1, 1},
|
||||
};
|
||||
static const Site v0_1_Entity_m0[] = { // in Entity.moveEntity(DDD)V
|
||||
{"net/minecraft/entity/Entity", "isSneaking", "()Z", INVOKE_VIRTUAL, 0}, // -> shouldClipLedge
|
||||
};
|
||||
static const Method v0_1_Entity[] = {
|
||||
{"moveEntity", "(DDD)V", v0_1_Entity_m0, 1},
|
||||
};
|
||||
static const Site v0_1_EntityLivingBase_m0[] = { // in EntityLivingBase.moveEntityWithHeading(FF)V
|
||||
{"net/minecraft/entity/EntityLivingBase", "moveFlying", "(FFF)V", INVOKE_VIRTUAL, 0}, // -> moveFlying
|
||||
};
|
||||
static const Method v0_1_EntityLivingBase[] = {
|
||||
{"moveEntityWithHeading", "(FF)V", v0_1_EntityLivingBase_m0, 1},
|
||||
};
|
||||
static const HookedClass v0_1[9] = {
|
||||
{&JIds::Minecraft, natives_Minecraft, 6, v0_1_Minecraft, 3},
|
||||
{&JIds::EntityRenderer, natives_EntityRenderer, 5, v0_1_EntityRenderer, 3},
|
||||
{&JIds::RenderManager, natives_RenderManager, 2, v0_1_RenderManager, 1},
|
||||
{&JIds::RendererLivingEntity, natives_RendererLivingEntity, 1, v0_1_RendererLivingEntity, 1},
|
||||
{&JIds::RenderPlayer, natives_RenderPlayer, 1, v0_1_RenderPlayer, 1},
|
||||
{&JIds::NetHandlerPlayClient, natives_NetHandlerPlayClient, 3, v0_1_NetHandlerPlayClient, 3},
|
||||
{&JIds::WorldClient, natives_WorldClient, 3, v0_1_WorldClient, 2},
|
||||
{&JIds::Entity, natives_Entity, 1, v0_1_Entity, 1},
|
||||
{&JIds::EntityLivingBase, natives_EntityLivingBase, 1, v0_1_EntityLivingBase, 1},
|
||||
};
|
||||
|
||||
// ---- Minecraft 1.7.10, SRG names ----
|
||||
static const Site v0_2_Minecraft_m0[] = { // in Minecraft.func_71407_l()V
|
||||
{"net/minecraft/client/renderer/EntityRenderer", "func_78473_a", "(F)V", INVOKE_VIRTUAL, 0}, // -> getMouseOver
|
||||
{"net/minecraft/client/Minecraft", "func_147116_af", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"net/minecraft/client/Minecraft", "func_147121_ag", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "func_72939_s", "()V", INVOKE_VIRTUAL, 4}, // -> updateEntities
|
||||
{"net/minecraft/client/multiplayer/PlayerControllerMP", "func_78765_e", "()V", INVOKE_VIRTUAL, 5}, // -> updateController
|
||||
};
|
||||
static const Site v0_2_Minecraft_m1[] = { // in Minecraft.runInputTick()V
|
||||
{"net/minecraft/client/Minecraft", "func_147116_af", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"net/minecraft/client/Minecraft", "func_147121_ag", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
};
|
||||
static const Site v0_2_Minecraft_m2[] = { // in Minecraft.func_147120_f()V
|
||||
{"org/lwjgl/opengl/Display", "update", "()V", INVOKE_STATIC, 1}, // -> updateDisplay
|
||||
};
|
||||
static const Method v0_2_Minecraft[] = {
|
||||
{"func_71407_l", "()V", v0_2_Minecraft_m0, 5},
|
||||
{"runInputTick", "()V", v0_2_Minecraft_m1, 2},
|
||||
{"func_147120_f", "()V", v0_2_Minecraft_m2, 1},
|
||||
};
|
||||
static const Site v0_2_EntityRenderer_m0[] = { // in EntityRenderer.func_78471_a(FJ)V
|
||||
{"org/lwjgl/opengl/GL11", "glClear", "(I)V", INVOKE_STATIC, 0}, // -> postWorldClear_1_7
|
||||
{"net/minecraft/client/renderer/ActiveRenderInfo", "func_74583_a", "(Lnet/minecraft/entity/player/EntityPlayer;Z)V", INVOKE_STATIC, 3}, // -> updateActiveRenderInfo
|
||||
};
|
||||
static const Site v0_2_EntityRenderer_m1[] = { // in EntityRenderer.func_78479_a(FI)V
|
||||
{"net/minecraft/client/renderer/EntityRenderer", "func_78467_g", "(F)V", INVOKE_VIRTUAL, 2}, // -> orientCamera
|
||||
};
|
||||
static const Site v0_2_EntityRenderer_m2[] = { // in EntityRenderer.func_78480_b(F)V
|
||||
{"org/lwjgl/opengl/Display", "isActive", "()Z", INVOKE_STATIC, 4}, // -> isDisplayActivePreRender
|
||||
};
|
||||
static const Method v0_2_EntityRenderer[] = {
|
||||
{"func_78471_a", "(FJ)V", v0_2_EntityRenderer_m0, 2},
|
||||
{"func_78479_a", "(FI)V", v0_2_EntityRenderer_m1, 1},
|
||||
{"func_78480_b", "(F)V", v0_2_EntityRenderer_m2, 1},
|
||||
};
|
||||
static const Site v0_2_RenderManager_m0[] = { // in RenderManager.func_147938_a(Lnet/minecraft/world/World;Lnet/minecraft/client/renderer/texture/TextureManager;Lnet/minecraft/client/gui/FontRenderer;Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/entity/Entity;Lnet/minecr
|
||||
{"net/minecraft/client/renderer/entity/RenderManager", "field_78732_j", "F", PUTFIELD, 0}, // -> setRenderX
|
||||
{"net/minecraft/client/renderer/entity/RenderManager", "field_78735_i", "F", PUTFIELD, 1}, // -> setRenderY
|
||||
};
|
||||
static const Method v0_2_RenderManager[] = {
|
||||
{"func_147938_a", "(Lnet/minecraft/world/World;Lnet/minecraft/client/renderer/texture/TextureManager;Lnet/minecraft/client/gui/FontRenderer;Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/entity/Entity;Lnet/minecr", v0_2_RenderManager_m0, 2},
|
||||
};
|
||||
static const Site v0_2_RendererLivingEntity_m0[] = { // in RendererLivingEntity.*
|
||||
{"net/minecraft/client/renderer/entity/RendererLivingEntity", "BAD@10000968", "BAD@10000970", INVOKE_VIRTUAL, 0}, // -> canRenderName
|
||||
};
|
||||
static const Method v0_2_RendererLivingEntity[] = {
|
||||
{ANY, ANY, v0_2_RendererLivingEntity_m0, 1},
|
||||
};
|
||||
static const Site v0_2_RenderPlayer_m0[] = { // in RenderPlayer.BAD@10000a90BAD@10000a98
|
||||
{"net/minecraft/client/renderer/entity/RendererLivingEntity", "BAD@10000980", "BAD@10000988", INVOKE_VIRTUAL, 0}, // -> renderPlayer
|
||||
};
|
||||
static const Method v0_2_RenderPlayer[] = {
|
||||
{"BAD@10000a90", "BAD@10000a98", v0_2_RenderPlayer_m0, 1},
|
||||
};
|
||||
static const Site v0_2_NetHandlerPlayClient_m0[] = { // in NetHandlerPlayClient.func_147263_a(Lnet/minecraft/network/play/server/S21PacketChunkData;)V
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "func_147458_c", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v0_2_NetHandlerPlayClient_m1[] = { // in NetHandlerPlayClient.func_147269_a(Lnet/minecraft/network/play/server/S26PacketMapChunkBulk;)V
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "func_147458_c", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v0_2_NetHandlerPlayClient_m2[] = { // in NetHandlerPlayClient.func_147297_a(Lnet/minecraft/network/Packet;)V
|
||||
{"net/minecraft/network/NetworkManager", "func_150725_a", "(Lnet/minecraft/network/Packet;[Lio/netty/util/concurrent/GenericFutureListener;)V", INVOKE_VIRTUAL, 1}, // -> sendPacket_1_7
|
||||
};
|
||||
static const Method v0_2_NetHandlerPlayClient[] = {
|
||||
{"func_147263_a", "(Lnet/minecraft/network/play/server/S21PacketChunkData;)V", v0_2_NetHandlerPlayClient_m0, 1},
|
||||
{"func_147269_a", "(Lnet/minecraft/network/play/server/S26PacketMapChunkBulk;)V", v0_2_NetHandlerPlayClient_m1, 1},
|
||||
{"func_147297_a", "(Lnet/minecraft/network/Packet;)V", v0_2_NetHandlerPlayClient_m2, 1},
|
||||
};
|
||||
static const Site v0_2_WorldClient_m0[] = { // in WorldClient.func_147492_c(IIILnet/minecraft/block/Block;I)Z
|
||||
{"net/minecraft/world/World", "func_147465_d", "(IIILnet/minecraft/block/Block;II)Z", INVOKE_VIRTUAL, 1}, // -> setBlock_1_7
|
||||
};
|
||||
static const Site v0_2_WorldClient_m1[] = { // in WorldClient.func_73025_a(IIZ)V
|
||||
{"net/minecraft/client/multiplayer/ChunkProviderClient", "func_73234_b", "(II)V", INVOKE_VIRTUAL, 0}, // -> unloadChunk
|
||||
};
|
||||
static const Method v0_2_WorldClient[] = {
|
||||
{"func_147492_c", "(IIILnet/minecraft/block/Block;I)Z", v0_2_WorldClient_m0, 1},
|
||||
{"func_73025_a", "(IIZ)V", v0_2_WorldClient_m1, 1},
|
||||
};
|
||||
static const Site v0_2_Entity_m0[] = { // in Entity.func_70091_d(DDD)V
|
||||
{"net/minecraft/entity/Entity", "func_70093_af", "()Z", INVOKE_VIRTUAL, 0}, // -> shouldClipLedge
|
||||
};
|
||||
static const Method v0_2_Entity[] = {
|
||||
{"func_70091_d", "(DDD)V", v0_2_Entity_m0, 1},
|
||||
};
|
||||
static const Site v0_2_EntityLivingBase_m0[] = { // in EntityLivingBase.func_70612_e(FF)V
|
||||
{"net/minecraft/entity/EntityLivingBase", "func_70060_a", "(FFF)V", INVOKE_VIRTUAL, 0}, // -> moveFlying
|
||||
};
|
||||
static const Method v0_2_EntityLivingBase[] = {
|
||||
{"func_70612_e", "(FF)V", v0_2_EntityLivingBase_m0, 1},
|
||||
};
|
||||
static const HookedClass v0_2[9] = {
|
||||
{&JIds::Minecraft, natives_Minecraft, 6, v0_2_Minecraft, 3},
|
||||
{&JIds::EntityRenderer, natives_EntityRenderer, 5, v0_2_EntityRenderer, 3},
|
||||
{&JIds::RenderManager, natives_RenderManager, 2, v0_2_RenderManager, 1},
|
||||
{&JIds::RendererLivingEntity, natives_RendererLivingEntity, 1, v0_2_RendererLivingEntity, 1},
|
||||
{&JIds::RenderPlayer, natives_RenderPlayer, 1, v0_2_RenderPlayer, 1},
|
||||
{&JIds::NetHandlerPlayClient, natives_NetHandlerPlayClient, 3, v0_2_NetHandlerPlayClient, 3},
|
||||
{&JIds::WorldClient, natives_WorldClient, 3, v0_2_WorldClient, 2},
|
||||
{&JIds::Entity, natives_Entity, 1, v0_2_Entity, 1},
|
||||
{&JIds::EntityLivingBase, natives_EntityLivingBase, 1, v0_2_EntityLivingBase, 1},
|
||||
};
|
||||
|
||||
// ---- Minecraft 1.8.9, obfuscated names ----
|
||||
static const Site v1_0_Minecraft_m0[] = { // in Minecraft.s()V
|
||||
{"bfk", "a", "(F)V", INVOKE_VIRTUAL, 0}, // -> getMouseOver
|
||||
{"ave", "aw", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"ave", "ax", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
{"bdb", "i", "()V", INVOKE_VIRTUAL, 4}, // -> updateEntities
|
||||
{"bda", "e", "()V", INVOKE_VIRTUAL, 5}, // -> updateController
|
||||
};
|
||||
static const Site v1_0_Minecraft_m1[] = { // in Minecraft.runInputTick()V
|
||||
{"ave", "aw", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"ave", "ax", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
};
|
||||
static const Site v1_0_Minecraft_m2[] = { // in Minecraft.h()V
|
||||
{"org/lwjgl/opengl/Display", "update", "()V", INVOKE_STATIC, 1}, // -> updateDisplay
|
||||
};
|
||||
static const Method v1_0_Minecraft[] = {
|
||||
{"s", "()V", v1_0_Minecraft_m0, 5},
|
||||
{"runInputTick", "()V", v1_0_Minecraft_m1, 2},
|
||||
{"h", "()V", v1_0_Minecraft_m2, 1},
|
||||
};
|
||||
static const Site v1_0_EntityRenderer_m0[] = { // in EntityRenderer.a(IFJ)V
|
||||
{"bfl", "m", "(I)V", INVOKE_STATIC, 1}, // -> postWorldClear_1_8
|
||||
{"auz", "a", "(Lwn;Z)V", INVOKE_STATIC, 3}, // -> updateActiveRenderInfo
|
||||
};
|
||||
static const Site v1_0_EntityRenderer_m1[] = { // in EntityRenderer.a(FI)V
|
||||
{"bfk", "f", "(F)V", INVOKE_VIRTUAL, 2}, // -> orientCamera
|
||||
};
|
||||
static const Site v1_0_EntityRenderer_m2[] = { // in EntityRenderer.a(FJ)V
|
||||
{"org/lwjgl/opengl/Display", "isActive", "()Z", INVOKE_STATIC, 4}, // -> isDisplayActivePreRender
|
||||
};
|
||||
static const Method v1_0_EntityRenderer[] = {
|
||||
{"a", "(IFJ)V", v1_0_EntityRenderer_m0, 2},
|
||||
{"a", "(FI)V", v1_0_EntityRenderer_m1, 1},
|
||||
{"a", "(FJ)V", v1_0_EntityRenderer_m2, 1},
|
||||
};
|
||||
static const Site v1_0_RenderManager_m0[] = { // in RenderManager.a(Ladm;Lavn;Lpk;Lpk;Lavh;F)V
|
||||
{"biu", "f", "F", PUTFIELD, 0}, // -> setRenderX
|
||||
{"biu", "e", "F", PUTFIELD, 1}, // -> setRenderY
|
||||
};
|
||||
static const Method v1_0_RenderManager[] = {
|
||||
{"a", "(Ladm;Lavn;Lpk;Lpk;Lavh;F)V", v1_0_RenderManager_m0, 2},
|
||||
};
|
||||
static const Site v1_0_RendererLivingEntity_m0[] = { // in RendererLivingEntity.*
|
||||
{"bjl", "BAD@10000968", "BAD@10000970", INVOKE_VIRTUAL, 0}, // -> canRenderName
|
||||
};
|
||||
static const Method v1_0_RendererLivingEntity[] = {
|
||||
{ANY, ANY, v1_0_RendererLivingEntity_m0, 1},
|
||||
};
|
||||
static const Site v1_0_RenderPlayer_m0[] = { // in RenderPlayer.BAD@10000a90BAD@10000a98
|
||||
{"bjl", "BAD@10000980", "BAD@10000988", INVOKE_VIRTUAL, 0}, // -> renderPlayer
|
||||
};
|
||||
static const Method v1_0_RenderPlayer[] = {
|
||||
{"BAD@10000a90", "BAD@10000a98", v1_0_RenderPlayer_m0, 1},
|
||||
};
|
||||
static const Site v1_0_NetHandlerPlayClient_m0[] = { // in NetHandlerPlayClient.a(Lgo;)V
|
||||
{"bdb", "b", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v1_0_NetHandlerPlayClient_m1[] = { // in NetHandlerPlayClient.a(Lgp;)V
|
||||
{"bdb", "b", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v1_0_NetHandlerPlayClient_m2[] = { // in NetHandlerPlayClient.a(Lff;)V
|
||||
{"ek", "a", "(Lff;)V", INVOKE_VIRTUAL, 2}, // -> sendPacket_1_8
|
||||
};
|
||||
static const Method v1_0_NetHandlerPlayClient[] = {
|
||||
{"a", "(Lgo;)V", v1_0_NetHandlerPlayClient_m0, 1},
|
||||
{"a", "(Lgp;)V", v1_0_NetHandlerPlayClient_m1, 1},
|
||||
{"a", "(Lff;)V", v1_0_NetHandlerPlayClient_m2, 1},
|
||||
};
|
||||
static const Site v1_0_WorldClient_m0[] = { // in WorldClient.b(Lcj;Lalz;)Z
|
||||
{"adm", "a", "(Lcj;Lalz;I)Z", INVOKE_VIRTUAL, 2}, // -> setBlock_1_8
|
||||
};
|
||||
static const Site v1_0_WorldClient_m1[] = { // in WorldClient.b(IIZ)V
|
||||
{"bcz", "b", "(II)V", INVOKE_VIRTUAL, 0}, // -> unloadChunk
|
||||
};
|
||||
static const Method v1_0_WorldClient[] = {
|
||||
{"b", "(Lcj;Lalz;)Z", v1_0_WorldClient_m0, 1},
|
||||
{"b", "(IIZ)V", v1_0_WorldClient_m1, 1},
|
||||
};
|
||||
static const Site v1_0_Entity_m0[] = { // in Entity.d(DDD)V
|
||||
{"pk", "av", "()Z", INVOKE_VIRTUAL, 0}, // -> shouldClipLedge
|
||||
};
|
||||
static const Method v1_0_Entity[] = {
|
||||
{"d", "(DDD)V", v1_0_Entity_m0, 1},
|
||||
};
|
||||
static const Site v1_0_EntityLivingBase_m0[] = { // in EntityLivingBase.g(FF)V
|
||||
{"pr", "a", "(FFF)V", INVOKE_VIRTUAL, 0}, // -> moveFlying
|
||||
};
|
||||
static const Method v1_0_EntityLivingBase[] = {
|
||||
{"g", "(FF)V", v1_0_EntityLivingBase_m0, 1},
|
||||
};
|
||||
static const HookedClass v1_0[9] = {
|
||||
{&JIds::Minecraft, natives_Minecraft, 6, v1_0_Minecraft, 3},
|
||||
{&JIds::EntityRenderer, natives_EntityRenderer, 5, v1_0_EntityRenderer, 3},
|
||||
{&JIds::RenderManager, natives_RenderManager, 2, v1_0_RenderManager, 1},
|
||||
{&JIds::RendererLivingEntity, natives_RendererLivingEntity, 1, v1_0_RendererLivingEntity, 1},
|
||||
{&JIds::RenderPlayer, natives_RenderPlayer, 1, v1_0_RenderPlayer, 1},
|
||||
{&JIds::NetHandlerPlayClient, natives_NetHandlerPlayClient, 3, v1_0_NetHandlerPlayClient, 3},
|
||||
{&JIds::WorldClient, natives_WorldClient, 3, v1_0_WorldClient, 2},
|
||||
{&JIds::Entity, natives_Entity, 1, v1_0_Entity, 1},
|
||||
{&JIds::EntityLivingBase, natives_EntityLivingBase, 1, v1_0_EntityLivingBase, 1},
|
||||
};
|
||||
|
||||
// ---- Minecraft 1.8.9, MCP names ----
|
||||
static const Site v1_1_Minecraft_m0[] = { // in Minecraft.runTick()V
|
||||
{"net/minecraft/client/renderer/EntityRenderer", "getMouseOver", "(F)V", INVOKE_VIRTUAL, 0}, // -> getMouseOver
|
||||
{"net/minecraft/client/Minecraft", "clickMouse", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"net/minecraft/client/Minecraft", "rightClickMouse", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "updateEntities", "()V", INVOKE_VIRTUAL, 4}, // -> updateEntities
|
||||
{"net/minecraft/client/multiplayer/PlayerControllerMP", "updateController", "()V", INVOKE_VIRTUAL, 5}, // -> updateController
|
||||
};
|
||||
static const Site v1_1_Minecraft_m1[] = { // in Minecraft.runInputTick()V
|
||||
{"net/minecraft/client/Minecraft", "clickMouse", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"net/minecraft/client/Minecraft", "rightClickMouse", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
};
|
||||
static const Site v1_1_Minecraft_m2[] = { // in Minecraft.updateDisplay()V
|
||||
{"org/lwjgl/opengl/Display", "update", "()V", INVOKE_STATIC, 1}, // -> updateDisplay
|
||||
};
|
||||
static const Method v1_1_Minecraft[] = {
|
||||
{"runTick", "()V", v1_1_Minecraft_m0, 5},
|
||||
{"runInputTick", "()V", v1_1_Minecraft_m1, 2},
|
||||
{"updateDisplay", "()V", v1_1_Minecraft_m2, 1},
|
||||
};
|
||||
static const Site v1_1_EntityRenderer_m0[] = { // in EntityRenderer.renderWorldPass(IFJ)V
|
||||
{"net/minecraft/client/renderer/GlStateManager", "clear", "(I)V", INVOKE_STATIC, 1}, // -> postWorldClear_1_8
|
||||
{"net/minecraft/client/renderer/ActiveRenderInfo", "updateRenderInfo", "(Lnet/minecraft/entity/player/EntityPlayer;Z)V", INVOKE_STATIC, 3}, // -> updateActiveRenderInfo
|
||||
};
|
||||
static const Site v1_1_EntityRenderer_m1[] = { // in EntityRenderer.setupCameraTransform(FI)V
|
||||
{"net/minecraft/client/renderer/EntityRenderer", "orientCamera", "(F)V", INVOKE_VIRTUAL, 2}, // -> orientCamera
|
||||
};
|
||||
static const Site v1_1_EntityRenderer_m2[] = { // in EntityRenderer.updateCameraAndRender(FJ)V
|
||||
{"org/lwjgl/opengl/Display", "isActive", "()Z", INVOKE_STATIC, 4}, // -> isDisplayActivePreRender
|
||||
};
|
||||
static const Method v1_1_EntityRenderer[] = {
|
||||
{"renderWorldPass", "(IFJ)V", v1_1_EntityRenderer_m0, 2},
|
||||
{"setupCameraTransform", "(FI)V", v1_1_EntityRenderer_m1, 1},
|
||||
{"updateCameraAndRender", "(FJ)V", v1_1_EntityRenderer_m2, 1},
|
||||
};
|
||||
static const Site v1_1_RenderManager_m0[] = { // in RenderManager.cacheActiveRenderInfo(Lnet/minecraft/world/World;Lnet/minecraft/client/gui/FontRenderer;Lnet/minecraft/entity/Entity;Lnet/minecraft/entity/Entity;Lnet/minecraft/client/settings/GameSettings;F)V
|
||||
{"net/minecraft/client/renderer/entity/RenderManager", "playerViewX", "F", PUTFIELD, 0}, // -> setRenderX
|
||||
{"net/minecraft/client/renderer/entity/RenderManager", "playerViewY", "F", PUTFIELD, 1}, // -> setRenderY
|
||||
};
|
||||
static const Method v1_1_RenderManager[] = {
|
||||
{"cacheActiveRenderInfo", "(Lnet/minecraft/world/World;Lnet/minecraft/client/gui/FontRenderer;Lnet/minecraft/entity/Entity;Lnet/minecraft/entity/Entity;Lnet/minecraft/client/settings/GameSettings;F)V", v1_1_RenderManager_m0, 2},
|
||||
};
|
||||
static const Site v1_1_RendererLivingEntity_m0[] = { // in RendererLivingEntity.*
|
||||
{"net/minecraft/client/renderer/entity/RendererLivingEntity", "BAD@10000968", "BAD@10000970", INVOKE_VIRTUAL, 0}, // -> canRenderName
|
||||
};
|
||||
static const Method v1_1_RendererLivingEntity[] = {
|
||||
{ANY, ANY, v1_1_RendererLivingEntity_m0, 1},
|
||||
};
|
||||
static const Site v1_1_RenderPlayer_m0[] = { // in RenderPlayer.BAD@10000a90BAD@10000a98
|
||||
{"net/minecraft/client/renderer/entity/RendererLivingEntity", "BAD@10000980", "BAD@10000988", INVOKE_VIRTUAL, 0}, // -> renderPlayer
|
||||
};
|
||||
static const Method v1_1_RenderPlayer[] = {
|
||||
{"BAD@10000a90", "BAD@10000a98", v1_1_RenderPlayer_m0, 1},
|
||||
};
|
||||
static const Site v1_1_NetHandlerPlayClient_m0[] = { // in NetHandlerPlayClient.handleChunkData(Lnet/minecraft/network/play/server/S21PacketChunkData;)V
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "markBlockRangeForRenderUpdate", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v1_1_NetHandlerPlayClient_m1[] = { // in NetHandlerPlayClient.handleMapChunkBulk(Lnet/minecraft/network/play/server/S26PacketMapChunkBulk;)V
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "markBlockRangeForRenderUpdate", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v1_1_NetHandlerPlayClient_m2[] = { // in NetHandlerPlayClient.addToSendQueue(Lnet/minecraft/network/Packet;)V
|
||||
{"net/minecraft/network/NetworkManager", "sendPacket", "(Lnet/minecraft/network/Packet;)V", INVOKE_VIRTUAL, 2}, // -> sendPacket_1_8
|
||||
};
|
||||
static const Method v1_1_NetHandlerPlayClient[] = {
|
||||
{"handleChunkData", "(Lnet/minecraft/network/play/server/S21PacketChunkData;)V", v1_1_NetHandlerPlayClient_m0, 1},
|
||||
{"handleMapChunkBulk", "(Lnet/minecraft/network/play/server/S26PacketMapChunkBulk;)V", v1_1_NetHandlerPlayClient_m1, 1},
|
||||
{"addToSendQueue", "(Lnet/minecraft/network/Packet;)V", v1_1_NetHandlerPlayClient_m2, 1},
|
||||
};
|
||||
static const Site v1_1_WorldClient_m0[] = { // in WorldClient.invalidateRegionAndSetBlock(Lnet/minecraft/util/BlockPos;Lnet/minecraft/block/state/IBlockState;)Z
|
||||
{"net/minecraft/world/World", "setBlockState", "(Lnet/minecraft/util/BlockPos;Lnet/minecraft/block/state/IBlockState;I)Z", INVOKE_VIRTUAL, 2}, // -> setBlock_1_8
|
||||
};
|
||||
static const Site v1_1_WorldClient_m1[] = { // in WorldClient.doPreChunk(IIZ)V
|
||||
{"net/minecraft/client/multiplayer/ChunkProviderClient", "unloadChunk", "(II)V", INVOKE_VIRTUAL, 0}, // -> unloadChunk
|
||||
};
|
||||
static const Method v1_1_WorldClient[] = {
|
||||
{"invalidateRegionAndSetBlock", "(Lnet/minecraft/util/BlockPos;Lnet/minecraft/block/state/IBlockState;)Z", v1_1_WorldClient_m0, 1},
|
||||
{"doPreChunk", "(IIZ)V", v1_1_WorldClient_m1, 1},
|
||||
};
|
||||
static const Site v1_1_Entity_m0[] = { // in Entity.moveEntity(DDD)V
|
||||
{"net/minecraft/entity/Entity", "isSneaking", "()Z", INVOKE_VIRTUAL, 0}, // -> shouldClipLedge
|
||||
};
|
||||
static const Method v1_1_Entity[] = {
|
||||
{"moveEntity", "(DDD)V", v1_1_Entity_m0, 1},
|
||||
};
|
||||
static const Site v1_1_EntityLivingBase_m0[] = { // in EntityLivingBase.moveEntityWithHeading(FF)V
|
||||
{"net/minecraft/entity/EntityLivingBase", "moveFlying", "(FFF)V", INVOKE_VIRTUAL, 0}, // -> moveFlying
|
||||
};
|
||||
static const Method v1_1_EntityLivingBase[] = {
|
||||
{"moveEntityWithHeading", "(FF)V", v1_1_EntityLivingBase_m0, 1},
|
||||
};
|
||||
static const HookedClass v1_1[9] = {
|
||||
{&JIds::Minecraft, natives_Minecraft, 6, v1_1_Minecraft, 3},
|
||||
{&JIds::EntityRenderer, natives_EntityRenderer, 5, v1_1_EntityRenderer, 3},
|
||||
{&JIds::RenderManager, natives_RenderManager, 2, v1_1_RenderManager, 1},
|
||||
{&JIds::RendererLivingEntity, natives_RendererLivingEntity, 1, v1_1_RendererLivingEntity, 1},
|
||||
{&JIds::RenderPlayer, natives_RenderPlayer, 1, v1_1_RenderPlayer, 1},
|
||||
{&JIds::NetHandlerPlayClient, natives_NetHandlerPlayClient, 3, v1_1_NetHandlerPlayClient, 3},
|
||||
{&JIds::WorldClient, natives_WorldClient, 3, v1_1_WorldClient, 2},
|
||||
{&JIds::Entity, natives_Entity, 1, v1_1_Entity, 1},
|
||||
{&JIds::EntityLivingBase, natives_EntityLivingBase, 1, v1_1_EntityLivingBase, 1},
|
||||
};
|
||||
|
||||
// ---- Minecraft 1.8.9, SRG names ----
|
||||
static const Site v1_2_Minecraft_m0[] = { // in Minecraft.func_71407_l()V
|
||||
{"net/minecraft/client/renderer/EntityRenderer", "func_78473_a", "(F)V", INVOKE_VIRTUAL, 0}, // -> getMouseOver
|
||||
{"net/minecraft/client/Minecraft", "func_147116_af", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"net/minecraft/client/Minecraft", "func_147121_ag", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "func_72939_s", "()V", INVOKE_VIRTUAL, 4}, // -> updateEntities
|
||||
{"net/minecraft/client/multiplayer/PlayerControllerMP", "func_78765_e", "()V", INVOKE_VIRTUAL, 5}, // -> updateController
|
||||
};
|
||||
static const Site v1_2_Minecraft_m1[] = { // in Minecraft.runInputTick()V
|
||||
{"net/minecraft/client/Minecraft", "func_147116_af", "()V", INVOKE_VIRTUAL, 2}, // -> clickMouse
|
||||
{"net/minecraft/client/Minecraft", "func_147121_ag", "()V", INVOKE_VIRTUAL, 3}, // -> rightClickMouse
|
||||
};
|
||||
static const Site v1_2_Minecraft_m2[] = { // in Minecraft.func_175601_h()V
|
||||
{"org/lwjgl/opengl/Display", "update", "()V", INVOKE_STATIC, 1}, // -> updateDisplay
|
||||
};
|
||||
static const Method v1_2_Minecraft[] = {
|
||||
{"func_71407_l", "()V", v1_2_Minecraft_m0, 5},
|
||||
{"runInputTick", "()V", v1_2_Minecraft_m1, 2},
|
||||
{"func_175601_h", "()V", v1_2_Minecraft_m2, 1},
|
||||
};
|
||||
static const Site v1_2_EntityRenderer_m0[] = { // in EntityRenderer.func_175068_a(IFJ)V
|
||||
{"net/minecraft/client/renderer/GlStateManager", "func_179086_m", "(I)V", INVOKE_STATIC, 1}, // -> postWorldClear_1_8
|
||||
{"net/minecraft/client/renderer/ActiveRenderInfo", "func_74583_a", "(Lnet/minecraft/entity/player/EntityPlayer;Z)V", INVOKE_STATIC, 3}, // -> updateActiveRenderInfo
|
||||
};
|
||||
static const Site v1_2_EntityRenderer_m1[] = { // in EntityRenderer.func_78479_a(FI)V
|
||||
{"net/minecraft/client/renderer/EntityRenderer", "func_78467_g", "(F)V", INVOKE_VIRTUAL, 2}, // -> orientCamera
|
||||
};
|
||||
static const Site v1_2_EntityRenderer_m2[] = { // in EntityRenderer.func_181560_a(FJ)V
|
||||
{"org/lwjgl/opengl/Display", "isActive", "()Z", INVOKE_STATIC, 4}, // -> isDisplayActivePreRender
|
||||
};
|
||||
static const Method v1_2_EntityRenderer[] = {
|
||||
{"func_175068_a", "(IFJ)V", v1_2_EntityRenderer_m0, 2},
|
||||
{"func_78479_a", "(FI)V", v1_2_EntityRenderer_m1, 1},
|
||||
{"func_181560_a", "(FJ)V", v1_2_EntityRenderer_m2, 1},
|
||||
};
|
||||
static const Site v1_2_RenderManager_m0[] = { // in RenderManager.func_180597_a(Lnet/minecraft/world/World;Lnet/minecraft/client/gui/FontRenderer;Lnet/minecraft/entity/Entity;Lnet/minecraft/entity/Entity;Lnet/minecraft/client/settings/GameSettings;F)V
|
||||
{"net/minecraft/client/renderer/entity/RenderManager", "field_78732_j", "F", PUTFIELD, 0}, // -> setRenderX
|
||||
{"net/minecraft/client/renderer/entity/RenderManager", "field_78735_i", "F", PUTFIELD, 1}, // -> setRenderY
|
||||
};
|
||||
static const Method v1_2_RenderManager[] = {
|
||||
{"func_180597_a", "(Lnet/minecraft/world/World;Lnet/minecraft/client/gui/FontRenderer;Lnet/minecraft/entity/Entity;Lnet/minecraft/entity/Entity;Lnet/minecraft/client/settings/GameSettings;F)V", v1_2_RenderManager_m0, 2},
|
||||
};
|
||||
static const Site v1_2_RendererLivingEntity_m0[] = { // in RendererLivingEntity.*
|
||||
{"net/minecraft/client/renderer/entity/RendererLivingEntity", "BAD@10000968", "BAD@10000970", INVOKE_VIRTUAL, 0}, // -> canRenderName
|
||||
};
|
||||
static const Method v1_2_RendererLivingEntity[] = {
|
||||
{ANY, ANY, v1_2_RendererLivingEntity_m0, 1},
|
||||
};
|
||||
static const Site v1_2_RenderPlayer_m0[] = { // in RenderPlayer.BAD@10000a90BAD@10000a98
|
||||
{"net/minecraft/client/renderer/entity/RendererLivingEntity", "BAD@10000980", "BAD@10000988", INVOKE_VIRTUAL, 0}, // -> renderPlayer
|
||||
};
|
||||
static const Method v1_2_RenderPlayer[] = {
|
||||
{"BAD@10000a90", "BAD@10000a98", v1_2_RenderPlayer_m0, 1},
|
||||
};
|
||||
static const Site v1_2_NetHandlerPlayClient_m0[] = { // in NetHandlerPlayClient.func_147263_a(Lnet/minecraft/network/play/server/S21PacketChunkData;)V
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "func_147458_c", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v1_2_NetHandlerPlayClient_m1[] = { // in NetHandlerPlayClient.func_147269_a(Lnet/minecraft/network/play/server/S26PacketMapChunkBulk;)V
|
||||
{"net/minecraft/client/multiplayer/WorldClient", "func_147458_c", "(IIIIII)V", INVOKE_VIRTUAL, 0}, // -> invalidateChunk
|
||||
};
|
||||
static const Site v1_2_NetHandlerPlayClient_m2[] = { // in NetHandlerPlayClient.func_147297_a(Lnet/minecraft/network/Packet;)V
|
||||
{"net/minecraft/network/NetworkManager", "func_179290_a", "(Lnet/minecraft/network/Packet;)V", INVOKE_VIRTUAL, 2}, // -> sendPacket_1_8
|
||||
};
|
||||
static const Method v1_2_NetHandlerPlayClient[] = {
|
||||
{"func_147263_a", "(Lnet/minecraft/network/play/server/S21PacketChunkData;)V", v1_2_NetHandlerPlayClient_m0, 1},
|
||||
{"func_147269_a", "(Lnet/minecraft/network/play/server/S26PacketMapChunkBulk;)V", v1_2_NetHandlerPlayClient_m1, 1},
|
||||
{"func_147297_a", "(Lnet/minecraft/network/Packet;)V", v1_2_NetHandlerPlayClient_m2, 1},
|
||||
};
|
||||
static const Site v1_2_WorldClient_m0[] = { // in WorldClient.func_180503_b(Lnet/minecraft/util/BlockPos;Lnet/minecraft/block/state/IBlockState;)Z
|
||||
{"net/minecraft/world/World", "func_180501_a", "(Lnet/minecraft/util/BlockPos;Lnet/minecraft/block/state/IBlockState;I)Z", INVOKE_VIRTUAL, 2}, // -> setBlock_1_8
|
||||
};
|
||||
static const Site v1_2_WorldClient_m1[] = { // in WorldClient.func_73025_a(IIZ)V
|
||||
{"net/minecraft/client/multiplayer/ChunkProviderClient", "func_73234_b", "(II)V", INVOKE_VIRTUAL, 0}, // -> unloadChunk
|
||||
};
|
||||
static const Method v1_2_WorldClient[] = {
|
||||
{"func_180503_b", "(Lnet/minecraft/util/BlockPos;Lnet/minecraft/block/state/IBlockState;)Z", v1_2_WorldClient_m0, 1},
|
||||
{"func_73025_a", "(IIZ)V", v1_2_WorldClient_m1, 1},
|
||||
};
|
||||
static const Site v1_2_Entity_m0[] = { // in Entity.func_70091_d(DDD)V
|
||||
{"net/minecraft/entity/Entity", "func_70093_af", "()Z", INVOKE_VIRTUAL, 0}, // -> shouldClipLedge
|
||||
};
|
||||
static const Method v1_2_Entity[] = {
|
||||
{"func_70091_d", "(DDD)V", v1_2_Entity_m0, 1},
|
||||
};
|
||||
static const Site v1_2_EntityLivingBase_m0[] = { // in EntityLivingBase.func_70612_e(FF)V
|
||||
{"net/minecraft/entity/EntityLivingBase", "func_70060_a", "(FFF)V", INVOKE_VIRTUAL, 0}, // -> moveFlying
|
||||
};
|
||||
static const Method v1_2_EntityLivingBase[] = {
|
||||
{"func_70612_e", "(FF)V", v1_2_EntityLivingBase_m0, 1},
|
||||
};
|
||||
static const HookedClass v1_2[9] = {
|
||||
{&JIds::Minecraft, natives_Minecraft, 6, v1_2_Minecraft, 3},
|
||||
{&JIds::EntityRenderer, natives_EntityRenderer, 5, v1_2_EntityRenderer, 3},
|
||||
{&JIds::RenderManager, natives_RenderManager, 2, v1_2_RenderManager, 1},
|
||||
{&JIds::RendererLivingEntity, natives_RendererLivingEntity, 1, v1_2_RendererLivingEntity, 1},
|
||||
{&JIds::RenderPlayer, natives_RenderPlayer, 1, v1_2_RenderPlayer, 1},
|
||||
{&JIds::NetHandlerPlayClient, natives_NetHandlerPlayClient, 3, v1_2_NetHandlerPlayClient, 3},
|
||||
{&JIds::WorldClient, natives_WorldClient, 3, v1_2_WorldClient, 2},
|
||||
{&JIds::Entity, natives_Entity, 1, v1_2_Entity, 1},
|
||||
{&JIds::EntityLivingBase, natives_EntityLivingBase, 1, v1_2_EntityLivingBase, 1},
|
||||
};
|
||||
|
||||
static const HookedClass *const kHooks[2][3] = {
|
||||
{v0_0, v0_1, v0_2}, // 1.7.10: obfuscated, MCP, SRG
|
||||
{v1_0, v1_1, v1_2}, // 1.8.9
|
||||
};
|
||||
@@ -0,0 +1,299 @@
|
||||
// JNI: entity field accessors.
|
||||
// cflags: /Ob1 /clang:-fno-slp-vectorize
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jcall.h"
|
||||
struct Rot { float yaw, pitch; };
|
||||
struct Vec3 { double x, y, z; };
|
||||
FN(Vec3, vec3_make, (double x, double y, double z));
|
||||
extern "C" void java_set_entity_player_item_in_use_count(Jvm *jvm, void *entity_player, int value) {
|
||||
JCALL(jvm, SetIntField, entity_player, ids->EntityPlayer_itemInUseCount, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_entity_player_get_equipment_in_slot(Jvm *jvm, void *entity_player, int value) {
|
||||
return JCALLN(jvm, CallObjectMethod, entity_player, ids->EntityPlayer_getEquipmentInSlot, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_entity_player_inventory_container(Jvm *jvm, void *entity_player) {
|
||||
return JCALL(jvm, GetObjectField, entity_player, ids->EntityPlayer_inventoryContainer);
|
||||
}
|
||||
|
||||
extern "C" bool java_get_entity_player_sleeping(Jvm *jvm, void *entity_player) {
|
||||
return JCALLN(jvm, GetBooleanField, entity_player, ids->EntityPlayer_sleeping) != 0;
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_entity_player_speed_on_ground(Jvm *jvm, void *entity_player) {
|
||||
return JCALL(jvm, GetFloatField, entity_player, ids->EntityPlayer_speedOnGround);
|
||||
}
|
||||
|
||||
extern "C" void java_set_entity_player_speed_on_ground(Jvm *jvm, void *entity_player, float value) {
|
||||
JCALL(jvm, SetFloatField, entity_player, ids->EntityPlayer_speedOnGround, value);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_entity_other_player_mp_other_player_mppos_rotation_increments(Jvm *jvm, void *entity_other_player_mp) {
|
||||
return JCALL(jvm, GetIntField, entity_other_player_mp, ids->EntityOtherPlayerMP_otherPlayerMPPosRotationIncrements);
|
||||
}
|
||||
|
||||
extern "C" jdouble java_get_entity_other_player_mp_other_player_mpyaw(Jvm *jvm, void *entity_other_player_mp) {
|
||||
return JCALL(jvm, GetDoubleField, entity_other_player_mp, ids->EntityOtherPlayerMP_otherPlayerMPYaw);
|
||||
}
|
||||
|
||||
extern "C" jobject get_block_state_id(Jvm *jvm, void *block) {
|
||||
return JCALLN(jvm, CallStaticObjectMethod, ids->Block, ids->Block_getBlockFromItem, block);
|
||||
}
|
||||
|
||||
extern "C" jint get_block_id(Jvm *jvm, void *block) {
|
||||
return JCALLN(jvm, CallStaticIntMethod, ids->Block, ids->Block_getIdFromBlock, block);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_tile_entity_block_type(Jvm *jvm, void *tile_entity) {
|
||||
return JCALL(jvm, GetObjectField, tile_entity, ids->TileEntity_blockType);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_world_player_entities(Jvm *jvm, void *world) {
|
||||
return JCALL(jvm, GetObjectField, world, ids->World_playerEntities);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_world_loaded_entity_list(Jvm *jvm, void *world) {
|
||||
return JCALL(jvm, GetObjectField, world, ids->World_loadedEntityList);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_world_loaded_tile_entity_list(Jvm *jvm, void *world) {
|
||||
return JCALL(jvm, GetObjectField, world, ids->World_loadedTileEntityList);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_chunk_storage_arrays(Jvm *jvm, void *chunk) {
|
||||
return JCALL(jvm, GetObjectField, chunk, ids->Chunk_storageArrays);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_chunk_x_position(Jvm *jvm, void *chunk) {
|
||||
return JCALL(jvm, GetIntField, chunk, ids->Chunk_xPosition);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_chunk_z_position(Jvm *jvm, void *chunk) {
|
||||
return JCALL(jvm, GetIntField, chunk, ids->Chunk_zPosition);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_id_b20(Jvm *jvm, void *object) {
|
||||
return JCALL(jvm, GetObjectField, object, ids->ExtendedBlockStorage_blockLSBArray);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_chunk_provider_client(Jvm *jvm, void *chunk_provider_client) {
|
||||
return JCALL(jvm, GetObjectField, chunk_provider_client, ids->ChunkProviderClient_chunkMapping);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_moving_object_position_hit_vec(Jvm *jvm, void *moving_object_position) {
|
||||
return JCALL(jvm, GetObjectField, moving_object_position, ids->MovingObjectPosition_hitVec);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_moving_object_position_entity_hit(Jvm *jvm, void *moving_object_position) {
|
||||
return JCALL(jvm, GetObjectField, moving_object_position, ids->MovingObjectPosition_entityHit);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_list_get(Jvm *jvm, void *list, int value) {
|
||||
return JCALLN(jvm, CallObjectMethod, list, ids->List_get, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_map_get(Jvm *jvm, void *map, void *value) {
|
||||
return JCALLN(jvm, CallObjectMethod, map, ids->Map_get, value);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_game_settings_mouse_sensitivity(Jvm *jvm, void *game_settings) {
|
||||
return JCALL(jvm, GetFloatField, game_settings, ids->GameSettings_mouseSensitivity);
|
||||
}
|
||||
|
||||
extern "C" jobject get_block_from_pos(Jvm *jvm, void *a1) {
|
||||
return JCALLN(jvm, CallObjectMethod, a1, ids->_init__getBlock);
|
||||
}
|
||||
|
||||
extern "C" void update_entities(Jvm *jvm, void *world) {
|
||||
JCALLN(jvm, CallVoidMethod, world, ids->World_updateEntities);
|
||||
}
|
||||
|
||||
extern "C" jint java_call_list_size(Jvm *jvm, void *list) {
|
||||
return JCALLN(jvm, CallIntMethod, list, ids->List_size);
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_entity_other_player_mp(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->EntityOtherPlayerMP) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" Vec3 java_get_entity_other_player_mp_other_player_mpx(Jvm *jvm, jobject entity_other_player_mp) {
|
||||
double x = JCALLN(jvm, GetDoubleField, entity_other_player_mp, ids->EntityOtherPlayerMP_otherPlayerMPX);
|
||||
double y = JCALLN(jvm, GetDoubleField, entity_other_player_mp, ids->EntityOtherPlayerMP_otherPlayerMPY);
|
||||
double z = JCALLN(jvm, GetDoubleField, entity_other_player_mp, ids->EntityOtherPlayerMP_otherPlayerMPZ);
|
||||
Vec3 r = vec3_make(x, y, z);
|
||||
asm volatile("");
|
||||
return r;
|
||||
}
|
||||
|
||||
// ---- hand-written (BlockPos / Vec3i glue, multi-argument calls)
|
||||
struct I3 { int x, y, z; };
|
||||
FN(jobject, get_block_from_pos, (Jvm *j, void *block_pos));
|
||||
|
||||
extern "C" __attribute__((noinline)) jobject java_block_pos_init(Jvm *jvm, int x, int y, int z) {
|
||||
return JNI(jvm)->NewObject(jvm->env, (jclass)jvm->ids->BlockPos, (jmethodID)jvm->ids->BlockPos__init_, x, y, z);
|
||||
}
|
||||
extern "C" Vec3 java_get_block_pos_a(Jvm *jvm, jobject block_pos) {
|
||||
double x = JCALLN(jvm, GetIntField, block_pos, (jfieldID)ids->BlockPos_a);
|
||||
double y = JCALLN(jvm, GetIntField, block_pos, (jfieldID)ids->BlockPos_c);
|
||||
double z = JCALLN(jvm, GetIntField, block_pos, (jfieldID)ids->BlockPos_d);
|
||||
Vec3 r = vec3_make(x, y, z);
|
||||
asm volatile("");
|
||||
return r;
|
||||
}
|
||||
extern "C" __attribute__((noinline)) void java_set_block_pos_a(Jvm *jvm, jobject block_pos, int value, int y, int z) {
|
||||
JCALLN(jvm, SetIntField, block_pos, (jfieldID)ids->BlockPos_a, value);
|
||||
JCALLN(jvm, SetIntField, block_pos, (jfieldID)ids->BlockPos_c, y);
|
||||
JCALL(jvm, SetIntField, block_pos, (jfieldID)ids->BlockPos_d, z);
|
||||
}
|
||||
extern "C" I3 get_block_position(Jvm *jvm, jobject object) {
|
||||
I3 r;
|
||||
r.x = JCALLN(jvm, GetIntField, object, (jfieldID)ids->BlockPos_a);
|
||||
r.y = JCALLN(jvm, GetIntField, object, (jfieldID)ids->BlockPos_c);
|
||||
r.z = JCALLN(jvm, GetIntField, object, (jfieldID)ids->BlockPos_d);
|
||||
return r;
|
||||
}
|
||||
// block position of an entity/object; the mapping set decides whether it goes through a BlockPos
|
||||
extern "C" Vec3 java_get_block_pos_as_vec3(Jvm *jvm, jobject block_pos) {
|
||||
int x;
|
||||
long yo, zo;
|
||||
if (jvm->ids->variant != 0) {
|
||||
block_pos = JCALLN(jvm, GetObjectField, block_pos, (jfieldID)ids->TileEntity_pos);
|
||||
x = JCALLN(jvm, GetIntField, block_pos, (jfieldID)ids->BlockPos_a);
|
||||
zo = 0x468; yo = 0x460;
|
||||
} else {
|
||||
x = JCALLN(jvm, GetIntField, block_pos, (jfieldID)ids->TileEntity_xCoord);
|
||||
zo = 0x780; yo = 0x778;
|
||||
}
|
||||
double dx = x;
|
||||
double dy = JCALLN(jvm, GetIntField, block_pos, *(jfieldID *)((char *)ids + yo));
|
||||
double dz = JCALLN(jvm, GetIntField, block_pos, *(jfieldID *)((char *)ids + zo));
|
||||
Vec3 r = vec3_make(dx, dy, dz);
|
||||
asm volatile("");
|
||||
return r;
|
||||
}
|
||||
extern "C" jobject java_call_variant(Jvm *jvm, jobject object, int x, int y, int z) {
|
||||
if (jvm->ids->variant != 0) {
|
||||
jobject p = java_block_pos_init(jvm, x, y, z);
|
||||
jobject r = JCALLN(jvm, CallObjectMethod, object, (jmethodID)ids->World_getBlockState, p);
|
||||
return get_block_from_pos(jvm, r);
|
||||
}
|
||||
return JNI(jvm)->CallObjectMethod(jvm->env, object, (jmethodID)jvm->ids->World_getBlock, x, y, z);
|
||||
}
|
||||
extern "C" jobject java_call_world_get_block_state(Jvm *jvm, jobject object, jobject pos, int x, int y, int z) {
|
||||
if (pos) {
|
||||
java_set_block_pos_a(jvm, pos, x, y, z);
|
||||
jobject r = JCALLN(jvm, CallObjectMethod, object, (jmethodID)ids->World_getBlockState, pos);
|
||||
return get_block_from_pos(jvm, r);
|
||||
}
|
||||
return JNI(jvm)->CallObjectMethod(jvm->env, object, (jmethodID)jvm->ids->World_getBlock, x, y, z);
|
||||
}
|
||||
extern "C" jboolean set_block_state(Jvm *jvm, jobject object, int a, int b, int c, void *d, int e, int f) {
|
||||
return JNI(jvm)->CallBooleanMethod(jvm->env, object, (jmethodID)jvm->ids->World_setBlock, a, b, c, d, e, f);
|
||||
}
|
||||
extern "C" jboolean set_block_at_pos(Jvm *jvm, jobject object, void *other_object, void *b, int c) {
|
||||
return JNI(jvm)->CallBooleanMethod(jvm->env, object, (jmethodID)jvm->ids->World_setBlockState, other_object, b, c);
|
||||
}
|
||||
extern "C" jobject java_world_ray_trace_blocks(Jvm *jvm, jobject object, void *other_object, void *b, bool c, bool d, bool e) {
|
||||
int c_ = c, d_ = d, e_ = e;
|
||||
|
||||
return JNI(jvm)->CallObjectMethod(jvm->env, object, (jmethodID)jvm->ids->World_rayTraceBlocks, other_object, b, c_, d_, e_);
|
||||
}
|
||||
extern "C" jobject java_world_get_chunk_from_chunk_coords(Jvm *jvm, jobject object, int a, int b) {
|
||||
return JNI(jvm)->CallObjectMethod(jvm->env, object, (jmethodID)jvm->ids->World_getChunkFromChunkCoords, a, b);
|
||||
}
|
||||
extern "C" void update_chunk(Jvm *jvm, jobject object, int a, int b, int c, int d, int e, int f) {
|
||||
JNI(jvm)->CallVoidMethod(jvm->env, object, (jmethodID)jvm->ids->World_markBlockRangeForRenderUpdate, a, b, c, d, e, f);
|
||||
}
|
||||
extern "C" jobject java_get_world_chunk_provider_client(Jvm *jvm, jobject chunk_provider_client) {
|
||||
jobject x = JCALLN(jvm, GetObjectField, chunk_provider_client, (jfieldID)ids->World_chunkProvider);
|
||||
return JCALLN(jvm, IsInstanceOf, x, (jclass)ids->ChunkProviderClient) ? x : nullptr;
|
||||
}
|
||||
extern "C" void set_block_meta(Jvm *jvm, jobject object, int a, int b) {
|
||||
JNI(jvm)->CallVoidMethod(jvm->env, object, (jmethodID)jvm->ids->ChunkProviderClient_unloadChunk, a, b);
|
||||
}
|
||||
extern "C" jobject java_moving_object_position_init(Jvm *jvm, void *moving_object_position, void *other_object) {
|
||||
return JNI(jvm)->NewObject(jvm->env, (jclass)jvm->ids->MovingObjectPosition, (jmethodID)jvm->ids->MovingObjectPosition__init_, moving_object_position, other_object);
|
||||
}
|
||||
|
||||
FN(jint, java_call_enum_ordinal, (Jvm *jvm, void *axis_aligned_bb)); // enum ordinal
|
||||
FN(Vec3, vec3_add, (Vec3 a, Vec3 b)); // a + b
|
||||
FN(Vec3, java_get_block_pos_a, (Jvm *j, jobject o));
|
||||
FN(I3, get_block_position, (Jvm *j, jobject block_pos));
|
||||
extern "C" const Vec3 jni_entity_accessors_facing_unit_offset[]; // facing -> unit offset
|
||||
|
||||
extern "C" jobject java_axis_aligned_bb_calculate_intercept(Jvm *jvm, jobject object, void *other_object, void *b) {
|
||||
return JNI(jvm)->CallObjectMethod(jvm->env, object, (jmethodID)jvm->ids->AxisAlignedBB_calculateIntercept, other_object, b);
|
||||
}
|
||||
extern "C" jint java_get_moving_object_position_type_ordinal(Jvm *jvm, jobject moving_object_position) {
|
||||
jobject x = JCALLN(jvm, GetObjectField, moving_object_position, (jfieldID)ids->MovingObjectPosition_typeOfHit);
|
||||
return java_call_enum_ordinal(jvm, x);
|
||||
}
|
||||
// side hit of a MovingObjectPosition (EnumFacing ordinal)
|
||||
extern "C" jint java_get_variant(Jvm *jvm, jobject object) {
|
||||
if (jvm->ids->variant != 0) {
|
||||
jobject x = JCALLN(jvm, GetObjectField, object, (jfieldID)ids->MovingObjectPosition_sideHit);
|
||||
return java_call_enum_ordinal(jvm, x);
|
||||
}
|
||||
return JCALL(jvm, GetIntField, object, (jfieldID)ids->MovingObjectPosition_sideHitIndex);
|
||||
}
|
||||
// block position of a MovingObjectPosition
|
||||
extern "C" Vec3 java_get_moving_object_position_block_x(Jvm *jvm, jobject moving_object_position) {
|
||||
if (jvm->ids->variant != 0) {
|
||||
jobject block_pos = JCALLN(jvm, GetObjectField, moving_object_position, (jfieldID)ids->MovingObjectPosition_blockPos);
|
||||
return java_get_block_pos_a(jvm, block_pos);
|
||||
}
|
||||
double x = JCALLN(jvm, GetIntField, moving_object_position, (jfieldID)ids->MovingObjectPosition_blockX);
|
||||
double y = JCALLN(jvm, GetIntField, moving_object_position, (jfieldID)ids->MovingObjectPosition_blockY);
|
||||
double z = JCALLN(jvm, GetIntField, moving_object_position, (jfieldID)ids->MovingObjectPosition_blockZ);
|
||||
return vec3_make(x, y, z);
|
||||
}
|
||||
extern "C" I3 java_get_moving_object_position_block_pos(Jvm *jvm, jobject moving_object_position) {
|
||||
if (jvm->ids->variant != 0) {
|
||||
jobject p = JCALLN(jvm, GetObjectField, moving_object_position, (jfieldID)ids->MovingObjectPosition_blockPos);
|
||||
return get_block_position(jvm, p);
|
||||
}
|
||||
I3 r;
|
||||
r.x = JCALLN(jvm, GetIntField, moving_object_position, (jfieldID)ids->MovingObjectPosition_blockX);
|
||||
r.y = JCALLN(jvm, GetIntField, moving_object_position, (jfieldID)ids->MovingObjectPosition_blockY);
|
||||
r.z = JCALLN(jvm, GetIntField, moving_object_position, (jfieldID)ids->MovingObjectPosition_blockZ);
|
||||
return r;
|
||||
}
|
||||
// block position + facing offset (the block the hit side points to)
|
||||
extern "C" Vec3 java_get_adjacent_block_position(Jvm *jvm, jobject object) {
|
||||
Vec3 p = java_get_moving_object_position_block_x(jvm, object);
|
||||
unsigned side;
|
||||
if (jvm->ids->variant != 0) {
|
||||
jobject x = JCALLN(jvm, GetObjectField, object, (jfieldID)ids->MovingObjectPosition_sideHit);
|
||||
side = java_call_enum_ordinal(jvm, x);
|
||||
} else {
|
||||
side = JCALLN(jvm, GetIntField, object, (jfieldID)ids->MovingObjectPosition_sideHitIndex);
|
||||
}
|
||||
return vec3_add(p, jni_entity_accessors_facing_unit_offset[side]);
|
||||
}
|
||||
extern "C" jobject java_get_game_settings_use_item_key(Jvm *jvm, jobject game_settings) {
|
||||
jobject object = JCALLN(jvm, GetObjectField, game_settings, (jfieldID)ids->GameSettings_keyBindings);
|
||||
return JCALL(jvm, GetObjectArrayElement, (jobjectArray)object, 1);
|
||||
}
|
||||
extern "C" jobject java_get_game_settings_key_8_or_9(Jvm *jvm, jobject game_settings) {
|
||||
jobject object = JCALLN(jvm, GetObjectField, game_settings, (jfieldID)ids->GameSettings_keyBindings);
|
||||
return JCALL(jvm, GetObjectArrayElement, (jobjectArray)object, ids->variant == 0 ? 8 : 9);
|
||||
}
|
||||
|
||||
// ---- JNI bridge functions found by the gap scan (no caller outside the virtualised init) ----
|
||||
extern "C" jobject java_call_world_get_entity_by_id(Jvm *jvm, void *world, int value) {
|
||||
return JCALLN(jvm, CallObjectMethod, world, ids->World_getEntityByID, value);
|
||||
}
|
||||
|
||||
extern "C" jboolean java_call_list_add(Jvm *jvm, void *list, void *value) {
|
||||
return JCALLN(jvm, CallBooleanMethod, list, ids->List_add, value);
|
||||
}
|
||||
|
||||
// ---- functions the first gap scan missed (padding / tail-jump neighbours) ----
|
||||
extern "C" jboolean java_call_list_remove(Jvm *jvm, void *list, void *value) {
|
||||
return JCALLN(jvm, CallBooleanMethod, list, ids->List_remove, value);
|
||||
}
|
||||
|
||||
extern "C" bool java_call_map_contains_key(Jvm *jvm, void *map, void *value) {
|
||||
return JCALLN(jvm, CallBooleanMethod, map, ids->Map_containsKey, value) != 0;
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
// JNI: entity rotation and angle accessors.
|
||||
// cflags: /Ob1 /clang:-fno-slp-vectorize
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jcall.h"
|
||||
struct Rot { float yaw, pitch; };
|
||||
struct Vec3 { double x, y, z; };
|
||||
FN(Vec3, vec3_make, (double x, double y, double z));
|
||||
FN(Rot, rot_make, (float yaw, float pitch));
|
||||
FN(float, limit_rotation_delta, (float lo, float hi, float v));
|
||||
FN(float, libc_sinf, (float x)); // sinf
|
||||
FN(float, libc_cosf, (float x)); // cosf
|
||||
typedef int v4i __attribute__((vector_size(16), aligned(16)));
|
||||
typedef float v4f __attribute__((vector_size(16), aligned(16)));
|
||||
extern "C" const float clamp_bound_neg_90, clamp_bound_90, clamp_bound_neg_90_in_jni_entity_rotation, clamp_bound_90_in_jni_entity_rotation, twenty_in_jni_entity_rotation, twenty_in_jni_entity_rotation_a;
|
||||
extern "C" const double neg_pi, half_turn_degrees_in_jni_entity_rotation, one_in_jni_entity_rotation;
|
||||
extern "C" const v4i jni_entity_rotation_sign_mask; // sign mask
|
||||
|
||||
extern "C" bool java_variant(Jvm *jvm) { return jvm->ids->variant == 0; }
|
||||
|
||||
// rotationYaw/rotationPitch packed into a Rot
|
||||
extern "C" Rot get_entity_rotation(Jvm *jvm, jobject object) {
|
||||
if (!object) return Rot{0.0f, 0.0f};
|
||||
float y = JCALLN(jvm, GetFloatField, object, ids->Entity_rotationYaw);
|
||||
float pitch = JCALLN(jvm, GetFloatField, object, ids->Entity_rotationPitch);
|
||||
return rot_make(y, pitch);
|
||||
}
|
||||
extern "C" Rot get_entity_prev_rotation(Jvm *jvm, jobject object) {
|
||||
if (!object) return Rot{0.0f, 0.0f};
|
||||
float y = JCALLN(jvm, GetFloatField, object, ids->Entity_prevRotationYaw);
|
||||
float pitch = JCALLN(jvm, GetFloatField, object, ids->Entity_prevRotationPitch);
|
||||
return rot_make(y, pitch);
|
||||
}
|
||||
// set yaw, pitch clamped to [lo, hi]
|
||||
extern "C" void set_entity_rotation(Jvm *jvm, jobject object, Rot rotation) {
|
||||
if (!object) return;
|
||||
JCALLN(jvm, SetFloatField, object, ids->Entity_rotationYaw, rotation.yaw);
|
||||
JIds *ids = jvm->ids; JNIEnv *env = jvm->env;
|
||||
auto f = (*env)->SetFloatField; jfieldID id = ids->Entity_rotationPitch;
|
||||
|
||||
f(env, object, id, limit_rotation_delta(clamp_bound_neg_90, clamp_bound_90, rotation.pitch));
|
||||
}
|
||||
extern "C" void set_entity_prev_rotation(Jvm *jvm, jobject object, Rot rotation) {
|
||||
if (!object) return;
|
||||
JCALLN(jvm, SetFloatField, object, ids->Entity_prevRotationYaw, rotation.yaw);
|
||||
JIds *ids = jvm->ids; JNIEnv *env = jvm->env;
|
||||
auto f = (*env)->SetFloatField; jfieldID id = ids->Entity_prevRotationPitch;
|
||||
|
||||
f(env, object, id, limit_rotation_delta(clamp_bound_neg_90_in_jni_entity_rotation, clamp_bound_90_in_jni_entity_rotation, rotation.pitch));
|
||||
}
|
||||
// look vector from the entity rotation
|
||||
extern "C" Vec3 java_get_entity_rotation_pitch(Jvm *jvm, jobject entity) {
|
||||
float y = JCALLN(jvm, GetFloatField, entity, ids->Entity_rotationYaw);
|
||||
float pitch = JCALLN(jvm, GetFloatField, entity, ids->Entity_rotationPitch);
|
||||
Rot rotation = rot_make(y, pitch);
|
||||
float a = (float)((double)rotation.yaw * neg_pi / half_turn_degrees_in_jni_entity_rotation + neg_pi);
|
||||
float ca = libc_cosf(a);
|
||||
float sa = libc_sinf(a);
|
||||
float b = (float)((double)rotation.pitch * neg_pi / half_turn_degrees_in_jni_entity_rotation);
|
||||
float cb = ((v4f)((v4i)(v4f){libc_cosf(b)} ^ jni_entity_rotation_sign_mask))[0];
|
||||
float sb = libc_sinf(b);
|
||||
float x = sa * cb;
|
||||
Vec3 v = vec3_make(x, sb, cb * ca);
|
||||
|
||||
return v;
|
||||
}
|
||||
extern "C" void java_set_entity_motion_x(Jvm *jvm, jobject entity, Vec3 value) {
|
||||
JCALLN(jvm, SetDoubleField, entity, ids->Entity_motionX, value.x);
|
||||
JCALLN(jvm, SetDoubleField, entity, ids->Entity_motionY, value.y);
|
||||
JCALL(jvm, SetDoubleField, entity, ids->Entity_motionZ, value.z);
|
||||
}
|
||||
extern "C" void set_motion(Jvm *jvm, jobject object, float x, float y, float z) {
|
||||
JNI(jvm)->CallVoidMethod(jvm->env, object, jvm->ids->Entity_moveFlying, (double)x, (double)y, (double)z);
|
||||
}
|
||||
extern "C" jobject java_call_entity_living_base_get_position(Jvm *jvm, jobject entity_living_base) {
|
||||
return JCALL1(jvm, CallObjectMethod, entity_living_base, ids->EntityLivingBase_getPosition, one_in_jni_entity_rotation);
|
||||
}
|
||||
static inline __attribute__((always_inline)) bool isnan_bits(float f) {
|
||||
return (__builtin_bit_cast(unsigned, f) & 0x7fffffff) >= 0x7f800001;
|
||||
}
|
||||
extern "C" float java_call_entity_living_base_get_health(Jvm *jvm, jobject entity_living_base) {
|
||||
float f = JCALLN(jvm, CallFloatMethod, entity_living_base, ids->EntityLivingBase_getHealth);
|
||||
return isnan_bits(f) ? twenty_in_jni_entity_rotation : f;
|
||||
}
|
||||
extern "C" float java_call_entity_living_base_get_max_health(Jvm *jvm, jobject entity_living_base) {
|
||||
float f = JCALLN(jvm, CallFloatMethod, entity_living_base, ids->EntityLivingBase_getMaxHealth);
|
||||
return isnan_bits(f) ? twenty_in_jni_entity_rotation_a : f;
|
||||
}
|
||||
extern "C" float java_call_entity_player_get_absorption_amount(Jvm *jvm, jobject entity_player) {
|
||||
float f = JCALLN(jvm, CallFloatMethod, entity_player, ids->EntityPlayer_getAbsorptionAmount);
|
||||
if (__builtin_expect(isnan_bits(f), 0)) f = 0.0f;
|
||||
return f;
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_entity_bounding_box(Jvm *jvm, void *entity) {
|
||||
return JCALL(jvm, GetObjectField, entity, ids->Entity_boundingBox);
|
||||
}
|
||||
|
||||
extern "C" void java_call_entity_set_sprinting(Jvm *jvm, void *entity, unsigned char value) {
|
||||
JCALL1(jvm, CallVoidMethod, entity, ids->Entity_setSprinting, value);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_entity_entity_id(Jvm *jvm, void *entity) {
|
||||
return JCALL(jvm, GetIntField, entity, ids->Entity_entityId);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_entity_uuid(Jvm *jvm, void *entity) {
|
||||
return JCALL(jvm, GetObjectField, entity, ids->Entity_uniqueId);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_entity_ticks_existed(Jvm *jvm, void *entity) {
|
||||
return JCALL(jvm, GetIntField, entity, ids->Entity_ticksExisted);
|
||||
}
|
||||
|
||||
extern "C" bool java_get_entity_on_ground(Jvm *jvm, void *entity) {
|
||||
return JCALLN(jvm, GetBooleanField, entity, ids->Entity_onGround) != 0;
|
||||
}
|
||||
|
||||
extern "C" bool java_get_entity_is_dead(Jvm *jvm, void *entity) {
|
||||
return JCALLN(jvm, GetBooleanField, entity, ids->Entity_isDead) != 0;
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_entity_fall_distance(Jvm *jvm, void *entity) {
|
||||
return JCALL(jvm, GetFloatField, entity, ids->Entity_fallDistance);
|
||||
}
|
||||
|
||||
extern "C" jint java_call_static_entity_list_get_entity_id(Jvm *jvm, void *entity_list) {
|
||||
return JCALLN(jvm, CallStaticIntMethod, ids->EntityList, ids->EntityList_getEntityID, entity_list);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_entity_riding_entity(Jvm *jvm, void *entity) {
|
||||
return JCALL(jvm, GetObjectField, entity, ids->Entity_ridingEntity);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_entity_living_base_last_attacker_time(Jvm *jvm, void *entity_living_base) {
|
||||
return JCALL(jvm, GetIntField, entity_living_base, ids->EntityLivingBase_lastAttackerTime);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_entity_living_base_hurt_time(Jvm *jvm, void *entity_living_base) {
|
||||
return JCALL(jvm, GetIntField, entity_living_base, ids->EntityLivingBase_hurtTime);
|
||||
}
|
||||
|
||||
extern "C" void java_set_entity_living_base_max_hurt_time(Jvm *jvm, void *entity_living_base, int value) {
|
||||
JCALL(jvm, SetIntField, entity_living_base, ids->EntityLivingBase_maxHurtTime, value);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_entity_living_base_move_forward(Jvm *jvm, void *entity_living_base) {
|
||||
return JCALL(jvm, GetFloatField, entity_living_base, ids->EntityLivingBase_moveForward);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_entity_living_base_move_strafing(Jvm *jvm, void *entity_living_base) {
|
||||
return JCALL(jvm, GetFloatField, entity_living_base, ids->EntityLivingBase_moveStrafing);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_entity_ray_trace(Jvm *jvm, void *object, double value, float a3) {
|
||||
return JCALL1(jvm, CallObjectMethod, object, ids->EntityLivingBase_rayTrace, value, (double)a3);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_entity_living_base_land_movement_factor(Jvm *jvm, void *entity_living_base) {
|
||||
return JCALL(jvm, GetFloatField, entity_living_base, ids->EntityLivingBase_landMovementFactor);
|
||||
}
|
||||
|
||||
extern "C" jint get_hurt_time(Jvm *jvm, void *entity_living_base) {
|
||||
return JCALL(jvm, GetIntField, entity_living_base, ids->EntityLivingBase_jumpTicks);
|
||||
}
|
||||
|
||||
extern "C" void java_set_entity_living_base_jump_ticks(Jvm *jvm, void *entity_living_base, int value) {
|
||||
JCALL(jvm, SetIntField, entity_living_base, ids->EntityLivingBase_jumpTicks, value);
|
||||
}
|
||||
|
||||
extern "C" void java_jni_call_nonvirtual_void_method_entity_living_base_swing_item(Jvm *jvm, void *entity_living_base) {
|
||||
JCALLN(jvm, CallNonvirtualVoidMethod, entity_living_base, ids->EntityLivingBase, ids->EntityLivingBase_swingItem);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_entity_living_base_get_active_potion_effect(Jvm *jvm, void *entity_living_base, void *value) {
|
||||
return JCALLN(jvm, CallObjectMethod, entity_living_base, ids->EntityLivingBase_getActivePotionEffect, value);
|
||||
}
|
||||
|
||||
extern "C" void java_entity_player_sp_render_arm_pitch(Jvm *jvm, void *entity_player_sp, Rot value) {
|
||||
JNI(jvm)->SetFloatField(jvm->env, entity_player_sp, jvm->ids->EntityPlayerSP_renderArmYaw, value.yaw);
|
||||
JNI(jvm)->SetFloatField(jvm->env, entity_player_sp, jvm->ids->EntityPlayerSP_renderArmPitch, value.pitch);
|
||||
}
|
||||
|
||||
extern "C" void java_entity_player_sp_prev_render_arm_pitch(Jvm *jvm, void *entity_player_sp, Rot value) {
|
||||
JNI(jvm)->SetFloatField(jvm->env, entity_player_sp, jvm->ids->EntityPlayerSP_prevRenderArmYaw, value.yaw);
|
||||
JNI(jvm)->SetFloatField(jvm->env, entity_player_sp, jvm->ids->EntityPlayerSP_prevRenderArmPitch, value.pitch);
|
||||
}
|
||||
|
||||
extern "C" void java_call_entity_player_sp_add_chat_message(Jvm *jvm, void *entity_player_sp, void *value) {
|
||||
JCALLN(jvm, CallVoidMethod, entity_player_sp, ids->EntityPlayerSP_addChatMessage, value);
|
||||
}
|
||||
|
||||
extern "C" void java_set_entity_player_sp_time_in_portal(Jvm *jvm, void *entity_player_sp, float value) {
|
||||
JCALL(jvm, SetFloatField, entity_player_sp, ids->EntityPlayerSP_timeInPortal, value);
|
||||
}
|
||||
|
||||
extern "C" void java_set_entity_player_sp_prev_time_in_portal(Jvm *jvm, void *entity_player_sp, float value) {
|
||||
JCALL(jvm, SetFloatField, entity_player_sp, ids->EntityPlayerSP_prevTimeInPortal, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_entity_player_inventory(Jvm *jvm, void *entity_player) {
|
||||
return JCALL(jvm, GetObjectField, entity_player, ids->EntityPlayer_inventory);
|
||||
}
|
||||
|
||||
extern "C" bool java_call_entity_player_is_invisible_to_player(Jvm *jvm, void *entity_player, void *value) {
|
||||
return JCALLN(jvm, CallBooleanMethod, entity_player, ids->EntityPlayer_isInvisibleToPlayer, value) != 0;
|
||||
}
|
||||
|
||||
extern "C" jint java_get_entity_player_item_in_use_count(Jvm *jvm, void *entity_player) {
|
||||
return JCALL(jvm, GetIntField, entity_player, ids->EntityPlayer_itemInUseCount);
|
||||
}
|
||||
|
||||
extern "C" bool java_call_entity_is_sprinting(Jvm *jvm, void *entity) {
|
||||
return JCALLN(jvm, CallBooleanMethod, entity, ids->Entity_isSprinting) != 0;
|
||||
}
|
||||
|
||||
extern "C" bool is_sneaking(Jvm *jvm, void *entity) {
|
||||
return JCALLN(jvm, CallBooleanMethod, entity, ids->Entity_isSneaking) != 0;
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_entity_get_formatted_command_sender_name(Jvm *jvm, void *entity) {
|
||||
return JCALLN(jvm, CallObjectMethod, entity, ids->Entity_getFormattedCommandSenderName);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_entity_get_command_sender_name(Jvm *jvm, void *entity) {
|
||||
return JCALLN(jvm, CallObjectMethod, entity, ids->Entity_getCommandSenderName);
|
||||
}
|
||||
|
||||
extern "C" jboolean java_call_entity_can_be_collided_with(Jvm *jvm, void *entity) {
|
||||
return JCALLN(jvm, CallBooleanMethod, entity, ids->Entity_canBeCollidedWith);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_entity_living_base_get_team(Jvm *jvm, void *entity_living_base) {
|
||||
return JCALLN(jvm, CallObjectMethod, entity_living_base, ids->EntityLivingBase_getTeam);
|
||||
}
|
||||
|
||||
extern "C" bool java_call_entity_player_is_using_item(Jvm *jvm, void *entity_player) {
|
||||
return JCALLN(jvm, CallBooleanMethod, entity_player, ids->EntityPlayer_isUsingItem) != 0;
|
||||
}
|
||||
|
||||
extern "C" Vec3 java_get_entity_pos_x(Jvm *jvm, jobject entity) {
|
||||
double x = JCALLN(jvm, GetDoubleField, entity, ids->Entity_posX);
|
||||
double y = JCALLN(jvm, GetDoubleField, entity, ids->Entity_posY);
|
||||
double z = JCALLN(jvm, GetDoubleField, entity, ids->Entity_posZ);
|
||||
Vec3 r = vec3_make(x, y, z);
|
||||
asm volatile("");
|
||||
return r;
|
||||
}
|
||||
|
||||
extern "C" Vec3 java_get_entity_prev_pos_x(Jvm *jvm, jobject entity) {
|
||||
double x = JCALLN(jvm, GetDoubleField, entity, ids->Entity_prevPosX);
|
||||
double y = JCALLN(jvm, GetDoubleField, entity, ids->Entity_prevPosY);
|
||||
double z = JCALLN(jvm, GetDoubleField, entity, ids->Entity_prevPosZ);
|
||||
Vec3 r = vec3_make(x, y, z);
|
||||
asm volatile("");
|
||||
return r;
|
||||
}
|
||||
|
||||
extern "C" Vec3 java_get_entity_motion_x(Jvm *jvm, jobject entity) {
|
||||
double x = JCALLN(jvm, GetDoubleField, entity, ids->Entity_motionX);
|
||||
double y = JCALLN(jvm, GetDoubleField, entity, ids->Entity_motionY);
|
||||
double z = JCALLN(jvm, GetDoubleField, entity, ids->Entity_motionZ);
|
||||
Vec3 r = vec3_make(x, y, z);
|
||||
asm volatile("");
|
||||
return r;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_entity_living_base(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->EntityLivingBase) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_entity_player(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->EntityPlayer) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
// ---- JNI bridge functions found by the gap scan (no caller outside the virtualised init) ----
|
||||
extern "C" void java_call_entity_set_angles(Jvm *jvm, void *entity, float value, float a3) {
|
||||
JCALL1(jvm, CallVoidMethod, entity, ids->Entity_setAngles, ({ double y = value; y; }), (double)a3);
|
||||
}
|
||||
|
||||
extern "C" jdouble java_get_entity_motion_x_value(Jvm *jvm, void *entity) {
|
||||
return JCALL(jvm, GetDoubleField, entity, ids->Entity_motionX);
|
||||
}
|
||||
|
||||
extern "C" void java_call_entity_living_base_jump(Jvm *jvm, void *entity_living_base) {
|
||||
JCALLN(jvm, CallVoidMethod, entity_living_base, ids->EntityLivingBase_jump);
|
||||
}
|
||||
|
||||
// ---- gap-scan functions: render-arm rotation of the local player, casts ----
|
||||
extern "C" Rot java_get_entity_player_sp_render_arm_pitch(Jvm *jvm, jobject entity_player_sp) { // renderArmYaw / renderArmPitch
|
||||
float y = JCALLN(jvm, GetFloatField, entity_player_sp, ids->EntityPlayerSP_renderArmYaw);
|
||||
float p = JCALLN(jvm, GetFloatField, entity_player_sp, ids->EntityPlayerSP_renderArmPitch);
|
||||
return Rot{y, p};
|
||||
}
|
||||
extern "C" Rot java_get_entity_player_sp_prev_render_arm_pitch(Jvm *jvm, jobject entity_player_sp) { // prevRenderArmYaw / prevRenderArmPitch
|
||||
float y = JCALLN(jvm, GetFloatField, entity_player_sp, ids->EntityPlayerSP_prevRenderArmYaw);
|
||||
float p = JCALLN(jvm, GetFloatField, entity_player_sp, ids->EntityPlayerSP_prevRenderArmPitch);
|
||||
return Rot{y, p};
|
||||
}
|
||||
extern "C" jobject java_jni_is_instance_of_entity_player_sp(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->EntityPlayerSP) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
// ---- functions the first gap scan missed (padding / tail-jump neighbours) ----
|
||||
extern "C" jdouble java_get_entity_motion_y(Jvm *jvm, void *entity) {
|
||||
return JCALL(jvm, GetDoubleField, entity, ids->Entity_motionY);
|
||||
}
|
||||
|
||||
extern "C" jdouble java_get_entity_motion_z(Jvm *jvm, void *entity) {
|
||||
return JCALL(jvm, GetDoubleField, entity, ids->Entity_motionZ);
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
// JNI: render-manager accessors (camera fields).
|
||||
// cflags: /Ob1 /clang:-fno-slp-vectorize
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jcall.h"
|
||||
struct Rot { float yaw, pitch; };
|
||||
struct Vec3 { double x, y, z; };
|
||||
extern "C" jobject java_new_c_init(Jvm *jvm, int a1) {
|
||||
return JCALLN(jvm, NewObject, ids->c, ids->c__init_, a1);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_c01_packet_chat_message(Jvm *jvm, void *c01_packet_chat_message) {
|
||||
return JCALL(jvm, GetObjectField, c01_packet_chat_message, ids->C01PacketChatMessage_Ljava_lang_String_);
|
||||
}
|
||||
|
||||
extern "C" void java_set_c01_packet_chat_message(Jvm *jvm, void *c01_packet_chat_message, void *value) {
|
||||
JCALL(jvm, SetObjectField, c01_packet_chat_message, ids->C01PacketChatMessage_Ljava_lang_String_, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_c14_packet_tab_complete(Jvm *jvm, void *c14_packet_tab_complete) {
|
||||
return JCALL(jvm, GetObjectField, c14_packet_tab_complete, ids->C14PacketTabComplete_Ljava_lang_String_);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_render_manager_instance(Jvm *jvm) {
|
||||
return JCALL(jvm, GetStaticObjectField, ids->RenderManager, ids->RenderManager_instance);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_render_manager_player_view_x(Jvm *jvm, void *render_manager) {
|
||||
return JCALL(jvm, GetFloatField, render_manager, ids->RenderManager_playerViewX);
|
||||
}
|
||||
|
||||
extern "C" void java_set_render_manager_player_view_x(Jvm *jvm, void *render_manager, float value) {
|
||||
JCALL(jvm, SetFloatField, render_manager, ids->RenderManager_playerViewX, value);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_render_manager_player_view_y(Jvm *jvm, void *render_manager) {
|
||||
return JCALL(jvm, GetFloatField, render_manager, ids->RenderManager_playerViewY);
|
||||
}
|
||||
|
||||
extern "C" void java_set_render_manager_player_view_y(Jvm *jvm, void *render_manager, float value) {
|
||||
JCALL(jvm, SetFloatField, render_manager, ids->RenderManager_playerViewY, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_render_manager_text_renderer(Jvm *jvm, void *render_manager) {
|
||||
return JCALL(jvm, GetObjectField, render_manager, ids->RenderManager_textRenderer);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_score_player_team(Jvm *jvm, void *score_player_team) {
|
||||
return JCALL(jvm, GetObjectField, score_player_team, ids->ScorePlayerTeam_Ljava_lang_String_);
|
||||
}
|
||||
|
||||
extern "C" void call_get_mouse_over(Jvm *jvm, void *entity_renderer, float value) {
|
||||
JCALL1(jvm, CallVoidMethod, entity_renderer, ids->EntityRenderer_getMouseOver, (double)value);
|
||||
}
|
||||
|
||||
extern "C" void call_orient_camera(Jvm *jvm, void *entity_renderer, float value) {
|
||||
JCALL1(jvm, CallVoidMethod, entity_renderer, ids->EntityRenderer_orientCamera, (double)value);
|
||||
}
|
||||
|
||||
extern "C" void set_render_manager_boolean(Jvm *jvm, void *render_global, unsigned char value) {
|
||||
JCALL(jvm, SetBooleanField, render_global, ids->RenderGlobal_displayListEntitiesDirty, value);
|
||||
}
|
||||
|
||||
extern "C" jboolean java_call_renderer_living_entity_can_render_name(Jvm *jvm, void *renderer_living_entity, void *value) {
|
||||
return JCALLN(jvm, CallBooleanMethod, renderer_living_entity, ids->RendererLivingEntity_canRenderName, value);
|
||||
}
|
||||
|
||||
extern "C" jint java_call_font_renderer_get_string_width(Jvm *jvm, void *font_renderer, void *value) {
|
||||
return JCALLN(jvm, CallIntMethod, font_renderer, ids->FontRenderer_getStringWidth, value);
|
||||
}
|
||||
|
||||
extern "C" void java_set_potion_effect_duration(Jvm *jvm, void *potion_effect, int value) {
|
||||
JCALL(jvm, SetIntField, potion_effect, ids->PotionEffect_duration, value);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_static_display_width(Jvm *jvm) {
|
||||
return JCALL(jvm, GetStaticIntField, ids->Display, ids->Display_width);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_static_display_height(Jvm *jvm) {
|
||||
return JCALL(jvm, GetStaticIntField, ids->Display, ids->Display_height);
|
||||
}
|
||||
|
||||
extern "C" jint display_width(Jvm *jvm) {
|
||||
return JCALL(jvm, GetStaticIntField, ids->Display, ids->Display_framebufferWidth);
|
||||
}
|
||||
|
||||
extern "C" jint display_height(Jvm *jvm) {
|
||||
return JCALL(jvm, GetStaticIntField, ids->Display, ids->Display_framebufferHeight);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_render_item_z_level(Jvm *jvm, void *render_item) {
|
||||
return JCALL(jvm, GetFloatField, render_item, ids->RenderItem_zLevel);
|
||||
}
|
||||
|
||||
extern "C" void java_set_render_item_z_level(Jvm *jvm, void *render_item, float value) {
|
||||
JCALL(jvm, SetFloatField, render_item, ids->RenderItem_zLevel, value);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_player_controller_mp_cur_block_damage_mp(Jvm *jvm, void *player_controller_mp) {
|
||||
return JCALL(jvm, GetFloatField, player_controller_mp, ids->PlayerControllerMP_curBlockDamageMP);
|
||||
}
|
||||
|
||||
extern "C" void java_set_player_controller_mp_cur_block_damage_mp(Jvm *jvm, void *player_controller_mp, float value) {
|
||||
JCALL(jvm, SetFloatField, player_controller_mp, ids->PlayerControllerMP_curBlockDamageMP, value);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_player_controller_mp_block_hit_delay(Jvm *jvm, void *player_controller_mp) {
|
||||
return JCALL(jvm, GetIntField, player_controller_mp, ids->PlayerControllerMP_blockHitDelay);
|
||||
}
|
||||
|
||||
extern "C" void java_set_player_controller_mp_block_hit_delay(Jvm *jvm, void *player_controller_mp, int value) {
|
||||
JCALL(jvm, SetIntField, player_controller_mp, ids->PlayerControllerMP_blockHitDelay, value);
|
||||
}
|
||||
|
||||
extern "C" jboolean java_get_player_controller_mp_is_hitting_block(Jvm *jvm, void *player_controller_mp) {
|
||||
return JCALL(jvm, GetBooleanField, player_controller_mp, ids->PlayerControllerMP_isHittingBlock);
|
||||
}
|
||||
|
||||
extern "C" void java_set_player_controller_mp_is_hitting_block(Jvm *jvm, void *player_controller_mp, unsigned char value) {
|
||||
JCALL(jvm, SetBooleanField, player_controller_mp, ids->PlayerControllerMP_isHittingBlock, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_nbttag_list_get_compound_tag_at(Jvm *jvm, void *nbt_tag_list, int value) {
|
||||
return JCALLN(jvm, CallObjectMethod, nbt_tag_list, ids->NBTTagList_getCompoundTagAt, value);
|
||||
}
|
||||
|
||||
extern "C" jshort java_jni_call_short_method_nbttag_compound_get_short(Jvm *jvm, void *nbt_tag_compound, void *value) {
|
||||
return JCALLN(jvm, CallShortMethod, nbt_tag_compound, ids->NBTTagCompound_getShort, value);
|
||||
}
|
||||
|
||||
extern "C" jboolean java_get_entity_arrow_in_ground(Jvm *jvm, void *entity_arrow) {
|
||||
return JCALL(jvm, GetBooleanField, entity_arrow, ids->EntityArrow_inGround);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_static_stat_collector_translate_to_local(Jvm *jvm, void *stat_collector) {
|
||||
return JCALLN(jvm, CallStaticObjectMethod, ids->StatCollector, ids->StatCollector_translateToLocal, stat_collector);
|
||||
}
|
||||
|
||||
extern "C" jint java_call_enum_ordinal(Jvm *jvm, void *enum_object) {
|
||||
return JCALLN(jvm, CallIntMethod, enum_object, ids->Enum_ordinal);
|
||||
}
|
||||
|
||||
extern "C" void gl_begin_2d(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_enableCull);
|
||||
}
|
||||
|
||||
extern "C" void gl_disable_lighting(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_disableLighting);
|
||||
}
|
||||
|
||||
extern "C" void gl_enable_depth(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_enableDepth);
|
||||
}
|
||||
|
||||
extern "C" void gl_push_state(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_disableDepth);
|
||||
}
|
||||
|
||||
extern "C" void gl_pop_state(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_enableBlend);
|
||||
}
|
||||
|
||||
extern "C" void gl_bind_texture(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_enableTexture2D);
|
||||
}
|
||||
|
||||
extern "C" void gl_clear_color(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_disableTexture2D);
|
||||
}
|
||||
|
||||
extern "C" void gl_depth_mask(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_enableAlpha);
|
||||
}
|
||||
|
||||
extern "C" void java_call_static_gl_state_manager_disable_alpha(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_disableAlpha);
|
||||
}
|
||||
|
||||
extern "C" void tick_netty(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->Display, ids->Display_update);
|
||||
}
|
||||
|
||||
extern "C" bool world_is_loaded(Jvm *jvm) {
|
||||
return JCALLN(jvm, CallStaticBooleanMethod, ids->Display, ids->Display_isActive) != 0;
|
||||
}
|
||||
|
||||
extern "C" jobject java_new_render_item(Jvm *jvm) {
|
||||
return JCALLN(jvm, NewObject, ids->RenderItem, ids->RenderItem_init);
|
||||
}
|
||||
|
||||
extern "C" void java_call_static_render_helper_enable_standard_item_lighting(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->RenderHelper, ids->RenderHelper_enableStandardItemLighting);
|
||||
}
|
||||
|
||||
extern "C" void java_call_static_render_helper_disable_standard_item_lighting(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->RenderHelper, ids->RenderHelper_disableStandardItemLighting);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_entity_item_get_entity_item(Jvm *jvm, void *entity_item) {
|
||||
return JCALLN(jvm, CallObjectMethod, entity_item, ids->EntityItem_getEntityItem);
|
||||
}
|
||||
|
||||
extern "C" jlong java_jni_call_long_method_uuid_get_least_significant_bits(Jvm *jvm, void *uuid) {
|
||||
return JCALLN(jvm, CallLongMethod, uuid, ids->UUID_getLeastSignificantBits);
|
||||
}
|
||||
|
||||
extern "C" jlong java_jni_call_long_method_uuid_get_most_significant_bits(Jvm *jvm, void *uuid) {
|
||||
return JCALLN(jvm, CallLongMethod, uuid, ids->UUID_getMostSignificantBits);
|
||||
}
|
||||
|
||||
extern "C" void gui_key_passthrough(Jvm *jvm, void *player_controller_mp) {
|
||||
JCALLN(jvm, CallVoidMethod, player_controller_mp, ids->PlayerControllerMP_updateController);
|
||||
}
|
||||
|
||||
extern "C" jint java_call_nbttag_list_tag_count(Jvm *jvm, void *nbt_tag_list) {
|
||||
return JCALLN(jvm, CallIntMethod, nbt_tag_list, ids->NBTTagList_tagCount);
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_c01_packet_chat_message(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->C01PacketChatMessage) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_c14_packet_tab_complete(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->C14PacketTabComplete) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_s2_apacket_particles(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->S2APacketParticles) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_score_player_team(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->ScorePlayerTeam) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_entity_item(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->EntityItem) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
// ---- hand-written
|
||||
FN(float, libc_roundf, (float v));
|
||||
|
||||
extern "C" float java_call_entity_renderer_get_fovmodifier(Jvm *jvm, jobject entity_renderer, float partial) {
|
||||
return JCALL1(jvm, CallFloatMethod, entity_renderer, (jmethodID)ids->EntityRenderer_getFOVModifier, (double)partial, 1);
|
||||
}
|
||||
extern "C" void set_active_render_info(Jvm *jvm, void *active_render_info, bool b) {
|
||||
JCALL2(jvm, CallStaticVoidMethod, (jclass)ids->ActiveRenderInfo, (jmethodID)ids->ActiveRenderInfo_updateRenderInfo, active_render_info, (int)b);
|
||||
}
|
||||
// MS x64 passes a {double,double} struct by reference, so declaring this as one D2 while every caller
|
||||
// (chams_render_living_entity, hook_render_player) passes three separate doubles made the callee read the
|
||||
// stale R9 as a pointer (fault at 0). Keep the three doubles in the signature: they are the method's
|
||||
// three D arguments and go to the variadic JNI call as such.
|
||||
extern "C" void java_jni_call_nonvirtual_renderer_living_entity_do_render(Jvm *jvm, jobject object, void *other_object, double x, double d0, double d1, float f0, float f1) {
|
||||
JCALL2(jvm, CallNonvirtualVoidMethod, object, (jclass)ids->RendererLivingEntity, (jmethodID)ids->RendererLivingEntity___2, other_object, x, d0, d1,
|
||||
(double)f0, (double)f1);
|
||||
}
|
||||
// FontRenderer.drawString: float coordinates in the newer mappings, rounded ints in the old ones
|
||||
extern "C" jint java_font_draw_string(Jvm *jvm, jobject font, jobject str, float x, float y, unsigned color, unsigned char shadow) {
|
||||
if (jvm->ids->variant != 0)
|
||||
return JNI(jvm)->CallIntMethod(jvm->env, font, (jmethodID)jvm->ids->FontRenderer_drawString, str, (double)x, (double)y, color, (int)shadow);
|
||||
int ix = (int)libc_roundf(x);
|
||||
int iy = (int)libc_roundf(y);
|
||||
return JNI(jvm)->CallIntMethod(jvm->env, font, (jmethodID)jvm->ids->FontRenderer_drawString, str, ix, iy, color, (int)shadow);
|
||||
}
|
||||
extern "C" void gl_blend_mode(Jvm *jvm, int a, int b) {
|
||||
JCALL2(jvm, CallStaticVoidMethod, (jclass)ids->GlStateManager, (jmethodID)ids->GlStateManager_blendFunc, a, b);
|
||||
}
|
||||
extern "C" void gl_set_color(Jvm *jvm, float a, float b, float c, float d) {
|
||||
JCALL2(jvm, CallStaticVoidMethod, (jclass)ids->GlStateManager, (jmethodID)ids->GlStateManager_color, (double)a, (double)b, (double)c, (double)d);
|
||||
}
|
||||
// element k (< 256) of a static object array
|
||||
extern "C" jobject java_potion_potion_types(Jvm *jvm, unsigned k) {
|
||||
if (k > 0xff) return nullptr;
|
||||
JIds *ids = jvm->ids;
|
||||
JNIEnv *env = jvm->env;
|
||||
jobject object = (*env)->GetStaticObjectField(env, (jclass)ids->Potion, (jfieldID)ids->Potion_potionTypes);
|
||||
if (!object) return nullptr;
|
||||
jobject value = (*env)->GetObjectArrayElement(env, (jobjectArray)object, k);
|
||||
(*env)->DeleteLocalRef(env, object);
|
||||
return value;
|
||||
}
|
||||
extern "C" void java_render_item_init(Jvm *jvm, jobject object, void *other_object, void *b, void *c, int d, int e) {
|
||||
if (jvm->ids->variant != 0)
|
||||
JNI(jvm)->CallVoidMethod(jvm->env, object, (jmethodID)jvm->ids->RenderItem__init_, c, d, e);
|
||||
else
|
||||
JNI(jvm)->CallVoidMethod(jvm->env, object, (jmethodID)jvm->ids->RenderItem__init_, other_object, b, c, d, e);
|
||||
}
|
||||
extern "C" void java_render_item_render_item_overlay_into_gui(Jvm *jvm, jobject object, void *other_object, void *b, void *c, int d, int e, void *f) {
|
||||
if (jvm->ids->variant != 0)
|
||||
JNI(jvm)->CallVoidMethod(jvm->env, object, (jmethodID)jvm->ids->RenderItem_renderItemOverlayIntoGUI, other_object, c, d, e, f);
|
||||
else
|
||||
JNI(jvm)->CallVoidMethod(jvm->env, object, (jmethodID)jvm->ids->RenderItem_renderItemOverlayIntoGUI, other_object, b, c, d, e, f);
|
||||
}
|
||||
|
||||
// ---- JNI bridge functions found by the gap scan (no caller outside the virtualised init) ----
|
||||
extern "C" void java_call_framebuffer_bind_framebuffer(Jvm *jvm, void *framebuffer, unsigned char value) {
|
||||
JCALL1(jvm, CallVoidMethod, framebuffer, ids->Framebuffer_bindFramebuffer, value);
|
||||
}
|
||||
|
||||
extern "C" void java_call_static_gl_state_manager_disable_cull(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_disableCull);
|
||||
}
|
||||
|
||||
extern "C" void java_call_static_gl_state_manager_enable_lighting(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_enableLighting);
|
||||
}
|
||||
|
||||
extern "C" void java_call_static_gl_state_manager_disable_blend(Jvm *jvm) {
|
||||
JCALLN(jvm, CallStaticVoidMethod, ids->GlStateManager, ids->GlStateManager_disableBlend);
|
||||
}
|
||||
|
||||
extern "C" void java_call_entity_item_search_for_other_items_nearby(Jvm *jvm, void *entity_item) {
|
||||
JCALLN(jvm, CallVoidMethod, entity_item, ids->EntityItem_searchForOtherItemsNearby);
|
||||
}
|
||||
|
||||
// EntityRenderer.renderHand(partialTicks, pass)
|
||||
extern "C" void java_call_entity_renderer_render_hand(Jvm *jvm, jobject renderer, float partial_ticks, int pass) {
|
||||
JCALL1(jvm, CallVoidMethod, renderer, ids->EntityRenderer_renderHand, (double)partial_ticks, pass);
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
// JNI: local-player accessors.
|
||||
// cflags: /Ob1 /clang:-fno-slp-vectorize
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jcall.h"
|
||||
struct Rot { float yaw, pitch; };
|
||||
struct Vec3 { double x, y, z; };
|
||||
FN(Vec3, vec3_make, (double x, double y, double z));
|
||||
|
||||
extern "C" jint java_get_game_settings_third_person_view(Jvm *jvm, void *game_settings) {
|
||||
return JCALL(jvm, GetIntField, game_settings, ids->GameSettings_thirdPersonView);
|
||||
}
|
||||
|
||||
extern "C" void java_set_game_settings_third_person_view(Jvm *jvm, void *game_settings, int value) {
|
||||
JCALL(jvm, SetIntField, game_settings, ids->GameSettings_thirdPersonView, value);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_key_binding_press_time(Jvm *jvm, void *key_binding) {
|
||||
return JCALL(jvm, GetIntField, key_binding, ids->KeyBinding_pressTime);
|
||||
}
|
||||
|
||||
extern "C" void java_set_key_binding_press_time(Jvm *jvm, void *key_binding, int value) {
|
||||
JCALL(jvm, SetIntField, key_binding, ids->KeyBinding_pressTime, value);
|
||||
}
|
||||
|
||||
extern "C" jint get_event_key(Jvm *jvm, void *key_binding) {
|
||||
return JCALL(jvm, GetIntField, key_binding, ids->KeyBinding_keyCode);
|
||||
}
|
||||
|
||||
extern "C" bool is_key_down(Jvm *jvm, void *key_binding) {
|
||||
return JCALLN(jvm, GetBooleanField, key_binding, ids->KeyBinding_pressed) != 0;
|
||||
}
|
||||
|
||||
extern "C" void java_set_key_binding_pressed(Jvm *jvm, void *key_binding, unsigned char value) {
|
||||
JCALL(jvm, SetBooleanField, key_binding, ids->KeyBinding_pressed, value);
|
||||
}
|
||||
|
||||
extern "C" bool java_call_static_keyboard_is_key_down(Jvm *jvm, int keyboard) {
|
||||
return JCALLN(jvm, CallStaticBooleanMethod, ids->Keyboard, ids->Keyboard_isKeyDown, keyboard) != 0;
|
||||
}
|
||||
|
||||
extern "C" bool java_call_static_mouse_is_button_down(Jvm *jvm, int mouse) {
|
||||
return JCALLN(jvm, CallStaticBooleanMethod, ids->Mouse, ids->Mouse_isButtonDown, mouse) != 0;
|
||||
}
|
||||
|
||||
extern "C" jfloat get_partial_ticks(Jvm *jvm, void *timer) {
|
||||
return JCALL(jvm, GetFloatField, timer, ids->Timer_renderPartialTicks);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_timer_timer_speed(Jvm *jvm, void *timer) {
|
||||
return JCALL(jvm, GetFloatField, timer, ids->Timer_timerSpeed);
|
||||
}
|
||||
|
||||
extern "C" void java_set_timer_timer_speed(Jvm *jvm, void *timer, float value) {
|
||||
JCALL(jvm, SetFloatField, timer, ids->Timer_timerSpeed, value);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_timer_elapsed_partial_ticks(Jvm *jvm, void *timer) {
|
||||
return JCALL(jvm, GetFloatField, timer, ids->Timer_elapsedPartialTicks);
|
||||
}
|
||||
|
||||
extern "C" void java_set_timer_elapsed_partial_ticks(Jvm *jvm, void *timer, float value) {
|
||||
JCALL(jvm, SetFloatField, timer, ids->Timer_elapsedPartialTicks, value);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_inventory_player_current_item(Jvm *jvm, void *inventory_player) {
|
||||
return JCALL(jvm, GetIntField, inventory_player, ids->InventoryPlayer_currentItem);
|
||||
}
|
||||
|
||||
extern "C" void java_set_inventory_player_current_item(Jvm *jvm, void *inventory_player, int value) {
|
||||
JCALL(jvm, SetIntField, inventory_player, ids->InventoryPlayer_currentItem, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_inventory_player_main_inventory(Jvm *jvm, void *inventory_player) {
|
||||
return JCALL(jvm, GetObjectField, inventory_player, ids->InventoryPlayer_mainInventory);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_inventory_player_item_stack(Jvm *jvm, void *inventory_player) {
|
||||
return JCALL(jvm, GetObjectField, inventory_player, ids->InventoryPlayer_itemStack);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_gui_container_the_slot(Jvm *jvm, void *gui_container) {
|
||||
return JCALL(jvm, GetObjectField, gui_container, ids->GuiContainer_theSlot);
|
||||
}
|
||||
|
||||
extern "C" bool java_jni_is_instance_of_gui_inventory(Jvm *jvm, void *candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, ids->GuiInventory) != 0;
|
||||
}
|
||||
|
||||
extern "C" bool java_jni_is_instance_of_gui_chest(Jvm *jvm, void *candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, ids->GuiChest) != 0;
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_gui_chest_lower_chest_inventory(Jvm *jvm, void *gui_chest) {
|
||||
return JCALL(jvm, GetObjectField, gui_chest, ids->GuiChest_lowerChestInventory);
|
||||
}
|
||||
|
||||
extern "C" jint java_call_static_item_get_id_from_item(Jvm *jvm, void *item) {
|
||||
return JCALLN(jvm, CallStaticIntMethod, ids->Item, ids->Item_getIdFromItem, item);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_call_item_stack_get_str_vs_block(Jvm *jvm, void *item_stack, void *value) {
|
||||
return JCALLN(jvm, CallFloatMethod, item_stack, ids->ItemStack_getStrVsBlock, value);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_item_stack_stack_size(Jvm *jvm, void *item_stack) {
|
||||
return JCALL(jvm, GetIntField, item_stack, ids->ItemStack_stackSize);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_item_stack_metadata(Jvm *jvm, void *item_stack) {
|
||||
return JCALL(jvm, GetIntField, item_stack, ids->ItemStack_metadata);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_net_handler_play_client(Jvm *jvm, void *net_handler_play_client) {
|
||||
return JCALL(jvm, GetObjectField, net_handler_play_client, ids->NetHandlerPlayClient_playerInfoMap);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_net_handler_play_client_net_manager(Jvm *jvm, void *net_handler_play_client) {
|
||||
return JCALL(jvm, GetObjectField, net_handler_play_client, ids->NetHandlerPlayClient_netManager);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_network_manager(Jvm *jvm, void *network_manager) {
|
||||
return JCALL(jvm, GetObjectField, network_manager, ids->NetworkManager_Lio_netty_channel_Channel_);
|
||||
}
|
||||
|
||||
extern "C" void send_packet(Jvm *jvm, void *a1, void *value) {
|
||||
JCALLN(jvm, CallVoidMethod, a1, ids->NetworkManager_sendPacket, value);
|
||||
}
|
||||
|
||||
extern "C" bool keyboard_has_next(Jvm *jvm) {
|
||||
return JCALLN(jvm, CallStaticBooleanMethod, ids->Keyboard, ids->Keyboard_next) != 0;
|
||||
}
|
||||
|
||||
extern "C" bool keyboard_can_repeat(Jvm *jvm) {
|
||||
return JCALLN(jvm, CallStaticBooleanMethod, ids->Keyboard, ids->Keyboard_getEventKeyState) != 0;
|
||||
}
|
||||
|
||||
extern "C" jint keyboard_next_key(Jvm *jvm) {
|
||||
return JCALLN(jvm, CallStaticIntMethod, ids->Keyboard, ids->Keyboard_getEventKey);
|
||||
}
|
||||
|
||||
extern "C" bool java_call_static_mouse_is_grabbed(Jvm *jvm) {
|
||||
return JCALLN(jvm, CallStaticBooleanMethod, ids->Mouse, ids->Mouse_isGrabbed) != 0;
|
||||
}
|
||||
|
||||
extern "C" bool keyboard_has_events(Jvm *jvm) {
|
||||
return JCALLN(jvm, CallStaticBooleanMethod, ids->Mouse, ids->Mouse_next) != 0;
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_item_stack_get_item(Jvm *jvm, void *item_stack) {
|
||||
return JCALLN(jvm, CallObjectMethod, item_stack, ids->ItemStack_getItem);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_item_stack_get_enchantment_tag_list(Jvm *jvm, void *item_stack) {
|
||||
return JCALLN(jvm, CallObjectMethod, item_stack, ids->ItemStack_getEnchantmentTagList);
|
||||
}
|
||||
|
||||
extern "C" jint java_call_item_stack_get_max_stack_size(Jvm *jvm, void *item_stack) {
|
||||
return JCALLN(jvm, CallIntMethod, item_stack, ids->ItemStack_getMaxStackSize);
|
||||
}
|
||||
|
||||
extern "C" bool java_call_item_stack_has_display_name(Jvm *jvm, void *item_stack) {
|
||||
return JCALLN(jvm, CallBooleanMethod, item_stack, ids->ItemStack_hasDisplayName) != 0;
|
||||
}
|
||||
|
||||
extern "C" Vec3 java_get_vec3_x_coord(Jvm *jvm, jobject vec3) {
|
||||
double x = JCALLN(jvm, GetDoubleField, vec3, ids->Vec3_xCoord);
|
||||
double y = JCALLN(jvm, GetDoubleField, vec3, ids->Vec3_yCoord);
|
||||
double z = JCALLN(jvm, GetDoubleField, vec3, ids->Vec3_zCoord);
|
||||
Vec3 r = vec3_make(x, y, z);
|
||||
asm volatile("");
|
||||
return r;
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_game_settings_sneak_key(Jvm *jvm, jobject game_settings) {
|
||||
jobject object = JCALLN(jvm, GetObjectField, game_settings, ids->GameSettings_keyBindings);
|
||||
return JCALL(jvm, GetObjectArrayElement, (jobjectArray)object, 7);
|
||||
}
|
||||
|
||||
extern "C" jobject get_held_item(Jvm *jvm, jobject object) {
|
||||
jobject a = JCALLN(jvm, GetObjectField, object, ids->GameSettings_keyBindings);
|
||||
return JCALL(jvm, GetObjectArrayElement, (jobjectArray)a, 6);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_game_settings_forward_key(Jvm *jvm, jobject game_settings) {
|
||||
jobject object = JCALLN(jvm, GetObjectField, game_settings, ids->GameSettings_keyBindings);
|
||||
return JCALL(jvm, GetObjectArrayElement, (jobjectArray)object, 2);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_game_settings_back_key(Jvm *jvm, jobject game_settings) {
|
||||
jobject object = JCALLN(jvm, GetObjectField, game_settings, ids->GameSettings_keyBindings);
|
||||
return JCALL(jvm, GetObjectArrayElement, (jobjectArray)object, 4);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_game_settings_left_key(Jvm *jvm, jobject game_settings) {
|
||||
jobject object = JCALLN(jvm, GetObjectField, game_settings, ids->GameSettings_keyBindings);
|
||||
return JCALL(jvm, GetObjectArrayElement, (jobjectArray)object, 3);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_game_settings_right_key(Jvm *jvm, jobject game_settings) {
|
||||
jobject object = JCALLN(jvm, GetObjectField, game_settings, ids->GameSettings_keyBindings);
|
||||
return JCALL(jvm, GetObjectArrayElement, (jobjectArray)object, 5);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_game_settings_attack_key(Jvm *jvm, jobject game_settings) {
|
||||
jobject object = JCALLN(jvm, GetObjectField, game_settings, ids->GameSettings_keyBindings);
|
||||
return JCALL(jvm, GetObjectArrayElement, (jobjectArray)object, 0);
|
||||
}
|
||||
|
||||
// ---- hand-written
|
||||
struct Box { Vec3 lo, hi; };
|
||||
FN(Box, aabb_make, (const Vec3 *lo, const Vec3 *hi));
|
||||
FN(bool, java_call_static_keyboard_is_key_down, (Jvm *j, int key)); // Keyboard.isKeyDown
|
||||
FN(bool, java_call_static_mouse_is_button_down, (Jvm *j, int button)); // Mouse.isButtonDown
|
||||
|
||||
extern "C" jobject java_get_game_settings_key_17_or_8(Jvm *jvm, jobject game_settings) {
|
||||
jobject object = JCALLN(jvm, GetObjectField, game_settings, (jfieldID)ids->GameSettings_keyBindings);
|
||||
return JCALL(jvm, GetObjectArrayElement, (jobjectArray)object, (ids->variant == 0) * 9 + 8);
|
||||
}
|
||||
extern "C" jobject get_game_screen(Jvm *jvm, jobject object) {
|
||||
jobject a = JCALLN(jvm, GetObjectField, object, (jfieldID)ids->GameSettings_keyBindings);
|
||||
return JCALL(jvm, GetObjectArrayElement, (jobjectArray)a, ids->variant == 0 ? 9 : 10);
|
||||
}
|
||||
extern "C" void java_increment_key_binding_press_time(Jvm *jvm, jobject key_binding) {
|
||||
jint v = JCALLN(jvm, GetIntField, key_binding, (jfieldID)ids->KeyBinding_pressTime);
|
||||
JCALL(jvm, SetIntField, key_binding, (jfieldID)ids->KeyBinding_pressTime, v + 1);
|
||||
}
|
||||
// key binding pressed: negative codes are mouse buttons (code + 100)
|
||||
extern "C" bool java_get_key_binding_key_code(Jvm *jvm, jobject key_binding) {
|
||||
jint keyboard = JCALLN(jvm, GetIntField, key_binding, (jfieldID)ids->KeyBinding_keyCode);
|
||||
if (keyboard < 0) return java_call_static_mouse_is_button_down(jvm, keyboard + 100);
|
||||
if ((unsigned)keyboard <= 0x100) return java_call_static_keyboard_is_key_down(jvm, keyboard);
|
||||
return false;
|
||||
}
|
||||
extern "C" jobject java_new_vec3_init(Jvm *jvm, Vec3 v) {
|
||||
return JCALL2(jvm, NewObject, (jclass)ids->Vec3, (jmethodID)ids->Vec3__init_, v.x, v.y, v.z);
|
||||
}
|
||||
extern "C" jobject java_new_axis_aligned_bb_init(Jvm *jvm, Box b) {
|
||||
return JCALL2(jvm, NewObject, (jclass)ids->AxisAlignedBB, (jmethodID)ids->AxisAlignedBB__init_,
|
||||
b.lo.x, b.lo.y, b.lo.z, b.hi.x, b.hi.y, b.hi.z);
|
||||
}
|
||||
extern "C" Box java_get_axis_aligned_bb_min_x(Jvm *jvm, jobject axis_aligned_bb) {
|
||||
double x = JCALLN(jvm, GetDoubleField, axis_aligned_bb, (jfieldID)ids->AxisAlignedBB_minX);
|
||||
double y = JCALLN(jvm, GetDoubleField, axis_aligned_bb, (jfieldID)ids->AxisAlignedBB_minY);
|
||||
double z = JCALLN(jvm, GetDoubleField, axis_aligned_bb, (jfieldID)ids->AxisAlignedBB_minZ);
|
||||
Vec3 lo = vec3_make(x, y, z);
|
||||
x = JCALLN(jvm, GetDoubleField, axis_aligned_bb, (jfieldID)ids->AxisAlignedBB_maxX);
|
||||
y = JCALLN(jvm, GetDoubleField, axis_aligned_bb, (jfieldID)ids->AxisAlignedBB_maxY);
|
||||
z = JCALLN(jvm, GetDoubleField, axis_aligned_bb, (jfieldID)ids->AxisAlignedBB_maxZ);
|
||||
Vec3 hi = vec3_make(x, y, z);
|
||||
Box r = aabb_make(&lo, &hi);
|
||||
|
||||
return r;
|
||||
}
|
||||
extern "C" void java_call_static_mouse_set_grabbed(Jvm *jvm, bool grab) {
|
||||
JCALL2(jvm, CallStaticVoidMethod, (jclass)ids->Mouse, (jmethodID)ids->Mouse_setGrabbed, grab);
|
||||
}
|
||||
extern "C" void java_call_static_mouse_set_cursor_position(Jvm *jvm, int x, int y) {
|
||||
JCALL2(jvm, CallStaticVoidMethod, (jclass)ids->Mouse, (jmethodID)ids->Mouse_setCursorPosition, x, y);
|
||||
}
|
||||
extern "C" void keyboard_send_key(Jvm *jvm, jobject object, unsigned short c, int k) {
|
||||
JCALL1(jvm, CallVoidMethod, object, (jmethodID)ids->GuiScreen_keyTyped, (int)c, k);
|
||||
}
|
||||
extern "C" void java_gui_container_handle_mouse_click(Jvm *jvm, jobject object, int a, int b, int c) {
|
||||
JNI(jvm)->CallVoidMethod(jvm->env, object, (jmethodID)jvm->ids->GuiContainer_handleMouseClick, 0, a, b, c);
|
||||
}
|
||||
extern "C" bool java_call_static_item_stack_are_item_stack_tags_equal(Jvm *jvm, void *item_stack, void *other_object) {
|
||||
return JCALL2(jvm, CallStaticBooleanMethod, (jclass)ids->ItemStack, (jmethodID)ids->ItemStack_areItemStackTagsEqual, item_stack, other_object) != 0;
|
||||
}
|
||||
extern "C" void send_packet_with_id(Jvm *jvm, jobject object, void *other_object, void *b) {
|
||||
JNI(jvm)->CallVoidMethod(jvm->env, object, (jmethodID)jvm->ids->NetworkManager_scheduleOutboundPacket, other_object, b);
|
||||
}
|
||||
// newer mappings take an extra (empty) array argument
|
||||
extern "C" void java_call_generic_future_listener(Jvm *jvm, jobject generic_future_listener, void *other_object) {
|
||||
if (jvm->ids->NetworkManager_scheduleOutboundPacket) {
|
||||
jobjectArray arr = JNI(jvm)->NewObjectArray(jvm->env, 0, (jclass)jvm->ids->GenericFutureListener, nullptr);
|
||||
JCALL1(jvm, CallVoidMethod, generic_future_listener, (jmethodID)ids->NetworkManager_scheduleOutboundPacket, other_object, arr);
|
||||
} else {
|
||||
JCALL1(jvm, CallVoidMethod, generic_future_listener, (jmethodID)ids->NetworkManager_sendPacket, other_object);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- JNI bridge functions found by the gap scan (no caller outside the virtualised init) ----
|
||||
extern "C" jint java_get_timer_elapsed_ticks(Jvm *jvm, void *timer) {
|
||||
return JCALL(jvm, GetIntField, timer, ids->Timer_elapsedTicks);
|
||||
}
|
||||
|
||||
extern "C" void java_jni_set_long_field_gui_container_last_click_time(Jvm *jvm, void *gui_container, long long value) {
|
||||
JCALL(jvm, SetLongField, gui_container, ids->GuiContainer_lastClickTime, (jlong)value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_static_item_get_item_by_id(Jvm *jvm, int item) {
|
||||
return JCALLN(jvm, CallStaticObjectMethod, ids->Item, ids->Item_getItemById, item);
|
||||
}
|
||||
|
||||
extern "C" void java_set_item_stack_stack_size(Jvm *jvm, void *item_stack, int value) {
|
||||
JCALL(jvm, SetIntField, item_stack, ids->ItemStack_stackSize, value);
|
||||
}
|
||||
|
||||
// ---- functions the first gap scan missed (padding / tail-jump neighbours) ----
|
||||
extern "C" jobject java_new_item_stack_init(Jvm *jvm, void *item_stack) {
|
||||
return JCALLN(jvm, NewObject, ids->ItemStack, ids->ItemStack__init_, item_stack);
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
// JNI: world and chunk accessors.
|
||||
// cflags: /Ob1 /clang:-fno-slp-vectorize
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jcall.h"
|
||||
struct Rot { float yaw, pitch; };
|
||||
struct Vec3 { double x, y, z; };
|
||||
FN(Vec3, vec3_make, (double x, double y, double z));
|
||||
extern "C" jobject java_get_network_player_info_display_name(Jvm *jvm, void *network_player_info) {
|
||||
return JCALL(jvm, GetObjectField, network_player_info, ids->NetworkPlayerInfo_displayName);
|
||||
}
|
||||
|
||||
extern "C" jobject java_new_chat_component_text_init(Jvm *jvm, void *chat_component_text) {
|
||||
return JCALLN(jvm, NewObject, ids->ChatComponentText, ids->ChatComponentText__init_, chat_component_text);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_iinventory_get_stack_in_slot(Jvm *jvm, void *i_inventory, int value) {
|
||||
return JCALLN(jvm, CallObjectMethod, i_inventory, ids->IInventory_getStackInSlot, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_container_player_craft_matrix(Jvm *jvm, void *container_player) {
|
||||
return JCALL(jvm, GetObjectField, container_player, ids->ContainerPlayer_craftMatrix);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_slot_slot_number(Jvm *jvm, void *slot) {
|
||||
return JCALL(jvm, GetIntField, slot, ids->Slot_slotNumber);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_inventory_basic(Jvm *jvm, void *inventory_basic) {
|
||||
return JCALL(jvm, GetObjectField, inventory_basic, ids->InventoryBasic_Ljava_lang_String_);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_channel_pipeline_get(Jvm *jvm, void *channel_pipeline, void *value) {
|
||||
return JCALLN(jvm, CallObjectMethod, channel_pipeline, ids->ChannelPipeline_get, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_channel_pipeline_remove(Jvm *jvm, void *channel_pipeline, void *value) {
|
||||
return JCALLN(jvm, CallObjectMethod, channel_pipeline, ids->ChannelPipeline_remove, value);
|
||||
}
|
||||
|
||||
extern "C" bool java_get_c03_packet_player_moving(Jvm *jvm, void *c03_packet_player) {
|
||||
return JCALLN(jvm, GetBooleanField, c03_packet_player, ids->C03PacketPlayer_moving) != 0;
|
||||
}
|
||||
|
||||
extern "C" bool java_get_c03_packet_player_on_ground(Jvm *jvm, void *c03_packet_player) {
|
||||
return JCALLN(jvm, GetBooleanField, c03_packet_player, ids->C03PacketPlayer_onGround) != 0;
|
||||
}
|
||||
|
||||
extern "C" jdouble java_get_c03_packet_player_b(Jvm *jvm, void *c03_packet_player) {
|
||||
return JCALL(jvm, GetDoubleField, c03_packet_player, ids->C03PacketPlayer_b);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_c08_packet_player_block_placement_stack(Jvm *jvm, void *c08_packet_player_block_placement) {
|
||||
return JCALL(jvm, GetObjectField, c08_packet_player_block_placement, ids->C08PacketPlayerBlockPlacement_stack);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_s12_packet_entity_velocity_entity_id(Jvm *jvm, void *s12_packet_entity_velocity) {
|
||||
return JCALL(jvm, GetIntField, s12_packet_entity_velocity, ids->S12PacketEntityVelocity_entityID);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_s14_packet_entity_entity_id(Jvm *jvm, void *s14_packet_entity) {
|
||||
return JCALL(jvm, GetIntField, s14_packet_entity, ids->S14PacketEntity_entityId);
|
||||
}
|
||||
|
||||
extern "C" jbyte java_jni_get_byte_field_s14_packet_entity_pos_x(Jvm *jvm, void *s14_packet_entity) {
|
||||
return JCALL(jvm, GetByteField, s14_packet_entity, ids->S14PacketEntity_posX);
|
||||
}
|
||||
|
||||
extern "C" jbyte java_jni_get_byte_field_s14_packet_entity_pos_y(Jvm *jvm, void *s14_packet_entity) {
|
||||
return JCALL(jvm, GetByteField, s14_packet_entity, ids->S14PacketEntity_posY);
|
||||
}
|
||||
|
||||
extern "C" jbyte java_jni_get_byte_field_s14_packet_entity_pos_z(Jvm *jvm, void *s14_packet_entity) {
|
||||
return JCALL(jvm, GetByteField, s14_packet_entity, ids->S14PacketEntity_posZ);
|
||||
}
|
||||
|
||||
extern "C" jbyte java_jni_get_byte_field_s14_packet_entity_yaw(Jvm *jvm, void *s14_packet_entity) {
|
||||
return JCALL(jvm, GetByteField, s14_packet_entity, ids->S14PacketEntity_yaw);
|
||||
}
|
||||
|
||||
extern "C" bool java_get_s14_packet_entity_g(Jvm *jvm, void *s14_packet_entity) {
|
||||
return JCALLN(jvm, GetBooleanField, s14_packet_entity, ids->S14PacketEntity_g) != 0;
|
||||
}
|
||||
|
||||
extern "C" jint java_get_s18_packet_entity_teleport_entity_id(Jvm *jvm, void *s18_packet_entity_teleport) {
|
||||
return JCALL(jvm, GetIntField, s18_packet_entity_teleport, ids->S18PacketEntityTeleport_entityId);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_s18_packet_entity_teleport_pos_x(Jvm *jvm, void *s18_packet_entity_teleport) {
|
||||
return JCALL(jvm, GetIntField, s18_packet_entity_teleport, ids->S18PacketEntityTeleport_posX);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_s18_packet_entity_teleport_pos_y(Jvm *jvm, void *s18_packet_entity_teleport) {
|
||||
return JCALL(jvm, GetIntField, s18_packet_entity_teleport, ids->S18PacketEntityTeleport_posY);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_s18_packet_entity_teleport_pos_z(Jvm *jvm, void *s18_packet_entity_teleport) {
|
||||
return JCALL(jvm, GetIntField, s18_packet_entity_teleport, ids->S18PacketEntityTeleport_posZ);
|
||||
}
|
||||
|
||||
extern "C" jbyte java_jni_get_byte_field_s18_packet_entity_teleport_yaw(Jvm *jvm, void *s18_packet_entity_teleport) {
|
||||
return JCALL(jvm, GetByteField, s18_packet_entity_teleport, ids->S18PacketEntityTeleport_yaw);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_s19_packet_entity_status_entity_id(Jvm *jvm, void *s19_packet_entity_status) {
|
||||
return JCALL(jvm, GetIntField, s19_packet_entity_status, ids->S19PacketEntityStatus_entityId);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_s27_packet_explosion_f(Jvm *jvm, void *s27_packet_explosion) {
|
||||
return JCALL(jvm, GetFloatField, s27_packet_explosion, ids->S27PacketExplosion_f);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_s27_packet_explosion_h(Jvm *jvm, void *s27_packet_explosion) {
|
||||
return JCALL(jvm, GetFloatField, s27_packet_explosion, ids->S27PacketExplosion_h);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_c16_enum_state_open_inventory(Jvm *jvm) {
|
||||
return JCALL(jvm, GetStaticObjectField, ids->C16PacketClientStatus_EnumState, ids->C16PacketClientStatus_EnumState_openInventory);
|
||||
}
|
||||
|
||||
extern "C" jobject java_new_c16_packet_client_status(Jvm *jvm, void *object) {
|
||||
return JCALLN(jvm, NewObject, ids->C16PacketClientStatus, ids->C16PacketClientStatus_init, object);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_network_player_info_get_player_team(Jvm *jvm, void *network_player_info) {
|
||||
return JCALLN(jvm, CallObjectMethod, network_player_info, ids->NetworkPlayerInfo_getPlayerTeam);
|
||||
}
|
||||
|
||||
extern "C" jint java_call_object_hash_code(Jvm *jvm, void *object) {
|
||||
return JCALLN(jvm, CallIntMethod, object, ids->Object_hashCode);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_ichat_component_get_formatted_text(Jvm *jvm, void *i_chat_component) {
|
||||
return JCALLN(jvm, CallObjectMethod, i_chat_component, ids->IChatComponent_getFormattedText);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_slot_get_stack(Jvm *jvm, void *slot) {
|
||||
return JCALLN(jvm, CallObjectMethod, slot, ids->Slot_getStack);
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_channel_pipeline(Jvm *jvm, void *channel) {
|
||||
return JCALLN(jvm, CallObjectMethod, channel, ids->Channel_pipeline);
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_inventory_basic(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->InventoryBasic) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_c03_packet_player(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->C03PacketPlayer) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_c07_packet_player_digging(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->C07PacketPlayerDigging) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_c08_packet_player_block_placement(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->C08PacketPlayerBlockPlacement) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_c02_packet_use_entity(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->C02PacketUseEntity) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_c0_bpacket_entity_action(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->C0BPacketEntityAction) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_s12_packet_entity_velocity(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->S12PacketEntityVelocity) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_s14_packet_entity(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->S14PacketEntity) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_s18_packet_entity_teleport(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->S18PacketEntityTeleport) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_s19_packet_entity_status(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->S19PacketEntityStatus) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_s29_packet_sound_effect(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->S29PacketSoundEffect) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_s06_packet_update_health(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->S06PacketUpdateHealth) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_s08_packet_player_pos_look(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->S08PacketPlayerPosLook) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_s27_packet_explosion(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->S27PacketExplosion) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_s00_packet_keep_alive(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->S00PacketKeepAlive) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_c00_packet_keep_alive(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->C00PacketKeepAlive) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_c0_epacket_click_window(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->C0EPacketClickWindow) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_c16_packet_client_status(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->C16PacketClientStatus) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_c(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->c) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
extern "C" Vec3 java_get_c03_packet_player_a(Jvm *jvm, jobject c03_packet_player) {
|
||||
double x = JCALLN(jvm, GetDoubleField, c03_packet_player, ids->C03PacketPlayer_a);
|
||||
double y = JCALLN(jvm, GetDoubleField, c03_packet_player, ids->C03PacketPlayer_b);
|
||||
double z = JCALLN(jvm, GetDoubleField, c03_packet_player, ids->C03PacketPlayer_c);
|
||||
Vec3 r = vec3_make(x, y, z);
|
||||
asm volatile("");
|
||||
return r;
|
||||
}
|
||||
|
||||
// ---- hand-written
|
||||
FN(jint, java_call_enum_ordinal, (Jvm *jvm, void *c07_packet_player_digging)); // enum ordinal
|
||||
|
||||
// enum field: object (ordinal via java_call_enum_ordinal) in the newer mappings, plain int in the old ones
|
||||
extern "C" jint java_c07_packet_player_digging_status(Jvm *jvm, jobject object) {
|
||||
JIds *ids = jvm->ids; JNIEnv *env = jvm->env;
|
||||
auto vt = *env; jfieldID id = (jfieldID)ids->C07PacketPlayerDigging_status;
|
||||
if (ids->variant != 0)
|
||||
return java_call_enum_ordinal(jvm, vt->GetObjectField(env, object, id));
|
||||
return vt->GetIntField(env, object, id);
|
||||
}
|
||||
extern "C" jint java_c0_bpacket_entity_action(Jvm *jvm, jobject object) {
|
||||
JIds *ids = jvm->ids; JNIEnv *env = jvm->env;
|
||||
auto vt = *env; jfieldID id = (jfieldID)ids->C0BPacketEntityAction_Lis_a_;
|
||||
if (ids->variant != 0)
|
||||
return java_call_enum_ordinal(jvm, vt->GetObjectField(env, object, id));
|
||||
return vt->GetIntField(env, object, id);
|
||||
}
|
||||
extern "C" jobject java_call_channel_pipeline_add_before(Jvm *jvm, jobject channel_pipeline, const char *a, const char *b, void *c) {
|
||||
JNIEnv *env = jvm->env;
|
||||
jstring sa = (*env)->NewStringUTF(env, a);
|
||||
jstring sb = (*env)->NewStringUTF(env, b);
|
||||
return JCALL1(jvm, CallObjectMethod, channel_pipeline, (jmethodID)ids->ChannelPipeline_addBefore, sa, sb, c);
|
||||
}
|
||||
extern "C" jint java_get_c16_packet_client_status_status(Jvm *jvm, jobject c16_packet_client_status) {
|
||||
jobject e = JCALLN(jvm, GetObjectField, c16_packet_client_status, (jfieldID)ids->C16PacketClientStatus_status);
|
||||
return java_call_enum_ordinal(jvm, e);
|
||||
}
|
||||
|
||||
// ---- JNI bridge functions found by the gap scan (no caller outside the virtualised init) ----
|
||||
extern "C" jobject java_call_static_ichat_component_serializer_json_to_component(Jvm *jvm, void *i_chat_component) {
|
||||
return JCALLN(jvm, CallStaticObjectMethod, ids->IChatComponent_Serializer, ids->IChatComponent_Serializer_jsonToComponent, i_chat_component);
|
||||
}
|
||||
|
||||
extern "C" jdouble java_get_c03_packet_player_x(Jvm *jvm, void *c03_packet_player) {
|
||||
return JCALL(jvm, GetDoubleField, c03_packet_player, ids->C03PacketPlayer_a);
|
||||
}
|
||||
|
||||
extern "C" jdouble java_get_c03_packet_player_c(Jvm *jvm, void *c03_packet_player) {
|
||||
return JCALL(jvm, GetDoubleField, c03_packet_player, ids->C03PacketPlayer_c);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_s12_packet_entity_velocity_motion_x(Jvm *jvm, void *s12_packet_entity_velocity) {
|
||||
return JCALL(jvm, GetIntField, s12_packet_entity_velocity, ids->S12PacketEntityVelocity_motionX);
|
||||
}
|
||||
|
||||
extern "C" jbyte java_jni_get_byte_field_s14_packet_entity_pitch(Jvm *jvm, void *s14_packet_entity) {
|
||||
return JCALL(jvm, GetByteField, s14_packet_entity, ids->S14PacketEntity_pitch);
|
||||
}
|
||||
|
||||
extern "C" jbyte java_jni_get_byte_field_s18_packet_entity_teleport_pitch(Jvm *jvm, void *s18_packet_entity_teleport) {
|
||||
return JCALL(jvm, GetByteField, s18_packet_entity_teleport, ids->S18PacketEntityTeleport_pitch);
|
||||
}
|
||||
|
||||
extern "C" void java_set_s27_packet_explosion_f(Jvm *jvm, void *s27_packet_explosion, float value) {
|
||||
JCALL(jvm, SetFloatField, s27_packet_explosion, ids->S27PacketExplosion_f, value);
|
||||
}
|
||||
|
||||
extern "C" void java_set_s27_packet_explosion_h(Jvm *jvm, void *s27_packet_explosion, float value) {
|
||||
JCALL(jvm, SetFloatField, s27_packet_explosion, ids->S27PacketExplosion_h, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_jni_is_instance_of_s3_fpacket_custom_payload(Jvm *jvm, jobject candidate) {
|
||||
return JCALLN(jvm, IsInstanceOf, candidate, (jclass)ids->S3FPacketCustomPayload) ? candidate : nullptr;
|
||||
}
|
||||
|
||||
// ---- functions the first gap scan missed (padding / tail-jump neighbours) ----
|
||||
extern "C" void java_set_s12_packet_entity_velocity_motion_x(Jvm *jvm, void *s12_packet_entity_velocity, int value) {
|
||||
JCALL(jvm, SetIntField, s12_packet_entity_velocity, ids->S12PacketEntityVelocity_motionX, value);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_s12_packet_entity_velocity_motion_y(Jvm *jvm, void *s12_packet_entity_velocity) {
|
||||
return JCALL(jvm, GetIntField, s12_packet_entity_velocity, ids->S12PacketEntityVelocity_motionY);
|
||||
}
|
||||
|
||||
extern "C" void java_set_s12_packet_entity_velocity_motion_y(Jvm *jvm, void *s12_packet_entity_velocity, int value) {
|
||||
JCALL(jvm, SetIntField, s12_packet_entity_velocity, ids->S12PacketEntityVelocity_motionY, value);
|
||||
}
|
||||
|
||||
extern "C" jint java_get_s12_packet_entity_velocity_motion_z(Jvm *jvm, void *s12_packet_entity_velocity) {
|
||||
return JCALL(jvm, GetIntField, s12_packet_entity_velocity, ids->S12PacketEntityVelocity_motionZ);
|
||||
}
|
||||
|
||||
extern "C" void java_set_s12_packet_entity_velocity_motion_z(Jvm *jvm, void *s12_packet_entity_velocity, int value) {
|
||||
JCALL(jvm, SetIntField, s12_packet_entity_velocity, ids->S12PacketEntityVelocity_motionZ, value);
|
||||
}
|
||||
|
||||
extern "C" jfloat java_get_s27_packet_explosion_g(Jvm *jvm, void *s27_packet_explosion) {
|
||||
return JCALL(jvm, GetFloatField, s27_packet_explosion, ids->S27PacketExplosion_g);
|
||||
}
|
||||
|
||||
extern "C" void java_set_s27_packet_explosion_g(Jvm *jvm, void *s27_packet_explosion, float value) {
|
||||
JCALL(jvm, SetFloatField, s27_packet_explosion, ids->S27PacketExplosion_g, value);
|
||||
}
|
||||
|
||||
extern "C" jobject java_get_s3_fpacket_custom_payload(Jvm *jvm, void *s3_f_packet_custom_payload) {
|
||||
return JCALL(jvm, GetObjectField, s3_f_packet_custom_payload, ids->S3FPacketCustomPayload_Ljava_lang_String_);
|
||||
}
|
||||
|
||||
extern "C" jint java_call_packet_buffer_readable_bytes(Jvm *jvm, void *packet_buffer) {
|
||||
return JCALLN(jvm, CallIntMethod, packet_buffer, ids->PacketBuffer_readableBytes);
|
||||
}
|
||||
|
||||
extern "C" jint java_call_packet_buffer_reader_index(Jvm *jvm, void *packet_buffer) {
|
||||
return JCALLN(jvm, CallIntMethod, packet_buffer, ids->PacketBuffer_readerIndex);
|
||||
}
|
||||
|
||||
// S3FPacketCustomPayload payload as a byte[]: 1.7 keeps a byte[] field; 1.8 keeps a PacketBuffer whose readable
|
||||
// bytes are copied into a new array (getBytes(readerIndex, array))
|
||||
extern "C" jobject java_get_s3_fpacket_custom_payload_data(Jvm *jvm, void *pkt) {
|
||||
jobject data = JCALLN(jvm, GetObjectField, pkt, ids->S3FPacketCustomPayload_data);
|
||||
if (jvm->ids->variant == 0) return data;
|
||||
jint len = JCALLN(jvm, CallIntMethod, data, ids->PacketBuffer_readableBytes);
|
||||
jint idx = JCALLN(jvm, CallIntMethod, data, ids->PacketBuffer_readerIndex);
|
||||
jobject arr = JCALLN(jvm, NewByteArray, len);
|
||||
if (!arr) return nullptr;
|
||||
JCALLN(jvm, CallObjectMethod, data, ids->PacketBuffer_getBytes, idx, arr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
extern "C" jobject java_call_packet_buffer_get_bytes(Jvm *jvm, void *buf, int idx, jobject arr) {
|
||||
return JCALL1(jvm, CallObjectMethod, buf, ids->PacketBuffer_getBytes, idx, arr);
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
// 0x28000: module manager - per-event handler lists, dispatch, small item helpers
|
||||
// cflags: /Ob1 /clang:-fno-slp-vectorize -mllvm -switch-to-lookup=false
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jcall.h"
|
||||
extern "C" void slinky_logf(const char *fmt, ...);
|
||||
typedef void (*Handler)(void *event);
|
||||
struct Module;
|
||||
struct Reg { Module **list; unsigned long long n; };
|
||||
struct Mgr {
|
||||
Module **mods; unsigned long long count; Reg cat[5]; // +0x10: modules per category
|
||||
Reg reg[27]; // +0x060: modules that implement each event type
|
||||
Handler *lists[27]; // +0x210: NULL-terminated handler list per event type
|
||||
Module **en_list; unsigned long long en_count; // +0x2e8 pending enables
|
||||
Module **dis_list; unsigned long long dis_count; // +0x2f8 pending disables
|
||||
void (*set308)(void *mod, int v); // +0x308
|
||||
};
|
||||
struct HSlot { Handler f; void *x; };
|
||||
struct Module { char padding_00[0x10]; unsigned cat; unsigned char enabled; char padding_15[0x33]; HSlot h[27]; };
|
||||
// dispatch: call every handler registered for the event's type (first dword of the event)
|
||||
extern "C" void dispatch_event(Mgr *manager, unsigned *e) {
|
||||
unsigned type = *e;
|
||||
if (type >= 27) return;
|
||||
Handler *h = manager->lists[type];
|
||||
for (Handler f = *h; f; f = *++h) f(e);
|
||||
}
|
||||
extern "C" bool is_event_handled(Mgr *manager, unsigned type) {
|
||||
if (type >= 27) return true;
|
||||
return *manager->lists[type] == 0;
|
||||
}
|
||||
extern "C" bool has_queued_modules(Mgr *manager) { return manager->dis_count != 0 || manager->en_count != 0; }
|
||||
extern "C" void apply_module_state(Mgr *manager, void *mod, int v) {
|
||||
auto set308 = manager->set308;
|
||||
asm("" : "+r"(set308));
|
||||
set308(mod, v);
|
||||
}
|
||||
|
||||
FN(void *, java_get_entity_player_inventory, (void *jvm, void *entity_player));
|
||||
FN(int, java_get_inventory_player_current_item, (void *jvm, void *inventory_player));
|
||||
FN(void *, java_get_inventory_player_main_inventory, (void *jvm, void *inventory_player));
|
||||
FN(void *, java_call_item_stack_get_item, (void *jvm, void *item_stack));
|
||||
FN(int, java_call_static_item_get_id_from_item, (void *jvm, void *item));
|
||||
// item held in a hotbar slot (0..8) of the entity's inventory
|
||||
extern "C" void *item_held_hotbar_slot(Jvm *jvm, void *ent) {
|
||||
void *inv = java_get_entity_player_inventory(jvm, ent);
|
||||
unsigned slot = java_get_inventory_player_current_item(jvm, inv);
|
||||
if (slot > 8) return 0;
|
||||
void *arr = java_get_inventory_player_main_inventory(jvm, inv);
|
||||
jobject st = (*jvm->env)->GetObjectArrayElement(jvm->env, (jobjectArray)arr, slot);
|
||||
if (!st) return 0;
|
||||
return java_call_item_stack_get_item(jvm, st);
|
||||
}
|
||||
// is the item a sword (ids 267, 268, 272, 276, 283)?
|
||||
extern "C" bool item_sword(Jvm *jvm, void *item) {
|
||||
unsigned char r;
|
||||
if (!item) { r = 0; goto out; }
|
||||
{
|
||||
unsigned k = java_call_static_item_get_id_from_item(jvm, item) - 0x10b;
|
||||
if (k <= 0x10) { asm volatile(""); r = (unsigned char)(0x10223u >> k); }
|
||||
else r = 0;
|
||||
}
|
||||
out:
|
||||
SHARED_EPILOG();
|
||||
return r & 1;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline)) bool is_code(unsigned short c) {
|
||||
return (unsigned short)(c - 0x61) < 6 | (unsigned short)(c - 0x30) < 10 | c == 0x72;
|
||||
}
|
||||
// colour code (0-9, a-f, r) in effect before the first name character of a display string, 0 if none
|
||||
static inline __attribute__((always_inline)) unsigned prefix_code(Jvm *jvm, jstring s) {
|
||||
JNIEnv *env = jvm->env;
|
||||
if (!s) return 0;
|
||||
jsize len = (*env)->GetStringLength(env, s);
|
||||
if (len <= 0) return 0;
|
||||
const jchar *ch = (const jchar *)(*env)->GetStringCritical(env, s, 0);
|
||||
if (!ch) return 0;
|
||||
unsigned r = 0;
|
||||
bool prev = false;
|
||||
long long i = 0;
|
||||
for (;;) {
|
||||
bool p = prev;
|
||||
unsigned short c = ch[i];
|
||||
prev = c == 0xa7;
|
||||
if (!prev) {
|
||||
if (p) {
|
||||
if ((unsigned short)(c - 0x30) < 10) r = c;
|
||||
if ((unsigned short)(c - 0x61) < 6) r = c;
|
||||
if (c == 0x72) r = c;
|
||||
} else {
|
||||
if (c == 0x5f || (unsigned short)(c - 0x30) < 10 || (unsigned short)((c & 0xffdf) - 0x41) < 0x1a)
|
||||
break;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
if (i >= (long long)len) { r = 0; break; }
|
||||
}
|
||||
(*env)->ReleaseStringCritical(env, s, ch);
|
||||
return r;
|
||||
}
|
||||
// last colour code (0-9, a-f, r) of a display string, 'f' if none
|
||||
static inline __attribute__((always_inline)) unsigned short last_code(Jvm *jvm, jstring s) {
|
||||
unsigned short res = 0x66;
|
||||
JNIEnv *env = jvm->env;
|
||||
jsize len = (*env)->GetStringLength(env, s);
|
||||
if (!len) return res;
|
||||
const jchar *ch = (const jchar *)(*env)->GetStringCritical(env, s, 0);
|
||||
if (!ch) return res;
|
||||
if (len > 0) {
|
||||
unsigned cand = 0;
|
||||
unsigned char valid = 0;
|
||||
for (unsigned long long k = (unsigned)len + 1; ; ) {
|
||||
unsigned short c = ch[k - 2];
|
||||
if (c != 0xa7) {
|
||||
valid = is_code(c);
|
||||
if (valid) cand = c;
|
||||
} else {
|
||||
if (valid & 1) { res = (unsigned short)cand; break; }
|
||||
valid = 0;
|
||||
}
|
||||
k--;
|
||||
if (k <= 1) break;
|
||||
}
|
||||
}
|
||||
(*env)->ReleaseStringCritical(env, s, ch);
|
||||
return res;
|
||||
}
|
||||
extern "C" unsigned name_prefix_code(Jvm *jvm, jstring s) { return prefix_code(jvm, s); }
|
||||
FN(jstring, java_get_score_player_team, (Jvm *jvm, void *score_player_team));
|
||||
extern "C" unsigned short name_last_code(Jvm *jvm, void *object) {
|
||||
jstring score_player_team = java_get_score_player_team(jvm, object);
|
||||
if (!score_player_team) return 0x66;
|
||||
return last_code(jvm, score_player_team);
|
||||
}
|
||||
FN(jstring, java_call_entity_get_command_sender_name, (Jvm *jvm, void *ent));
|
||||
FN(void *, java_jni_is_instance_of_entity_living_base, (Jvm *jvm, void *ent));
|
||||
FN(void *, java_call_entity_living_base_get_team, (Jvm *jvm, void *entity_living_base));
|
||||
FN(void *, java_jni_is_instance_of_score_player_team, (Jvm *jvm, void *candidate));
|
||||
// entity name colour: prefix code of the display name, else the team colour, else 'f'
|
||||
extern "C" unsigned short entity_name_colour(Jvm *jvm, void *ent) {
|
||||
unsigned short r = (unsigned short)prefix_code(jvm, java_call_entity_get_command_sender_name(jvm, ent));
|
||||
if (r) return r;
|
||||
void *entity_living_base = java_jni_is_instance_of_entity_living_base(jvm, ent);
|
||||
if (!entity_living_base) return 0x66;
|
||||
void *entity_living_base_get_team = java_call_entity_living_base_get_team(jvm, entity_living_base);
|
||||
if (!entity_living_base_get_team) return 0x66;
|
||||
void *score_player_team = java_jni_is_instance_of_score_player_team(jvm, entity_living_base_get_team);
|
||||
if (!score_player_team) return 0x66;
|
||||
jstring s = java_get_score_player_team(jvm, score_player_team);
|
||||
if (!s) return 0x66;
|
||||
return last_code(jvm, s);
|
||||
}
|
||||
|
||||
// apply pending enables/disables: rebuild the handler lists of the affected event types, fire event 5
|
||||
// Same layout as the context the frame code builds (java_natives_frame.cpp): toggle handlers read the game
|
||||
// objects, so these first fields have to line up.
|
||||
struct HCtx { void *jvm; Mgr *module_manager; void *profile_list; void *minecraft; void *world; void *player; };
|
||||
struct ToggleEvent { long long type; HCtx *ctx; Module *mod; unsigned char on; char rest[0x2f]; };
|
||||
FN(void, release_owner, (void *timer));
|
||||
static inline __attribute__((always_inline)) unsigned long long handler_mask(Module *module) {
|
||||
unsigned long long mask = 0;
|
||||
#pragma clang loop unroll_count(3)
|
||||
for (unsigned t = 0; t < 27; t++)
|
||||
if (module->h[t].f) mask |= 1u << t;
|
||||
return mask;
|
||||
}
|
||||
static inline __attribute__((always_inline)) void rebuild(Mgr *manager, unsigned long long mask) {
|
||||
#pragma clang loop unroll(disable)
|
||||
for (unsigned long long t = 0; t != 27; t++) {
|
||||
if (!((mask >> t) & 1)) continue;
|
||||
Handler *out = manager->lists[t];
|
||||
Reg *r = &manager->reg[t];
|
||||
#pragma clang loop unroll_count(2)
|
||||
for (unsigned long long i = 0; i < r->n; i++) {
|
||||
Module *mod = r->list[i];
|
||||
if (mod->enabled == 1 && mod->h[t].f) *out++ = mod->h[t].f;
|
||||
}
|
||||
*out = 0;
|
||||
}
|
||||
}
|
||||
extern "C" void apply_module_toggles(HCtx *ctx) {
|
||||
Mgr *manager = ctx->module_manager;
|
||||
// The handlers can touch the game objects: Block In's enable reads the local player's rotation, and the
|
||||
// start-up queue (modules with flags bit 1) is applied on the first frame, which on a fresh launch is
|
||||
// the main menu, where Minecraft.thePlayer/theWorld are NULL. Keeping the queue until the first frame
|
||||
// that has both is what the tick path already does (tick_modules has the same check); applying it
|
||||
// against a NULL player faulted in jni_fast_GetFloatField.
|
||||
if (!ctx->world || !ctx->player) return;
|
||||
if (manager->en_count) {
|
||||
unsigned long long mask = 0;
|
||||
for (unsigned long long i = 0; i != manager->en_count; i++) {
|
||||
Module *mod = manager->en_list[i];
|
||||
mod->enabled = 1;
|
||||
mask |= handler_mask(mod);
|
||||
}
|
||||
rebuild(manager, mask);
|
||||
slinky_logf("toggles: enable n=%llu mask=%llx first=%s", manager->en_count, mask, *(const char **)manager->en_list[0]);
|
||||
for (unsigned long long i = 0; i < manager->en_count; i++) {
|
||||
ToggleEvent e = {5, ctx, manager->en_list[i], 1, {}};
|
||||
Handler *h = ctx->module_manager->lists[5];
|
||||
for (Handler f = *h; f; f = *++h) f(&e);
|
||||
}
|
||||
manager->en_count = 0;
|
||||
manager = ctx->module_manager;
|
||||
}
|
||||
if (manager->dis_count) {
|
||||
unsigned long long mask = 0;
|
||||
for (unsigned long long i = 0; i != manager->dis_count; i++) {
|
||||
Module *mod = manager->dis_list[i];
|
||||
mod->enabled = 0;
|
||||
mask |= handler_mask(mod);
|
||||
}
|
||||
for (unsigned long long i = 0; i < manager->dis_count; i++) {
|
||||
Module *mod = manager->dis_list[i];
|
||||
ToggleEvent e = {5, ctx, mod, 0, {}};
|
||||
Handler *h = ctx->module_manager->lists[5];
|
||||
for (Handler f = *h; f; f = *++h) f(&e);
|
||||
release_owner(mod);
|
||||
}
|
||||
rebuild(manager, mask);
|
||||
slinky_logf("toggles: disable n=%llu mask=%llx first=%s", manager->dis_count, mask, *(const char **)manager->dis_list[0]);
|
||||
manager->dis_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// manager constructor: copy the static module table, bucket modules by category and by implemented event
|
||||
FN(void *, libc_memset, (void *dest, int c, unsigned long long n)); // memset
|
||||
FN(void *, libc_calloc, (unsigned long long n, unsigned long long size)); // calloc
|
||||
FN(void *, libc_memcpy_alt, (void *dest, const void *src, unsigned long long n)); // memcpy
|
||||
FN(void, libc_qsort, (void *base, unsigned long long n, unsigned long long size, int (*cmp)(const void *, const void *))); // qsort
|
||||
extern "C" Module *const ptr_to_aim_assist_module_descriptor[0x43];
|
||||
extern "C" unsigned module_manager_count;
|
||||
FN(int, compare_module_slots, (const void *left, const void *right));
|
||||
extern "C" int create_module_manager(Mgr *manager, void (*setter)(void *mod, int v)) {
|
||||
libc_memset(manager->cat, 0, 0x300);
|
||||
manager->count = 0x43;
|
||||
manager->mods = (Module **)libc_calloc(0x43, 8);
|
||||
if (!manager->mods) return 0x3ed;
|
||||
libc_memcpy_alt(manager->mods, ptr_to_aim_assist_module_descriptor, 0x218);
|
||||
#pragma clang loop unroll(disable)
|
||||
for (unsigned long long i = 0; i != 0x43; i++) {
|
||||
Module *mod = ptr_to_aim_assist_module_descriptor[i];
|
||||
*(unsigned long long *)((char *)manager + 0x18 + ((unsigned long long)mod->cat << 4)) += 1;
|
||||
#pragma clang loop unroll_count(2)
|
||||
for (unsigned t = 0; t < 27; t++)
|
||||
if (mod->h[t].f) manager->reg[t].n++;
|
||||
}
|
||||
for (int c = 0; c < 5; c++) {
|
||||
unsigned long long n = manager->cat[c].n;
|
||||
if (n) {
|
||||
manager->cat[c].list = (Module **)libc_calloc(n, 8);
|
||||
if (!manager->cat[c].list) return 0x3ed;
|
||||
} else {
|
||||
manager->cat[c].list = 0; // empty category: calloc(0) is implementation-defined
|
||||
}
|
||||
manager->cat[c].n = 0;
|
||||
}
|
||||
#pragma clang loop unroll(disable)
|
||||
for (int t = 0; t < 27; t++) {
|
||||
unsigned long long n = manager->reg[t].n;
|
||||
if (n) {
|
||||
manager->reg[t].list = (Module **)libc_calloc(n, 8);
|
||||
if (!manager->reg[t].list) return 0x3ed;
|
||||
} else {
|
||||
manager->reg[t].list = 0;
|
||||
}
|
||||
manager->lists[t] = (Handler *)libc_calloc(n + 1, 8);
|
||||
if (!manager->lists[t]) return 0x3ed;
|
||||
manager->reg[t].n = 0;
|
||||
}
|
||||
manager->en_list = (Module **)libc_calloc(0x43, 8);
|
||||
if (!manager->en_list) return 0x3ed;
|
||||
manager->dis_list = (Module **)libc_calloc(0x43, 8);
|
||||
if (!manager->dis_list) return 0x3ed;
|
||||
#pragma clang loop unroll(disable)
|
||||
for (unsigned long long i = 0; i != 0x43; i++) {
|
||||
Module *mod = ptr_to_aim_assist_module_descriptor[i];
|
||||
Reg *cr = &manager->cat[mod->cat];
|
||||
unsigned long long cn = cr->n;
|
||||
cr->list[cn] = mod;
|
||||
cr->n = cn + 1;
|
||||
#pragma clang loop unroll_count(2)
|
||||
for (unsigned t = 0; t < 27; t++)
|
||||
if (mod->h[t].f) {
|
||||
unsigned long long rn = manager->reg[t].n;
|
||||
manager->reg[t].list[rn] = mod;
|
||||
manager->reg[t].n = rn + 1;
|
||||
}
|
||||
}
|
||||
#pragma clang loop unroll(disable)
|
||||
for (unsigned t = 0; t != 27; t++) {
|
||||
module_manager_count = t;
|
||||
libc_qsort(manager->reg[t].list, manager->reg[t].n, 8, compare_module_slots);
|
||||
}
|
||||
manager->set308 = setter;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// queue a module enable/disable (applied by apply_module_toggles); a disable is announced first (event 4) and can be
|
||||
// cancelled by a handler setting the event's flag
|
||||
struct QEvent { int type; int padding_04; Module *mod; unsigned char cancel; char rest[0x37]; };
|
||||
extern "C" bool queue_module_toggle(Mgr *module_manager, Module *module, unsigned char enabled) {
|
||||
if (module->enabled == enabled) return true;
|
||||
Module ***list;
|
||||
if (enabled) {
|
||||
list = &module_manager->en_list;
|
||||
} else {
|
||||
QEvent ev = {};
|
||||
ev.type = 4;
|
||||
ev.mod = module;
|
||||
Handler *h = module_manager->lists[4];
|
||||
if (*h) {
|
||||
for (Handler f = *h; f; f = *++h) f((unsigned *)&ev);
|
||||
if (ev.cancel & 1) return false;
|
||||
}
|
||||
list = &module_manager->dis_list;
|
||||
}
|
||||
unsigned long long *n = (unsigned long long *)(list + 1);
|
||||
if (*n >= 0x43) return false; // en_list/dis_list capacity
|
||||
(*list)[(*n)++] = module;
|
||||
return true;
|
||||
}
|
||||
FN(void *, get_block_state_id, (Jvm *jvm, void *a1));
|
||||
FN(jint, get_block_id, (Jvm *jvm, void *a1));
|
||||
extern "C" bool block_entity_id(Jvm *jvm, void *ent) {
|
||||
bool r = false;
|
||||
if (ent) {
|
||||
void *block_state_id = get_block_state_id(jvm, ent);
|
||||
if (block_state_id) r = get_block_id(jvm, block_state_id) != 0;
|
||||
}
|
||||
SHARED_EPILOG();
|
||||
return r;
|
||||
}
|
||||
|
||||
// ---- gap-scan functions of the manager block ----
|
||||
// sort comparator: descending by the int in slot `module_manager_count` (slots are 16 bytes, starting at +0x50)
|
||||
struct SortSlot { int v; int padding_04[3]; };
|
||||
struct SortRec { char padding_00[0x50]; SortSlot k[1]; };
|
||||
extern "C" int compare_module_slots(const void *pa, const void *pb) {
|
||||
SortRec **a = (SortRec **)pa, **b = (SortRec **)pb;
|
||||
SortRec *rb = *b;
|
||||
unsigned i = module_manager_count;
|
||||
return rb->k[i].v - (*a)->k[i].v;
|
||||
}
|
||||
|
||||
// module manager destructor: free every list the constructor (create_module_manager) allocated
|
||||
FN(void, libc_free, (void *ptr));
|
||||
#define RELEASE(p) \
|
||||
do { \
|
||||
if (p) { \
|
||||
libc_free(p); \
|
||||
p = 0; \
|
||||
} \
|
||||
} while (0)
|
||||
extern "C" void destroy_module_manager(Mgr *manager) {
|
||||
RELEASE(manager->mods);
|
||||
for (int i = 0; i < 5; i++) RELEASE(manager->cat[i].list);
|
||||
#pragma clang loop unroll(disable)
|
||||
for (int i = 0; i < 27; i++) RELEASE(manager->reg[i].list);
|
||||
#pragma clang loop unroll(disable)
|
||||
for (int i = 0; i < 27; i++) RELEASE(manager->lists[i]);
|
||||
RELEASE(manager->en_list);
|
||||
RELEASE(manager->dis_list);
|
||||
}
|
||||
|
||||
// Minecraft formatting codes in a (modified UTF-8) name: returns the last colour code (0-9, a-f; 'r' resets to 'f')
|
||||
// seen before the first visible character (letter, digit or '_'); *hasText tells whether there was one.
|
||||
// "§" arrives as the byte pair C2 A7.
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
extern "C" int last_text_color_code(const char *s, int colour, bool *hasText) {
|
||||
*hasText = false;
|
||||
unsigned long long n = libc_strlen(s);
|
||||
bool sawC2 = false, afterSign = false;
|
||||
for (unsigned long long i = 0; i != n; i++) {
|
||||
signed char c = s[i];
|
||||
if (afterSign) {
|
||||
signed char code = c == 'r' ? 'f' : c;
|
||||
if ((unsigned short)(c - '0') < 10) colour = code;
|
||||
if ((unsigned short)(c - 'a') < 6) colour = code;
|
||||
if (c == 'r') colour = code;
|
||||
afterSign = false;
|
||||
} else if (sawC2) {
|
||||
if ((unsigned char)c == 0xa7) {
|
||||
afterSign = true;
|
||||
sawC2 = false;
|
||||
} else {
|
||||
sawC2 = (unsigned char)c == 0xc2;
|
||||
}
|
||||
} else {
|
||||
if ((unsigned char)c == 0xc2) {
|
||||
sawC2 = true;
|
||||
continue;
|
||||
}
|
||||
if (c == '_' || (unsigned short)(c - '0') < 10 || (unsigned short)((c & 0xdf) - 'A') <= 25) {
|
||||
*hasText = true;
|
||||
return colour;
|
||||
}
|
||||
}
|
||||
}
|
||||
return colour;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 0x2b000: refill-stack tracking for the held block, accumulated rotation delta
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jcall.h"
|
||||
|
||||
struct Rot { float yaw, pitch; };
|
||||
FN(void *, java_get_entity_player_inventory, (void *jvm, void *entity_player));
|
||||
FN(int, java_get_inventory_player_current_item, (void *jvm, void *inv));
|
||||
FN(void *, java_get_inventory_player_main_inventory, (void *jvm, void *inv));
|
||||
FN(void *, java_call_item_stack_get_item, (void *jvm, void *stack));
|
||||
FN(int, java_call_static_item_get_id_from_item, (void *jvm, void *item));
|
||||
FN(int, java_get_item_stack_stack_size, (void *jvm, void *stack)); // stack size
|
||||
FN(bool, block_entity_id, (void *jvm, void *item)); // is a block item
|
||||
FN(Rot, rot_add, (Rot a, Rot b));
|
||||
|
||||
extern "C" int rotation_hotbar_item_id, refill_state_ticks_since_swap, refill_state_item_id, refill_state_target_stack_size, refill_state_last_slot, refill_state_countdown;
|
||||
extern "C" unsigned char rotation_hotbar_item_pending;
|
||||
extern "C" unsigned long long aim_assist_cached_target_ref;
|
||||
extern "C" Rot aim_assist_mouse_movement_since_last_tick;
|
||||
|
||||
// remember the held block (id, count, slot) before placing
|
||||
extern "C" void tick_click(Ctx *ctx) {
|
||||
if (refill_state_item_id != 0) return;
|
||||
void *inv = java_get_entity_player_inventory(ctx->jvm, ctx->player);
|
||||
int slot = java_get_inventory_player_current_item(ctx->jvm, inv);
|
||||
void *items = java_get_inventory_player_main_inventory(ctx->jvm, inv);
|
||||
JNIEnv *env = ((Jvm *)ctx->jvm)->env;
|
||||
jobject st = (*env)->GetObjectArrayElement(env, (jobjectArray)items, slot);
|
||||
if (!st) return;
|
||||
void *item = java_call_item_stack_get_item(ctx->jvm, st);
|
||||
if (!block_entity_id(ctx->jvm, item)) return;
|
||||
refill_state_item_id = java_call_static_item_get_id_from_item(ctx->jvm, item);
|
||||
int n = java_get_item_stack_stack_size(ctx->jvm, st);
|
||||
refill_state_target_stack_size = n;
|
||||
if ((unsigned)refill_state_ticks_since_swap > 20 || refill_state_last_slot != slot) refill_state_countdown = n;
|
||||
refill_state_ticks_since_swap = 0;
|
||||
refill_state_last_slot = slot;
|
||||
}
|
||||
// after placing: if the stack ran out, request a switch to the same block id
|
||||
extern "C" void tick_click_done(Ctx *ctx) {
|
||||
if (refill_state_item_id == 0) return;
|
||||
void *inv = java_get_entity_player_inventory(ctx->jvm, ctx->player);
|
||||
int slot = java_get_inventory_player_current_item(ctx->jvm, inv);
|
||||
void *items = java_get_inventory_player_main_inventory(ctx->jvm, inv);
|
||||
JNIEnv *env = ((Jvm *)ctx->jvm)->env;
|
||||
jobject st = (*env)->GetObjectArrayElement(env, (jobjectArray)items, slot);
|
||||
if (st) {
|
||||
void *item = java_call_item_stack_get_item(ctx->jvm, st);
|
||||
if (item && java_call_static_item_get_id_from_item(ctx->jvm, item) != 0) {
|
||||
if (java_get_item_stack_stack_size(ctx->jvm, st) >= refill_state_target_stack_size) goto clear;
|
||||
if (--refill_state_countdown != 0) goto clear;
|
||||
}
|
||||
}
|
||||
rotation_hotbar_item_id = refill_state_item_id;
|
||||
rotation_hotbar_item_pending = 1;
|
||||
clear:
|
||||
refill_state_item_id = 0;
|
||||
}
|
||||
extern "C" unsigned long long get_refill_state() { return aim_assist_cached_target_ref; }
|
||||
extern "C" void add_rotation_delta(Rot rotation) { aim_assist_mouse_movement_since_last_tick = rot_add(aim_assist_mouse_movement_since_last_tick, rotation); }
|
||||
@@ -0,0 +1,324 @@
|
||||
// 0x2a000: rotation save/restore, hotbar slot switching, tagged-float entity flags, small id sets
|
||||
// cflags: /Ob1 /clang:-fno-slp-vectorize
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jcall.h"
|
||||
|
||||
struct F2 { float a, b; };
|
||||
FN(void *, get_minecraft, (void *jvm));
|
||||
FN(void *, get_local_player, (void *jvm, void *minecraft));
|
||||
FN(unsigned long long, get_entity_rotation, (void *jvm, void *entity)); // packed Rot
|
||||
FN(void, set_entity_rotation, (void *jvm, void *entity, unsigned long long rot));
|
||||
FN(void, set_entity_prev_rotation, (void *jvm, void *entity, unsigned long long rot));
|
||||
FN(float, java_get_entity_player_speed_on_ground, (void *jvm, void *entity_player)); // tagged flag float
|
||||
FN(void, java_set_entity_player_speed_on_ground, (void *jvm, void *entity_player, float value));
|
||||
FN(void *, java_get_entity_player_inventory, (void *jvm, void *entity_player));
|
||||
FN(int, java_get_inventory_player_current_item, (void *jvm, void *inv));
|
||||
FN(void *, java_get_inventory_player_main_inventory, (void *jvm, void *inv));
|
||||
FN(void *, java_call_item_stack_get_item, (void *jvm, void *stack));
|
||||
FN(int, java_call_static_item_get_id_from_item, (void *jvm, void *item));
|
||||
FN(void, java_set_inventory_player_current_item, (void *jvm, void *inv, int slot));
|
||||
FN(unsigned long long, libc_strlen, (const char *str)); // strlen
|
||||
FN(void **, element_at, (void *vec, int elem, void *hdr, void *key));
|
||||
FN(int, key_name_to_code, (void *key));
|
||||
FN(void *, key_code_to_index, (int idx));
|
||||
FN(void *, vector_find_insert, (void *t, int elem, void *hdr, void *e, void *cmp));
|
||||
FN(void *, vector_erase, (void *vec, int elem, void *hdr, void *key));
|
||||
FN(void *, binary_search, (void *vec, int elem, void *hdr, void *e, void *cmp));
|
||||
|
||||
extern "C" unsigned long long silent_rotation_current, rotation_hotbar_saved_current_rotation; // saved / current rotation
|
||||
extern "C" unsigned char silent_rotation_active_flag, render_rotation_dirty, rotation_hotbar_swap_pending;
|
||||
extern "C" void *rotation_hotbar_slot_owner; // slot owner
|
||||
extern "C" void *rotation_hotbar_module_that_switched_slot; // module that switched the slot
|
||||
extern "C" int rotation_hotbar_item_id, refill_state_ticks_since_swap;
|
||||
extern "C" unsigned char rotation_hotbar_item_pending;
|
||||
|
||||
// apply the saved rotation to the local player (silent rotations)
|
||||
extern "C" void tick_rotation(void *jvm) {
|
||||
void *minecraft = get_minecraft(jvm);
|
||||
void *player = get_local_player(jvm, minecraft);
|
||||
if (!player) return;
|
||||
rotation_hotbar_saved_current_rotation = get_entity_rotation(jvm, player);
|
||||
if (render_rotation_dirty == 1) {
|
||||
render_rotation_dirty = 0;
|
||||
set_entity_rotation(jvm, player, silent_rotation_current);
|
||||
set_entity_prev_rotation(jvm, player, silent_rotation_current);
|
||||
}
|
||||
if (silent_rotation_active_flag == 1) {
|
||||
rotation_hotbar_swap_pending = 1;
|
||||
set_entity_rotation(jvm, player, silent_rotation_current);
|
||||
}
|
||||
}
|
||||
// restore the real rotation
|
||||
extern "C" void apply_rotation_to_player(void *jvm, void *object) {
|
||||
if (rotation_hotbar_swap_pending != 1) return;
|
||||
silent_rotation_current = get_entity_rotation(jvm, object);
|
||||
rotation_hotbar_swap_pending = 0;
|
||||
set_entity_rotation(jvm, object, rotation_hotbar_saved_current_rotation);
|
||||
set_entity_prev_rotation(jvm, object, rotation_hotbar_saved_current_rotation);
|
||||
}
|
||||
|
||||
struct GRef { void *padding_00; jobject ref; };
|
||||
extern "C" void tick_post_frame(Jvm **jj, GRef *g) {
|
||||
if (g->ref) JNI(*jj)->DeleteGlobalRef((*jj)->env, g->ref);
|
||||
g->ref = nullptr;
|
||||
}
|
||||
// claim / release the hotbar for a module
|
||||
extern "C" bool claim_release_hotbar_module(void *owner) {
|
||||
void *cur = rotation_hotbar_slot_owner;
|
||||
if (cur == owner) return true;
|
||||
if (cur) return false;
|
||||
rotation_hotbar_slot_owner = owner;
|
||||
return true;
|
||||
}
|
||||
extern "C" void release_owner(void *owner) {
|
||||
if (rotation_hotbar_slot_owner == owner) rotation_hotbar_slot_owner = nullptr;
|
||||
}
|
||||
// slot index -> (column, row) of the inventory grid; hotbar slots get row 1
|
||||
extern "C" F2 slot_index(unsigned slot) {
|
||||
F2 r;
|
||||
if (slot <= 8) {
|
||||
r.b = 1.0f;
|
||||
r.a = (float)((int)slot - 5);
|
||||
} else {
|
||||
unsigned q = slot / 9;
|
||||
r.b = (float)(int)(slot - q * 9);
|
||||
r.a = (float)(q + 3);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
// append a letter/digit/space to a 100-byte text buffer
|
||||
extern "C" void key_name_append(char *buf, char character) {
|
||||
unsigned long long n = libc_strlen(buf);
|
||||
if (n > 99) return;
|
||||
if ((unsigned char)((character & 0xdf) + 0xbf) >= 0x1a && (unsigned char)(character - '0') >= 10 && character != ' ') return;
|
||||
buf[n] = character;
|
||||
}
|
||||
|
||||
struct IdVec { void *v; char hdr[4]; int count; };
|
||||
struct IdEnt { void *p; int id; int z; };
|
||||
extern "C" int id_vector_count(IdVec *id_vector) { return id_vector->count; }
|
||||
extern "C" void *id_vector_get(IdVec *id_vector, void *key) { return *element_at(id_vector->v, 0x10, id_vector->hdr, key); }
|
||||
extern "C" int compare_id_entry(void *left, IdEnt *right, IdEnt *b) { return right->id - b->id; }
|
||||
extern "C" bool id_vector_insert(IdVec *id_vector, void *key) {
|
||||
int id = key_name_to_code(key);
|
||||
if (id != -1) {
|
||||
IdEnt e = {key_code_to_index(id), id, 0};
|
||||
vector_find_insert(id_vector, 0x10, id_vector->hdr, &e, (void *)compare_id_entry);
|
||||
}
|
||||
return id != -1;
|
||||
}
|
||||
extern "C" void *id_vector_remove(IdVec *id_vector, void *key) { return vector_erase(id_vector->v, 0x10, id_vector->hdr, key); }
|
||||
extern "C" void id_vector_clear(IdVec *id_vector) { id_vector->count = 0; }
|
||||
extern "C" bool id_vector_contains(IdVec *id_vector, int id) {
|
||||
IdEnt e = {nullptr, id, 0};
|
||||
return binary_search(id_vector->v, 0x10, id_vector->hdr, &e, (void *)compare_id_entry) != nullptr;
|
||||
}
|
||||
|
||||
// entity flags kept in a float field as a NaN tagged 0xb315xxxx
|
||||
#define TAG 0xb3150000u
|
||||
static inline __attribute__((always_inline)) unsigned bits(float f) { return __builtin_bit_cast(unsigned, f); }
|
||||
extern "C" unsigned entity_flags_get(void *jvm, void *entity) {
|
||||
unsigned v = bits(java_get_entity_player_speed_on_ground(jvm, entity));
|
||||
return (v & 0xffff0000) == TAG ? (v & 0xffff) : 0;
|
||||
}
|
||||
extern "C" bool entity_flags_has(void *jvm, void *entity, int mask) {
|
||||
unsigned v = bits(java_get_entity_player_speed_on_ground(jvm, entity));
|
||||
unsigned f = (v & 0xffff0000) == TAG ? (v & 0xffff) : 0;
|
||||
return (f & mask) != 0;
|
||||
}
|
||||
extern "C" void entity_flags_set(void *jvm, void *entity, int mask) {
|
||||
unsigned v = bits(java_get_entity_player_speed_on_ground(jvm, entity));
|
||||
unsigned cur = (v & 0xffff0000) == TAG ? v : TAG;
|
||||
java_set_entity_player_speed_on_ground(jvm, entity, __builtin_bit_cast(float, ((cur | mask) & 0xffff) | TAG));
|
||||
}
|
||||
extern "C" void entity_flags_clear(void *jvm, void *entity, int mask) {
|
||||
unsigned v = bits(java_get_entity_player_speed_on_ground(jvm, entity));
|
||||
unsigned cur = (v & 0xffff0000) == TAG ? (v & 0xffff) : TAG;
|
||||
java_set_entity_player_speed_on_ground(jvm, entity, __builtin_bit_cast(float, (~mask & cur) | TAG));
|
||||
}
|
||||
|
||||
// switch to the best hotbar slot (exact item id first, else best score), once per module
|
||||
extern "C" void switch_best_hotbar_slot(Ctx *ctx, void *owner, unsigned char force, int (*score)(int)) {
|
||||
if (rotation_hotbar_module_that_switched_slot != nullptr) return;
|
||||
if (!force && rotation_hotbar_item_id == 0) return;
|
||||
void *inv = java_get_entity_player_inventory(ctx->jvm, ctx->player);
|
||||
int want = rotation_hotbar_item_id;
|
||||
int start = java_get_inventory_player_current_item(ctx->jvm, inv) + rotation_hotbar_item_pending;
|
||||
void *items = java_get_inventory_player_main_inventory(ctx->jvm, inv);
|
||||
JNIEnv *env = ((Jvm *)ctx->jvm)->env;
|
||||
int best = -1, bestScore = -1;
|
||||
#pragma clang loop unroll(disable)
|
||||
for (int i = 0; i != 9; i++) {
|
||||
int slot = (start + i) % 9;
|
||||
jobject st = (*env)->GetObjectArrayElement(env, (jobjectArray)items, slot);
|
||||
if (!st) continue;
|
||||
void *item = java_call_item_stack_get_item(ctx->jvm, st);
|
||||
if (!item) continue;
|
||||
int item_id = java_call_static_item_get_id_from_item(ctx->jvm, item);
|
||||
if (want != 0 && item_id == want) { best = slot; break; }
|
||||
int s = score(item_id);
|
||||
if (s > bestScore) { bestScore = s; best = slot; }
|
||||
}
|
||||
if (best == -1) return;
|
||||
if (!claim_release_hotbar_module(owner)) return;
|
||||
java_set_inventory_player_current_item(ctx->jvm, inv, best);
|
||||
rotation_hotbar_module_that_switched_slot = owner;
|
||||
}
|
||||
// block-id preference for scaffolding: 2 = solid building block, 1 = acceptable, -1 = no
|
||||
extern "C" int block_id_preference_scaffolding(int id) {
|
||||
switch (id) {
|
||||
case 1: case 2: case 3: case 35: case 45: return 2;
|
||||
case 5: case 17: case 20: case 24: case 87: case 121: case 159: case 162: return 1;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
extern "C" void track_owner(Ctx *ctx, void *owner, unsigned char force) {
|
||||
switch_best_hotbar_slot(ctx, owner, force, block_id_preference_scaffolding);
|
||||
}
|
||||
extern "C" void release_tracked_owner() {
|
||||
if (rotation_hotbar_module_that_switched_slot) {
|
||||
release_owner(rotation_hotbar_module_that_switched_slot);
|
||||
rotation_hotbar_module_that_switched_slot = nullptr;
|
||||
}
|
||||
refill_state_ticks_since_swap++;
|
||||
rotation_hotbar_item_id = 0;
|
||||
rotation_hotbar_item_pending = 0;
|
||||
}
|
||||
|
||||
// ---- "moving toward the target?" test, computed once per tick (state bit 0) ----
|
||||
struct V3a { double x, y, z; };
|
||||
FN(void *, java_get_minecraft_object_mouse_over, (void *jvm, void *minecraft));
|
||||
FN(int, java_get_moving_object_position_type_ordinal, (void *jvm, void *moving_object_position));
|
||||
FN(void *, java_get_moving_object_position_hit_vec, (void *jvm, void *moving_object_position));
|
||||
FN(V3a, java_get_vec3_x_coord, (void *jvm, void *vec3));
|
||||
FN(V3a, java_get_entity_pos_x, (void *jvm, void *entity));
|
||||
FN(unsigned char, java_variant, (void *jvm));
|
||||
FN(V3a, vec3_add_eye_height, (V3a v));
|
||||
FN(unsigned char, java_get_entity_on_ground, (void *jvm, void *entity));
|
||||
FN(float, java_get_entity_living_base_land_movement_factor, (void *jvm, void *entity_living_base));
|
||||
FN(void *, get_game_settings, (void *jvm, void *object));
|
||||
FN(void *, java_get_game_settings_forward_key, (void *jvm, void *game_settings));
|
||||
FN(void *, java_get_game_settings_back_key, (void *jvm, void *game_settings));
|
||||
FN(void *, java_get_game_settings_left_key, (void *jvm, void *game_settings));
|
||||
FN(void *, java_get_game_settings_right_key, (void *jvm, void *game_settings));
|
||||
FN(unsigned char, is_key_down, (void *jvm, void *object));
|
||||
FN(float, java_get_entity_living_base_move_forward, (void *jvm, void *entity_living_base));
|
||||
FN(float, java_get_entity_living_base_move_strafing, (void *jvm, void *entity_living_base));
|
||||
FN(V3a, horizontal_motion, (unsigned long long rot, float fwd, float strafe, float speed));
|
||||
FN(V3a, java_get_entity_motion_x, (void *jvm, void *entity));
|
||||
FN(V3a, vec3_add, (V3a a, V3a b));
|
||||
FN(double, vec3_distance_sq, (V3a a, V3a b));
|
||||
extern "C" const float airborne_movement_speed;
|
||||
extern "C" const double attack_motion_slowdown_factor;
|
||||
struct TickState { unsigned char done; char padding_01[0x17]; unsigned char result; };
|
||||
extern "C" void tick_pre_click(Ctx *ctx, TickState *st) {
|
||||
if (st->done & 1) return;
|
||||
unsigned char r = 1;
|
||||
void *hit = java_get_minecraft_object_mouse_over(ctx->jvm, ctx->minecraft);
|
||||
if (hit && java_get_moving_object_position_type_ordinal(ctx->jvm, hit) == 2) {
|
||||
V3a vec3_x_coord = java_get_vec3_x_coord(ctx->jvm, java_get_moving_object_position_hit_vec(ctx->jvm, hit));
|
||||
V3a eye = java_get_entity_pos_x(ctx->jvm, ctx->player);
|
||||
if (!java_variant(ctx->jvm)) eye = vec3_add_eye_height(eye);
|
||||
float speed = java_get_entity_on_ground(ctx->jvm, ctx->player) ? java_get_entity_living_base_land_movement_factor(ctx->jvm, ctx->player) : airborne_movement_speed;
|
||||
void *jvm = ctx->jvm;
|
||||
void *inv = get_game_settings(jvm, ctx->minecraft);
|
||||
void *game_settings_key_bindings = java_get_game_settings_forward_key(jvm, inv), *key_binding = java_get_game_settings_back_key(jvm, inv), *i2 = java_get_game_settings_left_key(jvm, inv), *i3 = java_get_game_settings_right_key(jvm, inv);
|
||||
is_key_down(jvm, game_settings_key_bindings); is_key_down(jvm, key_binding); is_key_down(jvm, i2); is_key_down(jvm, i3);
|
||||
unsigned long long rot = get_entity_rotation(ctx->jvm, ctx->player);
|
||||
float fwd = java_get_entity_living_base_move_forward(ctx->jvm, ctx->player);
|
||||
V3a mv = horizontal_motion(rot, fwd, java_get_entity_living_base_move_strafing(ctx->jvm, ctx->player), speed);
|
||||
V3a motion = java_get_entity_motion_x(ctx->jvm, ctx->player);
|
||||
V3a entity_motion_x = java_get_entity_motion_x(ctx->jvm, ctx->player);
|
||||
entity_motion_x.x *= attack_motion_slowdown_factor;
|
||||
entity_motion_x.z *= attack_motion_slowdown_factor;
|
||||
V3a da = vec3_add(motion, mv);
|
||||
V3a db = vec3_add(entity_motion_x, mv);
|
||||
V3a pa = vec3_add(eye, da);
|
||||
V3a pb = vec3_add(eye, db);
|
||||
double dB = vec3_distance_sq(pb, vec3_x_coord);
|
||||
r = vec3_distance_sq(pa, vec3_x_coord) > dB;
|
||||
}
|
||||
st->result = r;
|
||||
st->done |= 1;
|
||||
}
|
||||
|
||||
// ---- pick the aim target: crosshair entity, forced target, else nearest valid entity in FOV ----
|
||||
struct AB { V3a min, max; };
|
||||
struct Rot2 { float yaw, pitch; };
|
||||
FN(void *, java_get_moving_object_position_entity_hit, (void *jvm, void *moving_object_position));
|
||||
FN(void *, java_jni_is_instance_of_entity_other_player_mp, (void *jvm, void *candidate));
|
||||
FN(float, java_call_entity_living_base_get_health, (void *jvm, void *entity_living_base));
|
||||
FN(void *, get_refill_state, ());
|
||||
FN(void *, java_get_world_player_entities, (void *jvm, void *world));
|
||||
FN(int, java_call_list_size, (void *jvm, void *list));
|
||||
FN(void *, java_call_list_get, (void *jvm, void *list, int i));
|
||||
FN(unsigned char, java_get_entity_player_sleeping, (void *jvm, void *entity_player));
|
||||
FN(void *, java_get_entity_bounding_box, (void *jvm, void *entity));
|
||||
FN(AB, java_get_axis_aligned_bb_min_x, (void *jvm, void *bounding_box));
|
||||
FN(AB, aabb_expand, (AB b, double d));
|
||||
FN(V3a, aabb_clamp_point, (AB b, V3a p));
|
||||
FN(Rot2, rot_look_at, (V3a from, V3a to));
|
||||
FN(float, wrap_angle, (float x));
|
||||
extern "C" const double tenth_in_rotation_and_hotbar_ticks, search_range_squared;
|
||||
extern "C" const float threshold_4900;
|
||||
struct Target { int z0; int padding_04; jobject ref; double dist; };
|
||||
extern "C" bool target_clear(Ctx *ctx, Target *target) {
|
||||
target->z0 = 0;
|
||||
if (!ctx->world) {
|
||||
if (target->ref) { JNIEnv *env = ((Jvm *)ctx->jvm)->env; (*env)->DeleteGlobalRef(env, target->ref); }
|
||||
target->ref = 0;
|
||||
return true;
|
||||
}
|
||||
V3a eye = java_get_entity_pos_x(ctx->jvm, ctx->player);
|
||||
if (!java_variant(ctx->jvm)) eye = vec3_add_eye_height(eye);
|
||||
void *ent;
|
||||
JNIEnv *env;
|
||||
void *hit = java_get_minecraft_object_mouse_over(ctx->jvm, ctx->minecraft);
|
||||
if (hit && java_get_moving_object_position_type_ordinal(ctx->jvm, hit) == 2 && (ent = java_jni_is_instance_of_entity_other_player_mp(ctx->jvm, java_get_moving_object_position_entity_hit(ctx->jvm, hit))) != 0 &&
|
||||
!entity_flags_has(ctx->jvm, ent, 7) && java_call_entity_living_base_get_health(ctx->jvm, ent) > 0.0f) {
|
||||
env = ((Jvm *)ctx->jvm)->env;
|
||||
} else {
|
||||
ent = get_refill_state();
|
||||
env = ((Jvm *)ctx->jvm)->env;
|
||||
if (!ent) {
|
||||
Rot2 rot = __builtin_bit_cast(Rot2, get_entity_rotation(ctx->jvm, ctx->player));
|
||||
void *list = java_get_world_player_entities(ctx->jvm, ctx->world);
|
||||
void *best = 0;
|
||||
double bestD = search_range_squared;
|
||||
for (int i = 0; i < java_call_list_size(ctx->jvm, list); i++) {
|
||||
void *element = java_call_list_get(ctx->jvm, list, i);
|
||||
JNIEnv *e2 = ((Jvm *)ctx->jvm)->env;
|
||||
if ((*e2)->IsSameObject(e2, (jobject)ctx->player, (jobject)element)) continue;
|
||||
if (entity_flags_has(ctx->jvm, element, 7)) continue;
|
||||
if (!(java_call_entity_living_base_get_health(ctx->jvm, element) > 0.0f)) continue;
|
||||
if (java_get_entity_player_sleeping(ctx->jvm, element)) continue;
|
||||
AB bb = aabb_expand(java_get_axis_aligned_bb_min_x(ctx->jvm, java_get_entity_bounding_box(ctx->jvm, element)), tenth_in_rotation_and_hotbar_ticks);
|
||||
double d = vec3_distance_sq(eye, aabb_clamp_point(bb, eye));
|
||||
if (d > bestD) continue;
|
||||
Rot2 ang = rot_look_at(eye, java_get_entity_pos_x(ctx->jvm, element));
|
||||
float dp = ang.pitch - rot.pitch;
|
||||
float dy = wrap_angle(ang.yaw - rot.yaw);
|
||||
if (dy * dy + dp * dp > threshold_4900) continue;
|
||||
bestD = d;
|
||||
best = element;
|
||||
}
|
||||
if (best) target->dist = bestD;
|
||||
env = ((Jvm *)ctx->jvm)->env;
|
||||
if (target->ref) (*env)->DeleteGlobalRef(env, target->ref);
|
||||
if (!best) {
|
||||
target->ref = 0;
|
||||
return true;
|
||||
}
|
||||
jobject r = (*env)->NewGlobalRef(env, (jobject)best);
|
||||
if (!r) return false;
|
||||
target->ref = r;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (target->ref) (*env)->DeleteGlobalRef(env, target->ref);
|
||||
jobject r = (*env)->NewGlobalRef(env, (jobject)ent);
|
||||
if (r) target->ref = r;
|
||||
AB bb = aabb_expand(java_get_axis_aligned_bb_min_x(ctx->jvm, java_get_entity_bounding_box(ctx->jvm, ent)), tenth_in_rotation_and_hotbar_ticks);
|
||||
target->dist = vec3_distance_sq(eye, aabb_clamp_point(bb, eye));
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
// 0x29000: silent rotation state, mouse step, Minecraft colour codes and GL vertex-array helpers.
|
||||
// cflags: /Ob1 /clang:-fno-slp-vectorize
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jcall.h"
|
||||
|
||||
typedef void (*GlPtrFn)(int size, int type, int stride, const void *p); // glVertexPointer-style
|
||||
typedef void (*GlEnableFn)(int cap);
|
||||
extern "C" GlPtrFn *ptr_to_pGlVertexPointer; // cell -> fn ptr (size,type,stride,ptr)
|
||||
extern "C" GlEnableFn *ptr_to_pGlEnableClientState; // cell -> glEnableClientState
|
||||
extern "C" GlEnableFn *ptr_to_pGlDisableClientState;
|
||||
extern "C" void (**ptr_to_pGlDrawElements)(int, int, int, const void *);
|
||||
extern "C" char gl_array_xyz_float_data[], gl_array_color1_indices[], gl_array_color2_indices[], gl_array_xy_float_data[], gl_array_color3_indices[], gl_array_color4_indices[],
|
||||
gl_array_xy_float2_data[], gl_array_color5_indices[], gl_array_color6_indices[];
|
||||
|
||||
#define GL_VEC(name, tbl, n, cell, ptr) \
|
||||
extern "C" void name() { (*tbl)(n, 0x1406, 0, ptr); (*cell)(0x8074); }
|
||||
#define GL_COL(name, n, size, ptr) \
|
||||
extern "C" void name() { auto f = *ptr_to_pGlDrawElements; asm("" : "+r"(f)); f(n, size, 0x1401, ptr); }
|
||||
|
||||
GL_VEC(gl_array_xyz_float, ptr_to_pGlVertexPointer, 3, ptr_to_pGlEnableClientState, gl_array_xyz_float_data)
|
||||
GL_COL(gl_array_color1, 5, 0xe, gl_array_color1_indices)
|
||||
GL_COL(gl_array_color2, 1, 0x18, gl_array_color2_indices)
|
||||
GL_VEC(gl_array_xy_float, ptr_to_pGlVertexPointer, 2, ptr_to_pGlEnableClientState, gl_array_xy_float_data)
|
||||
GL_COL(gl_array_color3, 5, 4, gl_array_color3_indices)
|
||||
GL_COL(gl_array_color4, 2, 4, gl_array_color4_indices)
|
||||
GL_VEC(gl_array_xy_float2, ptr_to_pGlVertexPointer, 2, ptr_to_pGlEnableClientState, gl_array_xy_float2_data)
|
||||
GL_COL(gl_array_color5, 6, 4, gl_array_color5_indices)
|
||||
GL_COL(gl_array_color6, 2, 4, gl_array_color6_indices)
|
||||
extern "C" void disable_vertex_array_state() { (*ptr_to_pGlDisableClientState)(0x8074); }
|
||||
|
||||
struct Vec3 { double x, y, z; };
|
||||
FN(void *, java_new_vec3_init, (void *jvm, Vec3 v));
|
||||
FN(void *, java_world_ray_trace_blocks, (void *jvm, void *object, void *other_object, void *d, bool e, bool f, bool g));
|
||||
FN(void *, get_game_settings, (void *jvm, void *object));
|
||||
FN(void *, java_get_game_settings_attack_key, (void *jvm, void *game_settings));
|
||||
FN(int, get_event_key, (void *jvm, void *object));
|
||||
FN(Vec3, vec3_make, (double x, double y, double z));
|
||||
|
||||
extern "C" bool set_vertex_pointer(void *jvm, void *object, Vec3 *p, Vec3 *q) {
|
||||
void *from = java_new_vec3_init(jvm, *p);
|
||||
void *s = java_new_vec3_init(jvm, *q);
|
||||
return java_world_ray_trace_blocks(jvm, object, from, s, false, true, true) == nullptr;
|
||||
}
|
||||
|
||||
struct Ctx3 { void *jvm; char padding_08[0x10]; void *minecraft; char padding_20[0x10]; char *key_state_table; };
|
||||
extern "C" bool vertex_pointer_current(Ctx3 *c) {
|
||||
void *game_settings = get_game_settings(c->jvm, c->minecraft);
|
||||
game_settings = java_get_game_settings_attack_key(c->jvm, game_settings);
|
||||
int event_key = get_event_key(c->jvm, game_settings);
|
||||
bool f = event_key == -0x63;
|
||||
|
||||
return c->key_state_table[f + 1] != 0;
|
||||
}
|
||||
|
||||
// ---- rotation state (silent aim / mouse emulation)
|
||||
struct Rot { float yaw, pitch; };
|
||||
FN(Rot, rot_add, (Rot a, Rot b));
|
||||
FN(Rot, rot_make, (float yaw, float pitch));
|
||||
FN(float, wrap_angle, (float a)); // wrap to [-180, 180)
|
||||
FN(void *, get_game_settings, (void *j, void *x));
|
||||
FN(float, java_get_game_settings_mouse_sensitivity, (void *jvm, void *settings)); // mouse sensitivity
|
||||
FN(void, pack_flags, (void *jvm, bool on));
|
||||
FN(Rot, get_entity_rotation, (void *jvm, void *entity));
|
||||
FN(Rot, get_entity_prev_rotation, (void *jvm, void *entity));
|
||||
FN(void, set_entity_rotation, (void *jvm, void *entity, Rot rotation));
|
||||
FN(void, set_entity_prev_rotation, (void *jvm, void *entity, Rot rotation));
|
||||
extern "C" unsigned rotation_priority_peak;
|
||||
extern "C" Rot silent_rotation_offset, saved_entity_rotation, saved_entity_prev_rotation, silent_rotation_current;
|
||||
extern "C" int silent_rotation_age_ticks;
|
||||
extern "C" float mouse_sensitivity_step;
|
||||
extern "C" unsigned char rotation_reset_requested, rotation_reset_done, silent_rotation_active_flag, render_rotation_dirty;
|
||||
extern "C" Rot render_rotation;
|
||||
extern "C" const float factor_0_6_in_silent_rotation_and_chat_colors, offset_0_2, factor_8_in_silent_rotation_and_chat_colors, factor_0_15_in_silent_rotation_and_chat_colors;
|
||||
|
||||
extern "C" bool update_rotation_priority_peak(unsigned v) {
|
||||
unsigned old = rotation_priority_peak;
|
||||
if (v >= old) rotation_priority_peak = v;
|
||||
return v >= old;
|
||||
}
|
||||
extern "C" void apply_silent_rotation(Rot rotation) {
|
||||
silent_rotation_offset = rot_add(silent_rotation_offset, rotation);
|
||||
silent_rotation_age_ticks = 0;
|
||||
}
|
||||
extern "C" Rot get_silent_rotation_offset() { return silent_rotation_offset; }
|
||||
// per-tick: recompute the mouse step from the sensitivity (f = s*0.6+0.2; step = f^3*8*0.15)
|
||||
extern "C" void update_mouse_sensitivity_step(void *jvm, void *object) {
|
||||
float f = java_get_game_settings_mouse_sensitivity(jvm, get_game_settings(jvm, object)) * factor_0_6_in_silent_rotation_and_chat_colors + offset_0_2;
|
||||
mouse_sensitivity_step = f * f * f * factor_8_in_silent_rotation_and_chat_colors * factor_0_15_in_silent_rotation_and_chat_colors;
|
||||
if (silent_rotation_age_ticks++ > 0) silent_rotation_offset = rot_make(0.0f, 0.0f);
|
||||
rotation_priority_peak = 0;
|
||||
}
|
||||
extern "C" void request_rotation_reset_if_flagged(void **j) {
|
||||
if (rotation_reset_requested == 1) {
|
||||
pack_flags(*j, true);
|
||||
rotation_reset_done = 1;
|
||||
}
|
||||
}
|
||||
extern "C" void set_silent_rotation_active(void *jvm, void *object, bool save) {
|
||||
Rot *a, *b;
|
||||
if (save) {
|
||||
saved_entity_rotation = get_entity_rotation(jvm, object);
|
||||
saved_entity_prev_rotation = get_entity_prev_rotation(jvm, object);
|
||||
a = b = &silent_rotation_current;
|
||||
} else {
|
||||
b = &saved_entity_prev_rotation;
|
||||
a = &saved_entity_rotation;
|
||||
}
|
||||
set_entity_rotation(jvm, object, *a);
|
||||
set_entity_prev_rotation(jvm, object, *b);
|
||||
}
|
||||
extern "C" unsigned char is_silent_rotation_active() { return silent_rotation_active_flag; }
|
||||
extern "C" Rot get_silent_rotation() { return silent_rotation_current; }
|
||||
extern "C" void set_render_rotation(Rot rotation) {
|
||||
float d = wrap_angle(rotation.yaw - silent_rotation_current.yaw);
|
||||
render_rotation_dirty = 1;
|
||||
silent_rotation_current.yaw = d + silent_rotation_current.yaw;
|
||||
silent_rotation_current.pitch = rotation.pitch;
|
||||
}
|
||||
extern "C" Rot get_render_rotation() { return render_rotation; }
|
||||
|
||||
// Minecraft formatting colour code ('0'-'9', 'a'-'e', anything else white) -> RGB
|
||||
struct F2 { float a, b; };
|
||||
struct Col { F2 rg; float b; };
|
||||
extern "C" const F2 color_rg_1_1, color_rg_0p33_0p33, color_rg_0p67_0p67, color_rg_1_0p33, color_rg_0p33_1, color_rg_0_0p67, color_rg_1_0p67;
|
||||
extern "C" const float minecraft_colour_dark_component, one_in_silent_rotation_and_chat_colors, minecraft_colour_bright_component; // 0.33, 1.0, 0.67
|
||||
extern "C" Col minecraft_color_code_to_rgb(unsigned short ch) {
|
||||
Col c;
|
||||
F2 z = {0.0f, 0.0f};
|
||||
switch (ch) {
|
||||
case '0': c.rg = z; c.b = 0.0f; break;
|
||||
case '1': c.rg = z; c.b = minecraft_colour_bright_component; break;
|
||||
case '2': c.rg = color_rg_0_0p67; c.b = 0.0f; break;
|
||||
case '3': c.rg = color_rg_0_0p67; c.b = minecraft_colour_bright_component; break;
|
||||
case '4': c.rg.a = minecraft_colour_bright_component; c.rg.b = 0.0f; c.b = 0.0f; break;
|
||||
case '5': c.rg.a = minecraft_colour_bright_component; c.rg.b = 0.0f; c.b = minecraft_colour_bright_component; break;
|
||||
case '6': c.rg = color_rg_1_0p67; c.b = 0.0f; break;
|
||||
case '7': c.rg = color_rg_0p67_0p67; c.b = minecraft_colour_bright_component; break;
|
||||
case '8': c.rg = color_rg_0p33_0p33; c.b = minecraft_colour_dark_component; break;
|
||||
case '9': c.rg = color_rg_0p33_0p33; c.b = one_in_silent_rotation_and_chat_colors; break;
|
||||
case 'a': c.rg = color_rg_0p33_1; c.b = minecraft_colour_dark_component; break;
|
||||
case 'b': c.rg = color_rg_0p33_1; c.b = one_in_silent_rotation_and_chat_colors; break;
|
||||
case 'c': c.rg = color_rg_1_0p33; c.b = minecraft_colour_dark_component; break;
|
||||
case 'd': c.rg = color_rg_1_0p33; c.b = one_in_silent_rotation_and_chat_colors; break;
|
||||
case 'e': c.rg = color_rg_1_1; c.b = minecraft_colour_dark_component; break;
|
||||
default: c.rg = color_rg_1_1; c.b = one_in_silent_rotation_and_chat_colors; break;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// ---- chat output
|
||||
FN(void *, get_minecraft, (void *jvm));
|
||||
FN(void *, get_local_player, (void *jvm, void *minecraft));
|
||||
FN(int, libc_vsnprintf, (char *buf, unsigned long long n, const char *fmt, char *va)); // vsnprintf
|
||||
FN(void *, java_new_chat_component_text_init, (void *jvm, void *str)); // new ChatComponentText
|
||||
FN(void, java_call_entity_player_sp_add_chat_message, (void *jvm, void *entity_player_sp, void *msg));
|
||||
extern "C" char log_message_buffer[0x100];
|
||||
extern "C" void jni_throw_format(Jvm *jvm, const char *fmt, ...) {
|
||||
void *minecraft = get_minecraft(jvm);
|
||||
void *player = get_local_player(jvm, minecraft);
|
||||
if (!player) return;
|
||||
__builtin_va_list va;
|
||||
__builtin_va_start(va, fmt);
|
||||
libc_vsnprintf(log_message_buffer, 0x100, fmt, va);
|
||||
JNIEnv *env = jvm->env;
|
||||
jstring chat_component_text = (*env)->NewStringUTF(env, log_message_buffer);
|
||||
if (!chat_component_text) return;
|
||||
void *t = java_new_chat_component_text_init(jvm, chat_component_text);
|
||||
if (!t) return;
|
||||
java_call_entity_player_sp_add_chat_message(jvm, player, t);
|
||||
}
|
||||
|
||||
// ---- movement helpers
|
||||
FN(float, libc_sinf, (float x)); // sinf
|
||||
FN(float, libc_cosf, (float x)); // cosf
|
||||
extern "C" const float movement_input_friction, threshold_0_0001, one_in_silent_rotation_and_chat_colors_a, threshold_1_in_silent_rotation_and_chat_colors_b;
|
||||
extern "C" const double pi_in_silent_rotation_and_chat_colors, half_turn_degrees_in_silent_rotation_and_chat_colors;
|
||||
// horizontal motion for (forward, strafe) input at the given yaw (EntityLivingBase.moveFlying)
|
||||
extern "C" Vec3 horizontal_motion(Rot rotation, float fwd, float strafe, float speed) {
|
||||
fwd *= movement_input_friction;
|
||||
strafe *= movement_input_friction;
|
||||
float m = strafe * strafe + fwd * fwd;
|
||||
if (threshold_0_0001 > m) {
|
||||
Vec3 z = vec3_make(0.0, 0.0, 0.0);
|
||||
return z;
|
||||
}
|
||||
float d = __builtin_sqrtf(m);
|
||||
float k = speed * one_in_silent_rotation_and_chat_colors_a / (threshold_1_in_silent_rotation_and_chat_colors_b > d ? threshold_1_in_silent_rotation_and_chat_colors_b : d);
|
||||
strafe *= k;
|
||||
fwd *= k;
|
||||
float a = (float)((double)rotation.yaw * pi_in_silent_rotation_and_chat_colors / half_turn_degrees_in_silent_rotation_and_chat_colors);
|
||||
float sn = libc_sinf(a);
|
||||
float cs = libc_cosf(a);
|
||||
Vec3 v;
|
||||
float t = fwd * sn;
|
||||
|
||||
v.x = (double)(strafe * cs - t);
|
||||
v.y = 0.0;
|
||||
v.z = (double)(fwd * cs + strafe * sn);
|
||||
return v;
|
||||
}
|
||||
|
||||
// ---- world -> screen projection with the captured GL matrices
|
||||
struct View { char padding_00[0x20]; float w, h; const float *mv, *proj; };
|
||||
extern "C" const float one_in_silent_rotation_and_chat_colors_c, offset_0_5_in_silent_rotation_and_chat_colors; // 1.0, 0.5
|
||||
extern "C" bool project_point_ex(View *v, Vec3 p, F2 *out, float *depth) {
|
||||
float x = (float)p.x, y = (float)p.y, z = (float)p.z;
|
||||
const float *m = v->mv, *proj = v->proj;
|
||||
float e0 = m[0] * x + m[4] * y + m[8] * z + m[12];
|
||||
float e1 = m[1] * x + m[5] * y + m[9] * z + m[13];
|
||||
float e2 = m[2] * x + m[6] * y + m[10] * z + m[14];
|
||||
float e3 = m[3] * x + m[7] * y + m[11] * z + m[15];
|
||||
float w = proj[3] * e0 + proj[7] * e1 + proj[11] * e2 + proj[15] * e3;
|
||||
if (w == 0.0f) return false;
|
||||
float k = one_in_silent_rotation_and_chat_colors_c / w * offset_0_5_in_silent_rotation_and_chat_colors;
|
||||
float zz = (proj[2] * e0 + proj[6] * e1 + proj[10] * e2 + proj[14] * e3) * k + offset_0_5_in_silent_rotation_and_chat_colors;
|
||||
if (0.0f >= zz || zz >= one_in_silent_rotation_and_chat_colors_c) return false;
|
||||
float yy = (proj[1] * e0 + proj[5] * e1 + proj[9] * e2 + proj[13] * e3) * k + offset_0_5_in_silent_rotation_and_chat_colors;
|
||||
float xx = (proj[0] * e0 + proj[4] * e1 + proj[8] * e2 + proj[12] * e3) * k + offset_0_5_in_silent_rotation_and_chat_colors;
|
||||
out->a = xx * v->w;
|
||||
out->b = v->h - yy * v->h;
|
||||
if (depth) *depth = k;
|
||||
return true;
|
||||
}
|
||||
extern "C" bool project_point(View *v, Vec3 *p, F2 *out) { return project_point_ex(v, *p, out, nullptr); }
|
||||
|
||||
// ---- apply the pending mouse delta in whole sensitivity steps
|
||||
FN(float, libc_truncf, (float v)); // truncf
|
||||
FN(void, java_entity_player_sp_render_arm_pitch, (void *jvm, void *object, Rot rotation));
|
||||
FN(void, java_entity_player_sp_prev_render_arm_pitch, (void *jvm, void *object, Rot rotation));
|
||||
typedef int v4i __attribute__((vector_size(16), aligned(16)));
|
||||
typedef float v4f __attribute__((vector_size(16), aligned(16)));
|
||||
extern "C" const v4i gl_vertex_state_sign_mask; // sign mask
|
||||
extern "C" void set_rotation_active(void *jvm, void *object, bool render) {
|
||||
if (!object) return;
|
||||
float step = mouse_sensitivity_step;
|
||||
if (step == 0.0f) return;
|
||||
float neg = -step;
|
||||
float dx = 0.0f, dy = 0.0f;
|
||||
float yaw = silent_rotation_offset.yaw;
|
||||
if (neg >= yaw || yaw >= step) dx = libc_truncf(yaw / step) * step;
|
||||
silent_rotation_offset.yaw = yaw - dx;
|
||||
float pitch = silent_rotation_offset.pitch;
|
||||
if (neg >= pitch || pitch >= step) dy = libc_truncf(pitch / step) * step;
|
||||
silent_rotation_offset.pitch = pitch - dy;
|
||||
if (dx == 0.0f && dy == 0.0f) return;
|
||||
Rot rotation = rot_add(get_entity_rotation(jvm, object), Rot{dx, dy});
|
||||
set_entity_rotation(jvm, object, rotation);
|
||||
set_entity_prev_rotation(jvm, object, rotation);
|
||||
if (!render) return;
|
||||
java_entity_player_sp_prev_render_arm_pitch(jvm, object, rotation);
|
||||
java_entity_player_sp_render_arm_pitch(jvm, object, rotation);
|
||||
}
|
||||
|
||||
// ---- movement fix: remap the pressed WASD keys to the server-side yaw
|
||||
FN(bool, is_in_game, (void *jvm, void *minecraft));
|
||||
FN(void *, java_get_game_settings_forward_key, (void *jvm, void *game_settings)); // keyBindForward
|
||||
FN(void *, java_get_game_settings_back_key, (void *jvm, void *game_settings)); // keyBindBack
|
||||
FN(void *, java_get_game_settings_left_key, (void *jvm, void *game_settings)); // keyBindLeft
|
||||
FN(void *, java_get_game_settings_right_key, (void *jvm, void *game_settings)); // keyBindRight
|
||||
FN(bool, is_key_down, (void *jvm, void *kb)); // pressed
|
||||
FN(void, java_set_key_binding_pressed, (void *jvm, void *kb, bool pressed));
|
||||
FN(float, libc_roundf, (float v)); // roundf
|
||||
extern "C" unsigned char gl_vertex_state_captured_key_bits_fwd_back; // captured key bits: fwd, back, left, right
|
||||
extern "C" const float divisor_45; // 45
|
||||
extern "C" const unsigned short gl_vertex_state_octant_bytes[8]; // octant -> (fwd, strafe) bytes
|
||||
extern "C" void pack_flags(void *j, bool apply) {
|
||||
void *minecraft = get_minecraft(j);
|
||||
if (!is_in_game(j, minecraft)) return;
|
||||
void *game_settings = get_game_settings(j, minecraft);
|
||||
void *game_settings_key_bindings = java_get_game_settings_forward_key(j, game_settings), *key_binding = java_get_game_settings_back_key(j, game_settings), *kl = java_get_game_settings_left_key(j, game_settings), *kr = java_get_game_settings_right_key(j, game_settings);
|
||||
if (!apply) {
|
||||
java_set_key_binding_pressed(j, game_settings_key_bindings, gl_vertex_state_captured_key_bits_fwd_back & 1);
|
||||
java_set_key_binding_pressed(j, key_binding, (gl_vertex_state_captured_key_bits_fwd_back >> 1) & 1);
|
||||
java_set_key_binding_pressed(j, kl, (gl_vertex_state_captured_key_bits_fwd_back >> 2) & 1);
|
||||
java_set_key_binding_pressed(j, kr, (gl_vertex_state_captured_key_bits_fwd_back >> 3) & 1);
|
||||
return;
|
||||
}
|
||||
unsigned char a = is_key_down(j, game_settings_key_bindings);
|
||||
unsigned char b = is_key_down(j, key_binding);
|
||||
unsigned char bits = (unsigned char)(b + b) | a;
|
||||
unsigned char c = is_key_down(j, kl);
|
||||
bits = (unsigned char)(c << 2) | bits;
|
||||
unsigned char d = is_key_down(j, kr);
|
||||
gl_vertex_state_captured_key_bits_fwd_back = (unsigned char)(d << 3) | bits;
|
||||
void *player = get_local_player(j, minecraft);
|
||||
float delta = wrap_angle(get_entity_rotation(j, player).yaw - render_rotation.yaw);
|
||||
unsigned f = gl_vertex_state_captured_key_bits_fwd_back;
|
||||
signed char fwd = (signed char)((f & 1) + ((signed char)(f << 6) >> 7));
|
||||
signed char str = (signed char)(((signed char)(f << 4) >> 7) + ((f >> 2) & 1));
|
||||
unsigned short mv;
|
||||
if (fwd == 0 && str == 0) {
|
||||
mv = 0;
|
||||
} else {
|
||||
int dir;
|
||||
if (fwd == 0) dir = str == 1 ? 2 : (str == -1 ? 6 : 0);
|
||||
else if (fwd == -1) dir = 5 - (unsigned char)(str + 1);
|
||||
else if (str == 0) dir = 0;
|
||||
else dir = str == 1 ? 1 : (str == -1 ? 7 : 0);
|
||||
mv = gl_vertex_state_octant_bytes[((int)libc_roundf(delta / divisor_45) + dir) & 7];
|
||||
}
|
||||
unsigned hi = mv >> 8;
|
||||
java_set_key_binding_pressed(j, game_settings_key_bindings, (mv & 0xff) == 1);
|
||||
java_set_key_binding_pressed(j, key_binding, (mv & 0xff) == 0xff);
|
||||
java_set_key_binding_pressed(j, kl, hi == 1);
|
||||
java_set_key_binding_pressed(j, kr, hi == 0xff);
|
||||
}
|
||||
|
||||
// ---- silent-rotation controller: start (save real rotation) / stop (restore smoothly)
|
||||
extern "C" unsigned char use_target_rotation;
|
||||
extern "C" void apply_player_rotation(Ctx *ctx, bool on, unsigned char moveFix, unsigned char useTarget, Rot target) {
|
||||
if (rotation_reset_done == 1) {
|
||||
pack_flags(ctx->jvm, false);
|
||||
rotation_reset_done = 0;
|
||||
rotation_reset_requested = 0;
|
||||
}
|
||||
if (on) {
|
||||
if (!(silent_rotation_active_flag & 1) && ctx->player) {
|
||||
silent_rotation_active_flag = 1;
|
||||
silent_rotation_current = get_entity_rotation(ctx->jvm, ctx->player);
|
||||
}
|
||||
use_target_rotation = useTarget;
|
||||
render_rotation = target;
|
||||
rotation_reset_requested = moveFix;
|
||||
return;
|
||||
}
|
||||
if (!(silent_rotation_active_flag & 1)) return;
|
||||
silent_rotation_active_flag = 0;
|
||||
Rot *src = use_target_rotation ? &render_rotation : &silent_rotation_current;
|
||||
float yaw = src->yaw, pitch = src->pitch;
|
||||
float cur = get_entity_rotation(ctx->jvm, ctx->player).yaw;
|
||||
Rot rotation = {wrap_angle(yaw - cur) + cur, pitch};
|
||||
set_entity_rotation(ctx->jvm, ctx->player, rotation);
|
||||
set_entity_prev_rotation(ctx->jvm, ctx->player, rotation);
|
||||
java_entity_player_sp_prev_render_arm_pitch(ctx->jvm, ctx->player, rotation);
|
||||
java_entity_player_sp_render_arm_pitch(ctx->jvm, ctx->player, rotation);
|
||||
}
|
||||
|
||||
// wrap an angle into range when it lies outside (-limit, limit); limit = mouse_sensitivity_step
|
||||
FN(float, libc_truncf, (float x)); // floorf
|
||||
extern "C" void clamp_angle(float *angle) {
|
||||
float v = *angle, limit = mouse_sensitivity_step;
|
||||
float wrap = 0.0f;
|
||||
if (v <= -limit || v >= limit) wrap = libc_truncf(v / limit) * limit;
|
||||
*angle = v - wrap;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 0x97c70: 1D gradient noise (Perlin style) on a 256-entry permutation table; used by the aim assist's humanising jitter.
|
||||
// Library code of the image (just past the NanoVG range); translated from the listing, not byte-matched.
|
||||
extern "C" const unsigned char noise_permutation_table[256]; // permutation table
|
||||
extern "C" const float offset_neg_1_in_noise, factor_6_in_noise, offset_neg_15_in_noise, offset_10_in_noise, factor_0_188; // -1, 6, -15, 10, output scale
|
||||
|
||||
// gradient at lattice point with hash h: magnitude 1..8, sign from bit 3, times the distance x
|
||||
static inline float grad(unsigned char h, float x) {
|
||||
float g = (float)((h & 7) + 1);
|
||||
if (h & 8) g = -g;
|
||||
return g * x;
|
||||
}
|
||||
|
||||
extern "C" float noise_1d(float x) {
|
||||
int i = (int)x;
|
||||
if ((float)i > x) i--; // floor
|
||||
float f = x - (float)i;
|
||||
float fade = f * f * f * ((factor_6_in_noise * f + offset_neg_15_in_noise) * f + offset_10_in_noise); // 6f^5 - 15f^4 + 10f^3
|
||||
float g0 = grad(noise_permutation_table[i & 255], f);
|
||||
float g1 = grad(noise_permutation_table[(i + 1) & 255], f + offset_neg_1_in_noise);
|
||||
return (fade * (g1 - g0) + g0) * factor_0_188;
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
// 0xa000: Rot / Rect / float helpers, random numbers
|
||||
#include "slinky/engine.h"
|
||||
struct Rot { float yaw, pitch; };
|
||||
struct Rect { float x, y, w, h; };
|
||||
FN(float, libc_roundf, (float x));
|
||||
FN(float, libc_fmodf, (float x, float y));
|
||||
FN(double, time_now, ());
|
||||
extern "C" float full_turn_degrees, half_turn_degrees_b, offset_neg_360, threshold_neg_180, threshold_1_in_math_helpers_a, three_constant;
|
||||
extern "C" double current_time_seconds;
|
||||
|
||||
extern "C" __attribute__((noinline)) Rot rot_make_floats(float a, float b) { return Rot{a, b}; }
|
||||
extern "C" Rect make_rect(float x, float y, float w, float h) { return Rect{x, y, w, h}; }
|
||||
extern "C" bool point_in_rect(Rect *r, Rot p) {
|
||||
return p.yaw >= r->x && p.pitch >= r->y && r->x + r->w > p.yaw && r->y + r->h > p.pitch;
|
||||
}
|
||||
extern "C" bool rects_intersect(Rect *a, Rect *b) {
|
||||
if (a->x > b->x + b->w || b->x > a->x + a->w) return false;
|
||||
if (a->y > b->y + b->h) return false;
|
||||
return !(b->y > a->y + a->h);
|
||||
}
|
||||
typedef float v2f __attribute__((ext_vector_type(2)));
|
||||
extern "C" Rect inset_rect(Rect r, float d) {
|
||||
v2f pos = {r.x, r.y}, size = {r.w, r.h};
|
||||
v2f dd = {d, d};
|
||||
float d2 = d + d;
|
||||
v2f d22 = {d2, d2};
|
||||
pos += dd;
|
||||
size -= d22;
|
||||
return Rect{pos.x, pos.y, size.x, size.y};
|
||||
}
|
||||
extern "C" Rect round_rect(Rect *r) {
|
||||
float x0 = r->x, y0 = r->y;
|
||||
float x = libc_roundf(x0), y = libc_roundf(y0), w = libc_roundf(r->w), h = libc_roundf(r->h);
|
||||
return Rect{x, y, w, h};
|
||||
}
|
||||
extern "C" Rot offset_rect(Rot *p) { return *p; }
|
||||
extern "C" float limit_rotation_delta(float x, float a, float b) { return (b < x) ? x : (a < b ? a : b); }
|
||||
extern "C" float wrap_angle(float x) {
|
||||
float r = libc_fmodf(x, full_turn_degrees);
|
||||
if (r >= half_turn_degrees_b) return r + offset_neg_360;
|
||||
if (threshold_neg_180 > r) { r += full_turn_degrees; }
|
||||
return r;
|
||||
}
|
||||
extern "C" float lerp_clamped_float(float a, float b, float t) {
|
||||
float m = threshold_1_in_math_helpers_a < t ? threshold_1_in_math_helpers_a : t;
|
||||
return a + m * (b - a);
|
||||
}
|
||||
extern "C" float ease_curve(float t) { float sq = t * t; float d = t + t; return (three_constant - d) * sq; }
|
||||
extern "C" float lerp_unclamped_float(float a, float b, float t) { return a + (b - a) * t; }
|
||||
extern "C" double lerp_double(double a, double b, double t) { return a + (b - a) * t; }
|
||||
extern "C" int chunk_coordinate(int x) { return x >> 4; }
|
||||
extern "C" int block_coordinate(int x) { return x << 4; }
|
||||
extern "C" void update_frame_time() { current_time_seconds = time_now(); }
|
||||
|
||||
// ---- xoshiro128+ random numbers ----
|
||||
extern "C" unsigned init_table[4];
|
||||
extern "C" const float float_epsilon_unused_a, one_in_math_helpers_b, float_epsilon_unused_b, random_range_bound_1_in_math_helpers_c, random_walk_range_epsilon_a, random_range_bound_1_in_math_helpers_d, random_walk_range_epsilon_b;
|
||||
extern "C" const double threshold_0_2, threshold_0_2_in_math_helpers;
|
||||
typedef int v4i __attribute__((vector_size(16)));
|
||||
typedef float v4f __attribute__((vector_size(16)));
|
||||
extern "C" const v4i math_helpers_fabs_masks, math_helpers_fabs_masks_second; // fabs masks
|
||||
static inline __attribute__((always_inline)) unsigned rng_next() {
|
||||
unsigned *s = init_table;
|
||||
unsigned result = s[0] + s[3];
|
||||
unsigned t = s[1] << 9;
|
||||
s[2] ^= s[0];
|
||||
s[3] ^= s[1];
|
||||
s[1] ^= s[2];
|
||||
s[0] ^= s[3];
|
||||
s[2] ^= t;
|
||||
s[3] = (s[3] << 11) | (s[3] >> 21);
|
||||
return result;
|
||||
}
|
||||
static inline __attribute__((always_inline)) float rng_range(Rot rotation, float scale) {
|
||||
float d = rotation.pitch - rotation.yaw;
|
||||
return (float)(rng_next() >> 8) * scale * d + rotation.yaw;
|
||||
}
|
||||
static inline __attribute__((always_inline)) float fabs_m(float x, v4i m) {
|
||||
v4f v = {x, 0, 0, 0};
|
||||
return ((v4f)((v4i)v & m))[0];
|
||||
}
|
||||
extern "C" float random_rotation(Rot rotation) { return rng_range(rotation, float_epsilon_unused_a); }
|
||||
extern "C" bool random_chance(float p) { return p > rng_range(rot_make_floats(0.0f, one_in_math_helpers_b), float_epsilon_unused_b); }
|
||||
|
||||
struct Walk { double t0; float a, b; };
|
||||
extern "C" __attribute__((noinline)) void walk_randomize(Walk *w, bool both) {
|
||||
do {
|
||||
if (both) w->a = rng_range(rot_make_floats(0.0f, random_range_bound_1_in_math_helpers_d), random_walk_range_epsilon_b);
|
||||
w->b = rng_range(rot_make_floats(0.0f, random_range_bound_1_in_math_helpers_d), random_walk_range_epsilon_b);
|
||||
} while (threshold_0_2_in_math_helpers > (double)fabs_m(w->a - w->b, math_helpers_fabs_masks_second));
|
||||
}
|
||||
// smooth random value in [r.yaw, r.pitch], re-targeted every `period` seconds
|
||||
extern "C" __attribute__((noinline)) float walk_update(Walk *w, Rot rotation, double period) {
|
||||
if (current_time_seconds > w->t0 + period) {
|
||||
w->t0 = current_time_seconds;
|
||||
if (w->a == 0.0f && w->b == 0.0f) {
|
||||
walk_randomize(w, true);
|
||||
} else {
|
||||
w->a = w->b;
|
||||
do {
|
||||
w->b = rng_range(rot_make_floats(0.0f, random_range_bound_1_in_math_helpers_c), random_walk_range_epsilon_a);
|
||||
} while (threshold_0_2 > (double)fabs_m(w->a - w->b, math_helpers_fabs_masks));
|
||||
}
|
||||
}
|
||||
double t = (current_time_seconds - w->t0) / period;
|
||||
double u = (double)(w->b - w->a) * t + (double)w->a;
|
||||
return (float)((double)(rotation.pitch - rotation.yaw) * u + (double)rotation.yaw);
|
||||
}
|
||||
extern "C" const float random_range_bound_1_in_math_helpers_e, random_walk_range_epsilon_c, random_range_bound_1_in_math_helpers_f, random_walk_range_epsilon_d;
|
||||
extern "C" const double threshold_0_2_in_math_helpers_a;
|
||||
extern "C" const v4i abs_mask;
|
||||
// same walk driven by an accumulated time step instead of the clock
|
||||
extern "C" float walk_update_timed(Walk *w, Rot rotation, double period, double dt) {
|
||||
w->t0 += dt;
|
||||
if (w->t0 > period) {
|
||||
w->t0 = 0.0;
|
||||
if (w->a == 0.0f && w->b == 0.0f) {
|
||||
walk_randomize(w, true);
|
||||
} else {
|
||||
w->a = w->b;
|
||||
do {
|
||||
w->b = rng_range(rot_make_floats(0.0f, random_range_bound_1_in_math_helpers_e), random_walk_range_epsilon_c);
|
||||
} while (threshold_0_2_in_math_helpers_a > (double)fabs_m(w->a - w->b, abs_mask));
|
||||
}
|
||||
}
|
||||
double t = w->t0 / period;
|
||||
double u = (double)(w->b - w->a) * t + (double)w->a;
|
||||
return (float)((double)(rotation.pitch - rotation.yaw) * u + (double)rotation.yaw);
|
||||
}
|
||||
extern "C" bool walk_should_change(Walk *w, Rot rotation, double period) {
|
||||
float x = rng_range(rot_make_floats(0.0f, random_range_bound_1_in_math_helpers_f), random_walk_range_epsilon_d);
|
||||
return walk_update(w, rotation, period) > x;
|
||||
}
|
||||
|
||||
// Gaussian random number (Marsaglia polar method, second value cached)
|
||||
FN(float, libc_hypotf_alt, (float a, float b));
|
||||
FN(float, libc_logf, (float x)); // logf
|
||||
FN(float, libc_sqrtf, (float x)); // sqrtf (errno path)
|
||||
extern "C" const float factor_5_96046eneg_08_in_math_helpers_e, offset_neg_1, threshold_1_in_math_helpers, minus_two;
|
||||
struct Gauss { float spare; bool has; };
|
||||
extern "C" float gauss_random(Gauss *g) {
|
||||
if (g->has) {
|
||||
g->has = false;
|
||||
return g->spare;
|
||||
}
|
||||
float u, v, s;
|
||||
do {
|
||||
u = (float)(rng_next() >> 8) * factor_5_96046eneg_08_in_math_helpers_e;
|
||||
u = u + u + offset_neg_1;
|
||||
v = (float)(rng_next() >> 8) * factor_5_96046eneg_08_in_math_helpers_e;
|
||||
v = v + v + offset_neg_1;
|
||||
s = libc_hypotf_alt(u, v);
|
||||
} while (s >= threshold_1_in_math_helpers || s == 0.0f);
|
||||
float x = libc_logf(s) * minus_two / s;
|
||||
float m = x >= 0.0f ? __builtin_elementwise_sqrt(x) : libc_sqrtf(x);
|
||||
g->spare = v * m;
|
||||
g->has = true;
|
||||
return u * m;
|
||||
}
|
||||
|
||||
// define (or find) a class in `loader` and register its native methods
|
||||
#include "slinky/jnienv.h"
|
||||
FN(jclass, find_class, (jobject loader, JNIEnv *env, const char *name));
|
||||
FN(jobject, class_loader_ref, (jobject loader));
|
||||
extern "C" int define_class(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len,
|
||||
const JNINativeMethod *methods, jclass *out) {
|
||||
jclass cls = find_class(loader, env, name);
|
||||
if (!cls) {
|
||||
(*env)->ExceptionClear(env);
|
||||
auto define = (*env)->DefineClass;
|
||||
|
||||
cls = define(env, name, class_loader_ref(loader), buf, len);
|
||||
if (!cls) return 0x7d6;
|
||||
}
|
||||
if ((*env)->RegisterNatives(env, cls, methods, 1) != 0) return 0x7d1;
|
||||
*out = cls;
|
||||
return 0;
|
||||
}
|
||||
extern "C" int define_class_with_natives(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len,
|
||||
const JNINativeMethod *methods, jint n, jclass *out) {
|
||||
jclass cls = find_class(loader, env, name);
|
||||
if (!cls) {
|
||||
(*env)->ExceptionClear(env);
|
||||
auto define = (*env)->DefineClass;
|
||||
|
||||
cls = define(env, name, class_loader_ref(loader), buf, len);
|
||||
if (!cls) return 0x7d6;
|
||||
}
|
||||
if ((*env)->RegisterNatives(env, cls, methods, n) != 0) return 0x7d1;
|
||||
*out = cls;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// one xoshiro128+ step of the shared generator (out-of-line copy of rng_next)
|
||||
extern "C" unsigned xoshiro128_step_shared_generator() { return rng_next(); }
|
||||
|
||||
// ---- JavaVM lookup: JNI_GetCreatedJavaVMs from the already loaded "jvm" module (one VM) ----
|
||||
// 0x3e8: no jvm module, 0x3e9: no such export, 0x7d2: the call failed
|
||||
extern "C" const char str_jvm[], str_jni_getcreatedjavavms[]; // "jvm", "JNI_GetCreatedJavaVMs"
|
||||
FN(void *, kernel32_lookups, (const char *module));
|
||||
FN(void *, get_proc_address, (void *module, const char *name));
|
||||
typedef int (*GetCreatedJavaVMs)(void **vms, int n, int *count);
|
||||
extern "C" int get_java_vm(void **vm) {
|
||||
int err;
|
||||
if (!vm) return 0x7d2;
|
||||
*vm = nullptr;
|
||||
void *jvm = kernel32_lookups(str_jvm);
|
||||
if (!jvm) {
|
||||
err = 0x3e8;
|
||||
} else {
|
||||
GetCreatedJavaVMs get = (GetCreatedJavaVMs)get_proc_address(jvm, str_jni_getcreatedjavavms);
|
||||
if (!get) {
|
||||
err = 0x3e9;
|
||||
} else {
|
||||
int count = 0;
|
||||
void *created = nullptr;
|
||||
int result = get(&created, 1, &count);
|
||||
if (result == 0 && count >= 1 && created) {
|
||||
*vm = created;
|
||||
err = 0;
|
||||
} else {
|
||||
err = 0x7d2; // call failed or no VM has been created yet
|
||||
}
|
||||
}
|
||||
}
|
||||
SHARED_EPILOG();
|
||||
return err;
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// 0xb000: settings profile load/save (cJSON), saved-string helper
|
||||
#include "slinky/engine.h"
|
||||
struct cJSON;
|
||||
FN(cJSON *, cjson_parse, (const char *text)); // cJSON_Parse
|
||||
FN(cJSON *, cjson_get_object_item, (const cJSON *object, const char *name)); // cJSON_GetObjectItemCaseSensitive
|
||||
FN(int, cjson_is_array, (const cJSON *object)); // cJSON_IsArray
|
||||
FN(char *, cjson_get_string_value, (const cJSON *object)); // cJSON_GetStringValue
|
||||
FN(int, cjson_is_number, (const cJSON *object)); // cJSON_IsNumber
|
||||
FN(void, cjson_delete, (cJSON *object)); // cJSON_Delete
|
||||
FN(void, cjson_free_string, (void *p)); // cJSON_free
|
||||
struct JNode { JNode *next, *prev, *child; int type; char *vs; int vi; double vd; char *key; };
|
||||
struct Mod { char padding_00[0x18]; unsigned char flags; };
|
||||
struct Mods { Mod **data; unsigned long long count; };
|
||||
FN(unsigned char, load_module_settings, (cJSON *root, Mods *mods, Mod *module));
|
||||
FN(void *, find_profile, (void *ctx, const char *name));
|
||||
typedef void (*SetBind)(void *mod, int key);
|
||||
FN(SetBind, profile_set_bind, (void *ctx));
|
||||
extern "C" const char str_profile_binds[], str_name[], str_bind[], str_slinky_in_profiles[], str_settings[];
|
||||
extern "C" const double offset_0_5;
|
||||
|
||||
extern "C" bool load_module_states_from_text(const char *text, Mods *mods, void *ctx) {
|
||||
cJSON *root = cjson_parse(text);
|
||||
if (!root) return false;
|
||||
bool ok;
|
||||
for (unsigned long long i = 0; i < mods->count; i++) {
|
||||
Mod *module = mods->data[i];
|
||||
if (!(module->flags & 0x10)) continue;
|
||||
if (!load_module_settings(root, mods, module)) { ok = false; goto out; }
|
||||
}
|
||||
{
|
||||
cJSON *binds = cjson_get_object_item(root, str_profile_binds);
|
||||
unsigned char notarr = !cjson_is_array(binds);
|
||||
|
||||
unsigned char nul = binds == 0;
|
||||
|
||||
ok = true;
|
||||
if (nul | notarr) goto out;
|
||||
for (JNode *child = ((JNode *)binds)->child; child; child = child->next) {
|
||||
cJSON *json = cjson_get_object_item((cJSON *)child, str_name);
|
||||
if (!json) continue;
|
||||
char *name = cjson_get_string_value(json);
|
||||
if (!name) continue;
|
||||
void *mod = find_profile(ctx, name);
|
||||
if (!mod) continue;
|
||||
JNode *item = (JNode *)cjson_get_object_item((cJSON *)child, str_bind);
|
||||
int key = 0;
|
||||
if (cjson_is_number((cJSON *)item)) {
|
||||
int v = (int)(item->vd + offset_0_5);
|
||||
key = 0;
|
||||
if ((unsigned)(v - 0x68) >= (unsigned)-0x67) { key = v; }
|
||||
}
|
||||
profile_set_bind(ctx)(mod, key);
|
||||
}
|
||||
}
|
||||
out:
|
||||
cjson_delete(root);
|
||||
return ok;
|
||||
}
|
||||
|
||||
FN(int, settings_path, (char **path));
|
||||
FN(int, settings_path_append, (char **path, const char *suffix));
|
||||
FN(int, settings_path_ensure, (char **path));
|
||||
FN(void, free_path, (char **path));
|
||||
FN(int, write_text_file, (char **path, const char *data, bool text));
|
||||
FN(char *, serialize_settings, (Mods *mods, void *ctx));
|
||||
extern "C" int save_settings(void *module_manager, void *profile_list) {
|
||||
char *path;
|
||||
int error_code = settings_path(&path);
|
||||
if (error_code) return error_code;
|
||||
error_code = settings_path_append(&path, str_slinky_in_profiles);
|
||||
if (!error_code) error_code = settings_path_ensure(&path);
|
||||
if (!error_code) error_code = settings_path_append(&path, str_settings);
|
||||
if (!error_code) {
|
||||
char *json = serialize_settings((Mods *)module_manager, profile_list);
|
||||
if (!json) {
|
||||
error_code = 0x1388;
|
||||
} else {
|
||||
error_code = write_text_file(&path, json, true);
|
||||
cjson_free_string(json);
|
||||
}
|
||||
}
|
||||
free_path(&path);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
extern "C" char *settings_buffer;
|
||||
FN(void *, libc_malloc, (unsigned long long n)); // malloc
|
||||
FN(void *, libc_memcpy, (void *dest, const void *src, unsigned long long n)); // memcpy
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
FN(void, libc_free, (void *ptr));
|
||||
extern "C" void set_saved_string(const char *s) {
|
||||
if (settings_buffer) libc_free(settings_buffer);
|
||||
if (!s) {
|
||||
settings_buffer = 0;
|
||||
return;
|
||||
}
|
||||
unsigned long long n = libc_strlen(s);
|
||||
char *p = (char *)libc_malloc(n + 1);
|
||||
settings_buffer = p;
|
||||
if (!p) return;
|
||||
libc_memcpy(p, s, n);
|
||||
p[n] = 0;
|
||||
}
|
||||
|
||||
FN(cJSON *, cjson_create_object, ()); // cJSON_CreateObject
|
||||
FN(cJSON *, cjson_add_array_to_object, (cJSON *object, const char *name)); // cJSON_AddArrayToObject
|
||||
FN(cJSON *, cjson_add_string_to_object, (cJSON *object, const char *name, const char *s)); // cJSON_AddStringToObject
|
||||
FN(cJSON *, cjson_add_number_to_object, (cJSON *object, const char *name, double v)); // cJSON_AddNumberToObject
|
||||
FN(int, cjson_add_item_to_array, (cJSON *arr, cJSON *item)); // cJSON_AddItemToArray
|
||||
FN(char *, cjson_print_unformatted, (const cJSON *object)); // cJSON_PrintUnformatted
|
||||
FN(unsigned char, save_module_settings, (cJSON *root, Mod *module));
|
||||
struct BindEnt { const char *name; char padding_08[0x30]; unsigned bind; int padding_3c; };
|
||||
FN(BindEnt *, profile_array, (void *ctx, unsigned long long *n));
|
||||
// serialize all savable modules plus the key binds
|
||||
extern "C" char *serialize_settings(Mods *mods, void *ctx) {
|
||||
cJSON *root = cjson_create_object();
|
||||
char *out = 0;
|
||||
if (!root) goto done;
|
||||
for (unsigned long long i = 0; i < mods->count; i++) {
|
||||
Mod *module = mods->data[i];
|
||||
if (!(module->flags & 0x10)) continue;
|
||||
if (!save_module_settings(root, module)) goto done;
|
||||
}
|
||||
{
|
||||
cJSON *binds = cjson_add_array_to_object(root, str_profile_binds);
|
||||
if (!binds) { out = 0; goto done; }
|
||||
unsigned long long n;
|
||||
BindEnt *e = profile_array(ctx, &n);
|
||||
for (unsigned long long i = 0; i < n; i++, e++) {
|
||||
if (e->bind == 0) continue;
|
||||
cJSON *json = cjson_create_object();
|
||||
if (!json) goto done;
|
||||
if (!cjson_add_string_to_object(json, str_name, e->name)) goto done;
|
||||
if (!cjson_add_number_to_object(json, str_bind, (double)e->bind)) goto done;
|
||||
if (!cjson_add_item_to_array(binds, json)) goto done;
|
||||
}
|
||||
out = cjson_print_unformatted(root);
|
||||
}
|
||||
done:
|
||||
cjson_delete(root);
|
||||
return out;
|
||||
}
|
||||
|
||||
FN(int, read_text_file, (char **path, char **data));
|
||||
FN(unsigned char, load_module_states, (const char *data, Mods *mods));
|
||||
FN(void, apply_module_state, (Mods *mods, Mod *module, int v));
|
||||
FN(void, queue_module_toggle, (Mods *module_manager, Mod *module, unsigned char enabled));
|
||||
extern "C" const char str_profiles[];
|
||||
extern "C" unsigned char settings_dirty_flag;
|
||||
struct Pair16 { long long a, b; };
|
||||
extern "C" const Pair16 settings_saved_event;
|
||||
struct ModX { char padding_00[0x18]; unsigned char flags; char padding_19[7]; unsigned char padding_20; char padding_21[0x1df]; void (*register_settings)(Pair16 *); };
|
||||
// load profile `name` (NULL = reset every savable module to its defaults)
|
||||
extern "C" int load_profile_name(const char *name, Mods *mods) {
|
||||
char *data;
|
||||
char *path;
|
||||
Pair16 p;
|
||||
if (!name) {
|
||||
for (unsigned long long i = 0; i < mods->count; i++) {
|
||||
ModX *module = (ModX *)mods->data[i];
|
||||
if (module->flags & 0x10) continue;
|
||||
apply_module_state(mods, (Mod *)module, 0);
|
||||
module->padding_20 = 0;
|
||||
if (!(module->flags & 2)) queue_module_toggle(mods, (Mod *)module, 0);
|
||||
if (module->register_settings) {
|
||||
p = settings_saved_event;
|
||||
module->register_settings(&p);
|
||||
}
|
||||
}
|
||||
if (settings_buffer) libc_free(settings_buffer);
|
||||
settings_buffer = 0;
|
||||
settings_dirty_flag = 0;
|
||||
return 0;
|
||||
}
|
||||
int error_code = settings_path(&path);
|
||||
if (error_code) return error_code;
|
||||
if ((error_code = settings_path_append(&path, str_slinky_in_profiles)) != 0) goto clean;
|
||||
if ((error_code = settings_path_ensure(&path)) != 0) goto clean;
|
||||
if ((error_code = settings_path_append(&path, str_profiles)) != 0) goto clean;
|
||||
if ((error_code = settings_path_ensure(&path)) != 0) goto clean;
|
||||
if ((error_code = settings_path_append(&path, name)) != 0) goto clean;
|
||||
if ((error_code = read_text_file(&path, &data)) != 0) goto clean;
|
||||
{
|
||||
unsigned char ok = load_module_states(data, mods);
|
||||
error_code = 0x1389;
|
||||
if (ok) {
|
||||
set_saved_string(name);
|
||||
settings_dirty_flag = 0;
|
||||
error_code = 0;
|
||||
}
|
||||
}
|
||||
libc_free(data);
|
||||
clean:
|
||||
free_path(&path);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
// ---- run the settings visitor (descriptor +0x200) of every module in "remember" mode ----
|
||||
// Used at start-up to copy the static setting values into each setting's saved slot, which is the
|
||||
// configuration the Default profile restores and the fallback for fields missing from a profile.
|
||||
struct ModList { struct ModCb { char padding_00[0x200]; void (*register_settings)(void *ev); } **data; unsigned long long count; };
|
||||
struct Ev16 { long long a, b; };
|
||||
extern "C" const Ev16 settings_pending_event;
|
||||
extern "C" void save_module_states(ModList *l) {
|
||||
for (unsigned long long i = 0; i < l->count; i++) {
|
||||
auto register_settings = l->data[i]->register_settings;
|
||||
if (register_settings) {
|
||||
Ev16 ev = settings_pending_event;
|
||||
register_settings(&ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
// 0x9000: Vec3 / AABB helpers (MS ABI: 24/48-byte structs travel by pointer, results via sret)
|
||||
#include "slinky/engine.h"
|
||||
struct Vec3 { double x, y, z; };
|
||||
struct AABB { Vec3 min, max; };
|
||||
extern "C" double unused_constant_1_62, unused_constant_neg_1_62_b;
|
||||
|
||||
extern "C" Vec3 vec3_make(double x, double y, double z) { return Vec3{x, y, z}; }
|
||||
extern "C" Vec3 vec3_sub(Vec3 a, Vec3 b) { return Vec3{a.x - b.x, a.y - b.y, a.z - b.z}; }
|
||||
extern "C" Vec3 vec3_add(Vec3 a, Vec3 b) { return Vec3{b.x + a.x, b.y + a.y, b.z + a.z}; }
|
||||
extern "C" Vec3 vec3_scale(Vec3 a, double s) { return Vec3{s * a.x, s * a.y, s * a.z}; }
|
||||
extern "C" Vec3 vec3_lerp(Vec3 a, Vec3 b, double t) {
|
||||
return Vec3{a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t};
|
||||
}
|
||||
extern "C" double vec3_distance_sq(Vec3 *a, Vec3 *b) {
|
||||
double dx = a->x - b->x, dy = a->y - b->y, dz = a->z - b->z;
|
||||
return dz * dz + (dx * dx + dy * dy);
|
||||
}
|
||||
extern "C" double vec3_distance(Vec3 *a, Vec3 *b) {
|
||||
double ax = a->x, ay = a->y, az = a->z;
|
||||
double dx = ax - b->x, dy = ay - b->y, dz = az - b->z;
|
||||
return __builtin_sqrt(dz * dz + (dx * dx + dy * dy));
|
||||
}
|
||||
extern "C" Vec3 vec3_add_eye_height(Vec3 a) { return Vec3{a.x, a.y + unused_constant_1_62, a.z}; }
|
||||
extern "C" Vec3 vec3_add_eye_height_sneak(Vec3 a) { return Vec3{a.x, a.y + unused_constant_neg_1_62_b, a.z}; }
|
||||
extern "C" Vec3 vec3_reciprocal(Vec3 a) { return Vec3{1.0 / a.x, 1.0 / a.y, 1.0 / a.z}; }
|
||||
extern "C" AABB aabb_make(Vec3 a, Vec3 b) { return AABB{a, b}; }
|
||||
extern "C" AABB *aabb_expand(AABB *out, AABB *b, double d) {
|
||||
b->min.x -= d; b->min.y -= d; b->min.z -= d;
|
||||
b->max.x += d; b->max.y += d; b->max.z += d;
|
||||
*out = *b;
|
||||
return out;
|
||||
}
|
||||
extern "C" bool aabb_intersects(AABB *a, AABB *b) {
|
||||
return b->max.x > a->min.x && a->max.x > b->min.x && b->max.y > a->min.y &&
|
||||
a->max.y > b->min.y && b->max.z > a->min.z && a->max.z > b->min.z;
|
||||
}
|
||||
extern "C" bool aabb_contains_point(AABB *a, Vec3 *p) {
|
||||
return p->x > a->min.x && a->max.x > p->x && p->y > a->min.y && a->max.y > p->y &&
|
||||
p->z > a->min.z && a->max.z > p->z;
|
||||
}
|
||||
|
||||
// ---- Rot (yaw, pitch) packed in one register ----
|
||||
struct Rot { float yaw, pitch; };
|
||||
FN(float, libc_hypotf_alt, (float a, float b));
|
||||
FN(float, libc_hypotf, (float a, float b));
|
||||
extern "C" double libc_atan2(double y, double x);
|
||||
extern "C" double half_turn_degrees, pi_constant, offset_neg_90, minus_half_turn_degrees;
|
||||
|
||||
extern "C" Rot rot_make(float a, float b) { return Rot{a, b}; }
|
||||
extern "C" Rot rot_add(Rot a, Rot b) { return Rot{b.yaw + a.yaw, b.pitch + a.pitch}; }
|
||||
extern "C" Rot rot_sub(Rot a, Rot b) { return Rot{a.yaw - b.yaw, a.pitch - b.pitch}; }
|
||||
extern "C" Rot rot_scale(Rot a, float s) { return Rot{a.yaw * s, a.pitch * s}; }
|
||||
extern "C" Rot rot_lerp(Rot a, Rot b, float t) {
|
||||
float y = (b.yaw - a.yaw) * t + a.yaw;
|
||||
asm("" : "+x"(y));
|
||||
return Rot{y, (b.pitch - a.pitch) * t + a.pitch};
|
||||
}
|
||||
extern "C" float rot_delta_length(Rot a, Rot b) { return libc_hypotf_alt(a.yaw - b.yaw, a.pitch - b.pitch); }
|
||||
extern "C" float rot_distance(Rot a, Rot b) { return libc_hypotf(b.pitch - a.pitch, b.yaw - a.yaw); }
|
||||
extern "C" float rot_length(Rot a) { return libc_hypotf_alt(a.yaw, a.pitch); }
|
||||
extern "C" Rot rot_look_at(Vec3 *from, Vec3 *to) {
|
||||
double dx = to->x - from->x;
|
||||
|
||||
double dy = to->y - from->y;
|
||||
|
||||
double dz = to->z - from->z;
|
||||
double hor = __builtin_sqrt(dx * dx + dz * dz);
|
||||
float yaw = (float)(libc_atan2(dz, dx) * half_turn_degrees / pi_constant + offset_neg_90);
|
||||
float pitch = (float)(libc_atan2(dy, hor) * minus_half_turn_degrees / pi_constant);
|
||||
return Rot{yaw, pitch};
|
||||
}
|
||||
|
||||
// key code -> name (clang turns the switch into a relative string table)
|
||||
extern "C" const char str_none[], str_mouse1[], str_mouse2[], str_mouse3[], str_mouse4[], str_mouse5[], str_backspace[], str_tab[], str_enter[], str_caps[], str_esc[], str_space[], str_prev[], str_next[], str_end[], str_home[], str_left[], mc_obf_name_up[], str_right[], str_down[], str_prntscr[], str_ins[], str_del[], str_numlock[], str_scroll[], str_lshift[], str_rshift[], str_lcontrol[], str_rcontrol[], str_lmenu[], str_rmenu[], str_colon[], str_plus[], str_comma[], str_minus[], str_period[], str_slash[], str_grave[], str_lbracket[], str_backslash[], str_rbracket[], str_quote[], text_number_0[], text_number_1[], text_number_2[], text_number_3[], text_number_4[], text_number_5[], text_number_6[], text_number_7[], text_number_8[], text_number_9[], mc_obf_name_a[], mc_obf_name_b[], mc_obf_name_c[], mc_obf_name_d[], mc_obf_name_e[], mc_obf_name_f[], mc_obf_name_g[], mc_obf_name_h[], mc_obf_name_i[], mc_obf_name_j[], mc_obf_name_k[], mc_obf_name_l[], mc_obf_name_m[], mc_obf_name_n[], mc_obf_name_o[], mc_obf_name_p[], mc_obf_name_q[], mc_obf_name_r[], mc_obf_name_s[], mc_obf_name_t[], mc_obf_name_u[], mc_obf_name_v[], mc_obf_name_w[], mc_obf_name_x[], mc_obf_name_y[], mc_obf_name_z[], str_numpad0[], str_numpad1[], str_numpad2[], str_numpad3[], str_numpad4[], str_numpad5[], str_numpad6[], str_numpad7[], str_numpad8[], str_numpad9[], mc_obf_name_f1[], mc_obf_name_f2[], mc_obf_name_f3[], mc_obf_name_f4[], mc_obf_name_f5[], mc_obf_name_f6[], mc_obf_name_f7[], mc_obf_name_f8[], mc_obf_name_f9[], text_f10[], text_f11[], text_f12[], text_f13[], text_f14[], text_f15[], text_f16[];
|
||||
extern "C" const char *mouse_button_name(int v) {
|
||||
switch (v) {
|
||||
case 1: return str_mouse1; // "MOUSE1"
|
||||
case 2: return str_mouse2; // "MOUSE2"
|
||||
case 3: return str_mouse3; // "MOUSE3"
|
||||
case 4: return str_mouse4; // "MOUSE4"
|
||||
case 5: return str_mouse5; // "MOUSE5"
|
||||
case 6: return text_number_0; // "0"
|
||||
case 7: return text_number_1; // "1"
|
||||
case 8: return text_number_2; // "2"
|
||||
case 9: return text_number_3; // "3"
|
||||
case 10: return text_number_4; // "4"
|
||||
case 11: return text_number_5; // "5"
|
||||
case 12: return text_number_6; // "6"
|
||||
case 13: return text_number_7; // "7"
|
||||
case 14: return text_number_8; // "8"
|
||||
case 15: return text_number_9; // "9"
|
||||
case 16: return str_numpad0; // "NUMPAD0"
|
||||
case 17: return str_numpad1; // "NUMPAD1"
|
||||
case 18: return str_numpad2; // "NUMPAD2"
|
||||
case 19: return str_numpad3; // "NUMPAD3"
|
||||
case 20: return str_numpad4; // "NUMPAD4"
|
||||
case 21: return str_numpad5; // "NUMPAD5"
|
||||
case 22: return str_numpad6; // "NUMPAD6"
|
||||
case 23: return str_numpad7; // "NUMPAD7"
|
||||
case 24: return str_numpad8; // "NUMPAD8"
|
||||
case 25: return str_numpad9; // "NUMPAD9"
|
||||
case 26: return mc_obf_name_a; // "A"
|
||||
case 27: return mc_obf_name_b; // "B"
|
||||
case 28: return mc_obf_name_c; // "C"
|
||||
case 29: return mc_obf_name_d; // "D"
|
||||
case 30: return mc_obf_name_e; // "E"
|
||||
case 31: return mc_obf_name_f; // "F"
|
||||
case 32: return mc_obf_name_g; // "G"
|
||||
case 33: return mc_obf_name_h; // "H"
|
||||
case 34: return mc_obf_name_i; // "I"
|
||||
case 35: return mc_obf_name_j; // "J"
|
||||
case 36: return mc_obf_name_k; // "K"
|
||||
case 37: return mc_obf_name_l; // "L"
|
||||
case 38: return mc_obf_name_m; // "M"
|
||||
case 39: return mc_obf_name_n; // "N"
|
||||
case 40: return mc_obf_name_o; // "O"
|
||||
case 41: return mc_obf_name_p; // "P"
|
||||
case 42: return mc_obf_name_q; // "Q"
|
||||
case 43: return mc_obf_name_r; // "R"
|
||||
case 44: return mc_obf_name_s; // "S"
|
||||
case 45: return mc_obf_name_t; // "T"
|
||||
case 46: return mc_obf_name_u; // "U"
|
||||
case 47: return mc_obf_name_v; // "V"
|
||||
case 48: return mc_obf_name_w; // "W"
|
||||
case 49: return mc_obf_name_x; // "X"
|
||||
case 50: return mc_obf_name_y; // "Y"
|
||||
case 51: return mc_obf_name_z; // "Z"
|
||||
case 52: return str_colon; // "COLON"
|
||||
case 53: return str_plus; // "PLUS"
|
||||
case 54: return str_comma; // "COMMA"
|
||||
case 55: return str_minus; // "MINUS"
|
||||
case 56: return str_period; // "PERIOD"
|
||||
case 57: return str_slash; // "SLASH"
|
||||
case 58: return str_grave; // "GRAVE"
|
||||
case 59: return str_lbracket; // "LBRACKET"
|
||||
case 60: return str_backslash; // "BACKSLASH"
|
||||
case 61: return str_rbracket; // "RBRACKET"
|
||||
case 62: return str_quote; // "QUOTE"
|
||||
case 63: return str_left; // "LEFT"
|
||||
case 64: return mc_obf_name_up; // "UP"
|
||||
case 65: return str_right; // "RIGHT"
|
||||
case 66: return str_down; // "DOWN"
|
||||
case 67: return mc_obf_name_f1; // "F1"
|
||||
case 68: return mc_obf_name_f2; // "F2"
|
||||
case 69: return mc_obf_name_f3; // "F3"
|
||||
case 70: return mc_obf_name_f4; // "F4"
|
||||
case 71: return mc_obf_name_f5; // "F5"
|
||||
case 72: return mc_obf_name_f6; // "F6"
|
||||
case 73: return mc_obf_name_f7; // "F7"
|
||||
case 74: return mc_obf_name_f8; // "F8"
|
||||
case 75: return mc_obf_name_f9; // "F9"
|
||||
case 76: return text_f10; // "F10"
|
||||
case 77: return text_f11; // "F11"
|
||||
case 78: return text_f12; // "F12"
|
||||
case 79: return text_f13; // "F13"
|
||||
case 80: return text_f14; // "F14"
|
||||
case 81: return text_f15; // "F15"
|
||||
case 82: return text_f16; // "F16"
|
||||
case 83: return str_backspace; // "BACKSPACE"
|
||||
case 84: return str_tab; // "TAB"
|
||||
case 85: return str_enter; // "ENTER"
|
||||
case 86: return str_caps; // "CAPS"
|
||||
case 87: return str_esc; // "ESC"
|
||||
case 88: return str_space; // "SPACE"
|
||||
case 89: return str_prev; // "PREV"
|
||||
case 90: return str_next; // "NEXT"
|
||||
case 91: return str_end; // "END"
|
||||
case 92: return str_home; // "HOME"
|
||||
case 93: return str_prntscr; // "PRNTSCR"
|
||||
case 94: return str_ins; // "INS"
|
||||
case 95: return str_del; // "DEL"
|
||||
case 96: return str_numlock; // "NUMLOCK"
|
||||
case 97: return str_scroll; // "SCROLL"
|
||||
case 98: return str_lshift; // "LSHIFT"
|
||||
case 99: return str_rshift; // "RSHIFT"
|
||||
case 100: return str_lcontrol; // "LCONTROL"
|
||||
case 101: return str_rcontrol; // "RCONTROL"
|
||||
case 102: return str_lmenu; // "LMENU"
|
||||
case 103: return str_rmenu; // "RMENU"
|
||||
default: return str_none; // "NONE"
|
||||
}
|
||||
}
|
||||
|
||||
// ---- AABB union / clamp / ray slab tests (double fmin/fmax on float slab values, like the C source) ----
|
||||
extern "C" AABB aabb_union(Vec3 a, Vec3 b) {
|
||||
AABB r;
|
||||
r.min.z = __builtin_fmin(a.z, b.z);
|
||||
r.max.z = __builtin_fmax(a.z, b.z);
|
||||
r.min.x = __builtin_fmin(a.x, b.x); r.min.y = __builtin_fmin(a.y, b.y);
|
||||
r.max.x = __builtin_fmax(a.x, b.x); r.max.y = __builtin_fmax(a.y, b.y);
|
||||
return r;
|
||||
}
|
||||
extern "C" Vec3 aabb_clamp_point(AABB b, Vec3 p) {
|
||||
Vec3 r;
|
||||
r.x = p.x < b.min.x ? b.min.x : (b.max.x < p.x ? b.max.x : p.x);
|
||||
r.y = p.y < b.min.y ? b.min.y : (b.max.y < p.y ? b.max.y : p.y);
|
||||
r.z = p.z < b.min.z ? b.min.z : (b.max.z < p.z ? b.max.z : p.z);
|
||||
return r;
|
||||
}
|
||||
extern "C" const double positive_infinity, one_constant, positive_infinity_for_ray_test;
|
||||
extern "C" const float threshold_1_in_vec_aabb, threshold_1_in_vec_aabb_a;
|
||||
extern "C" bool aabb_ray_slab_test(const AABB *b, const Vec3 *o, const Vec3 *inv) {
|
||||
const double *mn = &b->min.x, *mx = &b->max.x, *po = &o->x, *pi = &inv->x;
|
||||
float tmin = 0.0f, tmax = (float)positive_infinity;
|
||||
#pragma clang loop unroll(full)
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float t1 = (float)((mn[i] - po[i]) * pi[i]);
|
||||
float t2 = (float)((mx[i] - po[i]) * pi[i]);
|
||||
tmin = (float)__builtin_fmin(__builtin_fmax(t1, tmin), __builtin_fmax(t2, tmin));
|
||||
tmax = (float)__builtin_fmax(__builtin_fmin(t1, tmax), __builtin_fmin(t2, tmax));
|
||||
}
|
||||
return tmin < tmax && tmin <= threshold_1_in_vec_aabb;
|
||||
}
|
||||
extern "C" bool aabb_ray_hit(const AABB *b, const Vec3 *o, const Vec3 *dir, Vec3 *hit) {
|
||||
double inv[3] = {one_constant / dir->x, one_constant / dir->y, one_constant / dir->z};
|
||||
const double *mn = &b->min.x, *mx = &b->max.x, *po = &o->x;
|
||||
float tmin = 0.0f, tmax = (float)positive_infinity_for_ray_test;
|
||||
#pragma clang loop unroll(full)
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float t1 = (float)((mn[i] - po[i]) * inv[i]);
|
||||
float t2 = (float)((mx[i] - po[i]) * inv[i]);
|
||||
tmin = (float)__builtin_fmin(__builtin_fmax(t1, tmin), __builtin_fmax(t2, tmin));
|
||||
tmax = (float)__builtin_fmax(__builtin_fmin(t1, tmax), __builtin_fmin(t2, tmax));
|
||||
}
|
||||
bool ok = !(tmax <= tmin) & !(threshold_1_in_vec_aabb_a < tmin);
|
||||
if (ok) {
|
||||
hit->x = o->x + dir->x * tmin;
|
||||
hit->y = o->y + dir->y * tmin;
|
||||
hit->z = o->z + dir->z * tmin;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// ---- packet hook: GUI-chat commands first, then the packet event (0x17); returns "cancel" ----
|
||||
#include "slinky/jids.h"
|
||||
struct HCtx { Jvm *jvm; void *module_manager; void *profile_list; void *minecraft; void *world; void *player; void *key_state_table; void *target;
|
||||
unsigned char in_game; unsigned char padding_41[3]; int padding_44; };
|
||||
extern "C" char java_natives_world_module_manager[], java_natives_frame_module_manager_target_state[];
|
||||
extern "C" void *init_profile_list, *java_natives_world_key_state_table_one_byte;
|
||||
extern "C" char *ptr_to_gui_module_descriptor;
|
||||
FN(void *, get_minecraft, (Jvm *jvm));
|
||||
FN(void *, get_local_player, (Jvm *jvm, void *minecraft));
|
||||
FN(void *, get_world, (Jvm *jvm, void *minecraft));
|
||||
FN(unsigned char, is_in_game, (Jvm *jvm, void *minecraft));
|
||||
FN(void *, java_jni_is_instance_of_c01_packet_chat_message, (Jvm *jvm, void *pkt));
|
||||
FN(void *, java_jni_is_instance_of_c14_packet_tab_complete, (Jvm *jvm, void *pkt));
|
||||
FN(unsigned char, handle_chat_message, (HCtx *ctx, void *object));
|
||||
FN(unsigned char, text_starts_with_bang, (HCtx *ctx, void *object));
|
||||
struct PktEvent { long long type; Jvm *j; void *a; void *pkt; unsigned char cancel; char rest[0x27]; };
|
||||
FN(void, dispatch_event, (void *mgr, PktEvent *e));
|
||||
extern "C" unsigned char handle_chat_packet(Jvm *jvm, void *object, void *pkt) {
|
||||
void *minecraft = get_minecraft(jvm);
|
||||
void *player = get_local_player(jvm, minecraft);
|
||||
void *world = get_world(jvm, minecraft);
|
||||
HCtx ctx;
|
||||
ctx.jvm = jvm; ctx.module_manager = java_natives_world_module_manager; ctx.profile_list = init_profile_list; ctx.minecraft = minecraft; ctx.world = world; ctx.player = player;
|
||||
ctx.key_state_table = java_natives_world_key_state_table_one_byte; ctx.target = java_natives_frame_module_manager_target_state;
|
||||
ctx.in_game = ptr_to_gui_module_descriptor[0x14] ? 0 : is_in_game(jvm, minecraft);
|
||||
*(int *)&ctx.padding_41[0] = 0;
|
||||
ctx.padding_44 = 0;
|
||||
void *c01_packet_chat_message = java_jni_is_instance_of_c01_packet_chat_message(jvm, pkt);
|
||||
unsigned char r;
|
||||
if (c01_packet_chat_message) {
|
||||
r = handle_chat_message(&ctx, c01_packet_chat_message);
|
||||
} else {
|
||||
void *c14_packet_tab_complete = java_jni_is_instance_of_c14_packet_tab_complete(jvm, pkt);
|
||||
if (!c14_packet_tab_complete) goto send;
|
||||
r = text_starts_with_bang(&ctx, c14_packet_tab_complete);
|
||||
}
|
||||
if (r) return 1;
|
||||
send:
|
||||
PktEvent e = {0x17, jvm, object, pkt, 0, {}};
|
||||
dispatch_event(java_natives_world_module_manager, &e);
|
||||
return e.cancel;
|
||||
}
|
||||
|
||||
// ---- gap-scan functions ----
|
||||
// client key code -> ASCII for text fields (a-z, 0-9, space, '.', '-'); 0 = not printable. Inverse of windows_key_to_client_key.
|
||||
extern "C" char key_code_to_char(int key) {
|
||||
if ((unsigned)(key - 0x1a) <= 0x19) return (char)(key + 0x47);
|
||||
if ((unsigned)(key - 6) <= 9) return (char)(key + 0x2a);
|
||||
switch (key) {
|
||||
case 0x58: return ' ';
|
||||
case 0x38: return '.';
|
||||
case 0x37: return '-';
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// horizontal distance (*horiz) and absolute height difference (*vert) between two points
|
||||
typedef long long v2l9 __attribute__((vector_size(16)));
|
||||
typedef double v2d9 __attribute__((vector_size(16)));
|
||||
extern "C" const v2l9 vec_aabb_fabs_mask; // fabs mask
|
||||
extern "C" double libc_hypot(double x, double z); // hypot
|
||||
extern "C" void vec3_horizontal_distance(const Vec3 *a, const Vec3 *b, double *horiz, double *vert) {
|
||||
double ax = a->x, az = a->z;
|
||||
|
||||
*horiz = libc_hypot(ax - b->x, az - b->z);
|
||||
v2d9 d = {a->y - b->y, 0};
|
||||
*vert = ((v2d9)((v2l9)d & vec_aabb_fabs_mask))[0];
|
||||
}
|
||||
|
||||
extern "C" Vec3 vec3_copy(Vec3 v) { // normalize
|
||||
double len = __builtin_sqrt((v.x * v.x + v.y * v.y) + v.z * v.z);
|
||||
v.x /= len;
|
||||
v.y /= len;
|
||||
v.z /= len;
|
||||
asm("" : "+m"(v)); // the original normalises its by-value copy in memory and copies it out
|
||||
return v;
|
||||
}
|
||||
extern "C" double vec3_length(const Vec3 *v) { return __builtin_sqrt((v->x * v->x + v->y * v->y) + v->z * v->z); }
|
||||
|
||||
struct V2f9 { float x, y; };
|
||||
extern "C" float vec2_length_sq(V2f9 v) { return v.x * v.x + v.y * v.y; }
|
||||
@@ -0,0 +1,21 @@
|
||||
// helper functions (formerly address block; names inferred from behaviour).
|
||||
#include "slinky/engine.h"
|
||||
FN(void *, java_potion_potion_types, (void *jvm, int id));
|
||||
FN(void *, java_call_entity_living_base_get_active_potion_effect, (void *jvm, void *entity_living_base, void *effect));
|
||||
FN(void, java_set_potion_effect_duration, (void *jvm, void *effect, int value));
|
||||
FN(void, java_set_entity_player_sp_time_in_portal, (void *jvm, void *entity_player_sp, float value));
|
||||
FN(void, java_set_entity_player_sp_prev_time_in_portal, (void *jvm, void *entity_player_sp, float value));
|
||||
extern "C" unsigned char anti_debuff_blindness_value, anti_debuff_nausea_value;
|
||||
struct Ev { char padding_00[8]; Ctx *ctx; };
|
||||
extern "C" void anti_debuff_tick(Ev *event) {
|
||||
Ctx *ctx = event->ctx;
|
||||
if (anti_debuff_blindness_value == 1) {
|
||||
void *jvm = java_potion_potion_types(ctx->jvm, 0xf);
|
||||
void *effect = java_call_entity_living_base_get_active_potion_effect(ctx->jvm, ctx->player, jvm);
|
||||
if (effect) java_set_potion_effect_duration(ctx->jvm, effect, 0);
|
||||
}
|
||||
if (anti_debuff_nausea_value == 1) {
|
||||
java_set_entity_player_sp_time_in_portal(ctx->jvm, ctx->player, 0.0f);
|
||||
java_set_entity_player_sp_prev_time_in_portal(ctx->jvm, ctx->player, 0.0f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// helper functions (formerly address block; names inferred from behaviour).
|
||||
#include "slinky/engine.h"
|
||||
FN(void, libc_free, (void *ptr));
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
struct Vec { unsigned long long size, cap; void *data; };
|
||||
struct Lists { char padding_00[8]; Vec a, b, c; };
|
||||
extern "C" void free_block_search_section_lists(Lists *l) {
|
||||
l->a.size = 0; l->a.cap = 0; libc_free(l->a.data); l->a.data = 0;
|
||||
l->b.size = 0; l->b.cap = 0; libc_free(l->b.data); l->b.data = 0;
|
||||
l->c.size = 0; l->c.cap = 0; libc_free(l->c.data); l->c.data = 0;
|
||||
}
|
||||
extern "C" void search_box_append_char(char *buf, char character) {
|
||||
unsigned long long n = libc_strlen(buf);
|
||||
if (n > 99) return;
|
||||
unsigned char u = (unsigned char)((character & 0xdf) - 0x41);
|
||||
asm("" : "+r"(u));
|
||||
if (u >= 0x1a) {
|
||||
unsigned char d = (unsigned char)(character - 0x30);
|
||||
asm("" : "+r"(d));
|
||||
unsigned char notdigit = d >= 10;
|
||||
asm("" : "+r"(notdigit));
|
||||
unsigned char notspace = character != 0x20;
|
||||
asm("" : "+r"(notspace));
|
||||
if (notdigit & notspace) return;
|
||||
}
|
||||
buf[n] = character;
|
||||
}
|
||||
|
||||
FN(void *, java_get_world_chunk_provider_client, (void *jvm, void *chunk_provider_client));
|
||||
FN(void *, java_get_chunk_provider_client, (void *jvm, void *chunk_provider_client));
|
||||
FN(int, java_call_list_size, (void *jvm, void *list));
|
||||
FN(void *, java_call_list_get, (void *jvm, void *list, int i));
|
||||
FN(int, java_get_chunk_x_position, (void *jvm, void *chunk));
|
||||
FN(int, java_get_chunk_z_position, (void *jvm, void *chunk));
|
||||
struct Rec { void *jvm; void *world; int one; int id; int lvl; int padding_1c, padding_20, padding_24; };
|
||||
FN(void, equipment_event, (Rec *r));
|
||||
struct S3f { void *jvm; char padding_08[0x18]; void *world; };
|
||||
extern "C" void block_esp_queue_loaded_chunks(S3f *s) {
|
||||
void *chunk_provider_client = java_get_world_chunk_provider_client(s->jvm, s->world);
|
||||
if (!chunk_provider_client) return;
|
||||
void *list = java_get_chunk_provider_client(s->jvm, chunk_provider_client);
|
||||
int n = java_call_list_size(s->jvm, list);
|
||||
if (n <= 0) return;
|
||||
for (int i = 0; i != n; i++) {
|
||||
void *element = java_call_list_get(s->jvm, list, i);
|
||||
int chunk_x_position = java_get_chunk_x_position(s->jvm, element);
|
||||
int lvl = java_get_chunk_z_position(s->jvm, element);
|
||||
Rec r;
|
||||
r.jvm = s->jvm;
|
||||
r.world = s->world;
|
||||
r.one = 1;
|
||||
r.id = chunk_x_position;
|
||||
r.lvl = lvl;
|
||||
r.padding_1c = 0; r.padding_20 = 0; r.padding_24 = 0;
|
||||
equipment_event(&r);
|
||||
}
|
||||
}
|
||||
|
||||
// rebuild a coloured box mesh: 8 corner vertices + 24 line indices per block
|
||||
FN(unsigned char, vector_grow, (void *data, unsigned long long elem, unsigned long long size, void *cap));
|
||||
struct Vtx { float x, y, z; unsigned char r, g, b, a; };
|
||||
struct Corner { float x, y, z; };
|
||||
struct Blk { unsigned char x, y, z, padding_03; char padding_04[4]; float *color; };
|
||||
struct Mesh {
|
||||
char padding_00[8]; unsigned long long nblocks; char padding_10[8]; Blk *blocks;
|
||||
unsigned long long nv, capv; Vtx *v;
|
||||
unsigned long long ni, capi; unsigned short *idx;
|
||||
};
|
||||
extern "C" const float factor_255_in_block_esp_mesh_and_search_box;
|
||||
extern "C" const Corner cube_corner_offsets[8];
|
||||
extern "C" const unsigned short cube_edge_indices[24];
|
||||
extern "C" void rebuild_block_esp_mesh(Mesh *m) {
|
||||
m->nv = 0;
|
||||
m->ni = 0;
|
||||
if (!m->nblocks) return;
|
||||
for (unsigned long long b = 0; b < m->nblocks; b++) {
|
||||
Blk *bk = &m->blocks[b];
|
||||
float *col = bk->color;
|
||||
unsigned char r = (unsigned char)(int)(col[2] * factor_255_in_block_esp_mesh_and_search_box);
|
||||
unsigned char g = (unsigned char)(int)(col[3] * factor_255_in_block_esp_mesh_and_search_box);
|
||||
unsigned char bl = (unsigned char)(int)(col[4] * factor_255_in_block_esp_mesh_and_search_box);
|
||||
unsigned long long base = m->nv;
|
||||
#pragma clang loop unroll(disable)
|
||||
for (int k = 0; k < 8; k++) {
|
||||
if (!vector_grow(&m->v, 0x10, m->nv, &m->capv)) return;
|
||||
Vtx *v = &m->v[m->nv];
|
||||
v->x = cube_corner_offsets[k].x + (float)bk->x;
|
||||
v->y = cube_corner_offsets[k].y + (float)bk->y;
|
||||
v->z = cube_corner_offsets[k].z + (float)bk->z;
|
||||
v->r = r; v->g = g; v->b = bl; v->a = 0xff;
|
||||
m->nv++;
|
||||
}
|
||||
#pragma clang loop unroll(disable)
|
||||
for (int k = 0; k < 24; k++) {
|
||||
if (!vector_grow(&m->idx, 2, m->ni, &m->capi)) return;
|
||||
m->idx[m->ni] = (unsigned short)(cube_edge_indices[k] + base);
|
||||
m->ni++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- free the whole block-search mesh cache: every section's three lists, then the section array ----
|
||||
struct LVec3f { unsigned long long size, cap; Lists *data; };
|
||||
extern "C" LVec3f block_esp_mesh_count_capacity;
|
||||
extern "C" void free_block_search_cache() {
|
||||
for (unsigned long long i = 0; i < block_esp_mesh_count_capacity.size; i++) {
|
||||
Lists *l = &block_esp_mesh_count_capacity.data[i];
|
||||
l->a.size = 0; l->a.cap = 0; libc_free(l->a.data); l->a.data = 0;
|
||||
l->b.size = 0; l->b.cap = 0; libc_free(l->b.data); l->b.data = 0;
|
||||
l->c.size = 0; l->c.cap = 0; libc_free(l->c.data); l->c.data = 0;
|
||||
}
|
||||
block_esp_mesh_count_capacity.size = 0;
|
||||
block_esp_mesh_count_capacity.cap = 0;
|
||||
libc_free(block_esp_mesh_count_capacity.data);
|
||||
block_esp_mesh_count_capacity.data = 0;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// 0x41000: sorted name table (24-byte entries) helpers
|
||||
#include "slinky/engine.h"
|
||||
struct Ent { void *name; long long q; int d; int key; };
|
||||
struct Vec { unsigned long long size, cap; Ent *data; };
|
||||
|
||||
extern "C" unsigned long long name_table_count, name_table_capacity;
|
||||
extern "C" Ent *name_table_entries;
|
||||
extern "C" unsigned char safe_walk_safe_walk_active;
|
||||
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
FN(int, key_name_to_code, (void *key));
|
||||
FN(void *, key_code_to_index, (int idx));
|
||||
FN(unsigned char, vector_grow, (void *vec, unsigned long long elem, unsigned long long n, void *cap));
|
||||
FN(void *, libc_bsearch, (const void *key, const void *base, unsigned long long n, unsigned long long size, int (*cmp)(const void *, const void *)));
|
||||
FN(void, libc_memmove, (void *dst, const void *src, unsigned long long n));
|
||||
FN(void, libc_free, (void *ptr));
|
||||
FN(void *, get_minecraft, (void *jvm));
|
||||
FN(void *, get_local_player, (void *jvm, void *minecraft));
|
||||
FN(void, java_set_entity_living_base_max_hurt_time, (void *jvm, void *entity_living_base, int value));
|
||||
|
||||
extern "C" int compare_name_key(const int *left, const Ent *right) { return *left - right->key; }
|
||||
extern "C" void text_filter_name(char *buf, char character) {
|
||||
unsigned long long n = libc_strlen(buf);
|
||||
if (n > 99) return;
|
||||
unsigned char u = (unsigned char)((character & 0xdf) - 0x41);
|
||||
asm("" : "+r"(u));
|
||||
if (u >= 0x1a) {
|
||||
unsigned char d = (unsigned char)(character - 0x30);
|
||||
asm("" : "+r"(d));
|
||||
unsigned char notdigit = d >= 10;
|
||||
asm("" : "+r"(notdigit));
|
||||
unsigned char notspace = character != 0x20;
|
||||
asm("" : "+r"(notspace));
|
||||
if (notdigit & notspace) return;
|
||||
}
|
||||
buf[n] = character;
|
||||
}
|
||||
extern "C" unsigned long long deref_u64(unsigned long long *p) { return *p; }
|
||||
extern "C" unsigned long long vec24_key(Vec *vector, long long i) { return *(unsigned long long *)((char *)vector->data + i * 24); }
|
||||
extern "C" void *vec24_name(Vec *vector, long long i) { return (char *)vector->data + i * 24 + 8; }
|
||||
extern "C" unsigned char name_table_find(Vec *vector, void *name, long long *src) {
|
||||
int id = key_name_to_code(name);
|
||||
if (id == -1) return 0;
|
||||
if (name_table_entries && libc_bsearch(&id, name_table_entries, name_table_count, 0x18, (int (*)(const void *, const void *))compare_name_key))
|
||||
return 0;
|
||||
unsigned long long idx = 0;
|
||||
if (vector->size) {
|
||||
for (idx = 0; idx < vector->size; idx++)
|
||||
if (id <= vector->data[idx].key) break;
|
||||
}
|
||||
void *nm = key_code_to_index(id);
|
||||
long long q = src[0];
|
||||
int d = (int)src[1];
|
||||
int k = id;
|
||||
unsigned char ok = vector_grow(&name_table_entries, 0x18, name_table_count, &name_table_capacity);
|
||||
if (!ok) return ok;
|
||||
Ent *base = name_table_entries;
|
||||
libc_memmove(base + idx + 1, base + idx, (name_table_count - idx) * 0x18);
|
||||
base[idx].name = nm;
|
||||
base[idx].q = q;
|
||||
base[idx].d = d;
|
||||
base[idx].key = k;
|
||||
name_table_count++;
|
||||
return ok;
|
||||
}
|
||||
extern "C" void vec24_erase(Vec *vector, long long i) {
|
||||
vector->size--;
|
||||
libc_memmove(&vector->data[i], &vector->data[i + 1], (vector->size - i) * sizeof(Ent));
|
||||
}
|
||||
extern "C" void vec24_clear(Vec *vector) {
|
||||
vector->size = 0; vector->cap = 0;
|
||||
libc_free(vector->data);
|
||||
vector->data = 0;
|
||||
}
|
||||
extern "C" unsigned char get_render_flag() { return safe_walk_safe_walk_active; }
|
||||
extern "C" void set_local_player_max_hurt_time(void *jvm) {
|
||||
void *player = get_local_player(jvm, get_minecraft(jvm));
|
||||
if (player) java_set_entity_living_base_max_hurt_time(jvm, player, 0x7fffffff);
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
// 0x40000: 24-byte entry vector accessors, comparators
|
||||
#include "slinky/engine.h"
|
||||
struct Ent { void *name; long long q; int d; int key; };
|
||||
struct Vec { unsigned long long size, cap; Ent *data; };
|
||||
extern "C" unsigned long long search_list_read_u64(unsigned long long *p) { return *p; }
|
||||
extern "C" unsigned long long search_entry_key(Vec *vector, long long i) { return *(unsigned long long *)((char *)vector->data + i * 24); }
|
||||
extern "C" void *search_entry_name(Vec *vector, long long i) { return (char *)vector->data + i * 24 + 8; }
|
||||
extern "C" int compare_vec24_key(const int *left, const Ent *right) { return *left - right->key; }
|
||||
extern "C" int compare_int_ids(const int *left, const int *right) {
|
||||
int result = left[0] - right[0];
|
||||
if (result == 0) result = left[1] - right[1];
|
||||
SHARED_EPILOG();
|
||||
return result;
|
||||
}
|
||||
|
||||
FN(void, libc_memmove, (void *dst, const void *src, unsigned long long n));
|
||||
FN(void, libc_free, (void *ptr));
|
||||
struct V3 { unsigned long long size, cap; void *data; };
|
||||
struct Lists { char padding_00[8]; V3 a, b, c; };
|
||||
struct LVec { unsigned long long size, cap; Lists *data; };
|
||||
extern "C" LVec block_esp_mesh_count_capacity;
|
||||
extern "C" unsigned char block_esp_rebuild_requested;
|
||||
static inline __attribute__((always_inline)) void clear_lists(Lists *l) {
|
||||
l->a.size = 0; l->a.cap = 0; libc_free(l->a.data); l->a.data = 0;
|
||||
l->b.size = 0; l->b.cap = 0; libc_free(l->b.data); l->b.data = 0;
|
||||
l->c.size = 0; l->c.cap = 0; libc_free(l->c.data); l->c.data = 0;
|
||||
}
|
||||
static inline __attribute__((always_inline)) void clear_all() {
|
||||
for (unsigned long long i = 0; i < block_esp_mesh_count_capacity.size; i++) clear_lists(&block_esp_mesh_count_capacity.data[i]);
|
||||
block_esp_mesh_count_capacity.size = 0; block_esp_mesh_count_capacity.cap = 0;
|
||||
libc_free(block_esp_mesh_count_capacity.data);
|
||||
block_esp_mesh_count_capacity.data = 0;
|
||||
block_esp_rebuild_requested = 1;
|
||||
}
|
||||
extern "C" void search_entries_erase(Vec *vector, long long i) {
|
||||
vector->size--;
|
||||
libc_memmove(&vector->data[i], &vector->data[i + 1], (vector->size - i) * sizeof(Ent));
|
||||
clear_all();
|
||||
}
|
||||
extern "C" void search_entries_clear(Vec *vector) {
|
||||
vector->size = 0; vector->cap = 0;
|
||||
libc_free(vector->data);
|
||||
vector->data = 0;
|
||||
clear_all();
|
||||
}
|
||||
|
||||
typedef char v16c __attribute__((vector_size(16)));
|
||||
typedef char v16cu __attribute__((vector_size(16), aligned(1)));
|
||||
FN(int, key_name_to_code, (void *key));
|
||||
FN(void *, key_code_to_index, (int idx));
|
||||
FN(unsigned char, vector_grow, (void *vec, unsigned long long elem, unsigned long long n, void *cap));
|
||||
FN(void *, libc_bsearch, (const void *key, const void *base, unsigned long long n, unsigned long long size, int (*cmp)(const void *, const void *)));
|
||||
extern "C" unsigned long long id_table_count, id_table_capacity;
|
||||
extern "C" Ent *id_table_entries;
|
||||
// insert a block-name entry into the sorted search list (and drop all cached meshes)
|
||||
extern "C" unsigned char insert_block_name_entry_sorted(Vec *vector, void *name, long long *src) {
|
||||
int id = key_name_to_code(name);
|
||||
if (id == -1) return 0;
|
||||
if (id_table_entries && libc_bsearch(&id, id_table_entries, id_table_count, 0x18, (int (*)(const void *, const void *))compare_vec24_key))
|
||||
return 0;
|
||||
unsigned long long idx = 0;
|
||||
if (vector->size) {
|
||||
for (idx = 0; idx < vector->size; idx++)
|
||||
if (id <= vector->data[idx].key) break;
|
||||
}
|
||||
void *nm = key_code_to_index(id);
|
||||
long long q = src[0];
|
||||
int d = (int)src[1];
|
||||
int k = id;
|
||||
clear_all();
|
||||
unsigned char ok = vector_grow(&id_table_entries, 0x18, id_table_count, &id_table_capacity);
|
||||
if (!ok) return ok;
|
||||
Ent *base = id_table_entries;
|
||||
libc_memmove(base + idx + 1, base + idx, (id_table_count - idx) * 0x18);
|
||||
base[idx].name = nm;
|
||||
base[idx].q = q;
|
||||
base[idx].d = d;
|
||||
base[idx].key = k;
|
||||
id_table_count++;
|
||||
return ok;
|
||||
}
|
||||
|
||||
struct Blk { unsigned char x, y, z, padding_03, padding_04, padding_05, padding_06, padding_07; Ent *ent; };
|
||||
struct BMesh { char padding_00[8]; unsigned long long n, cap; Blk *b; };
|
||||
FN(BMesh *, find_insert_mesh_record_chunk, (unsigned long long key));
|
||||
// scan a 16x16x16 chunk section for every searched block id and append the hits to the section mesh
|
||||
extern "C" void scan_16x16x16_chunk_section_searched(int cx, int sy, unsigned cz, const unsigned char *ids, BMesh **out) {
|
||||
if (!id_table_count) return;
|
||||
unsigned long long key = (unsigned)cx | ((unsigned long long)cz << 32);
|
||||
int ybase = sy << 4;
|
||||
for (unsigned long long e = 0; e < id_table_count; e++) {
|
||||
Ent *ent = &id_table_entries[e];
|
||||
char kb = (char)ent->key;
|
||||
v16c want = {kb, kb, kb, kb, kb, kb, kb, kb, kb, kb, kb, kb, kb, kb, kb, kb};
|
||||
for (unsigned long long r = 0; r != 0x100; r++) {
|
||||
v16c row = *(const v16cu *)(ids + r * 16);
|
||||
unsigned mask = __builtin_ia32_pmovmskb128((v16c)(row == want));
|
||||
if (!mask) continue;
|
||||
unsigned char y = (unsigned char)(((unsigned)(r << 4) >> 8) + ybase);
|
||||
unsigned char z = (unsigned char)(r & 0xf);
|
||||
for (int x = 0; x != 0x10; x++) {
|
||||
if (!((mask >> x) & 1)) continue;
|
||||
BMesh *m = *out;
|
||||
if (!m) {
|
||||
m = find_insert_mesh_record_chunk(key);
|
||||
*out = m;
|
||||
if (!m) return;
|
||||
}
|
||||
if (!vector_grow(&m->b, 0x10, m->n, &m->cap)) return;
|
||||
Blk *b = &m->b[m->n];
|
||||
b->x = (unsigned char)x; b->y = y; b->z = z;
|
||||
b->padding_03 = 0; b->padding_04 = 0; b->padding_05 = 0; b->padding_06 = 0; b->padding_07 = 0;
|
||||
b->ent = ent;
|
||||
m->n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef short v8s __attribute__((vector_size(16)));
|
||||
typedef short v8su __attribute__((vector_size(16), aligned(1)));
|
||||
// same scan for 16-bit block data (id << 4 | meta)
|
||||
extern "C" void scan_16_bit_block_data(int cx, int sy, unsigned cz, const unsigned short *ids, BMesh **out) {
|
||||
if (!id_table_count) return;
|
||||
unsigned long long key = (unsigned)cx | ((unsigned long long)cz << 32);
|
||||
int ybase = sy << 4;
|
||||
for (unsigned long long e = 0; e < id_table_count; e++) {
|
||||
Ent *ent = &id_table_entries[e];
|
||||
short kw = (short)ent->key;
|
||||
v8s want = {kw, kw, kw, kw, kw, kw, kw, kw};
|
||||
#pragma clang loop unroll(disable) vectorize(disable)
|
||||
for (unsigned long long r = 0; r != 0x200; r++) {
|
||||
v8s row = *(const v8su *)(ids + r * 8);
|
||||
unsigned mask = __builtin_ia32_pmovmskb128((v16c)((row >> 4) == want));
|
||||
if (!mask) continue;
|
||||
unsigned char x = (unsigned char)((r & 1) << 3);
|
||||
unsigned char y = (unsigned char)(((unsigned)(r * 8) >> 8) + ybase);
|
||||
unsigned char z = (unsigned char)((r >> 1) & 0xf);
|
||||
#pragma clang loop unroll(disable) vectorize(disable)
|
||||
for (int w = 0; w != 0x10; w += 2, x++) {
|
||||
if (!((mask >> w) & 3)) continue;
|
||||
BMesh *m = *out;
|
||||
if (!m) {
|
||||
m = find_insert_mesh_record_chunk(key);
|
||||
*out = m;
|
||||
if (!m) return;
|
||||
}
|
||||
if (!vector_grow(&m->b, 0x10, m->n, &m->cap)) return;
|
||||
Blk *b = &m->b[m->n];
|
||||
b->x = x; b->y = y; b->z = z;
|
||||
b->padding_03 = 0; b->padding_04 = 0; b->padding_05 = 0; b->padding_06 = 0; b->padding_07 = 0;
|
||||
b->ent = ent;
|
||||
m->n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" unsigned long long mesh_vector_capacity;
|
||||
// find or insert the mesh record of chunk section `key` (sorted by x, then z)
|
||||
extern "C" BMesh *find_insert_mesh_record_chunk(unsigned long long key) {
|
||||
Lists tmp = {};
|
||||
*(unsigned long long *)&tmp = key;
|
||||
if (block_esp_mesh_count_capacity.data) {
|
||||
void *hit = libc_bsearch(&tmp, block_esp_mesh_count_capacity.data, block_esp_mesh_count_capacity.size, 0x50,
|
||||
(int (*)(const void *, const void *))compare_int_ids);
|
||||
if (hit) return (BMesh *)hit;
|
||||
}
|
||||
unsigned long long idx = 0;
|
||||
if (block_esp_mesh_count_capacity.size) {
|
||||
for (idx = 0; idx < block_esp_mesh_count_capacity.size; idx++) {
|
||||
const int *e = (const int *)&block_esp_mesh_count_capacity.data[idx];
|
||||
int c = (int)key - e[0];
|
||||
if (c == 0) c = (int)(key >> 32) - e[1];
|
||||
if (c <= 0) break;
|
||||
}
|
||||
}
|
||||
if (!vector_grow(&block_esp_mesh_count_capacity.data, 0x50, block_esp_mesh_count_capacity.size, &mesh_vector_capacity)) return 0;
|
||||
Lists *base = block_esp_mesh_count_capacity.data;
|
||||
libc_memmove(base + idx + 1, base + idx, (block_esp_mesh_count_capacity.size - idx) * 0x50);
|
||||
*(unsigned long long *)&base[idx] = key;
|
||||
base[idx].a = V3{}; base[idx].b = V3{}; base[idx].c = V3{};
|
||||
block_esp_mesh_count_capacity.size++;
|
||||
return (BMesh *)&block_esp_mesh_count_capacity.data[idx];
|
||||
}
|
||||
|
||||
// ---- world events: chunk unload (0) / load (1) rebuild the chunk's meshes, block change (2) edits one ----
|
||||
#include "slinky/jids.h"
|
||||
struct WEv { Jvm *jvm; jobject world; unsigned type; int a, b, c, id; };
|
||||
FN(jobject, java_world_get_chunk_from_chunk_coords, (Jvm *jvm, jobject chunk, int cx, int cz));
|
||||
FN(jobject, java_get_chunk_storage_arrays, (Jvm *jvm, jobject chunk));
|
||||
FN(jobject, java_get_id_b20, (Jvm *jvm, jobject sec));
|
||||
FN(int, chunk_coordinate, (int x));
|
||||
FN(int, block_coordinate, (int x));
|
||||
FN(void, rebuild_block_esp_mesh, (BMesh *m));
|
||||
FN(void, free_block_search_section_lists, (Lists *l));
|
||||
extern "C" void (*block_esp_block_scan_callback)(int cx, int sy, int cz, void *ids, BMesh **out);
|
||||
static inline __attribute__((always_inline)) void drop_rec(Lists *dst) {
|
||||
unsigned long long i = dst - block_esp_mesh_count_capacity.data;
|
||||
block_esp_mesh_count_capacity.size--;
|
||||
libc_memmove(dst, dst + 1, (block_esp_mesh_count_capacity.size - i) * 0x50);
|
||||
}
|
||||
extern "C" void equipment_event(WEv *ev) {
|
||||
if (ev->type >= 2) {
|
||||
if (ev->type != 2) return;
|
||||
int id = ev->id;
|
||||
Ent *ent = id_table_entries ? (Ent *)libc_bsearch(&id, id_table_entries, id_table_count, 0x18,
|
||||
(int (*)(const void *, const void *))compare_vec24_key)
|
||||
: 0;
|
||||
int cx = chunk_coordinate(ev->a), cz = chunk_coordinate(ev->c);
|
||||
unsigned long long key = (unsigned)cx | (unsigned long long)(unsigned)cz << 32;
|
||||
Lists tmp = {};
|
||||
*(unsigned long long *)&tmp = key;
|
||||
BMesh *m = block_esp_mesh_count_capacity.data ? (BMesh *)libc_bsearch(&tmp, block_esp_mesh_count_capacity.data, block_esp_mesh_count_capacity.size, 0x50,
|
||||
(int (*)(const void *, const void *))compare_int_ids)
|
||||
: 0;
|
||||
unsigned char lx = ev->a - block_coordinate(cx), lz = ev->c - block_coordinate(cz), y = (unsigned char)ev->b;
|
||||
if (m) {
|
||||
unsigned long long n = m->n;
|
||||
for (unsigned long long i = 0; i < n; i++) {
|
||||
Blk *dst = &m->b[i];
|
||||
if (dst->x != lx || dst->y != y || dst->z != lz) continue;
|
||||
if (ent) {
|
||||
dst->ent = ent;
|
||||
} else {
|
||||
m->n = --n;
|
||||
libc_memmove(dst, dst + 1, (n - i) * sizeof(Blk));
|
||||
if (!m->n) {
|
||||
free_block_search_section_lists((Lists *)m);
|
||||
drop_rec((Lists *)m);
|
||||
return;
|
||||
}
|
||||
}
|
||||
rebuild_block_esp_mesh(m);
|
||||
return;
|
||||
}
|
||||
if (!ent) return;
|
||||
} else {
|
||||
if (!ent) return;
|
||||
unsigned long long idx = 0;
|
||||
if (block_esp_mesh_count_capacity.size) {
|
||||
for (idx = 0; idx < block_esp_mesh_count_capacity.size; idx++) {
|
||||
const int *e = (const int *)&block_esp_mesh_count_capacity.data[idx];
|
||||
int c = cx - e[0];
|
||||
if (c == 0) c = cz - e[1];
|
||||
if (c <= 0) break;
|
||||
}
|
||||
}
|
||||
if (!vector_grow(&block_esp_mesh_count_capacity.data, 0x50, block_esp_mesh_count_capacity.size, &mesh_vector_capacity)) return;
|
||||
Lists *base = block_esp_mesh_count_capacity.data;
|
||||
libc_memmove(base + idx + 1, base + idx, (block_esp_mesh_count_capacity.size - idx) * 0x50);
|
||||
*(unsigned long long *)&base[idx] = key;
|
||||
base[idx].a = V3{}; base[idx].b = V3{}; base[idx].c = V3{};
|
||||
block_esp_mesh_count_capacity.size++;
|
||||
if (!block_esp_mesh_count_capacity.data) return;
|
||||
m = (BMesh *)&block_esp_mesh_count_capacity.data[idx];
|
||||
}
|
||||
Blk nb = {lx, y, lz, 0, 0, 0, 0, 0, ent};
|
||||
if (!vector_grow(&m->b, 0x10, m->n, &m->cap)) return;
|
||||
m->b[m->n] = nb;
|
||||
m->n++;
|
||||
rebuild_block_esp_mesh(m);
|
||||
return;
|
||||
}
|
||||
Lists tmp = {};
|
||||
*(unsigned long long *)&tmp = *(unsigned long long *)&ev->a;
|
||||
if (block_esp_mesh_count_capacity.data) {
|
||||
Lists *r = (Lists *)libc_bsearch(&tmp, block_esp_mesh_count_capacity.data, block_esp_mesh_count_capacity.size, 0x50,
|
||||
(int (*)(const void *, const void *))compare_int_ids);
|
||||
if (r) {
|
||||
clear_lists(r);
|
||||
drop_rec(r);
|
||||
}
|
||||
}
|
||||
if (ev->type != 1) return;
|
||||
Jvm *jvm = ev->jvm;
|
||||
int cx = ev->a, cz = ev->b;
|
||||
jobject chunk = java_world_get_chunk_from_chunk_coords(jvm, ev->world, cx, cz);
|
||||
if (!chunk) return;
|
||||
BMesh *out = 0;
|
||||
JNIEnv *env = jvm->env;
|
||||
jobjectArray secs = (jobjectArray)java_get_chunk_storage_arrays(jvm, chunk);
|
||||
if (!secs) return;
|
||||
for (int i = 0; i != 16; i++) {
|
||||
jobject sec = (*env)->GetObjectArrayElement(env, secs, i);
|
||||
if (!sec) continue;
|
||||
jarray ids = (jarray)java_get_id_b20(jvm, sec);
|
||||
if (!ids) continue;
|
||||
void *p = (*env)->GetPrimitiveArrayCritical(env, ids, 0);
|
||||
if (!p) continue;
|
||||
block_esp_block_scan_callback(cx, i, cz, p, &out);
|
||||
(*env)->ReleasePrimitiveArrayCritical(env, ids, p, 0);
|
||||
}
|
||||
if (out) rebuild_block_esp_mesh(out);
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// 0x48000: chest-GUI check (localized title compare) and randomized click delay
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jids.h"
|
||||
struct Rot { float yaw, pitch; };
|
||||
FN(void *, get_current_screen, (void *jvm, void *object));
|
||||
FN(unsigned char, java_jni_is_instance_of_gui_chest, (void *jvm, void *candidate));
|
||||
FN(void *, java_get_gui_chest_lower_chest_inventory, (void *jvm, void *gui_chest));
|
||||
FN(void *, java_jni_is_instance_of_inventory_basic, (void *jvm, void *candidate));
|
||||
FN(void *, java_get_inventory_basic, (void *jvm, void *inventory_basic));
|
||||
FN(void *, java_call_static_stat_collector_translate_to_local, (void *jvm, void *stat_collector));
|
||||
FN(Rot, rot_make_floats, (float a, float b));
|
||||
FN(float, random_rotation, (Rot rotation));
|
||||
FN(void, slot_index, (int id));
|
||||
FN(int, libc_strncmp_alt, (const void *left, const void *right, unsigned long long n));
|
||||
extern "C" unsigned char chest_stealer_require_vanilla_title_value;
|
||||
extern "C" float chest_stealer_delay_after_open_value, chest_stealer_steal_delay, range_low_1_in_chest_gui, random_range_bound_1_5_in_chest_gui;
|
||||
extern "C" const char str_container_chest[], str_container_chestdouble[];
|
||||
|
||||
// does the Java string equal the translation of `key`?
|
||||
extern "C" bool java_string_equal_translation_key(Jvm *jvm, jstring s, const char *key) {
|
||||
JNIEnv *env = jvm->env;
|
||||
if (!s || !key) return false;
|
||||
jstring key_string = (*env)->NewStringUTF(env, key);
|
||||
jstring t = key_string ? (jstring)java_call_static_stat_collector_translate_to_local(jvm, key_string) : 0;
|
||||
if (key_string) (*env)->DeleteLocalRef(env, key_string);
|
||||
if (!t) return false;
|
||||
jsize len = (*env)->GetStringLength(env, s);
|
||||
if ((*env)->GetStringLength(env, t) != len) {
|
||||
(*env)->DeleteLocalRef(env, t);
|
||||
return false;
|
||||
}
|
||||
const jchar *a = (const jchar *)(*env)->GetStringChars(env, s, 0);
|
||||
const jchar *b = a ? (const jchar *)(*env)->GetStringChars(env, t, 0) : 0;
|
||||
bool eq = a && b && libc_strncmp_alt(a, b, (long long)len * 2) == 0;
|
||||
if (a) (*env)->ReleaseStringChars(env, s, a);
|
||||
if (b) (*env)->ReleaseStringChars(env, t, b);
|
||||
(*env)->DeleteLocalRef(env, t);
|
||||
return eq;
|
||||
}
|
||||
|
||||
struct Ev { char padding_00[8]; Ctx *ctx; };
|
||||
extern "C" void chest_gui_tick(Ev *event) {
|
||||
Ctx *ctx = event->ctx;
|
||||
void *screen = get_current_screen(ctx->jvm, ctx->minecraft);
|
||||
if (screen && java_jni_is_instance_of_gui_chest(ctx->jvm, screen)) {
|
||||
if (chest_stealer_require_vanilla_title_value != 1) return;
|
||||
void *jvm = ctx->jvm;
|
||||
void *inventory_basic = java_jni_is_instance_of_inventory_basic(jvm, java_get_gui_chest_lower_chest_inventory(jvm, screen));
|
||||
if (inventory_basic) {
|
||||
void *name = java_get_inventory_basic(ctx->jvm, inventory_basic);
|
||||
if (java_string_equal_translation_key((Jvm *)ctx->jvm, (jstring)name, str_container_chest)) return;
|
||||
if (java_string_equal_translation_key((Jvm *)ctx->jvm, (jstring)name, str_container_chestdouble)) return;
|
||||
}
|
||||
}
|
||||
chest_stealer_steal_delay = chest_stealer_delay_after_open_value;
|
||||
float lo = range_low_1_in_chest_gui;
|
||||
|
||||
chest_stealer_steal_delay = random_rotation(rot_make_floats(lo, random_range_bound_1_5_in_chest_gui)) * chest_stealer_steal_delay;
|
||||
slot_index(0xd);
|
||||
}
|
||||
|
||||
// item id -> {tool kind, material tier} (kind 1/14/16/22 = axe/shovel/pickaxe/sword, 15 = shears)
|
||||
struct ToolInfo { int kind, tier; };
|
||||
extern "C" ToolInfo chest_gui_title_id(int id) {
|
||||
switch (id) {
|
||||
case 0x100: return ToolInfo{16, 3};
|
||||
case 0x101: return ToolInfo{14, 3};
|
||||
case 0x102: return ToolInfo{1, 3};
|
||||
case 0x10b: return ToolInfo{22, 3};
|
||||
case 0x10c: return ToolInfo{22, 1};
|
||||
case 0x10d: return ToolInfo{16, 1};
|
||||
case 0x10e: return ToolInfo{14, 1};
|
||||
case 0x10f: return ToolInfo{1, 1};
|
||||
case 0x110: return ToolInfo{22, 2};
|
||||
case 0x111: return ToolInfo{16, 2};
|
||||
case 0x112: return ToolInfo{14, 2};
|
||||
case 0x113: return ToolInfo{1, 2};
|
||||
case 0x114: return ToolInfo{22, 5};
|
||||
case 0x115: return ToolInfo{16, 5};
|
||||
case 0x116: return ToolInfo{14, 5};
|
||||
case 0x117: return ToolInfo{1, 5};
|
||||
case 0x11b: return ToolInfo{22, 4};
|
||||
case 0x11c: return ToolInfo{16, 4};
|
||||
case 0x11d: return ToolInfo{14, 4};
|
||||
case 0x11e: return ToolInfo{1, 4};
|
||||
case 0x167: return ToolInfo{15, 1};
|
||||
default: return ToolInfo{0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
FN(int, java_call_nbttag_list_tag_count, (void *jvm, void *list));
|
||||
FN(void *, java_call_nbttag_list_get_compound_tag_at, (void *jvm, void *list, int i));
|
||||
FN(short, java_jni_call_short_method_nbttag_compound_get_short, (void *jvm, void *tag, void *key));
|
||||
extern "C" const char str_id_in_chest_gui[], str_lvl_in_chest_gui[];
|
||||
// weighted sum of enchantment levels (weights indexed by enchantment id 0..62)
|
||||
extern "C" unsigned short weighted_sum_enchantment_levels(Ctx *ctx, void *list, const unsigned char *weights) {
|
||||
if (!list) return 0;
|
||||
JNIEnv *env = ((Jvm *)ctx->jvm)->env;
|
||||
jstring idS = (*env)->NewStringUTF(env, str_id_in_chest_gui);
|
||||
if (!idS) return 0;
|
||||
jstring lvlS = (*env)->NewStringUTF(env, str_lvl_in_chest_gui);
|
||||
if (!lvlS) {
|
||||
(*env)->DeleteLocalRef(env, idS);
|
||||
return 0;
|
||||
}
|
||||
int n = java_call_nbttag_list_tag_count(ctx->jvm, list);
|
||||
if (n <= 0) {
|
||||
(*env)->DeleteLocalRef(env, idS);
|
||||
(*env)->DeleteLocalRef(env, lvlS);
|
||||
return 0;
|
||||
}
|
||||
unsigned short sum = 0;
|
||||
for (int i = 0; i != n; i++) {
|
||||
void *tag = java_call_nbttag_list_get_compound_tag_at(ctx->jvm, list, i);
|
||||
unsigned short nbttag_compound_get_short = (unsigned short)java_jni_call_short_method_nbttag_compound_get_short(ctx->jvm, tag, idS);
|
||||
if (nbttag_compound_get_short > 0x3e) {
|
||||
(*env)->DeleteLocalRef(env, tag);
|
||||
continue;
|
||||
}
|
||||
unsigned short w = weights[nbttag_compound_get_short];
|
||||
if (w == 0) {
|
||||
(*env)->DeleteLocalRef(env, tag);
|
||||
continue;
|
||||
}
|
||||
short lvl = java_jni_call_short_method_nbttag_compound_get_short(ctx->jvm, tag, lvlS);
|
||||
sum += lvl < 0 ? 0 : (unsigned short)(w * lvl);
|
||||
(*env)->DeleteLocalRef(env, tag);
|
||||
}
|
||||
(*env)->DeleteLocalRef(env, idS);
|
||||
(*env)->DeleteLocalRef(env, lvlS);
|
||||
return sum;
|
||||
}
|
||||
|
||||
extern "C" const unsigned char enchant_level_weights_c[], enchant_level_weights_b[], enchant_level_weights_a[], tier_base_levels[];
|
||||
// score of an item for an inventory-sorter category (higher = better); cases decoded from the image jump tables
|
||||
extern "C" int score_item_inventory_sorter_category(Ctx *ctx, int cat, int id, unsigned meta, void *nbt, unsigned char b) {
|
||||
switch (cat) {
|
||||
case 1: case 14: case 15: case 16: {
|
||||
ToolInfo ti = chest_gui_title_id(id);
|
||||
if (cat != ti.kind) return 0;
|
||||
int base = ti.tier * 100;
|
||||
return weighted_sum_enchantment_levels(ctx, nbt, enchant_level_weights_c) + base;
|
||||
}
|
||||
case 2:
|
||||
switch (id) {
|
||||
case 0x1: case 0x2: case 0x3: case 0x4: case 0x5: case 0x11: case 0x12: case 0x14: case 0x18: case 0x23:
|
||||
case 0x2d: case 0x31: case 0x4e: case 0x57: case 0x5f: case 0x62: case 0x79: case 0x7d: case 0x85:
|
||||
case 0x9b: case 0x9f: case 0xa2:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
case 3:
|
||||
if (id != 0x105) return 0;
|
||||
return weighted_sum_enchantment_levels(ctx, nbt, enchant_level_weights_b) + 1;
|
||||
case 4: return id == 0x36;
|
||||
case 5: return id == 0x170 ? 2 - b : 0;
|
||||
case 6: return id == 0x181;
|
||||
case 7: return id == 0x15a;
|
||||
case 8: return id == 0x118;
|
||||
case 9:
|
||||
if (id != 0x142) return 0;
|
||||
return (meta == 1) + 1;
|
||||
case 10: case 11: case 19: case 21: {
|
||||
if (id != 0x175) return 0;
|
||||
unsigned short base;
|
||||
switch (meta & 0xf) {
|
||||
case 1: base = 1; if (cat != 10) return 0; break;
|
||||
case 2: base = 1; if (cat != 19) return 0; break;
|
||||
case 5: base = 2; if (cat != 10) return 0; break;
|
||||
case 9: base = 1; if (cat != 21) return 0; break;
|
||||
case 11: base = 1; if (cat != 11) return 0; break;
|
||||
default: return 0;
|
||||
}
|
||||
return (unsigned short)(base + ((meta >> 6) & 1) + ((meta >> 5) & 1));
|
||||
}
|
||||
case 12: return id == 0x41;
|
||||
case 13: return id == 0x147;
|
||||
case 17: return id == 0x14c || id == 0x158;
|
||||
case 18: return id == 0x19d || id == 0x11a;
|
||||
case 20: return id == 0x103;
|
||||
case 22: {
|
||||
ToolInfo ti = chest_gui_title_id(id);
|
||||
if (ti.kind != 0x16) return 0;
|
||||
int base = tier_base_levels[ti.tier] * 40;
|
||||
return weighted_sum_enchantment_levels(ctx, nbt, enchant_level_weights_a) + base + 0xa0;
|
||||
}
|
||||
case 23: return id == 0x2e;
|
||||
case 24: return id == 0x146;
|
||||
case 25: {
|
||||
ToolInfo ti = chest_gui_title_id(id);
|
||||
unsigned short k;
|
||||
switch (ti.kind) {
|
||||
case 1: k = 3; break;
|
||||
case 14: k = 2; break;
|
||||
case 16: k = 1; break;
|
||||
case 22: k = 4; break;
|
||||
default: k = 0; break;
|
||||
}
|
||||
int base = (k + tier_base_levels[ti.tier]) * 40;
|
||||
return weighted_sum_enchantment_levels(ctx, nbt, enchant_level_weights_a) + base;
|
||||
}
|
||||
case 26: return id == 0x23;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// 0x4f000: cJSON (upstream source) - Add*ToObject, Create*
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/cjson.h"
|
||||
|
||||
extern "C" cJSON *cjson_add_string_to_object(cJSON *const object, const char *const name, const char *const string) { // cJSON_AddStringToObject
|
||||
cJSON *string_item = cJSON_CreateString(string);
|
||||
if (add_item_to_object(object, name, string_item, &global_hooks, false)) return string_item;
|
||||
cjson_delete(string_item);
|
||||
return NULL;
|
||||
}
|
||||
extern "C" cJSON *cjson_create_string(const char *string) { return cJSON_CreateString(string); } // cJSON_CreateString
|
||||
extern "C" cJSON *cjson_add_bool_to_object(cJSON *const object, const char *const name) { // cJSON_AddObjectToObject
|
||||
cJSON *object_item = cJSON_CreateObject();
|
||||
if (add_item_to_object(object, name, object_item, &global_hooks, false)) return object_item;
|
||||
cjson_delete(object_item);
|
||||
return NULL;
|
||||
}
|
||||
extern "C" cJSON *cjson_create_object(void) { return cJSON_CreateObject(); } // cJSON_CreateObject
|
||||
extern "C" cJSON *cjson_add_array_to_object(cJSON *const object, const char *const name) { // cJSON_AddArrayToObject
|
||||
cJSON *array = cJSON_CreateArray();
|
||||
if (add_item_to_object(object, name, array, &global_hooks, false)) return array;
|
||||
cjson_delete(array);
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// 0x4d000: cJSON (upstream source, MIT) - core: delete, parse entry, print entry, accessors.
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/cjson.h"
|
||||
|
||||
extern "C" char cjson_core_buffer[];
|
||||
FN(void *, libc_register_onexit_function, (void *jvm, void *b));
|
||||
extern "C" void *cjson_strdup(void *x) { return libc_register_onexit_function(cjson_core_buffer, x); }
|
||||
|
||||
extern "C" char *cjson_get_string_value(const cJSON *item) { // cJSON_GetStringValue
|
||||
if (!cJSON_IsString(item)) return NULL;
|
||||
return item->valuestring;
|
||||
}
|
||||
extern "C" cJSON_bool cjson_is_string(const cJSON *item) { return cJSON_IsString(item); }
|
||||
extern "C" double cjson_get_number_value(const cJSON *item) { // cJSON_GetNumberValue
|
||||
double d = not_a_number;
|
||||
if (cJSON_IsNumber(item)) d = item->valuedouble;
|
||||
return d;
|
||||
}
|
||||
extern "C" cJSON_bool cjson_is_number(const cJSON *item) { return cJSON_IsNumber(item); }
|
||||
|
||||
extern "C" void cjson_delete(cJSON *item) { // cJSON_Delete
|
||||
cJSON *next = NULL;
|
||||
while (item != NULL) {
|
||||
next = item->next;
|
||||
if (!(item->type & cJSON_IsReference) && (item->child != NULL)) cjson_delete(item->child);
|
||||
if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) global_hooks.deallocate(item->valuestring);
|
||||
if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) global_hooks.deallocate(item->string);
|
||||
global_hooks.deallocate(item);
|
||||
item = next;
|
||||
}
|
||||
}
|
||||
extern "C" void cjson_free_string(void *object) { global_hooks.deallocate(object); } // cJSON_free
|
||||
|
||||
static parse_buffer *buffer_skip_whitespace(parse_buffer *const buffer) {
|
||||
if ((buffer == NULL) || (buffer->content == NULL)) return NULL;
|
||||
if (cannot_access_at_index(buffer, 0)) return buffer;
|
||||
while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) buffer->offset++;
|
||||
if (buffer->offset == buffer->length) buffer->offset--;
|
||||
return buffer;
|
||||
}
|
||||
// buffer_skip_whitespace with its unused return value dropped (the original compiler did this for the
|
||||
// out-of-line copy).
|
||||
extern "C" void buffer_skip_whitespace_unused_return(parse_buffer *const buffer) {
|
||||
if ((buffer == NULL) || (buffer->content == NULL)) return;
|
||||
if (cannot_access_at_index(buffer, 0)) return;
|
||||
while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) buffer->offset++;
|
||||
if (buffer->offset == buffer->length) buffer->offset--;
|
||||
}
|
||||
|
||||
static parse_buffer *skip_utf8_bom(parse_buffer *const buffer) {
|
||||
if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) return NULL;
|
||||
if (can_access_at_index(buffer, 4) && (__builtin_strncmp((const char *)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0))
|
||||
buffer->offset += 3;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
FN(cJSON_bool, cjson_parse_value, (cJSON *const item, parse_buffer *const input_buffer)); // parse_value
|
||||
|
||||
extern "C" cJSON *cjson_parse_with_options(const char *value, size_t buffer_length, const char **return_parse_end,
|
||||
cJSON_bool require_null_terminated) { // cJSON_ParseWithLengthOpts
|
||||
parse_buffer buffer = {0, 0, 0, 0, {0, 0, 0}};
|
||||
cJSON *item = NULL;
|
||||
global_error.json = NULL;
|
||||
global_error.position = 0;
|
||||
if (value == NULL || 0 == buffer_length) goto fail;
|
||||
buffer.content = (const unsigned char *)value;
|
||||
buffer.length = buffer_length;
|
||||
buffer.offset = 0;
|
||||
buffer.hooks = global_hooks;
|
||||
item = cJSON_New_Item(&global_hooks);
|
||||
if (item == NULL) goto fail;
|
||||
if (!cjson_parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) goto fail;
|
||||
if (require_null_terminated) {
|
||||
buffer_skip_whitespace(&buffer);
|
||||
if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') goto fail;
|
||||
}
|
||||
if (return_parse_end) *return_parse_end = (const char *)buffer_at_offset(&buffer);
|
||||
return item;
|
||||
fail:
|
||||
if (item != NULL) cjson_delete(item);
|
||||
if (value != NULL) {
|
||||
error local_error;
|
||||
local_error.json = (const unsigned char *)value;
|
||||
local_error.position = 0;
|
||||
if (buffer.offset < buffer.length) local_error.position = buffer.offset;
|
||||
else if (buffer.length > 0) local_error.position = buffer.length - 1;
|
||||
if (return_parse_end != NULL) *return_parse_end = (const char *)local_error.json + local_error.position;
|
||||
global_error = local_error;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
extern "C" cJSON *cjson_parse(const char *value) { // cJSON_Parse
|
||||
size_t buffer_length;
|
||||
if (NULL == value) return NULL;
|
||||
buffer_length = strlen(value) + sizeof("");
|
||||
return cjson_parse_with_options(value, buffer_length, 0, 0);
|
||||
}
|
||||
|
||||
FN(cJSON_bool, cjson_print_value, (const cJSON *const item, printbuffer *const output_buffer)); // print_value
|
||||
|
||||
extern "C" unsigned char *cjson_print(const cJSON *const item, cJSON_bool format) { // print (global hooks)
|
||||
static const size_t default_buffer_size = 256;
|
||||
printbuffer buffer[1];
|
||||
unsigned char *printed = NULL;
|
||||
const internal_hooks *const hooks = &global_hooks;
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
buffer->buffer = (unsigned char *)hooks->allocate(default_buffer_size);
|
||||
buffer->length = default_buffer_size;
|
||||
buffer->format = format;
|
||||
buffer->hooks = *hooks;
|
||||
if (buffer->buffer == NULL) goto fail;
|
||||
if (!cjson_print_value(item, buffer)) goto fail;
|
||||
update_offset(buffer);
|
||||
if (hooks->reallocate != NULL) {
|
||||
printed = (unsigned char *)hooks->reallocate(buffer->buffer, buffer->offset + 1);
|
||||
if (printed == NULL) goto fail;
|
||||
buffer->buffer = NULL;
|
||||
} else {
|
||||
printed = (unsigned char *)hooks->allocate(buffer->offset + 1);
|
||||
if (printed == NULL) goto fail;
|
||||
memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1));
|
||||
printed[buffer->offset] = '\0';
|
||||
hooks->deallocate(buffer->buffer);
|
||||
}
|
||||
return printed;
|
||||
fail:
|
||||
if (buffer->buffer != NULL) hooks->deallocate(buffer->buffer);
|
||||
return NULL;
|
||||
}
|
||||
extern "C" char *cjson_print_unformatted(const cJSON *item) { return (char *)cjson_print(item, false); } // cJSON_PrintUnformatted
|
||||
@@ -0,0 +1,237 @@
|
||||
// 0x50000: cJSON (upstream source) - Duplicate, Is*, Compare
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/cjson.h"
|
||||
|
||||
extern "C" cJSON *cjson_duplicate(const cJSON *item, cJSON_bool recurse) { // cJSON_Duplicate
|
||||
cJSON *newitem = NULL;
|
||||
cJSON *child = NULL;
|
||||
cJSON *next = NULL;
|
||||
cJSON *newchild = NULL;
|
||||
if (!item) goto fail;
|
||||
newitem = cJSON_New_Item(&global_hooks);
|
||||
if (!newitem) goto fail;
|
||||
newitem->type = item->type & (~cJSON_IsReference);
|
||||
newitem->valueint = item->valueint;
|
||||
newitem->valuedouble = item->valuedouble;
|
||||
if (item->valuestring) {
|
||||
newitem->valuestring = (char *)cJSON_strdup((unsigned char *)item->valuestring, &global_hooks);
|
||||
if (!newitem->valuestring) goto fail;
|
||||
}
|
||||
if (item->string) {
|
||||
newitem->string = (item->type & cJSON_StringIsConst) ? item->string
|
||||
: (char *)cJSON_strdup((unsigned char *)item->string, &global_hooks);
|
||||
if (!newitem->string) goto fail;
|
||||
}
|
||||
if (!recurse) return newitem;
|
||||
child = item->child;
|
||||
while (child != NULL) {
|
||||
newchild = cjson_duplicate(child, true);
|
||||
if (!newchild) goto fail;
|
||||
if (next != NULL) {
|
||||
next->next = newchild;
|
||||
newchild->prev = next;
|
||||
next = newchild;
|
||||
} else {
|
||||
newitem->child = newchild;
|
||||
next = newchild;
|
||||
}
|
||||
child = child->next;
|
||||
}
|
||||
if (newitem && newitem->child) newitem->child->prev = newchild;
|
||||
return newitem;
|
||||
fail:
|
||||
if (newitem != NULL) cjson_delete(newitem);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern "C" cJSON_bool cjson_is_true(const cJSON *const item) { // cJSON_IsTrue
|
||||
if (item == NULL) return false;
|
||||
return (item->type & 0xff) == cJSON_True;
|
||||
}
|
||||
extern "C" cJSON_bool cjson_is_bool(const cJSON *const item) { // cJSON_IsBool
|
||||
if (item == NULL) return false;
|
||||
return (item->type & (cJSON_True | cJSON_False)) != 0;
|
||||
}
|
||||
extern "C" cJSON_bool cjson_is_array(const cJSON *const item) { // cJSON_IsArray
|
||||
if (item == NULL) return false;
|
||||
return (item->type & 0xFF) == cJSON_Array;
|
||||
}
|
||||
extern "C" cJSON_bool cjson_is_object(const cJSON *const item) { // cJSON_IsObject
|
||||
if (item == NULL) return false;
|
||||
return (item->type & 0xFF) == cJSON_Object;
|
||||
}
|
||||
|
||||
typedef double v2d __attribute__((vector_size(16)));
|
||||
extern "C" v2d cjson_duplicate_fabs_mask; // fabs mask
|
||||
extern "C" double double_epsilon; // DBL_EPSILON
|
||||
static inline __attribute__((always_inline)) double cj_fabs(double x) {
|
||||
v2d v = {x, 0};
|
||||
v = (v2d)((__attribute__((vector_size(16))) long long)v & (__attribute__((vector_size(16))) long long)cjson_duplicate_fabs_mask);
|
||||
return v[0];
|
||||
}
|
||||
static inline __attribute__((always_inline)) cJSON_bool compare_double(double left, double right) {
|
||||
double maxVal = cj_fabs(left) > cj_fabs(right) ? cj_fabs(left) : cj_fabs(right);
|
||||
return (cj_fabs(left - right) <= maxVal * double_epsilon);
|
||||
}
|
||||
FN(cJSON *, cjson_get_object_item_case_option, (const cJSON *const object, const char *const name, const cJSON_bool case_sensitive)); // get_object_item
|
||||
|
||||
extern "C" cJSON_bool cjson_compare(const cJSON *const a, const cJSON *const b, const cJSON_bool case_sensitive) { // cJSON_Compare
|
||||
if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) return false;
|
||||
switch (a->type & 0xFF) {
|
||||
case cJSON_False: case cJSON_True: case cJSON_NULL: case cJSON_Number: case cJSON_String: case cJSON_Raw:
|
||||
case cJSON_Array: case cJSON_Object:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (a == b) return true;
|
||||
switch (a->type & 0xFF) {
|
||||
case cJSON_False: case cJSON_True: case cJSON_NULL:
|
||||
return true;
|
||||
case cJSON_Number:
|
||||
if (compare_double(a->valuedouble, b->valuedouble)) return true;
|
||||
return false;
|
||||
case cJSON_String: case cJSON_Raw:
|
||||
if ((a->valuestring == NULL) || (b->valuestring == NULL)) return false;
|
||||
if (strcmp(a->valuestring, b->valuestring) == 0) return true;
|
||||
return false;
|
||||
case cJSON_Array: {
|
||||
cJSON *a_element = a->child;
|
||||
cJSON *b_element = b->child;
|
||||
for (; (a_element != NULL) && (b_element != NULL);) {
|
||||
if (!cjson_compare(a_element, b_element, case_sensitive)) return false;
|
||||
a_element = a_element->next;
|
||||
b_element = b_element->next;
|
||||
}
|
||||
if (a_element != b_element) return false;
|
||||
return true;
|
||||
}
|
||||
case cJSON_Object: {
|
||||
cJSON *a_element = NULL;
|
||||
cJSON *b_element = NULL;
|
||||
for (a_element = a->child; a_element != NULL; a_element = a_element->next) {
|
||||
b_element = cjson_get_object_item_case_option(b, a_element->string, case_sensitive);
|
||||
if (b_element == NULL) return false;
|
||||
if (!cjson_compare(a_element, b_element, case_sensitive)) return false;
|
||||
}
|
||||
for (b_element = b->child; b_element != NULL; b_element = b_element->next) {
|
||||
a_element = cjson_get_object_item_case_option(a, b_element->string, case_sensitive);
|
||||
if (a_element == NULL) return false;
|
||||
if (!cjson_compare(b_element, a_element, case_sensitive)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
FN(unsigned, cjson_parse_hex4, (const unsigned char *const input)); // parse_hex4
|
||||
static inline __attribute__((always_inline)) unsigned char
|
||||
utf16_literal_to_utf8(const unsigned char *const input_pointer, const unsigned char *const input_end, unsigned char **output_pointer) {
|
||||
long unsigned int codepoint = 0;
|
||||
unsigned int first_code = 0;
|
||||
const unsigned char *first_sequence = input_pointer;
|
||||
unsigned char utf8_length = 0;
|
||||
unsigned char utf8_position = 0;
|
||||
unsigned char sequence_length = 0;
|
||||
unsigned char first_byte_mark = 0;
|
||||
if ((input_end - first_sequence) < 6) goto fail;
|
||||
first_code = cjson_parse_hex4(first_sequence + 2);
|
||||
if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) goto fail;
|
||||
if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) {
|
||||
const unsigned char *second_sequence = first_sequence + 6;
|
||||
unsigned int second_code = 0;
|
||||
sequence_length = 12;
|
||||
if ((input_end - second_sequence) < 6) goto fail;
|
||||
if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) goto fail;
|
||||
second_code = cjson_parse_hex4(second_sequence + 2);
|
||||
if ((second_code < 0xDC00) || (second_code > 0xDFFF)) goto fail;
|
||||
codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF));
|
||||
} else {
|
||||
sequence_length = 6;
|
||||
codepoint = first_code;
|
||||
}
|
||||
if (codepoint < 0x80) {
|
||||
utf8_length = 1;
|
||||
} else if (codepoint < 0x800) {
|
||||
utf8_length = 2;
|
||||
first_byte_mark = 0xC0;
|
||||
} else if (codepoint < 0x10000) {
|
||||
utf8_length = 3;
|
||||
first_byte_mark = 0xE0;
|
||||
} else if (codepoint <= 0x10FFFF) {
|
||||
utf8_length = 4;
|
||||
first_byte_mark = 0xF0;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) {
|
||||
(*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF);
|
||||
codepoint >>= 6;
|
||||
}
|
||||
if (utf8_length > 1) (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF);
|
||||
else (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F);
|
||||
*output_pointer += utf8_length;
|
||||
return sequence_length;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" cJSON_bool cjson_replace_item_in_object(cJSON *const item, parse_buffer *const input_buffer) { // parse_string
|
||||
const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1;
|
||||
const unsigned char *input_end = buffer_at_offset(input_buffer) + 1;
|
||||
unsigned char *output_pointer = NULL;
|
||||
unsigned char *output = NULL;
|
||||
if (buffer_at_offset(input_buffer)[0] != '\"') goto fail;
|
||||
{
|
||||
size_t allocation_length = 0;
|
||||
size_t skipped_bytes = 0;
|
||||
while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) {
|
||||
if (input_end[0] == '\\') {
|
||||
if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) goto fail;
|
||||
skipped_bytes++;
|
||||
input_end++;
|
||||
}
|
||||
input_end++;
|
||||
}
|
||||
if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) goto fail;
|
||||
allocation_length = (size_t)(input_end - buffer_at_offset(input_buffer)) - skipped_bytes;
|
||||
output = (unsigned char *)input_buffer->hooks.allocate(allocation_length + sizeof(""));
|
||||
if (output == NULL) goto fail;
|
||||
}
|
||||
output_pointer = output;
|
||||
while (input_pointer < input_end) {
|
||||
if (*input_pointer != '\\') {
|
||||
*output_pointer++ = *input_pointer++;
|
||||
} else {
|
||||
unsigned char sequence_length = 2;
|
||||
if ((input_end - input_pointer) < 1) goto fail;
|
||||
switch (input_pointer[1]) {
|
||||
case 'b': *output_pointer++ = '\b'; break;
|
||||
case 'f': *output_pointer++ = '\f'; break;
|
||||
case 'n': *output_pointer++ = '\n'; break;
|
||||
case 'r': *output_pointer++ = '\r'; break;
|
||||
case 't': *output_pointer++ = '\t'; break;
|
||||
case '\"': case '\\': case '/': *output_pointer++ = input_pointer[1]; break;
|
||||
case 'u':
|
||||
sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer);
|
||||
if (sequence_length == 0) goto fail;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
input_pointer += sequence_length;
|
||||
}
|
||||
}
|
||||
*output_pointer = '\0';
|
||||
item->type = cJSON_String;
|
||||
item->valuestring = (char *)output;
|
||||
input_buffer->offset = (size_t)(input_end - input_buffer->content);
|
||||
input_buffer->offset++;
|
||||
return true;
|
||||
fail:
|
||||
if (output != NULL) input_buffer->hooks.deallocate(output);
|
||||
if (input_pointer != NULL) input_buffer->offset = (size_t)(input_pointer - input_buffer->content);
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 0x4e000: cJSON (upstream source) - object lookup, array/object building
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/cjson.h"
|
||||
|
||||
extern "C" cJSON *cjson_get_object_item(const cJSON *const object, const char *const string) { // cJSON_GetObjectItemCaseSensitive
|
||||
cJSON *current_element = NULL;
|
||||
if ((object == NULL) || (string == NULL)) return NULL;
|
||||
current_element = object->child;
|
||||
while ((current_element != NULL) && (current_element->string != NULL) &&
|
||||
(strcmp(string, current_element->string) != 0))
|
||||
current_element = current_element->next;
|
||||
if ((current_element == NULL) || (current_element->string == NULL)) return NULL;
|
||||
return current_element;
|
||||
}
|
||||
extern "C" cJSON_bool cjson_add_item_to_array(cJSON *array, cJSON *item) { return add_item_to_array(array, item); } // cJSON_AddItemToArray
|
||||
|
||||
extern "C" cJSON *cjson_add_item_to_object(cJSON *const object, const char *const name, const cJSON_bool boolean) { // cJSON_AddBoolToObject
|
||||
cJSON *bool_item = cJSON_CreateBool(boolean);
|
||||
if (add_item_to_object(object, name, bool_item, &global_hooks, false)) return bool_item;
|
||||
cjson_delete(bool_item);
|
||||
return NULL;
|
||||
}
|
||||
extern "C" cJSON *cjson_add_number_to_object(cJSON *const object, const char *const name, const double number) { // cJSON_AddNumberToObject
|
||||
cJSON *number_item = cJSON_CreateNumber(number);
|
||||
if (add_item_to_object(object, name, number_item, &global_hooks, false)) return number_item;
|
||||
cjson_delete(number_item);
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// 0x51000: cJSON (upstream source) - parse_hex4, ensure, print_string_ptr, parse_array/object
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/cjson.h"
|
||||
|
||||
extern "C" unsigned cjson_parse_hex4(const unsigned char *const input) { // parse_hex4
|
||||
unsigned int h = 0;
|
||||
size_t i = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
if ((input[i] >= '0') && (input[i] <= '9')) h += (unsigned int)input[i] - '0';
|
||||
else if ((input[i] >= 'A') && (input[i] <= 'F')) h += (unsigned int)10 + input[i] - 'A';
|
||||
else if ((input[i] >= 'a') && (input[i] <= 'f')) h += (unsigned int)10 + input[i] - 'a';
|
||||
else return 0;
|
||||
if (i < 3) h = h << 4;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
extern "C" unsigned char *cjson_print_string(printbuffer *const p, size_t needed) { // ensure
|
||||
unsigned char *newbuffer = NULL;
|
||||
size_t newsize = 0;
|
||||
if ((p == NULL) || (p->buffer == NULL)) return NULL;
|
||||
if ((p->length > 0) && (p->offset >= p->length)) return NULL;
|
||||
if (needed > INT_MAX) return NULL;
|
||||
needed += p->offset + 1;
|
||||
if (needed <= p->length) return p->buffer + p->offset;
|
||||
if (p->noalloc) return NULL;
|
||||
if (needed > (INT_MAX / 2)) {
|
||||
if (needed <= INT_MAX) newsize = INT_MAX;
|
||||
else return NULL;
|
||||
} else {
|
||||
newsize = needed * 2;
|
||||
}
|
||||
if (p->hooks.reallocate != NULL) {
|
||||
newbuffer = (unsigned char *)p->hooks.reallocate(p->buffer, newsize);
|
||||
if (newbuffer == NULL) {
|
||||
p->hooks.deallocate(p->buffer);
|
||||
p->length = 0;
|
||||
p->buffer = NULL;
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
newbuffer = (unsigned char *)p->hooks.allocate(newsize);
|
||||
if (!newbuffer) {
|
||||
p->hooks.deallocate(p->buffer);
|
||||
p->length = 0;
|
||||
p->buffer = NULL;
|
||||
return NULL;
|
||||
}
|
||||
memcpy(newbuffer, p->buffer, p->offset + 1);
|
||||
p->hooks.deallocate(p->buffer);
|
||||
}
|
||||
p->length = newsize;
|
||||
p->buffer = newbuffer;
|
||||
return newbuffer + p->offset;
|
||||
}
|
||||
|
||||
extern "C" int libc_sprintf(char *buf, const char *fmt, ...); // sprintf
|
||||
extern "C" const char fmt_u_hex4[]; // "u%04x"
|
||||
// print_string_ptr (the output_buffer NULL check was folded away by the original compiler: all callers pass one)
|
||||
extern "C" cJSON_bool cjson_print_string_ptr(const unsigned char *const input, printbuffer *const output_buffer) {
|
||||
const unsigned char *input_pointer = NULL;
|
||||
unsigned char *output = NULL;
|
||||
unsigned char *output_pointer = NULL;
|
||||
size_t output_length = 0;
|
||||
size_t escape_characters = 0;
|
||||
if (input == NULL) {
|
||||
output = cjson_print_string(output_buffer, sizeof("\"\""));
|
||||
if (output == NULL) return false;
|
||||
output[0] = '"'; output[1] = '"'; output[2] = '\0';
|
||||
return true;
|
||||
}
|
||||
for (input_pointer = input; *input_pointer; input_pointer++) {
|
||||
switch (*input_pointer) {
|
||||
case '\"': case '\\': case '\b': case '\f': case '\n': case '\r': case '\t':
|
||||
escape_characters++;
|
||||
break;
|
||||
default:
|
||||
if (*input_pointer < 32) escape_characters += 5;
|
||||
break;
|
||||
}
|
||||
}
|
||||
output_length = (size_t)(input_pointer - input) + escape_characters;
|
||||
output = cjson_print_string(output_buffer, output_length + sizeof("\"\""));
|
||||
if (output == NULL) return false;
|
||||
if (escape_characters == 0) {
|
||||
output[0] = '\"';
|
||||
memcpy(output + 1, input, output_length);
|
||||
output[output_length + 1] = '\"';
|
||||
output[output_length + 2] = '\0';
|
||||
return true;
|
||||
}
|
||||
output[0] = '\"';
|
||||
output_pointer = output + 1;
|
||||
for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) {
|
||||
if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) {
|
||||
*output_pointer = *input_pointer;
|
||||
} else {
|
||||
*output_pointer++ = '\\';
|
||||
switch (*input_pointer) {
|
||||
case '\\': *output_pointer = '\\'; break;
|
||||
case '\"': *output_pointer = '\"'; break;
|
||||
case '\b': *output_pointer = 'b'; break;
|
||||
case '\f': *output_pointer = 'f'; break;
|
||||
case '\n': *output_pointer = 'n'; break;
|
||||
case '\r': *output_pointer = 'r'; break;
|
||||
case '\t': *output_pointer = 't'; break;
|
||||
default:
|
||||
libc_sprintf((char *)output_pointer, fmt_u_hex4, *input_pointer);
|
||||
output_pointer += 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
output[output_length + 1] = '\"';
|
||||
output[output_length + 2] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
FN(cJSON_bool, cjson_replace_item_in_object, (cJSON *const item, parse_buffer *const input_buffer)); // parse_string
|
||||
FN(cJSON_bool, cjson_parse_value, (cJSON *const item, parse_buffer *const input_buffer)); // parse_value
|
||||
|
||||
extern "C" cJSON_bool cjson_parse_array(cJSON *const item, parse_buffer *const input_buffer) { // parse_object
|
||||
cJSON *head = NULL;
|
||||
cJSON *current_item = NULL;
|
||||
if (input_buffer->depth >= CJSON_NESTING_LIMIT) return false;
|
||||
input_buffer->depth++;
|
||||
if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) goto fail;
|
||||
input_buffer->offset++;
|
||||
buffer_skip_whitespace_i(input_buffer);
|
||||
if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) goto success;
|
||||
if (cannot_access_at_index(input_buffer, 0)) {
|
||||
input_buffer->offset--;
|
||||
goto fail;
|
||||
}
|
||||
input_buffer->offset--;
|
||||
do {
|
||||
cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));
|
||||
if (new_item == NULL) goto fail;
|
||||
if (head == NULL) {
|
||||
current_item = head = new_item;
|
||||
} else {
|
||||
current_item->next = new_item;
|
||||
new_item->prev = current_item;
|
||||
current_item = new_item;
|
||||
}
|
||||
input_buffer->offset++;
|
||||
buffer_skip_whitespace_i(input_buffer);
|
||||
if (!cjson_replace_item_in_object(current_item, input_buffer)) goto fail;
|
||||
buffer_skip_whitespace_i(input_buffer);
|
||||
current_item->string = current_item->valuestring;
|
||||
current_item->valuestring = NULL;
|
||||
if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) goto fail;
|
||||
input_buffer->offset++;
|
||||
buffer_skip_whitespace_i(input_buffer);
|
||||
if (!cjson_parse_value(current_item, input_buffer)) goto fail;
|
||||
buffer_skip_whitespace_i(input_buffer);
|
||||
} while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ','));
|
||||
if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) goto fail;
|
||||
success:
|
||||
input_buffer->depth--;
|
||||
if (head != NULL) head->prev = current_item;
|
||||
item->type = cJSON_Object;
|
||||
item->child = head;
|
||||
input_buffer->offset++;
|
||||
return true;
|
||||
fail:
|
||||
if (head != NULL) cjson_delete(head);
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// 0x3d000: comparators
|
||||
#include "slinky/engine.h"
|
||||
extern "C" int sort_key_table[];
|
||||
extern "C" int compare_module_height(char *left, char *right) { return (int)(*(float *)(right + 0x48) - *(float *)(left + 0x48)); }
|
||||
extern "C" int compare_key_table(short *left, short *right) { return sort_key_table[*left] - sort_key_table[*right]; }
|
||||
|
||||
// release the cached player records: their two global refs and five lists of global refs
|
||||
#include "slinky/jids.h"
|
||||
FN(void, libc_free, (void *ptr));
|
||||
struct RefList { int padding_00; int n; jobject *refs; };
|
||||
struct PRec { char padding_00[0x30]; jobject ref_primary, ref_secondary; char padding_40[0x10]; RefList lists[5]; };
|
||||
extern "C" unsigned long long name_tags_count;
|
||||
extern "C" PRec name_tags_table[];
|
||||
extern "C" void entity_list_for_render(Jvm *jvm) {
|
||||
if (name_tags_count) {
|
||||
JNIEnv *env = jvm->env;
|
||||
for (unsigned long long i = 0; i < name_tags_count; i++) {
|
||||
PRec *p = &name_tags_table[i];
|
||||
if (p->ref_secondary) (*env)->DeleteGlobalRef(env, p->ref_secondary);
|
||||
if (p->ref_primary) (*env)->DeleteGlobalRef(env, p->ref_primary);
|
||||
#pragma clang loop unroll(full)
|
||||
for (int k = 0; k < 5; k++) {
|
||||
RefList *l = &p->lists[k];
|
||||
if (!l->refs) continue;
|
||||
for (long long m = 0; m < l->n; m++) (*env)->DeleteGlobalRef(env, l->refs[m]);
|
||||
libc_free(l->refs);
|
||||
l->refs = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
name_tags_count = 0;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// helper functions (formerly address block; names inferred from behaviour).
|
||||
#include "slinky/engine.h"
|
||||
extern "C" char free_look_module_descriptor[]; extern "C" unsigned char free_look_active; extern "C" void *free_look_saved_camera;
|
||||
extern "C" int free_look_saved_perspective, free_look_target_perspective_value;
|
||||
struct Ev { char padding_00[8]; Ctx *ctx; void *tag; unsigned char on; };
|
||||
FN(unsigned char, is_silent_rotation_active, ());
|
||||
FN(void *, get_silent_rotation, ());
|
||||
FN(void *, get_entity_rotation, (void *jvm, void *entity));
|
||||
FN(void *, get_game_settings, (void *jvm, void *object));
|
||||
FN(int, java_get_game_settings_third_person_view, (void *jvm, void *game_settings));
|
||||
FN(void, java_set_game_settings_third_person_view, (void *jvm, void *game_settings, int value));
|
||||
FN(void, set_render_rotation, (void *cam));
|
||||
FN(void *, java_call_item_stack_get_enchantment_tag_list, (void *jvm, void *stack));
|
||||
FN(int, java_call_nbttag_list_tag_count, (void *jvm, void *list));
|
||||
FN(void *, java_call_nbttag_list_get_compound_tag_at, (void *jvm, void *list, int i));
|
||||
FN(short, java_jni_call_short_method_nbttag_compound_get_short, (void *jvm, void *tag, void *key));
|
||||
struct Jvm2 { void *ids; struct JE **env; };
|
||||
struct JE { char padding_00[0x538]; void *(*NewStringUTF)(void *, const char *); };
|
||||
extern "C" const char str_id_in_free_look_and_enchants[], str_lvl[];
|
||||
extern "C" void free_look_on_toggle(Ev *event) {
|
||||
if (event->tag != free_look_module_descriptor)
|
||||
return;
|
||||
Ctx *ctx = event->ctx;
|
||||
if (!ctx->world)
|
||||
return;
|
||||
if (event->on == 1) {
|
||||
free_look_active = 1;
|
||||
void *cam;
|
||||
if (is_silent_rotation_active())
|
||||
cam = get_silent_rotation();
|
||||
else
|
||||
cam = get_entity_rotation(ctx->jvm, ctx->player);
|
||||
free_look_saved_camera = cam;
|
||||
}
|
||||
void *x = get_game_settings(ctx->jvm, ctx->minecraft);
|
||||
void *jvm = ctx->jvm;
|
||||
if (event->on != 0) {
|
||||
free_look_saved_perspective = java_get_game_settings_third_person_view(jvm, x);
|
||||
void *jvm2 = ctx->jvm;
|
||||
unsigned persp = free_look_target_perspective_value;
|
||||
java_set_game_settings_third_person_view(jvm2, x, persp);
|
||||
return;
|
||||
}
|
||||
java_set_game_settings_third_person_view(jvm, x, free_look_saved_perspective);
|
||||
if (free_look_active == 1) {
|
||||
set_render_rotation(free_look_saved_camera);
|
||||
free_look_active = 0;
|
||||
}
|
||||
}
|
||||
extern "C" void free_look_save_rotation(char *p) { p[8] = 1; *(void **)(p + 0xc) = free_look_saved_camera; }
|
||||
|
||||
// enchantment flags of an item stack: 1 = id 16, 2 = id 19, 4 = id 20
|
||||
extern "C" unsigned get_weapon_enchant_flags(Jvm2 *j, void *stack) {
|
||||
void *list = java_call_item_stack_get_enchantment_tag_list(j, stack);
|
||||
unsigned flags = 0;
|
||||
if (!list) return flags;
|
||||
JE **env = j->env;
|
||||
void *idS = (*env)->NewStringUTF(env, str_id_in_free_look_and_enchants);
|
||||
if (!idS) return flags;
|
||||
if (!(*env)->NewStringUTF(env, str_lvl)) return flags;
|
||||
int n = java_call_nbttag_list_tag_count(j, list);
|
||||
if (n <= 0) return flags;
|
||||
flags = 0;
|
||||
int i = 0;
|
||||
do {
|
||||
void *tag = java_call_nbttag_list_get_compound_tag_at(j, list, i);
|
||||
switch (java_jni_call_short_method_nbttag_compound_get_short(j, tag, idS)) {
|
||||
case 0x10: flags |= 1; break;
|
||||
case 0x13: flags |= 2; break;
|
||||
case 0x14: flags |= 4; break;
|
||||
}
|
||||
} while (++i != n);
|
||||
return flags;
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// 0x3b000: friend-list style 32-byte key set (sorted vector) with chat feedback
|
||||
#include "slinky/engine.h"
|
||||
struct Key { unsigned long long hi, lo; };
|
||||
struct Ent { char b[0x20]; };
|
||||
extern "C" void *friend_set_friend_table; extern "C" char friend_set_table_meta[]; extern "C" int friend_set_dirty_count;
|
||||
extern "C" const char str_sect_aadded_s_as_friend[], str_sect_aremoved_s_as_friend[], empty_plural_suffix[], str_s_in_friend_set[], str_sect_aremoved_zu_friend_s[];
|
||||
FN(unsigned char, friend_resolve, (void *c, Key k, Ent *e, char *name));
|
||||
FN(void *, vector_find_insert, (void *t, int elem, void *hdr, void *e, void *cmp));
|
||||
FN(void *, binary_search, (void *t, int elem, void *hdr, void *e, void *cmp));
|
||||
FN(void, vector_erase_value, (void *t, int elem, void *hdr, void *at));
|
||||
extern "C" void jni_throw_format(void *jvm, const char *fmt, ...);
|
||||
|
||||
extern "C" int compare_friend_key(void *, unsigned long long *right, unsigned long long *b) {
|
||||
unsigned long long x = __builtin_bswap64(right[0]);
|
||||
asm("" : "+r"(x));
|
||||
|
||||
unsigned long long y = __builtin_bswap64(b[0]);
|
||||
if (x == y) {
|
||||
unsigned long long a1 = right[1], b1 = b[1];
|
||||
asm("" : "+r"(a1), "+r"(b1));
|
||||
|
||||
x = __builtin_bswap64(a1);
|
||||
y = __builtin_bswap64(b1);
|
||||
asm("" : "+r"(x), "+r"(y));
|
||||
|
||||
if (x == y) return 0;
|
||||
}
|
||||
return x >= y ? 1 : -1;
|
||||
}
|
||||
extern "C" unsigned char friend_set_insert(void **c, Key k) {
|
||||
Ent e; char name[0x20];
|
||||
unsigned char ok = friend_resolve(c, k, &e, name);
|
||||
if (!ok) return ok;
|
||||
vector_find_insert(&friend_set_friend_table, 0x20, friend_set_table_meta, &e, (void *)compare_friend_key);
|
||||
jni_throw_format(*c, str_sect_aadded_s_as_friend, name);
|
||||
return ok;
|
||||
}
|
||||
extern "C" unsigned char friend_set_erase(void **c, Key k) {
|
||||
Ent e; char name[0x20];
|
||||
unsigned char ok = friend_resolve(c, k, &e, name);
|
||||
if (!ok) return ok;
|
||||
void *entry = binary_search(friend_set_friend_table, 0x20, friend_set_table_meta, &e, (void *)compare_friend_key);
|
||||
if (entry) vector_erase_value(friend_set_friend_table, 0x20, friend_set_table_meta, entry);
|
||||
jni_throw_format(*c, str_sect_aremoved_s_as_friend, name);
|
||||
return ok;
|
||||
}
|
||||
extern "C" bool friend_set_clear(void **c) {
|
||||
unsigned long long n = (unsigned)friend_set_dirty_count;
|
||||
|
||||
void *jvm = *c;
|
||||
jni_throw_format(jvm, str_sect_aremoved_zu_friend_s, n, n == 1 ? empty_plural_suffix : str_s_in_friend_set);
|
||||
friend_set_dirty_count = 0;
|
||||
return true;
|
||||
}
|
||||
extern "C" int friend_set_dirty() { return friend_set_dirty_count; }
|
||||
|
||||
#include "slinky/jids.h"
|
||||
FN(void *, libc_memcpy_alt, (void *dest, const void *src, unsigned long long n));
|
||||
FN(void *, java_get_world_player_entities, (void *jvm, void *world));
|
||||
FN(int, java_call_list_size, (void *jvm, void *list));
|
||||
FN(void *, java_call_list_get, (void *jvm, void *list, int i));
|
||||
FN(jstring, java_call_entity_get_command_sender_name, (void *jvm, void *entity));
|
||||
FN(void *, java_get_entity_uuid, (void *jvm, void *entity));
|
||||
FN(unsigned long long, java_jni_call_long_method_uuid_get_most_significant_bits, (void *jvm, void *uuid));
|
||||
FN(unsigned long long, java_jni_call_long_method_uuid_get_least_significant_bits, (void *jvm, void *uuid));
|
||||
extern "C" const char str_sect_cusername_is_too_long[], str_sect_cfound_multiple_names_sta[], str_sect_cplayer_not_found[];
|
||||
struct Str { const char *s; unsigned long long len; };
|
||||
struct Uid { unsigned long long hi, lo, z0, z1; };
|
||||
struct C3b { Jvm *jvm; char padding_08[0x18]; void *world; void *self; };
|
||||
static inline __attribute__((always_inline)) unsigned char fold(unsigned char ch) {
|
||||
return (unsigned char)(((unsigned char)(ch - 0x41) < 0x1a) << 5) + ch;
|
||||
}
|
||||
// resolve a (unique, case-insensitive) player-name prefix to its UUID; prints the reason on failure
|
||||
extern "C" unsigned char friend_resolve(void *cv, Key kk, Ent *eo, char *name) {
|
||||
C3b *c = (C3b *)cv;
|
||||
Str k = __builtin_bit_cast(Str, kk);
|
||||
Uid *out = (Uid *)eo;
|
||||
unsigned long long len = k.len;
|
||||
if (len >= 0x11) { jni_throw_format(c->jvm, str_sect_cusername_is_too_long); return false; }
|
||||
const char *s = k.s;
|
||||
JNIEnv *env = c->jvm->env;
|
||||
char buf[0x18];
|
||||
libc_memcpy_alt(buf, s, len);
|
||||
buf[len] = 0;
|
||||
if (!(*env)->NewStringUTF(env, buf)) return false;
|
||||
void *list = java_get_world_player_entities(c->jvm, c->world);
|
||||
if (java_call_list_size(c->jvm, list) <= 0) { jni_throw_format(c->jvm, str_sect_cplayer_not_found); return false; }
|
||||
void *found = 0;
|
||||
for (int i = 0; i < java_call_list_size(c->jvm, list); i++) {
|
||||
void *element = java_call_list_get(c->jvm, list, i);
|
||||
JNIEnv *e2 = c->jvm->env;
|
||||
if ((*e2)->IsSameObject(e2, (jobject)element, (jobject)c->self)) continue;
|
||||
jstring nm = java_call_entity_get_command_sender_name(c->jvm, element);
|
||||
int L = (*env)->GetStringUTFLength(env, nm);
|
||||
if (len > (unsigned long long)(long long)L) continue;
|
||||
const char *chars = (*env)->GetStringUTFChars(env, nm, 0);
|
||||
unsigned long long j;
|
||||
for (j = 0; j < len; j++)
|
||||
if (fold(s[j]) != fold(chars[j])) break;
|
||||
if (j == len) {
|
||||
if (found) {
|
||||
(*env)->ReleaseStringUTFChars(env, nm, chars);
|
||||
jni_throw_format(c->jvm, str_sect_cfound_multiple_names_sta, (int)len, s);
|
||||
return false;
|
||||
}
|
||||
int n2 = L >= 0x10 ? 0x10 : L;
|
||||
libc_memcpy_alt(name, chars, (long long)n2);
|
||||
name[n2] = 0;
|
||||
found = element;
|
||||
}
|
||||
(*env)->ReleaseStringUTFChars(env, nm, chars);
|
||||
}
|
||||
if (!found) { jni_throw_format(c->jvm, str_sect_cplayer_not_found); return false; }
|
||||
void *uuid = java_get_entity_uuid(c->jvm, found);
|
||||
unsigned long long hi = java_jni_call_long_method_uuid_get_most_significant_bits(c->jvm, uuid);
|
||||
out->lo = java_jni_call_long_method_uuid_get_least_significant_bits(c->jvm, uuid);
|
||||
out->hi = hi;
|
||||
out->z0 = 0; out->z1 = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// hex string of friend entry i (first 24 bytes: the UUID + name hash), in a static buffer
|
||||
FN(unsigned char *, element_at, (void *vec, unsigned elem, void *hdr, unsigned i));
|
||||
extern "C" const char str_0123456789abcdef[]; // "0123456789abcdef"
|
||||
extern "C" char friend_hex_buffer[];
|
||||
extern "C" char *friend_set_count(void *, unsigned i) {
|
||||
const unsigned char *e = element_at(friend_set_friend_table, 0x20, friend_set_table_meta, i);
|
||||
for (int k = 0; k < 0x18; k++) {
|
||||
friend_hex_buffer[2 * k] = str_0123456789abcdef[e[k] >> 4];
|
||||
friend_hex_buffer[2 * k + 1] = str_0123456789abcdef[e[k] & 0xf];
|
||||
}
|
||||
friend_hex_buffer[0x30] = 0;
|
||||
return friend_hex_buffer;
|
||||
}
|
||||
|
||||
// add every player within `radius` blocks as a friend (optionally clearing the list first)
|
||||
struct V3b { double x, y, z; };
|
||||
FN(V3b, java_get_entity_pos_x, (void *jvm, void *entity));
|
||||
FN(unsigned char, entity_flags_has, (void *jvm, void *entity, int mask));
|
||||
FN(double, vec3_distance_sq, (V3b a, V3b b));
|
||||
extern "C" const char str_sect_aremoved_zu_added_zu_frie[], str_sect_aadded_zu_friend_s[];
|
||||
struct NearArgs { unsigned long long radius; char padding_08[8]; unsigned char clear; };
|
||||
typedef int v4i3 __attribute__((vector_size(16)));
|
||||
typedef double v2d3 __attribute__((vector_size(16)));
|
||||
extern "C" const v4i3 friend_set_0x43300000_0x45300000_0_0; // {0x43300000, 0x45300000, 0, 0}
|
||||
extern "C" const v2d3 friend_set_2_52_2_84; // {2^52, 2^84}
|
||||
// uint64 -> double with the image's constants (the original's lowering)
|
||||
static inline __attribute__((always_inline)) double u64_to_d(unsigned long long x) {
|
||||
v4i3 v = {(int)x, (int)(x >> 32), 0, 0};
|
||||
v4i3 u = __builtin_shufflevector(v, friend_set_0x43300000_0x45300000_0_0, 0, 4, 1, 5);
|
||||
v2d3 d = (v2d3)u - friend_set_2_52_2_84;
|
||||
return d[1] + d[0];
|
||||
}
|
||||
extern "C" bool friend_set_contains(C3b *c, NearArgs *a) {
|
||||
unsigned long long radius = a->radius;
|
||||
unsigned char clear = a->clear;
|
||||
unsigned long long old = (unsigned)friend_set_dirty_count;
|
||||
if (clear == 1) friend_set_dirty_count = 0;
|
||||
V3b eye = java_get_entity_pos_x(c->jvm, c->self);
|
||||
void *list = java_get_world_player_entities(c->jvm, c->world);
|
||||
unsigned long long added = 0;
|
||||
if (java_call_list_size(c->jvm, list) > 0) {
|
||||
double r = u64_to_d(radius);
|
||||
r *= r;
|
||||
for (int i = 0; i < java_call_list_size(c->jvm, list); i++) {
|
||||
void *element = java_call_list_get(c->jvm, list, i);
|
||||
JNIEnv *env = c->jvm->env;
|
||||
if ((*env)->IsSameObject(env, (jobject)element, (jobject)c->self)) continue;
|
||||
if (entity_flags_has(c->jvm, element, 2)) continue;
|
||||
V3b position = java_get_entity_pos_x(c->jvm, element);
|
||||
if (vec3_distance_sq(eye, position) > r) continue;
|
||||
void *uuid = java_get_entity_uuid(c->jvm, element);
|
||||
Uid k;
|
||||
k.hi = java_jni_call_long_method_uuid_get_most_significant_bits(c->jvm, uuid);
|
||||
k.lo = java_jni_call_long_method_uuid_get_least_significant_bits(c->jvm, uuid);
|
||||
k.z0 = 0;
|
||||
k.z1 = 0;
|
||||
void *existing = binary_search(friend_set_friend_table, 0x20, friend_set_table_meta, &k, (void *)compare_friend_key);
|
||||
vector_find_insert(&friend_set_friend_table, 0x20, friend_set_table_meta, &k, (void *)compare_friend_key);
|
||||
if (!existing) added++;
|
||||
}
|
||||
}
|
||||
const char *s = added == 1 ? empty_plural_suffix : str_s_in_friend_set;
|
||||
if (clear) jni_throw_format(c->jvm, str_sect_aremoved_zu_added_zu_frie, old, added, s);
|
||||
else jni_throw_format(c->jvm, str_sect_aadded_zu_friend_s, added, s);
|
||||
return true;
|
||||
}
|
||||
|
||||
// add a friend entry from its hex string (inverse of friend_set_count); a bad digit stops that part of the parse
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
static inline __attribute__((always_inline)) bool hexdig(unsigned char c, unsigned char *v) {
|
||||
if ((unsigned char)(c - '0') < 10) { *v = c - '0'; return true; }
|
||||
if ((unsigned char)(c - 'a') < 26) { *v = c - 'a' + 10; return true; }
|
||||
return false;
|
||||
}
|
||||
static inline __attribute__((always_inline)) void unhex(unsigned char *d, const char *s, int n) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
unsigned char h, l;
|
||||
if (!hexdig(s[i * 2], &h) || !hexdig(s[i * 2 + 1], &l)) return;
|
||||
d[i] = h << 4 | l;
|
||||
}
|
||||
}
|
||||
extern "C" bool friend_name_matches(void *, const char *s) {
|
||||
Ent e = {};
|
||||
unsigned long long n = libc_strlen(s);
|
||||
if (n >= 0x30) {
|
||||
unhex((unsigned char *)e.b, s, 16);
|
||||
unhex((unsigned char *)e.b + 16, s + 32, 8);
|
||||
vector_find_insert(&friend_set_friend_table, 0x20, friend_set_table_meta, &e, (void *)compare_friend_key);
|
||||
}
|
||||
return n >= 0x30;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// helper functions (formerly address block; names inferred from behaviour).
|
||||
#include "slinky/engine.h"
|
||||
extern "C" void *friend_set_friend_table; extern "C" char friend_set_table_meta[]; extern "C" int friend_set_dirty_count; extern "C" unsigned char friends_friends_setting_field_0x7c;
|
||||
FN(void, vector_erase, (void *vec, int elem, void *hdr, void *key));
|
||||
FN(void *, java_jni_is_instance_of_entity_player, (void *jvm, void *candidate));
|
||||
FN(unsigned char, entity_flags_has, (void *jvm, void *entity, int mode));
|
||||
FN(void *, java_call_renderer_living_entity_can_render_name, (void *jvm, void *renderer_living_entity, void *other_object));
|
||||
extern "C" void friend_set_remove_by_key(void *, void *key) { vector_erase(friend_set_friend_table, 0x20, friend_set_table_meta, key); }
|
||||
extern "C" void friend_set_clear_dirty() { friend_set_dirty_count = 0; }
|
||||
extern "C" void *should_render_player_name(void *jvm, void *render_player, void *other_object) {
|
||||
if (friends_friends_setting_field_0x7c == 1 && java_jni_is_instance_of_entity_player(jvm, other_object) != 0 && !entity_flags_has(jvm, other_object, 2)) return 0;
|
||||
return java_call_renderer_living_entity_can_render_name(jvm, render_player, other_object);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// 0x2f000: health-bar style line drawing (immediate-mode GL), comparator, hurt-time hook
|
||||
#include "slinky/engine.h"
|
||||
typedef void (*GlColor3f)(float, float, float);
|
||||
typedef void (*GlBegin)(int);
|
||||
typedef void (*GlVertex2f)(float, float);
|
||||
typedef void (*GlEnd)();
|
||||
extern "C" GlColor3f *ptr_to_pGlColor3f;
|
||||
extern "C" GlBegin *ptr_to_pGlBegin;
|
||||
extern "C" GlVertex2f *ptr_to_pGlVertex2f;
|
||||
extern "C" GlEnd *ptr_to_pGlEnd;
|
||||
extern "C" float one_in_healthbar_draw, healthbar_green_level;
|
||||
FN(float, lerp_unclamped_float, (float a, float b, float t)); // lerp
|
||||
|
||||
struct Bar { char padding_00[0x44]; float fill_fraction, overlay_fraction; };
|
||||
|
||||
extern "C" void draw_health_bar(Bar *b, float x, float y0, float y1) {
|
||||
float m = lerp_unclamped_float(y1, y0, b->fill_fraction);
|
||||
GlColor3f *color = ptr_to_pGlColor3f;
|
||||
(*color)(0.0f, 0.0f, 0.0f);
|
||||
GlBegin *begin = ptr_to_pGlBegin;
|
||||
(*begin)(1);
|
||||
GlVertex2f *vertex = ptr_to_pGlVertex2f;
|
||||
(*vertex)(x, y0);
|
||||
(*vertex)(x, m);
|
||||
GlEnd *end = ptr_to_pGlEnd;
|
||||
(*end)();
|
||||
if (b->overlay_fraction > 0.0f) {
|
||||
float m2 = lerp_unclamped_float(m, y1, b->overlay_fraction);
|
||||
float c0 = one_in_healthbar_draw;
|
||||
|
||||
(*color)(c0, healthbar_green_level, 0.0f);
|
||||
(*begin)(1);
|
||||
(*vertex)(x, m);
|
||||
(*vertex)(x, m2);
|
||||
(*end)();
|
||||
m = m2;
|
||||
}
|
||||
GlColor3f cf = *color;
|
||||
float fill_fraction = b->fill_fraction;
|
||||
float c = one_in_healthbar_draw;
|
||||
float r = lerp_unclamped_float(c, 0.0f, fill_fraction);
|
||||
float g = lerp_unclamped_float(0.0f, c, b->fill_fraction);
|
||||
cf(r, g, 0.0f);
|
||||
(*begin)(1);
|
||||
(*vertex)(x, m);
|
||||
(*vertex)(x, y1);
|
||||
(*end)();
|
||||
}
|
||||
|
||||
extern "C" int compare_healthbar_height(char *left, char *right) { return (int)(*(float *)(right + 0x3c) - *(float *)(left + 0x3c)); }
|
||||
|
||||
extern "C" int reach_last_value_seen_from_java, reach_cooldown_ticks;
|
||||
extern "C" unsigned char reach_require_sprint;
|
||||
extern "C" float reach_activate_time, reach_distance;
|
||||
FN(unsigned char, java_call_entity_is_sprinting, (void *jvm, void *entity));
|
||||
FN(int, java_get_entity_living_base_last_attacker_time, (void *jvm, void *entity_living_base));
|
||||
struct Ev { char padding_00[8]; Ctx *ctx; unsigned char on; char padding_11[7]; double reach; };
|
||||
extern "C" void healthbar_render(Ev *event) {
|
||||
int n = reach_cooldown_ticks;
|
||||
if (n > 0) reach_cooldown_ticks = --n;
|
||||
Ctx *ctx = event->ctx;
|
||||
if (reach_require_sprint == 1) {
|
||||
if (!java_call_entity_is_sprinting(ctx->jvm, ctx->player)) return;
|
||||
n = reach_cooldown_ticks;
|
||||
}
|
||||
if (n == 0) {
|
||||
int t = java_get_entity_living_base_last_attacker_time(ctx->jvm, ctx->player);
|
||||
if (t == reach_last_value_seen_from_java) {
|
||||
n = reach_cooldown_ticks;
|
||||
} else {
|
||||
reach_last_value_seen_from_java = t;
|
||||
reach_cooldown_ticks = 9;
|
||||
n = 9;
|
||||
}
|
||||
|
||||
}
|
||||
SHARED_EPILOG(); // original keeps one shared tail for both arms
|
||||
if (reach_activate_time > (float)n) {
|
||||
event->on = 1;
|
||||
event->reach = reach_distance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// 0x47000: pick the best inventory slot for an item category and plan the swap
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/jids.h"
|
||||
FN(void *, java_get_entity_player_inventory, (void *jvm, void *entity_player));
|
||||
FN(void *, java_call_iinventory_get_stack_in_slot, (void *jvm, void *object, int slot));
|
||||
FN(void *, java_get_inventory_player_main_inventory, (void *jvm, void *inventory_player));
|
||||
FN(unsigned, swap_delay, (Ctx *c, void *stack, unsigned kind));
|
||||
struct Plan { char padding_00[0x10]; int mode; unsigned char from; unsigned char padding_15, padding_16, padding_17; char padding_18[4]; unsigned char kind, to; };
|
||||
extern "C" bool plan_inventory_swap(Ctx *ctx, Plan *pl) {
|
||||
unsigned char kind = pl->kind;
|
||||
void *player = java_get_entity_player_inventory(ctx->jvm, ctx->player);
|
||||
void *jvm = ctx->jvm;
|
||||
|
||||
void *cur = java_call_iinventory_get_stack_in_slot(jvm, player, 0x27 - kind);
|
||||
void *inv = java_get_inventory_player_main_inventory(ctx->jvm, player);
|
||||
JNIEnv *env = ((Jvm *)ctx->jvm)->env;
|
||||
unsigned best = swap_delay(ctx, cur, kind);
|
||||
int idx = -1;
|
||||
for (int i = 0; i != 0x24; i++) {
|
||||
unsigned v = swap_delay(ctx, (*env)->GetObjectArrayElement(env, (jobjectArray)inv, i), kind);
|
||||
if (v > best) { idx = i; best = v; }
|
||||
}
|
||||
if (idx != -1) {
|
||||
if (!cur) {
|
||||
unsigned char to = (unsigned char)(idx + 0x24);
|
||||
if (idx >= 9) to = (unsigned char)idx;
|
||||
*(long long *)&pl->mode = 0;
|
||||
pl->to = to;
|
||||
} else if (idx <= 8) {
|
||||
pl->mode = 1;
|
||||
pl->from = (unsigned char)idx;
|
||||
pl->padding_15 = 0; pl->padding_16 = 0; pl->padding_17 = 0;
|
||||
pl->to = pl->kind + 5;
|
||||
} else {
|
||||
unsigned char k = pl->kind + 5;
|
||||
pl->mode = 2;
|
||||
pl->from = k;
|
||||
pl->padding_15 = 0; pl->padding_16 = 0; pl->padding_17 = 0;
|
||||
pl->to = (unsigned char)idx;
|
||||
}
|
||||
}
|
||||
return idx != -1;
|
||||
}
|
||||
|
||||
// armour value of a stack for slot `kind` (armour points + protection enchantments)
|
||||
FN(void *, java_call_item_stack_get_item, (void *jvm, void *item_stack));
|
||||
FN(int, java_call_static_item_get_id_from_item, (void *jvm, void *item));
|
||||
FN(void *, java_call_item_stack_get_enchantment_tag_list, (void *jvm, void *stack));
|
||||
FN(int, java_call_nbttag_list_tag_count, (void *jvm, void *list));
|
||||
FN(void *, java_call_nbttag_list_get_compound_tag_at, (void *jvm, void *list, int i));
|
||||
FN(short, java_jni_call_short_method_nbttag_compound_get_short, (void *jvm, void *tag, void *key));
|
||||
extern "C" const int item_id_offsets[];
|
||||
extern "C" const long long material_ids[];
|
||||
extern "C" const unsigned char item_points_table[];
|
||||
extern "C" const char str_id_in_chest_gui[], str_lvl_in_chest_gui[];
|
||||
extern "C" const double tenth_in_inventory_swap, three_quarters, three_in_inventory_swap, swap_blend_start_weight, one_in_inventory_swap;
|
||||
extern "C" const float half_in_inventory_swap, factor_1000_b;
|
||||
extern "C" unsigned swap_delay(Ctx *ctx, void *stack, unsigned kind) {
|
||||
if (!stack) return 0;
|
||||
void *item = java_call_item_stack_get_item(ctx->jvm, stack);
|
||||
if (!item) return 0;
|
||||
int item_id = java_call_static_item_get_id_from_item(ctx->jvm, item);
|
||||
unsigned k = item_id - 0x12a;
|
||||
if (k > 0x13) return 0;
|
||||
if ((unsigned)(item_id + item_id_offsets[k]) != kind) return 0;
|
||||
long long mat = material_ids[k];
|
||||
void *nbt = java_call_item_stack_get_enchantment_tag_list(ctx->jvm, stack);
|
||||
unsigned char points = item_points_table[mat * 4 + kind];
|
||||
double prot = 0.0;
|
||||
if (nbt) {
|
||||
JNIEnv *env = ((Jvm *)ctx->jvm)->env;
|
||||
jstring idS = (*env)->NewStringUTF(env, str_id_in_chest_gui);
|
||||
if (idS) {
|
||||
jstring lvlS = (*env)->NewStringUTF(env, str_lvl_in_chest_gui);
|
||||
if (lvlS) {
|
||||
int n = java_call_nbttag_list_tag_count(ctx->jvm, nbt);
|
||||
if (n > 0) {
|
||||
int i = 0;
|
||||
float extra = 0.0f;
|
||||
double k0 = tenth_in_inventory_swap;
|
||||
|
||||
double k1 = three_quarters;
|
||||
|
||||
double k2 = three_in_inventory_swap;
|
||||
|
||||
float k3 = half_in_inventory_swap;
|
||||
|
||||
float acc = 0.0f;
|
||||
for (; i != n; i++) {
|
||||
void *tag = java_call_nbttag_list_get_compound_tag_at(ctx->jvm, nbt, i);
|
||||
unsigned short eid = (unsigned short)java_jni_call_short_method_nbttag_compound_get_short(ctx->jvm, tag, idS);
|
||||
short lvl = java_jni_call_short_method_nbttag_compound_get_short(ctx->jvm, tag, lvlS);
|
||||
if (lvl < 0) continue;
|
||||
int L = lvl;
|
||||
switch (eid) {
|
||||
case 1: case 2: case 3: case 4:
|
||||
acc = (float)((double)L * k0 + (double)acc);
|
||||
break;
|
||||
case 0: {
|
||||
unsigned v = (unsigned)(long long)((double)(L * L + 6) * k1 / k2);
|
||||
if (v >= 0x19) v = 0x19;
|
||||
v = ((v + 1) & 0x3e) + (v >> 1);
|
||||
extra = (float)(int)v * k3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
prot = (double)(acc + extra);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
double c1 = swap_blend_start_weight, c2 = one_in_inventory_swap;
|
||||
double t = c2 - (double)points * c1;
|
||||
double r = c2 - (c2 - prot * c1) * t;
|
||||
return (unsigned)(long long)((float)r * factor_1000_b);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// helper functions (formerly address block; names inferred from behaviour).
|
||||
// cflags: -mllvm -switch-to-lookup=false
|
||||
#include "slinky/engine.h"
|
||||
FN(void *, java_get_minecraft_player_controller, (void *jvm, void *minecraft));
|
||||
FN(unsigned char, java_get_player_controller_mp_is_hitting_block, (void *jvm, void *player_controller_mp));
|
||||
FN(void, java_set_player_controller_mp_is_hitting_block, (void *jvm, void *player_controller_mp, int on));
|
||||
extern "C" unsigned char item_use_fix_item_use_flag, timer_range_active_flag, timer_range_timer_modified;
|
||||
extern "C" int timer_range_delay_ticks;
|
||||
struct Ev { char padding_00[8]; Ctx *ctx; unsigned char on; };
|
||||
extern "C" void item_use_fix_on_attack(Ev *event) {
|
||||
if (__builtin_expect(event->on != 0, 0))
|
||||
goto out;
|
||||
{
|
||||
Ctx *ctx = event->ctx;
|
||||
void *minecraft = java_get_minecraft_player_controller(ctx->jvm, ctx->minecraft);
|
||||
unsigned char usable = java_get_player_controller_mp_is_hitting_block(ctx->jvm, minecraft);
|
||||
item_use_fix_item_use_flag = usable != 0;
|
||||
if (usable) {
|
||||
java_set_player_controller_mp_is_hitting_block(ctx->jvm, minecraft, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
out:
|
||||
EPILOG_NOP();
|
||||
}
|
||||
extern "C" void item_use_fix_on_use(Ev *event) {
|
||||
if (item_use_fix_item_use_flag != 1 || event->on != 0) return;
|
||||
Ctx *ctx = event->ctx;
|
||||
void *minecraft = java_get_minecraft_player_controller(ctx->jvm, ctx->minecraft);
|
||||
void *jvm = ctx->jvm;
|
||||
|
||||
java_set_player_controller_mp_is_hitting_block(jvm, minecraft, item_use_fix_item_use_flag);
|
||||
}
|
||||
extern "C" bool timer_range_is_delay_pending() {
|
||||
unsigned char r;
|
||||
if (timer_range_active_flag == 1) { asm volatile(""); r = (timer_range_delay_ticks > 0) | timer_range_timer_modified; }
|
||||
else r = 0;
|
||||
SHARED_EPILOG();
|
||||
return r & 1;
|
||||
}
|
||||
|
||||
FN(unsigned char, java_call_entity_player_is_using_item, (void *jvm, void *entity_player));
|
||||
FN(void *, item_held_hotbar_slot, (void *jvm, void *slot));
|
||||
FN(int, java_call_static_item_get_id_from_item, (void *jvm, void *item));
|
||||
extern "C" float no_slow_mult_sword_value, no_slow_mult_bow_value, no_slow_mult_consumables_value;
|
||||
extern "C" double divisor_0_2_in_item_use_fix_and_no_slow;
|
||||
struct Ev2 { char padding_00[8]; Ctx *ctx; float x, y; };
|
||||
extern "C" void no_slow_item_use_speed(Ev2 *ev) {
|
||||
Ctx *ctx = ev->ctx;
|
||||
if (!java_call_entity_player_is_using_item(ctx->jvm, ctx->player)) return;
|
||||
void *item = item_held_hotbar_slot(ctx->jvm, ctx->player);
|
||||
if (!item) return;
|
||||
float *f;
|
||||
switch (java_call_static_item_get_id_from_item(ctx->jvm, item)) {
|
||||
case 0x10b: case 0x10c: case 0x110: case 0x114: case 0x11b: f = &no_slow_mult_sword_value; break; // swords
|
||||
case 0x105: f = &no_slow_mult_bow_value; break;
|
||||
default: f = &no_slow_mult_consumables_value; break;
|
||||
}
|
||||
double s = (double)*f / divisor_0_2_in_item_use_fix_and_no_slow;
|
||||
ev->x = (float)(s * ev->x);
|
||||
ev->y = (float)(s * ev->y);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// 0x4b000: key-code -> category
|
||||
#include "slinky/engine.h"
|
||||
extern "C" int key_category(int k) {
|
||||
switch (k) {
|
||||
case 0x1: case 0x2: case 0x3: case 0x14: case 0x18: case 0x2d: case 0x57: case 0x9f: return 2;
|
||||
case 0x4: case 0x5: case 0x11: case 0x79: case 0xa2: return 3;
|
||||
case 0x23: return 1;
|
||||
case 0x31: return 4;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// 0x4c000: Windows virtual-key -> key code, small CRT printf wrappers, string append
|
||||
#include "slinky/engine.h"
|
||||
#include "slinky/win32.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
extern "C" int windows_key_to_client_key(unsigned long long vk) {
|
||||
if (vk - 0x41 <= 0x19) return (int)vk - 0x27;
|
||||
if (vk - 0x30 <= 9) return (int)vk - 0x2a;
|
||||
if (vk - 0x60 <= 9) return (int)vk - 0x50;
|
||||
if ((vk & ~0xfull) == 0x70) return (int)vk - 0x2d;
|
||||
switch (vk) {
|
||||
case 0x8: return 0x53;
|
||||
case 0x9: return 0x54;
|
||||
case 0xd: return 0x55;
|
||||
case 0x14: return 0x56;
|
||||
case 0x1b: return 0x57;
|
||||
case 0x20: return 0x58;
|
||||
case 0x21: return 0x59;
|
||||
case 0x22: return 0x5a;
|
||||
case 0x23: return 0x5b;
|
||||
case 0x24: return 0x5c;
|
||||
case 0x25: return 0x3f;
|
||||
case 0x26: return 0x40;
|
||||
case 0x27: return 0x41;
|
||||
case 0x28: return 0x42;
|
||||
case 0x2a: return 0x5d;
|
||||
case 0x2d: return 0x5e;
|
||||
case 0x2e: return 0x5f;
|
||||
case 0x90: return 0x60;
|
||||
case 0x91: return 0x61;
|
||||
case 0xa0: return 0x62;
|
||||
case 0xa1: return 0x63;
|
||||
case 0xa2: return 0x64;
|
||||
case 0xa3: return 0x65;
|
||||
case 0xa4: return 0x66;
|
||||
case 0xa5: return 0x67;
|
||||
case 0xba: return 0x34;
|
||||
case 0xbb: return 0x35;
|
||||
case 0xbc: return 0x36;
|
||||
case 0xbd: return 0x37;
|
||||
case 0xbe: return 0x38;
|
||||
case 0xbf: return 0x39;
|
||||
case 0xc0: case 0xdf: return 0x3a;
|
||||
case 0xdb: return 0x3b;
|
||||
case 0xdc: return 0x3c;
|
||||
case 0xdd: return 0x3d;
|
||||
case 0xde: return 0x3e;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
FN(unsigned long long *, printf_options_ptr, ()); // __local_stdio_printf_options
|
||||
extern "C" int libc_vswprintf(unsigned long long opts, char *buf, unsigned long long n, const char *fmt, void *locale, va_list ap);
|
||||
extern "C" int libc_vswprintf_s(unsigned long long opts, wchar_t *buf, unsigned long long size, unsigned long long count,
|
||||
const wchar_t *fmt, void *locale, va_list ap);
|
||||
extern "C" const wchar_t ptr_to_rdata_2ba968[]; // L"%S"
|
||||
extern "C" const char wide_path_format[];
|
||||
FN(unsigned long long, libc_wcslen, (const char *str));
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
FN(void *, libc_calloc, (unsigned long long n, unsigned long long size));
|
||||
FN(void, libc_free, (void *ptr));
|
||||
|
||||
extern "C" int format_string(char *buf, unsigned long long n, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
return libc_vswprintf(*printf_options_ptr(), buf, n, fmt, 0, ap);
|
||||
}
|
||||
// _snwprintf_s(buf, 260, 260, L"%S", ...) with the constants propagated into the out-of-line copy
|
||||
extern "C" int snwprintf_s(wchar_t *buf, unsigned long long, unsigned long long, const wchar_t *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
return libc_vswprintf_s(*printf_options_ptr(), buf, 0x104, 0x104, ptr_to_rdata_2ba968, 0, ap);
|
||||
}
|
||||
extern "C" int settings_path_append(char **pstr, const char *suffix) {
|
||||
char *old = *pstr;
|
||||
if (!old || !suffix) return 0x3ed;
|
||||
unsigned long long a = libc_wcslen(old); // wcslen of the wide path
|
||||
unsigned long long b = libc_strlen(suffix); // strlen of the narrow suffix
|
||||
// the buffer holds 261 wide chars: old + '\' + suffix + NUL
|
||||
if (a > 0x104 || b > 0x104 || a + b + 2 > 0x105) return 0x3ee;
|
||||
char *nb = (char *)libc_calloc(0x105, 2); // 261 wide chars, as the image allocates
|
||||
if (!nb) return 0x3ed;
|
||||
wchar_t *w = (wchar_t *)nb;
|
||||
for (unsigned long long i = 0; i < a; i++) w[i] = ((const wchar_t *)old)[i];
|
||||
w[a] = L'\\';
|
||||
if (!MultiByteToWideChar(65001 /* CP_UTF8 */, 0, suffix, -1, w + a + 1, (int)(0x105 - (a + 1)))) {
|
||||
libc_free(nb);
|
||||
return 0x3ee;
|
||||
}
|
||||
libc_free(*pstr);
|
||||
*pstr = nb;
|
||||
return 0;
|
||||
}
|
||||
extern "C" void free_path(void **p) {
|
||||
libc_free(*p);
|
||||
*p = 0;
|
||||
}
|
||||
|
||||
// ---- gap-scan functions ----
|
||||
FN(unsigned char, crt_stub_returns_zero, ());
|
||||
extern "C" bool crt_initialized() { return crt_stub_returns_zero() != 0; }
|
||||
// cut a wide path at its last backslash (-> directory); no backslash: empty string
|
||||
extern "C" void cut_wide_path_last_backslash(wchar_t **path) {
|
||||
wchar_t *s = *path;
|
||||
unsigned long long i = libc_wcslen((const char *)s);
|
||||
for (; i; i--)
|
||||
if (s[i] == L'\\') {
|
||||
s += i;
|
||||
break;
|
||||
}
|
||||
*s = 0;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// 0x32000: key-name table helpers and a "speed" module's callbacks
|
||||
#include "slinky/engine.h"
|
||||
struct Vec3 { double x, y, z; };
|
||||
|
||||
extern "C" int key_colour_count;
|
||||
extern "C" char key_colour_vector_header[], gui_state_block[];
|
||||
extern "C" void *teams_colour_table;
|
||||
extern "C" long long gui_accent_color_setting_field_0x8; extern "C" int gui_accent_color_setting_field_0x10;
|
||||
extern "C" const char *teams_team_colors_setting_field_0x70[16];
|
||||
extern "C" char gui_module_descriptor[], timer_module_descriptor[];
|
||||
extern "C" float timer_speed_value, one_in_key_names, timer_speed_1_in_key_names_a;
|
||||
extern "C" unsigned char timer_require_mouse_value, timer_require_falling_value, timer_require_damage_value;
|
||||
|
||||
extern "C" int libc_strcmp(const char *left, const char *right);
|
||||
FN(unsigned long long, libc_strlen, (const char *str));
|
||||
FN(void **, element_at, (void *vec, int elem, void *hdr, void *key));
|
||||
FN(void *, vector_find_insert, (void *t, int elem, void *hdr, void *e, void *cmp));
|
||||
FN(void, vector_erase, (void *vec, int elem, void *hdr, void *key));
|
||||
FN(unsigned char, is_unload_requested, (void *x));
|
||||
FN(void *, get_timer, (void *jvm, void *timer));
|
||||
FN(void, java_set_timer_timer_speed, (void *jvm, void *timer, float speed));
|
||||
FN(void, queue_module_toggle, (void *module_manager, void *module, unsigned char enabled));
|
||||
FN(unsigned char, vertex_pointer_current, (Ctx *ctx));
|
||||
FN(unsigned char, java_get_entity_on_ground, (void *jvm, void *entity));
|
||||
FN(Vec3, java_get_entity_motion_x, (void *jvm, void *entity));
|
||||
FN(int, java_get_entity_living_base_hurt_time, (void *jvm, void *entity_living_base));
|
||||
|
||||
extern "C" int compare_signed_chars(const signed char *, const signed char *right, const signed char *b) { return *right - *b; }
|
||||
extern "C" void key_filter(char *buf, char character) {
|
||||
unsigned long long n = libc_strlen(buf);
|
||||
if (n > 99) return;
|
||||
unsigned char l = (unsigned char)(character - 0x61);
|
||||
asm("" : "+r"(l));
|
||||
if (l >= 0x1a) {
|
||||
unsigned char u = (unsigned char)(character - 0x41);
|
||||
asm("" : "+r"(u));
|
||||
unsigned char notupper = u >= 0x1a;
|
||||
asm("" : "+r"(notupper));
|
||||
unsigned char notspace = character != 0x20;
|
||||
asm("" : "+r"(notspace));
|
||||
if (notupper & notspace) return;
|
||||
}
|
||||
buf[n] = character;
|
||||
}
|
||||
extern "C" int get_key_name_index() { return key_colour_count; }
|
||||
extern "C" const char *key_name_for(void *, void *key) {
|
||||
signed char *p = (signed char *)element_at(teams_colour_table, 1, key_colour_vector_header, key);
|
||||
return teams_team_colors_setting_field_0x70[*p];
|
||||
}
|
||||
extern "C" void remove_key_name(void *, void *key) { vector_erase(teams_colour_table, 1, key_colour_vector_header, key); }
|
||||
extern "C" void clear_key_name() { key_colour_count = 0; }
|
||||
extern "C" void key_name_table(const char ***a, long long *n) { *a = teams_team_colors_setting_field_0x70; *n = 0x10; }
|
||||
extern "C" unsigned char is_unload_requested_flag() { return is_unload_requested(gui_state_block); }
|
||||
struct Pair { long long a; int b; };
|
||||
extern "C" Pair get_speed_pair() { Pair r; r.b = gui_accent_color_setting_field_0x10; r.a = gui_accent_color_setting_field_0x8; return r; }
|
||||
extern "C" bool enable_hud_editor(Ctx **h) { queue_module_toggle(h[1], gui_module_descriptor, 1); return true; }
|
||||
|
||||
struct Hk { char padding_00[8]; Ctx *ctx; };
|
||||
extern "C" bool key_name_matches(void *, const char *name) {
|
||||
int i;
|
||||
for (i = 0; i < 16; i++)
|
||||
if (libc_strcmp(name, teams_team_colors_setting_field_0x70[i]) == 0) break;
|
||||
if (i == 16) return false;
|
||||
signed char idx = (signed char)i;
|
||||
vector_find_insert(&teams_colour_table, 1, key_colour_vector_header, &idx, (void *)compare_signed_chars);
|
||||
return true;
|
||||
}
|
||||
extern "C" void speed_hook_tick(Hk *h) {
|
||||
Ctx *ctx = h->ctx;
|
||||
void *timer = get_timer(ctx->jvm, ctx->minecraft);
|
||||
float speed;
|
||||
if (timer_require_mouse_value == 1 && !vertex_pointer_current(ctx)) goto slow;
|
||||
if (timer_require_falling_value == 1) {
|
||||
if (java_get_entity_on_ground(ctx->jvm, ctx->player)) goto slow;
|
||||
Vec3 motion = java_get_entity_motion_x(ctx->jvm, ctx->player);
|
||||
if (motion.y >= 0.0) goto slow;
|
||||
}
|
||||
if (timer_require_damage_value == 1) {
|
||||
if (ctx->in_game != 1) goto slow;
|
||||
if (!java_get_entity_living_base_hurt_time(ctx->jvm, ctx->player)) goto slow;
|
||||
}
|
||||
speed = timer_speed_value;
|
||||
goto go;
|
||||
slow:
|
||||
speed = one_in_key_names;
|
||||
go:
|
||||
java_set_timer_timer_speed(ctx->jvm, timer, speed);
|
||||
}
|
||||
extern "C" void speed_hook_enable(Hk *h) {
|
||||
if (*(void **)((char *)h + 0x10) == timer_module_descriptor && *((char *)h + 0x18) == 0) {
|
||||
Ctx *ctx = h->ctx;
|
||||
void *timer = get_timer(ctx->jvm, ctx->minecraft);
|
||||
void *jvm = ctx->jvm;
|
||||
|
||||
java_set_timer_timer_speed(jvm, timer, timer_speed_1_in_key_names_a);
|
||||
}
|
||||
}
|
||||
|
||||
FN(void, empty_placeholder, (void *x));
|
||||
extern "C" void reset_key_state() { empty_placeholder(gui_state_block); }
|
||||
@@ -0,0 +1,51 @@
|
||||
// helper functions (formerly address block; names inferred from behaviour).
|
||||
#include "slinky/engine.h"
|
||||
extern "C" int no_item_release_mode; extern "C" unsigned char no_item_release_release_lock_flag; extern "C" float hitboxes_expand;
|
||||
struct Ev { char padding_00[8]; void *jvm; void *pkt; char padding_18[0xc]; int verdict; };
|
||||
FN(void *, java_jni_is_instance_of_c08_packet_player_block_placement, (void *w, void *candidate));
|
||||
FN(void *, java_get_c08_packet_player_block_placement_stack, (void *w, void *c08_packet_player_block_placement));
|
||||
FN(void *, java_call_item_stack_get_item, (void *jvm, void *item_stack));
|
||||
FN(int, java_call_static_item_get_id_from_item, (void *jvm, void *item));
|
||||
FN(void *, java_jni_is_instance_of_c07_packet_player_digging, (void *w, void *candidate));
|
||||
FN(int, java_c07_packet_player_digging_status, (void *w, void *object));
|
||||
extern "C" void no_item_release_on_send_packet(Ev *event) {
|
||||
unsigned char lock = 0;
|
||||
void *w = java_jni_is_instance_of_c08_packet_player_block_placement(event->jvm, event->pkt);
|
||||
void *rw = event->jvm;
|
||||
if (w) {
|
||||
void *t = java_get_c08_packet_player_block_placement_stack(rw, w);
|
||||
if (t) {
|
||||
void *item = java_call_item_stack_get_item(rw, t);
|
||||
if (item) {
|
||||
int id = java_call_static_item_get_id_from_item(rw, item);
|
||||
bool compute;
|
||||
switch (id) {
|
||||
case 267: case 268: case 272: case 276: case 283: // swords
|
||||
compute = no_item_release_mode != 1;
|
||||
break;
|
||||
case 261: // bow
|
||||
case 346:
|
||||
compute = false;
|
||||
break;
|
||||
default:
|
||||
compute = no_item_release_mode != 0;
|
||||
break;
|
||||
}
|
||||
if (compute) {
|
||||
int id2 = java_call_static_item_get_id_from_item(rw, item);
|
||||
lock = (id2 != 0x15a) & (id2 != 0x105);
|
||||
}
|
||||
}
|
||||
}
|
||||
no_item_release_release_lock_flag = lock;
|
||||
return;
|
||||
}
|
||||
w = java_jni_is_instance_of_c07_packet_player_digging(rw, event->pkt);
|
||||
if (!w || no_item_release_release_lock_flag == 0)
|
||||
return;
|
||||
if (java_c07_packet_player_digging_status(event->jvm, w) == 5)
|
||||
event->verdict = 2;
|
||||
no_item_release_release_lock_flag = 0;
|
||||
}
|
||||
// original engine copy of hitboxes_on_reach_query; the descriptor uses the logic function (dead duplicate)
|
||||
extern "C" void hitboxes_apply_hitbox_expansion_original(char *p) { p[0x10] = 1; *(double *)(p + 0x20) = hitboxes_expand; }
|
||||
Loaded 100 of 274 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user