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.
210 lines
8.2 KiB
CMake
210 lines
8.2 KiB
CMake
# embed_resources.cmake — embed the addons, the bindings XML and the
|
|
# built-in textures as Win32 RCDATA resources.
|
|
#
|
|
# Runs at configure time. It writes two files into OUT_DIR:
|
|
#
|
|
# embedded.rc one `<id> RCDATA "<path>"` line per pack; rc.exe
|
|
# reads each gzipped pack from disk.
|
|
# embedded_resources.h the IDs the DLL looks up at runtime through
|
|
# src/embed/Resource.h:
|
|
#
|
|
# namespace Embed::Ids { constexpr int kBindingsXml = ...; }
|
|
# namespace Addons::Embedded::Manifest {
|
|
# constexpr const char *kAddons[] = { ... }; // in ADDONS order
|
|
# constexpr size_t kAddonCount = ...;
|
|
# struct File { const char *path; int resourceId; int index; };
|
|
# constexpr File kFiles[] = { ... };
|
|
# constexpr size_t kFileCount = ...;
|
|
# }
|
|
# namespace Texture::Builtin::Manifest {
|
|
# struct File { const char *path; int resourceId; int index; };
|
|
# constexpr File kFiles[] = { ... };
|
|
# constexpr size_t kFileCount = ...;
|
|
# }
|
|
#
|
|
# Each resource is a pack: one gzip stream of several files, which
|
|
# pack_files.cmake joins at build time into `OUT_DIR/embed/<name>.pack.gz`.
|
|
# Resource 1 is the bindings XML, a pack of one; then each addon is one
|
|
# pack, in ADDONS order; then the textures, one pack. A pack compresses each
|
|
# file against the files before it, so the addon text packs to about 22% of
|
|
# its size, against 27% for one gzip stream per file. The DLL inflates a
|
|
# whole pack with the engine's zlib on first use (src/embed/Resource.cpp),
|
|
# so the load-on-demand addon costs nothing until it loads.
|
|
#
|
|
# Addon file paths are relative to ADDONS_ROOT, in backslash form
|
|
# (`!!!ClassicAPI\Util\Print.lua`) — the suffix the engine builds after
|
|
# `Interface\AddOns\`. Texture paths are relative to TEXTURES_ROOT, where
|
|
# each file sits at its game path (`Interface\Cursor\Quest.blp`), for
|
|
# src/texture/Builtin.cpp. The DLL compares both ignoring case. A file's
|
|
# index is its place in its pack, which is in path order.
|
|
#
|
|
# Each addon's `.toc` is the one file that is not embedded verbatim: its
|
|
# `## Version:` line is stamped with ADDON_VERSION into a copy under
|
|
# OUT_DIR, so a release build is a pure function of `-DCLASSICAPI_TAG` and
|
|
# the source tocs stay `DEV`. The developer marker `.classicapi-dev` is
|
|
# never embedded: it must reflect on-disk presence only (see
|
|
# DiskHasDevMarker in src/addons/Embedded.cpp).
|
|
#
|
|
# Rebuild triggers: adding or removing a file reconfigures through the
|
|
# CONFIGURE_DEPENDS glob; editing a toc reconfigures through
|
|
# CMAKE_CONFIGURE_DEPENDS; editing any other file re-packs its addon through
|
|
# the pack's custom command, which recompiles the .rc through its
|
|
# OBJECT_DEPENDS.
|
|
#
|
|
# Usage:
|
|
# embed_resources(TARGET <t> ADDONS_ROOT <dir> ADDONS <folder names...>
|
|
# ADDON_VERSION <v> BINDINGS_XML <file>
|
|
# TEXTURES_ROOT <dir> OUT_DIR <dir>)
|
|
|
|
set(_EMBED_PACK_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/pack_files.cmake")
|
|
|
|
# Add the build-time command that packs `ARGN` (absolute paths, in pack
|
|
# order) into `<out_dir>/embed/<name>.pack.gz`, and set `gz` in the caller
|
|
# to that path. The paths reach the script through a generated list file:
|
|
# an addon's paths are too long for one command line.
|
|
function(_embed_pack out_dir name)
|
|
set(pack "${out_dir}/embed/${name}.pack")
|
|
set(list "set(FILES\n")
|
|
foreach(path ${ARGN})
|
|
string(APPEND list " [==[${path}]==]\n")
|
|
endforeach()
|
|
string(APPEND list ")\n")
|
|
_embed_write_if_changed("${pack}.cmake" "${list}")
|
|
add_custom_command(
|
|
OUTPUT "${pack}.gz"
|
|
COMMAND "${CMAKE_COMMAND}" "-DLIST=${pack}.cmake" "-DOUT=${pack}.gz"
|
|
-P "${_EMBED_PACK_SCRIPT}"
|
|
DEPENDS ${ARGN} "${pack}.cmake" "${_EMBED_PACK_SCRIPT}"
|
|
VERBATIM)
|
|
set(gz "${pack}.gz" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
# Write `content` to `path` only when it differs, so an unchanged
|
|
# reconfigure does not touch the file and trigger a rebuild.
|
|
function(_embed_write_if_changed path content)
|
|
set(tmp "${path}.tmp")
|
|
file(WRITE "${tmp}" "${content}")
|
|
configure_file("${tmp}" "${path}" COPYONLY)
|
|
file(REMOVE "${tmp}")
|
|
endfunction()
|
|
|
|
function(embed_resources)
|
|
cmake_parse_arguments(E ""
|
|
"TARGET;ADDONS_ROOT;ADDON_VERSION;BINDINGS_XML;TEXTURES_ROOT;OUT_DIR"
|
|
"ADDONS" ${ARGN})
|
|
|
|
set(rc "// Generated by cmake/embed_resources.cmake - do not edit.\n\n")
|
|
set(addon_names "")
|
|
set(manifest "")
|
|
set(inputs "")
|
|
|
|
set(bindings_id 1)
|
|
_embed_pack("${E_OUT_DIR}" "Bindings.xml" "${E_BINDINGS_XML}")
|
|
string(APPEND rc "${bindings_id} RCDATA \"${gz}\"\n")
|
|
list(APPEND inputs "${gz}")
|
|
|
|
set(id 2)
|
|
foreach(addon ${E_ADDONS})
|
|
string(APPEND addon_names " \"${addon}\",\n")
|
|
file(GLOB_RECURSE files CONFIGURE_DEPENDS RELATIVE
|
|
"${E_ADDONS_ROOT}" "${E_ADDONS_ROOT}/${addon}/*")
|
|
list(FILTER files EXCLUDE REGEX "(^|/)\\.classicapi-dev$")
|
|
list(SORT files) # deterministic indexes
|
|
set(paths "")
|
|
set(index 0)
|
|
foreach(rel ${files})
|
|
set(abs "${E_ADDONS_ROOT}/${rel}")
|
|
if(rel MATCHES "\\.toc$")
|
|
file(READ "${abs}" toc)
|
|
string(REGEX REPLACE "## Version:[^\n\r]*"
|
|
"## Version: ${E_ADDON_VERSION}" toc "${toc}")
|
|
set(stamped "${E_OUT_DIR}/embed/${rel}")
|
|
_embed_write_if_changed("${stamped}" "${toc}")
|
|
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${abs}")
|
|
set(abs "${stamped}")
|
|
endif()
|
|
list(APPEND paths "${abs}")
|
|
string(REPLACE "/" "\\\\" rel_bs "${rel}")
|
|
string(APPEND manifest " { \"${rel_bs}\", ${id}, ${index} },\n")
|
|
math(EXPR index "${index} + 1")
|
|
endforeach()
|
|
_embed_pack("${E_OUT_DIR}" "${addon}" ${paths})
|
|
string(APPEND rc "${id} RCDATA \"${gz}\"\n")
|
|
list(APPEND inputs "${gz}")
|
|
math(EXPR id "${id} + 1")
|
|
endforeach()
|
|
|
|
file(GLOB_RECURSE textures CONFIGURE_DEPENDS RELATIVE
|
|
"${E_TEXTURES_ROOT}" "${E_TEXTURES_ROOT}/*")
|
|
list(SORT textures) # deterministic indexes
|
|
set(texture_paths "")
|
|
set(texture_manifest "")
|
|
set(index 0)
|
|
foreach(rel ${textures})
|
|
list(APPEND texture_paths "${E_TEXTURES_ROOT}/${rel}")
|
|
string(REPLACE "/" "\\\\" rel_bs "${rel}")
|
|
string(APPEND texture_manifest " { \"${rel_bs}\", ${id}, ${index} },\n")
|
|
math(EXPR index "${index} + 1")
|
|
endforeach()
|
|
_embed_pack("${E_OUT_DIR}" "Textures" ${texture_paths})
|
|
string(APPEND rc "${id} RCDATA \"${gz}\"\n")
|
|
list(APPEND inputs "${gz}")
|
|
|
|
_embed_write_if_changed("${E_OUT_DIR}/embedded.rc" "${rc}")
|
|
_embed_write_if_changed("${E_OUT_DIR}/embedded_resources.h"
|
|
"// Generated by cmake/embed_resources.cmake — do not edit.
|
|
// Resource IDs for the RCDATA packs in embedded.rc, and each embedded file's
|
|
// place in them. Load them through src/embed/Resource.h.
|
|
|
|
#pragma once
|
|
|
|
#include <cstddef>
|
|
|
|
namespace Embed::Ids {
|
|
constexpr int kBindingsXml = ${bindings_id}; // a pack of the one file
|
|
} // namespace Embed::Ids
|
|
|
|
namespace Addons::Embedded::Manifest {
|
|
|
|
// Registration order: the first one registers, and so loads, first.
|
|
constexpr const char *kAddons[] = {
|
|
${addon_names}};
|
|
|
|
constexpr size_t kAddonCount = sizeof(kAddons) / sizeof(kAddons[0]);
|
|
|
|
struct File {
|
|
const char *path;
|
|
int resourceId; // its addon's pack
|
|
int index; // its place in the pack
|
|
};
|
|
|
|
constexpr File kFiles[] = {
|
|
${manifest}};
|
|
|
|
constexpr size_t kFileCount = sizeof(kFiles) / sizeof(kFiles[0]);
|
|
|
|
} // namespace Addons::Embedded::Manifest
|
|
|
|
namespace Texture::Builtin::Manifest {
|
|
|
|
struct File {
|
|
const char *path; // the game path
|
|
int resourceId; // the textures' pack
|
|
int index; // its place in the pack
|
|
};
|
|
|
|
constexpr File kFiles[] = {
|
|
${texture_manifest}};
|
|
|
|
constexpr size_t kFileCount = sizeof(kFiles) / sizeof(kFiles[0]);
|
|
|
|
} // namespace Texture::Builtin::Manifest
|
|
")
|
|
|
|
add_custom_target(${E_TARGET}_embed DEPENDS ${inputs})
|
|
add_dependencies(${E_TARGET} ${E_TARGET}_embed)
|
|
target_sources(${E_TARGET} PRIVATE "${E_OUT_DIR}/embedded.rc")
|
|
set_source_files_properties("${E_OUT_DIR}/embedded.rc" PROPERTIES
|
|
OBJECT_DEPENDS "${inputs}")
|
|
endfunction()
|