Fix MailMailButton nil errors and bump to 1.4.8

- Fix MailMailButton nil errors in SendMailFrame_CanSend and sendmail_clear
- Keep a safe reference to the original Send Mail button
- Add fallback handling for MailSubjectEditBox
- Improve compatibility with UI/mailbox modifications
- Update version to 1.4.8
- Update README and audit documentation
This commit is contained in:
2026-09-14 06:51:49 +00:00
parent 682e843d0e
commit 0853d3854c
4 changed files with 192 additions and 7 deletions
+7 -1
View File
@@ -17,6 +17,12 @@ Target: Turtle WoW 1.18.x / Octo client.
- **Calendar tooltip:** disabled/empty calendar days no longer concatenate a nil/stale `mails` value.
- **OnUpdate rebinding:** `TurtleMail:init()` binds the original `on_update` function before the compatibility file loads; the patch explicitly rebinds the frame script so the safer handler is actually used.
## Fixed in 1.4.8
- **`MailMailButton` nil crash:** upstream stores the real `SendMailMailButton` in a loose global named `MailMailButton`, then replaces the Blizzard global with a proxy table. If the saved global is missing or another UI addon changes it, `SendMailFrame_CanSend()` and `sendmail_clear()` crash while calling `Enable()` / `Disable()`. The compatibility layer now preserves a strong reference to the real button before the proxy swap, restores it when the mailbox opens, replaces the fragile CanSend hook, and uses a guarded clear routine.
- **Paired subject edit-box hardening:** the same proxy pattern is used for `MailSubjectEditBox`; the real edit box is now preserved and restored alongside the send button to avoid the equivalent follow-up failure.
## Remaining architectural risks upstream
These are intentionally not rewritten in the compatibility layer because changing them would be much more invasive and could alter mail behavior:
@@ -38,4 +44,4 @@ The compatibility build was tested in-game on Turtle WoW 1.18.x and the reported
## Status
**1.4.6 is the current stable release.** The fixes remain isolated in `TurtleMailFix.lua` so upstream code stays easy to compare and future upstream changes remain easier to merge.
**1.4.8 is the current stable release.** The fixes remain isolated in `TurtleMailFix.lua` so upstream code stays easy to compare and future upstream changes remain easier to merge.
+4 -3
View File
@@ -76,7 +76,7 @@ When enabled, TurtleMail records sent and received mail information in its dedic
## 🔧 Turtle WoW compatibility
Version **1.4.7** extends the compatibility and safety layer for Turtle WoW 1.18.x environments.
Version **1.4.8** extends the compatibility and safety layer for Turtle WoW 1.18.x environments.
The compatibility work is isolated in `TurtleMailFix.lua` so the original TurtleMail code remains easier to compare with upstream versions.
@@ -95,6 +95,7 @@ The compatibility work is isolated in `TurtleMailFix.lua` so the original Turtle
- Fixed calendar tooltip nil/stale-value cases.
- Rebound the safer `OnUpdate` handler correctly after loading the compatibility layer.
- Prevented the send queue from remaining stuck when an attachment becomes unavailable during sending.
- Fixed `MailMailButton` nil crashes while enabling/disabling or clearing the send form, and hardened the paired subject edit-box reference.
The complete technical audit is available in [`AUDIT.md`](AUDIT.md).
@@ -129,11 +130,11 @@ TurtleMail includes localization support for:
Current stable version:
**1.4.7**
**1.4.8**
Based on upstream **TurtleMail 1.4.5**.
Version 1.4.7 has been validated in-game on Turtle WoW 1.18.x without Lua errors in the tested setup.
Version 1.4.8 adds an additional safeguard for the legacy send-button proxy used by TurtleMail. The earlier compatibility fixes remain unchanged.
## 🖼️ Screenshots
+1 -1
View File
@@ -2,7 +2,7 @@
## Interface: 11200
## Title: |cffabd473Turtle|cffffffffMail
## Author: shirsig/sica, Dusk-92 (compatibility fixes)
## Version: 1.4.7
## Version: 1.4.8
## Notes: Mailbox enhancement - Turtle WoW 1.18.x compatibility
## SavedVariables: TurtleMail_AutoCompleteNames
## SavedVariablesPerCharacter: TurtleMail_To TurtleMail_Point TurtleMail_Log
+180 -2
View File
@@ -8,7 +8,7 @@ local m = TurtleMail
local getn = table.getn
local function pack( ... ) return arg end
m.compat_version = "1.4.7"
m.compat_version = "1.4.8"
local DEFAULT_SENT_FILTERS = {
Money = 1,
@@ -110,6 +110,75 @@ local function ensure_horizontal_bars()
return m.api.MailHorizontalBarLeft and m.api.MailHorizontalBarRight
end
-- Upstream keeps the real Send button and subject edit box in ad-hoc globals
-- (MailMailButton / MailSubjectEditBox), then replaces the Blizzard globals with
-- proxy tables. If another addon changes those globals, or sendmail_load() runs
-- with an already-proxied widget, the saved globals can become nil and later
-- calls crash. Preserve strong references before the upstream swap happens.
local preserved_send_mail_button = m.api.SendMailMailButton
local preserved_subject_edit_box = m.api.SendMailSubjectEditBox
local function is_named_widget( widget, expected_name )
if not widget or type( widget.GetName ) ~= "function" then return false end
return widget:GetName() == expected_name
end
local function ensure_send_mail_button()
local button = preserved_send_mail_button
if not is_named_widget( button, "SendMailMailButton" ) then
button = m.real_send_mail_button
end
if not is_named_widget( button, "SendMailMailButton" ) then
button = MailMailButton
end
if not is_named_widget( button, "SendMailMailButton" ) then
button = m.api.SendMailMailButton
end
if is_named_widget( button, "SendMailMailButton" ) then
preserved_send_mail_button = button
m.real_send_mail_button = button
MailMailButton = button
return button
end
return nil
end
local function ensure_subject_edit_box()
local edit_box = preserved_subject_edit_box
if not is_named_widget( edit_box, "SendMailSubjectEditBox" ) then
edit_box = m.real_subject_edit_box
end
if not is_named_widget( edit_box, "SendMailSubjectEditBox" ) then
edit_box = MailSubjectEditBox
end
if not is_named_widget( edit_box, "SendMailSubjectEditBox" ) then
edit_box = m.api.SendMailSubjectEditBox
end
if is_named_widget( edit_box, "SendMailSubjectEditBox" ) then
preserved_subject_edit_box = edit_box
m.real_subject_edit_box = edit_box
MailSubjectEditBox = edit_box
return edit_box
end
return nil
end
local function restore_real_mail_widgets_for_upstream()
local button = ensure_send_mail_button()
local edit_box = ensure_subject_edit_box()
-- Only restore the Blizzard globals immediately before upstream sendmail_load().
-- That function intentionally replaces them with proxy tables afterwards.
if button then m.api.SendMailMailButton = button end
if edit_box then m.api.SendMailSubjectEditBox = edit_box end
end
-- SavedVariables are not guaranteed to have a valid schema. Repair them before
-- the original handlers touch nested fields.
do
@@ -166,7 +235,50 @@ do
local original = m.sendmail_load
function m.sendmail_load()
ensure_horizontal_bars()
if original then return original() end
restore_real_mail_widgets_for_upstream()
if original then
local result = original()
-- Upstream has now installed its proxy globals. Re-assert the private
-- references used by the legacy MailMailButton/MailSubjectEditBox code.
ensure_send_mail_button()
ensure_subject_edit_box()
return result
end
end
end
-- Replace the fragile upstream CanSend hook. The original directly calls the
-- global MailMailButton and crashes when that saved global is nil.
if m.hooks then
m.hook.SendMailFrame_CanSend = function()
local button = ensure_send_mail_button()
if not button then return end
local name_box = m.api.SendMailNameEditBox
local send_money_button = m.api.SendMailSendMoneyButton
local send_money_frame = m.api.SendMailMoney
local recipient = name_box and name_box.GetText and name_box:GetText() or ""
local attached = m.sendmail_num_attachments and m.sendmail_num_attachments() or 0
local price = m.api.GetSendMailPrice and (tonumber( m.api.GetSendMailPrice() ) or 0) or 0
local player_money = m.api.GetMoney and (tonumber( m.api.GetMoney() ) or 0) or 0
local money = 0
if send_money_button and send_money_button.GetChecked and send_money_button:GetChecked()
and m.api.MoneyInputFrame_GetCopper and send_money_frame then
money = tonumber( m.api.MoneyInputFrame_GetCopper( send_money_frame ) ) or 0
end
local can_send = not m.sendmail_sending
and string.len( recipient ) > 0
and money + price * math.max( 1, attached ) <= player_money
if can_send then
button:Enable()
else
button:Disable()
end
end
end
@@ -178,6 +290,67 @@ if m.hooks and m.hooks.SendMailFrame_Update then
end
end
-- Ensure the legacy click handler sees a valid subject edit box and real send
-- button before it reads them. This protects the same upstream proxy hack from
-- producing a follow-up MailSubjectEditBox nil error.
do
local original = m.send_mail_button_onclick
function m.send_mail_button_onclick()
ensure_send_mail_button()
ensure_subject_edit_box()
if original then return original() end
end
end
-- Replace the upstream clear routine because it directly dereferences
-- MailMailButton and MailSubjectEditBox. Keep its behavior, but guard every UI
-- object so mailbox replacements cannot turn cleanup into another Lua error.
function m.sendmail_clear()
local any_item
for i = 1, 21 do
local attachment = m.api[ "MailAttachment" .. i ]
if attachment then
any_item = any_item or attachment.item
attachment.item = nil
end
end
if any_item and m.api.ClearCursor and m.api.PickupContainerItem then
m.api.ClearCursor()
m.api.PickupContainerItem( unpack( any_item ) )
m.api.ClearCursor()
end
local button = ensure_send_mail_button()
if button and button.Disable then button:Disable() end
local name_box = m.api.SendMailNameEditBox
if name_box then
if name_box.SetText then name_box:SetText( "" ) end
if name_box.SetFocus then name_box:SetFocus() end
end
local subject = ensure_subject_edit_box()
if subject and subject.SetText then subject:SetText( "" ) end
if m.api.SendMailBodyEditBox and m.api.SendMailBodyEditBox.SetText then
m.api.SendMailBodyEditBox:SetText( "" )
end
if m.api.MoneyInputFrame_ResetMoney and m.api.SendMailMoney then
m.api.MoneyInputFrame_ResetMoney( m.api.SendMailMoney )
end
if m.api.SendMailRadioButton_OnClick then
m.api.SendMailRadioButton_OnClick( 1 )
end
if m.api.SendMailFrame_Update then
m.api.SendMailFrame_Update()
end
end
-- Do not assume MailFrame exists during bag events.
function m.BAG_UPDATE()
if m.api.MailFrame and m.api.MailFrame:IsVisible() and m.api.SendMailFrame_Update then
@@ -187,6 +360,11 @@ end
-- Safer MAIL_SHOW: package-button regions differ between UI replacements.
function m.MAIL_SHOW()
-- Re-assert the legacy references whenever the mailbox opens. This also
-- recovers if another UI addon changed those globals after addon loading.
ensure_send_mail_button()
ensure_subject_edit_box()
if not m.api.MailFrame then return end
if m.api.TurtleMail_Point then