mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
Adds the expandedCursors cvar (default 0; AddOn Options > ClassicAPI > Interface > Cursor). At 1 the hover cursor shows what an NPC offers: an anvil for a repairer, a hearthstone for an innkeeper, a horseshoe for a stable master, and a quest giver's ! or ? (a blue ? for a repeatable reward). The player's fishing bobber shows a fish hook in place of the Interact gear. Each has a gray form beyond reach or when not usable. NPCs: ClassifyHoveredUnit (0x00482200) reaches its NPC flag chain at 0x00482336 for a unit the player can interact with, the unit in ESI and the beyond-reach flag in EBX. The chain's `mov ecx, [esi+0x110]` becomes a call to a thunk. At 0 it replays the instruction and the stock chain runs. At 1 it picks from the quest giver status first (CGUnit_C +0xCB8, read by the meaning of the client's status-to-marker table at 0x0080C454), then the NPC flags in service order: repair, innkeeper, flight master, trainer, spirit healer, banker, registrar/tabard/ battlemaster, auctioneer, stable master, vendor, then gossip or a quest giver IsQuestGiverToTalk (0x005DF490) accepts. It leaves through the chain's own epilogue (0x00482356). Bobber: the fishing node handler's cursor slot (vtable 0x0080BC80, slot 4; GetCursor 0x005F8760 is its one reader) points at a wrapper that runs BaseHandlerGetCursor (0x005F3070) and maps Interact/UnableInteract to the hook. The engine still decides whether the bobber is interactable and usable. Showing a mode past 41: SetCursorMode indexes a 42-slot image table, so its existing hook now sends the new modes to Cursor::Extended::Show. It calls SetCustomCursor (0x00523C40) with the mode's path, so a real file wins, and otherwise does what that function does after its load: copy the 32x32 image into slot 41 (0x00C0BD80), store mode 41, send it (SendCursorImage 0x00523790) and keep the name. The hook also records the new modes while Hover::Mode computes, so the soft-interact pick, its icon and SetUnitCursorTexture follow; a quest cursor has no icon image. Images: the 14 BLPs ship inside the DLL (src/embed/Textures, a new TEXTURES_ROOT pack). Texture::Builtin decodes each once with the engine's BLP reader from memory: BlpParse (0x005A83B0) is BlpOpen without the file read, and BlpReset never frees the file buffer. The reader moves to Texture::BlpReader, shared with the map overlays. Texture loads of the same paths, such as the option label's inline icons, get the same images. The reader refuses a DXT5 file. BlpDecodeLevel (0x005A7F00) converts a DXT level through the matrix InitConvertMatrix (0x005A4FC0) fills, and that matrix has DXT1 and DXT3 converters to BGRA8888 but none for DXT5: BlpDecodeLevel would return 1 with its SMemAlloc buffer unwritten. No shipped cursor is DXT5; the check keeps a later embedded DXT5 file, or a DXT5 map overlay, from being read as heap memory. Game paths now compare through one rule, Common::SamePath (any case, either slash), and the embedded manifests through Embed::FindFile. The embedded addons, the bindings splice and sound muting each had their own copy of the rule; they and Texture::Builtin now share it. The option's header, label and tooltip are translated for every client locale, in Blizzard's own terms for each (NSC, PNJ, 物体, 物件, ...). Release Win32 build is clean. In game, only the soft-interact quest icon was checked so far.
166 lines
7.9 KiB
CMake
166 lines
7.9 KiB
CMake
cmake_minimum_required(VERSION 3.10)
|
|
|
|
project(ClassicAPI LANGUAGES CXX RC)
|
|
|
|
# WoW.exe is x86, so the injected DLL must be x86 too — there is no valid
|
|
# 64-bit configure of this project. Fail here rather than let one through:
|
|
# a 64-bit build's first visible symptom is a static_assert deep in a
|
|
# struct-layout check, which reads as an unrelated code fault. An IDE that
|
|
# configures on its own (VS Code's CMake Tools picks an architecture from a
|
|
# scanned kit) can flip an existing build directory this way, which is what
|
|
# CMakePresets.json's `win32` preset exists to prevent.
|
|
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4)
|
|
message(FATAL_ERROR
|
|
"ClassicAPI builds 32-bit only. This configure uses "
|
|
"${CMAKE_SIZEOF_VOID_P}-byte pointers. Delete the build directory, "
|
|
"because CMake cannot switch platform in place. Then configure with "
|
|
"`cmake --preset win32` or `cmake -B build -A Win32`.")
|
|
endif()
|
|
|
|
set(CMAKE_CXX_STANDARD 17)
|
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded")
|
|
|
|
# minhook's CMakeLists.txt declares `cmake_minimum_required(VERSION 3.0...3.5)`,
|
|
# which CMake 4.x flags as a deprecation warning every reconfigure. Bumping
|
|
# the minimum policy version here promotes its policy set to 3.10 without
|
|
# patching the vendored submodule.
|
|
set(CMAKE_POLICY_VERSION_MINIMUM 3.10)
|
|
add_subdirectory(external/minhook)
|
|
|
|
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp")
|
|
file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS "src/*.h")
|
|
|
|
set(TINYCBOR_SOURCES
|
|
external/tinycbor/src/cborencoder.c
|
|
external/tinycbor/src/cborencoder_close_container_checked.c
|
|
external/tinycbor/src/cborencoder_float.c
|
|
external/tinycbor/src/cborparser.c
|
|
external/tinycbor/src/cborparser_dup_string.c
|
|
external/tinycbor/src/cborparser_float.c
|
|
external/tinycbor/src/cborerrorstrings.c)
|
|
|
|
# Version derivation. Dev builds use a sentinel "newer than every
|
|
# plausible release" so addon-side `CLASSIC_API_VERSION >= X` gates
|
|
# always pass locally. CI release builds pass `-DCLASSICAPI_TAG=vX.Y.Z`,
|
|
# which drives BOTH the compiled `CLASSIC_API_VERSION` int AND the
|
|
# embedded addon's `## Version:` toc line (substituted in
|
|
# embed_resources.cmake). The on-disk source toc stays `## Version: DEV`;
|
|
# only the embedded copy is stamped, so a release build needs nothing
|
|
# but the flag — no source edit.
|
|
set(CLASSICAPI_VERSION_VALUE 99999999)
|
|
set(CLASSICAPI_VER_MAJOR 9999)
|
|
set(CLASSICAPI_VER_MINOR 99)
|
|
set(CLASSICAPI_VER_PATCH 99)
|
|
set(CLASSICAPI_TOC_VERSION "DEV")
|
|
|
|
set(CLASSICAPI_TAG
|
|
""
|
|
CACHE STRING "Release tag like v1.2.3")
|
|
|
|
if(CLASSICAPI_TAG MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
|
|
set(CLASSICAPI_VER_MAJOR "${CMAKE_MATCH_1}")
|
|
set(CLASSICAPI_VER_MINOR "${CMAKE_MATCH_2}")
|
|
set(CLASSICAPI_VER_PATCH "${CMAKE_MATCH_3}")
|
|
math(EXPR CLASSICAPI_VERSION_VALUE
|
|
"${CLASSICAPI_VER_MAJOR} * 10000 + ${CLASSICAPI_VER_MINOR} * 100 + ${CLASSICAPI_VER_PATCH}")
|
|
set(CLASSICAPI_TOC_VERSION
|
|
"${CLASSICAPI_VER_MAJOR}.${CLASSICAPI_VER_MINOR}.${CLASSICAPI_VER_PATCH}")
|
|
elseif(CLASSICAPI_TAG)
|
|
message(
|
|
FATAL_ERROR
|
|
"Invalid CLASSICAPI_TAG: ${CLASSICAPI_TAG} (expected vMAJOR.MINOR.PATCH)"
|
|
)
|
|
endif()
|
|
|
|
add_library(${PROJECT_NAME} SHARED
|
|
${SOURCES} ${HEADERS} ${TINYCBOR_SOURCES})
|
|
|
|
# Embed the addons under `src/embed/AddOns/`, `src/bindings/Bindings.xml` and
|
|
# the textures under `src/embed/Textures/` as RCDATA resources. The file-read
|
|
# hook in `src/addons/Embedded.cpp` serves the addon files when the engine
|
|
# asks for paths under `Interface\AddOns\<addon>\`, so the addons load even
|
|
# when the user doesn't have them on disk. `src/bindings/Inject.cpp` splices
|
|
# the bindings XML (our TARGETING-group additions) into the engine's
|
|
# `Interface\FrameXML\Bindings.xml` at load time. `src/texture/Builtin.cpp`
|
|
# serves each texture at its game path when no file has it.
|
|
include(cmake/embed_resources.cmake)
|
|
embed_resources(
|
|
TARGET ${PROJECT_NAME}
|
|
ADDONS_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/src/embed/AddOns"
|
|
ADDONS "!!!ClassicAPI" "ClassicAPI_DebugTools" "ClassicAPI_EventTrace"
|
|
ADDON_VERSION "${CLASSICAPI_TOC_VERSION}"
|
|
BINDINGS_XML "${CMAKE_CURRENT_SOURCE_DIR}/src/bindings/Bindings.xml"
|
|
TEXTURES_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/src/embed/Textures"
|
|
OUT_DIR "${CMAKE_BINARY_DIR}/generated")
|
|
|
|
target_compile_definitions(
|
|
${PROJECT_NAME}
|
|
PRIVATE CLASSICAPI_VERSION_VALUE=${CLASSICAPI_VERSION_VALUE})
|
|
|
|
# Stamp the PE VERSIONINFO resource from the same version components, so the
|
|
# DLL's file/product version (Explorer -> Properties -> Details) matches the
|
|
# CLASSIC_API_VERSION Lua global.
|
|
set(CLASSICAPI_VER_STRING
|
|
"${CLASSICAPI_VER_MAJOR}.${CLASSICAPI_VER_MINOR}.${CLASSICAPI_VER_PATCH}.0")
|
|
set(CLASSICAPI_VERSION_RC "${CMAKE_BINARY_DIR}/generated/version.rc")
|
|
configure_file(
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in"
|
|
"${CLASSICAPI_VERSION_RC}"
|
|
@ONLY)
|
|
target_sources(${PROJECT_NAME} PRIVATE "${CLASSICAPI_VERSION_RC}")
|
|
|
|
# The .def exports `Load` (VanillaFixes' post-injection entry point)
|
|
# undecorated so its GetProcAddress(module, "Load") resolves. See the
|
|
# loader-lock rationale in src/DllMain.cpp. Listed as a source so CMake emits
|
|
# a quoted /DEF: on every generator and relinks when the file changes.
|
|
target_sources(${PROJECT_NAME} PRIVATE src/ClassicAPI.def)
|
|
|
|
# ole32: COM (CoInitializeEx / CoCreateInstance / CoTaskMemFree) for the
|
|
# SAPI text-to-speech backend in src/voicechat/Tts.cpp. The SAPI GUIDs are
|
|
# instantiated in that TU via <initguid.h>, so no sapi.lib is needed.
|
|
# user32: Win32 clipboard API (OpenClipboard / SetClipboardData) for
|
|
# CopyToClipboard in src/clipboard/Copy.cpp.
|
|
target_link_libraries(${PROJECT_NAME} PRIVATE minhook advapi32 ole32 user32)
|
|
|
|
target_include_directories(
|
|
${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
|
|
${CMAKE_BINARY_DIR}/generated
|
|
external/minhook/include
|
|
external
|
|
external/tinycbor/src
|
|
external/unordered_dense/include)
|
|
|
|
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "ClassicAPI")
|
|
|
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MT")
|
|
# /O1 optimizes for size: the code is mostly short glue around engine calls,
|
|
# and /O1 made the DLL 280 KB smaller than /O2 /Ob2 (link-time code generation,
|
|
# /GL + /LTCG, made it larger again). /Zi retains full debug info (source file +
|
|
# line numbers, locals, types) in a separate PDB WITHOUT disabling
|
|
# optimization; the symbols just live in ClassicAPI.pdb on the side. Feeds the
|
|
# linker's /DEBUG below.
|
|
set(CMAKE_CXX_FLAGS_RELEASE "/MT /O1 /DNDEBUG /Zi")
|
|
set(CMAKE_SHARED_LINKER_FLAGS
|
|
# Two debug artifacts for triaging crashes from a SHIPPED DLL. Both are
|
|
# published as release artifacts, match this exact build, and have no effect
|
|
# on code layout:
|
|
# /MAP -> build/Release/ClassicAPI.map: a text symbol->address table
|
|
# that resolves the `0001:offset` addresses in WoW's ERROR #132
|
|
# crash log back to functions by hand (no debugger/dump needed).
|
|
# /DEBUG -> build/Release/ClassicAPI.pdb: full symbols (paired with the
|
|
# compiler's /Zi) so WinDbg/x64dbg/VS produce a line-level
|
|
# symbolicated call stack from a crash dump. A SEPARATE file —
|
|
# it is NOT embedded in the DLL, only a small signature record
|
|
# linking the two is.
|
|
# /DEBUG implicitly disables /OPT:REF and /OPT:ICF (dead-code strip +
|
|
# identical-COMDAT fold); they are listed explicitly below so they stay ON
|
|
# and the shipped DLL does not grow. (/OPT:ICF folds identical functions, so
|
|
# a few map symbols share an address — harmless.)
|
|
# The sections keep the standard layout: .text is read + execute only and
|
|
# constants stay in a read-only .rdata. Nothing writes into this DLL's own
|
|
# code; engine patches make their target writable themselves
|
|
# (Common::PatchBytes).
|
|
"/OPT:REF /OPT:ICF /NODEFAULTLIB:MSVCRT /MAP /DEBUG"
|
|
)
|