mirror of
https://forge.banditvault.co.uk/racoon/MinecraftConsoles.git
synced 2026-05-21 21:45:31 +00:00
- D-pad Left -> F3 debug overlay, D-pad Right -> chat (rising-edge XInput poll on the CoreWindow thread) - Mirror ChatScreen open state into g_xbox_chatOpen so the Xbox soft keyboard (IInputPane2) actually shows/hides - Add XboxOnKeyUp export and pair every CoreWindow KeyDown/KeyUp + D-pad press/release so g_KBMInput's released-edge handlers fire (fixes F3 toggle and chat re-opening) - Bump version to 1.2.1.0
403 lines
19 KiB
C++
403 lines
19 KiB
C++
#include <windows.h>
|
|
#include <wchar.h>
|
|
#include <stdio.h>
|
|
#include <roapi.h>
|
|
#include <wrl.h>
|
|
#include <wrl/wrappers/corewrappers.h>
|
|
#include <windows.applicationmodel.core.h>
|
|
#include <windows.ui.core.h>
|
|
#include <windows.ui.viewmanagement.h>
|
|
#include <windows.foundation.h>
|
|
#include <windows.system.h>
|
|
#include <dxgi1_2.h>
|
|
#include <d3d11.h>
|
|
#include <Xinput.h>
|
|
#include "SharedSurface.h"
|
|
|
|
#pragma comment(lib, "xinput9_1_0.lib")
|
|
|
|
using namespace Microsoft::WRL;
|
|
using namespace Microsoft::WRL::Wrappers;
|
|
using namespace ABI::Windows::ApplicationModel::Core;
|
|
using namespace ABI::Windows::UI::Core;
|
|
using namespace ABI::Windows::Foundation;
|
|
|
|
static HANDLE g_hMap = nullptr;
|
|
static LCE_SharedData* g_pShared = nullptr;
|
|
|
|
// D3D objects created on the CoreWindow thread, passed to the game DLL
|
|
static ComPtr<ID3D11Device> g_device;
|
|
static ComPtr<ID3D11DeviceContext> g_ctx;
|
|
static ComPtr<IDXGISwapChain1> g_swapChain;
|
|
static D3D_FEATURE_LEVEL g_fl = D3D_FEATURE_LEVEL_11_0;
|
|
|
|
static void WriteLog(const wchar_t* dir, const wchar_t* msg) {
|
|
wchar_t path[MAX_PATH]; swprintf_s(path, L"%s\\lce_launch.log", dir);
|
|
FILE* f = nullptr; if (_wfopen_s(&f, path, L"a") == 0 && f) { fwprintf(f, L"%s\n", msg); fclose(f); }
|
|
}
|
|
|
|
static bool OpenSharedMem() {
|
|
g_hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
|
|
sizeof(LCE_SharedData), LCE_SHARED_MEMORY_NAME);
|
|
if (!g_hMap) return false;
|
|
g_pShared = (LCE_SharedData*)MapViewOfFile(g_hMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LCE_SharedData));
|
|
if (!g_pShared) { CloseHandle(g_hMap); g_hMap = nullptr; return false; }
|
|
memset(g_pShared, 0, sizeof(LCE_SharedData));
|
|
return true;
|
|
}
|
|
|
|
typedef int (WINAPI* GameMainFn)(ID3D11Device*, ID3D11DeviceContext*, IDXGISwapChain*, D3D_FEATURE_LEVEL);
|
|
typedef void (*PfnXboxOnChar)(wchar_t);
|
|
typedef void (*PfnXboxOnKeyDown)(int);
|
|
typedef void (*PfnXboxOnKeyUp)(int);
|
|
typedef int (*PfnXboxIsChatOpen)();
|
|
|
|
// Set by GameThread after DLL is loaded; read from CoreWindow thread (lambdas check for null).
|
|
static PfnXboxOnChar g_pfnXboxOnChar = nullptr;
|
|
static PfnXboxOnKeyDown g_pfnXboxOnKeyDown = nullptr;
|
|
static PfnXboxOnKeyUp g_pfnXboxOnKeyUp = nullptr;
|
|
static PfnXboxIsChatOpen g_pfnXboxIsChatOpen = nullptr;
|
|
|
|
// Game thread entry — loads MinecraftClient.dll and calls GameMain
|
|
struct GameThreadArgs { wchar_t dllPath[MAX_PATH]; wchar_t logDir[MAX_PATH]; };
|
|
|
|
static DWORD WINAPI GameThread(LPVOID param) {
|
|
GameThreadArgs* args = (GameThreadArgs*)param;
|
|
wchar_t logDir[MAX_PATH]; wcscpy_s(logDir, args->logDir);
|
|
wchar_t dllPath[MAX_PATH]; wcscpy_s(dllPath, args->dllPath);
|
|
delete args;
|
|
|
|
WriteLog(logDir, L"GameThread: loading MinecraftClient.dll...");
|
|
HMODULE hGame = LoadLibraryW(dllPath);
|
|
if (!hGame) {
|
|
wchar_t msg[64]; swprintf_s(msg, L"LoadLibrary FAILED err=0x%X", GetLastError());
|
|
WriteLog(logDir, msg); return 1;
|
|
}
|
|
WriteLog(logDir, L"GameThread: DLL loaded, finding GameMain...");
|
|
|
|
GameMainFn gameMain = (GameMainFn)GetProcAddress(hGame, "GameMain");
|
|
if (!gameMain) {
|
|
WriteLog(logDir, L"GameMain export not found");
|
|
FreeLibrary(hGame); return 1;
|
|
}
|
|
// Keyboard bridge — used by CoreWindow event handlers to feed into g_KBMInput
|
|
g_pfnXboxOnChar = (PfnXboxOnChar) GetProcAddress(hGame, "XboxOnChar");
|
|
g_pfnXboxOnKeyDown = (PfnXboxOnKeyDown) GetProcAddress(hGame, "XboxOnKeyDown");
|
|
g_pfnXboxOnKeyUp = (PfnXboxOnKeyUp) GetProcAddress(hGame, "XboxOnKeyUp");
|
|
g_pfnXboxIsChatOpen = (PfnXboxIsChatOpen)GetProcAddress(hGame, "XboxIsChatOpen");
|
|
WriteLog(logDir, L"GameThread: calling GameMain...");
|
|
gameMain(g_device.Get(), g_ctx.Get(), g_swapChain.Get(), g_fl);
|
|
WriteLog(logDir, L"GameThread: GameMain returned");
|
|
FreeLibrary(hGame);
|
|
return 0;
|
|
}
|
|
|
|
class App : public RuntimeClass<RuntimeClassFlags<WinRtClassicComMix>, IFrameworkView>
|
|
{
|
|
public:
|
|
HRESULT STDMETHODCALLTYPE Initialize(ICoreApplicationView* view) override
|
|
{
|
|
m_view = view;
|
|
return S_OK;
|
|
}
|
|
HRESULT STDMETHODCALLTYPE SetWindow(ICoreWindow* window) override {
|
|
m_window = window;
|
|
|
|
// On Xbox, pressing B fires BackRequested on the SystemNavigationManager
|
|
// for the CURRENT VIEW. Must use GetForCurrentView() (a static factory
|
|
// method on the class, not GetActivationFactory on the interface) — this
|
|
// is the documented fix per Microsoft's UWP-on-Xbox guidance.
|
|
// Without this, the UWP shell suspends the app on every B press regardless
|
|
// of what the game thread does.
|
|
ComPtr<ABI::Windows::UI::Core::ISystemNavigationManagerStatics> navStatics;
|
|
if (SUCCEEDED(GetActivationFactory(
|
|
HStringReference(RuntimeClass_Windows_UI_Core_SystemNavigationManager).Get(),
|
|
&navStatics)))
|
|
{
|
|
ComPtr<ABI::Windows::UI::Core::ISystemNavigationManager> navManager;
|
|
if (SUCCEEDED(navStatics->GetForCurrentView(&navManager)))
|
|
{
|
|
EventRegistrationToken token;
|
|
navManager->add_BackRequested(
|
|
Callback<ABI::Windows::Foundation::IEventHandler<
|
|
ABI::Windows::UI::Core::BackRequestedEventArgs*>>(
|
|
[](IInspectable*,
|
|
ABI::Windows::UI::Core::IBackRequestedEventArgs* args) -> HRESULT {
|
|
args->put_Handled(TRUE);
|
|
return S_OK;
|
|
}).Get(),
|
|
&token);
|
|
}
|
|
}
|
|
|
|
// Route CoreWindow keyboard events into the game's g_KBMInput so
|
|
// Screen::tick()'s ConsumeChar drain feeds ChatScreen::keyPressed.
|
|
// CharacterReceived fires for every printable character plus backspace (0x08)
|
|
// and enter (0x0D) from the InputPane. KeyDown handles Escape separately
|
|
// because it never produces a character event.
|
|
{
|
|
EventRegistrationToken charToken = {}, keyToken = {};
|
|
|
|
window->add_CharacterReceived(
|
|
Callback<ABI::Windows::Foundation::ITypedEventHandler<
|
|
ABI::Windows::UI::Core::CoreWindow*,
|
|
ABI::Windows::UI::Core::CharacterReceivedEventArgs*>>(
|
|
[](ABI::Windows::UI::Core::ICoreWindow*,
|
|
ABI::Windows::UI::Core::ICharacterReceivedEventArgs* args) -> HRESULT
|
|
{
|
|
if (!g_pfnXboxOnChar) return S_OK;
|
|
UINT32 code = 0;
|
|
args->get_KeyCode(&code);
|
|
// printable + backspace (0x08) + enter (0x0D)
|
|
if (code >= 0x20 || code == 0x08 || code == 0x0D)
|
|
g_pfnXboxOnChar(static_cast<wchar_t>(code));
|
|
return S_OK;
|
|
}).Get(),
|
|
&charToken);
|
|
|
|
window->add_KeyDown(
|
|
Callback<ABI::Windows::Foundation::ITypedEventHandler<
|
|
ABI::Windows::UI::Core::CoreWindow*,
|
|
ABI::Windows::UI::Core::KeyEventArgs*>>(
|
|
[](ABI::Windows::UI::Core::ICoreWindow*,
|
|
ABI::Windows::UI::Core::IKeyEventArgs* args) -> HRESULT
|
|
{
|
|
ABI::Windows::System::VirtualKey vk =
|
|
ABI::Windows::System::VirtualKey_None;
|
|
args->get_VirtualKey(&vk);
|
|
// Escape doesn't produce a CharacterReceived event
|
|
if (vk == ABI::Windows::System::VirtualKey_Escape && g_pfnXboxOnKeyDown)
|
|
g_pfnXboxOnKeyDown(VK_ESCAPE);
|
|
return S_OK;
|
|
}).Get(),
|
|
&keyToken);
|
|
|
|
// Pair every KeyDown with a KeyUp so g_KBMInput's released-edge
|
|
// detection (used for F3 toggle and similar) actually fires.
|
|
EventRegistrationToken keyUpToken = {};
|
|
window->add_KeyUp(
|
|
Callback<ABI::Windows::Foundation::ITypedEventHandler<
|
|
ABI::Windows::UI::Core::CoreWindow*,
|
|
ABI::Windows::UI::Core::KeyEventArgs*>>(
|
|
[](ABI::Windows::UI::Core::ICoreWindow*,
|
|
ABI::Windows::UI::Core::IKeyEventArgs* args) -> HRESULT
|
|
{
|
|
if (!g_pfnXboxOnKeyUp) return S_OK;
|
|
ABI::Windows::System::VirtualKey vk =
|
|
ABI::Windows::System::VirtualKey_None;
|
|
args->get_VirtualKey(&vk);
|
|
if (vk == ABI::Windows::System::VirtualKey_Escape)
|
|
g_pfnXboxOnKeyUp(VK_ESCAPE);
|
|
return S_OK;
|
|
}).Get(),
|
|
&keyUpToken);
|
|
}
|
|
|
|
window->Activate();
|
|
return S_OK;
|
|
}
|
|
HRESULT STDMETHODCALLTYPE Load(HSTRING) override { return S_OK; }
|
|
|
|
HRESULT STDMETHODCALLTYPE Run() override
|
|
{
|
|
wchar_t selfPath[MAX_PATH], workDir[MAX_PATH];
|
|
GetModuleFileNameW(nullptr, selfPath, MAX_PATH);
|
|
wchar_t* slash = wcsrchr(selfPath, L'\\');
|
|
if (slash) { wcsncpy_s(workDir, selfPath, slash - selfPath); workDir[slash-selfPath] = L'\0'; }
|
|
|
|
{ wchar_t lp[MAX_PATH]; swprintf_s(lp, L"%s\\lce_launch.log", workDir);
|
|
FILE* f=nullptr; _wfopen_s(&f,lp,L"w"); if(f) fclose(f); }
|
|
WriteLog(workDir, L"LCE.Xbox Run() started");
|
|
SetCurrentDirectoryW(workDir);
|
|
|
|
// Create D3D device + CoreWindow swap chain on this thread
|
|
WriteLog(workDir, L"Creating D3D...");
|
|
HRESULT hr = CreateD3D();
|
|
if (FAILED(hr)) {
|
|
wchar_t msg[64]; swprintf_s(msg, L"CreateD3D FAILED 0x%08X", (unsigned)hr);
|
|
WriteLog(workDir, msg); return E_FAIL;
|
|
}
|
|
WriteLog(workDir, L"D3D ready");
|
|
|
|
// Launch game on a background thread (same process — no cross-process issues)
|
|
wchar_t dllPath[MAX_PATH];
|
|
swprintf_s(dllPath, L"%s\\MinecraftClient.dll", workDir);
|
|
|
|
GameThreadArgs* args = new GameThreadArgs;
|
|
wcscpy_s(args->dllPath, dllPath);
|
|
wcscpy_s(args->logDir, workDir);
|
|
|
|
HANDLE hThread = CreateThread(nullptr, 0, GameThread, args, 0, nullptr);
|
|
if (!hThread) { WriteLog(workDir, L"CreateThread FAILED"); return E_FAIL; }
|
|
WriteLog(workDir, L"Game thread started");
|
|
|
|
// Main loop: pump CoreWindow events + service Present requests
|
|
ComPtr<ICoreDispatcher> dispatcher;
|
|
m_window->get_Dispatcher(&dispatcher);
|
|
|
|
// InputPane: shows the Xbox soft keyboard when ChatScreen is active.
|
|
// TryShow/TryHide are on IInputPane2 — QI from the base IInputPane.
|
|
// Must be obtained on the CoreWindow thread.
|
|
ComPtr<ABI::Windows::UI::ViewManagement::IInputPane2> inputPane;
|
|
{
|
|
ComPtr<ABI::Windows::UI::ViewManagement::IInputPaneStatics> statics;
|
|
ComPtr<ABI::Windows::UI::ViewManagement::IInputPane> pane1;
|
|
if (SUCCEEDED(GetActivationFactory(
|
|
HStringReference(RuntimeClass_Windows_UI_ViewManagement_InputPane).Get(),
|
|
&statics)) && SUCCEEDED(statics->GetForCurrentView(&pane1)))
|
|
pane1.As(&inputPane); // QI to IInputPane2
|
|
}
|
|
bool lastChatOpen = false;
|
|
|
|
// D-pad Left → F3 (debug overlay), D-pad Right → T (chat key).
|
|
// Polled here on the CoreWindow thread; rising-edge only so a held
|
|
// button doesn't spam. Skipped while chat is open so the player can
|
|
// navigate the soft keyboard without re-triggering chat/F3.
|
|
WORD prevPadButtons = 0;
|
|
DWORD lastPadPacket = 0;
|
|
|
|
while (WaitForSingleObject(hThread, 0) == WAIT_TIMEOUT) {
|
|
dispatcher->ProcessEvents(CoreProcessEventsOption_ProcessAllIfPresent);
|
|
|
|
// Show/hide Xbox soft keyboard in sync with ChatScreen open state
|
|
if (inputPane && g_pfnXboxIsChatOpen) {
|
|
const bool chatOpen = g_pfnXboxIsChatOpen() != 0;
|
|
boolean bResult = FALSE;
|
|
if (chatOpen && !lastChatOpen) inputPane->TryShow(&bResult);
|
|
if (!chatOpen && lastChatOpen) inputPane->TryHide(&bResult);
|
|
lastChatOpen = chatOpen;
|
|
}
|
|
|
|
// Synthesize keyboard presses from D-pad on player 0's controller.
|
|
// Fire on the rising edge for press, falling edge for release —
|
|
// both are needed: F3's debug-info toggle is a release-edge check
|
|
// and chat-open guards against m_keyDown[T] still being held.
|
|
// Suppress while chat is open so the D-pad can drive the soft
|
|
// keyboard without bouncing F3/T.
|
|
if (g_pfnXboxOnKeyDown) {
|
|
XINPUT_STATE pad = {};
|
|
if (XInputGetState(0, &pad) == ERROR_SUCCESS) {
|
|
if (pad.dwPacketNumber != lastPadPacket) {
|
|
lastPadPacket = pad.dwPacketNumber;
|
|
const WORD btn = pad.Gamepad.wButtons;
|
|
const WORD pressed = static_cast<WORD>( btn & ~prevPadButtons);
|
|
const WORD released = static_cast<WORD>(~btn & prevPadButtons);
|
|
const bool chatOpen = g_pfnXboxIsChatOpen && g_pfnXboxIsChatOpen() != 0;
|
|
if (!chatOpen) {
|
|
if (pressed & XINPUT_GAMEPAD_DPAD_LEFT) g_pfnXboxOnKeyDown(VK_F3);
|
|
if (pressed & XINPUT_GAMEPAD_DPAD_RIGHT) g_pfnXboxOnKeyDown('T');
|
|
}
|
|
// Always send key-up on release, even if chat is open
|
|
// now — otherwise a held-then-released D-pad button
|
|
// gets stuck "down" forever in g_KBMInput.
|
|
if (g_pfnXboxOnKeyUp) {
|
|
if (released & XINPUT_GAMEPAD_DPAD_LEFT) g_pfnXboxOnKeyUp(VK_F3);
|
|
if (released & XINPUT_GAMEPAD_DPAD_RIGHT) g_pfnXboxOnKeyUp('T');
|
|
}
|
|
prevPadButtons = btn;
|
|
}
|
|
} else {
|
|
// No controller — release any synthesized keys still held
|
|
// and clear edge state so reconnecting starts fresh.
|
|
if (g_pfnXboxOnKeyUp) {
|
|
if (prevPadButtons & XINPUT_GAMEPAD_DPAD_LEFT) g_pfnXboxOnKeyUp(VK_F3);
|
|
if (prevPadButtons & XINPUT_GAMEPAD_DPAD_RIGHT) g_pfnXboxOnKeyUp('T');
|
|
}
|
|
prevPadButtons = 0;
|
|
}
|
|
}
|
|
|
|
// Service Present requests from game thread (must happen on CoreWindow thread)
|
|
if (g_pShared && g_pShared->presentState == LCE_PRESENT_REQUESTED) {
|
|
IDXGISwapChain* sc = static_cast<IDXGISwapChain*>(g_pShared->pSwapChain);
|
|
if (sc) {
|
|
// Use our vsyncEnabled flag rather than the value the 4J lib writes
|
|
// into syncInterval (which is always 1). Default (zero-init) = uncapped.
|
|
const UINT si = g_pShared->vsyncEnabled ? 1u : 0u;
|
|
sc->Present(si, g_pShared->presentFlags);
|
|
}
|
|
InterlockedExchange(&g_pShared->presentState, LCE_PRESENT_DONE);
|
|
// Present just happened — skip sleep so the game thread can
|
|
// immediately start the next frame without waiting ~1ms.
|
|
} else {
|
|
// No present pending: yield briefly to avoid burning a whole core
|
|
// on the CoreWindow thread while the game is rendering.
|
|
Sleep(1);
|
|
}
|
|
}
|
|
DWORD exitCode = 0; GetExitCodeThread(hThread, &exitCode);
|
|
wchar_t msg[64]; swprintf_s(msg, L"Game thread exited: 0x%08X", exitCode);
|
|
WriteLog(workDir, msg);
|
|
CloseHandle(hThread);
|
|
return S_OK;
|
|
}
|
|
HRESULT STDMETHODCALLTYPE Uninitialize() override { return S_OK; }
|
|
|
|
private:
|
|
ComPtr<ICoreWindow> m_window;
|
|
ComPtr<ICoreApplicationView> m_view;
|
|
|
|
HRESULT CreateD3D() {
|
|
D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1 };
|
|
HRESULT hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
|
|
D3D11_CREATE_DEVICE_BGRA_SUPPORT, // required for Iggy Flash interop
|
|
levels, ARRAYSIZE(levels), D3D11_SDK_VERSION,
|
|
g_device.GetAddressOf(), &g_fl, g_ctx.GetAddressOf());
|
|
if (FAILED(hr)) return hr;
|
|
|
|
ComPtr<IDXGIDevice> dxgiDev; g_device.As(&dxgiDev);
|
|
ComPtr<IDXGIAdapter> adapter; dxgiDev->GetAdapter(&adapter);
|
|
ComPtr<IDXGIFactory2> factory;
|
|
hr = adapter->GetParent(__uuidof(IDXGIFactory2), (void**)factory.GetAddressOf());
|
|
if (FAILED(hr)) return hr;
|
|
|
|
DXGI_SWAP_CHAIN_DESC1 sd = {};
|
|
sd.Width=1920; sd.Height=1080; sd.Format=DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
sd.SampleDesc={1,0};
|
|
sd.BufferUsage=DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
|
|
// FLIP_DISCARD + 3 buffers: Present(0,0) returns immediately without stalling
|
|
// on a vblank boundary. FLIP_SEQUENTIAL with 2 buffers would block when both
|
|
// buffers are consumed, effectively locking to the display refresh rate.
|
|
sd.BufferCount=3; sd.SwapEffect=DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
|
sd.Scaling=DXGI_SCALING_STRETCH; sd.AlphaMode=DXGI_ALPHA_MODE_IGNORE;
|
|
|
|
hr = factory->CreateSwapChainForCoreWindow(g_device.Get(),
|
|
static_cast<IUnknown*>(m_window.Get()), &sd, nullptr, g_swapChain.GetAddressOf());
|
|
{
|
|
wchar_t msg[64]; swprintf_s(msg, L"CreateSwapChainForCoreWindow hr=0x%08X", (unsigned)hr);
|
|
wchar_t dir[MAX_PATH]; GetModuleFileNameW(nullptr, dir, MAX_PATH);
|
|
wchar_t* s = wcsrchr(dir, L'\\'); if(s) *(s+1)=L'\0';
|
|
wchar_t lp[MAX_PATH]; swprintf_s(lp, L"%slce_launch.log", dir);
|
|
FILE* f=nullptr; _wfopen_s(&f,lp,L"a"); if(f){fwprintf(f,L"%s\n",msg);fclose(f);}
|
|
}
|
|
if (FAILED(hr)) return hr;
|
|
|
|
// Do an initial Present so the swap chain is fully initialized before game uses it
|
|
g_swapChain->Present(0, DXGI_PRESENT_DO_NOT_WAIT);
|
|
return S_OK;
|
|
}
|
|
};
|
|
|
|
class AppSource : public RuntimeClass<RuntimeClassFlags<WinRtClassicComMix>, IFrameworkViewSource>
|
|
{
|
|
public:
|
|
HRESULT STDMETHODCALLTYPE CreateView(IFrameworkView** view) override
|
|
{ return Make<App>().CopyTo(view); }
|
|
};
|
|
|
|
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
|
{
|
|
OpenSharedMem();
|
|
RoInitialize(RO_INIT_MULTITHREADED);
|
|
ComPtr<ICoreApplication> coreApp;
|
|
GetActivationFactory(
|
|
HStringReference(RuntimeClass_Windows_ApplicationModel_Core_CoreApplication).Get(),
|
|
&coreApp);
|
|
auto source = Make<AppSource>();
|
|
coreApp->Run(source.Get());
|
|
RoUninitialize();
|
|
if (g_pShared) UnmapViewOfFile(g_pShared);
|
|
if (g_hMap) CloseHandle(g_hMap);
|
|
return 0;
|
|
}
|