режим предатель, преезд всего GlueXML в patch-ruRU-4

This commit is contained in:
2026-04-07 23:27:24 +04:00
parent e4a0cc2c36
commit 0dbe8c95a2
395 changed files with 10324 additions and 21739 deletions
BIN
View File
Binary file not shown.
+339
View File
@@ -0,0 +1,339 @@
"""
Wow.exe binary patcher for MoonWell custom features.
Patches:
1. Script_CreateCharacter: reads lua_tonumber(L, 2) as outfitId,
stores to global, passes to CMSG_CHAR_CREATE packet instead of 0.
2. GetCharacterInfo: adds 11th return value — charFlags as a Lua number,
read from struct_offset +0x170 (same field as ghost flag 0x2000).
Lua checks bit 0x40000000 (CHARACTER_FLAG_UNK31) for traitor status.
Usage:
python make_modes_in_exe.py <path_to_Wow.exe>
A backup (Wow.exe.bak) is created before patching.
"""
import struct
import sys
import shutil
from pathlib import Path
# ---------------------------------------------------------------------------
# PE helpers
# ---------------------------------------------------------------------------
def _u16(data, off):
return struct.unpack_from("<H", data, off)[0]
def _u32(data, off):
return struct.unpack_from("<I", data, off)[0]
def parse_pe_sections(data):
if data[0:2] != b"MZ":
raise ValueError("Not a valid PE file (missing MZ)")
pe_off = _u32(data, 0x3C)
if data[pe_off:pe_off + 4] != b"PE\x00\x00":
raise ValueError("Not a valid PE file (missing PE signature)")
coff = pe_off + 4
num_secs = _u16(data, coff + 2)
opt_size = _u16(data, coff + 16)
opt = coff + 20
magic = _u16(data, opt)
if magic == 0x10b:
image_base = _u32(data, opt + 28)
elif magic == 0x20b:
image_base = struct.unpack_from("<Q", data, opt + 24)[0]
else:
raise ValueError(f"Unknown PE magic: 0x{magic:04x}")
sec_table = opt + opt_size
sections = []
for i in range(num_secs):
o = sec_table + i * 40
name = data[o:o + 8].rstrip(b"\x00").decode("ascii", errors="replace")
virt_size = _u32(data, o + 8)
virt_addr = _u32(data, o + 12)
raw_size = _u32(data, o + 16)
raw_off = _u32(data, o + 20)
sections.append((name, virt_addr, virt_size, raw_off, raw_size))
return image_base, sections
def va_to_offset(va, sections, image_base):
rva = va - image_base
for name, va0, vs, ro, _ in sections:
if va0 <= rva < va0 + vs:
return ro + (rva - va0)
raise ValueError(f"VA 0x{va:08x} not in any section")
def offset_to_va(off, sections, image_base):
for name, va0, _, ro, rs in sections:
if ro <= off < ro + rs:
return image_base + va0 + (off - ro)
raise ValueError(f"File offset 0x{off:08x} not in any section")
def find_cave(data, sections, image_base, size, search_from_va):
"""Return VA of the first run of >= size consecutive CC bytes after search_from_va."""
text = next((s for s in sections if s[0] == ".text"), None)
if not text is None:
_, va0, vs, ro, rs = text
else:
raise ValueError("No .text section")
rva_start = max(search_from_va - image_base, va0)
off_start = ro + (rva_start - va0)
off_end = ro + min(vs, rs)
i = off_start
while i <= off_end - size:
if data[i] != 0xcc:
i += 1
continue
j = i
while j < off_end and data[j] == 0xcc:
j += 1
if j - i >= size:
return offset_to_va(i, sections, image_base)
i = j
raise ValueError(f"No {size}-byte code cave found from VA 0x{search_from_va:08x}")
# ---------------------------------------------------------------------------
# Patch 1 & 2: outfitId in CMSG_CHAR_CREATE (fixed addresses)
# ---------------------------------------------------------------------------
_FIXED_PATCHES = [
(
0x004e0c60,
bytes.fromhex(
"558bec568b75086a0156e8f1d23600"
"83c40885c0740d6a006a0156e860d4"
"360083c40c6a006a0156e853d43600"
"50e8edf6ffff83c41033c05e5dc3cc"
"cccccccc"
),
bytes.fromhex(
"558bec568b75086a0256e8c1d33600"
"db1c245883c404a221 42ac006a006a"
"0156e85bd4360050e8f5f6ffff83c4"
"1033c05e5dc38855f08a1521 42ac00"
"8855f4c3"
).replace(b" ", b""), # spaces stripped for readability
"Script_CreateCharacter: read outfitId from Lua arg 2 -> global",
),
(
0x004e042f,
bytes.fromhex("8855f0c645f400"),
bytes.fromhex("e85f08000090 90").replace(b" ", b""),
"Packet builder: CALL helper -> load face + outfitId from global",
),
]
# ---------------------------------------------------------------------------
# Patch 3: GetCharacterInfo — add 11th return value (charFlags as number)
# ---------------------------------------------------------------------------
#
# Assembly context (FUN_004e3170 epilogue, valid branch):
#
# 004e31cd 56 PUSH ESI ← saves caller's ESI
# 004e31ce 8b f0 MOV ESI, EAX ← ESI = char struct ptr
# ... [function body — ESI used throughout for char struct] ...
# 004e32e5 8b 8e 70010000 MOV ECX, [ESI+0x170] ← charFlags (ghost flag check)
# ...
# 004e332d 83 c4 2c ADD ESP, 0x2c ← clean accumulated args ← PATCH HERE
# 004e3330 5e POP ESI ← restores caller's ESI (ESI valid until here)
# 004e3331 b8 0a 00 00 00 MOV EAX, 0xa (dead after patch)
# 004e3336 5f POP EDI ← JMP back target
# 004e3337 8b e5 MOV ESP, EBP
# 004e3339 5d POP EBP
# 004e333a c3 RET
#
# KEY: ESI = char struct ptr up to and including ADD ESP,0x2c (004e332d).
# After POP ESI (004e3330) it is caller's ESI — NOT char struct.
# Previous approach: CALL cave at 004e3331 (after POP ESI) -> ESI already wrong.
# Fix: JMP cave at 004e332d (before POP ESI) -> ESI still valid in cave.
#
# New approach: patch 5 bytes at 004e332d with JMP cave.
# Old bytes: 83 c4 2c (ADD ESP,0x2c) + 5e (POP ESI) + b8 (MOV EAX first byte)
# New bytes: e9 XX XX XX XX JMP cave
# The 4 dead bytes at 004e3332-004e3335 are unreachable.
#
# Cave (30 bytes, found automatically via CC scan over entire .text):
# 83 ec 08 SUB ESP, 8 ; make room for double
# db 86 70 01 00 00 FILD [ESI+0x170] ; load charFlags into FPU (ESI valid here!)
# dd 1c 24 FSTP [ESP] ; store as double
# 57 PUSH EDI ; lua_State* L
# e8 XX XX XX XX CALL FUN_0084e2a0 ; lua_pushnumber(L, charFlags)
# 83 c4 38 ADD ESP, 0x38 ; merged: own 0xc + stolen 0x2c
# 5e POP ESI ; stolen: restore caller's ESI
# 6a 0b PUSH 0xb ; \ EAX = 11 in 3 bytes
# 58 POP EAX ; / (vs MOV EAX,0xb = 5 bytes)
# e9 XX XX XX XX JMP 004e3336 ; continue epilogue (POP EDI ...)
#
# Lua code reads info[11] and checks bit 0x40000000 (CHARACTER_FLAG_UNK31 = traitor).
# charFlags confirmed at [ESI+0x170]: same field used for ghost flag (AND ECX,0x2000).
_CHARINFO_PATCH_VA = 0x004e332d # ADD ESP,0x2c (5 bytes including POP ESI + MOV EAX[0])
_CHARINFO_JMPBACK_VA = 0x004e3336 # POP EDI — resume epilogue after cave
_CHARINFO_CAVE_SIZE = 30
_CHARINFO_CAVE_SEARCH_FROM = 0x00401000 # search entire .text for first 35-byte CC block
_LUA_PUSHNUMBER_VA = 0x0084e2a0 # lua_pushnumber
def _build_charinfo_patches(data, sections, image_base):
"""
Build the dynamic GetCharacterInfo patches.
Returns list of (va, old_bytes, new_bytes, description), [] if already applied,
or None on unrecoverable error.
"""
patch_off = va_to_offset(_CHARINFO_PATCH_VA, sections, image_base)
current = bytes(data[patch_off:patch_off + 5])
# Already patched with new JMP approach?
if current[0:1] == b"\xe9":
return []
# Verify first 4 bytes look like the epilogue (ADD ESP,0x2c + POP ESI).
# Accept both unpatched (b8 at [4]) and old-CALL-patched (e8 at [4]) states.
if current[0:4] != bytes.fromhex("83c42c5e"):
print(f"[WARN] 0x{_CHARINFO_PATCH_VA:08x}: unexpected bytes {current.hex(' ')}")
print(" Expected ADD ESP,0x2c + POP ESI at this address.")
print(" Restore Wow.exe.bak and re-run.")
return None
old_b = current # exact 5 bytes currently in file (b8 or e8 at index 4)
# Find a 35-byte code cave in .text
cave_va = find_cave(data, sections, image_base,
_CHARINFO_CAVE_SIZE, _CHARINFO_CAVE_SEARCH_FROM)
# Cave layout offsets (30 bytes total):
# 0 SUB ESP,8 (3)
# 3 FILD [ESI+0x170] (6)
# 9 FSTP [ESP] (3)
# 12 PUSH EDI (1)
# 13 CALL lua_pushnumber (5) — next instr at 18
# 18 ADD ESP, 0x38 (3) merged: own 0xc + stolen 0x2c
# 21 POP ESI (1) stolen
# 22 PUSH 0xb (2) \ shorter than MOV EAX,0xb (5 bytes)
# 24 POP EAX (1) /
# 25 JMP 004e3336 (5) — next instr at cave_va+30
call_rel = _LUA_PUSHNUMBER_VA - (cave_va + 18)
jmp_back_rel = _CHARINFO_JMPBACK_VA - (cave_va + 30)
cave_bytes = (
bytes.fromhex("83ec08") # SUB ESP, 8 (3)
+ bytes.fromhex("db8670010000") # FILD [ESI+0x170] (6)
+ bytes.fromhex("dd1c24") # FSTP [ESP] (3)
+ bytes.fromhex("57") # PUSH EDI (1)
+ b"\xe8" + struct.pack("<i", call_rel) # CALL lua_pushnumber (5)
+ bytes.fromhex("83c438") # ADD ESP, 0x38 (0xc+0x2c) (3)
+ bytes.fromhex("5e") # POP ESI [stolen] (1)
+ bytes.fromhex("6a0b") # PUSH 0xb (2)
+ bytes.fromhex("58") # POP EAX -> EAX=11 (1)
+ b"\xe9" + struct.pack("<i", jmp_back_rel) # JMP 004e3336 (5)
)
assert len(cave_bytes) == _CHARINFO_CAVE_SIZE, \
f"Cave size {len(cave_bytes)} != {_CHARINFO_CAVE_SIZE}"
# Patch: JMP cave (5 bytes at 004e332d)
jmp_rel = cave_va - (_CHARINFO_PATCH_VA + 5)
patch_new = b"\xe9" + struct.pack("<i", jmp_rel)
return [
(
cave_va,
bytes([0xcc] * _CHARINFO_CAVE_SIZE),
cave_bytes,
f"GetCharacterInfo cave @ 0x{cave_va:08x}: push charFlags, stolen epilogue",
),
(
_CHARINFO_PATCH_VA,
old_b,
patch_new,
"GetCharacterInfo: JMP to cave before POP ESI (ESI=char struct), return 11",
),
]
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
if len(sys.argv) != 2:
print(f"Usage: python {Path(sys.argv[0]).name} <path_to_Wow.exe>")
sys.exit(1)
exe_path = Path(sys.argv[1])
if not exe_path.exists():
print(f"Error: file not found: {exe_path}")
sys.exit(1)
data = bytearray(exe_path.read_bytes())
image_base, sections = parse_pe_sections(data)
print(f"Image base: 0x{image_base:08x}")
print(f"Sections: {len(sections)}")
for n, va, vs, ro, rs in sections:
print(f" {n:8s} VA=0x{va:08x} VSize=0x{vs:08x} Raw=0x{ro:08x} RSize=0x{rs:08x}")
print()
charinfo_patches = _build_charinfo_patches(data, sections, image_base)
if charinfo_patches is None:
sys.exit(1)
all_patches = list(_FIXED_PATCHES) + charinfo_patches
# Verify all first
ok = True
for va, old_b, new_b, desc in all_patches:
off = va_to_offset(va, sections, image_base)
cur = bytes(data[off:off + len(old_b)])
if cur == new_b:
print(f"[SKIP] 0x{va:08x}: already patched — {desc}")
elif cur == old_b:
print(f"[ OK ] 0x{va:08x}: verified — {desc}")
else:
print(f"[FAIL] 0x{va:08x}: {desc}")
print(f" Expected: {old_b.hex(' ')}")
print(f" Found: {cur.hex(' ')}")
ok = False
if not ok:
print("\nByte mismatch — aborting. Is this the correct Wow.exe (3.3.5a build 12340)?")
sys.exit(1)
# Backup
bak = exe_path.with_suffix(".exe.bak")
if not bak.exists():
shutil.copy2(exe_path, bak)
print(f"\nBackup: {bak}")
else:
print(f"\nBackup already exists: {bak}")
# Apply
applied = 0
for va, old_b, new_b, desc in all_patches:
off = va_to_offset(va, sections, image_base)
cur = bytes(data[off:off + len(old_b)])
if cur == new_b:
continue
data[off:off + len(old_b)] = new_b
applied += 1
print(f"[PATCH] 0x{va:08x}: {desc}")
if applied == 0:
print("\nNothing to patch.")
else:
exe_path.write_bytes(data)
print(f"\n{applied} patch(es) written to {exe_path}")
print("Done!")
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -16,6 +16,9 @@ PAID_FACTION_CHANGE = 3;
PAID_SERVICE_CHARACTER_ID = nil;
PAID_SERVICE_TYPE = nil;
GAME_MODE_NORMAL = 0;
GAME_MODE_TRAITOR = 1;
FACTION_BACKDROP_COLOR_TABLE = {
["Alliance"] = {0.5, 0.5, 0.5, 0.09, 0.09, 0.19},
["Horde"] = {0.5, 0.2, 0.2, 0.19, 0.05, 0.05},
@@ -81,6 +84,10 @@ local raceTooltips = {};
local classTooltips = {};
local detailedRaceTooltips = {};
local detailedClassTooltips = {};
local GAME_MODE_TILE_BACKGROUNDS = {
[GAME_MODE_NORMAL] = "Interface\\GluesVideo\\Assets\\RealmSelection\\Realm1.blp",
[GAME_MODE_TRAITOR] = "Interface\\GluesVideo\\Assets\\RealmSelection\\Realm2.blp",
};
local function UTF8Chars(text)
return string.gmatch(text or "", "[%z\1-\127\194-\244][\128-\191]*")
@@ -636,6 +643,129 @@ function CharacterCreate_PositionAllButtons()
CharacterCreate_PositionGenderButtons()
end
function CharacterCreate_CreateWithGameMode(modeId)
if PAID_SERVICE_TYPE then
return;
end
if modeId ~= GAME_MODE_NORMAL and modeId ~= GAME_MODE_TRAITOR then
modeId = GAME_MODE_NORMAL;
end
-- The patched Wow.exe reads the second argument as outfitId
-- and writes it into the CMSG_CHAR_CREATE packet.
local name = CharacterCreateNameEdit:GetText();
message("DEBUG: CreateCharacter('" .. tostring(name) .. "', " .. tostring(modeId) .. ")");
CreateCharacter(name, modeId);
CharacterCreate.personalizationMode = false;
CharacterCreate.gameModeSelectionMode = false;
PlaySound("gsCharacterCreationCreateChar");
end
function CharacterCreate_UpdateGameModeVisibility()
if not CharacterCreateGameModeFrame then
return;
end
if CharacterCreate.personalizationMode and CharacterCreate.gameModeSelectionMode and not PAID_SERVICE_TYPE then
CharacterCreateGameModeFrame:Show();
else
CharacterCreateGameModeFrame:Hide();
end
end
local function CharacterCreate_GetEnteredName()
local text = "";
if CharacterCreateNameEdit and CharacterCreateNameEdit.GetText then
text = CharacterCreateNameEdit:GetText() or "";
end
text = string.gsub(text, "^%s+", "");
text = string.gsub(text, "%s+$", "");
return text;
end
local function CharacterCreate_HasEnteredName()
return UTF8Length(CharacterCreate_GetEnteredName()) >= MIN_CHAR_NAME_LENGTH;
end
local function CharacterCreate_UpdatePersonalizationStep()
if not CharacterCreate.personalizationMode then
return;
end
if CharacterCreate.gameModeSelectionMode and not CharacterCreate_HasEnteredName() then
CharacterCreate.gameModeSelectionMode = false;
end
local showGameModes = CharacterCreate.gameModeSelectionMode and not PAID_SERVICE_TYPE;
if showGameModes then
CharacterCreateRotateLeft:Hide();
CharacterCreateRotateRight:Hide();
CharacterCreateRotateLeft30:Hide();
CharacterCreateRotateRight30:Hide();
CharCreateRandomizeButton:Hide();
CharacterCreateNameEdit:Hide();
CharacterCreateRandomName:Hide();
CharCreateOkayButton:Hide();
for i=1, NUM_CHAR_CUSTOMIZATIONS do
_G["CharacterCustomizationButtonFrame"..i]:Hide();
end
else
CharacterCreateRotateLeft:Show();
CharacterCreateRotateRight:Show();
CharacterCreateRotateLeft30:Show();
CharacterCreateRotateRight30:Show();
CharCreateRandomizeButton:Show();
CharacterCreateNameEdit:Show();
CharacterCreateRandomName:Show();
CharCreateOkayButton:Show();
for i=1, NUM_CHAR_CUSTOMIZATIONS do
_G["CharacterCustomizationButtonFrame"..i]:Show();
end
end
CharacterCreate_UpdateGameModeVisibility();
end
local function CharacterCreate_InitGameModeTiles()
-- Background textures
CharacterCreateGameModeNormalBg:SetTexture(GAME_MODE_TILE_BACKGROUNDS[GAME_MODE_NORMAL]);
CharacterCreateGameModeTraitorBg:SetTexture(GAME_MODE_TILE_BACKGROUNDS[GAME_MODE_TRAITOR]);
-- Tile titles (MORPHEUS font)
CharacterCreateGameModeNormalTitle:SetFont("Fonts\\MORPHEUS.TTF", 24, "");
CharacterCreateGameModeNormalTitle:SetText("Обычный");
CharacterCreateGameModeNormalTitle:SetTextColor(0.96, 0.94, 0.88);
CharacterCreateGameModeTraitorTitle:SetFont("Fonts\\MORPHEUS.TTF", 24, "");
CharacterCreateGameModeTraitorTitle:SetText("Предатель");
CharacterCreateGameModeTraitorTitle:SetTextColor(0.96, 0.94, 0.88);
-- Frame header
CharacterCreateGameModeFrameTitle:SetFont("Fonts\\MORPHEUS.TTF", 28, "");
CharacterCreateGameModeFrameTitle:SetText("ВЫБОР РЕЖИМА");
CharacterCreateGameModeFrameTitle:SetTextColor(1.0, 0.82, 0.0);
CharacterCreateGameModeFrameSubtitle:SetText("Выберите режим для нового персонажа");
CharacterCreateGameModeFrameSubtitle:SetTextColor(0.78, 0.72, 0.55);
-- Play buttons
CharacterCreateGameModeNormalPlayButton:SetText("Играть");
CharacterCreateGameModeNormalPlayButton:SetScript("OnClick", function()
CharacterCreate_CreateWithGameMode(GAME_MODE_NORMAL);
end);
CharacterCreateGameModeTraitorPlayButton:SetText("Играть");
CharacterCreateGameModeTraitorPlayButton:SetScript("OnClick", function()
CharacterCreate_CreateWithGameMode(GAME_MODE_TRAITOR);
end);
end
function CharacterCreate_OnLoad(self)
self:SetSequence(0)
self:SetCamera(0)
@@ -646,9 +776,11 @@ function CharacterCreate_OnLoad(self)
CharacterCreate.numClasses = 0
CharacterCreate.selectedClass = 0
CharacterCreate.selectedGender = 0
CharacterCreate.gameModeSelectionMode = false
CharacterCreate.personalizationMode = false
CharacterCreate_MoveTexturesToBackground()
CharacterCreate_SetupCustomButtons()
CharacterCreate_InitGameModeTiles()
for i=1, NUM_CHAR_CUSTOMIZATIONS, 1 do
_G["CharacterCustomizationButtonFrame"..i.."Text"]:SetText(_G["CHAR_CUSTOMIZATION"..i.."_DESC"])
@@ -778,15 +910,12 @@ function CharacterCreate_TogglePersonalization()
CharCreatePersonalizeButton:Hide();
CharCreateOkayButton:Show();
CharacterCreate_UpdateHairCustomization();
CharCreateRandomizeButton:Show();
CharacterCreateNameEdit:Show();
CharacterCreateRandomName:Show();
CharacterCreate.gameModeSelectionMode = false;
CharacterCreate_UpdatePersonalizationStep();
for i=1, NUM_CHAR_CUSTOMIZATIONS do
_G["CharacterCustomizationButtonFrame"..i]:Show();
end
else
CharacterCreate.personalizationMode = false;
CharacterCreate.gameModeSelectionMode = false;
PlaySound("gsCharacterCreationCancel");
CharacterCreateRaceButtonsContainer:Show();
CharacterCreateClassButtonsContainer:Show();
@@ -804,6 +933,7 @@ function CharacterCreate_TogglePersonalization()
CharacterCreateRandomName:Hide();
CharCreatePersonalizeButton:Show();
CharCreateOkayButton:Hide();
CharacterCreate_UpdateGameModeVisibility();
for i=1, NUM_CHAR_CUSTOMIZATIONS do
_G["CharacterCustomizationButtonFrame"..i]:Hide();
@@ -844,6 +974,7 @@ function CharacterCreate_OnShow()
end
CharacterCreate.personalizationMode = false;
CharacterCreate.gameModeSelectionMode = false;
CharCreateOkayButton:Hide();
for i=1, NUM_CHAR_CUSTOMIZATIONS do
@@ -865,6 +996,8 @@ function CharacterCreate_OnShow()
hideScheduled = true
HideNameEditFrame:Show()
CharacterCreate.gameModeSelectionMode = false;
CharacterCreate_UpdateGameModeVisibility();
CharacterCreate_PositionAllButtons()
end
@@ -1316,6 +1449,9 @@ function SetCharacterClass(id)
end
function CharacterCreate_OnChar()
if CharacterCreate.personalizationMode then
CharacterCreate_UpdatePersonalizationStep();
end
end
function CharacterCreate_UpdateButtonCheckedStates()
@@ -1363,11 +1499,7 @@ end
function CharacterCreate_OnKeyDown(key)
if ( key == "ESCAPE" ) then
if (CharacterCreate.personalizationMode == true) then
CharacterCreate_TogglePersonalization();
else
CharacterCreate_Back();
end
CharacterCreate_Back();
elseif ( key == "ENTER" ) then
CharacterCreate_Okay();
elseif ( key == "PRINTSCREEN" ) then
@@ -1381,18 +1513,42 @@ function CharacterCreate_UpdateModel(self)
end
function CharacterCreate_Okay()
if ( not PAID_SERVICE_TYPE and CharacterCreate.personalizationMode and not CharacterCreate.gameModeSelectionMode ) then
if ( not CharacterCreate_HasEnteredName() ) then
CharacterCreateNameEdit:SetFocus();
return;
end
CharacterCreate.gameModeSelectionMode = true;
CharacterCreate_UpdatePersonalizationStep();
return;
end
-- In game mode selection — tile buttons handle character creation
if ( not PAID_SERVICE_TYPE and CharacterCreate.gameModeSelectionMode ) then
return;
end
if ( PAID_SERVICE_TYPE ) then
GlueDialog_Show("CONFIRM_PAID_SERVICE");
else
message("DEBUG: old path CreateCharacter (no outfitId!)");
CreateCharacter(CharacterCreateNameEdit:GetText());
end
CharacterCreate.personalizationMode = false;
CharacterCreate.gameModeSelectionMode = false;
PlaySound("gsCharacterCreationCreateChar");
end
function CharacterCreate_Back()
if (CharacterCreate.personalizationMode == true) then
if (CharacterCreate.gameModeSelectionMode == true) then
CharacterCreate.gameModeSelectionMode = false;
CharacterCreate_UpdatePersonalizationStep();
return;
end
CharacterCreate_TogglePersonalization();
CharacterCreate.personalizationMode = false;
return;
@@ -1482,6 +1638,7 @@ end
function CharacterCreate_ResetState()
CharacterCreate.personalizationMode = false;
CharacterCreate.gameModeSelectionMode = false;
CharacterCreateRaceButtonsContainer:Show();
CharacterCreateClassButtonsContainer:Show();
CharacterCreateGenderButtonsContainer:Show();
@@ -1498,6 +1655,7 @@ function CharacterCreate_ResetState()
CharacterCreateRandomName:Hide();
CharCreatePersonalizeButton:Show();
CharCreateOkayButton:Hide();
CharacterCreate_UpdateGameModeVisibility();
for i=1, NUM_CHAR_CUSTOMIZATIONS do
_G["CharacterCustomizationButtonFrame"..i]:Hide();
@@ -187,6 +187,57 @@
</Button>
</Frames>
</Frame>
<!-- Game mode tile template -->
<Frame name="CharacterCreateGameModeTileTemplate" virtual="true" enableMouse="false">
<Size x="220" y="126"/>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentBg">
<Anchors>
<Anchor point="TOPLEFT" x="4" y="-4"/>
<Anchor point="BOTTOMRIGHT" x="-4" y="4"/>
</Anchors>
</Texture>
</Layer>
<Layer level="ARTWORK">
<Texture name="$parentShade" file="Interface\Tooltips\UI-Tooltip-Background">
<Anchors>
<Anchor point="TOPLEFT" x="4" y="-4"/>
<Anchor point="BOTTOMRIGHT" x="-4" y="4"/>
</Anchors>
<Color r="0" g="0" b="0" a="0.18"/>
</Texture>
</Layer>
<Layer level="OVERLAY">
<FontString name="$parentTitle" inherits="GlueFontNormalLarge" justifyH="CENTER">
<Anchors>
<Anchor point="CENTER" x="0" y="16"/>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Button name="$parentPlayButton" inherits="GlueButtonSmallTemplate">
<Size x="120" y="26"/>
<Anchors>
<Anchor point="BOTTOM" x="0" y="12"/>
</Anchors>
</Button>
</Frames>
<Scripts>
<OnLoad>
self:SetBackdrop({
edgeFile = "Interface\\tooltips\\ui-tooltip-border-maw",
tile = true,
tileSize = 16,
edgeSize = 16,
insets = { left = 5, right = 5, top = 5, bottom = 5 }
});
self:SetBackdropBorderColor(0.92, 0.92, 0.92, 0.95);
</OnLoad>
</Scripts>
</Frame>
<ModelFFX name="CharacterCreate" toplevel="true" parent="GlueParent" setAllPoints="true" enableKeyboard="true" hidden="true">
<Frames>
<Frame name="CharacterCreateFrame" setAllPoints="true" enableMouse="true">
@@ -683,6 +734,40 @@
</HighlightTexture>
</Button>
<!-- Game mode selection -->
<Frame name="CharacterCreateGameModeFrame" hidden="true">
<Size x="520" y="190"/>
<Anchors>
<Anchor point="BOTTOM" relativeTo="CharacterCreateNameEdit" relativePoint="TOP" x="0" y="26"/>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<FontString name="CharacterCreateGameModeFrameTitle" inherits="GlueFontNormalLarge" justifyH="CENTER">
<Anchors>
<Anchor point="TOP" x="0" y="0"/>
</Anchors>
</FontString>
<FontString name="CharacterCreateGameModeFrameSubtitle" inherits="GlueFontHighlightSmall" justifyH="CENTER">
<Anchors>
<Anchor point="TOP" relativeTo="CharacterCreateGameModeFrameTitle" relativePoint="BOTTOM" x="0" y="-6"/>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Frame name="CharacterCreateGameModeNormal" inherits="CharacterCreateGameModeTileTemplate">
<Anchors>
<Anchor point="TOP" x="-120" y="-54"/>
</Anchors>
</Frame>
<Frame name="CharacterCreateGameModeTraitor" inherits="CharacterCreateGameModeTileTemplate">
<Anchors>
<Anchor point="TOP" x="120" y="-54"/>
</Anchors>
</Frame>
</Frames>
</Frame>
<Button name="CharCreatePersonalizeButton" inherits="GlueButtonTemplate" text="CHARACTER_CREATE_CUSTOMIZE">
<Anchors>
<Anchor point="BOTTOMRIGHT" x="-40" y="20"/>
@@ -8,6 +8,163 @@ CHARACTER_ROTATION_CONSTANT = 0.6;
MAX_CHARACTERS_DISPLAYED = 8;
MAX_CHARACTERS_PER_REALM = 8;
GAMEMODE_CHARACTER_FLAG_TRAITOR = 0x40000000;
CHARACTER_SELECT_DEBUG_CHARFLAGS = true;
local function CharacterSelect_HasFlag(charFlags, flagMask)
if ( type(charFlags) ~= "number" or type(flagMask) ~= "number" ) then
return false;
end
if ( bit and bit.band ) then
return bit.band(charFlags, flagMask) ~= 0;
end
if ( bit32 and bit32.band ) then
return bit32.band(charFlags, flagMask) ~= 0;
end
return math.floor(charFlags / flagMask) % 2 == 1;
end
local function CharacterSelect_IsTraitorCharacter(charFlags)
return CharacterSelect_HasFlag(charFlags, GAMEMODE_CHARACTER_FLAG_TRAITOR);
end
local function CharacterSelect_FormatCharFlags(charFlags)
if ( type(charFlags) ~= "number" ) then
return "nil";
end
charFlags = math.floor(charFlags);
return tostring(charFlags).." ("..format("0x%X", charFlags)..")";
end
local function CharacterSelect_GetCharacterInfoDebug(index)
local info = { GetCharacterInfo(index) };
local count = table.getn(info);
local parts = {};
for i = 1, count do
parts[i] = tostring(info[i]);
end
return info, count, table.concat(parts, " | ");
end
local function CharacterSelect_GetDisplayName(index)
local info = { GetCharacterInfo(index) };
local name = info[1];
local charFlags = info[11];
if ( not name ) then
return "";
end
if ( CharacterSelect_IsTraitorCharacter(charFlags) ) then
return name.." |cffd9534f[Предатель]|r";
end
return name;
end
local function CharacterSelect_ResetTraitorVisuals(button, buttonIndex)
if ( not button ) then
return;
end
local generalBg = _G["CharSelectCharacterButton"..buttonIndex.."GeneralBackground"];
if ( generalBg ) then
generalBg:SetVertexColor(1, 1, 1);
end
local background = _G["CharSelectCharacterButton"..buttonIndex.."Background"];
if ( background ) then
background:SetVertexColor(1, 1, 1);
end
local traitorGlow = _G["CharSelectCharacterButton"..buttonIndex.."TraitorGlow"];
if ( traitorGlow ) then
traitorGlow:Hide();
end
local traitorBadge = _G["CharSelectCharacterButton"..buttonIndex.."TraitorBadge"];
if ( traitorBadge ) then
traitorBadge:Hide();
end
local debugFlagsText = _G["CharSelectCharacterButton"..buttonIndex.."DebugFlags"];
if ( debugFlagsText ) then
debugFlagsText:Hide();
end
end
local function CharacterSelect_UpdateTraitorVisuals(button, buttonIndex, isTraitor)
if ( not button ) then
return;
end
CharacterSelect_ResetTraitorVisuals(button, buttonIndex);
if ( not isTraitor ) then
return;
end
local generalBg = _G["CharSelectCharacterButton"..buttonIndex.."GeneralBackground"];
if ( generalBg ) then
generalBg:SetVertexColor(1, 0.78, 0.78);
end
local background = _G["CharSelectCharacterButton"..buttonIndex.."Background"];
if ( background ) then
background:SetVertexColor(1, 0.72, 0.72);
end
local traitorGlow = _G["CharSelectCharacterButton"..buttonIndex.."TraitorGlow"];
if ( not traitorGlow ) then
traitorGlow = button:CreateTexture("CharSelectCharacterButton"..buttonIndex.."TraitorGlow", "OVERLAY", nil, 4);
traitorGlow:ClearAllPoints();
traitorGlow:SetPoint("TOPLEFT", button, "TOPLEFT", -18, 6);
traitorGlow:SetSize(256, 70);
traitorGlow:SetTexture("Interface\\Glues\\CharacterSelect\\Glue-CharacterSelect-Highlight");
traitorGlow:SetBlendMode("ADD");
end
traitorGlow:SetVertexColor(1, 0.18, 0.18);
traitorGlow:SetAlpha(0.55);
traitorGlow:Show();
local traitorBadge = _G["CharSelectCharacterButton"..buttonIndex.."TraitorBadge"];
if ( not traitorBadge ) then
traitorBadge = button:CreateFontString("CharSelectCharacterButton"..buttonIndex.."TraitorBadge", "OVERLAY", "GlueFontNormalSmall");
traitorBadge:ClearAllPoints();
traitorBadge:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -18, 10);
traitorBadge:SetFont("Fonts\\FRIZQT__.TTF", 10, "OUTLINE");
end
traitorBadge:SetText("ПРЕДАТЕЛЬ");
traitorBadge:SetTextColor(1, 0.25, 0.25);
traitorBadge:Show();
end
local function CharacterSelect_UpdateDebugCharFlags(button, buttonIndex, charFlags)
local debugFlagsText = _G["CharSelectCharacterButton"..buttonIndex.."DebugFlags"];
if ( not debugFlagsText ) then
debugFlagsText = button:CreateFontString("CharSelectCharacterButton"..buttonIndex.."DebugFlags", "OVERLAY", "GlueFontDisableSmall");
debugFlagsText:ClearAllPoints();
debugFlagsText:SetPoint("BOTTOMLEFT", button, "BOTTOMLEFT", 0, 8);
debugFlagsText:SetWidth(170);
debugFlagsText:SetJustifyH("LEFT");
debugFlagsText:SetWordWrap(false);
debugFlagsText:SetFont("Fonts\\FRIZQT__.TTF", 8, "OUTLINE");
end
if ( CHARACTER_SELECT_DEBUG_CHARFLAGS ) then
debugFlagsText:SetText("flags="..CharacterSelect_FormatCharFlags(charFlags));
debugFlagsText:SetTextColor(0.8, 0.8, 0.8);
debugFlagsText:Show();
else
debugFlagsText:Hide();
end
end
function CharacterSelect_OnLoad(self)
@@ -339,13 +496,13 @@ function CharacterSelect_OnEvent(self, event, ...)
UpdateAddonButton();
elseif ( event == "CHARACTER_LIST_UPDATE" ) then
UpdateCharacterList();
CharSelectCharacterName:SetText(GetCharacterInfo(self.selectedIndex));
CharSelectCharacterName:SetText(CharacterSelect_GetDisplayName(self.selectedIndex));
elseif ( event == "UPDATE_SELECTED_CHARACTER" ) then
local index = ...;
if ( index == 0 ) then
CharSelectCharacterName:SetText("");
else
CharSelectCharacterName:SetText(GetCharacterInfo(index));
CharSelectCharacterName:SetText(CharacterSelect_GetDisplayName(index));
self.selectedIndex = index;
end
UpdateCharacterSelection(self);
@@ -393,10 +550,12 @@ function UpdateCharacterList()
local numChars = GetNumCharacters();
local index = 1;
local coords;
local debugCharFlagsLines = {};
for i = 1, MAX_CHARACTERS_DISPLAYED do
local button = _G["CharSelectCharacterButton"..i];
button:Show();
CharacterSelect_ResetTraitorVisuals(button, i);
end
_G["CharacterSelectCharacterFrame"]:SetHeight(620);
@@ -532,7 +691,18 @@ function UpdateCharacterList()
};
for i=1, numChars, 1 do
local name, race, class, level, zone, sex, ghost, PCC, PRC, PFC = GetCharacterInfo(i);
local info, infoCount, infoDebug = CharacterSelect_GetCharacterInfoDebug(i);
local name = info[1];
local race = info[2];
local class = info[3];
local level = info[4];
local zone = info[5];
local sex = info[6];
local ghost = info[7];
local PCC = info[8];
local PRC = info[9];
local PFC = info[10];
local charFlags = info[11];
local button = _G["CharSelectCharacterButton"..index];
if ( not name ) then
@@ -581,6 +751,18 @@ function UpdateCharacterList()
factionIcon:SetTexture(FACTION_ICONS["Horde"]);
end
factionIcon:Show();
CharacterSelect_UpdateTraitorVisuals(button, index, CharacterSelect_IsTraitorCharacter(charFlags));
CharacterSelect_UpdateDebugCharFlags(button, index, charFlags);
if ( CHARACTER_SELECT_DEBUG_CHARFLAGS ) then
table.insert(
debugCharFlagsLines,
"#" .. tostring(i) ..
" " .. tostring(name) ..
" returns=" .. tostring(infoCount) ..
" flags=" .. CharacterSelect_FormatCharFlags(charFlags) ..
" raw=[" .. infoDebug .. "]"
);
end
-- Nombre
local nameText = _G["CharSelectCharacterButton"..index.."ButtonTextName"];
@@ -741,6 +923,15 @@ function UpdateCharacterList()
_G["CharSelectFactionChange"..i]:Hide();
button:Show();
CharacterSelect_ResetTraitorVisuals(button, i);
end
if ( CHARACTER_SELECT_DEBUG_CHARFLAGS and table.getn(debugCharFlagsLines) > 0 ) then
local debugCharFlagsMessage = table.concat(debugCharFlagsLines, "\n");
if ( CharacterSelect.lastCharFlagsDebugMessage ~= debugCharFlagsMessage ) then
CharacterSelect.lastCharFlagsDebugMessage = debugCharFlagsMessage;
message("CHAR_ENUM charFlags debug:\n" .. debugCharFlagsMessage);
end
end
if ( numChars == 0 ) then
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More