mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-11 04:17:38 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7103de564 | ||
|
|
c00eb24a8d | ||
|
|
b65c9e0319 | ||
|
|
de033896b1 | ||
|
|
dc8d4caf12 | ||
|
|
20069f3815 | ||
|
|
f2fa112f0a | ||
|
|
fca967c827 | ||
|
|
24404b5a23 | ||
|
|
a106d86969 | ||
|
|
fac3be3a0b | ||
|
|
b49c107778 | ||
|
|
53b7f79440 | ||
|
|
7b21453469 | ||
|
|
ebfd9d034d | ||
|
|
d825bcbdda | ||
|
|
6b36bb10e6 | ||
|
|
6628dcca19 | ||
|
|
55d94c80d6 | ||
|
|
c0b403ba05 | ||
|
|
f6b98bc3ec | ||
|
|
8f4f574174 | ||
|
|
43a989dd56 | ||
|
|
0caddb8fe6 | ||
|
|
8e603a7a0b | ||
|
|
4726fd7c85 | ||
|
|
3e0c40184b | ||
|
|
b3e8d92b03 | ||
|
|
0a3f8324ea | ||
|
|
6f3a8ffc1d | ||
|
|
3862408e1a | ||
|
|
12a81f7f89 | ||
|
|
05fa5845b7 |
@@ -10,3 +10,4 @@
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.ico binary
|
||||
*.blp binary
|
||||
|
||||
+9
-7
@@ -76,13 +76,14 @@ endif()
|
||||
add_library(${PROJECT_NAME} SHARED
|
||||
${SOURCES} ${HEADERS} ${TINYCBOR_SOURCES})
|
||||
|
||||
# Embed the addons under `src/embed/AddOns/` and `src/bindings/Bindings.xml` 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.
|
||||
# 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}
|
||||
@@ -90,6 +91,7 @@ embed_resources(
|
||||
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(
|
||||
|
||||
@@ -106,13 +106,13 @@ In the Frame rows, a name that starts with `:` is a method of the widget that th
|
||||
| [Loot](docs/API.md#loot) | `C_Loot.GetNearbyLootableUnits`, `C_Loot.GetLastScanResults`, `C_Loot.IsScanInProgress`, `C_Loot.LootAllCorpses`, `C_Loot.LootUnit`, `C_Loot.LootUnitItem`, `C_Loot.ScanNearbyLoot` |
|
||||
| [LootHistory](docs/API.md#loothistory) | `C_LootHistory.GetNumItems`, `C_LootHistory.GetItem`, `C_LootHistory.GetPlayerInfo`, `C_LootHistory.Clear` |
|
||||
| [LossOfControl](docs/API.md#lossofcontrol) | `C_LossOfControl.GetActiveLossOfControlData`, `C_LossOfControl.GetActiveLossOfControlDataCount`, `C_LossOfControl.GetSchoolLockout` |
|
||||
| [Lua](docs/API.md#lua) | `collectgarbage` (5.1 options), `coroutine.create`, `coroutine.resume`, `coroutine.running`, `coroutine.status`, `coroutine.wrap`, `coroutine.yield`, `CreateFromMixins`, `GetCallstackHeight`, `GetErrorCallstackHeight`, `math.fmod`, `math.huge`, `math.modf`, `Mixin`, `select`, `SetErrorCallstackHeight`, `strcmputf8i`, `strconcat`, `string.concat`, `string.gmatch`, `string.gsub` (table replacement), `string.join`, `string.match`, `string.reverse`, `string.split`, `string.trim`, `strjoin`, `strlenutf8`, `strmatch`, `strreplace`, `strrev`, `strsplit`, `strsplittable`, `strtrim`, `table.count`, `table.maxn`, `table.wipe`, `unpack` (range args), `wipe`, `xpcall` (argument forwarding) |
|
||||
| [Lua](docs/API.md#lua) | `collectgarbage` (5.1 options), `coroutine.create`, `coroutine.resume`, `coroutine.running`, `coroutine.status`, `coroutine.wrap`, `coroutine.yield`, `CreateFromMixins`, `debuglocals`, `debugstack` (coroutine argument), `difftime`, `GetCallstackHeight`, `GetErrorCallstackHeight`, `gmatch`, `math.cosh`, `math.fmod`, `math.huge`, `math.modf`, `math.sinh`, `math.tanh`, `Mixin`, `newproxy`, `scrub`, `select`, `SetErrorCallstackHeight`, `strbyte` (range args), `strcmputf8i`, `strconcat`, `string.byte` (range args), `string.concat`, `string.format` (two-digit `%n$`), `string.gmatch`, `string.gsub` (table replacement, `%0`), `string.join`, `string.match`, `string.reverse`, `string.split`, `string.trim`, `strjoin`, `strlenutf8`, `strmatch`, `strreplace`, `strrev`, `strsplit`, `strsplittable`, `strtrim`, `table.count`, `table.maxn`, `table.removemulti`, `table.wipe`, `unpack` (range args), `wipe`, `xpcall` (argument forwarding) |
|
||||
| [Macros](docs/API.md#macros) | `C_Macro.CreateMacro`, `C_Macro.EditMacro`, `C_Macro.SetMacroDisplay`, `DeleteMacro` / `EditMacro` / `GetMacroInfo` / `PickupMacro` by name, `GetLooseMacroIcons`, `GetLooseMacroItemIcons`, `GetMacroBody`, `GetMacroIcons`, `GetMacroItem`, `GetMacroItemIcons`, `GetMacroSpell`, `GetRunningMacro`, `GetRunningMacroButton`, `RunMacroText`, `SetMacroItem`, `SetMacroSpell`, `StopMacro` |
|
||||
| [Mail](docs/API.md#mail) | `GetInboxItemLink`, `GetSendMailItemLink` |
|
||||
| [Map](docs/API.md#map) | `C_Map.CanSetUserWaypointOnMap`, `C_Map.ClearUserWaypoint`, `C_Map.GetAreaInfo`, `C_Map.GetAreas`, `C_Map.GetAreaTriggerInfo`, `C_Map.GetAreaTriggers`, `C_Map.GetBestMapForUnit`, `C_Map.GetFallbackWorldMapID`, `C_Map.GetMapAreaIDs`, `C_Map.GetMapArtLayers`, `C_Map.GetMapArtLayerTextures`, `C_Map.GetMapChildrenInfo`, `C_Map.GetMapInfo`, `C_Map.GetMapInfoAtPosition`, `C_Map.GetMapOverlays`, `C_Map.GetMapPosFromWorldPos`, `C_Map.GetMapRectOnMap`, `C_Map.GetMapWorldSize`, `C_Map.GetPlayerMapPosition`, `C_Map.GetUserWaypoint`, `C_Map.GetUserWaypointFromHyperlink`, `C_Map.GetUserWaypointHyperlink`, `C_Map.GetUserWaypointPositionForMap`, `C_Map.GetWorldPosFromMapPos`, `C_Map.HasUserWaypoint`, `C_Map.MapHasArt`, `C_Map.SetUserWaypoint` |
|
||||
| [MapExplorationInfo](docs/API.md#mapexplorationinfo) | `C_MapExplorationInfo.GetExploredAreaIDsAtPosition`, `C_MapExplorationInfo.GetExploredMapTextures`, `C_MapExplorationInfo.GetUnexploredMapTextures` |
|
||||
| [MerchantFrame](docs/API.md#merchantframe) | `C_MerchantFrame.GetBuybackItemID`, `C_MerchantFrame.GetItemInfo`, `C_MerchantFrame.GetNumJunkItems`, `C_MerchantFrame.IsMerchantItemRefundable`, `C_MerchantFrame.IsSellAllJunkEnabled`, `C_MerchantFrame.SellAllJunkItems`, `GetBuybackItemLink` |
|
||||
| [Model](docs/API.md#model) | `DressUpModel:TryOn` (links, recipes, names), `DressUpModel:UndressSlot`, `Model:SetGlow`, `PlayerModel:RefreshCamera`, `PlayerModel:SetCamDistanceScale`, `PlayerModel:SetCreature`, `PlayerModel:SetDisplayInfo`, `PlayerModel:SetPortraitZoom`, `PlayerModel:SetRotation` (`animate`) |
|
||||
| [Model](docs/API.md#model) | `DressUpModel:TryOn` (links, recipes, names, hand, enchant glow, result), `DressUpModel:UndressSlot`, `DressUpModel:IsGeoReady`, `DressUpModel:SetSheathed` / `GetSheathed`, `DressUpModel:SetAutoDress` / `GetAutoDress`, `Model:SetGlow`, `PlayerModel:RefreshCamera`, `PlayerModel:SetCamDistanceScale`, `PlayerModel:SetCreature` (`displayID`), `PlayerModel:SetDisplayInfo`, `PlayerModel:SetItem`, `PlayerModel:SetPortraitZoom`, `PlayerModel:SetRotation` (`animate`), `PlayerModel:SetUnit` (result), `PlayerModel:CanSetUnit`, `PlayerModel:GetDisplayInfo`, `PlayerModel:SetAnimation`, `PlayerModel:FreezeAnimation`, `PlayerModel:HasAnimation`, `PlayerModel:SetDoBlend` / `GetDoBlend`, `PlayerModel:SetKeepModelOnHide` / `GetKeepModelOnHide`, `PlayerModel:ApplySpellVisualKit` |
|
||||
| [NamePlate](docs/API.md#nameplate) | `C_NamePlate.GetNamePlateForGUID`, `C_NamePlate.GetNamePlateForUnit`, `C_NamePlate.GetNamePlateGUIDs`, `C_NamePlate.GetNamePlates` |
|
||||
| [NameCache](docs/API.md#namecache) | `C_PlayerCache.GetPlayerInfoByName`, `C_PlayerCache.IsEnabled`, `C_PlayerCache.RememberPlayer`, `C_PlayerCache.SetEnabled`, `GetPlayerInfoByGUID`, `UnitNameFromGUID` |
|
||||
| [NewItems](docs/API.md#newitems) | `C_NewItems.ClearAll`, `C_NewItems.IsNewItem`, `C_NewItems.RemoveNewItem` |
|
||||
@@ -131,7 +131,7 @@ In the Frame rows, a name that starts with `:` is a method of the widget that th
|
||||
| [Talent](docs/API.md#talent) | `GetTalentIDByIndex`, `GetTalentSpellID` |
|
||||
| [Targeting](docs/API.md#targeting) | `GetPlayerFacing`, `TargetDirectionEnemy`, `TargetDirectionFriend`, `TargetDirectionFinished`, `TargetNearest`, `TargetNearestEnemyPlayer`, `TargetNearestFriendPlayer`, `InteractUnit`, `C_PlayerInteractionManager.InteractUnit`, `IsTargetLoose` |
|
||||
| [TaxiMap](docs/API.md#taximap) | `C_TaxiMap.GetTaxiNodesForMap`, `C_TaxiMap.GetAllTaxiNodes`, `C_TaxiMap.GetTaxiPaths`, `C_TaxiMap.GetTaxiPathWaypoints`, `C_TaxiMap.GetTaxiRoute` |
|
||||
| [Texture](docs/API.md#texture) | `C_Texture.GetAtlasElementID`, `C_Texture.GetAtlasElements`, `C_Texture.GetAtlasExists`, `C_Texture.GetAtlasID`, `C_Texture.GetAtlasInfo`, `C_Texture.RegisterAtlas` |
|
||||
| [Texture](docs/API.md#texture) | `C_Texture.GetAtlasElementID`, `C_Texture.GetAtlasElements`, `C_Texture.GetAtlasExists`, `C_Texture.GetAtlasID`, `C_Texture.GetAtlasInfo`, `C_Texture.RegisterAtlas`, `SetPortraitTextureFromCreatureDisplayID` |
|
||||
| [Time](docs/API.md#time) | `C_DateAndTime.AdjustTimeByDays`, `C_DateAndTime.AdjustTimeByMinutes`, `C_DateAndTime.CompareCalendarTime`, `C_DateAndTime.GetCalendarTimeFromEpoch`, `C_DateAndTime.GetCurrentCalendarTime`, `C_DateAndTime.GetSecondsUntilDailyReset`, `C_DateAndTime.GetServerTimeLocal`, `C_Timer.After`, `C_Timer.NewTicker`, `C_Timer.NewTimer`, `GetServerTime`, `GetTickTime`, `GetTimeCached` |
|
||||
| [Totem](docs/API.md#totem) | `GetTotemInfo`, `GetTotemTimeLeft`, `GetTotemDuration`, `TargetTotem` |
|
||||
| [Tracking](docs/API.md#tracking) | `GetNumTrackingTypes`, `GetTrackingInfo`, `SetTracking` |
|
||||
@@ -219,12 +219,13 @@ open the game menu and click AddOn Options, then ClassicAPI.
|
||||
| [Bindings](docs/API.md#actionbuttonusekeydown) | `ActionButtonUseKeyDown` | `0` | `1` uses an action-bar key binding when you press the key. `0` uses it when you release the key. |
|
||||
| [Combat](docs/API.md#stopautoattackontargetchange) | `stopAutoAttackOnTargetChange` | `0` | `1` stops melee auto-attack, Auto Shot and wand shots when you change target. |
|
||||
| [Combat log](docs/API.md#kept-entries) | `combatLogRetentionTime` | `300` | The number of seconds that the combat log keeps an entry. |
|
||||
| [Cast settings](docs/API.md#cast-settings-autostand-autodismount-autounshift) | `autoStand` | `0` | `1` makes you stand up when you cast while you sit. |
|
||||
| | `autoDismount` | `0` | `1` makes you dismount when you cast while you are mounted. |
|
||||
| | `autoUnshift` | `0` | `1` makes you leave your form when a spell cannot be cast in it. |
|
||||
| [Stand, dismount and form](docs/API.md#stand-dismount-and-form-settings-autostand-autodismount-autounshift) | `autoStand` | `0` | `1` makes you stand up when you cast, loot or open the flight map while you sit. |
|
||||
| | `autoDismount` | `0` | `1` makes you dismount when you cast, use an object, loot, attack or open the flight map while you are mounted. |
|
||||
| | `autoUnshift` | `0` | `1` makes you leave your form when you cast a spell that cannot be cast in it, use an item, or open the flight map. It also lets you talk to NPCs and use objects in druid forms and Ghost Wolf. |
|
||||
| [Bag sorting](docs/API.md#c_containergetsortbagsrighttoleft--c_containersetsortbagsrighttoleftenable) | `sortBagsRightToLeft` | `0` | `1` makes a sort fill your bags from the last slot of the last bag. |
|
||||
| | `backpackAutosortDisabled` | `0` | `1` keeps the items in your backpack in place when you sort your bags. |
|
||||
| | `bankAutosortDisabled` | `0` | `1` keeps the items in the main bank slots in place when you sort your bank. |
|
||||
| [Expanded cursors](docs/API.md#expanded-cursors-expandedcursors) | `expandedCursors` | `0` | `1` makes the cursor show what you can do with an NPC or an object: an anvil for repairs, a hearthstone for an inn, a horseshoe for a stable, `!` or `?` for quests, and a fish hook for your fishing bobber. |
|
||||
| [Equipment sets](docs/API.md#equipment-sets-on-item-tooltips) | `dontShowEquipmentSetsOnItems` | `0` | `1` removes the Equipment Sets line from item tooltips. |
|
||||
| [Name completion](docs/API.md#name-completion-settings) | `autoCompleteResortNamesOnRecency` | `1` | `1` puts the players that you whispered first in `C_AutoComplete` results, the most recent first. `0` uses alphabetical order. |
|
||||
| | `autoCompleteWhenEditingFromCenter` | `1` | `1` also matches the text after the cursor when you type inside a name. |
|
||||
@@ -504,10 +505,10 @@ the embedded addon.
|
||||
## Lua errors
|
||||
|
||||
ClassicAPI replaces the game's Lua error box with a Lua error window. The
|
||||
window opens when an error occurs. It shows the message, the time, the count
|
||||
and the stack of each error. You can select and copy the text. The arrow
|
||||
buttons show the previous and the next error. The **Reload UI** button
|
||||
reloads the interface.
|
||||
window opens when an error occurs. It shows the message, the time, the count,
|
||||
the stack and the local variables of each error. You can select and copy the
|
||||
text. The arrow buttons show the previous and the next error. The
|
||||
**Reload UI** button reloads the interface.
|
||||
|
||||
If you use `!BugGrabber` (with BugSack), BugGrabber gets the errors and the
|
||||
window does not open. If an addon sets its own error handler, the window also
|
||||
|
||||
+52
-12
@@ -1,5 +1,5 @@
|
||||
# embed_resources.cmake — embed the addons and the bindings XML as Win32
|
||||
# RCDATA resources.
|
||||
# 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:
|
||||
#
|
||||
@@ -16,20 +16,27 @@
|
||||
# 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. 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.
|
||||
# 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.
|
||||
#
|
||||
# File paths are relative to ADDONS_ROOT, in backslash form
|
||||
# Addon file paths are relative to ADDONS_ROOT, in backslash form
|
||||
# (`!!!ClassicAPI\Util\Print.lua`) — the suffix the engine builds after
|
||||
# `Interface\AddOns\`. The DLL compares them ignoring case. A file's index
|
||||
# is its place in its addon's pack, which is in path order.
|
||||
# `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
|
||||
@@ -46,7 +53,8 @@
|
||||
#
|
||||
# Usage:
|
||||
# embed_resources(TARGET <t> ADDONS_ROOT <dir> ADDONS <folder names...>
|
||||
# ADDON_VERSION <v> BINDINGS_XML <file> OUT_DIR <dir>)
|
||||
# ADDON_VERSION <v> BINDINGS_XML <file>
|
||||
# TEXTURES_ROOT <dir> OUT_DIR <dir>)
|
||||
|
||||
set(_EMBED_PACK_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/pack_files.cmake")
|
||||
|
||||
@@ -81,7 +89,8 @@ function(_embed_write_if_changed path content)
|
||||
endfunction()
|
||||
|
||||
function(embed_resources)
|
||||
cmake_parse_arguments(E "" "TARGET;ADDONS_ROOT;ADDON_VERSION;BINDINGS_XML;OUT_DIR"
|
||||
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")
|
||||
@@ -125,6 +134,22 @@ function(embed_resources)
|
||||
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.
|
||||
@@ -159,6 +184,21 @@ ${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})
|
||||
|
||||
+1405
-891
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -178,9 +178,9 @@ behaviour the comment describes (`push 0; mov edx, 1; mov ecx, L`) is
|
||||
| `0x006F4450` | `lua_next` | fastcall | calls `luaH_next` at `0x006FA2A0` | ★ |
|
||||
| `0x006F44E0` | `lua_concat` | fastcall | calls `luaV_concat` at `0x006F84D0` | ★ |
|
||||
| `0x006F4560` | `lua_newuserdata` | fastcall | tag 7; calls `luaS_newudata` at `0x006F9E30` | ★ |
|
||||
| `0x006F45B0` | `lua_getupvalue` | fastcall | reads upvalue from C closure | ◦ |
|
||||
| `0x006F4660` | `lua_setupvalue` | fastcall | | ◦ |
|
||||
| `0x006F46E0` | *internal upvalue helper*| | shared by get/set above | ◦ |
|
||||
| `0x006F45B0` | `lua_pushupvalues` | fastcall | pushes the running C closure's upvalues, returns how many | ★ |
|
||||
| `0x006F4660` | `lua_getupvalue` | fastcall | `(L, funcindex, n)`: pushes upvalue n, returns its name or null | ★ |
|
||||
| `0x006F46E0` | `aux_upvalue` | | finds upvalue n of a function, returns its name | ★ |
|
||||
|
||||
## Auxiliary library (`luaL_*`)
|
||||
|
||||
|
||||
@@ -64,6 +64,17 @@ bool RedirectJcc(uintptr_t site, uintptr_t expected, const void *target) {
|
||||
return PatchBytes(reinterpret_cast<uint8_t *>(site) + 2, &rel, sizeof rel);
|
||||
}
|
||||
|
||||
bool RedirectSlot(uintptr_t slot, uintptr_t expected, const void *target) {
|
||||
uintptr_t current = 0;
|
||||
std::memcpy(¤t, reinterpret_cast<const void *>(slot), sizeof current);
|
||||
const uintptr_t to = reinterpret_cast<uintptr_t>(target);
|
||||
if (current == to)
|
||||
return true;
|
||||
if (current != expected)
|
||||
return false;
|
||||
return PatchBytes(reinterpret_cast<void *>(slot), &to, sizeof to);
|
||||
}
|
||||
|
||||
bool ReplaceWithCall(uintptr_t site, const uint8_t *original, size_t len, const void *target) {
|
||||
uint8_t code[64];
|
||||
if (len < 5 || len > sizeof code)
|
||||
@@ -102,4 +113,20 @@ void EnsureParentDirs(const char *relPath) {
|
||||
}
|
||||
}
|
||||
|
||||
char FoldPathChar(char c) {
|
||||
if (c == '/')
|
||||
return '\\';
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return static_cast<char>(c + ('a' - 'A'));
|
||||
return c;
|
||||
}
|
||||
|
||||
bool SamePath(const char *a, const char *b) {
|
||||
for (; FoldPathChar(*a) == FoldPathChar(*b); ++a, ++b) {
|
||||
if (*a == '\0')
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -28,6 +28,10 @@ bool RedirectCall(uintptr_t site, uintptr_t expected, const void *target);
|
||||
// at `site` to `target` when it still reaches `expected`. Same contract.
|
||||
bool RedirectJcc(uintptr_t site, uintptr_t expected, const void *target);
|
||||
|
||||
// RedirectCall for a function-pointer slot (a vtable entry): points the slot
|
||||
// at `slot` to `target` when it still holds `expected`. Same contract.
|
||||
bool RedirectSlot(uintptr_t slot, uintptr_t expected, const void *target);
|
||||
|
||||
// Writes `call target` plus NOP padding over the `len` bytes (5 to 64) of
|
||||
// code at `site` when they still hold `original`; true when the site
|
||||
// now calls `target`. A site holding anything else (another DLL's patch, a
|
||||
@@ -45,4 +49,11 @@ void BoundedCopy(char *dst, const char *src, size_t n);
|
||||
// directory. Directories that already exist are a no-op.
|
||||
void EnsureParentDirs(const char *relPath);
|
||||
|
||||
// `c` as game paths compare it: '/' as '\', and A-Z as a-z.
|
||||
char FoldPathChar(char c);
|
||||
|
||||
// Whether `a` and `b` are the same game path: equal after FoldPathChar, so
|
||||
// any case and either slash.
|
||||
bool SamePath(const char *a, const char *b);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
+7
-4
@@ -80,10 +80,13 @@ static void __stdcall LoadGlueScriptFunctions_h() {
|
||||
}
|
||||
|
||||
// Every Lua-side `frame:RegisterEvent(...)` is a chance to claim a slot
|
||||
// for any custom event still waiting. By this point the engine's table
|
||||
// is fully populated and SuperWoWhook / other DLLs have done their
|
||||
// post-rebuild writes, so the table state is settled and our walk from the
|
||||
// low end finds genuinely free slots.
|
||||
// for any custom event still waiting. By the first FrameXML registration
|
||||
// the engine's table is fully populated and SuperWoWhook / other DLLs have
|
||||
// done their post-rebuild writes, so the walk from the low end finds
|
||||
// genuinely free slots. A registration that arrives EARLIER (another DLL's
|
||||
// frame, between LoadScriptFunctions and the table build — issue #134)
|
||||
// still sees glue's 26-entry table; `RetryClaims` refuses that table and
|
||||
// re-validates its cached slots against the live one on every call.
|
||||
static void __fastcall FrameRegisterEvent_h(void *frame, void *edx,
|
||||
const char *eventName) {
|
||||
Event::Custom::RetryClaims();
|
||||
|
||||
+16
-11
@@ -252,6 +252,12 @@ void *ResolveEditBox(void *L, int idx, bool raiseError) {
|
||||
void *ResolveModel(void *L, int idx, bool raiseError) {
|
||||
return ResolveTypedObject(L, idx, Offsets::VAR_MODEL_LUA_TYPE_ID, raiseError);
|
||||
}
|
||||
void *ResolvePlayerModel(void *L, int idx, bool raiseError) {
|
||||
return ResolveTypedObject(L, idx, Offsets::VAR_PLAYERMODEL_LUA_TYPE_ID, raiseError);
|
||||
}
|
||||
void *ResolveDressUpModel(void *L, int idx, bool raiseError) {
|
||||
return ResolveTypedObject(L, idx, Offsets::VAR_DRESSUPMODEL_LUA_TYPE_ID, raiseError);
|
||||
}
|
||||
|
||||
// Looks up `_G[name]`. If absent, creates a fresh table and binds it.
|
||||
// Leaves the resulting table on top of the stack.
|
||||
@@ -361,11 +367,11 @@ void RegisterGlueFunction(const char *name, CFunction func, const Doc::Function
|
||||
Api::Documentation::RecordGlobal(name, doc);
|
||||
}
|
||||
|
||||
void RegisterFrameMethods(void *context, const FrameMethodEntry *table, int count,
|
||||
const Doc::Method *docs, int docCount) {
|
||||
void RegisterFrameMethods(void *context, ArrayRef<FrameMethodEntry> methods,
|
||||
Doc::MethodList docs) {
|
||||
auto fn = reinterpret_cast<RegisterFrameMethods_t>(Offsets::FUN_REGISTER_FRAME_METHODS);
|
||||
fn(table, count, context);
|
||||
Api::Documentation::RecordFrameMethods(context, table, count, docs, docCount);
|
||||
fn(methods.data, methods.count, context);
|
||||
Api::Documentation::RecordFrameMethods(context, methods, docs);
|
||||
}
|
||||
|
||||
// Registers `func` at `_G[tableName][methodName]`. If the namespace
|
||||
@@ -431,17 +437,16 @@ static bool SetIntegerTable(const char *parent, const char *sub,
|
||||
return true;
|
||||
}
|
||||
|
||||
void RegisterIntegerEnum(const char *parent, const char *sub,
|
||||
const EnumIntegerEntry *entries, int count,
|
||||
void RegisterIntegerEnum(const char *parent, const char *sub, ArrayRef<EnumIntegerEntry> entries,
|
||||
const char *docSystem) {
|
||||
if (SetIntegerTable(parent, sub, entries, count))
|
||||
Api::Documentation::RecordEnum(parent, sub, entries, count, docSystem);
|
||||
if (SetIntegerTable(parent, sub, entries.data, entries.count))
|
||||
Api::Documentation::RecordEnum(parent, sub, entries, docSystem);
|
||||
}
|
||||
|
||||
void RegisterConstants(const char *sub, const EnumIntegerEntry *entries, int count,
|
||||
void RegisterConstants(const char *sub, ArrayRef<EnumIntegerEntry> entries,
|
||||
const char *docSystem) {
|
||||
if (SetIntegerTable("Constants", sub, entries, count))
|
||||
Api::Documentation::RecordConstants(sub, entries, count, docSystem);
|
||||
if (SetIntegerTable("Constants", sub, entries.data, entries.count))
|
||||
Api::Documentation::RecordConstants(sub, entries, docSystem);
|
||||
}
|
||||
|
||||
void SetFieldNumber(void *L, const char *key, double value) {
|
||||
|
||||
+33
-16
@@ -147,6 +147,18 @@ int StrCmpI(const char *a, const char *b, int n = 0x7FFFFFFF);
|
||||
// literals + file-scope arrays): no heap, no dynamic init.
|
||||
// - Summaries are user-facing text: one sentence, plain words, present
|
||||
// tense, no patch versions.
|
||||
|
||||
// A view over a file-scope `const T[]` that takes its count from the array's
|
||||
// type, so no call site passes one; `{}` means none.
|
||||
template <typename T>
|
||||
struct ArrayRef {
|
||||
const T *data = nullptr;
|
||||
int count = 0;
|
||||
constexpr ArrayRef() = default;
|
||||
template <size_t N>
|
||||
constexpr ArrayRef(const T (&a)[N]) : data(a), count(static_cast<int>(N)) {}
|
||||
};
|
||||
|
||||
namespace Doc {
|
||||
|
||||
struct Field {
|
||||
@@ -172,14 +184,7 @@ constexpr Field Vararg(const char *type, const char *doc = nullptr) {
|
||||
return {"...", type, true, nullptr, doc};
|
||||
}
|
||||
|
||||
// A view over a file-scope `const Field[]`; `{}` means none.
|
||||
struct FieldList {
|
||||
const Field *data = nullptr;
|
||||
int count = 0;
|
||||
constexpr FieldList() = default;
|
||||
template <size_t N>
|
||||
constexpr FieldList(const Field (&a)[N]) : data(a), count(static_cast<int>(N)) {}
|
||||
};
|
||||
using FieldList = ArrayRef<Field>;
|
||||
|
||||
// A registered function. Positional init: `{summary, args, rets}` for a
|
||||
// table function, `{summary, args, rets, "XGlobals"}` for a global, and
|
||||
@@ -200,6 +205,7 @@ struct Method {
|
||||
const char *name;
|
||||
const Function *doc;
|
||||
};
|
||||
using MethodList = ArrayRef<Method>;
|
||||
|
||||
// A table shape a function returns or accepts → `Tables{Type="Structure"}`
|
||||
// in `system`. Self-registering: nothing binds a structure to Lua, so it
|
||||
@@ -240,7 +246,9 @@ constexpr int UpvalueIndex(int i) { return GLOBALS_INDEX - i; }
|
||||
// `lua_call` / `lua_pcall` nresults sentinel meaning "all".
|
||||
constexpr int MULTRET = -1;
|
||||
|
||||
// Type tag values returned by `Type()` (lua_type).
|
||||
// Type tag values returned by `Type()` (lua_type). TYPE_NONE is an absent
|
||||
// argument (past the top of the stack).
|
||||
constexpr int TYPE_NONE = -1;
|
||||
constexpr int TYPE_NIL = 0;
|
||||
constexpr int TYPE_BOOLEAN = 1;
|
||||
constexpr int TYPE_LIGHTUSERDATA = 2;
|
||||
@@ -479,6 +487,8 @@ void *ResolveTexture(void *L, int idx = 1, bool raiseError = true);
|
||||
void *ResolveFontString(void *L, int idx = 1, bool raiseError = true);
|
||||
void *ResolveEditBox(void *L, int idx = 1, bool raiseError = true);
|
||||
void *ResolveModel(void *L, int idx = 1, bool raiseError = true);
|
||||
void *ResolvePlayerModel(void *L, int idx = 1, bool raiseError = true);
|
||||
void *ResolveDressUpModel(void *L, int idx = 1, bool raiseError = true);
|
||||
|
||||
// Every name these registrars bind is ALSO bound under `_G.ClassicAPI` —
|
||||
// `ClassicAPI.GetSpellInfo`, `ClassicAPI.C_Item.IsBound` — as an escape
|
||||
@@ -523,10 +533,11 @@ struct FrameMethodEntry {
|
||||
|
||||
// Registers a batch of methods on a per-frame-type registry (e.g.
|
||||
// GameTooltipMethodRegistry for `tooltip:Foo()` calls). `context` is the
|
||||
// registry address — see Offsets::VAR_*_METHOD_REGISTRY. `docs` pairs
|
||||
// descriptors to the entries by NAME (any order, any subset).
|
||||
void RegisterFrameMethods(void *context, const FrameMethodEntry *table, int count,
|
||||
const Doc::Method *docs = nullptr, int docCount = 0);
|
||||
// registry address — see Offsets::VAR_*_METHOD_REGISTRY. `methods` and
|
||||
// `docs` are file-scope arrays; `docs` pairs descriptors to the entries by
|
||||
// NAME (any order, any subset).
|
||||
void RegisterFrameMethods(void *context, ArrayRef<FrameMethodEntry> methods,
|
||||
Doc::MethodList docs = {});
|
||||
|
||||
// Registers `func` at `_G[tableName][methodName]`, creating the namespace
|
||||
// table if it doesn't already exist. This is how modern WoW C_*-style APIs
|
||||
@@ -557,14 +568,14 @@ struct EnumIntegerEntry {
|
||||
// `Enum.AddOnSecurityStatus = { Secure=0, Insecure=1, ... }` shapes.
|
||||
// `docSystem` is the documentation System the enumeration belongs to
|
||||
// ("SpellBook", "Item", …); the entries themselves are the documentation.
|
||||
void RegisterIntegerEnum(const char *parent, const char *sub,
|
||||
const EnumIntegerEntry *entries, int count,
|
||||
// `entries` is a file-scope array.
|
||||
void RegisterIntegerEnum(const char *parent, const char *sub, ArrayRef<EnumIntegerEntry> entries,
|
||||
const char *docSystem = nullptr);
|
||||
|
||||
// Registers `Constants[sub] = { entries }`, Blizzard's engine constant
|
||||
// tables (`Constants.MoneyFormattingConstants`). Documented as a "Constants"
|
||||
// table of `docSystem`.
|
||||
void RegisterConstants(const char *sub, const EnumIntegerEntry *entries, int count,
|
||||
void RegisterConstants(const char *sub, ArrayRef<EnumIntegerEntry> entries,
|
||||
const char *docSystem);
|
||||
|
||||
// Set `t[key] = value` on the table currently at stack[-1] (the most
|
||||
@@ -777,6 +788,12 @@ bool RunHookRegistrations();
|
||||
// issue #33, Frame::Attributes). A pure-C++ cache keyed by spellID/itemID is
|
||||
// NOT fragile and must not register.
|
||||
//
|
||||
// It runs AFTER the frames are gone: CGGameUI::Shutdown destroys every frame
|
||||
// (and each Model frame's scene with every model in it) before
|
||||
// CGGameUI::Initialize resets the Lua state. So a cleanup here only FORGETS
|
||||
// pointers; it never releases an engine object tied to a frame. Release those
|
||||
// when the frame dies (Model::Teardown for Model frames).
|
||||
//
|
||||
// Same lifetime + ordering rules as ModuleAutoRegister: static-init chaining,
|
||||
// undefined cross-TU order — fine, because each callback only clears its OWN
|
||||
// state. This is the "clear stale state" category only; a save-before-teardown
|
||||
|
||||
+609
-43
@@ -671,6 +671,39 @@ enum Offsets {
|
||||
OFF_ANCHOR_NODE_Y = 0x08,
|
||||
OFF_ANCHOR_NODE_RELATIVE_POINT = 0x10,
|
||||
VTBL_ANCHOR_NODE_GET_RELATIVE = 0x0C,
|
||||
// The relative node's other three slots (Ghidra: each function's only
|
||||
// reference is its slot): +0x00 the deleting destructor 0x00767D80, `void
|
||||
// *__thiscall(node, int flags)`, `ret 4` — every free of a point (SetPoint,
|
||||
// ClearAllPoints, FUN_REGION_CLEAR_POINT_BY_RELATIVE, the layout destructor)
|
||||
// is `push 1; call [vtbl]`; +0x04 / +0x08 the x / y getters 0x007A2F90 /
|
||||
// 0x007A3070, `float __thiscall(node, float scale)`, `ret 4` (benilla-re
|
||||
// ui/anchors "Relative point coordinates"). A getter ends a deferred
|
||||
// relative's deferral with FUN_REGION_LAYOUT_REALIZE(0) (0x007A2FC0), then
|
||||
// reads the node again (0x007A2FC5).
|
||||
VTBL_ANCHOR_NODE_DELETE = 0x00,
|
||||
VTBL_ANCHOR_NODE_X = 0x04,
|
||||
VTBL_ANCHOR_NODE_Y = 0x08,
|
||||
FUN_ANCHOR_NODE_DELETE = 0x00767D80,
|
||||
FUN_ANCHOR_NODE_X = 0x007A2F90,
|
||||
FUN_ANCHOR_NODE_Y = 0x007A3070,
|
||||
// A layout's point walks, 4.3.4's GetFramePointX / Y: `float
|
||||
// __thiscall(layout, const int *points, int count)`, `ret 8` — calls
|
||||
// VTBL_ANCHOR_NODE_X (Y) of each set slot in `points` with the layout's
|
||||
// scale (+0x58) and returns the first result that is not
|
||||
// VAR_LAYOUT_UNDEFINED. The six edge resolvers are the only callers
|
||||
// (Ghidra xrefs): left / centre x / right call the x walk, top / centre y /
|
||||
// bottom the y walk.
|
||||
FUN_LAYOUT_POINTS_X = 0x007671A0,
|
||||
FUN_LAYOUT_POINTS_Y = 0x007671F0,
|
||||
CALL_LAYOUT_LEFT_POINTS = 0x007673F1,
|
||||
CALL_LAYOUT_CENTER_X_POINTS = 0x00767281,
|
||||
CALL_LAYOUT_RIGHT_POINTS = 0x00767561,
|
||||
CALL_LAYOUT_TOP_POINTS = 0x007674F1,
|
||||
CALL_LAYOUT_CENTER_Y_POINTS = 0x00767381,
|
||||
CALL_LAYOUT_BOTTOM_POINTS = 0x007675D1,
|
||||
// float: the "no value" an edge, a point or a walk returns (benilla-re
|
||||
// s_layoutUndefined); the walk compares with `fcomp [0x00CF550C]`.
|
||||
VAR_LAYOUT_UNDEFINED = 0x00CF550C,
|
||||
|
||||
// The point names the XML loader and SetPoint share (benilla-re
|
||||
// ui/anchors "Point slots and point names"), 4.3.4's StringToFramePoint /
|
||||
@@ -1944,10 +1977,36 @@ enum Offsets {
|
||||
VAR_CURSOR_BASE_MODE = 0x00BE2C4C,
|
||||
CURSOR_MODE_POINT = 1,
|
||||
CURSOR_MODE_CAST = 2,
|
||||
CURSOR_MODE_BUY = 3,
|
||||
CURSOR_MODE_ATTACK = 4,
|
||||
CURSOR_MODE_INTERACT = 5,
|
||||
CURSOR_MODE_SPEAK = 6,
|
||||
CURSOR_MODE_PICKUP = 8,
|
||||
CURSOR_MODE_TAXI = 9,
|
||||
CURSOR_MODE_TRAINER = 10,
|
||||
CURSOR_MODE_LAST_NAMED = 20,
|
||||
CURSOR_MODE_UNABLE_OFFSET = 20,
|
||||
CURSOR_MODE_CUSTOM = 0x29,
|
||||
// The shown mode, the one SetCursorMode compares and stores.
|
||||
VAR_CURSOR_MODE = 0x00BE2C2C,
|
||||
// SendCursorImage — `void __cdecl(void)`: copies the image slot of
|
||||
// VAR_CURSOR_MODE (slot n at 0x00BE2D80 + n * 0x1000, CURSOR_IMAGE_SIZE
|
||||
// squared BGRA8888 texels) to the device cursor, or the dragged payload's
|
||||
// picture while one is held (disassembled 0x523790-0x52380D).
|
||||
FUN_SEND_CURSOR_IMAGE = 0x00523790,
|
||||
// SetCustomCursor — `uint32_t __fastcall(const char *path)`, plain ret:
|
||||
// shows a named BLP as mode 41. Returns 1 at once when mode 41 already
|
||||
// shows `path` (SStrCmpI against VAR_CUSTOM_CURSOR_NAME). Otherwise it
|
||||
// loads the file, and for a 32x32 image stores mode 41, copies the texels
|
||||
// into slot 41 (VAR_CURSOR_IMAGE_CUSTOM), sends the image, copies `path`
|
||||
// into VAR_CUSTOM_CURSOR_NAME (CURSOR_FILE_SIZE bytes) and returns 1; a
|
||||
// file that does not load returns 0 and changes nothing (disassembled
|
||||
// 0x523C40-0x523D0E). ClassifyHoveredGameObject uses it for a
|
||||
// record-named cursor.
|
||||
FUN_SET_CUSTOM_CURSOR = 0x00523C40,
|
||||
VAR_CURSOR_IMAGE_CUSTOM = 0x00C0BD80,
|
||||
VAR_CUSTOM_CURSOR_NAME = 0x00BE2C70,
|
||||
CURSOR_IMAGE_SIZE = 32,
|
||||
// s_cursorNames: `const char *[42]`, mode -> image name (0 and 41 null;
|
||||
// 21..40 carry their own "Unable…" names). The loader formats
|
||||
// `Interface\Cursor\%s.blp` from it (0x5236C0); dumped from the exe.
|
||||
@@ -1967,6 +2026,16 @@ enum Offsets {
|
||||
// 9 / 0x19 and 0x81 after the object's IsMouseoverable answers nonzero.
|
||||
FUN_WORLD_FRAME_CLASSIFY_UNIT = 0x00482200,
|
||||
FUN_WORLD_FRAME_CLASSIFY_CORPSE = 0x00482740,
|
||||
// ClassifyHoveredUnit's NPC flag chain (0x482336-0x4826D6), reached for a
|
||||
// unit the player can interact with now (0x606880) with the unit in ESI,
|
||||
// the player in EDI, and EBX 1 when the unit is beyond the interact reach
|
||||
// (squared distance above 0xB4B32C), else 0. Its head is `mov ecx,
|
||||
// [esi + 0x110]` (8B 8E 10 01 00 00), and no jump in the function lands
|
||||
// inside it. CLASSIFY_UNIT_RETURN is the epilogue after the chain's first
|
||||
// SetCursorMode call (`pop edi; pop esi; pop ebx; mov esp, ebp; pop ebp;
|
||||
// ret 4`), with ESP as it is at the head (disassembled 0x482200-0x4826D6).
|
||||
SITE_CLASSIFY_UNIT_NPC_FLAGS = 0x00482336,
|
||||
CLASSIFY_UNIT_RETURN = 0x00482356,
|
||||
// CGGameObject_C::GetCursor — `uint32_t __thiscall(go)`: 0 when not
|
||||
// interactable, CURSOR_MODE_CUSTOM for a record-named cursor, else the
|
||||
// handler's mode (+20 when not usable). IsUsable — `bool __thiscall(go)`,
|
||||
@@ -1975,6 +2044,17 @@ enum Offsets {
|
||||
// a custom-cursor texture load for 41.
|
||||
FUN_GAMEOBJECT_GET_CURSOR = 0x005F8760,
|
||||
FUN_GAMEOBJECT_IS_USABLE = 0x005F8810,
|
||||
// The handler's mode is its vtable slot 4 (+0x10), which GetCursor
|
||||
// tail-jumps to with the handler in ECX (`jmp [eax+0x10]` at 0x5F878B);
|
||||
// GetCursor is the slot's one reader. BaseHandlerGetCursor — `uint32_t
|
||||
// __thiscall(handler)`, plain ret — is the slot in most types: 0 when slot
|
||||
// 5 says not interactable; a lock's LockType cursor by name; else
|
||||
// CURSOR_MODE_INTERACT when usable (slot 6), its Unable form when not
|
||||
// (disassembled 0x5F3070-0x5F3123). The fishing node (type 17) handler's
|
||||
// vtable is 0x80BC80, whose slot 4 holds it (benilla-re
|
||||
// s_fishingNodeHandlerVtable; xref 0x80BC90).
|
||||
FUN_GO_HANDLER_BASE_GET_CURSOR = 0x005F3070,
|
||||
SLOT_FISHING_NODE_HANDLER_GET_CURSOR = 0x0080BC90,
|
||||
// Object type words (descriptor OBJECT_FIELD_TYPE) the hover dispatch
|
||||
// switches on, exactly (0x4828D0): unit, player, game object, corpse.
|
||||
OBJECT_TYPE_WORD_UNIT = 0x09,
|
||||
@@ -2814,23 +2894,14 @@ enum Offsets {
|
||||
UNIT_POWER_MIN_TYPE = 0,
|
||||
UNIT_POWER_MAX_TYPE = 4, // happiness; types 5/6 only valid post-WotLK
|
||||
|
||||
// Per-power-type display divisor. Raw descriptor values get
|
||||
// divided by this before they're surfaced through Lua. Vanilla
|
||||
// 1.12 stores rage as 0..1000 (internally) and divides by 10 to
|
||||
// show 0..100; happiness is stored at 1000x scale and divides
|
||||
// by 1000. Engine reads from `Script_UnitMana` at
|
||||
// `0x006E7130 + type * 4` (= `0x0086F978`). Same trick the
|
||||
// 3.3.5/4.x clients use; the table is per-client, the design is
|
||||
// stable.
|
||||
//
|
||||
// [0] = 1 MANA
|
||||
// [1] = 10 RAGE
|
||||
// [2] = 1 FOCUS
|
||||
// [3] = 1 ENERGY
|
||||
// [4] = 1000 HAPPINESS
|
||||
VAR_UNIT_POWER_DIVISOR_TABLE = 0x0086F978,
|
||||
// GetPowerDisplayDivisor — `int __fastcall(int powerType)`, the reader of
|
||||
// the table above (1 for a negative type). benilla-re spell symbols.
|
||||
// GetPowerDisplayDivisor — `uint32_t __fastcall(int powerType)`: the
|
||||
// divisor that turns a stored power amount (descriptor POWER/MAXPOWER,
|
||||
// Spell.dbc ManaCost, the cost helper's answer) into displayed units.
|
||||
// Returns `[0x0086F978 + 4·type]`, or 1 for a negative type (verified by
|
||||
// disassembly). Table {1, 10, 1, 1, 1000}: rage is stored in tenths
|
||||
// (Shield Slam's 20 rage is 200), happiness at 1000x. Same table as
|
||||
// 3.3.5's `s_powerDisplayMod`. Read it only through
|
||||
// `Unit::Power::DisplayDivisor`.
|
||||
FUN_POWER_DISPLAY_DIVISOR = 0x006E7130,
|
||||
OFF_UNIT_FIELD_LEVEL = 0x70,
|
||||
// UNIT_FIELD_BYTES_0 dword at +0x78; byte 3 (= +0x7B) is the unit's
|
||||
@@ -3179,13 +3250,6 @@ enum Offsets {
|
||||
// 4.3.4 `GetSpellInfo` reports this instead of the cost (with
|
||||
// isFunnel = true) when it is positive.
|
||||
FUN_GET_SPELL_POWER_PER_SECOND = 0x006E3300,
|
||||
// Divisor that turns a stored power amount into its displayed units:
|
||||
// `__fastcall uint32_t(int powerType)` → `[0x0086F978 + 4·type]`, 1 for a
|
||||
// negative type. Table {1, 10, 1, 1, 1000} — rage is stored in tenths
|
||||
// (Heroic Strike's 15 rage is 150 in Spell.dbc). Verified by disassembly;
|
||||
// same table as 3.3.5's `s_powerDisplayMod`.
|
||||
FUN_GET_POWER_DISPLAY_DIVISOR = 0x006E7130,
|
||||
|
||||
// The engine's spell-castability verdict for the LOCAL player — the
|
||||
// function behind `IsUsableAction` for a player spell slot (the spell
|
||||
// branch of the per-slot recompute `FUN_004E5050`) and behind the
|
||||
@@ -3281,6 +3345,19 @@ enum Offsets {
|
||||
CALL_CAST_CHECK_TARGET_RANGE = 0x006E4F26,
|
||||
FUN_SPELL_TARGET_ARM = 0x006E5250,
|
||||
CALL_CAST_TARGET_ARM = 0x006E5038,
|
||||
// SendCast — `void __fastcall(void *pendingCast /*ecx*/, uint8_t flag
|
||||
// /*dl*/)`, plain ret: builds and sends CMSG_CAST_SPELL / CMSG_USE_ITEM /
|
||||
// CMSG_PET_CAST_SPELL (4.3.4's Spell_C_SendPendingCast). Its five callers
|
||||
// (xrefs) all pass the global pending cast (`mov ecx,0xCEAC48`): the
|
||||
// target arm's two immediate sends, and the object / location / trade-
|
||||
// slot binders the targeting cursor's click also runs. The arm's one
|
||||
// caller is the cast request, after the caster check.
|
||||
FUN_SPELL_SEND_CAST = 0x006E54F0,
|
||||
CALL_TARGET_ARM_SEND_CAST = 0x006E5349,
|
||||
CALL_TARGET_ARM_SEND_CAST_GUID = 0x006E5467,
|
||||
CALL_BIND_TARGET_OBJECT_SEND_CAST = 0x006E60D7,
|
||||
CALL_BIND_TARGET_LOCATION_SEND_CAST = 0x006E6177,
|
||||
CALL_BIND_TARGET_TRADE_SLOT_SEND_CAST = 0x006E694F,
|
||||
// GetSpellHostility — `int __thiscall(const uint8_t *record)`: 2 harmful,
|
||||
// 1 helpful, 0 neither, from the Targets word and the implicit targets
|
||||
// (benilla-re spell/handlers#hostility-classifier).
|
||||
@@ -3310,6 +3387,125 @@ enum Offsets {
|
||||
// state locally; a no-op while mounted, on a taxi, dead, channeling or
|
||||
// moving (benilla-re objectlayer/standstate#the-sit-gate).
|
||||
FUN_PLAYER_SEND_STAND_STATE = 0x005ED430,
|
||||
// CGUnit_C::IsShapeShifted — `char __thiscall(const void *unit)`, no
|
||||
// args: the form byte names a SpellShapeshiftForm row without flag bit
|
||||
// 0 (a stance is not a shapeshift). Disassembled.
|
||||
FUN_UNIT_IS_SHAPESHIFTED = 0x0060E9F0,
|
||||
// The two interaction form gates, each `call FUN_UNIT_IS_SHAPESHIFTED`
|
||||
// (ECX = the player) followed by a test for SHAPESHIFT_FORM_FLAG_CAN_-
|
||||
// INTERACT; shifted without it refuses (disassembled):
|
||||
// the NPC right-click (CGPlayer_C::HandleOnRightClick, 0x005F0130):
|
||||
// ERR_CANT_INTERACT_SHAPESHIFTED, before the range check and walk;
|
||||
// the game-object use check (the handler's vtable slot 6, 0x005F3130,
|
||||
// 4.3.4's CGGameObject_C_TypeBase::CanUseNow; every type but door and
|
||||
// chair): error 0x1A0, only for an object with no Lock row.
|
||||
CALL_NPC_INTERACT_IS_SHAPESHIFTED = 0x005F0150,
|
||||
CALL_GAMEOBJECT_CAN_USE_IS_SHAPESHIFTED = 0x005F31E1,
|
||||
// CGUnit_C::CanInteractNow (0x00606880, `this` = the player, ECX = EDI)
|
||||
// has the same form test at this call. Its only caller is the hover
|
||||
// cursor (0x00482310, a .text call scan), which shows the NPC's service
|
||||
// cursor only when it passes; 4.3.4's CursorTrackUnit asks the same.
|
||||
CALL_UNIT_CAN_INTERACT_NOW_IS_SHAPESHIFTED = 0x006068B4,
|
||||
// The use check's mount rung, the same no-Lock-row branch: `mov ecx,
|
||||
// [eax+0x1FC]` (8B 88 FC 01 00 00, the player's mount display), then
|
||||
// `test ecx,ecx; jle pass`, then the mailbox test; refuses 0x19F
|
||||
// (ERR_NOT_WHILE_MOUNTED, no 1.12 text). EDI = the player; EAX is dead.
|
||||
SITE_GAMEOBJECT_CAN_USE_MOUNT_READ = 0x005F31A8,
|
||||
// 4.3.4's IsUsableMounted — `bool __thiscall(const void *const *fields)`,
|
||||
// ECX = &object->descriptor: GAMEOBJECT_TYPE_ID is 19, the mailbox.
|
||||
FUN_GAMEOBJECT_USABLE_MOUNTED = 0x0047CFF0,
|
||||
OFF_GAMEOBJECT_DESCRIPTOR = 0x110,
|
||||
// A type handler's object pointer (benilla-re objectlayer/gameobjects).
|
||||
OFF_GAMEOBJECT_HANDLER_OBJECT = 0x4,
|
||||
// The default use (slot 7, 0x005F33E0, 4.3.4's TypeBase::Use): its
|
||||
// unlocked, no-spell branch sends CMSG_GAMEOBJ_USE, opening with `mov
|
||||
// edi,0x7FF9E4` (BF E4 F9 7F 00, the CDataStore vtable). [EBP-4] = the
|
||||
// handler; EAX, ECX and EDX are dead there.
|
||||
SITE_GAMEOBJECT_USE_SEND = 0x005F35DF,
|
||||
// The two types whose slot 7 replaces the default use (vtable dump):
|
||||
// the mailbox (opens the mail frame) and text (opens the page). Both
|
||||
// `char __thiscall(void *handler)`, no args, return 1.
|
||||
SLOT_GAMEOBJECT_MAILBOX_USE = 0x0080BDBC,
|
||||
FUN_GAMEOBJECT_MAILBOX_USE = 0x005F6820,
|
||||
SLOT_GAMEOBJECT_TEXT_USE = 0x0080B954,
|
||||
FUN_GAMEOBJECT_TEXT_USE = 0x005F58C0,
|
||||
|
||||
// The rest of 4.3.4's CanAutoStand / CanAutoDismount / CanAutoUnshift
|
||||
// sites, each found as the matching 1.12 gate (disassembled):
|
||||
//
|
||||
// Stand-state reads before a loot, `call [reg+0xA4]` (GetStandState, 6
|
||||
// bytes) with ECX = the player, then `test eax,eax; jnz` to
|
||||
// ERR_LOOT_NOTSTANDING (4.3.4 CGUnit_C::OnRightClick / CGCorpse_C::
|
||||
// OnRightClick): the unit right-click's loot step (FF 90 A4 00 00 00)
|
||||
// and the corpse right-click (0x005D6BF0, FF 92 A4 00 00 00).
|
||||
SITE_UNIT_LOOT_STAND_CALL = 0x0060BFB7,
|
||||
SITE_CORPSE_LOOT_STAND_CALL = 0x005D6C3B,
|
||||
// The unit right-click's loot step skips a mounted player: `mov eax,
|
||||
// [ecx+0x1FC]` (8B 81 FC 01 00 00), `test eax,eax; jg skin`. ESI = the
|
||||
// player; ECX and EDX are dead. 4.3.4 has no such test there and its
|
||||
// LootUnit dismounts instead.
|
||||
SITE_UNIT_LOOT_MOUNT_READ = 0x0060BF98,
|
||||
// CGPlayer_C::LootUnit (0x005DF2A0) past its reach / walk-up, before the
|
||||
// loot window close and CMSG_LOOT: `push 0; xor dl,dl; mov cl,1` (6A 00
|
||||
// 32 D2 B1 01) for FUN_0048F200(1, 0, 0). ESI = the player; EAX is
|
||||
// dead. 4.3.4's LootUnit dismounts at this point.
|
||||
SITE_LOOT_UNIT_SEND = 0x005DF3B3,
|
||||
// The item use's form refusal (0x005D8D00, 4.3.4 CGItem_C::Use):
|
||||
// `push 0x15E` (68 5E 01 00 00, ERR_NO_ITEMS_WHILE_SHAPESHIFTED), then
|
||||
// `jmp` to the error tail. ESI = the player; EAX, ECX, EDX are dead.
|
||||
// ITEM_USE_FORM_PASSED is where the form test's pass jumps. Turtle's exe
|
||||
// makes the refusal unreachable (`jmp` over the form test at
|
||||
// 0x005D9139); the stock 1.12.1 exe reaches it.
|
||||
SITE_ITEM_USE_FORM_REFUSAL = 0x005D914D,
|
||||
ITEM_USE_FORM_PASSED = 0x005D9157,
|
||||
// Past the form test, the item use's toggle test (4.3.4 runs it before
|
||||
// the form cancel): a use spell with an activeIconID whose aura is on
|
||||
// the player is cancelled instead of cast, `mov ecx,[ebp-0xC]` (spell)
|
||||
// then CALL_ITEM_USE_TOGGLE_CANCEL → FUN_CANCEL_AURA_SEND, return 0.
|
||||
// Every other path joins at SITE_ITEM_USE_BIND_TEST, the bind-on-use
|
||||
// test: `mov ecx,[ebp-8]; cmp dword [ecx+0x194],3` (8B 4D F8 83 B9 94
|
||||
// 01 00 00 03), then `jne`. There EDI = the spell (read after), EBX and
|
||||
// EBP live; EAX, ECX, EDX are reloaded before use (disassembled).
|
||||
CALL_ITEM_USE_TOGGLE_CANCEL = 0x005D9237,
|
||||
SITE_ITEM_USE_BIND_TEST = 0x005D91D3,
|
||||
// The mount reads in 4.3.4's CGUnit_C::CanAttackNow, inlined in 1.12
|
||||
// (each followed by `test; jg refuse`):
|
||||
// the unit right-click's attack step (8B 82 FC 01 00 00), player ESI,
|
||||
// EAX out, ECX and EDX dead;
|
||||
// the hover cursor's attack cursor (8B 82 FC 01 00 00), player EDI,
|
||||
// EAX out, ECX and EDX dead;
|
||||
// StartAttack (0x005ECB70, 8B 81 FC 01 00 00), player ESI, EAX out,
|
||||
// ECX and EDX dead;
|
||||
// the attack resolver (0x00612DF0, 4.3.4 VerifyAttackIconPressed's
|
||||
// ERR_ATTACK_MOUNTED, 8B 81 FC 01 00 00), player EDI, EAX out;
|
||||
// the resolver's can-attack step (8B 90 FC 01 00 00), player ECX
|
||||
// (live after), EDX out, EAX dead.
|
||||
SITE_RIGHT_CLICK_ATTACK_MOUNT_READ = 0x0060C1B4,
|
||||
SITE_CURSOR_ATTACK_MOUNT_READ = 0x00482659,
|
||||
SITE_START_ATTACK_MOUNT_READ = 0x005ECC06,
|
||||
SITE_ATTACK_RESOLVER_MOUNT_READ = 0x00613039,
|
||||
SITE_ATTACK_RESOLVER_CAN_ATTACK_MOUNT_READ = 0x00613142,
|
||||
// StartAttack once every test passed, before the swing: `mov eax,[esi+
|
||||
// 0xC8C]` (8B 86 8C 0C 00 00), ESI = the player, EDI = the target; ECX
|
||||
// and EDX are dead. START_ATTACK_RETURN is its plain epilogue. 4.3.4's
|
||||
// CombatModeEnter dismounts here.
|
||||
SITE_START_ATTACK_PASSED = 0x005ECC56,
|
||||
START_ATTACK_RETURN = 0x005ECD9A,
|
||||
// ERR_OUT_OF_RANGE's message id (benilla-re tables/messages.tsv).
|
||||
GAME_ERROR_OUT_OF_RANGE = 125,
|
||||
// CGPlayer_C::CanAutoInteract — `bool __thiscall(const void *player)`,
|
||||
// no args: a live active mover with the click-to-move cvar on
|
||||
// (disassembled). 4.3.4's right-click passes it as CombatModeEnter's
|
||||
// `silent`, so the out-of-reach error stays quiet while the player walks.
|
||||
FUN_PLAYER_CAN_AUTO_INTERACT = 0x0060F900,
|
||||
// The taxi map opener (0x004DB710) ends like 4.3.4's: TAXIMAP_OPENED,
|
||||
// then tutorial 34 through FUN_TRIGGER_TUTORIAL (`void __fastcall(int
|
||||
// id, int)`) at this call; 4.3.4 then stands, dismounts and leaves the
|
||||
// form when the map has more than one node (VAR_TAXI_MAP_NODE_COUNT,
|
||||
// the count the opener's TaxiNodeSetCurrent loop runs to).
|
||||
CALL_TAXI_MAP_OPENED_TUTORIAL = 0x004DBAA2,
|
||||
FUN_TRIGGER_TUTORIAL = 0x004B5390,
|
||||
VAR_TAXI_MAP_NODE_COUNT = 0x00BB4AA8,
|
||||
|
||||
// Effective spell/channel duration (ms):
|
||||
// `__fastcall int(const void *spellRecord, int pet /*0 = player,
|
||||
@@ -3949,6 +4145,20 @@ enum Offsets {
|
||||
VTBL_SIMPLEMODEL_UPDATE_MODEL = 0x98,
|
||||
// `CM2Scene *__thiscall(frame)` — the frame's scene, made on first use.
|
||||
FUN_SIMPLEMODEL_GET_SCENE = 0x0076CFC0,
|
||||
// CSimpleModelFFX::SetModelInstance — `__thiscall(frame, model)`, ret 4:
|
||||
// AddRefs the new model, releases the old one, stores it at +0x318.
|
||||
// Slot 36 of the Model and pane vtables; DressUpModel's own slot 36
|
||||
// (0x00504350) calls it first.
|
||||
FUN_SIMPLEMODEL_SET_MODEL_INSTANCE = 0x0076CD30,
|
||||
// Every Model frame's member teardown (fastcall frame), called by the
|
||||
// CSimpleModel deleting dtor 0x0076CA20 and CGCharacterModelBase's dtor
|
||||
// 0x00505870 (PlayerModel, DressUpModel). It releases the frame's M2
|
||||
// scene (+0x314), which frees the models in it even when something else
|
||||
// still holds a reference (seen live: a kept compositor's release faulted
|
||||
// on the freed model after /reload).
|
||||
FUN_SIMPLEMODEL_DESTROY = 0x0076CA50,
|
||||
// The Model frame's per-paint update — `__thiscall(frame)`, slot 38.
|
||||
FUN_SIMPLEMODEL_UPDATE_MODEL = 0x0076D1A0,
|
||||
|
||||
// ---- PlayerModel / DressUpModel / TabardModel (benilla ui/playermodel) --
|
||||
// CGCharacterModelBase: a Model frame (0x3F8 bytes) that shows a copy of a
|
||||
@@ -3967,6 +4177,8 @@ enum Offsets {
|
||||
// write it; the show hook (0x00505D49) reads it to rebuild the model.
|
||||
// Those four are the only accesses in 0x00500000..0x00507000.
|
||||
OFF_PLAYERMODEL_DISPLAY_RECORD = 0x3F0,
|
||||
// The pane's unit GUID (u64), stored by the SetUnit helper (0x00505B50).
|
||||
OFF_PLAYERMODEL_UNIT_GUID = 0x3E0,
|
||||
// SetDisplay — `__thiscall(pane, record)`: clears the unit GUID, stores
|
||||
// the record and, if the frame is visible, builds the model.
|
||||
FUN_PLAYERMODEL_SET_DISPLAY = 0x00505CB0,
|
||||
@@ -3975,6 +4187,24 @@ enum Offsets {
|
||||
// 0x004797B0), installs it (vtable +0x90), registers the done callback
|
||||
// and arms Stand. No character compositor: 4.3.4's SetUpModel adds it.
|
||||
FUN_PLAYERMODEL_ATTACH_DISPLAY_MODEL = 0x00505A70,
|
||||
// The pane's animation-done callback, which AttachDisplayModel registers
|
||||
// with the pane as user data (0x00505AFB) and which arms Stand when a
|
||||
// whole-body track completes.
|
||||
FUN_PLAYERMODEL_ON_ANIM_DONE = 0x005062A0,
|
||||
// CM2Model::SetAnimDoneCallback — `__thiscall(model, callback, uint64
|
||||
// userData)`, ret 0xC. AttachDisplayModel passes the pane sign-extended
|
||||
// (`cdq` at 0x00505AF8).
|
||||
FUN_M2MODEL_SET_ANIM_DONE_CALLBACK = 0x00711BB0,
|
||||
// CM2Model::ReplaceTexture — `__thiscall(model, type, HTEXTURE)`, ret 8.
|
||||
// Queues the bind when the model has not loaded yet (0x00710FFF). Item
|
||||
// models take their skin as type 2 (0x00479933 in AttachItemModelFile,
|
||||
// 0x0047A043 in the loose ammo model).
|
||||
FUN_M2MODEL_REPLACE_TEXTURE = 0x00710EC0,
|
||||
// The CStatus every item-model texture load reports to: AttachItemModelFile
|
||||
// (0x00479912) and the loose ammo model (0x0047A01A) pass it to
|
||||
// FUN_TEXTURE_LOAD_BY_PATH as `(path, status, flags, 1, 0)`, with flags
|
||||
// from FUN_GX_TEXFLAGS_INIT(3, 0,0, 0,0,0, 1, 0).
|
||||
VAR_ITEM_MODEL_TEXTURE_STATUS = 0x00B424F0,
|
||||
// ConfigureCamera — `__thiscall(pane)`, run by the model-ready hook
|
||||
// (0x00505B30): a frozen copy of the model's camera index 1, or a fixed
|
||||
// camera at the bounding-box centre. 4.3.4's ConfigureCamera is this plus
|
||||
@@ -3982,6 +4212,37 @@ enum Offsets {
|
||||
FUN_PLAYERMODEL_CONFIGURE_CAMERA = 0x00505890,
|
||||
// The engine's Lua PlayerModel:SetRotation(angle) — `int __fastcall(L)`.
|
||||
FUN_SCRIPT_PLAYERMODEL_SET_ROTATION = 0x00505F00,
|
||||
// The engine's Lua PlayerModel:SetUnit(unit) — `int __fastcall(L)`,
|
||||
// returns nothing: FUN_TOKEN_TO_GUID, then the SetUnit helper below.
|
||||
// Turtle's client patches its tail (0x00505E19) to also take a number.
|
||||
FUN_SCRIPT_PLAYERMODEL_SET_UNIT = 0x00505D70,
|
||||
// SetUnit helper — `__thiscall(pane, uint64 guid)`, ret 8: stores the
|
||||
// GUID, clears the display record and, if the frame is visible
|
||||
// (OFF_FRAME_VISIBLE), builds the model from the unit (type mask 8).
|
||||
// Its callers: SetUnit, RefreshUnit (0x00505E40), DressUpModel:Dress.
|
||||
FUN_PLAYERMODEL_SET_UNIT_GUID = 0x00505B50,
|
||||
// Hide and Show — `__thiscall(pane)`, slots 33 and 34 of all three pane
|
||||
// vtables. Hide drops the model (slot 36 with null), then jumps to the
|
||||
// base hide. Show, while the frame is not yet visible, builds the model
|
||||
// from the GUID or the display record, then jumps to the base show.
|
||||
FUN_PLAYERMODEL_HIDE = 0x00505CE0,
|
||||
FUN_PLAYERMODEL_SHOW = 0x00505D00,
|
||||
FUN_SIMPLEFRAME_HIDE = 0x0076AD50,
|
||||
FUN_SIMPLEFRAME_SHOW = 0x0076AE10,
|
||||
// The turn latch (SetRotation sets it, the next paint clears it) and the
|
||||
// turn deadline in OS ms (now + 100 on a turn, 0 once Stand is armed).
|
||||
OFF_PLAYERMODEL_TURNING = 0x3E8,
|
||||
OFF_PLAYERMODEL_TURN_DEADLINE = 0x3EC,
|
||||
// UpdateModel — `__thiscall(pane)`, slot 38 (DressUpModel's 0x00504470
|
||||
// calls it first): the base update, then, with no turn latched and the
|
||||
// deadline passed (0 counts as passed), arms Stand (-1, 0, -1, 0, 1.0, 1,
|
||||
// 1) when key bone -1 plays anything else, and zeroes the deadline.
|
||||
// 4.3.4's UpdateModel is the same with the pane's m_sequence and
|
||||
// m_sequenceVariation in place of 0 and -1, and a loaded-model gate.
|
||||
FUN_PLAYERMODEL_UPDATE_MODEL = 0x00505C50,
|
||||
// vtable slot 36: SetModelInstance `__thiscall(frame, model)`; the pane
|
||||
// Hide passes null to drop the model (0x00505CE7).
|
||||
VTBL_SIMPLEMODEL_SET_MODEL_INSTANCE = 0x90,
|
||||
|
||||
// DressUpModel fields (benilla ui/dressup): the character component, and
|
||||
// the main- and off-hand lanes, 8 bytes each: byte 0 item class (0 =
|
||||
@@ -3989,11 +4250,63 @@ enum Offsets {
|
||||
OFF_DRESSUPMODEL_COMPONENT = 0x3F8,
|
||||
OFF_DRESSUPMODEL_MAIN_HAND_LANE = 0x3FC,
|
||||
OFF_DRESSUPMODEL_OFF_HAND_LANE = 0x404,
|
||||
// Byte: the unit showed its native display when the pane was built (not
|
||||
// shapeshifted). TryOnItem's armor arms need it and the component.
|
||||
OFF_DRESSUPMODEL_READY = 0x40C,
|
||||
// SetModelInstance — `__thiscall(pane, model)`, DressUpModel vtable slot
|
||||
// 36 (0x008083A8 + 0x90; its only reference): sets the model, clones the
|
||||
// unit's component and copies the unit's hand lanes. Null on hide.
|
||||
FUN_DRESSUPMODEL_SET_MODEL_INSTANCE = 0x00504350,
|
||||
SLOT_DRESSUPMODEL_SET_MODEL_INSTANCE = 0x00808438,
|
||||
// ClearModel — `__thiscall(pane)`: Undress's worker.
|
||||
FUN_DRESSUPMODEL_CLEAR_MODEL = 0x00504490,
|
||||
// The engine's Lua DressUpModel:Dress() — `int __fastcall(L)`: re-runs
|
||||
// the SetUnit helper (0x00505B50) with the pane's GUID.
|
||||
FUN_SCRIPT_DRESSUPMODEL_DRESS = 0x00504CD0,
|
||||
// AttachItemModel — `int __fastcall(model, displayRecord, bodySlot,
|
||||
// sheatheType, bool sheathed, bool shield, bool ranged)`, ret 0x14: hangs
|
||||
// a held item at the hand, or at its sheath point when `sheathed`. The
|
||||
// unit's own AttachHandItem (0x0060B8C0) passes its sheath state there.
|
||||
FUN_ATTACH_ITEM_MODEL = 0x0047A0C0,
|
||||
// TryOnItem's held arm: the install (`push 0` as sheathed) and the two
|
||||
// evictions of the other lane, each a `call` to the function above or
|
||||
// FUN_DETACH_HELD_ITEM_SLOT. The third detach (0x00504A85) is the
|
||||
// target lane's own occupant.
|
||||
CALL_TRY_ON_ATTACH_ITEM = 0x00504AE6,
|
||||
CALL_TRY_ON_EVICT_OFF_HAND = 0x00504A0C,
|
||||
CALL_TRY_ON_EVICT_MAIN_HAND = 0x00504A55,
|
||||
// TryOnItem's hand choice, reached only from the held arm (jump-table
|
||||
// entry 0x0A: inventory types 13-15, 17, 21-23, 25, 26). The site is
|
||||
// `mov edx, [edi+0x2C]; cmp edx, 0x0D` (8B 57 2C 83 FA 0D) before the
|
||||
// `jnz`. Both paths start with ECX = the player (or null), ESI = the
|
||||
// pane, EDI = the item record, and set [ebp-4] to body slot 0xF / 0x10
|
||||
// before the CanHoldBoth (0x00504BC0) eviction of the other lane.
|
||||
SITE_TRY_ON_HAND_CHOICE = 0x0050492D,
|
||||
TRY_ON_MAIN_HAND_PATH = 0x0050495F,
|
||||
TRY_ON_OFF_HAND_PATH = 0x005049B1,
|
||||
// AttachItemVisuals — `__fastcall(model, itemVisualID)`: detaches the
|
||||
// model's children, then hangs each ItemVisualEffects model of the
|
||||
// ItemVisuals row (attachments 0-4). Ignores an id with no row.
|
||||
FUN_ATTACH_ITEM_VISUALS = 0x00479700,
|
||||
// CM2Model::FindChild — `__thiscall(model, attachId)`, ret 4: the child
|
||||
// model hung at that attachment, or null.
|
||||
FUN_M2MODEL_FIND_CHILD = 0x00712F00,
|
||||
// GetItemDescVisual — `int __thiscall(visibleItem)`: -1 when the item's
|
||||
// display has its own ItemVisuals row (AttachItemModel already hung it),
|
||||
// else the visual of the first of its 7 enchants (+0x0C) that has one,
|
||||
// else 0. CGPlayer_C::AttachEnchantVisual (0x005EED50) gives the result
|
||||
// to FindChild + AttachItemVisuals on the attachment AttachItemModel
|
||||
// used; AttachHandItem (0x0060B8C0) runs that for players only.
|
||||
FUN_VISIBLE_ITEM_GET_DESC_VISUAL = 0x0062EC70,
|
||||
// UnequipCharacterSlot — `__thiscall(component, slot 0..11)`.
|
||||
FUN_CHARCOMP_UNEQUIP_SLOT = 0x00478960,
|
||||
// DetachHeldItemSlot — `__fastcall(model, bodySlot 0xF/0x10,
|
||||
// sheatheType, bool shield)`; Undress passes `invType == 14` as shield.
|
||||
FUN_DETACH_HELD_ITEM_SLOT = 0x0047A310,
|
||||
// Unit vtable slot 39 — `const void *__thiscall(unit, hand 0/1/2)`: the
|
||||
// ItemDisplayInfo row of the hand's item (CGUnit_C 0x00605EE0, CGPlayer_C
|
||||
// 0x005EC350); AttachHandItem's record source.
|
||||
VTBL_UNIT_GET_HELD_ITEM_DISPLAY = 0x9C,
|
||||
// TryOnItem — `__thiscall(pane, itemID)`: the worker behind TryOn.
|
||||
FUN_DRESSUPMODEL_TRY_ON_ITEM = 0x00504540,
|
||||
// The worker's item-cache callback (0x00504BA0) re-runs it once the
|
||||
@@ -4039,6 +4352,11 @@ enum Offsets {
|
||||
OFF_M2MODEL_SHARED = 0x30,
|
||||
OFF_M2SHARED_DATA = 0x130,
|
||||
OFF_M2DATA_CAMERA_ID_LOOKUP = 0x12C,
|
||||
// The sequence count, and the {count, uint16 *} lookup from animation id
|
||||
// to sequence. PlayAnimation (0x007121A0) maps a resolved id through the
|
||||
// lookup and drops it when the index is not below the count.
|
||||
OFF_M2DATA_SEQUENCE_COUNT = 0x1C,
|
||||
OFF_M2DATA_ANIMATION_LOOKUP = 0x24,
|
||||
// SetAnimated — `__thiscall(model, bool)`; M2SceneAnimateAndCollect —
|
||||
// `__thiscall(scene, const float *cameraPos)`. ConfigureCamera runs both
|
||||
// (with the frame's facing zeroed) before it copies a model camera.
|
||||
@@ -5781,6 +6099,37 @@ enum Offsets {
|
||||
VAR_ITEMDISPLAYINFO_RECORDS = 0x00C0DC10,
|
||||
VAR_ITEMDISPLAYINFO_COUNT = 0x00C0DC14,
|
||||
OFF_ITEMDISPLAYINFO_ICON = 0x14,
|
||||
// ItemVisuals ID (column 22). Read by AttachItemModel (0x0047A200) and
|
||||
// GetItemDescVisual (0x0062EC93); 365 stock rows set it.
|
||||
OFF_ITEMDISPLAYINFO_ITEM_VISUAL = 0x58,
|
||||
// The two model names (+0x04, +0x08) and their two textures (+0x0C,
|
||||
// +0x10), char* each. AttachItemModel (0x0047A0C0) builds a held item
|
||||
// from name 0 and texture 0 (0x0047A192, 0x0047A1CF); the loose ammo
|
||||
// model (0x00479F40) from name 1 and texture 1 (0x00479F58, 0x00479F64).
|
||||
OFF_ITEMDISPLAYINFO_MODEL_NAME = 0x04,
|
||||
OFF_ITEMDISPLAYINFO_MODEL_TEXTURE = 0x0C,
|
||||
|
||||
// ItemVisuals.dbc — an ID plus five ItemVisualEffects IDs (6 columns),
|
||||
// the glow AttachItemVisuals hangs on a weapon. Instance 0x00C0DB68.
|
||||
VAR_ITEMVISUALS_RECORDS = 0x00C0DB70,
|
||||
VAR_ITEMVISUALS_COUNT = 0x00C0DB74,
|
||||
|
||||
// SpellVisualKit.dbc (35 columns, 0x8C-byte rows). The char-select ghost
|
||||
// kit (0x004727F0) reads the store at 0x004727F6/0x0047280A: nine effect
|
||||
// IDs from +0x0C (attachments 20, 34, 19, 21, 22, 17, 23, 24, 25, pushed
|
||||
// at 0x00472821..0x0047287F), then four procedure kinds from +0x3C with
|
||||
// their first values from +0x4C (the loop at 0x00472888). Kind 1 is a
|
||||
// tint, the value an 0xRRGGBB colour stored as a float; kind 14 an alpha.
|
||||
VAR_SPELLVISUALKIT_RECORDS = 0x00C0D74C,
|
||||
VAR_SPELLVISUALKIT_COUNT = 0x00C0D750,
|
||||
OFF_SPELLVISUALKIT_EFFECTS = 0x0C,
|
||||
OFF_SPELLVISUALKIT_PROC_KIND = 0x3C,
|
||||
OFF_SPELLVISUALKIT_PROC_VALUE = 0x4C,
|
||||
// SpellVisualEffectName.dbc: the model file at +0x08, which the ghost
|
||||
// kit's effect step (0x00472780) creates (0x004727AF).
|
||||
VAR_SPELLVISUALEFFECTNAME_RECORDS = 0x00C0D760,
|
||||
VAR_SPELLVISUALEFFECTNAME_COUNT = 0x00C0D764,
|
||||
OFF_SPELLVISUALEFFECTNAME_FILE = 0x08,
|
||||
|
||||
// ItemSet.dbc — armor/equipment set definitions referenced by
|
||||
// `ItemStats_C::m_itemSet`. Standard 5-DWORD class shape at
|
||||
@@ -5826,15 +6175,37 @@ enum Offsets {
|
||||
// (21) set only the main-hand bit, off-hand-only (22) only the off-hand.
|
||||
VAR_INVTYPE_SLOT_MASKS = 0x00809200,
|
||||
|
||||
// The two `m_inventoryType` values that mean "consumed when used", so
|
||||
// both the item-consumable test (`C_Item.IsConsumableItem`) and the
|
||||
// action-slot one (`IsConsumableAction`) single them out. Indices into
|
||||
// the table above: 24 = `INVTYPE_AMMO`, 25 = `INVTYPE_THROWN`.
|
||||
// `m_inventoryType` (`OFF_ITEMSTATS_INVENTORY_TYPE`) values, named by
|
||||
// their key in the string table above (read from WoW.exe).
|
||||
INVTYPE_NON_EQUIP = 0,
|
||||
INVTYPE_HEAD = 1,
|
||||
INVTYPE_NECK = 2,
|
||||
INVTYPE_SHOULDER = 3,
|
||||
INVTYPE_BODY = 4,
|
||||
INVTYPE_CHEST = 5,
|
||||
INVTYPE_WAIST = 6,
|
||||
INVTYPE_LEGS = 7,
|
||||
INVTYPE_FEET = 8,
|
||||
INVTYPE_WRIST = 9,
|
||||
INVTYPE_HAND = 10,
|
||||
INVTYPE_FINGER = 11,
|
||||
INVTYPE_TRINKET = 12,
|
||||
INVTYPE_WEAPON = 13,
|
||||
INVTYPE_SHIELD = 14,
|
||||
INVTYPE_RANGED = 15,
|
||||
INVTYPE_CLOAK = 16,
|
||||
INVTYPE_2HWEAPON = 17,
|
||||
INVTYPE_BAG = 18,
|
||||
INVTYPE_TABARD = 19,
|
||||
INVTYPE_ROBE = 20,
|
||||
INVTYPE_WEAPONMAINHAND = 21,
|
||||
INVTYPE_WEAPONOFFHAND = 22,
|
||||
INVTYPE_HOLDABLE = 23,
|
||||
INVTYPE_AMMO = 24,
|
||||
INVTYPE_THROWN = 25,
|
||||
// Index 16 of the table above (read from WoW.exe). The bag search skips
|
||||
// a cloak's subclass name ("Cloth"), as 4.3.4's does.
|
||||
INVTYPE_CLOAK = 16,
|
||||
INVTYPE_RANGEDRIGHT = 26,
|
||||
INVTYPE_QUIVER = 27,
|
||||
INVTYPE_RELIC = 28,
|
||||
|
||||
// Faction "displayed list" — the engine maintains a sorted/visible list
|
||||
// of factions the player has rep with. `Script_GetNumFactions` (at
|
||||
@@ -5906,6 +6277,13 @@ enum Offsets {
|
||||
VAR_CHRRACES_COUNT = 0x00C0DEE4,
|
||||
OFF_CHRRACES_FILENAME = 0x3C,
|
||||
OFF_CHRRACES_NAMES = 0x44,
|
||||
// The client prefix ("Hu", "Or", ...) the head-item path takes: the helm
|
||||
// builder (0x004799A0) reads it at 0x00479A21 for
|
||||
// `Item\ObjectComponents\Head\<name>_<prefix><sex letter>.mdx`.
|
||||
OFF_CHRRACES_CLIENT_PREFIX = 0x18,
|
||||
// The helm builder's sex letters, `const char *[3]` = "M", "F", "U",
|
||||
// indexed by the sex byte (0x00479A1A).
|
||||
VAR_SEX_LETTERS = 0x00838AB4,
|
||||
|
||||
// CreatureFamily.dbc — pet/beast family metadata. Standard 5-DWORD
|
||||
// class shape (instance 0x00C0DE74; records 0x00C0DE7C, count
|
||||
@@ -6275,6 +6653,17 @@ enum Offsets {
|
||||
OFF_UNIT_DESCRIPTOR_RACE_BYTE = 0x78,
|
||||
OFF_UNIT_DESCRIPTOR_CLASS_BYTE = 0x79,
|
||||
OFF_UNIT_DESCRIPTOR_SEX_BYTE = 0x7A,
|
||||
// UNIT_NPC_FLAGS, read at descriptor +0x234 by ClassifyHoveredUnit's flag
|
||||
// chain (0x48233C) and IsQuestGiverToTalk (0x5DF49C).
|
||||
OFF_UNIT_DESCRIPTOR_NPC_FLAGS = 0x234,
|
||||
// CGUnit_C::m_questStatus: the quest giver status the server last sent
|
||||
// (SMSG_QUESTGIVER_STATUS), stored by SetQuestGiverStatus (0x607440) for
|
||||
// a unit with NPC flag 0x2 (benilla-re objectlayer/unitnames).
|
||||
OFF_CGUNIT_QUEST_STATUS = 0xCB8,
|
||||
// IsQuestGiverToTalk — `uint32_t __stdcall(CGUnit_C *unit)`, ret 4: 1 for
|
||||
// NPC flag 0x2 with a quest status other than 0 and 1. ClassifyHoveredUnit
|
||||
// shows Speak for it (disassembled 0x5DF490-0x5DF4C4).
|
||||
FUN_IS_QUEST_GIVER_TO_TALK = 0x005DF490,
|
||||
|
||||
// The local player's UNIT_FIELD_BYTES_0 race/class/sex bytes, mirrored
|
||||
// into a login-time global that is populated at character-enter —
|
||||
@@ -7984,6 +8373,9 @@ enum Offsets {
|
||||
OFF_SPELLITEMENCHANT_AMOUNT = 0x10, // int Amount[3] (EffectPointsMin)
|
||||
OFF_SPELLITEMENCHANT_ARG = 0x28, // int EffectArg[3]
|
||||
OFF_SPELLITEMENCHANT_NAME = 0x34, // char *name[8], locale-indexed
|
||||
// ItemVisuals ID (column 22), the weapon glow; read by GetItemDescVisual
|
||||
// (0x0062ECD7). Crusader 1900 = 103, Fiery 803 = 25, Icy 1894 = 126.
|
||||
OFF_SPELLITEMENCHANT_ITEM_VISUAL = 0x58,
|
||||
|
||||
// ItemRandomProperties.dbc — the table a random-suffix ID (the third
|
||||
// field of an `item:id:enchant:suffix:unique` link) indexes to get
|
||||
@@ -8287,17 +8679,20 @@ enum Offsets {
|
||||
// unit-event observers. Matches nampower's `CGRaidInfo_m_raidtargets`.
|
||||
VAR_RAID_TARGET_GUIDS = 0x00B71368,
|
||||
|
||||
VAR_LOCALE_INDEX = 0x00C0E080, // 0..8, picks one of the 9 localized strings
|
||||
// 0..7: start-up stores the Wow.ini Language+Country tag's position in
|
||||
// VAR_LOCALE_NAME_TABLE (0x402562), 0 when it matches none (enGB).
|
||||
// Picks a DBC localized-string column. NOT 4.3.4's nine-slot locale
|
||||
// order — language rules go by name (Localization::Locale).
|
||||
VAR_LOCALE_INDEX = 0x00C0E080,
|
||||
|
||||
// Locale-name string table — a parallel `const char *[8]` indexed by
|
||||
// VAR_LOCALE_INDEX, holding the exact codes `GetLocale()` returns:
|
||||
// enUS / koKR / frFR / deDE / zhCN / zhTW / esES / xxYY. This is the
|
||||
// source `Script_GetLocale` (FUN_0048d8b0) reads:
|
||||
// enUS / koKR / frFR / deDE / zhCN / zhTW / esES / xxYY on a stock
|
||||
// client. This is the source `Script_GetLocale` (FUN_0048d8b0) reads:
|
||||
// lua_pushstring(L, (&PTR_DAT_008558a4)[DAT_00c0e080]);
|
||||
// Verified by resolving all 8 entries from the binary. Index range is
|
||||
// 0..7 (VAR_LOCALE_INDEX is documented 0..8, but slot 8 overreads into
|
||||
// the string pool — clamp to 0..7 before reading). Used by the TOC
|
||||
// `[TextLocale]` / `[AllowLoadTextLocale]` directives (AddOns::TocRewrite).
|
||||
// and the list SetAddOnMetadata (0x51D580) matches a TOC key's
|
||||
// `-xxXX` suffix against. Verified by resolving all 8 entries from the
|
||||
// binary. Read through Localization::Locale::Client().
|
||||
VAR_LOCALE_NAME_TABLE = 0x008558A4,
|
||||
|
||||
LUA_IS_NUMBER = 0x6F34D0,
|
||||
@@ -8795,8 +9190,32 @@ enum Offsets {
|
||||
LUA_GET_STACK = 0x006FBAA0,
|
||||
LUA_GET_INFO = 0x006FBC70,
|
||||
SIZEOF_LUA_DEBUG = 0x60,
|
||||
OFF_LUA_DEBUG_NAME = 0x04,
|
||||
OFF_LUA_DEBUG_SOURCE = 0x10,
|
||||
OFF_LUA_DEBUG_CURRENTLINE = 0x14,
|
||||
OFF_LUA_DEBUG_LINEDEFINED = 0x1C,
|
||||
// `lua_getlocal(L, ar, n)` — `const char *__fastcall` (n on the stack):
|
||||
// pushes local n of the call level ar names (ar+0x5C, i_ci) and returns
|
||||
// its name, or returns null and pushes nothing (no such local, or a C
|
||||
// level). No engine callers.
|
||||
LUA_GET_LOCAL = 0x006FBB20,
|
||||
// `lua_getupvalue(L, funcindex, n)` — `const char *__fastcall` (n on the
|
||||
// stack): pushes upvalue n of the function at funcindex and returns its
|
||||
// name ("" for a C closure), or null for no such upvalue or a value that
|
||||
// is not a function. (0x006F45B0 is lua_pushupvalues.)
|
||||
LUA_GET_UPVALUE = 0x006F4660,
|
||||
// `debugstack([start [, count1 [, count2]]])` — the engine's C function
|
||||
// (base-function table 0x811E28); no engine callers. It reads the stack
|
||||
// of the state it is called on through five lua_getstack calls and one
|
||||
// lua_getinfo("Snl"), and pushes its text onto that same state.
|
||||
// BaseLib::DebugStack points those six calls at a thread's state.
|
||||
FUN_SCRIPT_DEBUGSTACK = 0x00703760,
|
||||
CALL_DEBUGSTACK_GETSTACK_FIRST = 0x0070383F,
|
||||
CALL_DEBUGSTACK_GETSTACK_TAIL_PROBE = 0x0070386E,
|
||||
CALL_DEBUGSTACK_GETSTACK_TAIL_START = 0x00703896,
|
||||
CALL_DEBUGSTACK_GETSTACK_TAIL_SKIP = 0x007038AD,
|
||||
CALL_DEBUGSTACK_GETSTACK_NEXT = 0x00703998,
|
||||
CALL_DEBUGSTACK_GETINFO = 0x007038CD,
|
||||
// `luaO_chunkid(out, source, bufflen)` — `void __fastcall(char *out /*ecx*/,
|
||||
// const char *source /*edx*/, int bufflen /*stack*/)`, RET 4: the name a
|
||||
// chunk has in error messages. `=name` is copied as is, `@file` is the
|
||||
@@ -9264,9 +9683,21 @@ enum Offsets {
|
||||
FUN_BLP_DECODE_LEVEL = 0x005A7F00,
|
||||
FUN_BLP_FREE_SCRATCH = 0x005A8100,
|
||||
FUN_BLP_RESET = 0x005A7AA0,
|
||||
// Parse(this, const void *file) → nonzero for a 'BLP2' type-1 header
|
||||
// (ret 4): Open's second half. It copies BLP_HEADER_SIZE bytes of `file`
|
||||
// into the record and keeps `file` as the pixel source (+0x498), which
|
||||
// Reset only drops (it frees the decoded chain at +0x00), so a buffer the
|
||||
// caller owns decodes like the one Open reads (disassembled
|
||||
// 0x5A83B0-0x5A842A).
|
||||
FUN_BLP_PARSE = 0x005A83B0,
|
||||
BLP_HEADER_SIZE = 0x494,
|
||||
BLP_RECORD_SIZE = 0x4B4,
|
||||
OFF_BLP_MAGIC = 0x04,
|
||||
OFF_BLP_VERSION = 0x08,
|
||||
// byte: 1 palette, 2 DXT, 3 raw BGRA (DecodeLevel's switch, 0x5A7F6B)
|
||||
OFF_BLP_COMPRESSION = 0x0C,
|
||||
// byte: for DXT, 0 DXT1, 1 DXT3, 7 DXT5 (DecodeLevel takes its class
|
||||
// at 0x5A7FC3)
|
||||
OFF_BLP_ALPHA_TYPE = 0x0E,
|
||||
OFF_BLP_WIDTH = 0x10,
|
||||
OFF_BLP_HEIGHT = 0x14,
|
||||
@@ -9276,6 +9707,15 @@ enum Offsets {
|
||||
// benilla image/convert#palette-to-bgra). GetPortraitAlphaMask decodes
|
||||
// to this format.
|
||||
GX_TEX_FORMAT_BGRA8888 = 2,
|
||||
// DecodeLevel converts a DXT level to another format through the matrix
|
||||
// InitConvertMatrix (0x005A4FC0) fills, by the class of the alpha type.
|
||||
// It fills DXT1 and DXT3 to BGRA8888 (cells 0x00C0F840, 0x00C0F8D0) and
|
||||
// leaves DXT5's (0x00C0F960) empty, so a DXT5 level gets an unwritten
|
||||
// SMemAlloc buffer and DecodeLevel still returns 1 (disassembled
|
||||
// 0x5A4FC0-0x5A5062 and 0x5A7FC3-0x5A8045; benilla
|
||||
// image/convert#conversion-matrix).
|
||||
BLP_COMPRESSION_DXT = 2,
|
||||
BLP_ALPHA_TYPE_DXT5 = 7,
|
||||
|
||||
// MPQ file enumerator — walks every mounted archive's `(listfile)`
|
||||
// and invokes a callback for each entry whose path begins with a
|
||||
@@ -10429,6 +10869,10 @@ enum Offsets {
|
||||
// descriptor — though we only expose `IsMounted()` for the
|
||||
// player to match modern API semantics.
|
||||
OFF_UNIT_FIELD_MOUNTDISPLAYID = 0x1FC,
|
||||
// UNIT_FIELD_DISPLAYID: the display the unit shows now (a shapeshift
|
||||
// form's while shapeshifted). Field 0x7D in the update-field name table
|
||||
// (0x0083A73C).
|
||||
OFF_UNIT_FIELD_DISPLAYID = 0x1F4,
|
||||
|
||||
// Local player movement-flags u32. Lives directly on the
|
||||
// CGPlayer object (not the broadcast descriptor) — this is
|
||||
@@ -10523,10 +10967,6 @@ enum Offsets {
|
||||
INVSLOT_OFFHAND = 17,
|
||||
INVSLOT_MAINHAND = 16,
|
||||
|
||||
// INVTYPE (`OFF_ITEMSTATS_INVENTORY_TYPE`) of a two-handed weapon,
|
||||
// which occupies the main-hand slot and blocks the off-hand.
|
||||
INVTYPE_2HWEAPON = 17,
|
||||
|
||||
// Character-pane equipment slot range: 1..19 (head=1, neck=2, …,
|
||||
// tabard=19). Used by `C_Item.IsEquippedItem` to walk every slot
|
||||
// looking for a matching item.
|
||||
@@ -10989,6 +11429,13 @@ enum Offsets {
|
||||
// event-driven (state changes only), not per-frame, so per-call
|
||||
// overhead from a hooked predicate is negligible.
|
||||
FUN_FIRE_EVENT_NO_ARGS = 0x00703E50,
|
||||
// The enter cascade's (0x004908C0) `mov ecx, 0x10E; call
|
||||
// FUN_FIRE_EVENT_NO_ARGS`: PLAYER_LOGIN, the only place it fires. It runs
|
||||
// only while the arm byte 0x00B4E260 is set, which the interface
|
||||
// initializer (0x0048FBF0) sets after the addons load and this path
|
||||
// clears, so it fires once per UI instance: at login and after a reload
|
||||
// (benilla ui/worldenter). E8 call, disassembled.
|
||||
CALL_ENTER_WORLD_FIRE_PLAYER_LOGIN = 0x00490959,
|
||||
|
||||
// `__fastcall(int sendRelease, char earlyReturnOnGameObject,
|
||||
// char showError)` — engine's "close the current loot window"
|
||||
@@ -11624,11 +12071,16 @@ enum Offsets {
|
||||
// column 11: the parsed 1.12 DBC gives 0x7 for the warrior stances, 0x1
|
||||
// Stealth, 0x9 Shadowform, matching 3.3.5's column. 0x100 is the form
|
||||
// flag 3.3.5 / 4.3.4 `CGUnit_C::CanAutoUnshift` refuses on (set there
|
||||
// only on Spirit of Redemption and the Ghoul forms); no 1.12 row has it.
|
||||
// only on Spirit of Redemption and the Ghoul forms); no 1.12 row has it,
|
||||
// so Spell::AutoPrepare adds it to Spirit of Redemption (form 32).
|
||||
VAR_SPELL_SHAPESHIFT_FORM_RECORDS = 0x00C0D774,
|
||||
VAR_SPELL_SHAPESHIFT_FORM_COUNT = 0x00C0D778,
|
||||
OFF_SPELL_SHAPESHIFT_FORM_FLAGS = 0x2C,
|
||||
SHAPESHIFT_FORM_FLAG_NO_AUTO_UNSHIFT = 0x100,
|
||||
// Flag 0x8: the form may talk to NPCs and use objects (Shadowform's 0x9).
|
||||
// Tested by both interaction gates (`test byte [eax+0x2C],0x8` at
|
||||
// 0x005F017F / 0x005F3210) and 4.3.4's HandleOnRightClick / CanUseNow.
|
||||
SHAPESHIFT_FORM_FLAG_CAN_INTERACT = 0x8,
|
||||
|
||||
// Control-loss aura effect types (EffectApplyAuraName values), used by
|
||||
// C_LossOfControl to classify a debuff. Values are authoritative from the
|
||||
@@ -12611,6 +13063,120 @@ enum Offsets {
|
||||
// the per-stage applies clamp against (FUN_005a29d0/FUN_005a2b40).
|
||||
VAR_GX_DEVICE = 0x00C0ED38,
|
||||
OFF_GXDEV_STAGE_CAP = 0x23C,
|
||||
// ---- Unit portrait bake (BakePortrait 0x00524F60, benilla ui/portraits)
|
||||
// Every entry below is a call the bake makes, read off its disassembly;
|
||||
// calling convention from the call site. `Model::Portrait` replays the
|
||||
// bake on a display model of its own.
|
||||
// M2CreateScene — `void *()`: a fresh scene on the model cache.
|
||||
FUN_M2_CREATE_SCENE = 0x00706E10,
|
||||
// `__thiscall(scene)`: drops one scene reference; at zero the destructor
|
||||
// releases every instance on the scene's model list.
|
||||
FUN_M2SCENE_RELEASE = 0x00707320,
|
||||
// CreateModelFromPath — `void *__thiscall(scene, const char *path,
|
||||
// uint32 flags)`, `ret 8`: a new instance of the file's shared model
|
||||
// (Spells\ErrorCube.mdx when the file is missing). AttachDisplayModel
|
||||
// passes the CreatureModelData path and 0. The caller owns one reference.
|
||||
FUN_M2SCENE_CREATE_MODEL = 0x00707350,
|
||||
// CreateModelFromModel — `void *__thiscall(scene, source, uint32 flags)`:
|
||||
// a new instance of the source's shared model, with its attachments.
|
||||
FUN_M2SCENE_COPY_MODEL = 0x00707400,
|
||||
// M2SceneDraw — `__thiscall(scene, pass)`; the bake draws 0 then 1.
|
||||
FUN_M2SCENE_DRAW = 0x00708900,
|
||||
// `__fastcall(model)`: drops one instance reference (HandleClose2).
|
||||
FUN_MODEL_INSTANCE_RELEASE = 0x007103A0,
|
||||
// CM2Model::IsReady — `__thiscall(model, wait, recurseChildren)`:
|
||||
// loaded AND every texture has its device texture. `wait` blocks on the
|
||||
// reads and creates the device textures (GetTextureDeviceObject(.., 1));
|
||||
// the bake's gate passes (0, 1), the bake itself (1, 1).
|
||||
FUN_MODEL_INSTANCE_READY = 0x00710450,
|
||||
// CM2Model::SetVisible — `__thiscall(model, bool)`.
|
||||
FUN_M2MODEL_SET_VISIBLE = 0x00710C50,
|
||||
// CM2Model::SetAlpha — `__thiscall(model, float)`; SetDiffuseColor —
|
||||
// `__thiscall(model, const float rgb[3])`. Plain stores, which the
|
||||
// ghost kit applies (0x004728C1, 0x00472939). Attached children take
|
||||
// them through the per-frame combine.
|
||||
FUN_M2MODEL_SET_ALPHA = 0x00710CB0,
|
||||
FUN_M2MODEL_SET_DIFFUSE_COLOR = 0x00710CF0,
|
||||
// CM2Model::HasAttachment — `__thiscall(model, id)`, ret 4. Waits for
|
||||
// the model file (0x00710520) when it has not loaded.
|
||||
FUN_M2MODEL_HAS_ATTACHMENT = 0x00712CB0,
|
||||
// CM2Model::Attach — `__thiscall(child, parent, id)`, ret 8. The
|
||||
// parent takes a reference; the attach index resolves once the parent
|
||||
// loads.
|
||||
FUN_M2MODEL_ATTACH = 0x00712F70,
|
||||
// CM2Model::Detach — `__thiscall(child)`: unlinks the child from its
|
||||
// parent, clears its parent (OFF_M2MODEL_PARENT) and drops the parent's
|
||||
// reference. The destructor (0x0070E3F5) detaches every child this way.
|
||||
FUN_M2MODEL_DETACH = 0x00713020,
|
||||
OFF_M2MODEL_PARENT = 0x1CC,
|
||||
// CM2Model::HasLiveParticles — `__thiscall(model)`: +0x3D8, which
|
||||
// Animate sets while an emitter emits or holds a live particle. The
|
||||
// dying-effect drain (0x0061F6CC) releases a stopped effect once false.
|
||||
FUN_M2MODEL_HAS_LIVE_PARTICLES = 0x007135C0,
|
||||
// CM2Model::PlayAnimation — `__thiscall(model, 7 args)`, `ret 0x1C`. The
|
||||
// bake passes (-1, 0, 0, 0, 1.0f, 0, 1).
|
||||
FUN_M2MODEL_PLAY_ANIMATION = 0x007121A0,
|
||||
// CM2Model::GetKeyBoneAnimId — `__thiscall(model, keyBone)`, ret 4: the id
|
||||
// last requested on the key bone (bone state +0xF8, the id as given, not
|
||||
// its fallback). Blocks on the shared data when the model is not loaded.
|
||||
FUN_M2MODEL_GET_KEY_BONE_ANIM = 0x00712090,
|
||||
// The bone states, 0x118 bytes each, at [model+0x90]; key bone -1 is bone
|
||||
// 0 (GetKeyBoneAnimId above). PlayAnimation fills a bone's primary slot
|
||||
// (+0x98): the offset at +0xB8, where the sequence time is start +
|
||||
// (ftol((now - startStamp) * speed) + offset) mod length, and the done
|
||||
// latch at +0xC0, which it clears. Update (0x00719370) queues the done
|
||||
// callback once when the bone's window reaches its end stamp (+0xAC) and
|
||||
// the latch is 0, and sets the latch. A speed of 0 makes the end stamp
|
||||
// the start stamp, so done would fire on the next update.
|
||||
OFF_M2MODEL_BONE_STATES = 0x90,
|
||||
OFF_M2BONE_SEQUENCE_OFFSET = 0xB8,
|
||||
OFF_M2BONE_DONE_REPORTED = 0xC0,
|
||||
// ApplyCreatureDisplay — `__fastcall(model, displayRec, modelDataRec)`,
|
||||
// `ret 4`: the CreatureDisplayInfo row's three skins as the model's
|
||||
// replaceable textures 11..13 (AttachDisplayModel 0x00505AE6).
|
||||
FUN_APPLY_CREATURE_DISPLAY = 0x004797B0,
|
||||
// DetachAllItemModels — `__fastcall(model)`: the bake strips the copy's
|
||||
// attachments (helm, shoulders, held items) before it draws.
|
||||
FUN_DETACH_ALL_ITEM_MODELS = 0x0047A230,
|
||||
// CameraSetDeviceTransforms — `__fastcall(camera, const float rect[4],
|
||||
// int)`: sets the device projection and view from the camera over the
|
||||
// rect; the bake passes {0, 0, 1, aspect} and 0.
|
||||
FUN_CAMERA_SET_DEVICE_TRANSFORMS = 0x007ADA40,
|
||||
// PortraitLighting — the bake's lighting callback (ambient 0.45 grey and a
|
||||
// white light along (-1, 0, -1)), set with FUN_MODEL_INSTANCE_SET_RENDER_CB
|
||||
// and a null argument.
|
||||
FUN_PORTRAIT_LIGHTING = 0x00525B10,
|
||||
// GetPortraitAlphaMask — `void *__fastcall(uint32 size)`: an array whose
|
||||
// byte data (pointer at +8) is the round mask's alpha, one per texel.
|
||||
FUN_PORTRAIT_ALPHA_MASK = 0x005258A0,
|
||||
// GxCapsScreenSize — `__fastcall(float rect[4])`, {top, left, bottom,
|
||||
// right}; see FUN_GX_CAPS_WINDOW_SIZE.
|
||||
FUN_GX_CAPS_SCREEN_SIZE = 0x0058A250,
|
||||
// The bake's device gate: `int ()` each. WINDOW_ACTIVE reads the device's
|
||||
// active flag (+0xF30, cleared while minimised), DEVICE_ALIVE reads
|
||||
// OFF_GXDEVD3D_ALIVE (+0xF2C).
|
||||
FUN_GX_WINDOW_ACTIVE = 0x0058A260,
|
||||
FUN_GX_DEVICE_ALIVE = 0x0058A280,
|
||||
// GxMasterEnable get / set — `bool __fastcall(cap)` / `__fastcall(cap,
|
||||
// value)`. The bake saves cap 1 (fog), turns it off, and restores it.
|
||||
FUN_GX_MASTER_ENABLE_GET = 0x0058A210,
|
||||
FUN_GX_MASTER_ENABLE_SET = 0x0058A1E0,
|
||||
GX_MASTER_FOG = 1,
|
||||
// SetClearColorDevice — `__stdcall(uint32 argb)`; the bake sets 0.
|
||||
FUN_GX_SET_CLEAR_COLOR = 0x0058A920,
|
||||
// ClearDevice — `__fastcall(uint32 mask)`; the bake clears 3 before the
|
||||
// draw and again after the readback.
|
||||
FUN_GX_CLEAR = 0x0058A970,
|
||||
// `__fastcall(const int rect[4], GrowableArray<uint32> *out)`: reads a
|
||||
// back-buffer rectangle (device slot +0x48); the bake reads {0,0,64,64}.
|
||||
FUN_GX_READ_PIXELS = 0x00589CF0,
|
||||
// GxTexMarkRectDirty — `__fastcall(CGxTex *, const int rect[4], uint32
|
||||
// immediate)`, `ret 4`: re-latches a callback texture's texels; the bake's
|
||||
// re-draw of a cached portrait uploads its new pixels with immediate 1.
|
||||
FUN_GX_TEX_MARK_RECT_DIRTY = 0x0058ACD0,
|
||||
// CSimpleTexture::SetImage — `__thiscall(texture, HTEXTURE)`, `ret 4`:
|
||||
// binds a texture handle (taking a reference) and notifies the layer.
|
||||
FUN_SIMPLETEXTURE_SET_IMAGE = 0x00770300,
|
||||
// The vertex-stream primitive every textured region draws through
|
||||
// (FUN_0076fb00 calls it per region). Decompiler signature (13 params):
|
||||
// (count, positions, posStride, s3, s3Stride, colors, colorStride, drop8,
|
||||
|
||||
+1
-3
@@ -170,9 +170,7 @@ static void RegisterLuaFunctions() {
|
||||
&Script_HasSpellActionButtons, &kHasSpellActionButtons);
|
||||
Game::Lua::RegisterTableFunction("C_ActionBar", "IsOnBarOrSpecialBar",
|
||||
&Script_IsOnBarOrSpecialBar, &kIsOnBarOrSpecialBar);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ActionBarSet", kActionBarSet,
|
||||
static_cast<int>(sizeof(kActionBarSet) / sizeof(kActionBarSet[0])),
|
||||
"ActionBar");
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ActionBarSet", kActionBarSet, "ActionBar");
|
||||
}
|
||||
|
||||
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
+8
-23
@@ -65,6 +65,7 @@
|
||||
|
||||
#include "embedded_resources.h"
|
||||
|
||||
#include "Common.h"
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "addons/Embedded.h"
|
||||
@@ -103,26 +104,12 @@ constexpr const char *kDevMarkerFile = ".classicapi-dev";
|
||||
// paths are case-insensitive.
|
||||
constexpr const char *kAddOnsPrefix = "Interface\\AddOns\\";
|
||||
|
||||
char NormalizeChar(char c) {
|
||||
if (c == '/') return '\\';
|
||||
if (c >= 'A' && c <= 'Z') return static_cast<char>(c + 32);
|
||||
return c;
|
||||
}
|
||||
|
||||
bool PathEqualsCI(const char *a, const char *b) {
|
||||
while (*a && *b) {
|
||||
if (NormalizeChar(*a) != NormalizeChar(*b)) return false;
|
||||
++a; ++b;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
// `path` with the case-insensitive `prefix` stripped, NULL when it does
|
||||
// not start with it.
|
||||
const char *StripPrefixCI(const char *path, const char *prefix) {
|
||||
while (*prefix) {
|
||||
if (*path == '\0') return nullptr;
|
||||
if (NormalizeChar(*path) != NormalizeChar(*prefix)) return nullptr;
|
||||
if (Common::FoldPathChar(*path) != Common::FoldPathChar(*prefix)) return nullptr;
|
||||
++path; ++prefix;
|
||||
}
|
||||
return path;
|
||||
@@ -130,7 +117,7 @@ const char *StripPrefixCI(const char *path, const char *prefix) {
|
||||
|
||||
int IndexOfAddon(const char *name) {
|
||||
for (size_t i = 0; i < kAddonCount; ++i) {
|
||||
if (PathEqualsCI(name, kAddons[i]))
|
||||
if (Common::SamePath(name, kAddons[i]))
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
return -1;
|
||||
@@ -144,7 +131,7 @@ int MatchAddonPath(const char *path, const char **relPath) {
|
||||
if (rel == nullptr) return -1;
|
||||
for (size_t i = 0; i < kAddonCount; ++i) {
|
||||
const char *rest = StripPrefixCI(rel, kAddons[i]);
|
||||
if (rest != nullptr && NormalizeChar(*rest) == '\\') {
|
||||
if (rest != nullptr && Common::FoldPathChar(*rest) == '\\') {
|
||||
*relPath = rel;
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
@@ -155,12 +142,10 @@ int MatchAddonPath(const char *path, const char **relPath) {
|
||||
// The embedded bytes for manifest path `relPath`; `data` is NULL when it
|
||||
// is not embedded.
|
||||
Embed::Bytes LookupEmbedded(const char *relPath) {
|
||||
for (size_t i = 0; i < Manifest::kFileCount; ++i) {
|
||||
if (PathEqualsCI(relPath, Manifest::kFiles[i].path))
|
||||
return Embed::File(Manifest::kFiles[i].resourceId,
|
||||
Manifest::kFiles[i].index);
|
||||
}
|
||||
return {nullptr, 0};
|
||||
const Manifest::File *file = Embed::FindFile(Manifest::kFiles, relPath);
|
||||
if (file == nullptr)
|
||||
return {nullptr, 0};
|
||||
return Embed::File(file->resourceId, file->index);
|
||||
}
|
||||
|
||||
// Engine file I/O + Storm allocator — see addons/EngineIO.h for the shapes and
|
||||
|
||||
+1
-5
@@ -408,11 +408,7 @@ static void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterTableFunction("C_AddOns", "GetAddOnInterfaceVersion",
|
||||
&Script_GetAddOnInterfaceVersion,
|
||||
&kGetAddOnInterfaceVersion);
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "AddOnSecurityStatus",
|
||||
kAddOnSecurityStatusEntries,
|
||||
sizeof(kAddOnSecurityStatusEntries) /
|
||||
sizeof(kAddOnSecurityStatusEntries[0]));
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "AddOnSecurityStatus", kAddOnSecurityStatusEntries);
|
||||
}
|
||||
|
||||
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "addons/EngineIO.h"
|
||||
#include "addons/TocRewrite.h"
|
||||
#include "addons/Toc.h"
|
||||
#include "localization/Locale.h"
|
||||
|
||||
#include "Offsets.h"
|
||||
#include "turtle/Detect.h"
|
||||
@@ -34,17 +35,7 @@ using AddOns::Toc::Lower;
|
||||
|
||||
bool IsSpace(char c) { return c == ' ' || c == '\t'; }
|
||||
|
||||
// The client locale code ("enUS", "frFR", …) — the exact string
|
||||
// GetLocale() returns (Offsets.h VAR_LOCALE_NAME_TABLE derivation).
|
||||
const char *ClientLocale() {
|
||||
uint32_t idx = *reinterpret_cast<const uint32_t *>(
|
||||
static_cast<uintptr_t>(Offsets::VAR_LOCALE_INDEX));
|
||||
if (idx > 7) idx = 0; // slot 8 overreads the string pool — clamp
|
||||
const char *const *table = reinterpret_cast<const char *const *>(
|
||||
static_cast<uintptr_t>(Offsets::VAR_LOCALE_NAME_TABLE));
|
||||
const char *s = table[idx];
|
||||
return (s != nullptr) ? s : "enUS";
|
||||
}
|
||||
using Localization::Locale::Client;
|
||||
|
||||
// True iff the comma-separated `list` (spaces allowed) contains `token` (CI).
|
||||
bool ListContains(const char *list, size_t listLen, const char *token) {
|
||||
@@ -66,7 +57,7 @@ bool ListContains(const char *list, size_t listLen, const char *token) {
|
||||
const char *VariableValue(const char *head, size_t headLen) {
|
||||
if (EqCI(head, headLen, "Family")) return "Classic";
|
||||
if (EqCI(head, headLen, "Game")) return "Vanilla";
|
||||
if (EqCI(head, headLen, "TextLocale")) return ClientLocale();
|
||||
if (EqCI(head, headLen, "TextLocale")) return Client();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -158,7 +149,7 @@ bool EvalCondition(const char *head, size_t headLen, const char *args,
|
||||
if (EqCI(head, headLen, "ExcludeLoadGameType"))
|
||||
return !ListContainsGameType(args, argsLen);
|
||||
if (EqCI(head, headLen, "AllowLoadTextLocale"))
|
||||
return ListContains(args, argsLen, ClientLocale());
|
||||
return ListContains(args, argsLen, Client());
|
||||
if (EqCI(head, headLen, "AllowLoad"))
|
||||
return ListContains(args, argsLen, "game"); // addon files: in-game only
|
||||
return false;
|
||||
|
||||
@@ -560,9 +560,8 @@ void RegisterLuaFunctions() {
|
||||
RegisterClass(Anim::s_rotationClass);
|
||||
RegisterClass(Anim::s_pathClass);
|
||||
RegisterClass(ControlPoint::s_class);
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), kRegionMethods,
|
||||
static_cast<int>(sizeof(kRegionMethods) / sizeof(kRegionMethods[0])),
|
||||
kRegionDocs, static_cast<int>(sizeof(kRegionDocs) / sizeof(kRegionDocs[0])));
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), kRegionMethods, kRegionDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -665,10 +665,15 @@ void RecordTable(const char *table, const char *name, const Function *doc) {
|
||||
Record(KIND_TABLE, table, name, nullptr, doc);
|
||||
}
|
||||
|
||||
void RecordFrameMethods(const void *registry, const Game::Lua::FrameMethodEntry *table,
|
||||
int count, const Method *docs, int docCount) {
|
||||
void RecordFrameMethods(const void *registry,
|
||||
Game::ArrayRef<Game::Lua::FrameMethodEntry> methods,
|
||||
Game::Doc::MethodList docList) {
|
||||
if (g_pass == ENV_NONE)
|
||||
return;
|
||||
const Game::Lua::FrameMethodEntry *table = methods.data;
|
||||
const int count = methods.count;
|
||||
const Method *docs = docList.data;
|
||||
const int docCount = docList.count;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const Function *doc = nullptr;
|
||||
for (int d = 0; d < docCount; ++d) {
|
||||
@@ -720,14 +725,14 @@ void RecordValueTable(Kind kind, const char *parent, const char *sub,
|
||||
|
||||
} // namespace
|
||||
|
||||
void RecordEnum(const char *parent, const char *sub, const Game::Lua::EnumIntegerEntry *entries,
|
||||
int count, const char *system) {
|
||||
RecordValueTable(KIND_ENUM, parent, sub, entries, count, system);
|
||||
void RecordEnum(const char *parent, const char *sub,
|
||||
Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries, const char *system) {
|
||||
RecordValueTable(KIND_ENUM, parent, sub, entries.data, entries.count, system);
|
||||
}
|
||||
|
||||
void RecordConstants(const char *sub, const Game::Lua::EnumIntegerEntry *entries, int count,
|
||||
void RecordConstants(const char *sub, Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries,
|
||||
const char *system) {
|
||||
RecordValueTable(KIND_CONSTANTS, "Constants", sub, entries, count, system);
|
||||
RecordValueTable(KIND_CONSTANTS, "Constants", sub, entries.data, entries.count, system);
|
||||
}
|
||||
|
||||
} // namespace Api::Documentation
|
||||
|
||||
@@ -32,14 +32,15 @@ void EndPass();
|
||||
|
||||
void RecordGlobal(const char *name, const Game::Doc::Function *doc);
|
||||
void RecordTable(const char *table, const char *name, const Game::Doc::Function *doc);
|
||||
void RecordFrameMethods(const void *registry, const Game::Lua::FrameMethodEntry *table,
|
||||
int count, const Game::Doc::Method *docs, int docCount);
|
||||
void RecordFrameMethods(const void *registry,
|
||||
Game::ArrayRef<Game::Lua::FrameMethodEntry> methods,
|
||||
Game::Doc::MethodList docs);
|
||||
// Names the widget system of a method hash this DLL owns (a frame class it
|
||||
// adds), alongside the engine registries the exporter already knows.
|
||||
void NameWidgetSystem(const void *registry, const char *system);
|
||||
void RecordEnum(const char *parent, const char *sub, const Game::Lua::EnumIntegerEntry *entries,
|
||||
int count, const char *system);
|
||||
void RecordConstants(const char *sub, const Game::Lua::EnumIntegerEntry *entries, int count,
|
||||
void RecordEnum(const char *parent, const char *sub,
|
||||
Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries, const char *system);
|
||||
void RecordConstants(const char *sub, Game::ArrayRef<Game::Lua::EnumIntegerEntry> entries,
|
||||
const char *system);
|
||||
|
||||
} // namespace Api::Documentation
|
||||
|
||||
+2
-9
@@ -1023,16 +1023,9 @@ CompressState *FindOrClaimCompressState(uint64_t targetGuid,
|
||||
// would compress it, this gate excludes it exactly as the server's does.
|
||||
// (Drain Soul passes via its effect-2 periodic damage: aura=[86,3,0].)
|
||||
bool HasPeriodicDamageOrLeech(const uint8_t *rec) {
|
||||
if (rec == nullptr)
|
||||
return false;
|
||||
constexpr int32_t kPeriodicDamage = 3, kPeriodicLeech = 53;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const int32_t aura = *reinterpret_cast<const int32_t *>(
|
||||
rec + Offsets::OFF_SPELL_RECORD_EFFECT_APPLY_AURA_NAME + i * 4);
|
||||
if (aura == kPeriodicDamage || aura == kPeriodicLeech)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return Spell::Lookup::HasAuraEffect(rec, kPeriodicDamage) ||
|
||||
Spell::Lookup::HasAuraEffect(rec, kPeriodicLeech);
|
||||
}
|
||||
|
||||
// Compression pairs by CASTER, mirroring the server (each warlock's Dark
|
||||
|
||||
@@ -457,12 +457,10 @@ static void RegisterLuaFunctions() {
|
||||
&Script_GetAutoCompleteResults, &kGetAutoCompleteResults);
|
||||
Game::Lua::RegisterTableFunction("C_AutoComplete", "IsRecognizedName", &Script_IsRecognizedName,
|
||||
&kIsRecognizedName);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "AutoCompleteEntryFlag", kEntryFlagEntries,
|
||||
sizeof(kEntryFlagEntries) / sizeof(kEntryFlagEntries[0]),
|
||||
"AutoComplete");
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "AutoCompletePriority", kPriorityEntries,
|
||||
sizeof(kPriorityEntries) / sizeof(kPriorityEntries[0]),
|
||||
"AutoComplete");
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "AutoCompleteEntryFlag", kEntryFlagEntries, "AutoComplete");
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "AutoCompletePriority", kPriorityEntries, "AutoComplete");
|
||||
}
|
||||
|
||||
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// `debuglocals([level [, skipFunctionsAndUserdata]])` — the locals and then
|
||||
// the upvalues of a call level as text, one `name=value` line each, for an
|
||||
// error report. 4.3.4's Script_debuglocals and the FrameScript_PackVariable
|
||||
// it formats with, in retail's spacing (probed: `n=7`, `s="hi"`,
|
||||
// `t=<table>{`, ` k=1`, `}`; 4.3.4 writes `n = 7` and `t = <table> {`):
|
||||
// - Only while an error handler runs; otherwise it returns nothing. 4.3.4
|
||||
// tests a flag its error function sets around the handler call. 1.12's
|
||||
// error function sets none, but while a handler runs that function's
|
||||
// level is on the stack (ErrorCallstack::ErrorFunctionLevel).
|
||||
// - `level` counts as lua_getstack and debugstack do: 1 is the caller.
|
||||
// Less than 1 is 1.
|
||||
// - A table prints as `name=<table>{`, or with the frame's name for a
|
||||
// frame (t[0], as FrameScript_GetObject reads it), then its fields one
|
||||
// space in at the top level only, then `}`. A function prints where it
|
||||
// is defined. The text stops at 4095 bytes, each line at 1023.
|
||||
// Retail's second argument, which its Blizzard_ScriptErrors passes as true,
|
||||
// leaves out the function and userdata values, at the top level and in
|
||||
// tables.
|
||||
//
|
||||
// A number key in int range prints truncated with %d, as 4.3.4's
|
||||
// IsIntegerNumber tests only the range: 2.5 prints as 2. Two departures from
|
||||
// 4.3.4, both where it would fail inside the handler: a number key outside
|
||||
// that range is formatted from a copy, not converted in place (that breaks
|
||||
// lua_next), and a key that is not a number or string prints as its type in
|
||||
// angle brackets (4.3.4 formats a null string).
|
||||
//
|
||||
// Retail also lists `(*temporary)` lines: 5.1's lua_getlocal names each stack
|
||||
// slot past a level's locals so, a C level's arguments included (an
|
||||
// `error("x")` report shows `(*temporary)="x"`). 1.12's lua_getlocal
|
||||
// (0x6FBB20) is 5.0's, which names declared locals only. Those slots are
|
||||
// the compiler's registers, which 5.0 and 5.1 allocate differently, so they
|
||||
// are left out rather than imitated.
|
||||
//
|
||||
// Registered on both Lua states, like the base-library helpers.
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "baselib/ErrorCallstack.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace BaseLib::DebugLocals {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace Lua = Game::Lua;
|
||||
|
||||
using lua_getlocal_t = const char *(__fastcall *)(void *L, const void *ar, int n);
|
||||
using lua_getupvalue_t = const char *(__fastcall *)(void *L, int funcindex, int n);
|
||||
|
||||
const auto GetLocal = reinterpret_cast<lua_getlocal_t>(Offsets::LUA_GET_LOCAL);
|
||||
const auto GetUpvalue = reinterpret_cast<lua_getupvalue_t>(Offsets::LUA_GET_UPVALUE);
|
||||
|
||||
constexpr size_t kTextMax = 4095; // 4.3.4's 4096-byte buffer, less its NUL
|
||||
constexpr size_t kLineSize = 1024;
|
||||
|
||||
struct Packer {
|
||||
void *L;
|
||||
bool skipFunctionsAndUserdata;
|
||||
std::string text;
|
||||
|
||||
void Line(const char *fmt, const char *indent, const char *name, const char *value = "",
|
||||
int number = 0) {
|
||||
char line[kLineSize];
|
||||
std::snprintf(line, sizeof line, fmt, indent, name, value, number);
|
||||
const size_t room = kTextMax - text.size();
|
||||
text.append(line, std::min(room, std::strlen(line)));
|
||||
}
|
||||
|
||||
// The value on the stack top, as 4.3.4's FrameScript_PackVariable.
|
||||
void Pack(const char *name, int depth) {
|
||||
const char *indent = depth > 0 ? " " : "";
|
||||
switch (Lua::Type(L, -1)) {
|
||||
case Lua::TYPE_NIL:
|
||||
Line("%s%s=nil\n", indent, name);
|
||||
break;
|
||||
case Lua::TYPE_BOOLEAN:
|
||||
Line("%s%s=%s\n", indent, name, Lua::ToBoolean(L, -1) ? "true" : "false");
|
||||
break;
|
||||
case Lua::TYPE_LIGHTUSERDATA:
|
||||
case Lua::TYPE_USERDATA:
|
||||
if (!skipFunctionsAndUserdata)
|
||||
Line("%s%s=<userdata>\n", indent, name);
|
||||
break;
|
||||
case Lua::TYPE_NUMBER:
|
||||
Line("%s%s=%s\n", indent, name, Lua::ToString(L, -1)); // a copy: safe to convert
|
||||
break;
|
||||
case Lua::TYPE_STRING:
|
||||
Line("%s%s=\"%s\"\n", indent, name, Lua::ToString(L, -1));
|
||||
break;
|
||||
case Lua::TYPE_TABLE:
|
||||
PackTable(name, depth, indent);
|
||||
break;
|
||||
case Lua::TYPE_FUNCTION:
|
||||
if (!skipFunctionsAndUserdata)
|
||||
PackFunction(name, indent);
|
||||
break;
|
||||
default:
|
||||
Line("%s%s=<%s>\n", indent, name, Lua::TypeName(L, Lua::Type(L, -1)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PackTable(const char *name, int depth, const char *indent) {
|
||||
void *object = Lua::ResolveObject(L, -1);
|
||||
Line("%s%s=%s{\n", indent, name, object ? Lua::ObjectDisplayName(object) : "<table>");
|
||||
if (depth == 0) {
|
||||
Lua::PushNil(L);
|
||||
while (Lua::Next(L, -2) != 0) {
|
||||
char key[32];
|
||||
const char *keyName = key;
|
||||
const int keyType = Lua::Type(L, -2);
|
||||
const double number = keyType == Lua::TYPE_NUMBER ? Lua::ToNumber(L, -2) : 0;
|
||||
if (keyType == Lua::TYPE_NUMBER && std::isfinite(number) && number >= -2147483648.0 &&
|
||||
number <= 2147483647.0) {
|
||||
std::snprintf(key, sizeof key, "%d", static_cast<int>(number));
|
||||
} else if (keyType == Lua::TYPE_STRING) {
|
||||
keyName = Lua::ToString(L, -2);
|
||||
} else if (keyType == Lua::TYPE_NUMBER) {
|
||||
Lua::PushValue(L, -2);
|
||||
std::snprintf(key, sizeof key, "%s", Lua::ToString(L, -1));
|
||||
Lua::SetTop(L, -2);
|
||||
} else {
|
||||
std::snprintf(key, sizeof key, "<%s>", Lua::TypeName(L, keyType));
|
||||
}
|
||||
Pack(keyName, 1);
|
||||
Lua::SetTop(L, -2); // the value; the key stays for lua_next
|
||||
}
|
||||
}
|
||||
Line("%s}\n", indent, "");
|
||||
}
|
||||
|
||||
void PackFunction(const char *name, const char *indent) {
|
||||
alignas(4) uint8_t ar[Offsets::SIZEOF_LUA_DEBUG];
|
||||
Lua::PushValue(L, -1);
|
||||
Lua::GetInfo(L, ">nS", ar); // pops the copy
|
||||
const auto field = [&ar](uintptr_t offset) { return Game::Read<const char *>(ar, offset); };
|
||||
const char *source = field(Offsets::OFF_LUA_DEBUG_SOURCE);
|
||||
const int line = Game::Read<int>(ar, Offsets::OFF_LUA_DEBUG_LINEDEFINED);
|
||||
if (const char *fn = field(Offsets::OFF_LUA_DEBUG_NAME)) {
|
||||
char named[kLineSize];
|
||||
std::snprintf(named, sizeof named, "%s() defined %s", fn, source ? source : "?");
|
||||
Line("%s%s=%s:%d\n", indent, name, named, line);
|
||||
} else {
|
||||
Line("%s%s=<function> defined %s:%d\n", indent, name, source ? source : "?", line);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int __fastcall Script_debuglocals(void *L) {
|
||||
if (ErrorCallstack::ErrorFunctionLevel(L) == 0)
|
||||
return 0;
|
||||
int level = static_cast<int>(Lua::ToNumber(L, 1));
|
||||
if (level <= 0)
|
||||
level = 1;
|
||||
Packer packer{L, Lua::ToBoolean(L, 2) != 0, {}};
|
||||
|
||||
alignas(4) uint8_t ar[Offsets::SIZEOF_LUA_DEBUG];
|
||||
if (Lua::GetStack(L, level, ar)) {
|
||||
const char *name;
|
||||
for (int i = 1; (name = GetLocal(L, ar, i)) != nullptr; i++) {
|
||||
packer.Pack(name, 0);
|
||||
Lua::SetTop(L, -2);
|
||||
}
|
||||
Lua::GetInfo(L, "f", ar); // the level's function
|
||||
for (int i = 1; (name = GetUpvalue(L, -1, i)) != nullptr; i++) {
|
||||
packer.Pack(name, 0);
|
||||
Lua::SetTop(L, -2);
|
||||
}
|
||||
Lua::SetTop(L, -2);
|
||||
}
|
||||
Lua::PushLString(L, packer.text.data(), static_cast<unsigned int>(packer.text.size()));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const Game::Doc::Field kDebuglocalsArgs[] = {
|
||||
Game::Doc::Opt("level", "number", "1", "The call level, as debugstack counts it."),
|
||||
Game::Doc::Opt("skipFunctionsAndUserdata", "boolean", "false",
|
||||
"Leaves out function and userdata values."),
|
||||
};
|
||||
const Game::Doc::Field kDebuglocalsRets[] = {
|
||||
Game::Doc::Opt("locals", "string", nullptr,
|
||||
"One `name=value` line for each local and upvalue. Nothing outside an "
|
||||
"error handler."),
|
||||
};
|
||||
const Game::Doc::Function kDebuglocals{
|
||||
"The local variables and upvalues of a call level as text, for an error report.",
|
||||
kDebuglocalsArgs, kDebuglocalsRets, "LuaGlobals"};
|
||||
|
||||
void RegisterInGame() {
|
||||
Game::Lua::RegisterGlobalFunction("debuglocals", &Script_debuglocals, &kDebuglocals);
|
||||
}
|
||||
|
||||
void RegisterGlue() {
|
||||
Game::Lua::RegisterGlueFunction("debuglocals", &Script_debuglocals, &kDebuglocals);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterInGame};
|
||||
const Game::GlueModuleAutoRegister _autoregGlue{&RegisterGlue};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace BaseLib::DebugLocals
|
||||
@@ -0,0 +1,102 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// `debugstack([thread,] [start [, count1 [, count2]]])` — the stack of a
|
||||
// coroutine, as 4.3.4's Script_debugstack takes it: with a thread first,
|
||||
// the levels are that thread's and `start` defaults to 0 (the thread's
|
||||
// innermost level) instead of 1.
|
||||
//
|
||||
// 1.12's debugstack (FUN_SCRIPT_DEBUGSTACK) has no thread argument, and a
|
||||
// thread falls through its argument checks so that it returns the thread
|
||||
// itself: `msg .. debugstack(co)` raises "attempt to concatenate a thread
|
||||
// value" in the coroutine error paths of WeakAuras, Questie's ThreadLib,
|
||||
// MRT and Zygor.
|
||||
//
|
||||
// The engine's function still does the work, so its text and its head and
|
||||
// tail windows stay exactly as they are. It reads levels through six calls
|
||||
// (five lua_getstack, one lua_getinfo), and those calls are redirected to
|
||||
// read from the state Script_debugstack names for the call: the thread, or
|
||||
// the calling state when there is none. Script_debugstack replaces the
|
||||
// global and is the engine function's only way in, so the state is set for
|
||||
// every call; an error part way cannot leave a thread behind for the next.
|
||||
//
|
||||
// Registered on both Lua states, like the base-library helpers.
|
||||
|
||||
#include "Common.h"
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
|
||||
namespace BaseLib::DebugStack {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace Lua = Game::Lua;
|
||||
|
||||
// The state whose levels the engine's debugstack reads, or null for its own.
|
||||
void *g_levelsOf = nullptr;
|
||||
|
||||
int __fastcall GetStack(void *L, int level, void *ar) {
|
||||
return Lua::GetStack(g_levelsOf ? g_levelsOf : L, level, ar);
|
||||
}
|
||||
|
||||
int __fastcall GetInfo(void *L, const char *what, void *ar) {
|
||||
return Lua::GetInfo(g_levelsOf ? g_levelsOf : L, what, ar);
|
||||
}
|
||||
|
||||
int __fastcall Script_debugstack(void *L) {
|
||||
const auto engineDebugStack =
|
||||
reinterpret_cast<Lua::CFunction>(Offsets::FUN_SCRIPT_DEBUGSTACK);
|
||||
g_levelsOf = nullptr;
|
||||
if (Lua::Type(L, 1) == Lua::TYPE_THREAD) {
|
||||
g_levelsOf = Lua::ToThread(L, 1);
|
||||
Lua::Remove(L, 1);
|
||||
if (!Lua::IsNumber(L, 1)) { // start defaults to 0 for a thread
|
||||
Lua::PushNumber(L, 0);
|
||||
Lua::Insert(L, 1);
|
||||
}
|
||||
}
|
||||
const int results = engineDebugStack(L);
|
||||
g_levelsOf = nullptr;
|
||||
return results;
|
||||
}
|
||||
|
||||
void Apply() {
|
||||
const auto getStack = reinterpret_cast<const void *>(&GetStack);
|
||||
for (const uintptr_t site :
|
||||
{Offsets::CALL_DEBUGSTACK_GETSTACK_FIRST, Offsets::CALL_DEBUGSTACK_GETSTACK_TAIL_PROBE,
|
||||
Offsets::CALL_DEBUGSTACK_GETSTACK_TAIL_START, Offsets::CALL_DEBUGSTACK_GETSTACK_TAIL_SKIP,
|
||||
Offsets::CALL_DEBUGSTACK_GETSTACK_NEXT})
|
||||
Common::RedirectCall(site, Offsets::LUA_GET_STACK, getStack);
|
||||
Common::RedirectCall(Offsets::CALL_DEBUGSTACK_GETINFO, Offsets::LUA_GET_INFO,
|
||||
reinterpret_cast<const void *>(&GetInfo));
|
||||
}
|
||||
|
||||
void RegisterInGame() {
|
||||
Apply();
|
||||
Lua::RegisterGlobalFunction("debugstack", &Script_debugstack);
|
||||
}
|
||||
|
||||
void RegisterGlue() {
|
||||
Apply();
|
||||
Lua::RegisterGlueFunction("debugstack", &Script_debugstack);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterInGame};
|
||||
const Game::GlueModuleAutoRegister _autoregGlue{&RegisterGlue};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace BaseLib::DebugStack
|
||||
@@ -40,12 +40,26 @@
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "baselib/ErrorCallstack.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace BaseLib::ErrorCallstack {
|
||||
|
||||
int ErrorFunctionLevel(void *L) {
|
||||
const auto errorFunction = reinterpret_cast<Game::Lua::CFunction>(Offsets::FUN_FRAMESCRIPT_CALL_ERROR_HANDLER);
|
||||
alignas(4) uint8_t ar[Offsets::SIZEOF_LUA_DEBUG];
|
||||
for (int level = 1; Game::Lua::GetStack(L, level, ar); ++level) {
|
||||
Game::Lua::GetInfo(L, "f", ar); // the level's function; nil for a lost tail call
|
||||
const bool found = Game::Lua::ToCFunction(L, -1) == errorFunction;
|
||||
Game::Lua::SetTop(L, -2);
|
||||
if (found)
|
||||
return level;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::optional<int> g_setHeight;
|
||||
@@ -59,21 +73,6 @@ int Height(void *L) {
|
||||
return height;
|
||||
}
|
||||
|
||||
// The innermost level whose function is the engine's error function, or 0
|
||||
// when no error is being reported.
|
||||
int ErrorFunctionLevel(void *L) {
|
||||
const auto errorFunction = reinterpret_cast<Game::Lua::CFunction>(Offsets::FUN_FRAMESCRIPT_CALL_ERROR_HANDLER);
|
||||
alignas(4) uint8_t ar[Offsets::SIZEOF_LUA_DEBUG];
|
||||
for (int level = 1; Game::Lua::GetStack(L, level, ar); ++level) {
|
||||
Game::Lua::GetInfo(L, "f", ar); // the level's function; nil for a lost tail call
|
||||
const bool found = Game::Lua::ToCFunction(L, -1) == errorFunction;
|
||||
Game::Lua::SetTop(L, -2);
|
||||
if (found)
|
||||
return level;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __fastcall Script_GetCallstackHeight(void *L) {
|
||||
Game::Lua::PushNumber(L, Height(L));
|
||||
return 1;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace BaseLib::ErrorCallstack {
|
||||
|
||||
// The innermost call level whose function is the engine's error function
|
||||
// (FUN_FRAMESCRIPT_CALL_ERROR_HANDLER), or 0 when no error is being
|
||||
// reported. Nonzero exactly while an error handler runs.
|
||||
int ErrorFunctionLevel(void *L);
|
||||
|
||||
} // namespace BaseLib::ErrorCallstack
|
||||
+2
-15
@@ -47,6 +47,7 @@
|
||||
// section's header follows.
|
||||
|
||||
#include "Inject.h"
|
||||
#include "Common.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
#include "embed/Resource.h"
|
||||
@@ -212,20 +213,6 @@ const char *FindSplicePosition(const char *content, size_t size,
|
||||
return FindOutsideComment(content, end, "</Bindings>");
|
||||
}
|
||||
|
||||
char NormalizeChar(char c) {
|
||||
if (c == '/') return '\\';
|
||||
if (c >= 'A' && c <= 'Z') return static_cast<char>(c + 32);
|
||||
return c;
|
||||
}
|
||||
|
||||
bool PathEqualsCI(const char *a, const char *b) {
|
||||
while (*a != '\0' && *b != '\0') {
|
||||
if (NormalizeChar(*a) != NormalizeChar(*b)) return false;
|
||||
++a; ++b;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
using SMemAlloc_t = void *(__stdcall *)(size_t size, const char *file, int line, int flags);
|
||||
using SMemFree_t = void(__stdcall *)(void *buf, const char *file, int line, int flags);
|
||||
|
||||
@@ -233,7 +220,7 @@ using SMemFree_t = void(__stdcall *)(void *buf, const char *file, int line, int
|
||||
|
||||
bool TryHandle(int unused, const char *path, void **outBuf, size_t *outSize,
|
||||
size_t extraBytes, int flag1, int flag2, FileReadFn original) {
|
||||
if (path == nullptr || !PathEqualsCI(path, kFrameXMLBindingsPath))
|
||||
if (path == nullptr || !Common::SamePath(path, kFrameXMLBindingsPath))
|
||||
return false;
|
||||
|
||||
// Pull the real file through the original reader with the caller's
|
||||
|
||||
+21
-9
@@ -72,18 +72,25 @@ struct State {
|
||||
uint32_t endMs = 0;
|
||||
bool active = false; // at least one reset has happened this session
|
||||
bool dirty = false; // needs a PLAYER_SWING fire on the next WorldTick
|
||||
bool held = false; // the latest reset was recorded but not reported yet
|
||||
};
|
||||
State g_swing[3];
|
||||
|
||||
// `notify` false records the server's new timer without firing PLAYER_SWING:
|
||||
// the reset is real, but no swing is coming yet (see OnSpellGo).
|
||||
// the reset is real, but no swing is coming yet (see OnSpellGo). It stays
|
||||
// `held` until the attack start reports it or a later reported reset replaces it.
|
||||
void MarkReset(SwingType type, uint32_t durationMs, bool notify = true) {
|
||||
if (durationMs == 0)
|
||||
return; // no weapon for this hand right now -- nothing to report
|
||||
State &s = g_swing[type];
|
||||
s.endMs = NowMs() + durationMs;
|
||||
s.active = true;
|
||||
s.dirty = s.dirty || notify;
|
||||
if (notify) {
|
||||
s.dirty = true;
|
||||
s.held = false;
|
||||
} else {
|
||||
s.held = true;
|
||||
}
|
||||
}
|
||||
|
||||
using HasAttackTarget_t = int(__fastcall *)(const void *unit);
|
||||
@@ -410,11 +417,17 @@ void OnPacket(uint32_t opcode, CDataStore *packet) {
|
||||
auto *playerObj = const_cast<uint8_t *>(Unit::Identity::PlayerObject());
|
||||
if (InMeleeRange(playerObj, victimObj)) {
|
||||
MarkReset(OFF_HAND, AttackTimeMs(OFF_HAND));
|
||||
// A cast reset made before the attack still holds the
|
||||
// main-hand swing back; report it now that one is coming.
|
||||
// A cast reset made before the attack is still held back;
|
||||
// report it now that a swing is coming. Only a held reset:
|
||||
// a target switch mid-attack also lands here, and there the
|
||||
// main-hand swing was never reset -- re-reporting its
|
||||
// remaining time would read as a fresh, short swing.
|
||||
State &main = g_swing[MAIN_HAND];
|
||||
if (main.active && Time::Clock::Remaining(NowMs(), main.endMs) > 0)
|
||||
main.dirty = true;
|
||||
if (main.held) {
|
||||
main.held = false;
|
||||
if (Time::Clock::Remaining(NowMs(), main.endMs) > 0)
|
||||
main.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -477,6 +490,7 @@ void OnWorldTick() {
|
||||
if (!s.dirty)
|
||||
continue;
|
||||
s.dirty = false;
|
||||
s.held = false;
|
||||
if (slot < 0)
|
||||
continue;
|
||||
const uint32_t remaining = Time::Clock::Remaining(now, s.endMs);
|
||||
@@ -497,9 +511,7 @@ const Game::Lua::EnumIntegerEntry kSwingTypeEntries[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "PlayerSwingType", kSwingTypeEntries,
|
||||
sizeof(kSwingTypeEntries) / sizeof(kSwingTypeEntries[0]), "SwingTimer");
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "PlayerSwingType", kSwingTypeEntries, "SwingTimer");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
+2
-14
@@ -45,6 +45,7 @@
|
||||
#include "time/Clock.h"
|
||||
#include "unit/Focus.h"
|
||||
#include "unit/Identity.h"
|
||||
#include "unit/Power.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
@@ -617,17 +618,6 @@ uint32_t SpellSchoolMask(const uint8_t *spell) {
|
||||
: 0;
|
||||
}
|
||||
|
||||
bool HasAuraEffect(const uint8_t *spell, int auraName) {
|
||||
if (spell == nullptr)
|
||||
return false;
|
||||
for (int i = 0; i < Offsets::SPELL_RECORD_EFFECT_COUNT; ++i) {
|
||||
if (Game::Read<int>(spell, Offsets::OFF_SPELL_RECORD_EFFECT_APPLY_AURA_NAME + i * 4) ==
|
||||
auraName)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Entry Begin(uint64_t source, uint64_t dest, int event, const uint8_t *spell) {
|
||||
Entry e;
|
||||
e.event = event;
|
||||
@@ -684,9 +674,7 @@ void SetItem(Entry &e, int itemId) {
|
||||
}
|
||||
|
||||
int DisplayAmount(int amount, int powerType) {
|
||||
using Divisor_t = int(__fastcall *)(int powerType);
|
||||
const int divisor = reinterpret_cast<Divisor_t>(
|
||||
static_cast<uintptr_t>(Offsets::FUN_POWER_DISPLAY_DIVISOR))(powerType);
|
||||
const int divisor = static_cast<int>(Unit::Power::DisplayDivisor(powerType));
|
||||
return divisor != 0 ? amount / divisor : amount;
|
||||
}
|
||||
|
||||
|
||||
@@ -167,10 +167,6 @@ uint32_t SchoolMask(uint32_t school);
|
||||
// The spell's school as a mask (4.3.4's SpellRec m_schoolMask), 0 for nullptr.
|
||||
uint32_t SpellSchoolMask(const uint8_t *spell);
|
||||
|
||||
// Whether one of the spell's three effects applies the aura `auraName`
|
||||
// (EffectApplyAuraName).
|
||||
bool HasAuraEffect(const uint8_t *spell, int auraName);
|
||||
|
||||
// GetCombatLogEntry: an entry for `event` from `source` to `dest` (either may
|
||||
// be 0), with the spell prefix when `spell` is a record.
|
||||
Entry Begin(uint64_t source, uint64_t dest, int event, const uint8_t *spell);
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#include "aura/Data.h"
|
||||
#include "net/PacketDispatch.h"
|
||||
#include "object/Resolve.h"
|
||||
#include "spell/Lookup.h"
|
||||
#include "time/Clock.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -501,7 +502,7 @@ const uint8_t *ShieldSpell(uint64_t owner, uint32_t school) {
|
||||
const uint8_t *spell = SpellRecord(static_cast<int>(spellId));
|
||||
if (spell == nullptr ||
|
||||
Game::Read<uint32_t>(spell, Offsets::OFF_SPELL_RECORD_SCHOOL) != school ||
|
||||
!HasAuraEffect(spell, AURA_DAMAGE_SHIELD))
|
||||
!Spell::Lookup::HasAuraEffect(spell, AURA_DAMAGE_SHIELD))
|
||||
continue;
|
||||
if (found != nullptr && found != spell)
|
||||
return nullptr;
|
||||
|
||||
@@ -145,12 +145,8 @@ void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterGlobalFunction("ConsoleGetAllCommands", &Script_GetAllCommands);
|
||||
Game::Lua::RegisterGlobalFunction("ConsolePrintAllMatchingCommands",
|
||||
&Script_ConsolePrintAllMatchingCommands);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCommandType", kCommandTypeEntries,
|
||||
sizeof(kCommandTypeEntries) /
|
||||
sizeof(kCommandTypeEntries[0]));
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCategory", kCategoryEntries,
|
||||
sizeof(kCategoryEntries) /
|
||||
sizeof(kCategoryEntries[0]));
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCommandType", kCommandTypeEntries);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "ConsoleCategory", kCategoryEntries);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -42,8 +42,7 @@ const Game::Lua::EnumIntegerEntry kEntries[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "BagIndex", kEntries,
|
||||
sizeof(kEntries) / sizeof(kEntries[0]), "BagIndexConstants");
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "BagIndex", kEntries, "BagIndexConstants");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
+31
-13
@@ -55,6 +55,7 @@
|
||||
#include "item/Location.h"
|
||||
#include "item/Record.h"
|
||||
#include "item/TypeInfo.h"
|
||||
#include "localization/Locale.h"
|
||||
#include "unit/Identity.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -86,12 +87,29 @@ char16_t ToLower(char16_t c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
// Appends the lowercased `c` with 4.3.4's accent folding for `locale`
|
||||
// (frFR 2, deDE 3, esES/esMX 6/7, ruRU 8); æ, œ and ß become two letters.
|
||||
// Returns the new length.
|
||||
size_t AppendFolded(int locale, char16_t c, char16_t *out, size_t n) {
|
||||
switch (locale) {
|
||||
case 2: // frFR
|
||||
// 4.3.4's accent-folding sets. 4.3.4 picks one by its locale index (frFR 2,
|
||||
// deDE 3, esES/esMX 6/7, ruRU 8); 1.12's index has other slots, so the set
|
||||
// is picked by locale name.
|
||||
enum class Folding { None, French, German, Spanish, Russian };
|
||||
|
||||
Folding ClientFolding() {
|
||||
using Localization::Locale::Is;
|
||||
if (Is("frFR"))
|
||||
return Folding::French;
|
||||
if (Is("deDE"))
|
||||
return Folding::German;
|
||||
if (Is("esES") || Is("esMX"))
|
||||
return Folding::Spanish;
|
||||
if (Is("ruRU"))
|
||||
return Folding::Russian;
|
||||
return Folding::None;
|
||||
}
|
||||
|
||||
// Appends the lowercased `c` with `folding`'s accent folding; æ, œ and ß
|
||||
// become two letters. Returns the new length.
|
||||
size_t AppendFolded(Folding folding, char16_t c, char16_t *out, size_t n) {
|
||||
switch (folding) {
|
||||
case Folding::French:
|
||||
switch (c) {
|
||||
case 0xE0: case 0xE2: case 0xE4: c = u'a'; break;
|
||||
case 0xE6: out[n++] = u'a'; c = u'e'; break;
|
||||
@@ -103,14 +121,13 @@ size_t AppendFolded(int locale, char16_t c, char16_t *out, size_t n) {
|
||||
case 0xF9: case 0xFA: case 0xFB: case 0xFC: c = u'u'; break;
|
||||
}
|
||||
break;
|
||||
case 3: // deDE
|
||||
case Folding::German:
|
||||
if (c == 0xDF) {
|
||||
out[n++] = u's';
|
||||
c = u's';
|
||||
}
|
||||
break;
|
||||
case 6: // esES
|
||||
case 7: // esMX
|
||||
case Folding::Spanish:
|
||||
switch (c) {
|
||||
case 0xE1: c = u'a'; break;
|
||||
case 0xE9: c = u'e'; break;
|
||||
@@ -120,10 +137,12 @@ size_t AppendFolded(int locale, char16_t c, char16_t *out, size_t n) {
|
||||
case 0xF1: c = u'n'; break;
|
||||
}
|
||||
break;
|
||||
case 8: // ruRU
|
||||
case Folding::Russian:
|
||||
if (c == 0x451)
|
||||
c = 0x435;
|
||||
break;
|
||||
case Folding::None:
|
||||
break;
|
||||
}
|
||||
out[n++] = c;
|
||||
return n;
|
||||
@@ -141,11 +160,10 @@ size_t Fold(const char *text, char16_t (&out)[kFoldCap]) {
|
||||
// One unit short of the buffer: a full conversion stores no terminator.
|
||||
reinterpret_cast<Convert_t>(Offsets::FUN_SUNI_CONVERT_UTF8_TO_16)(
|
||||
wide, kFoldCap - 1, text, 0x7FFFFFFF, nullptr, nullptr);
|
||||
const int locale =
|
||||
*reinterpret_cast<const int *>(static_cast<uintptr_t>(Offsets::VAR_LOCALE_INDEX));
|
||||
const Folding folding = ClientFolding();
|
||||
size_t n = 0;
|
||||
for (size_t i = 0; wide[i] != 0 && n + 2 < kFoldCap; ++i)
|
||||
n = AppendFolded(locale, ToLower(wide[i]), out, n);
|
||||
n = AppendFolded(folding, ToLower(wide[i]), out, n);
|
||||
out[n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -131,10 +131,7 @@ int __fastcall Script_RequestLoadCreatureByID(void *L) {
|
||||
Game::Lua::PushBool(L, false);
|
||||
return 1;
|
||||
}
|
||||
auto *cache = reinterpret_cast<void *>(Offsets::VAR_CREATURE_CACHE);
|
||||
const bool accepted =
|
||||
Cache::QueryLoad::RequestLoad(cache, static_cast<uint32_t>(creatureID));
|
||||
Game::Lua::PushBool(L, accepted);
|
||||
Game::Lua::PushBool(L, RequestLoad(static_cast<uint32_t>(creatureID)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -381,4 +378,9 @@ const uint8_t *CacheRecord(uint32_t creatureID) {
|
||||
return creatureID != 0 ? PeekCreature(creatureID) : nullptr;
|
||||
}
|
||||
|
||||
bool RequestLoad(uint32_t creatureID) {
|
||||
return Cache::QueryLoad::RequestLoad(reinterpret_cast<void *>(Offsets::VAR_CREATURE_CACHE),
|
||||
creatureID);
|
||||
}
|
||||
|
||||
} // namespace Creature::Info
|
||||
|
||||
@@ -24,6 +24,12 @@ namespace Creature::Info {
|
||||
// engine's SetDisplay takes, and `PlayerModel:SetCreature` passes it there.
|
||||
const uint8_t *CacheRecord(uint32_t creatureID);
|
||||
|
||||
// Starts loading `creatureID` into the creature cache; fires
|
||||
// `CREATURE_DATA_LOAD_RESULT(creatureID, success)` when it lands (at once
|
||||
// when already cached). False only when the load could not be tracked.
|
||||
// Backs `C_CreatureInfo.RequestLoadCreatureByID`.
|
||||
bool RequestLoad(uint32_t creatureID);
|
||||
|
||||
// The faction group a player race belongs to, resolved the way
|
||||
// `Script_UnitFactionGroup` does it for a player whose object isn't in the
|
||||
// world (its name-cache branch, which reads the race): ChrRaces -> FactionTemplate
|
||||
|
||||
@@ -27,10 +27,8 @@ const Game::Lua::EnumIntegerEntry kMoneyFormattingConstants[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterConstants("MoneyFormattingConstants", kMoneyFormattingConstants,
|
||||
sizeof(kMoneyFormattingConstants) /
|
||||
sizeof(kMoneyFormattingConstants[0]),
|
||||
"MoneyConstants");
|
||||
Game::Lua::RegisterConstants(
|
||||
"MoneyFormattingConstants", kMoneyFormattingConstants, "MoneyConstants");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -119,9 +119,7 @@ void OnTick() {
|
||||
const Tick::WorldTick::AutoSubscribe _tick{&OnTick};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "UICursorType", kCursorTypeEntries,
|
||||
sizeof(kCursorTypeEntries) / sizeof(kCursorTypeEntries[0]));
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "UICursorType", kCursorTypeEntries);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// The cursor modes later clients added, shown on 1.12's cursor, and the
|
||||
// `expandedCursors` cvar that turns them on (0 by default: they change what
|
||||
// 1.12 shows).
|
||||
//
|
||||
// The engine's cursor is a table of 42 images of 32x32, one per mode;
|
||||
// SendCursorImage copies slot VAR_CURSOR_MODE to the device. SetCursorMode
|
||||
// stores the mode it is given and sends, so a mode past 41 must never reach
|
||||
// it. Slot 41 is how the engine shows an image outside the table:
|
||||
// SetCustomCursor loads a named BLP into it, stores mode 41, sends it and
|
||||
// keeps the name, and returns at once while that name is already showing. The
|
||||
// game object classifier uses it for a record-named cursor.
|
||||
//
|
||||
// Show() calls SetCustomCursor with the mode's path, so a file of that name (a
|
||||
// patch MPQ, a loose file) wins, and a repeat each frame costs one name
|
||||
// compare. 1.12 has none of these files, so on a miss Show() does what
|
||||
// SetCustomCursor does after its load, with the image embedded in the DLL
|
||||
// (Texture::Builtin), which a texture load of the same path also gets.
|
||||
//
|
||||
// The files are 4.3.4's Interface\Cursor images, under 4.3.4's names. 4.3.4
|
||||
// already ships StableMaster.blp and Fishing.blp, but only as art a record
|
||||
// could name. StableMaster is a mode in retail's table (Enum.Cursormode
|
||||
// StablemasterCursor, listed after Ping and Enchant), not in 4.3.4's; no
|
||||
// client's table has Fishing.
|
||||
|
||||
#include "cursor/Extended.h"
|
||||
|
||||
#include "Common.h"
|
||||
#include "Game.h"
|
||||
#include "cvar/Factory.h"
|
||||
#include "texture/Builtin.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace Cursor::Extended {
|
||||
|
||||
namespace {
|
||||
|
||||
using SetCustomCursor_t = uint32_t(__fastcall *)(const char *path);
|
||||
using SendCursorImage_t = void(__cdecl *)();
|
||||
|
||||
// In mode order, the Unable forms after the others.
|
||||
const char *const kPaths[] = {
|
||||
"Interface\\Cursor\\RepairNPC.blp",
|
||||
"Interface\\Cursor\\Innkeeper.blp",
|
||||
"Interface\\Cursor\\Quest.blp",
|
||||
"Interface\\Cursor\\QuestRepeatable.blp",
|
||||
"Interface\\Cursor\\QuestTurnIn.blp",
|
||||
"Interface\\Cursor\\StableMaster.blp",
|
||||
"Interface\\Cursor\\Fishing.blp",
|
||||
"Interface\\Cursor\\UnableRepairNPC.blp",
|
||||
"Interface\\Cursor\\UnableInnkeeper.blp",
|
||||
"Interface\\Cursor\\UnableQuest.blp",
|
||||
"Interface\\Cursor\\UnableQuestRepeatable.blp",
|
||||
"Interface\\Cursor\\UnableQuestTurnIn.blp",
|
||||
"Interface\\Cursor\\UnableStableMaster.blp",
|
||||
"Interface\\Cursor\\UnableFishing.blp",
|
||||
};
|
||||
static_assert(sizeof kPaths / sizeof kPaths[0] == 2 * kUnableOffset, "one path per mode");
|
||||
|
||||
CVar::Factory::Handle g_enabled = nullptr;
|
||||
|
||||
void Register() {
|
||||
g_enabled = CVar::Factory::Register(
|
||||
"expandedCursors",
|
||||
"Show what an NPC or object offers on the cursor: anvil for repairs, ! and ? for quests, "
|
||||
"hook for your bobber",
|
||||
Offsets::CVAR_CATEGORY_GAME, "0");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&Register};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Enabled() { return CVar::Factory::GetInt(g_enabled) != 0; }
|
||||
|
||||
bool Is(uint32_t mode) { return mode >= kRepairNpc && mode < kRepairNpc + 2 * kUnableOffset; }
|
||||
|
||||
bool IsUnable(uint32_t mode) { return Is(mode) && mode >= kRepairNpc + kUnableOffset; }
|
||||
|
||||
bool IsQuest(uint32_t mode) {
|
||||
if (!Is(mode))
|
||||
return false;
|
||||
const uint32_t kind = IsUnable(mode) ? mode - kUnableOffset : mode;
|
||||
return kind >= kQuest && kind <= kQuestTurnIn;
|
||||
}
|
||||
|
||||
const char *Path(uint32_t mode) { return kPaths[mode - kRepairNpc]; }
|
||||
|
||||
void Show(uint32_t mode) {
|
||||
const char *path = Path(mode);
|
||||
if (reinterpret_cast<SetCustomCursor_t>(Offsets::FUN_SET_CUSTOM_CURSOR)(path) != 0)
|
||||
return;
|
||||
const Texture::Builtin::Image *image = Texture::Builtin::Find(path);
|
||||
if (image == nullptr || image->width != Offsets::CURSOR_IMAGE_SIZE ||
|
||||
image->height != Offsets::CURSOR_IMAGE_SIZE)
|
||||
return;
|
||||
Game::Ref<uint32_t>(Offsets::VAR_CURSOR_MODE) = Offsets::CURSOR_MODE_CUSTOM;
|
||||
std::memcpy(reinterpret_cast<void *>(Offsets::VAR_CURSOR_IMAGE_CUSTOM), image->texels,
|
||||
Offsets::CURSOR_IMAGE_SIZE * Offsets::CURSOR_IMAGE_SIZE * sizeof(uint32_t));
|
||||
reinterpret_cast<SendCursorImage_t>(Offsets::FUN_SEND_CURSOR_IMAGE)();
|
||||
Common::BoundedCopy(reinterpret_cast<char *>(Offsets::VAR_CUSTOM_CURSOR_NAME), path,
|
||||
Offsets::CURSOR_FILE_SIZE);
|
||||
}
|
||||
|
||||
} // namespace Cursor::Extended
|
||||
@@ -0,0 +1,57 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Cursor::Extended {
|
||||
|
||||
// The cursor modes later clients added that 1.12 has no image for, numbered
|
||||
// on from the engine's last mode (CURSOR_MODE_CUSTOM): 4.3.4's RepairNPC,
|
||||
// Innkeeper and quest cursors, retail's StableMaster, and the fish hook for
|
||||
// the fishing bobber. As with the engine's modes, each has an Unable form,
|
||||
// kUnableOffset further on.
|
||||
constexpr uint32_t kRepairNpc = Offsets::CURSOR_MODE_CUSTOM + 1;
|
||||
constexpr uint32_t kInnkeeper = kRepairNpc + 1;
|
||||
constexpr uint32_t kQuest = kRepairNpc + 2;
|
||||
constexpr uint32_t kQuestRepeatable = kRepairNpc + 3;
|
||||
constexpr uint32_t kQuestTurnIn = kRepairNpc + 4;
|
||||
constexpr uint32_t kStableMaster = kRepairNpc + 5;
|
||||
constexpr uint32_t kFishing = kRepairNpc + 6;
|
||||
constexpr uint32_t kUnableOffset = 7;
|
||||
|
||||
// Whether the `expandedCursors` cvar asks for these modes. The pickers that
|
||||
// give them (cursor/Npc, cursor/Fishing) keep 1.12's cursor while it is 0.
|
||||
bool Enabled();
|
||||
|
||||
// Whether `mode` is one of these, in either form.
|
||||
bool Is(uint32_t mode);
|
||||
|
||||
// Whether `mode` is one of these in its Unable form.
|
||||
bool IsUnable(uint32_t mode);
|
||||
|
||||
// Whether `mode` is a quest giver's cursor (Quest, QuestRepeatable,
|
||||
// QuestTurnIn), in either form.
|
||||
bool IsQuest(uint32_t mode);
|
||||
|
||||
// The image file of one of these modes, `Interface\Cursor\<its name>.blp`.
|
||||
const char *Path(uint32_t mode);
|
||||
|
||||
// Shows one of these modes as the cursor. The engine's SetCursorMode cannot,
|
||||
// so its hook (cursor/Hover) sends them here.
|
||||
void Show(uint32_t mode);
|
||||
|
||||
} // namespace Cursor::Extended
|
||||
@@ -0,0 +1,67 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// With `expandedCursors` at 1 (Cursor::Extended::Enabled), the cursor over
|
||||
// the player's fishing bobber is a fish hook in place of the Interact gear.
|
||||
//
|
||||
// A game object's cursor is data. GetCursor (FUN_GAMEOBJECT_GET_CURSOR) gives
|
||||
// the cursor its record names, else its type handler's: BaseHandlerGetCursor
|
||||
// for most types, which gives a lock's LockType cursor, else Interact. The
|
||||
// record's name comes from the server, and tortoise-wow sends it empty; even
|
||||
// 3.3.5's data names none for the bobber (AzerothCore's template 35591), and
|
||||
// a bobber has no lock, so every client shows the gear. Later clients ship
|
||||
// the hook (Interface\Cursor\Fishing.blp) only for data to name.
|
||||
//
|
||||
// The fishing node handler (type 17) gets its own cursor slot, the way
|
||||
// 4.3.4's guild bank type overrides its cursor. The slot still runs
|
||||
// BaseHandlerGetCursor, which decides everything (0 for a bobber the player
|
||||
// is not channelling, Unable when not usable), and swaps only the image. The
|
||||
// classifier passes the mode to SetCursorMode, whose hook (cursor/Hover)
|
||||
// shows it.
|
||||
|
||||
#include "Common.h"
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "cursor/Extended.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Cursor::Fishing {
|
||||
|
||||
namespace {
|
||||
|
||||
using HandlerGetCursor_t = uint32_t(__thiscall *)(void *handler);
|
||||
|
||||
uint32_t __fastcall FishingNodeCursor(void *handler, void * /*edx*/) {
|
||||
const uint32_t mode =
|
||||
reinterpret_cast<HandlerGetCursor_t>(Offsets::FUN_GO_HANDLER_BASE_GET_CURSOR)(handler);
|
||||
if (!Cursor::Extended::Enabled())
|
||||
return mode;
|
||||
if (mode == Offsets::CURSOR_MODE_INTERACT)
|
||||
return Cursor::Extended::kFishing;
|
||||
if (mode == Offsets::CURSOR_MODE_INTERACT + Offsets::CURSOR_MODE_UNABLE_OFFSET)
|
||||
return Cursor::Extended::kFishing + Cursor::Extended::kUnableOffset;
|
||||
return mode;
|
||||
}
|
||||
|
||||
void Patch() {
|
||||
Common::RedirectSlot(Offsets::SLOT_FISHING_NODE_HANDLER_GET_CURSOR,
|
||||
Offsets::FUN_GO_HANDLER_BASE_GET_CURSOR,
|
||||
reinterpret_cast<const void *>(&FishingNodeCursor));
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&Patch};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Cursor::Fishing
|
||||
+23
-1
@@ -27,14 +27,21 @@
|
||||
// The spell-targeting and mouselook legs of 0x4828D0 are left out: they
|
||||
// answer for the mouse, not for the object.
|
||||
//
|
||||
// The unit classifier and the fishing node's handler can also pick a mode of
|
||||
// Cursor::Extended (cursor/Npc, cursor/Fishing). The engine's SetCursorMode
|
||||
// cannot show one, so the same hook hands those to Cursor::Extended::Show,
|
||||
// and while capturing records them like any other.
|
||||
//
|
||||
// The images are the engine's own: `Interface\Cursor\<name>.blp` from its
|
||||
// cursor name table, and a game object's named cursor from its record
|
||||
// (FormatGameObjectCursorFile, `Unable` when not usable).
|
||||
// (FormatGameObjectCursorFile, `Unable` when not usable). An extended mode's
|
||||
// image is its embedded file (Cursor::Extended::Path).
|
||||
|
||||
#include "cursor/Hover.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "cursor/Extended.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
@@ -58,6 +65,10 @@ void __fastcall SetCursorMode_h(uint32_t mode) {
|
||||
g_captured = mode;
|
||||
return;
|
||||
}
|
||||
if (Cursor::Extended::Is(mode)) {
|
||||
Cursor::Extended::Show(mode);
|
||||
return;
|
||||
}
|
||||
SetCursorMode_o(mode);
|
||||
}
|
||||
|
||||
@@ -122,6 +133,17 @@ bool ImagePath(void *object, uint32_t mode, bool lowPriority, char *buf, std::si
|
||||
std::snprintf(buf, size, "%s", file);
|
||||
return true;
|
||||
}
|
||||
if (Cursor::Extended::Is(mode)) {
|
||||
// Retail shows no icon for a quest giver's cursor, not even the
|
||||
// low-priority pointer (checked in game, 2026-11-03, with the soft
|
||||
// interact icon and SoftTargetLowPriorityIcons 1). Blizzard_NamePlates
|
||||
// shows that icon exactly when SetUnitCursorTexture returns true, so
|
||||
// the refusal is the function's own.
|
||||
if (Cursor::Extended::IsQuest(mode))
|
||||
return false;
|
||||
std::snprintf(buf, size, "%s", Cursor::Extended::Path(mode));
|
||||
return true;
|
||||
}
|
||||
if (mode >= Offsets::CURSOR_MODE_CUSTOM)
|
||||
return false;
|
||||
const char *name = Game::Read<const char *>(Offsets::VAR_CURSOR_NAMES + 4 * mode);
|
||||
|
||||
+7
-6
@@ -18,9 +18,9 @@
|
||||
|
||||
namespace Cursor::Hover {
|
||||
|
||||
// The cursor mode (Offsets::CURSOR_MODE_*) the world cursor would show with
|
||||
// the mouse over `object` — a unit, player, game object or corpse — or 0
|
||||
// when it shows none. Computes it without showing it.
|
||||
// The cursor mode (Offsets::CURSOR_MODE_* or Cursor::Extended) the world
|
||||
// cursor would show with the mouse over `object` — a unit, player, game
|
||||
// object or corpse — or 0 when it shows none. Computes it without showing it.
|
||||
uint32_t Mode(void *object);
|
||||
|
||||
// For a game object's named cursor (CURSOR_MODE_CUSTOM): whether the player
|
||||
@@ -28,9 +28,10 @@ uint32_t Mode(void *object);
|
||||
bool IsUsable(void *object);
|
||||
|
||||
// Writes the image file of `mode` over `object` into `buf`. False, with
|
||||
// `buf` untouched, for no mode, and for the base (resting) mode unless
|
||||
// `lowPriority` — retail's low-priority image, the plain pointer over a unit
|
||||
// with nothing to do (SetUnitCursorTexture's includeLowPriority).
|
||||
// `buf` untouched, for no mode, for a quest giver's cursor (retail shows no
|
||||
// icon for one), and for the base (resting) mode unless `lowPriority` —
|
||||
// retail's low-priority image, the plain pointer over a unit with nothing to
|
||||
// do (SetUnitCursorTexture's includeLowPriority).
|
||||
bool ImagePath(void *object, uint32_t mode, bool lowPriority, char *buf, std::size_t size);
|
||||
|
||||
} // namespace Cursor::Hover
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// With `expandedCursors` at 1 (Cursor::Extended::Enabled), the cursor over an
|
||||
// NPC shows what it offers, as in later clients: an anvil for a repairer, a
|
||||
// hearthstone for an innkeeper, a horseshoe for a stable master, and a quest
|
||||
// giver's ! or ? (Cursor::Extended). At 0, the default, it stays 1.12's.
|
||||
//
|
||||
// 1.12 picks the cursor for a hovered unit in ClassifyHoveredUnit
|
||||
// (FUN_WORLD_FRAME_CLASSIFY_UNIT). For a unit the player can interact with,
|
||||
// its NPC flag chain takes the lowest flag bit first: gossip, quest giver,
|
||||
// vendor, flight master and so on. It never tests the repair flag, and shows
|
||||
// Interact for an innkeeper. 4.3.4's CursorTrackUnit first asks the quest
|
||||
// giver's status (GetQuestGiverCursor), then tests the services from repair
|
||||
// down, with vendor and gossip near the end.
|
||||
//
|
||||
// The thunk stands in for the head of 1.12's chain
|
||||
// (SITE_CLASSIFY_UNIT_NPC_FLAGS). Everything the engine decides before it
|
||||
// stays the engine's: mouselook, spell targeting, charm, whether the unit can
|
||||
// be interacted with now, and whether it is beyond the interact reach (EBX,
|
||||
// the Unable form). With the cvar at 0 the thunk does the instruction it
|
||||
// replaced and the stock chain runs. The pick goes to the engine's
|
||||
// SetCursorMode, whose hook (cursor/Hover) shows an extended mode and records
|
||||
// every mode while the soft-interact code asks for the hover verdict.
|
||||
|
||||
#include "Common.h"
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "cursor/Extended.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Cursor::Npc {
|
||||
|
||||
namespace {
|
||||
|
||||
// UNIT_NPC_FLAGS bits, as 1.12's chain tests them (tortoise-wow
|
||||
// UnitDefines.h names them).
|
||||
constexpr uint32_t kGossip = 0x1;
|
||||
constexpr uint32_t kVendor = 0x4;
|
||||
constexpr uint32_t kFlightMaster = 0x8;
|
||||
constexpr uint32_t kTrainer = 0x10;
|
||||
constexpr uint32_t kSpiritHealer = 0x20;
|
||||
constexpr uint32_t kSpiritGuide = 0x40;
|
||||
constexpr uint32_t kInnkeeper = 0x80;
|
||||
constexpr uint32_t kBanker = 0x100;
|
||||
constexpr uint32_t kPetitioner = 0x200;
|
||||
constexpr uint32_t kTabardDesigner = 0x400;
|
||||
constexpr uint32_t kBattleMaster = 0x800;
|
||||
constexpr uint32_t kAuctioneer = 0x1000;
|
||||
constexpr uint32_t kStableMaster = 0x2000;
|
||||
constexpr uint32_t kRepair = 0x4000;
|
||||
|
||||
// Quest giver statuses (OFF_CGUNIT_QUEST_STATUS). 1.12 numbers them its own
|
||||
// way (tortoise-wow QuestDef.h); its status-to-marker table (0x80C454) shows
|
||||
// each one's meaning: 3 a grey ?, 4 a blue ?, 5 a yellow !, 6 and 7 a yellow
|
||||
// ?. 0 none, 1 unavailable (grey !) and 2 chat (no marker) have no cursor,
|
||||
// as 4.3.4's none and unavailable have none.
|
||||
constexpr uint32_t kStatusIncomplete = 3;
|
||||
constexpr uint32_t kStatusRewardRepeatable = 4;
|
||||
constexpr uint32_t kStatusAvailable = 5;
|
||||
constexpr uint32_t kStatusRewardOld = 6;
|
||||
constexpr uint32_t kStatusReward = 7;
|
||||
|
||||
using IsQuestGiverToTalk_t = uint32_t(__stdcall *)(void *unit);
|
||||
using SetCursorMode_t = void(__fastcall *)(uint32_t mode);
|
||||
|
||||
uintptr_t g_classifyReturn = Offsets::CLASSIFY_UNIT_RETURN;
|
||||
|
||||
// 4.3.4's quest cursor for a status (GetQuestGiverCursor), matched by the
|
||||
// status's meaning: 4.3.4 gives Quest to an available and to an incomplete
|
||||
// quest, QuestRepeatable to a repeatable reward (its blue ?), and
|
||||
// QuestTurnIn to a reward (its yellow ?).
|
||||
uint32_t QuestMode(uint32_t status) {
|
||||
switch (status) {
|
||||
case kStatusIncomplete:
|
||||
case kStatusAvailable:
|
||||
return Cursor::Extended::kQuest;
|
||||
case kStatusRewardRepeatable:
|
||||
return Cursor::Extended::kQuestRepeatable;
|
||||
case kStatusRewardOld:
|
||||
case kStatusReward:
|
||||
return Cursor::Extended::kQuestTurnIn;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 4.3.4's order (CursorTrackUnit) in 1.12's flag bits, or 0 for none.
|
||||
// 4.3.4's guild master, guild banker and spell click tests have no 1.12
|
||||
// flag, and its flight master test also reads an icon type; for that flag
|
||||
// 1.12 shows Taxi, as here. 4.3.4 also takes a cursor that the creature's
|
||||
// cache record names (IconName), which 1.12's creature query does not carry.
|
||||
// A stable master gets retail's StableMaster in 4.3.4's place for it, where
|
||||
// 4.3.4 shows Speak. Where 4.3.4 tests gossip, this also takes the quest
|
||||
// giver 1.12 would speak to (IsQuestGiverToTalk), so a status without a
|
||||
// quest cursor keeps 1.12's Speak.
|
||||
uint32_t ServiceMode(void *unit) {
|
||||
if (const uint32_t quest = QuestMode(Game::Read<uint32_t>(unit, Offsets::OFF_CGUNIT_QUEST_STATUS)))
|
||||
return quest;
|
||||
const uint32_t flags =
|
||||
Game::Read<uint32_t>(Game::Read<const void *>(unit, Offsets::OFF_UNIT_DESCRIPTOR),
|
||||
Offsets::OFF_UNIT_DESCRIPTOR_NPC_FLAGS);
|
||||
if (flags & kRepair)
|
||||
return Cursor::Extended::kRepairNpc;
|
||||
if (flags & kInnkeeper)
|
||||
return Cursor::Extended::kInnkeeper;
|
||||
if (flags & kFlightMaster)
|
||||
return Offsets::CURSOR_MODE_TAXI;
|
||||
if (flags & kTrainer)
|
||||
return Offsets::CURSOR_MODE_TRAINER;
|
||||
if (flags & (kSpiritHealer | kSpiritGuide))
|
||||
return Offsets::CURSOR_MODE_SPEAK;
|
||||
if (flags & kBanker)
|
||||
return Offsets::CURSOR_MODE_BUY;
|
||||
if (flags & (kPetitioner | kTabardDesigner | kBattleMaster))
|
||||
return Offsets::CURSOR_MODE_SPEAK;
|
||||
if (flags & kAuctioneer)
|
||||
return Offsets::CURSOR_MODE_BUY;
|
||||
if (flags & kStableMaster)
|
||||
return Cursor::Extended::kStableMaster;
|
||||
if (flags & kVendor)
|
||||
return Offsets::CURSOR_MODE_PICKUP;
|
||||
if ((flags & kGossip) ||
|
||||
reinterpret_cast<IsQuestGiverToTalk_t>(Offsets::FUN_IS_QUEST_GIVER_TO_TALK)(unit) != 0)
|
||||
return Offsets::CURSOR_MODE_SPEAK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// True when it set the mode. `unable` is the engine's EBX: 1 beyond the
|
||||
// interact reach, else 0. A unit with no service gets the base mode, where
|
||||
// the stock chain ends too (ShowBaseCursor).
|
||||
bool __stdcall Pick(void *unit, uint32_t unable) {
|
||||
if (!Cursor::Extended::Enabled())
|
||||
return false;
|
||||
uint32_t mode = ServiceMode(unit);
|
||||
if (mode == 0)
|
||||
mode = Game::Read<uint32_t>(Offsets::VAR_CURSOR_BASE_MODE);
|
||||
else if (unable != 0)
|
||||
mode += Cursor::Extended::Is(mode) ? Cursor::Extended::kUnableOffset
|
||||
: Offsets::CURSOR_MODE_UNABLE_OFFSET;
|
||||
reinterpret_cast<SetCursorMode_t>(Offsets::FUN_SET_CURSOR_MODE)(mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
static_assert(Offsets::OFF_UNIT_DESCRIPTOR == 0x110, "NpcFlagsThunk replays [esi + 0x110]");
|
||||
|
||||
// Stands in for `mov ecx, [esi + 0x110]`, the head of the chain: the unit in
|
||||
// ESI, the Unable flag in EBX. When Pick set the mode, it leaves the
|
||||
// classifier through the chain's epilogue; otherwise it does the instruction
|
||||
// it replaced and the chain runs.
|
||||
__declspec(naked) void NpcFlagsThunk() {
|
||||
__asm {
|
||||
push ebx
|
||||
push esi
|
||||
call Pick
|
||||
test al, al
|
||||
jnz picked
|
||||
mov ecx, dword ptr [esi + 110h]
|
||||
ret
|
||||
picked:
|
||||
add esp, 4
|
||||
jmp dword ptr [g_classifyReturn]
|
||||
}
|
||||
}
|
||||
|
||||
void Patch() {
|
||||
static const uint8_t kFlagsRead[6] = {0x8B, 0x8E, 0x10, 0x01, 0x00, 0x00};
|
||||
Common::ReplaceWithCall(Offsets::SITE_CLASSIFY_UNIT_NPC_FLAGS, kFlagsRead, sizeof kFlagsRead,
|
||||
reinterpret_cast<const void *>(&NpcFlagsThunk));
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&Patch};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Cursor::Npc
|
||||
+5
-3
@@ -25,11 +25,13 @@ const char *NormalizeEmpty(const char *s) {
|
||||
|
||||
} // namespace
|
||||
|
||||
// The id is compared unsigned: a DBC column's "none" is -1, which a signed
|
||||
// compare passes as less than the count and then indexes records[-1]. The
|
||||
// engine rejects it with a signed `jl` before its `jg` against the count
|
||||
// (0x00472797). An unloaded store's count is -1, so it takes no id.
|
||||
const uint8_t *Record(uintptr_t recordsVar, uintptr_t countVar, uint32_t id) {
|
||||
if (id == 0)
|
||||
return nullptr;
|
||||
const int count = *reinterpret_cast<const int *>(countVar);
|
||||
if (static_cast<int>(id) > count)
|
||||
if (id == 0 || count <= 0 || id > static_cast<uint32_t>(count))
|
||||
return nullptr;
|
||||
const uint8_t *const *records =
|
||||
*reinterpret_cast<const uint8_t *const *const *>(recordsVar);
|
||||
|
||||
+2
-1
@@ -39,7 +39,8 @@ namespace DBC {
|
||||
|
||||
// Resolves `id` to its record pointer in the table whose records-base
|
||||
// global is at `recordsVar` and count global at `countVar`. Returns
|
||||
// `nullptr` for `id == 0`, OOR ids (> count), and unpopulated slots.
|
||||
// `nullptr` for `id == 0`, OOR ids (> count, which includes -1 and every
|
||||
// other negative id), and unpopulated slots.
|
||||
// Caller reads whatever fields it needs off the returned record.
|
||||
//
|
||||
// Use this when `LocalizedField` / `StringField` don't fit — e.g. for
|
||||
|
||||
@@ -206,9 +206,8 @@ void RegisterLuaFunctions() {
|
||||
&Script_GetSelfResurrectOptions, &kGetSelfResurrectOptions);
|
||||
Game::Lua::RegisterTableFunction("C_DeathInfo", "UseSelfResurrectOption",
|
||||
&Script_UseSelfResurrectOption, &kUseSelfResurrectOption);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "SelfResurrectOptionType", kOptionTypeEntries,
|
||||
sizeof(kOptionTypeEntries) / sizeof(kOptionTypeEntries[0]),
|
||||
"DeathInfo");
|
||||
Game::Lua::RegisterIntegerEnum(
|
||||
"Enum", "SelfResurrectOptionType", kOptionTypeEntries, "DeathInfo");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <iterator>
|
||||
|
||||
namespace Duration::Object {
|
||||
|
||||
@@ -451,8 +450,7 @@ const Game::Doc::Function kCreateDuration{"Creates a zero duration object that c
|
||||
void RegisterLuaFunctions() {
|
||||
CreateMetatable(Lua::State());
|
||||
Lua::RegisterTableFunction("C_DurationUtil", "CreateDuration", &Script_CreateDuration, &kCreateDuration);
|
||||
Lua::RegisterIntegerEnum("Enum", "DurationTimeModifier", kModifiers, static_cast<int>(std::size(kModifiers)),
|
||||
"LuaDurationObjectShared");
|
||||
Lua::RegisterIntegerEnum("Enum", "DurationTimeModifier", kModifiers, "LuaDurationObjectShared");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -780,8 +779,8 @@ void RegisterLuaFunctions() {
|
||||
CreateMetatable(Lua::State());
|
||||
Lua::RegisterTableFunction("C_DurationUtil", "CreateDurationTextBinding", &Script_CreateDurationTextBinding,
|
||||
&kCreateDurationTextBinding);
|
||||
Lua::RegisterIntegerEnum("Enum", "DurationTextBindingProperty", kProperties,
|
||||
static_cast<int>(std::size(kProperties)), "DurationTextBindingShared");
|
||||
Lua::RegisterIntegerEnum(
|
||||
"Enum", "DurationTextBindingProperty", kProperties, "DurationTextBindingShared");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -125,8 +125,8 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -83,9 +83,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
+1
-3
@@ -86,9 +86,7 @@ void RegisterLuaFunctions() {
|
||||
Common::RedirectCall(Offsets::CALL_EDITBOX_IME_COMPOSITION_INSERT, Offsets::FUN_EDITBOX_INSERT,
|
||||
reinterpret_cast<const void *>(&CompositionInsert));
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -30,8 +30,7 @@ const Game::Lua::EnumIntegerEntry kEntries[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "InputContext", kEntries,
|
||||
sizeof(kEntries) / sizeof(kEntries[0]), "InputConstants");
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "InputContext", kEntries, "InputConstants");
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -234,9 +234,7 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY),
|
||||
g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_EDITBOX_METHOD_REGISTRY), g_methods);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
## Interface: 11200
|
||||
## Title: !!!ClassicAPI
|
||||
## Notes: Library to emulate aspects of the modern WoW API on 1.12.1 / Lua 5.0.
|
||||
## Notes-ruRU: Библиотека, эмулирующая аспекты современного API WoW на 1.12.1 / Lua 5.0.
|
||||
## Version: DEV
|
||||
## Author: Brues
|
||||
## X-Website: https://github.com/brues-code/ClassicAPI
|
||||
@@ -143,6 +144,7 @@ FrameXML\ColorSwatch.lua
|
||||
FrameXML\ColorSwatch.xml
|
||||
FrameXML\ScriptErrorsFrame.lua
|
||||
FrameXML\ScriptErrorsFrame.xml
|
||||
FrameXML\DressUpFrame.lua
|
||||
|
||||
Menu\MenuConstants.lua
|
||||
Menu\Classic\MenuConstants.lua
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
-- 4.3.4's DressUpItemLink (FrameXML/ModelFrames.lua): the whole link goes to
|
||||
-- the model, so DressUpModel:TryOn shows the link's enchant glow, and an item
|
||||
-- the model cannot wear does not open the window. 1.12.1's version passes
|
||||
-- only the item ID.
|
||||
--
|
||||
-- Differences from the 4.3.4 source:
|
||||
-- - The link goes through DressUpItem, which 4.3.4 folded in. Here
|
||||
-- Blizzard_AuctionUI replaces DressUpItem to show its own model, and
|
||||
-- some clients add steps to it (Turtle dresses the tabard).
|
||||
-- - An uncached item skips the IsDressableItem test, which reads only the
|
||||
-- cache. 1.12.1 opens no tooltip before a chat link's Control-click, so
|
||||
-- the test would drop most chat links; TryOn loads the item instead.
|
||||
|
||||
function DressUpItemLink(link)
|
||||
if ( not link or (C_Item.IsItemDataCachedByID(link) and not IsDressableItem(link)) ) then
|
||||
return;
|
||||
end
|
||||
DressUpItem(link);
|
||||
end
|
||||
@@ -23,15 +23,15 @@
|
||||
-- - The text is a ScrollingEditBoxTemplate with a MinimalScrollBar, the
|
||||
-- ScrollBox pair retail uses for scrolling edit boxes, not retail's
|
||||
-- ScrollFrameTemplate, which ClassicAPI does not port.
|
||||
-- - The stack is retail's GetErrorData without the locals, taken in the
|
||||
-- SetText slot: GetErrorCallstackHeight finds the code that failed
|
||||
-- however many handlers sit between it and the slot. 1.12 has no
|
||||
-- debuglocals.
|
||||
-- - The stack and locals are retail's GetErrorData, taken in the SetText
|
||||
-- slot: GetErrorCallstackHeight finds the code that failed however many
|
||||
-- handlers sit between it and the slot. No |K strings to remove, and no
|
||||
-- C_StringUtil.EscapeDecimalNonPrintables.
|
||||
-- - No scriptErrors cvar (1.12 always shows errors), no LUA_WARNING, no
|
||||
-- gamepad legend.
|
||||
-- - Lua 5.0: no `...` expression, no # operator.
|
||||
|
||||
local function GetErrorStack()
|
||||
local function GetErrorData()
|
||||
-- Example of how debug stack level is calculated
|
||||
-- Current stack: [1, 2, 3, 4, 5] (current function is at 1, total current height is 5)
|
||||
-- Stack at time of error: [1, 2] (these are currently now index 4 and 5, but at the time of error the stack height is 2)
|
||||
@@ -40,13 +40,20 @@ local function GetErrorStack()
|
||||
local errorCallStackHeight = GetErrorCallstackHeight();
|
||||
local errorStackOffset = errorCallStackHeight and (errorCallStackHeight - 1);
|
||||
local debugStackLevel = currentStackHeight - (errorStackOffset or 0);
|
||||
return debugstack(debugStackLevel);
|
||||
local skipFunctionsAndUserdata = true;
|
||||
|
||||
local stack = debugstack(debugStackLevel);
|
||||
local locals = debuglocals(debugStackLevel, skipFunctionsAndUserdata);
|
||||
|
||||
return stack, locals;
|
||||
end
|
||||
|
||||
local ERROR_FORMAT = "|cffffd200Message:|r|cffffffff %s|r\n"
|
||||
.. "|cffffd200Time:|r|cffffffff %s|r\n"
|
||||
.. "|cffffd200Count:|r|cffffffff %s|r\n"
|
||||
.. "|cffffd200Stack:|r\n"
|
||||
.. "|cffffffff%s|r\n"
|
||||
.. "|cffffd200Locals:|r\n"
|
||||
.. "|cffffffff%s|r";
|
||||
|
||||
local INDEX_ORDER_FORMAT = "%d / %d";
|
||||
@@ -68,15 +75,16 @@ function ScriptErrorsFrame_OnLoad(self)
|
||||
ScrollUtil.RegisterScrollBoxWithScrollBar(scrollBox, self.ScrollBar);
|
||||
|
||||
ScriptErrors_Message.SetText = function(_, message)
|
||||
ScriptErrorsFrame_OnError(message, GetErrorStack());
|
||||
ScriptErrorsFrame_OnError(message, GetErrorData());
|
||||
end
|
||||
ScriptErrors.Show = function() end
|
||||
end
|
||||
|
||||
function ScriptErrorsFrame_OnError(message, stack)
|
||||
function ScriptErrorsFrame_OnError(message, stack, locals)
|
||||
local self = ScriptErrorsFrame;
|
||||
message = tostring(message);
|
||||
stack = stack or "";
|
||||
locals = locals or "<none>";
|
||||
|
||||
local messageKey = message .. "\n" .. stack;
|
||||
local index = self.seen[messageKey];
|
||||
@@ -84,7 +92,7 @@ function ScriptErrorsFrame_OnError(message, stack)
|
||||
local errorData = self.errorData[index];
|
||||
errorData.count = errorData.count + 1;
|
||||
else
|
||||
table.insert(self.errorData, { count = 1, message = message, time = date(), stack = stack });
|
||||
table.insert(self.errorData, { count = 1, message = message, time = date(), stack = stack, locals = locals });
|
||||
index = table.getn(self.errorData);
|
||||
self.seen[messageKey] = index;
|
||||
end
|
||||
@@ -106,7 +114,7 @@ function ScriptErrorsFrame_Update()
|
||||
-- SetText puts the cursor and the scroll back at the top.
|
||||
local errorData = self.errorData[self.index];
|
||||
if ( errorData ) then
|
||||
self.ScrollingEditBox:SetText(string.format(ERROR_FORMAT, errorData.message, errorData.time, errorData.count, errorData.stack));
|
||||
self.ScrollingEditBox:SetText(string.format(ERROR_FORMAT, errorData.message, errorData.time, errorData.count, errorData.stack, errorData.locals));
|
||||
self.ScrollingEditBox:GetEditBox():HighlightText(0);
|
||||
else
|
||||
self.ScrollingEditBox:ClearText();
|
||||
|
||||
@@ -607,6 +607,17 @@ AddKeybinding(bags, "AOELOOT")
|
||||
|
||||
local interface = NewPage(CLASSICAPI_INTERFACE, main.name)
|
||||
|
||||
-- The label ends with some of the cursors the setting shows. The DLL serves
|
||||
-- each image at its Interface\Cursor path, and the markup stays out of the
|
||||
-- translated string.
|
||||
local expandedCursorIcons = ""
|
||||
for _, name in ipairs({ "RepairNPC", "Innkeeper", "StableMaster", "Quest", "QuestTurnIn", "Fishing" }) do
|
||||
expandedCursorIcons = expandedCursorIcons .. " |TInterface\\Cursor\\" .. name .. ":16|t"
|
||||
end
|
||||
|
||||
AddHeader(interface, CLASSICAPI_CURSOR)
|
||||
AddCheckbox(interface, CLASSICAPI_EXPANDED_CURSORS .. expandedCursorIcons, CLASSICAPI_EXPANDED_CURSORS_TOOLTIP, CVarSetting("expandedCursors"))
|
||||
|
||||
AddHeader(interface, CLASSICAPI_NAMEPLATES)
|
||||
AddSlider(interface, CLASSICAPI_TARGET_NAMEPLATE_ALPHA, CLASSICAPI_TARGET_NAMEPLATE_ALPHA_TOOLTIP, NumberCVarSetting("nameplateSelectedAlpha"), 0, 1, 0.05)
|
||||
AddSlider(interface, CLASSICAPI_OTHER_NAMEPLATE_ALPHA, CLASSICAPI_OTHER_NAMEPLATE_ALPHA_TOOLTIP, NumberCVarSetting("nameplateNotSelectedAlpha"), 0, 1, 0.05)
|
||||
|
||||
@@ -8,6 +8,9 @@ BINDING_NAME_FOCUSTARGET = "Fokus setzen";
|
||||
BINDING_NAME_INTERACTMOUSEOVER = "Mit Mouseover interagieren";
|
||||
BINDING_NAME_INTERACTTARGET = "Mit Ziel interagieren";
|
||||
BINDING_NAME_TARGETFOCUS = "Fokus anvisieren";
|
||||
CLASSICAPI_CURSOR = "Mauszeiger";
|
||||
CLASSICAPI_EXPANDED_CURSORS = "Erweiterte Mauszeiger anzeigen";
|
||||
CLASSICAPI_EXPANDED_CURSORS_TOOLTIP = "Wenn Ihr mit der Maus auf einen NSC oder ein Objekt zeigt, stellt der Mauszeiger dar, was Ihr damit tun könnt. Zum Beispiel einen Amboss für Reparaturen, ! oder ? für Quests und einen Haken für Euren Angelschwimmer.";
|
||||
COMBAT_LABEL = "Kampf";
|
||||
COUNTDOWN_FOR_COOLDOWNS_TEXT = "Zahlen für Abklingzeiten anzeigen";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "Seid Ihr sicher, dass Ihr das Ausrüstungsset \"%s\" löschen wollt?";
|
||||
|
||||
@@ -13,15 +13,16 @@ CLASSICAPI_ACTION_TARGETING = "Action Targeting";
|
||||
CLASSICAPI_ADVANCED = "Advanced";
|
||||
CLASSICAPI_ALL_SOFT_TARGET_ICONS = "Icons for All Soft Targets";
|
||||
CLASSICAPI_AUTO_DISMOUNT = "Auto Dismount";
|
||||
CLASSICAPI_AUTO_DISMOUNT_TOOLTIP = "Dismount when you cast a spell while you are mounted.";
|
||||
CLASSICAPI_AUTO_DISMOUNT_TOOLTIP = "Dismount when you cast a spell, use an object, loot, attack, or open the flight map while you are mounted.";
|
||||
CLASSICAPI_AUTO_STAND = "Auto Stand";
|
||||
CLASSICAPI_AUTO_STAND_TOOLTIP = "Stand up when you cast a spell while you sit.";
|
||||
CLASSICAPI_AUTO_STAND_TOOLTIP = "Stand up when you cast a spell, loot, or open the flight map while you sit.";
|
||||
CLASSICAPI_AUTO_UNSHIFT = "Auto Cancel Form";
|
||||
CLASSICAPI_AUTO_UNSHIFT_TOOLTIP = "Leave your form when you cast a spell that you cannot cast in it.";
|
||||
CLASSICAPI_AUTO_UNSHIFT_TOOLTIP = "Leave your form when you cast a spell that you cannot cast in it, use an item, or open the flight map. Also lets you talk to NPCs and use objects in druid forms and Ghost Wolf.";
|
||||
CLASSICAPI_BAGS_AND_ITEMS = "Bags and Items";
|
||||
CLASSICAPI_CASTING_AND_ATTACKING = "Casting and Attacking";
|
||||
CLASSICAPI_COMBAT_LOG_RETENTION = "Combat Log Retention";
|
||||
CLASSICAPI_COMBAT_LOG_RETENTION_TOOLTIP = "How many seconds the combat log keeps entries for addons.";
|
||||
CLASSICAPI_CURSOR = "Cursor";
|
||||
CLASSICAPI_DIRECTION_ANY = "Any direction";
|
||||
CLASSICAPI_DIRECTION_CONE = "Within 30 degrees";
|
||||
CLASSICAPI_DIRECTION_FRONT = "Directly in front";
|
||||
@@ -30,6 +31,8 @@ CLASSICAPI_ENEMY_DIRECTION = "Enemy Direction";
|
||||
CLASSICAPI_ENEMY_ICON = "Show Enemy Icon";
|
||||
CLASSICAPI_ENEMY_ICON_TOOLTIP = "Shows an icon over the enemy that action targeting picks.";
|
||||
CLASSICAPI_ENEMY_RANGE = "Enemy Range";
|
||||
CLASSICAPI_EXPANDED_CURSORS = "Show Expanded Cursors";
|
||||
CLASSICAPI_EXPANDED_CURSORS_TOOLTIP = "When you point at an NPC or an object, the cursor shows what you can do with it. For example, an anvil for repairs, ! or ? for quests, and a hook for your fishing bobber.";
|
||||
CLASSICAPI_FRIEND_DIRECTION = "Friend Direction";
|
||||
CLASSICAPI_FRIEND_ICON = "Show Friend Icon";
|
||||
CLASSICAPI_FRIEND_ICON_TOOLTIP = "Shows an icon over the friendly unit that friendly action targeting picks.";
|
||||
|
||||
@@ -8,6 +8,9 @@ BINDING_NAME_FOCUSTARGET = "Establecer objetivo";
|
||||
BINDING_NAME_INTERACTMOUSEOVER = "Interactuar al pasar el ratón";
|
||||
BINDING_NAME_INTERACTTARGET = "Interactuar con objetivo";
|
||||
BINDING_NAME_TARGETFOCUS = "Seleccionar enfocado";
|
||||
CLASSICAPI_CURSOR = "Cursor";
|
||||
CLASSICAPI_EXPANDED_CURSORS = "Mostrar cursores ampliados";
|
||||
CLASSICAPI_EXPANDED_CURSORS_TOOLTIP = "Al pasar el ratón sobre un PNJ o un objeto, el cursor muestra lo que puedes hacer con él. Por ejemplo, un yunque para las reparaciones, ! o ? para las misiones y un anzuelo para tu corcho de pesca.";
|
||||
COMBAT_LABEL = "Combate";
|
||||
COUNTDOWN_FOR_COOLDOWNS_TEXT = "Mostrar tiempos de reutilización en cifras";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "¿Seguro que quieres eliminar el equipamiento %s?";
|
||||
|
||||
@@ -8,6 +8,9 @@ BINDING_NAME_FOCUSTARGET = "Définir cible";
|
||||
BINDING_NAME_INTERACTMOUSEOVER = "Interagir par survol de la souris";
|
||||
BINDING_NAME_INTERACTTARGET = "Interagir avec la cible";
|
||||
BINDING_NAME_TARGETFOCUS = "Reprendre cible";
|
||||
CLASSICAPI_CURSOR = "Curseur";
|
||||
CLASSICAPI_EXPANDED_CURSORS = "Afficher les curseurs étendus";
|
||||
CLASSICAPI_EXPANDED_CURSORS_TOOLTIP = "Quand vous survolez un PNJ ou un objet, le curseur indique ce que vous pouvez en faire. Par exemple, une enclume pour les réparations, ! ou ? pour les quêtes et un hameçon pour votre flotteur de pêche.";
|
||||
COMBAT_LABEL = "Combat";
|
||||
COUNTDOWN_FOR_COOLDOWNS_TEXT = "Temps de recharge en chiffres";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "Voulez-vous vraiment supprimer l'ensemble d'équipement %s ?";
|
||||
|
||||
@@ -13,3 +13,6 @@ FOURTH_NUMBER = "조";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<지정을 해제하려면 오른쪽 클릭>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "\"%s\" 단축키 지정 또는 %s 눌러 취소";
|
||||
CLASSICAPI_CURSOR = "커서";
|
||||
CLASSICAPI_EXPANDED_CURSORS = "확장 커서 표시";
|
||||
CLASSICAPI_EXPANDED_CURSORS_TOOLTIP = "NPC나 물체를 가리키면 커서가 할 수 있는 행동을 보여줍니다. 예를 들어 수리는 모루, 퀘스트는 ! 또는 ?, 낚시찌는 낚싯바늘로 표시됩니다.";
|
||||
|
||||
@@ -4,14 +4,109 @@ ACTION_BUTTON_USE_KEY_DOWN = "Способности при нажатии кл
|
||||
ACTION_TARGETING_OPTION = "Разрешить динамическое выделение";
|
||||
ADDON_MEM_KB_ABBR = "(%.0f КБ) %s";
|
||||
ADDON_MEM_MB_ABBR = "(%.2f МБ) %s";
|
||||
ADDON_OPTIONS = "Параметры модификаций";
|
||||
BINDING_NAME_AOELOOT = "Сбор добычи по области";
|
||||
BINDING_NAME_FOCUSTARGET = "Запомненная цель";
|
||||
BINDING_NAME_INTERACTMOUSEOVER = "Взаимодействовать указателем мыши";
|
||||
BINDING_NAME_INTERACTMOUSEOVER = "Взаимодействовать с объектом под курсором";
|
||||
BINDING_NAME_INTERACTTARGET = "Взаимодействовать с целью";
|
||||
BINDING_NAME_TARGETFOCUS = "Восстановить цель";
|
||||
CLASSICAPI_ACTION_BARS = "Панели команд";
|
||||
CLASSICAPI_ACTION_TARGETING = "Динамическое выделение";
|
||||
CLASSICAPI_ADVANCED = "Дополнительно";
|
||||
CLASSICAPI_ALL_SOFT_TARGET_ICONS = "Значки для всех мягких целей";
|
||||
CLASSICAPI_AUTO_DISMOUNT = "Автоматически спешиваться";
|
||||
CLASSICAPI_AUTO_DISMOUNT_TOOLTIP = "Спешиваться при применении заклинания, использовании объекта, сборе добычи, атаке или открытии карты полетов, если вы едете верхом.";
|
||||
CLASSICAPI_AUTO_STAND = "Автоматически вставать";
|
||||
CLASSICAPI_AUTO_STAND_TOOLTIP = "Вставать при применении заклинания, сборе добычи или открытии карты полетов, если вы сидите.";
|
||||
CLASSICAPI_AUTO_UNSHIFT = "Автоматически снимать облик";
|
||||
CLASSICAPI_AUTO_UNSHIFT_TOOLTIP = "Покидать свой облик при применении заклинания, которое в нем недоступно, использовании предмета, а также при открытии карты полетов. Также позволяет говорить с NPC и использовать объекты в обликах друида и призрачного волка.";
|
||||
CLASSICAPI_BAGS_AND_ITEMS = "Сумки и предметы";
|
||||
CLASSICAPI_CASTING_AND_ATTACKING = "Заклинания и атаки";
|
||||
CLASSICAPI_COMBAT_LOG_RETENTION = "Хранение журнала боя";
|
||||
CLASSICAPI_COMBAT_LOG_RETENTION_TOOLTIP = "Сколько секунд журнал боя хранит записи для модификаций.";
|
||||
CLASSICAPI_CURSOR = "Курсор";
|
||||
CLASSICAPI_DIRECTION_ANY = "Любое направление";
|
||||
CLASSICAPI_DIRECTION_CONE = "В пределах 30 градусов";
|
||||
CLASSICAPI_DIRECTION_FRONT = "Прямо перед собой";
|
||||
CLASSICAPI_ENEMIES = "Противники";
|
||||
CLASSICAPI_ENEMY_DIRECTION = "Направление на противника";
|
||||
CLASSICAPI_ENEMY_ICON = "Показывать значок противника";
|
||||
CLASSICAPI_ENEMY_ICON_TOOLTIP = "Показывает значок над противником, которого выбирает динамическое выделение.";
|
||||
CLASSICAPI_ENEMY_RANGE = "Дистанция до противников";
|
||||
CLASSICAPI_EXPANDED_CURSORS = "Показывать расширенные курсоры";
|
||||
CLASSICAPI_EXPANDED_CURSORS_TOOLTIP = "При наведении на NPC или объект курсор показывает, что с ним можно сделать. Например, наковальню для ремонта, ! или ? для заданий и крючок для вашего поплавка.";
|
||||
CLASSICAPI_FRIEND_DIRECTION = "Направление на союзника";
|
||||
CLASSICAPI_FRIEND_ICON = "Показывать значок союзника";
|
||||
CLASSICAPI_FRIEND_ICON_TOOLTIP = "Показывает значок над дружественной целью, которую выбирает дружественное динамическое выделение.";
|
||||
CLASSICAPI_FRIEND_RANGE = "Дистанция до союзников";
|
||||
CLASSICAPI_FRIEND_TARGETING = "Включить дружественное динамическое выделение";
|
||||
CLASSICAPI_FRIEND_TARGETING_TOOLTIP = "Выбирает ближайшую дружественную цель в поле зрения. Если у вас нет цели, полезные заклинания применяются к ней.";
|
||||
CLASSICAPI_FRIENDS = "Союзники";
|
||||
CLASSICAPI_GAME_OBJECT_ICON = "Показывать значок взаимодействия на объектах";
|
||||
CLASSICAPI_GAME_OBJECT_ICON_TOOLTIP = "Также показывает значок взаимодействия над объектами, которые нельзя выбрать целью, например почтовыми ящиками и сундуками.";
|
||||
CLASSICAPI_HIDE_EQUIPMENT_SETS = "Скрывать комплекты экипировки на предметах";
|
||||
CLASSICAPI_HIDE_EQUIPMENT_SETS_TOOLTIP = "Убирает строку с комплектами экипировки из подсказок предметов.";
|
||||
CLASSICAPI_ICON_SIZE = "Размер значков";
|
||||
CLASSICAPI_ICON_SIZE_TOOLTIP = "Размер значков взаимодействия, противников и союзников.";
|
||||
CLASSICAPI_IGNORE_BACKPACK = "Не сортировать рюкзак";
|
||||
CLASSICAPI_IGNORE_BACKPACK_TOOLTIP = "При сортировке сумок предметы в рюкзаке остаются на своих местах.";
|
||||
CLASSICAPI_IGNORE_BANK = "Не сортировать основной банк";
|
||||
CLASSICAPI_IGNORE_BANK_TOOLTIP = "При сортировке банка предметы в основных ячейках банка остаются на своих местах.";
|
||||
CLASSICAPI_INTERACT_DIRECTION = "Направление взаимодействия";
|
||||
CLASSICAPI_INTERACT_DIRECTION_TOOLTIP = "Где должен находиться NPC или объект, чтобы его выбрала клавиша взаимодействия. Направление определяется камерой, а не персонажем.";
|
||||
CLASSICAPI_INTERACT_ICON = "Показывать значок взаимодействия";
|
||||
CLASSICAPI_INTERACT_ICON_TOOLTIP = "Показывает значок над NPC или объектом, которые использует клавиша взаимодействия.";
|
||||
CLASSICAPI_INTERACT_KEY = "Клавиша взаимодействия";
|
||||
CLASSICAPI_INTERACT_RANGE = "Дистанция взаимодействия";
|
||||
CLASSICAPI_INTERFACE = "Интерфейс";
|
||||
CLASSICAPI_KEEP_ACTION_TARGETING = "Сохранять динамическое выделение";
|
||||
CLASSICAPI_KEEP_ACTION_TARGETING_TOOLTIP = "Динамическое выделение продолжает выбирать противников и союзников, пока у вас есть цель, выбранная вручную.";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS = "За кем следует обычная цель";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_ENEMY = "За мягкой целью противника";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_FRIEND = "За мягкой целью союзника";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_NONE = "Ни за кем";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_TOOLTIP = "Цель, выбранная динамическим выделением, меняется вместе с мягкой целью противника или союзника. Цель, выбранная вручную, сохраняется.";
|
||||
CLASSICAPI_LOW_PRIORITY_ICONS = "Показывать значки низкого приоритета";
|
||||
CLASSICAPI_LOW_PRIORITY_ICONS_TOOLTIP = "Также показывает значок над целью, с которой нельзя ничего сделать, например над членом группы. Значок выглядит как обычный указатель.";
|
||||
CLASSICAPI_MATCH_INSIDE_NAMES = "Учитывать правки внутри имени";
|
||||
CLASSICAPI_MATCH_INSIDE_NAMES_TOOLTIP = "Когда вы вводите текст в середине имени, предлагаемые имена также учитывают буквы после курсора.";
|
||||
CLASSICAPI_MATCH_THE_COMMAND = "Учитывать команду";
|
||||
CLASSICAPI_MATCH_THE_COMMAND_TOOLTIP = "Предлагает только имена, подходящие для команды, например участников гильдии для /gpromote. Если выключено, предлагаются все известные вам имена.";
|
||||
CLASSICAPI_MOTION = "Движение";
|
||||
CLASSICAPI_NAME_SUGGESTIONS = "Подсказки имен";
|
||||
CLASSICAPI_NAMEPLATES = "Таблички с именами";
|
||||
CLASSICAPI_NUMBERS = "Числа";
|
||||
CLASSICAPI_OPTIONS_ABOUT = "Настройки возможностей, которые ClassicAPI добавляет в игру. Каждый параметр меняет одну настройку, и изменение применяется сразу.";
|
||||
CLASSICAPI_OTHER_NAMEPLATE_ALPHA = "Непрозрачность чужих табличек";
|
||||
CLASSICAPI_OTHER_NAMEPLATE_ALPHA_TOOLTIP = "Непрозрачность табличек с именами целей, которые не являются вашей целью.";
|
||||
CLASSICAPI_PREFER_YOUR_TARGET = "Отдавать предпочтение вашей цели";
|
||||
CLASSICAPI_PREFER_YOUR_TARGET_TOOLTIP = "Если выбранную вами цель можно взять, динамическое выделение выбирает ее. Если параметр выключен, всегда выбирается ближайшая цель.";
|
||||
CLASSICAPI_RANGE_TOOLTIP = "Максимальное расстояние в ярдах. Оно не может превышать значение параметра targetNearestDistance.";
|
||||
CLASSICAPI_RECENT_WHISPERS_FIRST = "Сначала недавние шепоты";
|
||||
CLASSICAPI_RECENT_WHISPERS_FIRST_TOOLTIP = "Предлагаемые имена начинаются с игроков, которым вы шептали совсем недавно.";
|
||||
CLASSICAPI_REMEMBER_PLAYERS = "Запоминать игроков";
|
||||
CLASSICAPI_REMEMBER_PLAYERS_TOOLTIP = "Сохраняет имя, класс, расу и пол встречавшихся вам игроков, чтобы модификации могли показывать их в следующих сеансах. Данные общие для всех ваших персонажей на этом сервере.";
|
||||
CLASSICAPI_SCRIPT_PROFILE = "Профилирование скриптов";
|
||||
CLASSICAPI_SCRIPT_PROFILE_TOOLTIP = "Измеряет время процессора, которое используют модификации. Изменение вступает в силу после перезагрузки интерфейса.";
|
||||
CLASSICAPI_SEPARATE_LARGE_NUMBERS = "Разделять большие числа";
|
||||
CLASSICAPI_SEPARATE_LARGE_NUMBERS_TOOLTIP = "Ставит разделитель между группами из трех цифр, когда модификации форматируют числа с помощью BreakUpLargeNumbers.";
|
||||
CLASSICAPI_SORT_BAGS_RIGHT_TO_LEFT = "Сортировать сумки справа налево";
|
||||
CLASSICAPI_SORT_BAGS_RIGHT_TO_LEFT_TOOLTIP = "При сортировке предметы перемещаются в конец последней сумки, а хлам — в начало рюкзака.";
|
||||
CLASSICAPI_STRICT_INTERACT_RANGE = "Строгая дистанция взаимодействия";
|
||||
CLASSICAPI_STRICT_INTERACT_RANGE_TOOLTIP = "Никогда не выбирает NPC или объект за пределами дистанции взаимодействия. Если параметр выключен, объект, доступный для использования сейчас, выбирается даже на большем расстоянии.";
|
||||
CLASSICAPI_TARGET_NAMEPLATE_ALPHA = "Непрозрачность таблички цели";
|
||||
CLASSICAPI_TARGET_NAMEPLATE_ALPHA_TOOLTIP = "Непрозрачность таблички с именем вашей цели.";
|
||||
CLASSICAPI_UI_SHAKE_STRENGTH = "Сила дрожания интерфейса";
|
||||
CLASSICAPI_UI_SHAKE_STRENGTH_TOOLTIP = "Определяет, насколько сильно эффекты дрожания смещают рамки. 0 отключает дрожание.";
|
||||
CLASSICAPI_UNIT_DIRECTION_TOOLTIP = "Где должна находиться цель, чтобы ее выбрало динамическое выделение. Направление определяется камерой, а не персонажем.";
|
||||
CLASSICAPI_WHEN_YOU_HAVE_A_TARGET = "Когда у вас есть цель";
|
||||
COMBAT_LABEL = "Бой";
|
||||
COUNTDOWN_FOR_COOLDOWNS_TEXT = "Отсчет времени восстановления";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "Вы действительно хотите удалить комплект экипировки \"%s\"?";
|
||||
CONFIRM_SAVE_EQUIPMENT_SET = "Хотите сохранить комплект экипировки \"%s\"?";
|
||||
COPPER_AMOUNT = "%d |4медная монета:медные монеты:медных монет;";
|
||||
COPPER_AMOUNT_SYMBOL = "м";
|
||||
COPPER_AMOUNT_TEXTURE = "%d|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:2:3:0:1|t";
|
||||
DEBUFF_SYMBOL_CURSE = "Пр";
|
||||
DEBUFF_SYMBOL_DISEASE = "Бз";
|
||||
DEBUFF_SYMBOL_MAGIC = "Мг";
|
||||
@@ -21,74 +116,66 @@ ENABLE_INTERACT_TEXT = "Включить клавишу взаимодейств
|
||||
EQUIPMENT_MANAGER = "Комплекты экипировки";
|
||||
EQUIPMENT_MANAGER_IGNORE_SLOT = "Пропустить ячейку";
|
||||
EQUIPMENT_MANAGER_IS_DISABLED = "Управление экипировкой отключено.";
|
||||
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(предмет для ячейки \"%d\" отсутствует)|r";
|
||||
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(не хватает: %d)|r";
|
||||
EQUIPMENT_MANAGER_PLACE_IN_BAGS = "Поместить в сумку";
|
||||
EQUIPMENT_MANAGER_UNIGNORE_SLOT = "Учитывать ячейку";
|
||||
EQUIPMENT_SETS = "Комплекты экипировки: |cFFFFFFFF%s|r";
|
||||
EQUIPMENT_SETS_CANT_RENAME = "Это название уже используется для другого комплекта.";
|
||||
EQUIPMENT_SETS_TOO_MANY = "Вы не можете создавать новые комплекты экипировки.";
|
||||
EQUIPMENT_SET_ASSIGN_TO_SPEC = "Выбрать специализацию:";
|
||||
EQUIPMENT_SET_ASSIGN_TO_SPEC = "Назначить специализации:";
|
||||
EQUIPMENT_SET_EDIT = "Изменить название/значок";
|
||||
EQUIPMENT_SET_SETTINGS = "Параметры";
|
||||
EQUIPSET_EQUIP = "Надеть";
|
||||
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "Не удалось заменить комплект обмундирования. Инвентарь переполнен.";
|
||||
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "Некоторые предметы из набора \"%s\" не будут экипированы, поскольку вы участвуете в бою.";
|
||||
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "Недостает одного или более предметов из набора \"%s\".";
|
||||
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "Не удалось сменить экипировку. Инвентарь переполнен.";
|
||||
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "Некоторые предметы из комплекта \"%s\" нельзя сменить, поскольку вы участвуете в бою.";
|
||||
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "Один или несколько предметов из комплекта \"%s\" отсутствуют и не были надеты.";
|
||||
EVENTTRACE_APPLY_DEFAULT_FILTER = "Применить фильтр по умолчанию";
|
||||
EVENTTRACE_ARG_FMT = "Арг. %d:";
|
||||
EVENTTRACE_BUTTON_DISABLE_FILTERS = "Снять выделение";
|
||||
EVENTTRACE_ARG_FMT = "Аргумент %d:";
|
||||
EVENTTRACE_BUTTON_DISABLE_FILTERS = "Снять все";
|
||||
EVENTTRACE_BUTTON_DISCARD_FILTER = "Сбросить все";
|
||||
EVENTTRACE_BUTTON_ENABLE_FILTERS = "Выделить все";
|
||||
EVENTTRACE_BUTTON_ENABLE_FILTERS = "Выбрать все";
|
||||
EVENTTRACE_BUTTON_MARKER = "Маркер";
|
||||
EVENTTRACE_BUTTON_PAUSE = "Пауза";
|
||||
EVENTTRACE_BUTTON_PLAY = "Играть";
|
||||
EVENTTRACE_BUTTON_PLAY = "Продолжить";
|
||||
EVENTTRACE_FILTER_HEADER = "Фильтр";
|
||||
EVENTTRACE_HEADER = "Журнал события";
|
||||
EVENTTRACE_LOG_CR_EVENTS = "Отображать глобальные события по регистрации";
|
||||
EVENTTRACE_HEADER = "Журнал событий";
|
||||
EVENTTRACE_LOG_CR_EVENTS = "Показывать события глобального реестра";
|
||||
EVENTTRACE_LOG_DISCARD = "Журнал сброшен";
|
||||
EVENTTRACE_LOG_HEADER = "Журнал";
|
||||
EVENTTRACE_LOG_PAUSE = "Регистрация участников события приостановлена";
|
||||
EVENTTRACE_LOG_PAUSE_WHILE_HIDDEN = "Фиксация событий прекращается в свернутом состоянии";
|
||||
EVENTTRACE_LOG_START = "Регистрация участников события началась";
|
||||
EVENTTRACE_LOG_WHEN_HIDDEN = "Фиксировать события в свернутом состоянии";
|
||||
EVENTTRACE_LOG_PAUSE = "Запись событий приостановлена";
|
||||
EVENTTRACE_LOG_PAUSE_WHILE_HIDDEN = "Запись событий приостановлена, пока окно скрыто";
|
||||
EVENTTRACE_LOG_START = "Запись событий начата";
|
||||
EVENTTRACE_LOG_WHEN_HIDDEN = "Записывать события, когда окно скрыто";
|
||||
EVENTTRACE_MARKER = "Маркер";
|
||||
EVENTTRACE_MESSAGE_FORMAT = "--- %s ---";
|
||||
EVENTTRACE_OPTIONS = "Параметры";
|
||||
EVENTTRACE_RESULTS = "Результаты: %d";
|
||||
EVENTTRACE_SHOW_ARGUMENTS = "Отображать аргументы события";
|
||||
EVENTTRACE_SHOW_TIMESTAMP = "Отображать метку времени события";
|
||||
EVENTTRACE_RESULTS = "Результатов: %d";
|
||||
EVENTTRACE_SHOW_ARGUMENTS = "Показывать аргументы событий";
|
||||
EVENTTRACE_SHOW_TIMESTAMP = "Показывать время событий";
|
||||
EVENTTRACE_TIMESTAMP = "Время:";
|
||||
CLEAR_FOCUS = "Отменить запоминание цели";
|
||||
INTERACT_ICONS_DEFAULT = "Только NPC (по умолчанию)";
|
||||
INTERACT_ICONS_OPTION = "Значки клавиш взаимодействия";
|
||||
INTERACT_ICONS_SHOW_ALL = "Показать все";
|
||||
INTERACT_ICONS_OPTION = "Значки клавиши взаимодействия";
|
||||
INTERACT_ICONS_SHOW_ALL = "Показывать все";
|
||||
INTERACT_ICONS_SHOW_NONE = "Не показывать";
|
||||
OPTION_TOOLTIP_ACTION_BUTTON_USE_KEY_DOWN = "Способности будут задействоваться, когда пользователь нажимает, а не отпускает клавишу.";
|
||||
OPTION_TOOLTIP_ACTION_TARGETING = "Система захвата целей, динамически выбирающая цели в поле зрения. Работает параллельно со стандартной системой наведения.";
|
||||
OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "Отображение обратного отсчета для времени восстановления на панели команд.";
|
||||
OPTION_TOOLTIP_ENABLE_INTERACT = "Позволяет взаимодействовать с NPC и объектами по нажатию клавиши.";
|
||||
OPTION_TOOLTIP_INTERACT_ICONS = "Определяет, какие значки появляются над NPC и объектами, если включен параметр клавиши взаимодействия.";
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Прекращение автоматической атаки при смене цели.";
|
||||
OPTION_TOOLTIP_ACTION_BUTTON_USE_KEY_DOWN = "Способности на панели команд срабатывают при нажатии клавиши, а не при ее отпускании.";
|
||||
OPTION_TOOLTIP_ACTION_TARGETING = "Система выделения, которая динамически выбирает противников в зависимости от направления взгляда. Работает вместе со стандартной системой выделения.";
|
||||
OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "Показывать числовой отсчет времени восстановления на панели команд.";
|
||||
OPTION_TOOLTIP_ENABLE_INTERACT = "Позволяет взаимодействовать с NPC и объектами нажатием клавиши.";
|
||||
OPTION_TOOLTIP_INTERACT_ICONS = "Определяет, какие значки появляются над NPC и объектами, когда включена клавиша взаимодействия.";
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Прекращать автоатаку при смене цели.";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<ПКМ, чтобы отвязать>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Назначьте клавишу \"%s\"или нажмите %s для отмены";
|
||||
KEY_BINDING_TOOLTIP = "<ПКМ, чтобы снять привязку>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Назначьте клавишу для \"%s\" или нажмите %s для отмены";
|
||||
SET_FOCUS = "Запомнить цель";
|
||||
FOCUS_TOKEN_NOT_FOUND = "<фокус отсутствует>";
|
||||
FOCUSTARGET = "Запомнить цель";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d |4предмет:предмета:предметов;";
|
||||
ITEMS_EQUIPPED = "%d |4предмет экипирован:предмета экипировано:предметов экипировано;";
|
||||
ITEMS_IN_INVENTORY = "%d |4предмет:предмета:предметов; в сумках";
|
||||
ITEM_SLOTS_IGNORED = "%d |4ячейка пропущена:ячейки пропущены:ячейки пропущены;";
|
||||
ITEM_MISSING = "Предмет для ячейки %s отсутствует";
|
||||
ITEMS_NOT_IN_INVENTORY = "%d |4предмет отсутствует:предмета отсутствует:предметов отсутствуют;";
|
||||
STOP_AUTO_ATTACK = "Прекращение автоатаки";
|
||||
TARGETFOCUS = "Восстановить цель";
|
||||
-- BINDING_NAME_AOELOOT = "AoE Loot";
|
||||
COPPER_AMOUNT = "%d |4медная монета:медные монеты:медных монет;";
|
||||
COPPER_AMOUNT_SYMBOL = "м";
|
||||
GEARSETS_POPUP_TEXT = "Введите название комплекта (не более 16 символов):";
|
||||
GENERIC_FRACTION_STRING = "%d/%d";
|
||||
GOLD_AMOUNT = "%d |4золотая:золотые:золотых;";
|
||||
GOLD_AMOUNT_SYMBOL = "з";
|
||||
GOLD_AMOUNT_TEXTURE = "%d|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:0:1:0:1|t";
|
||||
GOLD_AMOUNT_TEXTURE_STRING = "%s|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:0:1:0:1|t";
|
||||
HOURS_MINUTES_SECONDS = "%.2d:%.2d:%.2d";
|
||||
ICON_FILTER_ALL = "Все значки";
|
||||
ICON_FILTER_ITEM = "Предметы";
|
||||
@@ -106,17 +193,17 @@ ITEM_MOD_ATTACK_POWER_SHORT = "к силе атаки";
|
||||
ITEM_MOD_RANGED_ATTACK_POWER_SHORT = "к силе атаки дальнего боя";
|
||||
ITEM_MOD_CRIT_MELEE_RATING_SHORT = "к показателю критического удара в ближнем бою";
|
||||
ITEM_MOD_CRIT_RANGED_RATING_SHORT = "к показателю критического удара в дальнем бою";
|
||||
ITEM_MOD_HIT_MELEE_RATING_SHORT = "к меткости в ближнем бою";
|
||||
ITEM_MOD_HIT_MELEE_RATING_SHORT = "к меткости в ближнем бою";
|
||||
ITEM_MOD_HIT_RANGED_RATING_SHORT = "к меткости в дальнем бою";
|
||||
ITEM_MOD_HIT_SPELL_RATING_SHORT = "к меткости заклинаний";
|
||||
ITEM_MOD_CRIT_SPELL_RATING_SHORT = "к критическому эффекту заклинаний";
|
||||
ITEM_MOD_SPELL_DAMAGE_DONE_SHORT = "к дополнительному урону";
|
||||
ITEM_MOD_SPELL_HEALING_DONE_SHORT = "к дополнительному исцелению";
|
||||
ITEM_MOD_MANA_REGENERATION_SHORT = "к восполнению маны";
|
||||
ITEM_MOD_DEFENSE_SKILL_RATING_SHORT = "Защита";
|
||||
ITEM_MOD_DODGE_RATING_SHORT = "Уклонение";
|
||||
ITEM_MOD_PARRY_RATING_SHORT = "Парирование";
|
||||
ITEM_MOD_BLOCK_RATING_SHORT = "Блок";
|
||||
ITEM_MOD_DEFENSE_SKILL_RATING_SHORT = "к защите";
|
||||
ITEM_MOD_DODGE_RATING_SHORT = "к уклонению";
|
||||
ITEM_MOD_PARRY_RATING_SHORT = "к парированию";
|
||||
ITEM_MOD_BLOCK_RATING_SHORT = "к блокированию";
|
||||
ITEM_MOD_BLOCK_VALUE_SHORT = "к показателю блокирования";
|
||||
ITEM_MOD_HOLY_SPELL_DAMAGE_DONE_SHORT = "к урону от магии света";
|
||||
ITEM_MOD_FIRE_SPELL_DAMAGE_DONE_SHORT = "к урону от магии огня";
|
||||
@@ -128,6 +215,12 @@ ITEM_MOD_SPELL_PENETRATION_SHORT = "к проникающей способнос
|
||||
ITEM_MOD_ARMOR_PENETRATION_RATING_SHORT = "к пробиванию брони";
|
||||
ITEM_MOD_HEALTH_REGEN_SHORT = "к восполнению здоровья";
|
||||
ITEM_MOD_DAMAGE_PER_SECOND_SHORT = "к урону в секунду";
|
||||
ITEMS_EQUIPPED = "%d |4предмет экипирован:предмета экипировано:предметов экипировано;";
|
||||
ITEMS_IN_INVENTORY = "%d |4предмет:предмета:предметов; в инвентаре";
|
||||
ITEMS_NOT_IN_INVENTORY = "%d |4предмет отсутствует:предмета отсутствует:предметов отсутствует;";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d |4предмет:предмета:предметов;";
|
||||
ITEM_MISSING = "%s отсутствует";
|
||||
ITEM_SLOTS_IGNORED = "%d |4ячейка пропущена:ячейки пропущены:ячеек пропущено;";
|
||||
LARGE_NUMBER_SEPERATOR = "";
|
||||
DECIMAL_SEPERATOR = ",";
|
||||
FIRST_NUMBER = "тыс.";
|
||||
@@ -138,14 +231,18 @@ SECOND_NUMBER_CAP = "М";
|
||||
SECOND_NUMBER_CAP_NO_SPACE = "М";
|
||||
THIRD_NUMBER = "млрд";
|
||||
FOURTH_NUMBER = "трлн";
|
||||
LUA_ERROR = "Ошибка Lua";
|
||||
MINUTES_SECONDS = "%.2d:%.2d";
|
||||
PAPERDOLL_EQUIPMENTMANAGER = "Комплекты экипировки";
|
||||
PAPERDOLL_NEWEQUIPMENTSET = "Новый комплект";
|
||||
PERCENTAGE_STRING = "%d%%";
|
||||
SET_FRIENDNOTE_LABEL = "Заметка о |3-5(%s):";
|
||||
PLAYER_LIST_DELIMITER = ", ";
|
||||
RELOADUI = "Перезагрузить интерфейс";
|
||||
SET_FRIENDNOTE_LABEL = "Задать заметку для %s:";
|
||||
SET_NOTE = "Добавить заметку";
|
||||
SILVER_AMOUNT = "%d |4серебряная:серебряные:серебряных;";
|
||||
SILVER_AMOUNT_SYMBOL = "с";
|
||||
SILVER_AMOUNT_TEXTURE = "%d|TInterface\\MoneyFrame\\UI-MoneyIcons:%d:%d:2:0:4:1:1:2:0:1|t";
|
||||
SLASH_CANCELAURA1 = "/снятьауру";
|
||||
SLASH_CANCELAURA2 = "/cancelaura";
|
||||
SLASH_CANCELFORM1 = "/нормоблик";
|
||||
@@ -219,10 +316,14 @@ SLASH_TARGET_NEAREST_RAID2 = "/targetraid";
|
||||
SLASH_USE1 = "/use";
|
||||
SLASH_USE2 = "/использовать";
|
||||
SOCIAL_LABEL = "Общение";
|
||||
LINK_TRADESKILL_TOOLTIP = "Щелкните здесь, чтобы создать ссылку на вашу профессию.";
|
||||
TEXT_TO_SPEECH = "Текст в речь";
|
||||
TEXT_TO_SPEECH_ADJUST_RATE = "Изменить скорость речи.";
|
||||
STOP_AUTO_ATTACK = "Прекращение автоатаки";
|
||||
SYMBOLIC_NEGATIVE_NUMBER = "-%s";
|
||||
SYMBOLIC_POSITIVE_NUMBER = "+%s";
|
||||
TARGETFOCUS = "Восстановить цель";
|
||||
LINK_TRADESKILL_TOOLTIP = "Щелкните здесь, чтобы создать ссылку на свою профессию.";
|
||||
TEXT_TO_SPEECH = "Синтез речи";
|
||||
TEXT_TO_SPEECH_ADJUST_RATE = "Изменить скорость речи";
|
||||
TEXT_TO_SPEECH_ADJUST_VOLUME = "Громкость речи";
|
||||
TEXT_TO_SPEECH_VOICE_OPTION = "Параметры голоса";
|
||||
TOTAL_MEM_KB_ABBR = "Память: %.0f КБ";
|
||||
TOTAL_MEM_MB_ABBR = "Модификации занимают в памяти: %.2f МБ";
|
||||
TEXT_TO_SPEECH_VOICE_OPTION = "Выбор голоса";
|
||||
TOTAL_MEM_KB_ABBR = "Память модификаций: %.0f КБ";
|
||||
TOTAL_MEM_MB_ABBR = "Память модификаций: %.2f МБ";
|
||||
|
||||
@@ -4,10 +4,102 @@ ACTION_BUTTON_USE_KEY_DOWN = "按下快捷键时施法";
|
||||
ACTION_TARGETING_OPTION = "开启动作瞄准";
|
||||
ADDON_MEM_KB_ABBR = "(%.0f KB) %s";
|
||||
ADDON_MEM_MB_ABBR = "(%.2f MB) %s";
|
||||
ADDON_OPTIONS = "插件设置";
|
||||
BINDING_NAME_AOELOOT = "范围拾取";
|
||||
BINDING_NAME_FOCUSTARGET = "焦点目标";
|
||||
BINDING_NAME_INTERACTMOUSEOVER = "与鼠标悬停处互动";
|
||||
BINDING_NAME_INTERACTTARGET = "与目标互动";
|
||||
BINDING_NAME_TARGETFOCUS = "目标焦点";
|
||||
CLASSICAPI_ACTION_BARS = "动作条";
|
||||
CLASSICAPI_ACTION_TARGETING = "动作瞄准";
|
||||
CLASSICAPI_ADVANCED = "高级";
|
||||
CLASSICAPI_ALL_SOFT_TARGET_ICONS = "所有软目标图标";
|
||||
CLASSICAPI_AUTO_DISMOUNT = "自动下马";
|
||||
CLASSICAPI_AUTO_DISMOUNT_TOOLTIP = "骑乘状态下施放法术、使用物体、拾取、攻击或打开飞行地图时自动下马。";
|
||||
CLASSICAPI_AUTO_STAND = "自动站起";
|
||||
CLASSICAPI_AUTO_STAND_TOOLTIP = "坐下状态下施放法术、拾取或打开飞行地图时自动站起。";
|
||||
CLASSICAPI_AUTO_UNSHIFT = "自动取消形态";
|
||||
CLASSICAPI_AUTO_UNSHIFT_TOOLTIP = "施放当前形态无法施放的法术、使用物品或打开飞行地图时自动取消该形态。同时允许在德鲁伊形态和幽魂之狼形态下与NPC对话和使用物体。";
|
||||
CLASSICAPI_BAGS_AND_ITEMS = "背包与物品";
|
||||
CLASSICAPI_CASTING_AND_ATTACKING = "施法与攻击";
|
||||
CLASSICAPI_COMBAT_LOG_RETENTION = "战斗记录保留时间";
|
||||
CLASSICAPI_COMBAT_LOG_RETENTION_TOOLTIP = "战斗记录为插件保留条目的秒数。";
|
||||
CLASSICAPI_CURSOR = "鼠标指针";
|
||||
CLASSICAPI_DIRECTION_ANY = "任意方向";
|
||||
CLASSICAPI_DIRECTION_CONE = "30度范围内";
|
||||
CLASSICAPI_DIRECTION_FRONT = "正前方";
|
||||
CLASSICAPI_ENEMIES = "敌人";
|
||||
CLASSICAPI_ENEMY_DIRECTION = "敌人方向";
|
||||
CLASSICAPI_ENEMY_ICON = "显示敌人图标";
|
||||
CLASSICAPI_ENEMY_ICON_TOOLTIP = "在动作瞄准选中的敌人上方显示图标。";
|
||||
CLASSICAPI_ENEMY_RANGE = "敌人范围";
|
||||
CLASSICAPI_EXPANDED_CURSORS = "显示扩展鼠标指针";
|
||||
CLASSICAPI_EXPANDED_CURSORS_TOOLTIP = "指向NPC或物体时,鼠标指针会显示你可以对其进行的操作。例如,铁砧表示修理,! 或 ? 表示任务,鱼钩表示你的鱼漂。";
|
||||
CLASSICAPI_FRIEND_DIRECTION = "友方方向";
|
||||
CLASSICAPI_FRIEND_ICON = "显示友方图标";
|
||||
CLASSICAPI_FRIEND_ICON_TOOLTIP = "在友方动作瞄准选中的友方单位上方显示图标。";
|
||||
CLASSICAPI_FRIEND_RANGE = "友方范围";
|
||||
CLASSICAPI_FRIEND_TARGETING = "开启友方动作瞄准";
|
||||
CLASSICAPI_FRIEND_TARGETING_TOOLTIP = "选中你视野方向最近的友方单位。当你没有目标时,你的有益法术会施放给它。";
|
||||
CLASSICAPI_FRIENDS = "友方";
|
||||
CLASSICAPI_GAME_OBJECT_ICON = "在物体上显示交互图标";
|
||||
CLASSICAPI_GAME_OBJECT_ICON_TOOLTIP = "同时在你无法选中的物体上方显示交互图标,例如邮箱和箱子。";
|
||||
CLASSICAPI_HIDE_EQUIPMENT_SETS = "在物品上隐藏装备方案";
|
||||
CLASSICAPI_HIDE_EQUIPMENT_SETS_TOOLTIP = "从物品提示信息中移除装备方案一行。";
|
||||
CLASSICAPI_ICON_SIZE = "图标大小";
|
||||
CLASSICAPI_ICON_SIZE_TOOLTIP = "交互、敌人和友方图标的大小。";
|
||||
CLASSICAPI_IGNORE_BACKPACK = "整理时忽略行囊";
|
||||
CLASSICAPI_IGNORE_BACKPACK_TOOLTIP = "整理背包时,行囊中的物品保持原位。";
|
||||
CLASSICAPI_IGNORE_BANK = "整理时忽略主银行";
|
||||
CLASSICAPI_IGNORE_BANK_TOOLTIP = "整理银行时,主银行栏位中的物品保持原位。";
|
||||
CLASSICAPI_INTERACT_DIRECTION = "交互方向";
|
||||
CLASSICAPI_INTERACT_DIRECTION_TOOLTIP = "NPC或物体需要处于什么方向,交互按键才会选中它。方向以镜头为准,而不是以角色为准。";
|
||||
CLASSICAPI_INTERACT_ICON = "显示交互图标";
|
||||
CLASSICAPI_INTERACT_ICON_TOOLTIP = "在交互按键选中的NPC或物体上方显示图标。";
|
||||
CLASSICAPI_INTERACT_KEY = "交互按键";
|
||||
CLASSICAPI_INTERACT_RANGE = "交互范围";
|
||||
CLASSICAPI_INTERFACE = "界面";
|
||||
CLASSICAPI_KEEP_ACTION_TARGETING = "保持动作瞄准";
|
||||
CLASSICAPI_KEEP_ACTION_TARGETING_TOOLTIP = "当你选定了自己选择的目标时,动作瞄准仍会选中敌人和友方。";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS = "自由目标跟随";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_ENEMY = "软目标敌人";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_FRIEND = "软目标友方";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_NONE = "不跟随";
|
||||
CLASSICAPI_LOOSE_TARGET_FOLLOWS_TOOLTIP = "动作瞄准设置的目标会跟随软目标敌人或软目标友方变化。你自己选择的目标则保持不变。";
|
||||
CLASSICAPI_LOW_PRIORITY_ICONS = "显示低优先级图标";
|
||||
CLASSICAPI_LOW_PRIORITY_ICONS_TOOLTIP = "同时在你无法对其做任何操作的单位上方显示图标,例如队伍成员。该图标为普通指针。";
|
||||
CLASSICAPI_MATCH_INSIDE_NAMES = "匹配名称中间的编辑";
|
||||
CLASSICAPI_MATCH_INSIDE_NAMES_TOOLTIP = "当你在名称中间输入时,建议的名称也会匹配光标之后的字母。";
|
||||
CLASSICAPI_MATCH_THE_COMMAND = "匹配命令";
|
||||
CLASSICAPI_MATCH_THE_COMMAND_TOOLTIP = "只建议符合命令的名称,例如 /gpromote 时只建议公会成员。关闭时,会建议你认识的所有名称。";
|
||||
CLASSICAPI_MOTION = "动态效果";
|
||||
CLASSICAPI_NAME_SUGGESTIONS = "名称建议";
|
||||
CLASSICAPI_NAMEPLATES = "姓名板";
|
||||
CLASSICAPI_NUMBERS = "数字";
|
||||
CLASSICAPI_OPTIONS_ABOUT = "ClassicAPI为游戏添加的功能设置。每个选项对应一项设置,修改后立即生效。";
|
||||
CLASSICAPI_OTHER_NAMEPLATE_ALPHA = "其他姓名板不透明度";
|
||||
CLASSICAPI_OTHER_NAMEPLATE_ALPHA_TOOLTIP = "非目标单位姓名板的不透明度。";
|
||||
CLASSICAPI_PREFER_YOUR_TARGET = "优先你的目标";
|
||||
CLASSICAPI_PREFER_YOUR_TARGET_TOOLTIP = "当你选择的目标可以被选中时,动作瞄准会优先选中它。关闭时,总是选中最近的单位。";
|
||||
CLASSICAPI_RANGE_TOOLTIP = "最大距离(码)。不能大于targetNearestDistance设置。";
|
||||
CLASSICAPI_RECENT_WHISPERS_FIRST = "优先最近的密语";
|
||||
CLASSICAPI_RECENT_WHISPERS_FIRST_TOOLTIP = "名称建议从你最近密语过的玩家开始。";
|
||||
CLASSICAPI_REMEMBER_PLAYERS = "记住玩家";
|
||||
CLASSICAPI_REMEMBER_PLAYERS_TOOLTIP = "保存你遇到的玩家的名字、职业、种族和性别,以便插件在以后的游戏中显示。你在该服务器的所有角色共享这些数据。";
|
||||
CLASSICAPI_SCRIPT_PROFILE = "脚本性能分析";
|
||||
CLASSICAPI_SCRIPT_PROFILE_TOOLTIP = "统计插件占用的CPU时间。更改将在你重载界面后生效。";
|
||||
CLASSICAPI_SEPARATE_LARGE_NUMBERS = "分隔大数字";
|
||||
CLASSICAPI_SEPARATE_LARGE_NUMBERS_TOOLTIP = "当插件使用BreakUpLargeNumbers格式化数字时,每三位数字之间添加分隔符,例如1,234,567。";
|
||||
CLASSICAPI_SORT_BAGS_RIGHT_TO_LEFT = "从右到左整理背包";
|
||||
CLASSICAPI_SORT_BAGS_RIGHT_TO_LEFT_TOOLTIP = "整理时,将你的物品放到最后一个背包的末尾,将垃圾放到行囊的开头。";
|
||||
CLASSICAPI_STRICT_INTERACT_RANGE = "严格交互范围";
|
||||
CLASSICAPI_STRICT_INTERACT_RANGE_TOOLTIP = "绝不选中超出交互范围的NPC或物体。关闭时,即使超出范围,也会选中当前可以交互的目标。";
|
||||
CLASSICAPI_TARGET_NAMEPLATE_ALPHA = "目标姓名板不透明度";
|
||||
CLASSICAPI_TARGET_NAMEPLATE_ALPHA_TOOLTIP = "你目标姓名板的不透明度。";
|
||||
CLASSICAPI_UI_SHAKE_STRENGTH = "界面震动强度";
|
||||
CLASSICAPI_UI_SHAKE_STRENGTH_TOOLTIP = "缩放震动效果移动框架的距离。0表示关闭震动。";
|
||||
CLASSICAPI_UNIT_DIRECTION_TOOLTIP = "单位需要处于什么方向,动作瞄准才会选中它。方向以镜头为准,而不是以角色为准。";
|
||||
CLASSICAPI_WHEN_YOU_HAVE_A_TARGET = "当你拥有目标时";
|
||||
COMBAT_LABEL = "战斗";
|
||||
COUNTDOWN_FOR_COOLDOWNS_TEXT = "显示冷却时间";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "你确认要删除装备方案%s吗?";
|
||||
@@ -74,6 +166,7 @@ KEY_BINDING_TOOLTIP = "<右键解除键位>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "设置 \"%s\"的快捷键,或者按 %s 取消";
|
||||
SET_FOCUS = "设置焦点";
|
||||
FOCUSTARGET = "设定焦点目标";
|
||||
FOCUS_TOKEN_NOT_FOUND = "<无焦点>";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d件物品";
|
||||
ITEMS_EQUIPPED = "已装备%d件物品";
|
||||
ITEMS_IN_INVENTORY = "背包中有%d件物品";
|
||||
@@ -82,7 +175,6 @@ ITEM_MISSING = "%s缺失";
|
||||
ITEMS_NOT_IN_INVENTORY = "缺少%d件物品";
|
||||
STOP_AUTO_ATTACK = "停止自动攻击";
|
||||
TARGETFOCUS = "选中焦点目标";
|
||||
-- BINDING_NAME_AOELOOT = "AoE Loot";
|
||||
COPPER_AMOUNT = "%d铜币";
|
||||
COPPER_AMOUNT_SYMBOL = "铜";
|
||||
GEARSETS_POPUP_TEXT = "输入方案名称(最多16个字符):";
|
||||
@@ -141,6 +233,8 @@ FOURTH_NUMBER = "兆";
|
||||
MINUTES_SECONDS = "%.2d:%.2d";
|
||||
PAPERDOLL_EQUIPMENTMANAGER = "装备管理";
|
||||
PAPERDOLL_NEWEQUIPMENTSET = "新的方案";
|
||||
PLAYER_LIST_DELIMITER = "、";
|
||||
RELOADUI = "重载界面";
|
||||
PERCENTAGE_STRING = "%d%%";
|
||||
SET_FRIENDNOTE_LABEL = "为%s设置备注:";
|
||||
SET_NOTE = "设置备注";
|
||||
@@ -220,6 +314,7 @@ SLASH_USE1 = "/use";
|
||||
SLASH_USE2 = "/use";
|
||||
SOCIAL_LABEL = "社交";
|
||||
LINK_TRADESKILL_TOOLTIP = "点击这里以创建一个链接到你的专业的快捷方式。";
|
||||
LUA_ERROR = "Lua错误";
|
||||
TEXT_TO_SPEECH = "文本转语音";
|
||||
TEXT_TO_SPEECH_ADJUST_RATE = "调节讲话速度";
|
||||
TEXT_TO_SPEECH_ADJUST_VOLUME = "音量";
|
||||
|
||||
@@ -8,6 +8,9 @@ BINDING_NAME_FOCUSTARGET = "鎖定目標";
|
||||
BINDING_NAME_INTERACTMOUSEOVER = "與滑鼠指定的目標互動";
|
||||
BINDING_NAME_INTERACTTARGET = "與目標互動";
|
||||
BINDING_NAME_TARGETFOCUS = "選定鎖定目標";
|
||||
CLASSICAPI_CURSOR = "游標";
|
||||
CLASSICAPI_EXPANDED_CURSORS = "顯示擴充游標";
|
||||
CLASSICAPI_EXPANDED_CURSORS_TOOLTIP = "指向NPC或物件時,游標會顯示你可以對其進行的操作。例如,鐵砧表示修理,! 或 ? 表示任務,魚鉤表示你的魚漂。";
|
||||
COMBAT_LABEL = "戰鬥";
|
||||
COUNTDOWN_FOR_COOLDOWNS_TEXT = "顯示冷卻時間";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "你確定要刪除裝備設定:%s嗎?";
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Embed {
|
||||
@@ -30,4 +32,15 @@ struct Bytes {
|
||||
// resource is missing, does not inflate or split, or has no file `index`.
|
||||
Bytes File(int id, size_t index);
|
||||
|
||||
// The entry of `files`, a manifest in the generated `embedded_resources.h`,
|
||||
// whose path is the game path `path` (Common::SamePath); null when none is.
|
||||
template <class Entry, size_t N>
|
||||
const Entry *FindFile(const Entry (&files)[N], const char *path) {
|
||||
for (const Entry &file : files) {
|
||||
if (Common::SamePath(path, file.path))
|
||||
return &file;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Embed
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -217,8 +217,7 @@ void Register() {
|
||||
{"Standard", static_cast<int>(Variant::Standard)},
|
||||
{"UrlSafe", static_cast<int>(Variant::UrlSafe)},
|
||||
};
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "Base64Variant",
|
||||
kBase64Variant, 2);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "Base64Variant", kBase64Variant);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -305,8 +305,7 @@ void Register() {
|
||||
{"Zlib", static_cast<int>(Method::Zlib)},
|
||||
{"Gzip", static_cast<int>(Method::Gzip)},
|
||||
};
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "CompressionMethod",
|
||||
kCompressionMethod, 3);
|
||||
Game::Lua::RegisterIntegerEnum("Enum", "CompressionMethod", kCompressionMethod);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -224,9 +224,7 @@ static void RegisterLuaFunctions() {
|
||||
// 4.3.4's switch for the item-tooltip line; dedups by name on /reload.
|
||||
g_hideSetsCVar = CVar::Factory::Register("dontShowEquipmentSetsOnItems", "Don't show which equipment sets an item is associated with", Offsets::CVAR_CATEGORY_GAME, "0");
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY),
|
||||
g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_GAMETOOLTIP_METHOD_REGISTRY), g_methods);
|
||||
}
|
||||
|
||||
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
+81
-19
@@ -55,6 +55,10 @@ struct Reservation {
|
||||
int slot; // -1 until claimed
|
||||
const Game::Doc::Event *doc; // API documentation; nullptr = undocumented
|
||||
int fixedId; // an `EngineName`'s engine id (its slot for good); -1 = claimed
|
||||
// The SStrDup'd name `Claim` wrote into `slot`'s entry; nullptr while
|
||||
// unclaimed. `RetryClaims` compares it against the live entry to notice
|
||||
// that the engine rebuilt the table underneath a cached slot.
|
||||
const char *claimed;
|
||||
};
|
||||
Reservation g_reserved[MAX_RESERVED];
|
||||
int g_reservedCount = 0;
|
||||
@@ -63,7 +67,7 @@ bool g_writesEnabled = false;
|
||||
// claim) is logged once per session instead of silently dropping the
|
||||
// event — the failure mode that would otherwise rot unnoticed. Re-armed on
|
||||
// `/reload` (the table is rebuilt fresh). In practice this never triggers:
|
||||
// the engine leaves ~115 claimable gaps below 549 (see `TryClaim`) against
|
||||
// the engine leaves ~115 claimable gaps below 549 (see `Claim`) against
|
||||
// our ~77 reservations, and `Grow` adds room past that. The log is
|
||||
// purely a backstop if some future install somehow drains the gap pool.
|
||||
bool g_warnedTableFull = false;
|
||||
@@ -147,23 +151,39 @@ int ClaimDemand() {
|
||||
// tail. Below 549 only nampower writes, at 369 and 540, both before any Lua
|
||||
// runs, so skip-occupied steps over them. Claiming low keeps us out of
|
||||
// everyone's way and independent of their clamp / hook order.
|
||||
int TryClaim(const char *eventName) {
|
||||
//
|
||||
// Claims only into the IN-GAME table. `CGGameUI::Initialize` (0x0048FE00)
|
||||
// calls `RegisterScriptFunctions` (our `EnableWrites` hook) and only THEN
|
||||
// `SetScriptEventTable(0xBE1198, 0x225)`, so between the two the live table
|
||||
// is still glue's 26-entry one. A `Frame::RegisterEvent` in that window
|
||||
// (a third-party DLL registering a frame from its own LoadScriptFunctions
|
||||
// hook — issue #134) used to grow the glue table and claim into it; the
|
||||
// engine then freed that buffer and our cached ids silently aliased engine
|
||||
// events in the real table. The in-game build always passes 0x225 names
|
||||
// (clamps by other DLLs only enlarge it), so a smaller count is not it.
|
||||
bool GameTableInstalled(int count) {
|
||||
return count >= Offsets::EVENT_NAME_TABLE_STATIC_COUNT;
|
||||
}
|
||||
|
||||
void Claim(Reservation &r) {
|
||||
if (!g_writesEnabled)
|
||||
return -1;
|
||||
return;
|
||||
auto *base = Game::Read<uint8_t *>(Offsets::VAR_EVENT_TABLE_BASE_PTR);
|
||||
const int count = Game::Read<int>(Offsets::VAR_EVENT_TABLE_COUNT);
|
||||
if (base == nullptr || count <= 0)
|
||||
return -1;
|
||||
if (base == nullptr || !GameTableInstalled(count))
|
||||
return;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (Claimable(base, i)) {
|
||||
auto **namePtr = Game::Ptr<const char *>(
|
||||
base + i * Offsets::EVENT_ENTRY_STRIDE,
|
||||
Offsets::OFF_EVENT_ENTRY_NAME);
|
||||
char *copy = SStrDup(eventName);
|
||||
char *copy = SStrDup(r.name);
|
||||
if (copy == nullptr)
|
||||
return -1;
|
||||
return;
|
||||
*namePtr = copy;
|
||||
return i;
|
||||
r.slot = i;
|
||||
r.claimed = copy;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Writes are enabled and the table is valid, but every slot is taken —
|
||||
@@ -173,9 +193,39 @@ int TryClaim(const char *eventName) {
|
||||
Debug::Log::Printf(
|
||||
"[event] table full (%d slots, no NULL left) — cannot claim '%s'"
|
||||
" and any further custom events; even Grow couldn't make room.",
|
||||
count, eventName ? eventName : "?");
|
||||
count, r.name);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// True when a claimed reservation's slot no longer holds its name: the
|
||||
// engine rebuilt the table (every entry re-`SStrDup`'d at a fresh buffer)
|
||||
// without our `PrepareForReload` seeing it. The pointer test is the cheap
|
||||
// common case; the strcmp covers an allocator handing the engine our freed
|
||||
// block for a different name.
|
||||
bool Stale(const Reservation &r) {
|
||||
if (r.claimed == nullptr)
|
||||
return false;
|
||||
auto *base = Game::Read<uint8_t *>(Offsets::VAR_EVENT_TABLE_BASE_PTR);
|
||||
const int count = Game::Read<int>(Offsets::VAR_EVENT_TABLE_COUNT);
|
||||
if (base == nullptr || r.slot >= count)
|
||||
return true;
|
||||
const char *live = Game::Read<const char *>(
|
||||
base + r.slot * Offsets::EVENT_ENTRY_STRIDE, Offsets::OFF_EVENT_ENTRY_NAME);
|
||||
if (live == r.claimed)
|
||||
return false;
|
||||
return live == nullptr || std::strcmp(live, r.name) != 0;
|
||||
}
|
||||
|
||||
// Forget every claimed slot: the table they were claimed in is gone. The
|
||||
// engine freed our name copies with its per-entry teardown, so nothing to
|
||||
// release here. Re-arms the per-build one-shots.
|
||||
void ForgetClaims() {
|
||||
for (int i = 0; i < g_reservedCount; ++i) {
|
||||
g_reserved[i].slot = g_reserved[i].fixedId;
|
||||
g_reserved[i].claimed = nullptr;
|
||||
}
|
||||
g_warnedTableFull = false;
|
||||
g_growLatched = false;
|
||||
}
|
||||
|
||||
// On-demand event-table grow. Correctly TIMED: called once per in-game
|
||||
@@ -205,12 +255,14 @@ int TryClaim(const char *eventName) {
|
||||
void Grow() {
|
||||
if (g_growLatched || !g_writesEnabled)
|
||||
return;
|
||||
g_growLatched = true; // one attempt per build, whether or not it grows
|
||||
|
||||
const int count = Game::Read<int>(Offsets::VAR_EVENT_TABLE_COUNT);
|
||||
auto *base = Game::Read<uint8_t *>(Offsets::VAR_EVENT_TABLE_BASE_PTR);
|
||||
if (base == nullptr || !GameTableInstalled(count))
|
||||
return; // still glue's table — the in-game build hasn't run yet
|
||||
g_growLatched = true; // one attempt per build, whether or not it grows
|
||||
|
||||
const int demand = ClaimDemand();
|
||||
if (base == nullptr || count <= 0 || demand <= 0)
|
||||
if (demand <= 0)
|
||||
return;
|
||||
|
||||
int nulls = 0;
|
||||
@@ -320,6 +372,7 @@ AutoReserve::AutoReserve(const char *name, const Game::Doc::Event *doc) {
|
||||
g_reserved[g_reservedCount].slot = -1;
|
||||
g_reserved[g_reservedCount].doc = doc;
|
||||
g_reserved[g_reservedCount].fixedId = -1;
|
||||
g_reserved[g_reservedCount].claimed = nullptr;
|
||||
index_ = g_reservedCount;
|
||||
++g_reservedCount;
|
||||
}
|
||||
@@ -339,7 +392,7 @@ EngineName::EngineName(int id, const char *name, const Game::Doc::Event *doc) {
|
||||
if (*slot != nullptr)
|
||||
return; // the engine names it; never take an engine event's slot
|
||||
*slot = name;
|
||||
g_reserved[g_reservedCount] = {name, id, doc, id};
|
||||
g_reserved[g_reservedCount] = {name, id, doc, id, nullptr};
|
||||
++g_reservedCount;
|
||||
}
|
||||
|
||||
@@ -428,6 +481,18 @@ void FireValues(int slot, void *L, int firstArgIdx, int nargs) {
|
||||
}
|
||||
|
||||
void RetryClaims() {
|
||||
// A table rebuilt behind our back (no FrameScript_Initialize in between)
|
||||
// leaves every cached slot pointing at whatever engine event now sits at
|
||||
// that id. Check the claims against the live table and start over if any
|
||||
// moved — we run before the original registers, so no frame has joined a
|
||||
// chain in the new table yet.
|
||||
for (int i = 0; i < g_reservedCount; ++i) {
|
||||
if (Stale(g_reserved[i])) {
|
||||
Debug::Log::Printf("[event] table rebuilt under cached slots — re-claiming");
|
||||
ForgetClaims();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Grow the table first if the low gap pool is short (dormant valve).
|
||||
// Runs at most once per table build; on the first call every chain is
|
||||
// still empty (this is the first registration), which is the only safe
|
||||
@@ -435,18 +500,15 @@ void RetryClaims() {
|
||||
Grow();
|
||||
for (int i = 0; i < g_reservedCount; ++i) {
|
||||
if (g_reserved[i].slot < 0)
|
||||
g_reserved[i].slot = TryClaim(g_reserved[i].name);
|
||||
Claim(g_reserved[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void EnableWrites() { g_writesEnabled = true; }
|
||||
|
||||
void PrepareForReload() {
|
||||
for (int i = 0; i < g_reservedCount; ++i)
|
||||
g_reserved[i].slot = g_reserved[i].fixedId;
|
||||
ForgetClaims();
|
||||
g_writesEnabled = false;
|
||||
g_warnedTableFull = false; // fresh table after the rebuild — re-arm
|
||||
g_growLatched = false; // re-arm the one-shot grow for the new table
|
||||
}
|
||||
|
||||
static const Game::ReloadAutoRegister _reloadReg{&PrepareForReload};
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ inline void FireIdSuccess(int eventID, int id, bool success) {
|
||||
// catch any reservations that couldn't claim earlier.
|
||||
void RetryClaims();
|
||||
|
||||
// Internal: permit `TryClaim` to write to the event table. Held
|
||||
// Internal: permit `Claim` to write to the event table. Held
|
||||
// closed until `LoadScriptFunctions_h` returns, so the boot phase
|
||||
// (during which the engine and SuperWoWhook fire many internal
|
||||
// `RegisterEvent` calls) can't trigger our writes. Writing during
|
||||
|
||||
@@ -144,10 +144,11 @@ const Game::Doc::Method kFontDocs[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY),
|
||||
kFontStringMethods, 2, kFontStringDocs, 2);
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FONT_METHOD_REGISTRY), kFontMethods,
|
||||
2, kFontDocs, 2);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), kFontStringMethods,
|
||||
kFontStringDocs);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONT_METHOD_REGISTRY), kFontMethods, kFontDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -181,8 +181,8 @@ const Game::Doc::Method kHtmlDocs[] = {
|
||||
{"GetIndentedWordWrap", &kHtmlGet},
|
||||
};
|
||||
|
||||
template <typename P> void Register(uintptr_t registry, const Game::Doc::Method *docs) {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(registry), P::methods, 2, docs, 2);
|
||||
template <typename P> void Register(uintptr_t registry, Game::Doc::MethodList docs) {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(registry), P::methods, docs);
|
||||
}
|
||||
|
||||
// A font string takes the bit from its font only when its settable mask
|
||||
|
||||
@@ -378,9 +378,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -96,8 +96,8 @@ const Game::Doc::Method kDocs[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), kMethods, 2,
|
||||
kDocs, 2);
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FONTSTRING_METHOD_REGISTRY), kMethods, kDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -324,9 +324,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// A script that re-anchors a region while that region is being laid out no
|
||||
// longer frees the anchor point the layout is reading.
|
||||
//
|
||||
// An edge resolver walks the region's points (FUN_LAYOUT_POINTS_X / Y) and asks
|
||||
// each for its coordinate (VTBL_ANCHOR_NODE_X / Y). When the point's relative
|
||||
// frame is deferred, the getter first ends the deferral with
|
||||
// FUN_REGION_LAYOUT_REALIZE(0), which lays the relative out at once: its
|
||||
// OnSizeChanged runs right there. The getter then reads the point again. If
|
||||
// that script moved the region with SetPoint or ClearAllPoints, the point is
|
||||
// freed, and the read gets heap memory: an access violation in
|
||||
// FUN_REGION_GET_RECT on the "relative frame" the freed point holds (crash
|
||||
// log 2026-11-03: `[ecx+0x3c]`, ecx = 5). The path is easy to reach: Show runs
|
||||
// a frame's OnSizeChanged before its children leave their deferral, and the
|
||||
// ported ScrollBox reads sizes from that handler.
|
||||
//
|
||||
// 3.3.5 fixed it in CFramePoint::GetRelativeRect (0x0049C830): the point is
|
||||
// marked busy around its relative's resolve, a point changed while busy is
|
||||
// flagged, and the getter stops on the flag without reading the point again.
|
||||
// GetFramePointX / Y then return a failure, and GetHorizontalFramePoint /
|
||||
// GetVerticalFramePoint (0x00488ED0 / 0x00488F10) walk the points again, up to
|
||||
// ten times, with the anchors as they are now. 1.12 has no flag word in its
|
||||
// point, and SetPoint never changes a point in place (it frees the old one and
|
||||
// allocates a new one), so "changed" here means "freed": the point's deleting
|
||||
// destructor (VTBL_ANCHOR_NODE_DELETE, every free goes through it) flags a busy
|
||||
// point.
|
||||
//
|
||||
// The getters end the deferral themselves, as 3.3.5 does, and stop when the
|
||||
// point is flagged. Otherwise they call the engine's own getter for the value.
|
||||
// If the resolve deferred the relative again (a Hide in its script), the
|
||||
// getter would end the deferral a second time. 3.3.5 ends it once and then
|
||||
// reads the rect, so the bit is hidden from the engine's getter during the
|
||||
// read. The walks are the engine's own, retried as 3.3.5 retries them.
|
||||
//
|
||||
// Nothing is hooked: the point vtable (VAR_ANCHOR_VTABLE_WITH_RELATIVE) is the
|
||||
// only way to reach the getters and the destructor, and the six edge
|
||||
// resolvers are the only callers of the walks.
|
||||
|
||||
#include "Common.h"
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Frame::AnchorResolve {
|
||||
|
||||
namespace {
|
||||
|
||||
using Coordinate_t = float(__thiscall *)(void *point, float scale);
|
||||
using Walk_t = float(__thiscall *)(void *layout, const int *points, int count);
|
||||
using Delete_t = void *(__thiscall *)(void *point, int flags);
|
||||
using Realize_t = void(__thiscall *)(void *layout, int defer);
|
||||
|
||||
// 3.3.5 GetHorizontalFramePoint / GetVerticalFramePoint: `for (i = 0; i < 10; i++)`.
|
||||
constexpr int kWalkAttempts = 10;
|
||||
|
||||
// A point whose relative is being laid out. Its destructor sets `freed`.
|
||||
struct BusyPoint {
|
||||
void *point;
|
||||
bool freed;
|
||||
BusyPoint *outer;
|
||||
};
|
||||
|
||||
// A point walk in progress. A getter sets `changed` when its point was freed.
|
||||
struct PointWalk {
|
||||
bool changed;
|
||||
PointWalk *outer;
|
||||
};
|
||||
|
||||
BusyPoint *g_busy = nullptr;
|
||||
PointWalk *g_walk = nullptr;
|
||||
|
||||
float Undefined() { return Game::Read<float>(Offsets::VAR_LAYOUT_UNDEFINED); }
|
||||
|
||||
float Coordinate(uintptr_t engine, void *point, float scale) {
|
||||
if (g_walk != nullptr && g_walk->changed)
|
||||
return Undefined(); // 3.3.5 stops the walk at the changed point
|
||||
|
||||
auto *relative = Game::Read<void *>(point, Offsets::OFF_ANCHOR_NODE_RELATIVE_TO);
|
||||
if ((Game::Read<uint32_t>(relative, Offsets::OFF_LAYOUT_FLAGS) & Offsets::LAYOUT_FLAG_DEFERRED) != 0) {
|
||||
BusyPoint busy{point, false, g_busy};
|
||||
g_busy = &busy;
|
||||
reinterpret_cast<Realize_t>(Offsets::FUN_REGION_LAYOUT_REALIZE)(relative, 0);
|
||||
g_busy = busy.outer;
|
||||
if (busy.freed) {
|
||||
if (g_walk != nullptr)
|
||||
g_walk->changed = true;
|
||||
return Undefined();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t &flags = Game::Ref<uint32_t>(relative, Offsets::OFF_LAYOUT_FLAGS);
|
||||
const uint32_t deferred = flags & Offsets::LAYOUT_FLAG_DEFERRED;
|
||||
flags &= ~deferred;
|
||||
const float value = reinterpret_cast<Coordinate_t>(engine)(point, scale);
|
||||
flags |= deferred;
|
||||
return value;
|
||||
}
|
||||
|
||||
float __fastcall PointX(void *point, void * /*edx*/, float scale) {
|
||||
return Coordinate(Offsets::FUN_ANCHOR_NODE_X, point, scale);
|
||||
}
|
||||
|
||||
float __fastcall PointY(void *point, void * /*edx*/, float scale) {
|
||||
return Coordinate(Offsets::FUN_ANCHOR_NODE_Y, point, scale);
|
||||
}
|
||||
|
||||
void *__fastcall DeletePoint(void *point, void * /*edx*/, int flags) {
|
||||
for (BusyPoint *busy = g_busy; busy != nullptr; busy = busy->outer) {
|
||||
if (busy->point == point)
|
||||
busy->freed = true;
|
||||
}
|
||||
return reinterpret_cast<Delete_t>(Offsets::FUN_ANCHOR_NODE_DELETE)(point, flags);
|
||||
}
|
||||
|
||||
float Walk(uintptr_t engine, void *layout, const int *points, int count) {
|
||||
for (int attempt = 0; attempt < kWalkAttempts; ++attempt) {
|
||||
PointWalk walk{false, g_walk};
|
||||
g_walk = &walk;
|
||||
const float value = reinterpret_cast<Walk_t>(engine)(layout, points, count);
|
||||
g_walk = walk.outer;
|
||||
if (!walk.changed)
|
||||
return value;
|
||||
}
|
||||
return Undefined();
|
||||
}
|
||||
|
||||
float __fastcall WalkX(void *layout, void * /*edx*/, const int *points, int count) {
|
||||
return Walk(Offsets::FUN_LAYOUT_POINTS_X, layout, points, count);
|
||||
}
|
||||
|
||||
float __fastcall WalkY(void *layout, void * /*edx*/, const int *points, int count) {
|
||||
return Walk(Offsets::FUN_LAYOUT_POINTS_Y, layout, points, count);
|
||||
}
|
||||
|
||||
void PatchCode() {
|
||||
constexpr uintptr_t vtable = Offsets::VAR_ANCHOR_VTABLE_WITH_RELATIVE;
|
||||
Common::RedirectSlot(vtable + Offsets::VTBL_ANCHOR_NODE_DELETE, Offsets::FUN_ANCHOR_NODE_DELETE,
|
||||
reinterpret_cast<const void *>(&DeletePoint));
|
||||
Common::RedirectSlot(vtable + Offsets::VTBL_ANCHOR_NODE_X, Offsets::FUN_ANCHOR_NODE_X,
|
||||
reinterpret_cast<const void *>(&PointX));
|
||||
Common::RedirectSlot(vtable + Offsets::VTBL_ANCHOR_NODE_Y, Offsets::FUN_ANCHOR_NODE_Y,
|
||||
reinterpret_cast<const void *>(&PointY));
|
||||
|
||||
const auto walkX = reinterpret_cast<const void *>(&WalkX);
|
||||
Common::RedirectCall(Offsets::CALL_LAYOUT_LEFT_POINTS, Offsets::FUN_LAYOUT_POINTS_X, walkX);
|
||||
Common::RedirectCall(Offsets::CALL_LAYOUT_CENTER_X_POINTS, Offsets::FUN_LAYOUT_POINTS_X, walkX);
|
||||
Common::RedirectCall(Offsets::CALL_LAYOUT_RIGHT_POINTS, Offsets::FUN_LAYOUT_POINTS_X, walkX);
|
||||
const auto walkY = reinterpret_cast<const void *>(&WalkY);
|
||||
Common::RedirectCall(Offsets::CALL_LAYOUT_TOP_POINTS, Offsets::FUN_LAYOUT_POINTS_Y, walkY);
|
||||
Common::RedirectCall(Offsets::CALL_LAYOUT_CENTER_Y_POINTS, Offsets::FUN_LAYOUT_POINTS_Y, walkY);
|
||||
Common::RedirectCall(Offsets::CALL_LAYOUT_BOTTOM_POINTS, Offsets::FUN_LAYOUT_POINTS_Y, walkY);
|
||||
}
|
||||
|
||||
// Each pass runs before its GlueXML or FrameXML loads; the patch is idempotent.
|
||||
const Game::GlueModuleAutoRegister _gluePatch{&PatchCode};
|
||||
const Game::ModuleAutoRegister _autoreg{&PatchCode};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Frame::AnchorResolve
|
||||
@@ -1165,9 +1165,7 @@ const Game::Lua::FrameMethodEntry g_frameMethods[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY),
|
||||
g_frameMethods,
|
||||
static_cast<int>(sizeof(g_frameMethods) / sizeof(g_frameMethods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_frameMethods);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -117,9 +117,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -186,9 +186,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -102,8 +102,7 @@ const Game::Lua::FrameMethodEntry g_methods[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_BUTTON_METHOD_REGISTRY), g_methods);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -453,9 +453,7 @@ void RegisterLuaFunctions() {
|
||||
Common::ReplaceWithCall(Offsets::CALL_FRAME_DRAW_SELF_CHILD, kDrawChildCall, sizeof kDrawChildCall,
|
||||
reinterpret_cast<const void *>(&DrawChildThunk));
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_FRAME_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -1588,9 +1588,7 @@ void RegisterLuaFunctions() {
|
||||
reinterpret_cast<RegisterFactory_t>(Offsets::FUN_FRAMEXML_REGISTER_FACTORY)(
|
||||
"Cooldown", &CreateCooldown, 0);
|
||||
Api::Documentation::NameWidgetSystem(g_hash, "FrameAPICooldown");
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
g_hash, g_methods, static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])),
|
||||
g_methodDocs, static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
Game::Lua::RegisterFrameMethods(g_hash, g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -53,9 +53,6 @@ using SetAttr_t = void(__thiscall *)(void *node, const char *name, const char *v
|
||||
using Build_t = void *(__fastcall *)(const void *node, void *parent, void *status);
|
||||
using StatusReset_t = void(__thiscall *)(void *self);
|
||||
|
||||
// LUA_TNONE: lua_type of an index past the top.
|
||||
constexpr int kTypeNone = -1;
|
||||
|
||||
// 4.3.4's usage line; vanilla's (0x00872F60) has no `id`.
|
||||
constexpr const char *kUsage =
|
||||
"Usage: CreateFrame(\"frameType\" [, \"name\"] [, parent] [, \"template\"] [, id])";
|
||||
@@ -102,7 +99,7 @@ int __fastcall CreateFrame_h(void *L) {
|
||||
|
||||
const int parentType = Type(L, 3);
|
||||
if (!IsString(L, 1) ||
|
||||
(parentType != kTypeNone && parentType != TYPE_NIL && parentType != TYPE_TABLE)) {
|
||||
(parentType != TYPE_NONE && parentType != TYPE_NIL && parentType != TYPE_TABLE)) {
|
||||
Error(L, kUsage);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -111,9 +111,7 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
@@ -221,9 +221,8 @@ const Game::Doc::Method g_methodDocs[] = {
|
||||
};
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterFrameMethods(reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods,
|
||||
static_cast<int>(sizeof(g_methods) / sizeof(g_methods[0])), g_methodDocs,
|
||||
static_cast<int>(sizeof(g_methodDocs) / sizeof(g_methodDocs[0])));
|
||||
Game::Lua::RegisterFrameMethods(
|
||||
reinterpret_cast<void *>(Offsets::VAR_REGION_METHOD_REGISTRY), g_methods, g_methodDocs);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user