From 93ade9df1e2da1e10a336009e5bca99534a54b87 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Thu, 6 Aug 2026 14:11:39 -0700 Subject: [PATCH] Crash fixes for saveas/about dialog. --- src/CMakeLists.txt | 8 + src/Opus/help.c | 38 ++- src/Opus/ourmath.h | 9 + src/Opus/save.c | 22 +- src/port/original/about.sdm | 15 +- src/port/original/opus_sdm_runtime.cpp | 322 ++++++++++++++++++++++++- src/port/original/opus_x64_compat.h | 10 + src/port/original/saveas.sdm | 16 +- src/port/tools/opus_cabi_tool.cpp | 199 +++++++++++++++ 9 files changed, 620 insertions(+), 19 deletions(-) create mode 100644 src/port/tools/opus_cabi_tool.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9c3d88e..de1885f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -992,3 +992,11 @@ add_test(NAME opus_word1_font_typing_test COMMAND $ $ --font-typing ) set_tests_properties(opus_word1_font_typing_test PROPERTIES TIMEOUT 20) +add_test(NAME opus_word1_about_test + COMMAND $ $ --about +) +set_tests_properties(opus_word1_about_test PROPERTIES TIMEOUT 20) +add_test(NAME opus_word1_save_as_test + COMMAND $ $ --save-as +) +set_tests_properties(opus_word1_save_as_test PROPERTIES TIMEOUT 20) diff --git a/src/Opus/help.c b/src/Opus/help.c index bd3ad78..2166f23 100644 --- a/src/Opus/help.c +++ b/src/Opus/help.c @@ -955,12 +955,17 @@ CMB *pcmb; CMD CmdAbout(pcmb) CMB *pcmb; { +#ifdef OPUS_X64 + SetPropA(vhwndApp, "OpusX64AboutStage", (HANDLE)(INT_PTR)1); +#endif if (FCmdFillCab()) { extern BOOL f8087; long l; char st[cchMaxSz]; +#ifndef OPUS_X64 long (FAR PASCAL *lpfn)(); +#endif /* values come from version.h */ FSetCabSz(pcmb->hcab, szApp, @@ -969,8 +974,27 @@ CMB *pcmb; Iag(CABABOUT, hszAboutVersion)); FSetCabSz(pcmb->hcab, SzShared(szCopyrightDef), Iag(CABABOUT, hszAboutCopyright)); +#ifdef OPUS_X64 + SetPropA(vhwndApp, "OpusX64AboutStage", (HANDLE)(INT_PTR)2); +#endif +#ifdef OPUS_X64 + /* Win16 KERNEL ordinal 169 was GetFreeSpace. It does not exist on + * Win64; use the native system-memory API for the same About field. */ + { + MEMORYSTATUSEX memoryStatus; + ULONGLONG freeKb; + memoryStatus.dwLength = sizeof(memoryStatus); + if (GlobalMemoryStatusEx(&memoryStatus)) + { + freeKb = memoryStatus.ullAvailPhys >> 10; + l = (long) min(freeKb, 0x7fffffffULL); + } + else + l = GlobalCompact(0L) >> 10; + } +#else if (vwWinVersion >= 0x0300 && (lpfn = GetProcAddress(GetModuleHandle(SzFrame("KERNEL")), MAKEINTRESOURCE(idoGetFreeSpace))) != NULL) @@ -978,6 +1002,7 @@ CMB *pcmb; l = (*lpfn)(0) >> 10; else l = GlobalCompact(0L) >> 10; +#endif BuildStMstRgw(mstAboutKB, &l, st, cchMaxSz, hNil); FSetCabSt(pcmb->hcab, st, Iag(CABABOUT, hszAboutMem)); @@ -998,6 +1023,9 @@ CMB *pcmb; if (!fInitialized) InitMath(); } +#ifdef OPUS_X64 + SetPropA(vhwndApp, "OpusX64AboutStage", (HANDLE)(INT_PTR)3); +#endif FSetCabSz(pcmb->hcab, f8087 ? SzSharedKey("Present", Present) : szNone, Iag(CABABOUT, hszAboutMath)); @@ -1013,9 +1041,17 @@ CMB *pcmb; if (pcmb->fDialog || pcmb->fAction) { char dlt[sizeof(dltAbout)]; + TMC tmc; BltDlt(dltAbout, dlt); - if (TmcOurDoDlg(dlt, pcmb) == tmcError) +#ifdef OPUS_X64 + SetPropA(vhwndApp, "OpusX64AboutStage", (HANDLE)(INT_PTR)4); +#endif + tmc = TmcOurDoDlg(dlt, pcmb); +#ifdef OPUS_X64 + SetPropA(vhwndApp, "OpusX64AboutStage", (HANDLE)(INT_PTR)(100 + tmc)); +#endif + if (tmc == tmcError) { return cmdError; } diff --git a/src/Opus/ourmath.h b/src/Opus/ourmath.h index 3b8b3e5..0c73bf0 100644 --- a/src/Opus/ourmath.h +++ b/src/Opus/ourmath.h @@ -27,7 +27,11 @@ typedef struct typedef struct { +#ifdef OPUS_X64 + SHORT w1, w2; +#else int w1, w2; +#endif } CMPNUMRET; /* really CmpNum returns a long */ @@ -82,8 +86,13 @@ typedef union /* temporary simulation stuff until we get the real mathpack */ typedef union { struct { +#ifdef OPUS_X64 + WORD w1; + SHORT w0; +#else unsigned w1; int w0; +#endif }; LONG l; } NUMCVT; diff --git a/src/Opus/save.c b/src/Opus/save.c index 0d8b81e..f9f2009 100644 --- a/src/Opus/save.c +++ b/src/Opus/save.c @@ -253,9 +253,13 @@ LRet: /* C M D S A V E A S */ /* %%Function:CmdSaveAs %%Owner:peterj */ CMD CmdSaveAs(pcmb) +CMB * pcmb; { CMD cmd; BOOL fDirty; +#ifdef OPUS_X64 + SetPropA(vhwndApp, "OpusX64SaveAsStage", (HANDLE)(INT_PTR)1); +#endif if (selCur.doc == docNil) { @@ -272,6 +276,9 @@ CMD CmdSaveAs(pcmb) { DoPostSaveDoc(pcmb, fDirty); } +#ifdef OPUS_X64 + SetPropA(vhwndApp, "OpusX64SaveAsStage", (HANDLE)(INT_PTR)(100 + cmd)); +#endif LRet: /* at all costs, avoid telling the user to save his work when giving an error message for save! */ @@ -795,8 +802,16 @@ CMB * pcmb; pcab = *pcmb->hcab; pcab->sab = 0; pcab->fOptions = fFalse; +#ifdef OPUS_X64 + SetPropA(vhwndApp, "OpusX64SaveAsStage", (HANDLE)(INT_PTR)2); +#endif - switch (TmcOurDoDlg(dlt, pcmb)) + { + TMC tmc = TmcOurDoDlg(dlt, pcmb); +#ifdef OPUS_X64 + SetPropA(vhwndApp, "OpusX64SaveAsStage", (HANDLE)(INT_PTR)(200 + tmc)); +#endif + switch (tmc) { #ifdef DEBUG default: @@ -812,9 +827,10 @@ CMB * pcmb; cmd = cmdCancelled; break; - case tmcOK: - cmd = cmdOK; + case tmcOK: + cmd = cmdOK; } + } pcmb->bcm = bcmSav; diff --git a/src/port/original/about.sdm b/src/port/original/about.sdm index 76c51ec..c9ae07a 100644 --- a/src/port/original/about.sdm +++ b/src/port/original/about.sdm @@ -1,6 +1,9 @@ -/* - * Compile-only template boundary for Opus/dlg/about.des. The full template - * must be regenerated by the ported historical dialog compiler before the - * About dialog is runtime-active. - */ -DLG_CONST DLT dltAbout = { 0 }; +#pragma once + +/* Regenerated from Opus/dlg/about.des. The x64 SDM compatibility layer + materializes the archived controls as native Win32 controls. */ +extern BOOL FDlgAbout(DLM, TMC, WORD, WORD, WORD); +DLG_CONST DLT dltAbout = { + { 85, 55, 205, 115 }, IDDAbout, tmcOK, FDlgAbout, + 13, bdrSysMenu, { 0 } +}; diff --git a/src/port/original/opus_sdm_runtime.cpp b/src/port/original/opus_sdm_runtime.cpp index 18c32b7..9414765 100644 --- a/src/port/original/opus_sdm_runtime.cpp +++ b/src/port/original/opus_sdm_runtime.cpp @@ -131,9 +131,11 @@ constexpr Word kDlmDialogClick = 0x0012; constexpr UINT kWmCommitRibbonSelection = WM_APP + 0x352; constexpr Word kIddNewDoc = 2; constexpr Word kIddOpen = 3; +constexpr Word kIddSaveAs = 4; constexpr Word kIddCharacter = 16; constexpr Word kIddApplyStyle = 23; constexpr Word kIddDefineStyle = 24; +constexpr Word kIddAbout = 44; constexpr Word kCxtRibbonIconBar = 0x8005; constexpr Word kCxtRulerIconBar = 0x8006; constexpr Tmc kTmcOk = 1; @@ -173,6 +175,16 @@ constexpr Tmc kTmcOpenFileList = 0x0401; constexpr Tmc kTmcOpenFileDir = 0x0402; constexpr Tmc kTmcOpenCatalog = 0x0403; constexpr Tmc kTmcOpenReadOnly = 0x0404; +constexpr Tmc kTmcSaveFile = kTmcUserMin; +constexpr Tmc kTmcSaveOptions = kTmcUserMin + 1; +constexpr Tmc kTmcSaveDirectoryText = kTmcUserMin + 2; +constexpr Tmc kTmcSaveDirectoryList = kTmcUserMin + 3; +constexpr Tmc kTmcSaveDirectory = kTmcUserMin + 4; +constexpr Tmc kTmcSaveFormatPrompt = kTmcUserMin + 5; +constexpr Tmc kTmcSaveFormat = kTmcUserMin + 6; +constexpr Tmc kTmcSaveQuick = kTmcUserMin + 7; +constexpr Tmc kTmcSaveBackup = kTmcUserMin + 8; +constexpr Tmc kTmcSaveLockAnnotations = kTmcUserMin + 9; constexpr Word kOpenFileNameIag = 1; constexpr std::size_t kOpenCabBytes = 24; constexpr std::size_t kOpenReadOnlyOffset = 20; @@ -246,6 +258,7 @@ ATOM ensure_native_dialog_class() { HWND create_dialog_host(const DialogState& dialog, const Dli* initializer) { if (dialog.modal && (dialog.hid == kIddOpen || dialog.hid == kIddNewDoc || + dialog.hid == kIddSaveAs || dialog.hid == kIddAbout || dialog.hid == kIddCharacter || dialog.hid == kIddApplyStyle || dialog.hid == kIddDefineStyle)) { if (ensure_native_dialog_class() == 0) { @@ -291,6 +304,9 @@ HWND create_dialog_host(const DialogState& dialog, const Dli* initializer) { case kIddNewDoc: caption = "New"; break; + case kIddSaveAs: + caption = "Save As"; + break; case kIddCharacter: caption = "Character"; break; @@ -300,6 +316,9 @@ HWND create_dialog_host(const DialogState& dialog, const Dli* initializer) { case kIddDefineStyle: caption = "Define Style"; break; + case kIddAbout: + caption = "About Microsoft Word"; + break; } return CreateWindowExA( extended_style, "OpusSdmDialog", caption, style, x, y, width, @@ -783,6 +802,247 @@ void materialize_open_template(DialogState& dialog) { read_open_cab(dialog); } +std::string read_cab_string(const DialogState& dialog, const Word iag) { + char buffer[1024] = {}; + if (dialog.cab != nullptr) { + GetCabSz(dialog.cab, buffer, static_cast(sizeof(buffer)), iag); + } + return buffer; +} + +void materialize_about_template(DialogState& dialog) { + if (dialog.hid != kIddAbout || dialog.window == nullptr) { + return; + } + + const auto static_from_cab = [&dialog](const Word iag, const Rec& rec, + const DWORD alignment) { + const std::string text = read_cab_string(dialog, iag); + create_untracked_control(dialog, "STATIC", text.c_str(), rec, + alignment); + }; + static_from_cab(1, {4, 5, 192, 9}, SS_CENTER); + static_from_cab(2, {4, 22, 192, 9}, SS_CENTER); + static_from_cab(3, {4, 34, 192, 9}, SS_CENTER); + create_native_control(dialog, kTmcOk, "BUTTON", "OK", + {84, 48, 34, 14}, + WS_TABSTOP | BS_DEFPUSHBUTTON); + create_untracked_control(dialog, "STATIC", "", {0, 64, 205, 4}, + SS_ETCHEDHORZ); + create_untracked_control(dialog, "STATIC", "Conventional Memory:", + {5, 74, 110, 9}, SS_RIGHT); + create_untracked_control(dialog, "STATIC", "Expanded Memory:", + {5, 83, 110, 9}, SS_RIGHT); + create_untracked_control(dialog, "STATIC", "Math Co-processor:", + {5, 92, 110, 9}, SS_RIGHT); + create_untracked_control(dialog, "STATIC", "Disk Space:", + {5, 101, 110, 9}, SS_RIGHT); + static_from_cab(4, {120, 74, 76, 9}, SS_LEFT); + static_from_cab(5, {120, 83, 76, 9}, SS_LEFT); + static_from_cab(6, {120, 92, 76, 9}, SS_LEFT); + static_from_cab(7, {120, 101, 76, 9}, SS_LEFT); + dialog.caption = "About Microsoft Word"; + dialog.native_modal = true; +} + +struct CabSaveNative { + Word simple_words; + Word handle_words; + Word sab; + Word alignment; + char** file_name; + int directory_list; + int format; + int quick_save; + int backup; + int lock_annotations; + int options; +}; + +CabSaveNative* save_cab(DialogState& dialog) { + if (dialog.cab == nullptr || *dialog.cab == nullptr || + OpusCbOfH(dialog.cab) < sizeof(CabSaveNative)) { + return nullptr; + } + return static_cast(*dialog.cab); +} + +void set_save_check(DialogState& dialog, const Tmc tmc, const bool checked) { + auto& state = dialog.controls[tmc]; + state.value = checked; + if (state.window != nullptr && IsWindow(state.window)) { + SendMessageA(state.window, BM_SETCHECK, + checked ? BST_CHECKED : BST_UNCHECKED, 0); + } +} + +void populate_save_directories(DialogState& dialog) { + char directory[32768] = {}; + if (dialog.current_directory.empty()) { + GetCurrentDirectoryA(static_cast(sizeof(directory)), directory); + dialog.current_directory = directory; + } + + auto& list = dialog.controls[kTmcSaveDirectoryList]; + list.entries.clear(); + if (list.window != nullptr) { + SendMessageA(list.window, LB_RESETCONTENT, 0, 0); + } + + char parent[32768] = {}; + const std::string parent_spec = join_path(dialog.current_directory, ".."); + if (GetFullPathNameA(parent_spec.c_str(), + static_cast(sizeof(parent)), parent, + nullptr) != 0 && + _stricmp(parent, dialog.current_directory.c_str()) != 0) { + add_native_list_entry(dialog, kTmcSaveDirectoryList, "[..]"); + } + + WIN32_FIND_DATAA find_data{}; + HANDLE find = FindFirstFileA( + join_path(dialog.current_directory, "*.*").c_str(), &find_data); + if (find != INVALID_HANDLE_VALUE) { + do { + if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 && + std::strcmp(find_data.cFileName, ".") != 0 && + std::strcmp(find_data.cFileName, "..") != 0) { + add_native_list_entry( + dialog, kTmcSaveDirectoryList, + "[" + std::string(find_data.cFileName) + "]"); + } + } while (FindNextFileA(find, &find_data)); + FindClose(find); + } + + auto& label = dialog.controls[kTmcSaveDirectory]; + label.text = dialog.current_directory; + if (label.window != nullptr) { + SetWindowTextA(label.window, label.text.c_str()); + } +} + +void set_save_options_visible(DialogState& dialog, const bool visible) { + const Tmc option_controls[] = { + kTmcSaveFormatPrompt, kTmcSaveFormat, kTmcSaveQuick, + kTmcSaveBackup, kTmcSaveLockAnnotations}; + for (const Tmc tmc : option_controls) { + auto found = dialog.controls.find(tmc); + if (found == dialog.controls.end()) { + continue; + } + found->second.visible = visible; + if (found->second.window != nullptr) { + ShowWindow(found->second.window, visible ? SW_SHOWNA : SW_HIDE); + } + } + if (dialog.window == nullptr || !IsWindow(dialog.window)) { + return; + } + RECT client{0, 0, scaled_x(150), scaled_y(visible ? 157 : 102)}; + const DWORD style = static_cast(GetWindowLongPtrA( + dialog.window, GWL_STYLE)); + const DWORD extended_style = static_cast(GetWindowLongPtrA( + dialog.window, GWL_EXSTYLE)); + AdjustWindowRectEx(&client, style, false, extended_style); + SetWindowPos(dialog.window, nullptr, 0, 0, + client.right - client.left, client.bottom - client.top, + SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); +} + +void read_save_cab(DialogState& dialog) { + auto& edit = dialog.controls[kTmcSaveFile]; + edit.text = read_cab_string(dialog, 1); + if (edit.window != nullptr) { + SetWindowTextA(edit.window, edit.text.c_str()); + } + + if (auto* cab = save_cab(dialog); cab != nullptr) { + set_save_check(dialog, kTmcSaveQuick, cab->quick_save != 0); + set_save_check(dialog, kTmcSaveBackup, cab->backup != 0); + set_save_check(dialog, kTmcSaveLockAnnotations, + cab->lock_annotations != 0); + auto& format = dialog.controls[kTmcSaveFormat]; + format.value = static_cast(cab->format); + const std::string entry = "Current document format"; + format.entries = {entry}; + add_entry_to_native_control(format, entry); + if (format.window != nullptr) { + SendMessageA(format.window, CB_SETCURSEL, 0, 0); + } + } + populate_save_directories(dialog); +} + +void sync_save_cab(DialogState& dialog) { + auto& edit = dialog.controls[kTmcSaveFile]; + if (edit.window != nullptr && IsWindow(edit.window)) { + const int length = GetWindowTextLengthA(edit.window); + std::vector buffer(static_cast(length) + 1); + GetWindowTextA(edit.window, buffer.data(), + static_cast(buffer.size())); + edit.text = buffer.data(); + } + if (dialog.cab != nullptr) { + FSetCabSz(dialog.cab, edit.text.c_str(), 1); + } + if (auto* cab = save_cab(dialog); cab != nullptr) { + const auto checked = [&dialog](const Tmc tmc) { + const auto found = dialog.controls.find(tmc); + return found != dialog.controls.end() && + found->second.window != nullptr && + SendMessageA(found->second.window, BM_GETCHECK, 0, 0) == + BST_CHECKED; + }; + cab->quick_save = checked(kTmcSaveQuick); + cab->backup = checked(kTmcSaveBackup); + cab->lock_annotations = checked(kTmcSaveLockAnnotations); + cab->options = dialog.sab == 0 && + dialog.controls[kTmcSaveFormat].visible; + } +} + +void materialize_save_as_template(DialogState& dialog) { + if (dialog.hid != kIddSaveAs || dialog.window == nullptr) { + return; + } + create_static_text(dialog, "Save File &Name:", {4, 2, 60, 9}); + create_native_control(dialog, kTmcSaveFile, "EDIT", "", + {4, 13, 81, 12}, + WS_TABSTOP | WS_BORDER | ES_AUTOHSCROLL); + create_native_control(dialog, kTmcOk, "BUTTON", "OK", {97, 6, 47, 14}, + WS_TABSTOP | BS_DEFPUSHBUTTON); + create_native_control(dialog, kTmcCancel, "BUTTON", "Cancel", + {97, 25, 47, 14}, WS_TABSTOP | BS_PUSHBUTTON); + create_native_control(dialog, kTmcSaveOptions, "BUTTON", "&Options >>", + {97, 42, 47, 14}, WS_TABSTOP | BS_PUSHBUTTON); + create_native_control(dialog, kTmcSaveDirectoryText, "STATIC", + "&Directories:", {4, 38, 73, 9}, SS_LEFT); + create_native_control(dialog, kTmcSaveDirectoryList, "LISTBOX", "", + {4, 50, 89, 48}, + WS_TABSTOP | WS_BORDER | WS_VSCROLL | LBS_NOTIFY | + LBS_SORT | LBS_NOINTEGRALHEIGHT); + create_native_control(dialog, kTmcSaveDirectory, "STATIC", "", + {4, 27, 90, 9}, SS_LEFT); + create_native_control(dialog, kTmcSaveFormatPrompt, "STATIC", + "&File Format:", {4, 105, 50, 9}, SS_LEFT); + create_native_control(dialog, kTmcSaveFormat, "COMBOBOX", "", + {55, 103, 91, 72}, + WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST); + create_native_control(dialog, kTmcSaveQuick, "BUTTON", "Fast &Save", + {4, 116, 46, 12}, + WS_TABSTOP | BS_AUTOCHECKBOX); + create_native_control(dialog, kTmcSaveBackup, "BUTTON", "Create &Backup", + {4, 128, 62, 12}, + WS_TABSTOP | BS_AUTOCHECKBOX); + create_native_control(dialog, kTmcSaveLockAnnotations, "BUTTON", + "&Lock for Annotations", {4, 140, 92, 12}, + WS_TABSTOP | BS_AUTOCHECKBOX); + dialog.caption = "Save As"; + dialog.native_modal = true; + read_save_cab(dialog); + set_save_options_visible(dialog, false); +} + bool is_font_name_control(const DialogState& dialog, const Tmc tmc) { return (dialog.hid == kCxtRibbonIconBar && tmc == kTmcUserMin) || (dialog.hid == kIddCharacter && tmc == kTmcCharacterName); @@ -1546,6 +1806,8 @@ void enter_open_directory(DialogState& dialog) { void finish_native_dialog(DialogState& dialog, const Tmc result) { if (dialog.hid == kIddOpen) { sync_open_cab(dialog); + } else if (dialog.hid == kIddSaveAs) { + sync_save_cab(dialog); } else if (dialog.hid == kIddNewDoc) { sync_new_cab(dialog); } else if (dialog.hid == kIddApplyStyle) { @@ -1593,6 +1855,17 @@ void handle_dialog_command(const Hdlg handle, const WPARAM w_param, GetWindowTextA(found->second.window, text.data(), static_cast(text.size())); found->second.text = text.data(); + } else if (tmc == kTmcSaveFile && dialog->hid == kIddSaveAs) { + const int length = GetWindowTextLengthA(found->second.window); + std::vector text(static_cast(length) + 1); + GetWindowTextA(found->second.window, text.data(), + static_cast(text.size())); + found->second.text = text.data(); + } else if (dialog->hid == kIddSaveAs && + (tmc == kTmcSaveQuick || tmc == kTmcSaveBackup || + tmc == kTmcSaveLockAnnotations)) { + found->second.value = static_cast(SendMessageA( + found->second.window, BM_GETCHECK, 0, 0) == BST_CHECKED); } } g_current_dialog = handle; @@ -1685,6 +1958,24 @@ void handle_dialog_command(const Hdlg handle, const WPARAM w_param, invoke_dialog_proc(*dialog, kDlmChange, tmc); return; } + if (dialog->hid == kIddSaveAs && tmc == kTmcSaveFile && + notification == EN_CHANGE) { + invoke_dialog_proc(*dialog, kDlmChange, tmc); + return; + } + if (dialog->hid == kIddSaveAs && tmc == kTmcSaveDirectoryList && + (notification == LBN_SELCHANGE || notification == LBN_DBLCLK)) { + const LRESULT selection = SendMessageA( + found->second.window, LB_GETCURSEL, 0, 0); + if (selection != LB_ERR) { + found->second.value = static_cast(selection); + } + invoke_dialog_proc(*dialog, + notification == LBN_DBLCLK ? kDlmDblClk + : kDlmClick, + tmc, found->second.value); + return; + } if (dialog->hid == kIddOpen && tmc == kTmcOpenFileList && (notification == LBN_SELCHANGE || notification == LBN_DBLCLK)) { select_open_file(*dialog); @@ -1723,7 +2014,10 @@ void handle_dialog_command(const Hdlg handle, const WPARAM w_param, tmc == kTmcSummary)) { finish_native_dialog(*dialog, tmc); } else { - invoke_dialog_proc(*dialog, kDlmClick, tmc); + const Word new_value = found == dialog->controls.end() + ? 0 + : found->second.value; + invoke_dialog_proc(*dialog, kDlmClick, tmc, new_value); } } } @@ -1868,6 +2162,8 @@ Hdlg HdlgStartDlg(DltHeader** dialog_template, Hcab cab, Dli* initializer) { materialize_icon_bar_template(g_dialogs.at(handle)); materialize_new_template(g_dialogs.at(handle)); materialize_open_template(g_dialogs.at(handle)); + materialize_save_as_template(g_dialogs.at(handle)); + materialize_about_template(g_dialogs.at(handle)); materialize_character_template(g_dialogs.at(handle)); materialize_apply_style_template(g_dialogs.at(handle)); materialize_define_style_template(g_dialogs.at(handle)); @@ -2063,6 +2359,9 @@ void SdmScaleRec(Rec*) {} int FSetDlgSab(Word sab) { g_dialog.sab = sab; + if (g_dialog.hid == kIddSaveAs) { + set_save_options_visible(g_dialog, true); + } return true; } Word SabGetDlg() { return g_dialog.sab; } @@ -2071,7 +2370,14 @@ void SetTmcVal_sdm21(Tmc tmc, Word value) { auto& state = control(tmc); state.value = value; if (state.window != nullptr && IsWindow(state.window)) { - SendMessageA(state.window, CB_SETCURSEL, value, 0); + if (window_is_class(state.window, "BUTTON")) { + SendMessageA(state.window, BM_SETCHECK, + value ? BST_CHECKED : BST_UNCHECKED, 0); + } else if (window_is_class(state.window, "LISTBOX")) { + SendMessageA(state.window, LB_SETCURSEL, value, 0); + } else { + SendMessageA(state.window, CB_SETCURSEL, value, 0); + } } } Word ValGetTmc(Tmc tmc) { @@ -2080,6 +2386,18 @@ Word ValGetTmc(Tmc tmc) { if (found == g_dialog.controls.end()) { return 0; } + if (found->second.window != nullptr && + window_is_class(found->second.window, "BUTTON")) { + found->second.value = static_cast(SendMessageA( + found->second.window, BM_GETCHECK, 0, 0) == BST_CHECKED); + } else if (found->second.window != nullptr && + window_is_class(found->second.window, "LISTBOX")) { + const LRESULT selection = SendMessageA( + found->second.window, LB_GETCURSEL, 0, 0); + if (selection != LB_ERR) { + found->second.value = static_cast(selection); + } + } refresh_font_control_value(g_dialog, value_tmc, found->second, true); return found->second.value; } diff --git a/src/port/original/opus_x64_compat.h b/src/port/original/opus_x64_compat.h index 8c93dbc..87a1e7c 100644 --- a/src/port/original/opus_x64_compat.h +++ b/src/port/original/opus_x64_compat.h @@ -19,6 +19,16 @@ #include #include +/* A null Win16 module handle made GetProcAddress fail. On current Windows, + * passing that invalid handle can instead search the process image. Opus + * probes retired modules such as KERNEL, GDI, and USER by ordinal, so an + * unsuccessful probe must not be allowed to resolve an unrelated WORD1 + * export with the same ordinal. */ +static __inline FARPROC OpusGetProcAddress(HMODULE module, LPCSTR name) { + return module != NULL ? GetProcAddress(module, name) : NULL; +} +#define GetProcAddress OpusGetProcAddress + /* The SDK retains ChangeMenu only for source compatibility, with its Win16 * UINT submenu parameter. That truncates HMENU on Win64. */ #ifdef ChangeMenu diff --git a/src/port/original/saveas.sdm b/src/port/original/saveas.sdm index 009c4f0..1f58fd5 100644 --- a/src/port/original/saveas.sdm +++ b/src/port/original/saveas.sdm @@ -1,7 +1,9 @@ -/* - * Placeholder template boundary for the checked-in saveas.des description. - * It lets the original save algorithms compile while the missing historical - * dialog compiler is being ported. This template must be regenerated before - * the Save As dialog is made runtime-active. - */ -DLG_CONST DLT dltSaveAs = { 0 }; +#pragma once + +/* Regenerated from Opus/dlg/saveas.des. The x64 SDM compatibility layer + materializes the archived controls as native Win32 controls. */ +extern BOOL FDlgSaveAs(DLM, TMC, WORD, WORD, WORD); +DLG_CONST DLT dltSaveAs = { + { 14, 18, 150, 102 }, IDDSaveAs, tmcSavFile, FDlgSaveAs, + 13, bdrSysMenu, { 0 } +}; diff --git a/src/port/tools/opus_cabi_tool.cpp b/src/port/tools/opus_cabi_tool.cpp new file mode 100644 index 0000000..ea4517a --- /dev/null +++ b/src/port/tools/opus_cabi_tool.cpp @@ -0,0 +1,199 @@ +#include "opus_x64_compat.h" + +#undef native +#include +#include +#include + +#ifndef NEAR +#define NEAR +#endif +extern "C" { +#include "sdmver.h" +#include "sdm.h" +} + +/* Reconstructed outputs of Microsoft's missing Dialog Editor compiler. The + original MKCMD parser consumes only each header's numeric cabi definition; + compiling the contracts here lets sizeof reflect the native pointer width + and alignment used by the x64 source modules. */ +#include "about.hs" +#include "abspos.hs" +#include "apprun.hs" +#include "asgn2key.hs" +#include "asgn2mnu.hs" +#include "autosave.hs" +#include "bookmark.hs" +#include "catalog.hs" +#include "catprog.hs" +#include "catsrch.hs" +#include "char.hs" +#include "chgpr.hs" +#include "cmpfile.hs" +#include "confirmr.hs" +#include "cust.hs" +#include "doc.hs" +#include "docstat.hs" +#include "docsum.hs" +#include "edmacro.hs" +#include "edstyle.hs" +#include "footnote.hs" +#include "glsy.hs" +#include "goto.hs" +#include "header.hs" +#include "hyphen.hs" +#include "index.hs" +#include "indexent.hs" +#include "insbreak.hs" +#include "insfield.hs" +#include "insfile.hs" +#include "inspgnum.hs" +#include "inspic.hs" +#include "mrgstyle.hs" +#include "new.hs" +#include "newopen.hs" +#include "open.hs" +#include "para.hs" +#include "password.hs" +#include "pastelnk.hs" +#include "pict.hs" +#include "print.hs" +#include "printmrg.hs" +#include "prompt.hs" +#include "recorder.hs" +#include "renmacro.hs" +#include "renstyle.hs" +#include "renum.hs" +#include "replace.hs" +#include "revmark.hs" +#include "ribbon.hs" +#include "ribbon3.hs" +#include "ruler.hs" +#include "ruler3.hs" +#include "runmacro.hs" +#include "saveas.hs" +#include "search.hs" +#include "sect.hs" +#include "showvars.hs" +#include "sort.hs" +#include "spell.hs" +#include "spellmm.hs" +#include "style.hs" +#include "tablecmd.hs" +#include "tablefmt.hs" +#include "tableins.hs" +#include "tabletxt.hs" +#include "tabs.hs" +#include "thesaur.hs" +#include "toc.hs" +#include "username.hs" +#include "usrdlg.hs" +#include "viewpref.hs" +#include "vrfcnvtr.hs" + +namespace { + +bool WriteCabi(const std::filesystem::path& directory, const char* file_name, + const char* macro_name, const unsigned value) { + std::ofstream output(directory / file_name, std::ios::trunc); + if (!output) { + return false; + } + output << "#define " << macro_name << ' ' << value << '\n'; + return output.good(); +} + +} // namespace + +#define WRITE_CABI(file_name, macro_name) \ + success = WriteCabi(output_directory, file_name, #macro_name, \ + static_cast(macro_name)) && \ + success + +int main(int argc, char** argv) { + if (argc != 2) { + return 2; + } + const std::filesystem::path output_directory(argv[1]); + std::error_code error; + std::filesystem::create_directories(output_directory, error); + if (error) { + return 3; + } + + bool success = true; + WRITE_CABI("about.hs", cabiCABABOUT); + WRITE_CABI("abspos.hs", cabiCABABSPOS); + WRITE_CABI("apprun.hs", cabiCABAPPRUN); + WRITE_CABI("asgn2key.hs", cabiCABCHANGEKEYS); + WRITE_CABI("asgn2mnu.hs", cabiCABASSIGNTOMENU); + WRITE_CABI("autosave.hs", cabiCABAUTOSAVE); + WRITE_CABI("bookmark.hs", cabiCABINSBOOKMARK); + WRITE_CABI("catalog.hs", cabiCABCATALOG); + WRITE_CABI("catprog.hs", cabiCABCATSRHPROG); + WRITE_CABI("catsrch.hs", cabiCABCATSEARCH); + WRITE_CABI("char.hs", cabiCABCHARACTER); + WRITE_CABI("chgpr.hs", cabiCABCHGPR); + WRITE_CABI("cmpfile.hs", cabiCABCMPFILE); + WRITE_CABI("confirmr.hs", cabiCABCONFIRMREPL); + WRITE_CABI("cust.hs", cabiCABCUSTOMIZE); + WRITE_CABI("doc.hs", cabiCABDOCUMENT); + WRITE_CABI("docstat.hs", cabiCABDOCSTAT); + WRITE_CABI("docsum.hs", cabiCABDOCSUM); + WRITE_CABI("edmacro.hs", cabiCABEDMACRO); + WRITE_CABI("edstyle.hs", cabiCABDEFINESTYLE); + WRITE_CABI("footnote.hs", cabiCABINSERTFTN); + WRITE_CABI("glsy.hs", cabiCABGLOSSARY); + WRITE_CABI("goto.hs", cabiCABGOTO); + WRITE_CABI("header.hs", cabiCABHEADER); + WRITE_CABI("hyphen.hs", cabiCABHYPHEN); + WRITE_CABI("index.hs", cabiCABINDEX); + WRITE_CABI("indexent.hs", cabiCABINDEXENTRY); + WRITE_CABI("insbreak.hs", cabiCABINSBREAK); + WRITE_CABI("insfield.hs", cabiCABINSFIELD); + WRITE_CABI("insfile.hs", cabiCABINSFILE); + WRITE_CABI("inspgnum.hs", cabiCABINSPGNUM); + WRITE_CABI("inspic.hs", cabiCABINSPIC); + WRITE_CABI("mrgstyle.hs", cabiCABMERGESTYLE); + WRITE_CABI("new.hs", cabiCABNEWDOC); + WRITE_CABI("newopen.hs", cabiCABNEWOPEN); + WRITE_CABI("open.hs", cabiCABOPEN); + WRITE_CABI("para.hs", cabiCABPARALOOKS); + WRITE_CABI("password.hs", cabiCABFILEPSWD); + WRITE_CABI("pastelnk.hs", cabiCABPASTELINK); + WRITE_CABI("pict.hs", cabiCABFORMATPIC); + WRITE_CABI("print.hs", cabiCABPRINT); + WRITE_CABI("printmrg.hs", cabiCABPRINTMERGE); + WRITE_CABI("prompt.hs", cabiCABPROMPT); + WRITE_CABI("recorder.hs", cabiCABRECORDER); + WRITE_CABI("renmacro.hs", cabiCABRENMACRO); + WRITE_CABI("renstyle.hs", cabiCABRENAMESTYLE); + WRITE_CABI("renum.hs", cabiCABRENUMPARAS); + WRITE_CABI("replace.hs", cabiCABREPLACE); + WRITE_CABI("revmark.hs", cabiCABREVMARKING); + WRITE_CABI("ribbon.hs", cabiCABRIBBON); + WRITE_CABI("ribbon3.hs", cabiCABRIBBON3); + WRITE_CABI("ruler.hs", cabiCABRULER); + WRITE_CABI("ruler3.hs", cabiCABRULER3); + WRITE_CABI("runmacro.hs", cabiCABRUNMACRO); + WRITE_CABI("saveas.hs", cabiCABSAVE); + WRITE_CABI("search.hs", cabiCABSEARCH); + WRITE_CABI("sect.hs", cabiCABSECTION); + WRITE_CABI("showvars.hs", cabiCABSHOWVARS); + WRITE_CABI("sort.hs", cabiCABSORT); + WRITE_CABI("spell.hs", cabiCABSPELLER); + WRITE_CABI("spellmm.hs", cabiCABSPELLERMM); + WRITE_CABI("style.hs", cabiCABAPPLYSTYLE); + WRITE_CABI("tablecmd.hs", cabiCABEDITTABLE); + WRITE_CABI("tablefmt.hs", cabiCABFORMATTABLE); + WRITE_CABI("tableins.hs", cabiCABINSERTTABLE); + WRITE_CABI("tabletxt.hs", cabiCABTABLETOTEXT); + WRITE_CABI("tabs.hs", cabiCABTABS); + WRITE_CABI("thesaur.hs", cabiCABTHESAURUS); + WRITE_CABI("toc.hs", cabiCABTOC); + WRITE_CABI("username.hs", cabiCABUSERNAME); + WRITE_CABI("usrdlg.hs", cabiCABUSRDLG); + WRITE_CABI("viewpref.hs", cabiCABVIEWPREF); + WRITE_CABI("vrfcnvtr.hs", cabiCABEXCR); + return success ? 0 : 4; +}