sc: bone eater preliminary support (#895)
## Link to GitHub Issue or related Pull Request, if one exists Fixes #114 ## Description of change Initial support for Silent Scope: Bone Eater, enough to be playable; doesn't include QoS work needed to make this a smoother experience. * Game detection * Fix DX11 overlay to work with non-Unity games * Implements sub monitor i/o (only really needed to get past I/O check) * Try to make the game reasonably playable with a mouse * Ignore the game's attempt to move windows, position them in a reasonable place * Fix language hook ## Testing
This commit is contained in:
@@ -531,6 +531,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
games/otoca/p4io.cpp
|
games/otoca/p4io.cpp
|
||||||
games/silentscope/silentscope.cpp
|
games/silentscope/silentscope.cpp
|
||||||
games/silentscope/io.cpp
|
games/silentscope/io.cpp
|
||||||
|
games/silentscope/projector.cpp
|
||||||
games/pcm/pcm.cpp
|
games/pcm/pcm.cpp
|
||||||
games/pcm/io.cpp
|
games/pcm/io.cpp
|
||||||
games/onpara/onpara.cpp
|
games/onpara/onpara.cpp
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "bmpu.h"
|
#include "bmpu.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "acio/icca/icca.h"
|
#include "acio/icca/icca.h"
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
#include "cfg/api.h"
|
#include "cfg/api.h"
|
||||||
@@ -9,6 +11,7 @@
|
|||||||
#include "games/ftt/io.h"
|
#include "games/ftt/io.h"
|
||||||
#include "games/museca/io.h"
|
#include "games/museca/io.h"
|
||||||
#include "games/silentscope/io.h"
|
#include "games/silentscope/io.h"
|
||||||
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "launcher/launcher.h"
|
#include "launcher/launcher.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
|
|
||||||
@@ -530,15 +533,38 @@ static bool __cdecl ac_io_bmpu_update_control_status_buffer() {
|
|||||||
STATUS_BUFFER[4] |= 0x20;
|
STATUS_BUFFER[4] |= 0x20;
|
||||||
}
|
}
|
||||||
|
|
||||||
// joy stick raw input
|
// gun position, sent big endian; the game keeps the top 12 bits of each pair
|
||||||
auto &analogs = games::silentscope::get_analogs();
|
auto &analogs = games::silentscope::get_analogs();
|
||||||
|
auto &gun_x = analogs.at(games::silentscope::Analogs::GUN_X);
|
||||||
|
auto &gun_y = analogs.at(games::silentscope::Analogs::GUN_Y);
|
||||||
unsigned short joy_x = 0x7FFF;
|
unsigned short joy_x = 0x7FFF;
|
||||||
unsigned short joy_y = 0x7FFF;
|
unsigned short joy_y = 0x7FFF;
|
||||||
if (analogs.at(games::silentscope::Analogs::GUN_X).isSet()) {
|
|
||||||
joy_x = (unsigned short) (Analogs::getState(RI_MGR, analogs.at(games::silentscope::Analogs::GUN_X)) * USHRT_MAX);
|
if (gun_x.isSet() || gun_y.isSet()) {
|
||||||
|
if (gun_x.isSet()) {
|
||||||
|
joy_x = (unsigned short) (Analogs::getState(RI_MGR, gun_x) * USHRT_MAX);
|
||||||
|
}
|
||||||
|
if (gun_y.isSet()) {
|
||||||
|
joy_y = (unsigned short) (Analogs::getState(RI_MGR, gun_y) * USHRT_MAX);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// the gun reports where it is aimed, which a relative pointer cannot express - the
|
||||||
|
// calibration screen asks for the screen centre and two corners, so read the cursor
|
||||||
|
POINT cursor {};
|
||||||
|
RECT client {};
|
||||||
|
POINT origin {};
|
||||||
|
if (NDD_MAIN_WINDOW != nullptr &&
|
||||||
|
GetCursorPos(&cursor) &&
|
||||||
|
GetClientRect(NDD_MAIN_WINDOW, &client) &&
|
||||||
|
ClientToScreen(NDD_MAIN_WINDOW, &origin) &&
|
||||||
|
client.right > 1 && client.bottom > 1)
|
||||||
|
{
|
||||||
|
const LONG x = std::clamp(cursor.x - origin.x, 0L, client.right - 1);
|
||||||
|
const LONG y = std::clamp(cursor.y - origin.y, 0L, client.bottom - 1);
|
||||||
|
joy_x = (unsigned short) (x * USHRT_MAX / (client.right - 1));
|
||||||
|
joy_y = (unsigned short) (y * USHRT_MAX / (client.bottom - 1));
|
||||||
}
|
}
|
||||||
if (analogs.at(games::silentscope::Analogs::GUN_Y).isSet()) {
|
|
||||||
joy_y = (unsigned short) (Analogs::getState(RI_MGR, analogs.at(games::silentscope::Analogs::GUN_Y)) * USHRT_MAX);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// invert X axis
|
// invert X axis
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
#include "projector.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cwchar>
|
||||||
|
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// request/response layout: identifier, command, two zero bytes, payload length,
|
||||||
|
// the payload itself and a checksum over everything before it
|
||||||
|
constexpr size_t HEADER_SIZE = 5;
|
||||||
|
constexpr uint8_t RESPONSE_ID = 0x23;
|
||||||
|
|
||||||
|
constexpr uint8_t COMMAND_COMMON_DATA = 0x8A;
|
||||||
|
constexpr uint8_t COMMAND_TEMPERATURE = 0x99;
|
||||||
|
constexpr uint8_t COMMAND_LAMP_CURRENT = 0x9B;
|
||||||
|
|
||||||
|
// shown in the test menu as PROJTIM, PROJHEATIN and PROJHEAT
|
||||||
|
constexpr uint32_t LAMP_SECONDS = 0;
|
||||||
|
constexpr uint32_t TEMPERATURE_INTAKE = 25;
|
||||||
|
constexpr uint32_t TEMPERATURE_EXHAUST = 35;
|
||||||
|
|
||||||
|
// anything below 1000 is treated as a lamp anomaly and raises PROJERROR unless the exact
|
||||||
|
// value was already recorded in /projecter/current on an earlier boot
|
||||||
|
constexpr uint32_t LAMP_CURRENT = 1000;
|
||||||
|
|
||||||
|
void put32(uint8_t *dest, uint32_t value) {
|
||||||
|
dest[0] = (uint8_t) (value & 0xFF);
|
||||||
|
dest[1] = (uint8_t) ((value >> 8) & 0xFF);
|
||||||
|
dest[2] = (uint8_t) ((value >> 16) & 0xFF);
|
||||||
|
dest[3] = (uint8_t) ((value >> 24) & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool games::silentscope::ProjectorHandle::open(LPCWSTR lpFileName) {
|
||||||
|
if (wcscmp(lpFileName, L"COM2") != 0 && wcscmp(lpFileName, L"\\\\.\\COM2") != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("silentscope", "Opened COM2 (projector)");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int games::silentscope::ProjectorHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex);
|
||||||
|
|
||||||
|
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
|
||||||
|
|
||||||
|
DWORD bytes_read = 0;
|
||||||
|
while (bytes_read < nNumberOfBytesToRead && !this->response.empty()) {
|
||||||
|
buffer[bytes_read++] = this->response.front();
|
||||||
|
this->response.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
int games::silentscope::ProjectorHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex);
|
||||||
|
|
||||||
|
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
|
||||||
|
this->request.insert(this->request.end(), buffer, buffer + nNumberOfBytesToWrite);
|
||||||
|
|
||||||
|
while (this->request.size() >= HEADER_SIZE) {
|
||||||
|
const size_t packet_size = HEADER_SIZE + this->request[4] + 1;
|
||||||
|
if (this->request.size() < packet_size) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->process_request(this->request.data());
|
||||||
|
this->request.erase(this->request.begin(), this->request.begin() + packet_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) nNumberOfBytesToWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t games::silentscope::ProjectorHandle::bytes_available() {
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex);
|
||||||
|
|
||||||
|
return this->response.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool games::silentscope::ProjectorHandle::close() {
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex);
|
||||||
|
|
||||||
|
this->request.clear();
|
||||||
|
this->response.clear();
|
||||||
|
|
||||||
|
log_info("silentscope", "Closed COM2 (projector)");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void games::silentscope::ProjectorHandle::process_request(const uint8_t *packet) {
|
||||||
|
const uint8_t command = packet[1];
|
||||||
|
const uint8_t length = packet[4];
|
||||||
|
const uint8_t *data = packet + HEADER_SIZE;
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case COMMAND_COMMON_DATA: {
|
||||||
|
|
||||||
|
// the game only looks at the lamp usage time near the end of the block
|
||||||
|
std::vector<uint8_t> payload(98, 0);
|
||||||
|
put32(&payload[94], LAMP_SECONDS);
|
||||||
|
this->reply(command, payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case COMMAND_TEMPERATURE: {
|
||||||
|
|
||||||
|
// the requested sensor is echoed back along with its reading
|
||||||
|
const uint8_t sensor = length > 0 ? data[0] : 0;
|
||||||
|
std::vector<uint8_t> payload(5, 0);
|
||||||
|
payload[0] = sensor;
|
||||||
|
put32(&payload[1], sensor == 0 ? TEMPERATURE_INTAKE : TEMPERATURE_EXHAUST);
|
||||||
|
this->reply(command, payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case COMMAND_LAMP_CURRENT: {
|
||||||
|
|
||||||
|
// the three byte item selector is echoed back along with the measurement
|
||||||
|
std::vector<uint8_t> payload(7, 0);
|
||||||
|
memcpy(payload.data(), data, std::min<size_t>(length, 3));
|
||||||
|
put32(&payload[3], LAMP_CURRENT);
|
||||||
|
this->reply(command, payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log_misc("silentscope", "unknown projector command {:#04x}", command);
|
||||||
|
this->reply(command, {});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void games::silentscope::ProjectorHandle::reply(uint8_t command, const std::vector<uint8_t> &data) {
|
||||||
|
std::vector<uint8_t> packet {
|
||||||
|
RESPONSE_ID, command, 0x00, 0x00, (uint8_t) data.size()
|
||||||
|
};
|
||||||
|
packet.insert(packet.end(), data.begin(), data.end());
|
||||||
|
|
||||||
|
uint8_t checksum = 0;
|
||||||
|
for (auto byte : packet) {
|
||||||
|
checksum += byte;
|
||||||
|
}
|
||||||
|
packet.push_back(checksum);
|
||||||
|
|
||||||
|
this->response.insert(this->response.end(), packet.begin(), packet.end());
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "hooks/devicehook.h"
|
||||||
|
|
||||||
|
namespace games::silentscope {
|
||||||
|
|
||||||
|
// The cabinet talks to its projector over COM2. Without an answer the game stops at
|
||||||
|
// I/O error 5-1560-0004 (IOCOM2, "the projector is not connected correctly").
|
||||||
|
class ProjectorHandle : public CustomHandle {
|
||||||
|
public:
|
||||||
|
bool open(LPCWSTR lpFileName) override;
|
||||||
|
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
|
||||||
|
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
|
||||||
|
size_t bytes_available() override;
|
||||||
|
bool close() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex mutex;
|
||||||
|
std::vector<uint8_t> request;
|
||||||
|
std::deque<uint8_t> response;
|
||||||
|
|
||||||
|
void process_request(const uint8_t *packet);
|
||||||
|
void reply(uint8_t command, const std::vector<uint8_t> &data);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
#include "silentscope.h"
|
#include "silentscope.h"
|
||||||
|
|
||||||
#include "acioemu/handle.h"
|
|
||||||
#include "cfg/configurator.h"
|
#include "cfg/configurator.h"
|
||||||
#include "hooks/devicehook.h"
|
#include "hooks/devicehook.h"
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
|
|
||||||
|
#include "projector.h"
|
||||||
|
|
||||||
namespace games::silentscope {
|
namespace games::silentscope {
|
||||||
|
|
||||||
SilentScopeGame::SilentScopeGame() : Game("Silent Scope") {
|
SilentScopeGame::SilentScopeGame() : Game("Silent Scope") {
|
||||||
@@ -16,9 +17,8 @@ namespace games::silentscope {
|
|||||||
// load the game DLL so hooks apply
|
// load the game DLL so hooks apply
|
||||||
libutils::try_library("gamendd.dll");
|
libutils::try_library("gamendd.dll");
|
||||||
|
|
||||||
// TODO(felix): implement stuff on this port
|
|
||||||
devicehook_init();
|
devicehook_init();
|
||||||
devicehook_add(new acioemu::ACIOHandle(L"COM2"));
|
devicehook_add(new ProjectorHandle());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SilentScopeGame::detach() {
|
void SilentScopeGame::detach() {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ void graphics_d3d11_shutdown() {}
|
|||||||
#include <dxgi.h>
|
#include <dxgi.h>
|
||||||
#include <dxgi1_2.h>
|
#include <dxgi1_2.h>
|
||||||
|
|
||||||
|
#include "avs/game.h"
|
||||||
#include "d3d11_internal.h"
|
#include "d3d11_internal.h"
|
||||||
#include "util/nt_loader.h"
|
#include "util/nt_loader.h"
|
||||||
|
|
||||||
@@ -237,10 +238,11 @@ bool d3dcompiler_available() {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void graphics_d3d11_init() {
|
void graphics_d3d11_init() {
|
||||||
// dx11 titles always run under execexe. skipping on pure-dx9 games keeps
|
// dx11 titles run under execexe, except Silent Scope: Bone Eater, whose Aska
|
||||||
// their startup path completely untouched (no exports patched, no poll
|
// engine LoadLibrary's d3d11 itself. skipping on pure-dx9 games keeps their
|
||||||
// thread, no LDR callback).
|
// startup path completely untouched (no exports patched, no poll thread, no
|
||||||
if (!GetModuleHandleW(L"execexe.dll")) {
|
// LDR callback).
|
||||||
|
if (!GetModuleHandleW(L"execexe.dll") && !avs::game::is_model("NDD")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ static HWND GFDM_RIGHT_WINDOW = nullptr;
|
|||||||
static HMONITOR GFDM_TWO_HEAD_SMALL_MONITOR = nullptr;
|
static HMONITOR GFDM_TWO_HEAD_SMALL_MONITOR = nullptr;
|
||||||
static HWND GFDM_TWO_HEAD_SMALL_WINDOW = nullptr;
|
static HWND GFDM_TWO_HEAD_SMALL_WINDOW = nullptr;
|
||||||
HWND POPN_SUBSCREEN_WINDOW = nullptr;
|
HWND POPN_SUBSCREEN_WINDOW = nullptr;
|
||||||
|
static HWND NDD_SUBSCREEN_WINDOW = nullptr;
|
||||||
|
HWND NDD_MAIN_WINDOW = nullptr;
|
||||||
bool FAKE_SUBSCREEN_ADAPTER = false;
|
bool FAKE_SUBSCREEN_ADAPTER = false;
|
||||||
|
|
||||||
// icon
|
// icon
|
||||||
@@ -634,6 +636,9 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
bool is_sdvx_sub_window = is_sdvx && window_name.ends_with(" Sub Screen");
|
bool is_sdvx_sub_window = is_sdvx && window_name.ends_with(" Sub Screen");
|
||||||
bool is_sdvx_main_window = is_sdvx && window_name.ends_with(" Main Screen");
|
bool is_sdvx_main_window = is_sdvx && window_name.ends_with(" Main Screen");
|
||||||
bool is_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
|
bool is_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
|
||||||
|
const bool is_ndd = avs::game::is_model("NDD");
|
||||||
|
bool is_ndd_sub_window = is_ndd && window_name.starts_with("Aska MultiDisplay");
|
||||||
|
bool is_ndd_main_window = is_ndd && window_name == "ASKA";
|
||||||
const std::string gfdm_window_name = games::gitadora::is_arena_model()
|
const std::string gfdm_window_name = games::gitadora::is_arena_model()
|
||||||
? gitadora_canonical_window_name(effective_window_name)
|
? gitadora_canonical_window_name(effective_window_name)
|
||||||
: "";
|
: "";
|
||||||
@@ -777,6 +782,14 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_ndd_sub_window) {
|
||||||
|
NDD_SUBSCREEN_WINDOW = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_ndd_main_window) {
|
||||||
|
NDD_MAIN_WINDOW = result;
|
||||||
|
}
|
||||||
|
|
||||||
disable_touch_gestures(result);
|
disable_touch_gestures(result);
|
||||||
log_misc(
|
log_misc(
|
||||||
"graphics",
|
"graphics",
|
||||||
@@ -902,6 +915,26 @@ static BOOL WINAPI EnumDisplayDevicesA_hook(LPCTSTR lpDevice, DWORD iDevNum,
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the sub screen renders into a fixed 800x480 buffer, but the game's saved layout asks for rects
|
||||||
|
// that do not match it, and dxgi stretches the buffer to fill whatever the client area ends up as
|
||||||
|
static void ndd_subscreen_size(HWND hWnd, int &width, int &height) {
|
||||||
|
RECT rect {};
|
||||||
|
SetRect(&rect, 0, 0, 800, 480);
|
||||||
|
AdjustWindowRect(&rect, GetWindowLongA(hWnd, GWL_STYLE), 0);
|
||||||
|
|
||||||
|
width = rect.right - rect.left;
|
||||||
|
height = rect.bottom - rect.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the saved layout drops the sub window wherever it sat on the machine that wrote the file
|
||||||
|
static void ndd_subscreen_position(int &x, int &y) {
|
||||||
|
RECT main {};
|
||||||
|
if (NDD_MAIN_WINDOW != nullptr && GetWindowRect(NDD_MAIN_WINDOW, &main)) {
|
||||||
|
x = main.right;
|
||||||
|
y = main.top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint) {
|
static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint) {
|
||||||
log_misc("graphics", "MoveWindow hook hit ({}, {}, {}, {}, {}, {})",
|
log_misc("graphics", "MoveWindow hook hit ({}, {}, {}, {}, {}, {})",
|
||||||
fmt::ptr(hWnd),
|
fmt::ptr(hWnd),
|
||||||
@@ -931,6 +964,11 @@ static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHei
|
|||||||
nHeight = rect.bottom - rect.top;
|
nHeight = rect.bottom - rect.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GRAPHICS_WINDOWED && NDD_SUBSCREEN_WINDOW && hWnd == NDD_SUBSCREEN_WINDOW) {
|
||||||
|
ndd_subscreen_size(hWnd, nWidth, nHeight);
|
||||||
|
ndd_subscreen_position(X, Y);
|
||||||
|
}
|
||||||
|
|
||||||
// iidx windowed TDJ mode
|
// iidx windowed TDJ mode
|
||||||
if (GRAPHICS_WINDOWED && TDJ_SUBSCREEN_WINDOW && hWnd == TDJ_SUBSCREEN_WINDOW) {
|
if (GRAPHICS_WINDOWED && TDJ_SUBSCREEN_WINDOW && hWnd == TDJ_SUBSCREEN_WINDOW) {
|
||||||
if (GRAPHICS_IIDX_WSUB) {
|
if (GRAPHICS_IIDX_WSUB) {
|
||||||
@@ -1073,6 +1111,15 @@ static LONG WINAPI SetWindowLongW_hook(HWND hWnd, int nIndex, LONG dwNewLong) {
|
|||||||
static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
|
static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
|
||||||
int X, int Y, int cx, int cy, UINT uFlags) {
|
int X, int Y, int cx, int cy, UINT uFlags) {
|
||||||
|
|
||||||
|
if (GRAPHICS_WINDOWED && NDD_SUBSCREEN_WINDOW && hWnd == NDD_SUBSCREEN_WINDOW) {
|
||||||
|
if (!(uFlags & SWP_NOSIZE)) {
|
||||||
|
ndd_subscreen_size(hWnd, cx, cy);
|
||||||
|
}
|
||||||
|
if (!(uFlags & SWP_NOMOVE)) {
|
||||||
|
ndd_subscreen_position(X, Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (is_gfdm_two_head_small_window(hWnd) &&
|
if (is_gfdm_two_head_small_window(hWnd) &&
|
||||||
((uFlags & SWP_HIDEWINDOW) ||
|
((uFlags & SWP_HIDEWINDOW) ||
|
||||||
(uFlags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE))) {
|
(uFlags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE))) {
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ extern HWND TDJ_SUBSCREEN_WINDOW;
|
|||||||
extern HWND SDVX_SUBSCREEN_WINDOW;
|
extern HWND SDVX_SUBSCREEN_WINDOW;
|
||||||
extern HWND POPN_SUBSCREEN_WINDOW;
|
extern HWND POPN_SUBSCREEN_WINDOW;
|
||||||
extern HWND GFDM_SUBSCREEN_WINDOW;
|
extern HWND GFDM_SUBSCREEN_WINDOW;
|
||||||
|
extern HWND NDD_MAIN_WINDOW;
|
||||||
|
|
||||||
extern bool SUBSCREEN_FORCE_REDRAW;
|
extern bool SUBSCREEN_FORCE_REDRAW;
|
||||||
extern bool FAKE_SUBSCREEN_ADAPTER;
|
extern bool FAKE_SUBSCREEN_ADAPTER;
|
||||||
|
|||||||
@@ -336,9 +336,11 @@ void hooks::lang::early_init() {
|
|||||||
&GetLocaleInfoA_orig);
|
&GetLocaleInfoA_orig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for TDJ subscreen search keyboard and T44 narrow-string handling
|
// for TDJ subscreen search keyboard
|
||||||
|
// T44 narrow-string handling
|
||||||
|
// NDD text measuring
|
||||||
if ((avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) ||
|
if ((avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) ||
|
||||||
avs::game::is_model("T44")) {
|
avs::game::is_model({ "T44", "NDD" })) {
|
||||||
log_info("hooks::lang", "hooking IsDBCSLeadByte");
|
log_info("hooks::lang", "hooking IsDBCSLeadByte");
|
||||||
detour::trampoline_try(
|
detour::trampoline_try(
|
||||||
"kernel32.dll",
|
"kernel32.dll",
|
||||||
@@ -358,8 +360,9 @@ void hooks::lang::early_init() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef SPICE64
|
#ifdef SPICE64
|
||||||
|
// NDD renders through GetTextExtentPoint32A, so its wide strings go back through CP_ACP first
|
||||||
const auto hook_wide_char_to_multi_byte =
|
const auto hook_wide_char_to_multi_byte =
|
||||||
games::gitadora::is_arena_model() || avs::game::is_model("T44");
|
games::gitadora::is_arena_model() || avs::game::is_model({ "T44", "NDD" });
|
||||||
#else
|
#else
|
||||||
// XG2 converts UTF-8 property strings through CP_ACP before rendering.
|
// XG2 converts UTF-8 property strings through CP_ACP before rendering.
|
||||||
const auto hook_wide_char_to_multi_byte = avs::game::is_model({ "K32", "K33" });
|
const auto hook_wide_char_to_multi_byte = avs::game::is_model({ "K32", "K33" });
|
||||||
|
|||||||
@@ -730,6 +730,8 @@ void eamuse_autodetect_game() {
|
|||||||
eamuse_set_game("Mahjong Fight Girl");
|
eamuse_set_game("Mahjong Fight Girl");
|
||||||
else if (avs::game::is_model("XIF"))
|
else if (avs::game::is_model("XIF"))
|
||||||
eamuse_set_game("Polaris Chord");
|
eamuse_set_game("Polaris Chord");
|
||||||
|
else if (avs::game::is_model("NDD"))
|
||||||
|
eamuse_set_game("Silent Scope: Bone Eater");
|
||||||
else {
|
else {
|
||||||
log_warning("eamuse", "unknown game model: {}", avs::game::MODEL);
|
log_warning("eamuse", "unknown game model: {}", avs::game::MODEL);
|
||||||
eamuse_set_game("unknown");
|
eamuse_set_game("unknown");
|
||||||
|
|||||||
Reference in New Issue
Block a user