Maps in widened views: TAB overlay in place, M map across the full width
- hud: shift SetViewport/Clear with moved windows (TAB overview map) - megamap: view constants patched to the render width (clip, scroll limit, edge scrolling, centre, dark band, zoomed view and its markers); frame stretched to the screen edges, zoom controls and their click area centred - hud: Anchor::Screen windows are drawn and clicked unshifted but still own the event scope - autotest waits for log markers instead of fixed delays (~18 s per run) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a7f8496b2b
commit
c6c5530709
9 files changed
+327
-18
No files matched your search
@@ -52,6 +52,7 @@ add_library(sacred_re SHARED
|
|||||||
src/mods/chainlightning.cpp
|
src/mods/chainlightning.cpp
|
||||||
src/mods/escape.cpp
|
src/mods/escape.cpp
|
||||||
src/mods/recordcache.cpp
|
src/mods/recordcache.cpp
|
||||||
|
src/mods/megamap.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(sacred_re PRIVATE src)
|
target_include_directories(sacred_re PRIVATE src)
|
||||||
target_compile_options(sacred_re PRIVATE ${SACRED_RE_CXX_FLAGS})
|
target_compile_options(sacred_re PRIVATE ${SACRED_RE_CXX_FLAGS})
|
||||||
|
|||||||
+29
@@ -86,3 +86,32 @@ tests. Visible dialog tooltips: `mods::hud::VisibleDialogTooltips`.
|
|||||||
|
|
||||||
The standalone control hooks (buttons, texts) above turned out not to be what
|
The standalone control hooks (buttons, texts) above turned out not to be what
|
||||||
the quest dialog is made of; they stay, for top-level controls in general.
|
the quest dialog is made of; they stay, for top-level controls in general.
|
||||||
|
|
||||||
|
## Large map (M) across the render width
|
||||||
|
|
||||||
|
`mods/megamap.cpp`. cUI_Megamap (vtable 0x894714) is not moved like the other
|
||||||
|
windows (`Anchor::Screen`: drawn and clicked unshifted); its 1024x768 view is
|
||||||
|
widened instead, when the render size is applied (`PatchLayout`):
|
||||||
|
|
||||||
|
| What | Where | Game | Now |
|
||||||
|
|---|---|---|---|
|
||||||
|
| cursor at the right edge (edge scrolling, cursor shape) | 9 × `cmp reg, 3FFh` 0x6c7625..0x6c9e32 | 1023 | width − 1 |
|
||||||
|
| tiles right of the view skipped; labels flipped left | `cmp reg, 400h` 0x6c9aa3, 0x6ca2c6, 0x6ca2e5 | 1024 | width |
|
||||||
|
| largest scroll x = map width (+0xc0160, 2048) − view | `add eax, -400h` 0x6c9edd | −1024 | −width |
|
||||||
|
| view centre (zoom frame, centred image) | 0x6ca53f, 0x6ca637, 0x6ca645 | 0x200 | width / 2 |
|
||||||
|
| scroll centred on the hero; zoomed view; its quest markers | `[0x88e6fc]` (512.0) at 0x6c7bfe, 0x6cb27a, 0x6ccb7f, 0x6cf782 | 512 | own float |
|
||||||
|
| zoomed view: other markers at 768 − d (centre + 256) | `[0x890040]` (768.0) at 0x6ce156 | 768 | width / 2 + 256 |
|
||||||
|
| dark band, right outer / inner edge | vertex immediates 0x6ca774, 0x6ca808 / 0x6ca7bc, 0x6ca84c | 1024 / 954 | width / width − 70 |
|
||||||
|
|
||||||
|
The frame is child statics of the window (vector at +0x78..+0x7c; measured:
|
||||||
|
corners 41×44, 32 px tiles, left side x < 48, right side from x 976). On
|
||||||
|
Show (vf24 0x6c7930) the right side moves to the screen edge, the top and
|
||||||
|
bottom tiles stretch over the wider span, and the four controls the map holds
|
||||||
|
itself (+0xc0440, +0xc04bc statics, +0xc0544, +0xc0600 buttons: the zoom
|
||||||
|
controls, absolute positions) stay centred, and so does the rectangle around
|
||||||
|
them at +0xc0538: the map passes only clicks inside it to the buttons
|
||||||
|
(0x6c7f32). Each control is placed once (the
|
||||||
|
game lays them out at creation only).
|
||||||
|
|
||||||
|
A window anchored `Screen` still counts as the outermost scope for events, so
|
||||||
|
its child buttons (hooked as standalone controls, `Center`) don't shift again.
|
||||||
+3
-3
@@ -56,9 +56,9 @@ follow.
|
|||||||
## Map and map zoom in widened views
|
## Map and map zoom in widened views
|
||||||
|
|
||||||
Reported 2026-10-15: the map (and zooming it) does not support the widened
|
Reported 2026-10-15: the map (and zooming it) does not support the widened
|
||||||
render sizes yet (docs/widescreen.md, docs/hud.md: windows are laid out for
|
render sizes yet. 2026-10-16: TAB map fixed (viewport shifted), M map spans
|
||||||
1024 and centred). To check: the map window's draw area and the zoom's
|
the render width (docs/hud.md, "Large map"), zoomed view and its markers included.
|
||||||
hardcoded 1024/768 (or 512/384 centre) values.
|
Confirmed in-game 2026-10-16.
|
||||||
|
|
||||||
## Quest dialog glitches in widened views
|
## Quest dialog glitches in widened views
|
||||||
|
|
||||||
|
|||||||
+55
-7
@@ -45,13 +45,14 @@ thread_local int g_offsetX = 0;
|
|||||||
thread_local bool g_eventShifted = false;
|
thread_local bool g_eventShifted = false;
|
||||||
|
|
||||||
|
|
||||||
enum class Anchor { Center, Right };
|
// Screen: laid out across the whole render width by its mod (drawn unshifted).
|
||||||
|
enum class Anchor { Center, Right, Screen };
|
||||||
|
|
||||||
int OffsetFor(Anchor anchor) {
|
int OffsetFor(Anchor anchor) {
|
||||||
static const bool enabled = re::ConfigInt("hud", "Enabled", 1) != 0;
|
static const bool enabled = re::ConfigInt("hud", "Enabled", 1) != 0;
|
||||||
const int width = mods::widescreen::RenderWidth();
|
const int width = mods::widescreen::RenderWidth();
|
||||||
if (!enabled || width <= kLayoutWidth || mods::widescreen::IsClassicFrame() ||
|
if (!enabled || width <= kLayoutWidth || mods::widescreen::IsClassicFrame() ||
|
||||||
mods::widescreen::MenuIsShowing()) {
|
mods::widescreen::MenuIsShowing() || anchor == Anchor::Screen) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return anchor == Anchor::Center ? (width - kLayoutWidth) / 2 : width - kLayoutWidth;
|
return anchor == Anchor::Center ? (width - kLayoutWidth) / 2 : width - kLayoutWidth;
|
||||||
@@ -283,11 +284,12 @@ int MouseEventXOffsets(void* event, int (&offsets)[2]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RAII: shift the mouse position by -dx for the scope (outermost window only).
|
// RAII: shift the mouse position by -dx for the scope (outermost window only;
|
||||||
|
// one with dx 0, e.g. Anchor::Screen, still keeps its children from shifting).
|
||||||
class EventOffset {
|
class EventOffset {
|
||||||
public:
|
public:
|
||||||
EventOffset(void* event, int dx) : cursor_(dx) {
|
EventOffset(void* event, int dx) : outermost_(!g_eventShifted), cursor_(outermost_ ? dx : 0) {
|
||||||
if (dx == 0 || g_eventShifted) {
|
if (!outermost_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int offsets[2];
|
int offsets[2];
|
||||||
@@ -309,6 +311,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool outermost_;
|
||||||
int count_ = 0;
|
int count_ = 0;
|
||||||
int32_t* x_[2] = {};
|
int32_t* x_[2] = {};
|
||||||
int32_t original_[2] = {};
|
int32_t original_[2] = {};
|
||||||
@@ -387,6 +390,45 @@ HRESULT __stdcall DrawIndexedPrimitiveShifted(void* device, DWORD type, DWORD fv
|
|||||||
indexCount, flags);
|
indexCount, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Viewports and cleared rectangles in screen space (the overview map, TAB,
|
||||||
|
// sets a viewport at its frame, 162,120 700x525, and draws the map inside with
|
||||||
|
// its own projection): moved like the vertices while a window draws.
|
||||||
|
struct Viewport7 {
|
||||||
|
DWORD x, y, width, height;
|
||||||
|
float minZ, maxZ;
|
||||||
|
};
|
||||||
|
struct Rect7 {
|
||||||
|
LONG x1, y1, x2, y2;
|
||||||
|
};
|
||||||
|
using SetViewportFn = HRESULT(__stdcall*)(void* device, Viewport7* viewport);
|
||||||
|
using ClearFn = HRESULT(__stdcall*)(void* device, DWORD count, Rect7* rects, DWORD flags, DWORD color, float z,
|
||||||
|
DWORD stencil);
|
||||||
|
SetViewportFn g_setViewport = nullptr;
|
||||||
|
ClearFn g_clear = nullptr;
|
||||||
|
|
||||||
|
HRESULT __stdcall SetViewportShifted(void* device, Viewport7* viewport) {
|
||||||
|
if (g_offsetX == 0 || !viewport) {
|
||||||
|
return g_setViewport(device, viewport);
|
||||||
|
}
|
||||||
|
Viewport7 shifted = *viewport;
|
||||||
|
shifted.x = static_cast<DWORD>(static_cast<int>(shifted.x) + g_offsetX);
|
||||||
|
return g_setViewport(device, &shifted);
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT __stdcall ClearShifted(void* device, DWORD count, Rect7* rects, DWORD flags, DWORD color, float z,
|
||||||
|
DWORD stencil) {
|
||||||
|
if (g_offsetX == 0 || count == 0 || !rects) {
|
||||||
|
return g_clear(device, count, rects, flags, color, z, stencil);
|
||||||
|
}
|
||||||
|
thread_local std::vector<Rect7> shifted;
|
||||||
|
shifted.assign(rects, rects + count);
|
||||||
|
for (Rect7& r : shifted) {
|
||||||
|
r.x1 += g_offsetX;
|
||||||
|
r.x2 += g_offsetX;
|
||||||
|
}
|
||||||
|
return g_clear(device, count, shifted.data(), flags, color, z, stencil);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int CursorOffsetX() {
|
int CursorOffsetX() {
|
||||||
@@ -414,6 +456,12 @@ void OnDevice(void* device) {
|
|||||||
g_drawIndexedPrimitive = reinterpret_cast<DrawIndexedPrimitiveFn>(vtable[26]);
|
g_drawIndexedPrimitive = reinterpret_cast<DrawIndexedPrimitiveFn>(vtable[26]);
|
||||||
re::AttachRuntimeHook("D3D7 DrawIndexedPrimitive", reinterpret_cast<void**>(&g_drawIndexedPrimitive),
|
re::AttachRuntimeHook("D3D7 DrawIndexedPrimitive", reinterpret_cast<void**>(&g_drawIndexedPrimitive),
|
||||||
reinterpret_cast<void*>(&DrawIndexedPrimitiveShifted));
|
reinterpret_cast<void*>(&DrawIndexedPrimitiveShifted));
|
||||||
|
// Clear 10, SetViewport 13.
|
||||||
|
g_clear = reinterpret_cast<ClearFn>(vtable[10]);
|
||||||
|
re::AttachRuntimeHook("D3D7 Clear", reinterpret_cast<void**>(&g_clear), reinterpret_cast<void*>(&ClearShifted));
|
||||||
|
g_setViewport = reinterpret_cast<SetViewportFn>(vtable[13]);
|
||||||
|
re::AttachRuntimeHook("D3D7 SetViewport", reinterpret_cast<void**>(&g_setViewport),
|
||||||
|
reinterpret_cast<void*>(&SetViewportShifted));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,8 +593,8 @@ HUD_EVENT(0x006c5f40, UiInventory_HandleEvent, Anchor::Center)
|
|||||||
HUD_RENDER(0x006c6140, UiInventory_Render, Anchor::Center)
|
HUD_RENDER(0x006c6140, UiInventory_Render, Anchor::Center)
|
||||||
HUD_EVENT(0x006c70e0, UiOverviewMap_HandleEvent, Anchor::Center)
|
HUD_EVENT(0x006c70e0, UiOverviewMap_HandleEvent, Anchor::Center)
|
||||||
HUD_RENDER(0x006c69d0, UiOverviewMap_Render, Anchor::Center)
|
HUD_RENDER(0x006c69d0, UiOverviewMap_Render, Anchor::Center)
|
||||||
HUD_EVENT(0x006c7ec0, UiMegamap_HandleEvent, Anchor::Center)
|
HUD_EVENT(0x006c7ec0, UiMegamap_HandleEvent, Anchor::Screen)
|
||||||
HUD_RENDER(0x006c9ce0, UiMegamap_Render, Anchor::Center)
|
HUD_RENDER(0x006c9ce0, UiMegamap_Render, Anchor::Screen) // laid out by mods/megamap.cpp
|
||||||
HUD_EVENT(0x006d1080, UiMaster_HandleEvent, Anchor::Center)
|
HUD_EVENT(0x006d1080, UiMaster_HandleEvent, Anchor::Center)
|
||||||
HUD_RENDER(0x006d0f00, UiMaster_Render, Anchor::Center)
|
HUD_RENDER(0x006d0f00, UiMaster_Render, Anchor::Center)
|
||||||
HUD_EVENT(0x006d2450, UiMasterCombo_HandleEvent, Anchor::Center)
|
HUD_EVENT(0x006d2450, UiMasterCombo_HandleEvent, Anchor::Center)
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
// The large map (M, cUI_Megamap) spans the whole render width.
|
||||||
|
//
|
||||||
|
// The game lays the map out for a 1024x768 screen: a scrollable view of the
|
||||||
|
// 2048x2816 world map (256 px tiles, scroll position at +0xc016c / +0xc0170),
|
||||||
|
// clipped and edge-scrolled at x 1024, centred at x 512, under a dark band
|
||||||
|
// (inner edge 70 px) and a frame of child statics (corners, 32 px edge tiles).
|
||||||
|
// Moving it as a centred window left the world visible at the sides; instead
|
||||||
|
// the view constants are set to the render width, the frame's right side moves
|
||||||
|
// to the right screen edge and its top and bottom tiles stretch across, and
|
||||||
|
// the zoom buttons at the bottom stay centred. The window is drawn and clicked
|
||||||
|
// unshifted (mods/hud.cpp, Anchor::Screen). See docs/hud.md.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "game/addresses.h"
|
||||||
|
#include "mods/megamap.h"
|
||||||
|
#include "mods/widescreen.h"
|
||||||
|
#include "re/hook.h"
|
||||||
|
#include "re/log.h"
|
||||||
|
#include "re/patch.h"
|
||||||
|
|
||||||
|
namespace mods::megamap {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr int kLayoutWidth = 1024;
|
||||||
|
|
||||||
|
// cmp reg, 3FFh: cursor at the right edge (edge scrolling, cursor shape).
|
||||||
|
constexpr uint32_t kRightEdgeCompares[] = {0x006c7625, 0x006c764c, 0x006c768c, 0x006c76d9, 0x006c76fe,
|
||||||
|
0x006c773a, 0x006c9dce, 0x006c9df4, 0x006c9e32};
|
||||||
|
// cmp reg, 400h: tiles right of the view are skipped (0x6c9aa3); a label that
|
||||||
|
// would end right of it is placed left of its point (0x6ca2c6, 0x6ca2e5).
|
||||||
|
constexpr uint32_t kWidthCompares[] = {0x006c9aa3, 0x006ca2c6, 0x006ca2e5};
|
||||||
|
// add eax, -400h: the largest scroll x is the map width minus the view.
|
||||||
|
constexpr uint32_t kScrollLimit = 0x006c9edd;
|
||||||
|
// The view centre 200h: the zoom-in frame (0x6ca53f) and the image centred
|
||||||
|
// on it (0x6ca637, 0x6ca645).
|
||||||
|
constexpr uint32_t kCentreImmediates[] = {0x006ca53f, 0x006ca637, 0x006ca645};
|
||||||
|
// fsub/fld/fadd [512.0]: centring the scroll on the hero (opening the map
|
||||||
|
// 0x6c7bfe, 0x6cb27a), the zoomed view (0x6ccb7f) and its quest markers
|
||||||
|
// (0x6cf782).
|
||||||
|
constexpr uint32_t kCentreFloatUses[] = {0x006c7bfe, 0x006cb27a, 0x006ccb7f, 0x006cf782};
|
||||||
|
// fld [768.0] as an x: markers in the zoomed view at 768 - d (0x6ce156), the
|
||||||
|
// centre plus 256.
|
||||||
|
constexpr uint32_t kZoomMarkerX = 0x006ce156;
|
||||||
|
constexpr float kZoomMarkerFromCentre = 256.0f;
|
||||||
|
// The dark band around the view (10 vertices, mov [esp+..], imm32): its right
|
||||||
|
// outer edge x 1024 and inner edge x 954.
|
||||||
|
constexpr uint32_t kBandRightOuter[] = {0x006ca774, 0x006ca808};
|
||||||
|
constexpr uint32_t kBandRightInner[] = {0x006ca7bc, 0x006ca84c};
|
||||||
|
constexpr int kBandWidth = 70;
|
||||||
|
|
||||||
|
float g_halfWidth = 512.0f;
|
||||||
|
float g_zoomMarkerX = 768.0f;
|
||||||
|
|
||||||
|
uint32_t FloatBits(float value) {
|
||||||
|
uint32_t bits;
|
||||||
|
std::memcpy(&bits, &value, sizeof(bits));
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI control layout (relative to the parent at +0x50, if any).
|
||||||
|
// Rectangles: x, y (int32), width, height (int16); a control's at +0x24.
|
||||||
|
constexpr size_t kControlRect = 0x24;
|
||||||
|
// cUI_Window2: child controls, a vector of pointers.
|
||||||
|
constexpr size_t kChildrenBegin = 0x78;
|
||||||
|
constexpr size_t kChildrenEnd = 0x7c;
|
||||||
|
// The frame: left side x < 48, right side from x 976, top tiles above y 48,
|
||||||
|
// bottom tiles from y 720 (measured).
|
||||||
|
constexpr int kFrameLeft = 48;
|
||||||
|
constexpr int kFrameRight = 976;
|
||||||
|
constexpr int kFrameTop = 48;
|
||||||
|
constexpr int kFrameBottom = 720;
|
||||||
|
// Controls the map holds itself (absolute positions, drawn by its render):
|
||||||
|
// two statics and two buttons, the zoom controls at the bottom centre.
|
||||||
|
constexpr size_t kOwnControls[] = {0xc0440, 0xc04bc, 0xc0544, 0xc0600};
|
||||||
|
// Rectangle around them: the map passes only clicks inside it to the buttons
|
||||||
|
// (its event handler, 0x6c7f32).
|
||||||
|
constexpr size_t kZoomControlsRect = 0xc0538;
|
||||||
|
|
||||||
|
int& RectX(uint8_t* rect) {
|
||||||
|
return *reinterpret_cast<int*>(rect);
|
||||||
|
}
|
||||||
|
int RectY(uint8_t* rect) {
|
||||||
|
return *reinterpret_cast<int*>(rect + 4);
|
||||||
|
}
|
||||||
|
int16_t& RectWidth(uint8_t* rect) {
|
||||||
|
return *reinterpret_cast<int16_t*>(rect + 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The game lays the controls out once, when it creates the map; they are
|
||||||
|
// placed for the render width once, too (the map is opened many times).
|
||||||
|
struct Placement {
|
||||||
|
int x;
|
||||||
|
int16_t width;
|
||||||
|
};
|
||||||
|
std::unordered_map<const void*, Placement> g_placed;
|
||||||
|
|
||||||
|
void Place(uint8_t* rect, int x, int width) {
|
||||||
|
RectX(rect) = x;
|
||||||
|
RectWidth(rect) = static_cast<int16_t>(width);
|
||||||
|
g_placed[rect] = {x, static_cast<int16_t>(width)};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AlreadyPlaced(uint8_t* rect) {
|
||||||
|
const auto it = g_placed.find(rect);
|
||||||
|
return it != g_placed.end() && it->second.x == RectX(rect) && it->second.width == RectWidth(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame pieces: the right side moves by `extra`, the top and bottom tiles
|
||||||
|
// between the corners stretch over the wider span; anything else stays
|
||||||
|
// centred.
|
||||||
|
void PlaceFramePiece(uint8_t* rect, int extra) {
|
||||||
|
const int x = RectX(rect);
|
||||||
|
const int width = RectWidth(rect);
|
||||||
|
const int y = RectY(rect);
|
||||||
|
if (x + width <= kFrameLeft) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (x >= kFrameRight) {
|
||||||
|
Place(rect, x + extra, width);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (y < kFrameTop || y >= kFrameBottom) {
|
||||||
|
const double scale = static_cast<double>(kFrameRight - kFrameLeft + extra) / (kFrameRight - kFrameLeft);
|
||||||
|
const int left = kFrameLeft + static_cast<int>((x - kFrameLeft) * scale + 0.5);
|
||||||
|
const int right = kFrameLeft + static_cast<int>((x + width - kFrameLeft) * scale + 0.5);
|
||||||
|
Place(rect, left, right - left);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Place(rect, x + extra / 2, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaceCentred(uint8_t* rect, int extra) {
|
||||||
|
if (!AlreadyPlaced(rect)) {
|
||||||
|
Place(rect, RectX(rect) + extra / 2, RectWidth(rect));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Layout(uint8_t* megamap) {
|
||||||
|
const int extra = mods::widescreen::RenderWidth() - kLayoutWidth;
|
||||||
|
if (extra <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto** child = *reinterpret_cast<uint8_t***>(megamap + kChildrenBegin);
|
||||||
|
auto** end = *reinterpret_cast<uint8_t***>(megamap + kChildrenEnd);
|
||||||
|
for (; child && child != end; ++child) {
|
||||||
|
if (*child && !AlreadyPlaced(*child + kControlRect)) {
|
||||||
|
PlaceFramePiece(*child + kControlRect, extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t offset : kOwnControls) {
|
||||||
|
PlaceCentred(megamap + offset + kControlRect, extra);
|
||||||
|
}
|
||||||
|
PlaceCentred(megamap + kZoomControlsRect, extra);
|
||||||
|
uint8_t* window = megamap + kControlRect;
|
||||||
|
if (!AlreadyPlaced(window) && RectX(window) == 0 && RectWidth(window) == kLayoutWidth) {
|
||||||
|
Place(window, 0, kLayoutWidth + extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void PatchLayout(int width) {
|
||||||
|
g_halfWidth = width / 2.0f;
|
||||||
|
int failed = re::PatchAll(kRightEdgeCompares, 0x3ff, static_cast<uint32_t>(width - 1));
|
||||||
|
failed += re::PatchAll(kWidthCompares, 0x400, static_cast<uint32_t>(width));
|
||||||
|
failed += !re::PatchInstructionDword(kScrollLimit, static_cast<uint32_t>(-kLayoutWidth),
|
||||||
|
static_cast<uint32_t>(-width));
|
||||||
|
failed += re::PatchAll(kCentreImmediates, 0x200, static_cast<uint32_t>(width / 2));
|
||||||
|
for (uint32_t instruction : kCentreFloatUses) {
|
||||||
|
failed += !re::RedirectOperand(instruction, game::addr::kScreenHalfWidthF, &g_halfWidth);
|
||||||
|
}
|
||||||
|
g_zoomMarkerX = g_halfWidth + kZoomMarkerFromCentre;
|
||||||
|
failed += !re::RedirectOperand(kZoomMarkerX, game::addr::kScreenHeightF, &g_zoomMarkerX);
|
||||||
|
failed += re::PatchAll(kBandRightOuter, FloatBits(1024.0f), FloatBits(static_cast<float>(width)));
|
||||||
|
failed += re::PatchAll(kBandRightInner, FloatBits(1024.0f - kBandWidth),
|
||||||
|
FloatBits(static_cast<float>(width - kBandWidth)));
|
||||||
|
re::Log("megamap: view width %d%s", width, failed ? " (some patches FAILED)" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mods::megamap
|
||||||
|
|
||||||
|
namespace game {
|
||||||
|
|
||||||
|
// cUI_Megamap::Show (vf24 0x6c7930, the argument: show / hide).
|
||||||
|
void __fastcall UiMegamap_Show(void* self, void* edx, int show);
|
||||||
|
RE_HOOK(0x006c7930, UiMegamap_Show);
|
||||||
|
void __fastcall UiMegamap_Show(void* self, void* edx, int show) {
|
||||||
|
if (self && (show & 0xff)) {
|
||||||
|
mods::megamap::Layout(static_cast<uint8_t*>(self));
|
||||||
|
}
|
||||||
|
UiMegamap_Show_original(self, edx, show);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace mods::megamap {
|
||||||
|
|
||||||
|
// Sets the large map's view constants to the render width (once, when the
|
||||||
|
// render size is applied).
|
||||||
|
void PatchLayout(int width);
|
||||||
|
|
||||||
|
} // namespace mods::megamap
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "mods/widescreen.h"
|
#include "mods/widescreen.h"
|
||||||
|
|
||||||
#include "mods/hud.h"
|
#include "mods/hud.h"
|
||||||
|
#include "mods/megamap.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -318,6 +319,7 @@ void ApplyRenderSize(game::DxDisplayMode& mode, HWND window) {
|
|||||||
PatchGrannyOrthoPushes(width, height);
|
PatchGrannyOrthoPushes(width, height);
|
||||||
PatchLabelArea(width, height);
|
PatchLabelArea(width, height);
|
||||||
PatchLetterbox(width);
|
PatchLetterbox(width);
|
||||||
|
mods::megamap::PatchLayout(width);
|
||||||
}
|
}
|
||||||
const double scaleX = width / kOriginalWidth;
|
const double scaleX = width / kOriginalWidth;
|
||||||
const double scaleY = height / kOriginalHeight;
|
const double scaleY = height / kOriginalHeight;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
param(
|
param(
|
||||||
[string]$Name = "autotest",
|
[string]$Name = "autotest",
|
||||||
[string]$Ini = "",
|
[string]$Ini = "",
|
||||||
[int]$LoadSeconds = 30,
|
[int]$LoadTimeoutSeconds = 60,
|
||||||
# Row of the save game in the load screen's list (1-3 visible rows).
|
# Row of the save game in the load screen's list (1-3 visible rows).
|
||||||
[int]$Save = 1,
|
[int]$Save = 1,
|
||||||
# Runs in-game after loading, before the screenshots (e.g. clicks).
|
# Runs in-game after loading, before the screenshots (e.g. clicks).
|
||||||
@@ -28,12 +28,13 @@ if ($Ini) { Set-Content $iniPath ($iniBackup + "`r`n" + $Ini + "`r`n") -Encoding
|
|||||||
$game = $null
|
$game = $null
|
||||||
try {
|
try {
|
||||||
$game = Start-Sacred
|
$game = Start-Sacred
|
||||||
Click-At 1720 779 -AfterMs 4000 # main menu: "Spiel laden"
|
Click-At 1720 779 -AfterMs 1500 # main menu: "Spiel laden"
|
||||||
if ($Save -gt 1) {
|
if ($Save -gt 1) {
|
||||||
Click-At 1342 (609 + ($Save - 1) * 254) -AfterMs 800 # load screen: the save's name (rows 254 px apart)
|
Click-At 1342 (609 + ($Save - 1) * 254) -AfterMs 800 # load screen: the save's name (rows 254 px apart)
|
||||||
}
|
}
|
||||||
Click-At 2382 568 -AfterMs 2000 # load screen: "Laden"
|
Click-At 2382 568 -AfterMs 2000 # load screen: "Laden"
|
||||||
Click-At 1551 826 -AfterMs ($LoadSeconds * 1000) # confirm: "Ok"
|
Click-At 1551 826 -AfterMs 0 # confirm: "Ok"
|
||||||
|
Wait-Log "cEngine End and exit constructor" -TimeoutSeconds $LoadTimeoutSeconds -AfterMs 1500 -Game $game
|
||||||
if ($InGame) { & $InGame }
|
if ($InGame) { & $InGame }
|
||||||
Save-Screen "screenshots\${Name}_ingame.png" | Out-Null
|
Save-Screen "screenshots\${Name}_ingame.png" | Out-Null
|
||||||
Press-Key 0x1B -AfterMs 2500 # ESC menu
|
Press-Key 0x1B -AfterMs 2500 # ESC menu
|
||||||
|
|||||||
+27
-5
@@ -31,16 +31,38 @@ function Focus-Sacred($Game) {
|
|||||||
[SacredRe.Input]::SetForegroundWindow($Game.MainWindowHandle)
|
[SacredRe.Input]::SetForegroundWindow($Game.MainWindowHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Start-Sacred([string]$Arguments = "log", [int]$WaitSeconds = 18) {
|
$script:LogPath = Join-Path $script:Root "build\x86-debug\sacred_re.log"
|
||||||
|
|
||||||
|
# Waits until sacred_re.log (written anew at every start) has at least $Count
|
||||||
|
# lines containing $Pattern, focusing the game meanwhile; then waits $AfterMs.
|
||||||
|
function Wait-Log([string]$Pattern, [int]$Count = 1, [int]$TimeoutSeconds = 60, [int]$AfterMs = 500, $Game = $null) {
|
||||||
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||||
|
while ((Get-Date) -lt $deadline) {
|
||||||
|
if ($Game) { Focus-Sacred $Game | Out-Null }
|
||||||
|
$found = 0
|
||||||
|
if (Test-Path $script:LogPath) {
|
||||||
|
$found = @(Select-String -Path $script:LogPath -Pattern $Pattern -SimpleMatch).Count
|
||||||
|
}
|
||||||
|
if ($found -ge $Count) { Start-Sleep -Milliseconds $AfterMs; return }
|
||||||
|
Start-Sleep -Milliseconds 200
|
||||||
|
}
|
||||||
|
throw "timeout waiting for '$Pattern' in sacred_re.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Starts the game and returns once its main menu is up (the UI manager
|
||||||
|
# starts its menu thread a second time when the menu appears).
|
||||||
|
function Start-Sacred([string]$Arguments = "log") {
|
||||||
if (Get-Process sacred -ErrorAction SilentlyContinue) { throw "sacred.exe is already running" }
|
if (Get-Process sacred -ErrorAction SilentlyContinue) { throw "sacred.exe is already running" }
|
||||||
|
if (Test-Path $script:LogPath) { Remove-Item $script:LogPath -ErrorAction SilentlyContinue }
|
||||||
$null = Start-Process (Join-Path $script:Root "build\x86-debug\sacred_re.exe") -ArgumentList $Arguments
|
$null = Start-Process (Join-Path $script:Root "build\x86-debug\sacred_re.exe") -ArgumentList $Arguments
|
||||||
$game = $null
|
$game = $null
|
||||||
for ($i = 0; $i -lt $WaitSeconds * 4; $i++) {
|
for ($i = 0; $i -lt 80 -and -not $game; $i++) {
|
||||||
Start-Sleep -Milliseconds 250
|
Start-Sleep -Milliseconds 250
|
||||||
if (-not $game) { $game = Get-Process sacred -ErrorAction SilentlyContinue | Select-Object -First 1 }
|
$game = Get-Process sacred -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
if ($game) { Focus-Sacred $game | Out-Null }
|
|
||||||
}
|
}
|
||||||
if (-not $game -or $game.HasExited) { throw "sacred.exe did not start (or crashed)" }
|
if (-not $game) { throw "sacred.exe did not start" }
|
||||||
|
Wait-Log "starting menu thread!" -Count 2 -AfterMs 1500 -Game $game
|
||||||
|
if ($game.HasExited) { throw "sacred.exe crashed" }
|
||||||
$game
|
$game
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user