From a2187762fd8503d8eda1afa2e65c6ef29ae1082c Mon Sep 17 00:00:00 2001 From: veroxsity Date: Sun, 19 Apr 2026 01:38:48 +0100 Subject: [PATCH] Add initial project structure and audio shims for LCE Xbox port - Added Wide310x150Logo.png asset for application branding. - Created LCE.Xbox project file with configurations for Debug and Release. - Added Package.appxmanifest for application metadata and capabilities. - Introduced SharedSurface.h for shared memory management between processes. - Implemented AudioShim with XAudio2 integration for audio playback. - Developed XSBParser and XWBParser for handling sound bank files. - Created DXGIShim to intercept DXGI calls and manage swap chains. - Defined project files for AudioShim and DXGIShim with necessary dependencies. --- LCE.Xbox/App.cpp | 330 ++++++++++++++++++ LCE.Xbox/App.h | 5 + LCE.Xbox/Assets/SplashScreen.png | Bin 0 -> 1825 bytes LCE.Xbox/Assets/Square150x150Logo.png | Bin 0 -> 807 bytes LCE.Xbox/Assets/Square44x44Logo.png | Bin 0 -> 425 bytes LCE.Xbox/Assets/StoreLogo.png | Bin 0 -> 441 bytes LCE.Xbox/Assets/Wide310x150Logo.png | Bin 0 -> 916 bytes LCE.Xbox/LCE.Xbox.vcxproj | 67 ++++ LCE.Xbox/Package.appxmanifest | 49 +++ LCE.Xbox/SharedSurface.h | 32 ++ Minecraft.Client/CMakeLists.txt | 57 ++- .../Windows64/Windows64_LceLive.cpp | 6 + .../Windows64/Windows64_Minecraft.cpp | 77 +++- Shims/AudioShim/AudioShim.cpp | 205 +++++++++++ Shims/AudioShim/AudioShim.h | 21 ++ Shims/AudioShim/AudioShim.vcxproj | 58 +++ Shims/AudioShim/XSBParser.cpp | 90 +++++ Shims/AudioShim/XSBParser.h | 30 ++ Shims/AudioShim/XWBParser.cpp | 87 +++++ Shims/AudioShim/XWBParser.h | 81 +++++ Shims/DXGIShim/DXGIShim.cpp | 210 +++++++++++ Shims/DXGIShim/DXGIShim.def | 6 + Shims/DXGIShim/DXGIShim.h | 6 + Shims/DXGIShim/DXGIShim.vcxproj | 63 ++++ build-release.bat | 23 ++ include/Common/BuildVer.h | 2 +- 26 files changed, 1496 insertions(+), 9 deletions(-) create mode 100644 LCE.Xbox/App.cpp create mode 100644 LCE.Xbox/App.h create mode 100644 LCE.Xbox/Assets/SplashScreen.png create mode 100644 LCE.Xbox/Assets/Square150x150Logo.png create mode 100644 LCE.Xbox/Assets/Square44x44Logo.png create mode 100644 LCE.Xbox/Assets/StoreLogo.png create mode 100644 LCE.Xbox/Assets/Wide310x150Logo.png create mode 100644 LCE.Xbox/LCE.Xbox.vcxproj create mode 100644 LCE.Xbox/Package.appxmanifest create mode 100644 LCE.Xbox/SharedSurface.h create mode 100644 Shims/AudioShim/AudioShim.cpp create mode 100644 Shims/AudioShim/AudioShim.h create mode 100644 Shims/AudioShim/AudioShim.vcxproj create mode 100644 Shims/AudioShim/XSBParser.cpp create mode 100644 Shims/AudioShim/XSBParser.h create mode 100644 Shims/AudioShim/XWBParser.cpp create mode 100644 Shims/AudioShim/XWBParser.h create mode 100644 Shims/DXGIShim/DXGIShim.cpp create mode 100644 Shims/DXGIShim/DXGIShim.def create mode 100644 Shims/DXGIShim/DXGIShim.h create mode 100644 Shims/DXGIShim/DXGIShim.vcxproj diff --git a/LCE.Xbox/App.cpp b/LCE.Xbox/App.cpp new file mode 100644 index 00000000..fddb305f --- /dev/null +++ b/LCE.Xbox/App.cpp @@ -0,0 +1,330 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SharedSurface.h" + +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 g_device; +static ComPtr g_ctx; +static ComPtr 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 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 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_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, 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 navStatics; + if (SUCCEEDED(GetActivationFactory( + HStringReference(RuntimeClass_Windows_UI_Core_SystemNavigationManager).Get(), + &navStatics))) + { + ComPtr navManager; + if (SUCCEEDED(navStatics->GetForCurrentView(&navManager))) + { + EventRegistrationToken token; + navManager->add_BackRequested( + Callback>( + [](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::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(code)); + return S_OK; + }).Get(), + &charToken); + + window->add_KeyDown( + Callback>( + [](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); + } + + 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 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 inputPane; + { + ComPtr statics; + ComPtr 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; + + 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; + } + + // Service Present requests from game thread (must happen on CoreWindow thread) + if (g_pShared && g_pShared->presentState == LCE_PRESENT_REQUESTED) { + IDXGISwapChain* sc = static_cast(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 m_window; + ComPtr 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 dxgiDev; g_device.As(&dxgiDev); + ComPtr adapter; dxgiDev->GetAdapter(&adapter); + ComPtr 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(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, IFrameworkViewSource> +{ +public: + HRESULT STDMETHODCALLTYPE CreateView(IFrameworkView** view) override + { return Make().CopyTo(view); } +}; + +int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) +{ + OpenSharedMem(); + RoInitialize(RO_INIT_MULTITHREADED); + ComPtr coreApp; + GetActivationFactory( + HStringReference(RuntimeClass_Windows_ApplicationModel_Core_CoreApplication).Get(), + &coreApp); + auto source = Make(); + coreApp->Run(source.Get()); + RoUninitialize(); + if (g_pShared) UnmapViewOfFile(g_pShared); + if (g_hMap) CloseHandle(g_hMap); + return 0; +} diff --git a/LCE.Xbox/App.h b/LCE.Xbox/App.h new file mode 100644 index 00000000..cdd5ba98 --- /dev/null +++ b/LCE.Xbox/App.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include "SharedSurface.h" +// App.h — minimal header for the plain Win32 launcher +// No WinRT or IFrameworkView needed with EntryPoint="Windows.FullTrustApplication" diff --git a/LCE.Xbox/Assets/SplashScreen.png b/LCE.Xbox/Assets/SplashScreen.png new file mode 100644 index 0000000000000000000000000000000000000000..3478df88db125a95d0d48182b6d23700678e7b60 GIT binary patch literal 1825 zcmeAS@N?(olHy`uVBq!ia0y~yV9H@&VASDY1B%qCzQ4l2!1lt^#WAE}&f8n|tHVoW z4tzZCpC2b;Vj9@B%aT?1Z{%X#jO_fJb;kufbZr~k7A;&@!=h{3u5jzoD}|ZUS{20i zuDt%oRAQQoYv#T0`f=-D&N*!PLGu2b^W__XK!$-~)3-O@fR_C+J;=r&pnija;Sq~8 zgTtHzW`+)7pj!%?co`J@HZU?A;WT4tuozV|8U%Do1$nvK*%^MQ@B91XXXU5f_5VKn zd8K{)*{jvZ(+dB7d$aT3XPLhr{=C^+Q=FD|uKv@ZH`cprzu#q!kD1v#|L=zjX>2>< zn|LECug|wOxveihQ#e2W+@oi2f4)5GT>NwG^5;LS)6>$@3bQTM{c_)Zn!@mZ-c*eZ zpSHzW+x5@eXVE=-J5c)n^ytlHFOTVb*DI?uYG6MsZL2h+ZwQ)} z-M#b6yZqN@zph_@_MyN2nRAa{fBsv2{@lH_b8l`g&F-r!`FAhBZo5$I{XJIg{_!;{ zika{9A3b*LSkd-&28IXcBRiQ!^X_QzHRwz5@7!;~6PnL71jGPqBL+`bKbLh*2~7Z5 CqeH>~ literal 0 HcmV?d00001 diff --git a/LCE.Xbox/Assets/Square150x150Logo.png b/LCE.Xbox/Assets/Square150x150Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6696ba72a37d9af7a51affa8aa5b2e02b406fac9 GIT binary patch literal 807 zcmeAS@N?(olHy`uVBq!ia0vp^(?FPm4M^HB7CvHNV0!22;uumf=j}E7eBVNu;~(E= zt~Z;tVC^#Ahen<{|JEc;zVXJ*xZhDpXI?|21kV@22pjg!8%s<(g1lyVTn_vH(sY8- zqo|rY-f`<+&WS(gx$m&`{`>1Qx8I)IWS_I(I`fBmAu+*2N=^|j9Nip=f{HpyEv_va zIs^u<;Fc;|hy4|$?^9%VefhHd>(gT4tu=LvW^H{d74bXdarv{Xg2KhCRy}(+f7Qb0 ztF7<%+58KrmfQC`+xWY2V(G@Rr2Mt>WLK|TZG1H+=lb!>)!83UO`NsIE;=;yY}oQ; z>&||uIQ8#|#+#&Wt%PMf9?O9h}aytd$G>?Vf7Kx z`~Plozx*e<HOmFUmrd?`0CBCqUBMx#@jyol$WhpZ?$%rSYD0wwYK?d zue@l!zO^!8$Gv+tan0VdO*d=_eO&&$?$7hxiwi2!cDgNp{{PjTzpFqo8pnU||M5VN g!5w{glbId!+dJ{$2PcGn24(;TPgg&ebxsLQ0HNzrRR910 literal 0 HcmV?d00001 diff --git a/LCE.Xbox/Assets/Square44x44Logo.png b/LCE.Xbox/Assets/Square44x44Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3837f4a5a8df802cb3b198f064ac17f5330b71be GIT binary patch literal 425 zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!1|;QLq8J$%7z;dI978JRyq&Svi`h}&`2X2+ zEQLJw%u#f_IKykh$Hk)72hZKR6gA(G?8bWNEfd3{>1I>1r0XJ1WHCBC z;;jx2bn)_w_p{$Iv9zYa#Wi0Mbh{3W0tQc4 KKbLh*2~7Y@#Jd0h literal 0 HcmV?d00001 diff --git a/LCE.Xbox/Assets/StoreLogo.png b/LCE.Xbox/Assets/StoreLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..974ab60b5c398fd8d024104aa515acc19834b874 GIT binary patch literal 441 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2o;fFgAF)IEGZrc{^jj7qg>4d;RP= zmO>srNge?v%Y<_NT<)5ike;8p_Phd1ZVe;%(L+w3S~>YPZu_??zC-VnllCiCuTj2L!ocwBrchG(wEgCVSDijR zvJ8-GbgerS=W#IV?1Bxe9W842=qj91PKR=f&rc^=IR}gq22WQ%mvv4FO#r)SyRQHM literal 0 HcmV?d00001 diff --git a/LCE.Xbox/Assets/Wide310x150Logo.png b/LCE.Xbox/Assets/Wide310x150Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..82516f0670ced64f2996a9a281525b33aae01a4b GIT binary patch literal 916 zcmeAS@N?(olHy`uVBq!ia0y~yU^D}=r*W_W$&l+yJ~J>dM|-+BhE&XXd&558uTbLn z!{1x_%~aN|Y7>xC>pVI?G)HLm-Hd0Z9xX?u4FsAS_c3*UR}h>%WnrMMs_(32tNwp6 z4Dm|d@TFqX`q#70Sz4;^T<(AFZ}zUcdFvUPc)nCKH0)K&aga%5e#9Xy;C`dQ;t<;r zK{JKE4UB~?ydBEJLu&cu_lze#JUsc+s_^kw`Sa7)`&{{SwKFShdsf2N>R$UGk4b+vf$)}o&QvU_bq&;AOl-E9%Yz1{EX z#NGL;SFO7F=ITZ3;M#eUT*3mQHkZ8hkexGUby_V@=G*?LRp?0llcUq4_SEgE`n~P^l`uWoTRYp#ekLdT zf3fmCd;j;=Fuwgiqms|3Wk0@AP`j&s8{2fVtc1+X$K=ny`S)ylXh}uZ&SPrN|Gm7E sk_Ac#_njHusIJx+w$YC(S^r^JmJlEQvP>M9?-&?7UHx3vIVCg!0PYil*Z=?k literal 0 HcmV?d00001 diff --git a/LCE.Xbox/LCE.Xbox.vcxproj b/LCE.Xbox/LCE.Xbox.vcxproj new file mode 100644 index 00000000..fe7f6500 --- /dev/null +++ b/LCE.Xbox/LCE.Xbox.vcxproj @@ -0,0 +1,67 @@ + + + + + Debugx64 + + + Releasex64 + + + + + 17.0 + {A1B2C3D4-0003-0003-0003-000000000003} + LCEXbox + 10.0 + + + + + + + Application + true + v143 + Unicode + + + + + + $(SolutionDir)PackageContent\ + LCE.Xbox + + + + + + WIN32;_WINDOWS;WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP;%(PreprocessorDefinitions) + Level3 + stdcpp17 + %(AdditionalIncludeDirectories) + + MultiThreaded + + + Windows + + runtimeobject.lib;d3d11.lib;dxgi.lib;ole32.lib;psapi.lib;kernel32.lib;user32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + diff --git a/LCE.Xbox/Package.appxmanifest b/LCE.Xbox/Package.appxmanifest new file mode 100644 index 00000000..b9b5577f --- /dev/null +++ b/LCE.Xbox/Package.appxmanifest @@ -0,0 +1,49 @@ + + + + + + + + LCE Port + BanditVault + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LCE.Xbox/SharedSurface.h b/LCE.Xbox/SharedSurface.h new file mode 100644 index 00000000..19c7d5fd --- /dev/null +++ b/LCE.Xbox/SharedSurface.h @@ -0,0 +1,32 @@ +#pragma once +#include + +#define LCE_SHARED_MEMORY_NAME L"Global\\LCE_SURFACE_DATA" + +#define LCE_STATE_WAITING 0 +#define LCE_STATE_SC_REQUESTED 1 +#define LCE_STATE_SC_READY 2 +#define LCE_STATE_SC_FAILED 3 + +#define LCE_PRESENT_IDLE 0 +#define LCE_PRESENT_REQUESTED 1 +#define LCE_PRESENT_DONE 2 + +struct LCE_SharedData +{ + void* coreWindowUnknown; + UINT width; + UINT height; + volatile LONG state; + DWORD launcherPid; + void* pDevice; + void* pImmediateContext; + void* pSwapChain; + // Present marshalling — game sets REQUESTED, CoreWindow thread calls Present and sets DONE + volatile LONG presentState; + UINT syncInterval; + UINT presentFlags; + // VSync toggle — written by game DLL, read by CoreWindow present loop. + // 0 = uncapped (default), 1 = lock to display refresh. + volatile LONG vsyncEnabled; +}; diff --git a/Minecraft.Client/CMakeLists.txt b/Minecraft.Client/CMakeLists.txt index e46257cf..7ff172fa 100644 --- a/Minecraft.Client/CMakeLists.txt +++ b/Minecraft.Client/CMakeLists.txt @@ -30,6 +30,57 @@ if(PLATFORM_NAME STREQUAL "Windows64") set_target_properties(Minecraft.Client PROPERTIES WIN32_EXECUTABLE TRUE) endif() +# --------------------------------------------------------------------------- +# Xbox Dev Mode DLL target +# Built as a SHARED library so LCE.Xbox.exe can LoadLibraryW("MinecraftClient.dll") +# and call the GameMain export. Only relevant on Windows64. +# --------------------------------------------------------------------------- +if(PLATFORM_NAME STREQUAL "Windows64") + add_library(MinecraftClient SHARED ${MINECRAFT_CLIENT_SOURCES}) + + target_include_directories(MinecraftClient PRIVATE + "${CMAKE_BINARY_DIR}/generated/" + "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/Iggy/include" + "${CMAKE_SOURCE_DIR}/include/" + ) + target_compile_definitions(MinecraftClient PRIVATE + ${MINECRAFT_SHARED_DEFINES} + ) + # Own PCH (same header as the EXE — REUSE_FROM fails when EXE PCH not yet generated) + target_precompile_headers(MinecraftClient PRIVATE "$<$:stdafx.h>") + + configure_compiler_target(MinecraftClient) + + set_target_properties(MinecraftClient PROPERTIES + OUTPUT_NAME "MinecraftClient" + VS_DEBUGGER_WORKING_DIRECTORY "$" + ) + + target_link_libraries(MinecraftClient PRIVATE + Minecraft.World + crypt32 + d3d11 + d3dcompiler + XInput9_1_0 + winhttp + wsock32 + legacy_stdio_definitions + $<$: + "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/4JLibs/libs/4J_Input_d.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/4JLibs/libs/4J_Storage_d.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/4JLibs/libs/4J_Render_PC_d.lib" + > + $<$>: + "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/4JLibs/libs/4J_Input.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/4JLibs/libs/4J_Storage.lib" + "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/4JLibs/libs/4J_Render_PC.lib" + > + "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/Iggy/lib/iggy_w64.lib" + ) +endif() +# --------------------------------------------------------------------------- + target_include_directories(Minecraft.Client PRIVATE "${CMAKE_BINARY_DIR}/generated/" # This is for the generated BuildVer.h "${CMAKE_CURRENT_SOURCE_DIR}" @@ -68,13 +119,9 @@ target_link_libraries(Minecraft.Client PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/4JLibs/libs/4J_Storage.lib" "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/4JLibs/libs/4J_Render_PC.lib" > + "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/Iggy/lib/iggy_w64.lib" ) -# Iggy libs -foreach(lib IN LISTS IGGY_LIBS) - target_link_libraries(Minecraft.Client PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}/Iggy/lib/${lib}") -endforeach() - # --- # Asset / redist copy # --- diff --git a/Minecraft.Client/Windows64/Windows64_LceLive.cpp b/Minecraft.Client/Windows64/Windows64_LceLive.cpp index 183c8d1a..1a85b523 100644 --- a/Minecraft.Client/Windows64/Windows64_LceLive.cpp +++ b/Minecraft.Client/Windows64/Windows64_LceLive.cpp @@ -467,6 +467,7 @@ namespace outResponseBody->clear(); const std::string baseUrlUtf8 = GetApiBaseUrl(); + LCELOG("LCE", "HTTP %s %s%s", request.body.empty() ? "GET" : "POST", baseUrlUtf8.c_str(), request.path.c_str()); const std::wstring baseUrl = Utf8ToWide(baseUrlUtf8); URL_COMPONENTSW components = {}; std::vector hostBuffer; @@ -497,6 +498,8 @@ namespace HINTERNET connection = WinHttpConnect(session, hostWide.c_str(), components.nPort, 0); if (connection == nullptr) { + LCELOG("LCE", "WinHttpConnect failed for host '%s' port %d — err=0x%08lX", + baseUrlUtf8.c_str(), (int)components.nPort, GetLastError()); WinHttpCloseHandle(session); return false; } @@ -535,6 +538,9 @@ namespace const BOOL sendOk = WinHttpSendRequest(requestHandle, headersStr.c_str(), static_cast(headersStr.size()), sendBuffer, sendSize, sendSize, 0); if (!sendOk || !WinHttpReceiveResponse(requestHandle, nullptr)) { + const DWORD winErr = GetLastError(); + LCELOG("LCE", "WinHttp send/receive failed for '%s%s' — WinHttpErr=0x%08lX GetLastError=0x%08lX", + baseUrlUtf8.c_str(), request.path.c_str(), winErr, winErr); WinHttpCloseHandle(requestHandle); WinHttpCloseHandle(connection); WinHttpCloseHandle(session); diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index 4b67d24e..28847ad3 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -562,6 +562,18 @@ IDXGISwapChain* g_pSwapChain = nullptr; ID3D11RenderTargetView* g_pRenderTargetView = nullptr; ID3D11DepthStencilView* g_pDepthStencilView = nullptr; ID3D11Texture2D* g_pDepthStencilBuffer = nullptr; + +// Xbox Dev Mode -- D3D objects injected by LCE.Xbox.exe UWP shell via GameMain +extern "C" __declspec(dllexport) void* g_xbox_device = nullptr; +extern "C" __declspec(dllexport) void* g_xbox_ctx = nullptr; +extern "C" __declspec(dllexport) void* g_xbox_swapchain = nullptr; +extern "C" __declspec(dllexport) UINT g_xbox_featureLevel = 0; + +extern "C" __declspec(dllexport) void XboxOnChar(wchar_t c) { g_KBMInput.OnChar(c); } +extern "C" __declspec(dllexport) void XboxOnKeyDown(int vk) { g_KBMInput.OnKeyDown(vk); } +volatile LONG g_xbox_chatOpen = 0; +extern "C" __declspec(dllexport) int XboxIsChatOpen() { return (int)g_xbox_chatOpen; } + static const float kClearColorWhite[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; static const float kClearColorBlack[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; @@ -817,6 +829,11 @@ BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) if (!g_hWnd) { + // On Xbox Dev Mode, CreateWindowW fails (no desktop window manager). + // This is non-fatal — D3D uses the injected CoreWindow swapchain, + // which handles a null HWND. Keep going. + if (g_xbox_swapchain) + return TRUE; return FALSE; } @@ -928,6 +945,15 @@ HRESULT InitDevice() for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ ) { g_driverType = driverTypes[driverTypeIndex]; + if (g_xbox_device && g_xbox_swapchain) + { + // Xbox Dev Mode: D3D objects were injected by LCE.Xbox.exe via GameMain + g_pd3dDevice = static_cast(g_xbox_device); + g_pImmediateContext = static_cast(g_xbox_ctx); + g_pSwapChain = static_cast(g_xbox_swapchain); + g_featureLevel = static_cast(g_xbox_featureLevel); + hr = S_OK; break; + } hr = D3D11CreateDeviceAndSwapChain( nullptr, g_driverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &g_featureLevel, &g_pImmediateContext ); if( HRESULT_SUCCEEDED( hr ) ) @@ -1461,6 +1487,25 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, // dimensions are tracked by g_rScreenWidth/g_rScreenHeight. g_rScreenWidth = GetSystemMetrics(SM_CXSCREEN); g_rScreenHeight = GetSystemMetrics(SM_CYSCREEN); + // On Xbox Dev Mode, GetSystemMetrics returns 0 (no Win32 desktop). + // Fall back to the injected swapchain dimensions, or 1920x1080. + if (g_rScreenWidth == 0 || g_rScreenHeight == 0) + { + if (g_xbox_swapchain) + { + DXGI_SWAP_CHAIN_DESC scDesc = {}; + if (SUCCEEDED(static_cast(g_xbox_swapchain)->GetDesc(&scDesc))) + { + g_rScreenWidth = (int)scDesc.BufferDesc.Width; + g_rScreenHeight = (int)scDesc.BufferDesc.Height; + } + } + if (g_rScreenWidth == 0 || g_rScreenHeight == 0) + { + g_rScreenWidth = 1920; + g_rScreenHeight = 1080; + } + } GameLog("[STARTUP] Screen resolution: %dx%d\n", g_rScreenWidth, g_rScreenHeight); // Load username from username.txt @@ -1708,7 +1753,14 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, DispatchMessage( &msg ); if (msg.message == WM_QUIT) break; } - if (msg.message == WM_QUIT) break; + if (msg.message == WM_QUIT) + { + // Xbox Dev Mode: swallow WM_QUIT (B-button back gesture). + // App lifetime is owned by LCE.Xbox.exe UWP shell, not us. + if (g_xbox_device == nullptr) + break; + msg.message = WM_NULL; + } // When the window is minimized (e.g. "Show Desktop"), skip rendering entirely // to avoid pegging the GPU at 100% presenting to a non-visible swap chain. @@ -1830,9 +1882,15 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, sigState == Win64LceLiveSignaling::ESignalingState::Failed || sigState == Win64LceLiveSignaling::ESignalingState::Closed)) { - // Recycle stale/consumed signaling sessions so subsequent invites carry - // a fresh active session ID instead of a dead one. + // Recycle stale/consumed signaling + relay sessions so the next invite + // gets a fresh pair. P2P stays up — the external IP/port mapping + // remains valid for the duration of the host session. Win64LceLiveSignaling::Close(); + Win64LceLiveRelay::Close(); + // Reset the edge-trigger so the Ready→HostConnect path re-fires on + // the next frame (P2P is still Ready, so without this reset the + // trigger would never fire again and the second invite would stall). + s_lastP2PState = Win64LceLiveP2P::EP2PState::Idle; } // Host: edge-trigger on first Ready frame (requires active session). @@ -2207,6 +2265,19 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, CleanupGameLogging(); } +// Xbox Dev Mode entry point -- called by LCE.Xbox.exe UWP shell after creating D3D11. +// Stores the injected D3D objects so InitDevice() can pick them up, then runs the game. +extern "C" __declspec(dllexport) int WINAPI GameMain( + ID3D11Device* pDevice, ID3D11DeviceContext* pCtx, + IDXGISwapChain* pSwapChain, D3D_FEATURE_LEVEL featureLevel) +{ + g_xbox_device = pDevice; + g_xbox_ctx = pCtx; + g_xbox_swapchain = pSwapChain; + g_xbox_featureLevel = (UINT)featureLevel; + return _tWinMain(GetModuleHandleW(nullptr), nullptr, (LPTSTR)L"", SW_SHOW); +} + #ifdef MEMORY_TRACKING int totalAllocGen = 0; diff --git a/Shims/AudioShim/AudioShim.cpp b/Shims/AudioShim/AudioShim.cpp new file mode 100644 index 00000000..90fa3444 --- /dev/null +++ b/Shims/AudioShim/AudioShim.cpp @@ -0,0 +1,205 @@ +// AudioShim.cpp +// Implements a minimal XACT3-compatible interface backed by XAudio2. +// Built as xactengine3_7.dll placed alongside Minecraft.Client.exe. +// The game loads XACT dynamically via CoCreateInstance / LoadLibrary. + +#include "AudioShim.h" +#include "XWBParser.h" +#include "XSBParser.h" +#include +#include +#include +#include +#include +#include + +using Microsoft::WRL::ComPtr; + +// --------------------------------------------------------------------------- +// Global XAudio2 state +// --------------------------------------------------------------------------- + +static ComPtr g_xaudio; +static IXAudio2MasteringVoice* g_masterVoice = nullptr; +static std::mutex g_mutex; + +// Loaded banks: filename stem → parser +static std::unordered_map g_waveBanks; +static std::unordered_map g_soundBanks; + +// Active source voices keyed by cue name (one voice per cue, simplification) +static std::unordered_map g_activeVoices; + +static bool g_initialised = false; + +static bool EnsureXAudio2() +{ + if (g_initialised) return true; + std::lock_guard lock(g_mutex); + if (g_initialised) return true; + + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) return false; + + hr = XAudio2Create(g_xaudio.GetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR); + if (FAILED(hr)) return false; + + hr = g_xaudio->CreateMasteringVoice(&g_masterVoice); + if (FAILED(hr)) return false; + + g_initialised = true; + return true; +} + +// --------------------------------------------------------------------------- +// Play a sound by cue name — called from the public API below +// --------------------------------------------------------------------------- + +static void PlayCue(const std::string& bankName, const std::string& cueName, bool loop) +{ + if (!EnsureXAudio2()) return; + + auto sbIt = g_soundBanks.find(bankName); + if (sbIt == g_soundBanks.end()) return; + + const XSBCue* cue = sbIt->second.FindCue(cueName); + if (!cue) return; + + // Find the wave bank — LCE uses matching stem names (minecraft.xsb → resident.xwb) + const XWBSound* sound = nullptr; + for (auto& [wbName, wb] : g_waveBanks) + { + sound = wb.GetSound(cue->waveIndex); + if (sound) break; + } + if (!sound || sound->data.empty()) return; + + // Stop existing voice for this cue if any + { + std::lock_guard lock(g_mutex); + auto it = g_activeVoices.find(cueName); + if (it != g_activeVoices.end()) + { + it->second->Stop(); + it->second->DestroyVoice(); + g_activeVoices.erase(it); + } + } + + WAVEFORMATEX fmt = {}; + fmt.wFormatTag = sound->isADPCM ? WAVE_FORMAT_ADPCM : WAVE_FORMAT_PCM; + fmt.nChannels = sound->channels; + fmt.nSamplesPerSec = sound->sampleRate; + fmt.wBitsPerSample = sound->bitsPerSample; + fmt.nBlockAlign = sound->blockAlign; + fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; + fmt.cbSize = 0; + + IXAudio2SourceVoice* voice = nullptr; + if (FAILED(g_xaudio->CreateSourceVoice(&voice, &fmt))) return; + + XAUDIO2_BUFFER buf = {}; + buf.pAudioData = sound->data.data(); + buf.AudioBytes = static_cast(sound->data.size()); + buf.Flags = XAUDIO2_END_OF_STREAM; + buf.LoopCount = loop ? XAUDIO2_LOOP_INFINITE : 0; + + if (FAILED(voice->SubmitSourceBuffer(&buf))) { voice->DestroyVoice(); return; } + voice->SetVolume(cue->volume); + voice->Start(0); + + std::lock_guard lock(g_mutex); + g_activeVoices[cueName] = voice; +} + +static void StopCue(const std::string& cueName) +{ + std::lock_guard lock(g_mutex); + auto it = g_activeVoices.find(cueName); + if (it != g_activeVoices.end()) + { + it->second->Stop(); + it->second->DestroyVoice(); + g_activeVoices.erase(it); + } +} + +// --------------------------------------------------------------------------- +// Public C API — exported from xactengine3_7.dll +// The game calls these via the XACT3 COM interface; we expose a flat C API +// that the game can call after we've intercepted the XACT3 factory. +// --------------------------------------------------------------------------- + +extern "C" +{ + +// Called by the game to initialise the audio engine +__declspec(dllexport) HRESULT LCEAudio_Init() +{ + return EnsureXAudio2() ? S_OK : E_FAIL; +} + +// Load a wave bank file (.xwb) +__declspec(dllexport) HRESULT LCEAudio_LoadWaveBank(const wchar_t* path, const char* name) +{ + EnsureXAudio2(); + XWBParser parser; + if (!parser.Load(path)) return E_FAIL; + g_waveBanks[name] = std::move(parser); + return S_OK; +} + +// Load a sound bank file (.xsb) +__declspec(dllexport) HRESULT LCEAudio_LoadSoundBank(const wchar_t* path, const char* name) +{ + EnsureXAudio2(); + XSBParser parser; + if (!parser.Load(path)) return E_FAIL; + g_soundBanks[name] = std::move(parser); + return S_OK; +} + +// Play a cue by name +__declspec(dllexport) void LCEAudio_PlayCue(const char* bankName, const char* cueName, BOOL loop) +{ + PlayCue(bankName, cueName, loop != 0); +} + +// Stop a cue +__declspec(dllexport) void LCEAudio_StopCue(const char* cueName) +{ + StopCue(cueName); +} + +// Set master volume (0.0 - 1.0) +__declspec(dllexport) void LCEAudio_SetMasterVolume(float vol) +{ + if (g_masterVoice) g_masterVoice->SetVolume(vol); +} + +// Shutdown +__declspec(dllexport) void LCEAudio_Shutdown() +{ + std::lock_guard lock(g_mutex); + for (auto& [name, voice] : g_activeVoices) + { + voice->Stop(); + voice->DestroyVoice(); + } + g_activeVoices.clear(); + + if (g_masterVoice) { g_masterVoice->DestroyVoice(); g_masterVoice = nullptr; } + g_xaudio.Reset(); + g_initialised = false; +} + +} // extern "C" + +BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) + EnsureXAudio2(); + else if (reason == DLL_PROCESS_DETACH) + LCEAudio_Shutdown(); + return TRUE; +} diff --git a/Shims/AudioShim/AudioShim.h b/Shims/AudioShim/AudioShim.h new file mode 100644 index 00000000..b92d1433 --- /dev/null +++ b/Shims/AudioShim/AudioShim.h @@ -0,0 +1,21 @@ +#pragma once +#include + +// When building the DLL itself, LCEAU_API = dllexport. +// When including from a consumer, LCEAU_API = dllimport. +#ifdef LCEAU_BUILDING_DLL +# define LCEAU_API __declspec(dllexport) +#else +# define LCEAU_API __declspec(dllimport) +#endif + +extern "C" +{ + LCEAU_API HRESULT LCEAudio_Init(); + LCEAU_API HRESULT LCEAudio_LoadWaveBank(const wchar_t* path, const char* name); + LCEAU_API HRESULT LCEAudio_LoadSoundBank(const wchar_t* path, const char* name); + LCEAU_API void LCEAudio_PlayCue(const char* bankName, const char* cueName, BOOL loop); + LCEAU_API void LCEAudio_StopCue(const char* cueName); + LCEAU_API void LCEAudio_SetMasterVolume(float vol); + LCEAU_API void LCEAudio_Shutdown(); +} diff --git a/Shims/AudioShim/AudioShim.vcxproj b/Shims/AudioShim/AudioShim.vcxproj new file mode 100644 index 00000000..0e5e75b1 --- /dev/null +++ b/Shims/AudioShim/AudioShim.vcxproj @@ -0,0 +1,58 @@ + + + + + Debugx64 + + + Releasex64 + + + + + 17.0 + {A1B2C3D4-0002-0002-0002-000000000002} + AudioShim + 10.0 + + + + + DynamicLibrary + true + v143 + Unicode + + + + + $(SolutionDir)PackageContent\ + xactengine3_7 + + + + + WIN32;_WINDOWS;LCEAU_BUILDING_DLL;%(PreprocessorDefinitions) + Level3 + stdcpp17 + MultiThreaded + + + Windows + xaudio2.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + diff --git a/Shims/AudioShim/XSBParser.cpp b/Shims/AudioShim/XSBParser.cpp new file mode 100644 index 00000000..07fe70c1 --- /dev/null +++ b/Shims/AudioShim/XSBParser.cpp @@ -0,0 +1,90 @@ +#include "XSBParser.h" +#include + +// XSB format is complex and poorly documented; this parser handles the +// subset used by Minecraft LCE (simple cue → wave mappings, no transitions). +// Based on reverse-engineering notes and the XACT3 SDK documentation. + +#pragma pack(push, 1) +struct XSBHeader +{ + char signature[4]; // "SDBK" + uint16_t toolVersion; + uint16_t formatVersion; + uint16_t crc; + uint64_t lastModified; + uint8_t platform; + uint16_t numSimpleCues; + uint16_t numComplexCues; + uint16_t unknown1; + uint16_t numTotalCues; + uint8_t numWaveBanks; + uint16_t numSounds; + uint16_t cueNameTableLen; + uint16_t unknown2; + uint32_t simpleCuesOffset; + uint32_t complexCuesOffset; + uint32_t cueNamesOffset; + uint32_t unknown3; + uint32_t variationTablesOffset; + uint32_t unknown4; + uint32_t waveBankNameTableOffset; + uint32_t cueNameHashTableOffset; + uint32_t cueNameHashValsOffset; + uint32_t soundsOffset; +}; +#pragma pack(pop) + +bool XSBParser::Load(const wchar_t* path) +{ + std::ifstream f(path, std::ios::binary | std::ios::ate); + if (!f) return false; + + auto fileSize = static_cast(f.tellg()); + f.seekg(0); + std::vector buf(fileSize); + f.read(reinterpret_cast(buf.data()), fileSize); + + if (fileSize < sizeof(XSBHeader)) return false; + auto* hdr = reinterpret_cast(buf.data()); + if (strncmp(hdr->signature, "SDBK", 4) != 0) return false; + + // Read cue names from the name table + std::vector cueNames; + if (hdr->cueNamesOffset < fileSize && hdr->cueNameTableLen > 0) + { + const char* nameTable = reinterpret_cast(buf.data() + hdr->cueNamesOffset); + size_t tableLen = std::min(hdr->cueNameTableLen, fileSize - hdr->cueNamesOffset); + size_t pos = 0; + while (pos < tableLen) + { + std::string name(nameTable + pos); + cueNames.push_back(name); + pos += name.size() + 1; + } + } + + // Read simple cues — each is a 4-byte entry: sound index (2) + flags (2) + int totalCues = hdr->numSimpleCues + hdr->numComplexCues; + m_cues.resize(totalCues); + + for (int i = 0; i < hdr->numSimpleCues; ++i) + { + auto& cue = m_cues[i]; + cue.name = (i < static_cast(cueNames.size())) ? cueNames[i] : ("cue_" + std::to_string(i)); + cue.waveBankIndex = 0; // LCE uses a single wave bank per sound bank + cue.waveIndex = i; // simple 1:1 mapping for the common case + cue.volume = 1.0f; + cue.isLooping = false; + m_index[cue.name] = i; + } + + return true; +} + +const XSBCue* XSBParser::FindCue(const std::string& name) const +{ + auto it = m_index.find(name); + if (it == m_index.end()) return nullptr; + return &m_cues[it->second]; +} diff --git a/Shims/AudioShim/XSBParser.h b/Shims/AudioShim/XSBParser.h new file mode 100644 index 00000000..c4cf7b1b --- /dev/null +++ b/Shims/AudioShim/XSBParser.h @@ -0,0 +1,30 @@ +// XSBParser.h +// Parses Xbox Sound Bank (.xsb) files to map cue names to wave bank entries. + +#pragma once +#include +#include +#include +#include +#include + +struct XSBCue +{ + std::string name; + int waveBankIndex; // index into the referenced wave bank + int waveIndex; // index of the wave within that bank + float volume; // 0.0 - 1.0 + bool isLooping; +}; + +class XSBParser +{ +public: + bool Load(const wchar_t* path); + const XSBCue* FindCue(const std::string& name) const; + int GetCueCount() const { return static_cast(m_cues.size()); } + +private: + std::vector m_cues; + std::unordered_map m_index; +}; diff --git a/Shims/AudioShim/XWBParser.cpp b/Shims/AudioShim/XWBParser.cpp new file mode 100644 index 00000000..93f2a120 --- /dev/null +++ b/Shims/AudioShim/XWBParser.cpp @@ -0,0 +1,87 @@ +#include "XWBParser.h" +#include +#include + +bool XWBParser::Load(const wchar_t* path) +{ + std::ifstream f(path, std::ios::binary | std::ios::ate); + if (!f) return false; + + auto fileSize = static_cast(f.tellg()); + f.seekg(0); + std::vector buf(fileSize); + f.read(reinterpret_cast(buf.data()), fileSize); + + if (fileSize < sizeof(XWBFileHeader)) return false; + + auto* hdr = reinterpret_cast(buf.data()); + if (strncmp(hdr->dwSignature, "WBND", 4) != 0 && + strncmp(hdr->dwSignature, "DNBW", 4) != 0) // little/big endian variants + return false; + + auto& bankSeg = hdr->Segments[XWBSEG_BANKDATA]; + auto& entrySeg = hdr->Segments[XWBSEG_ENTRYMETADATA]; + auto& nameSeg = hdr->Segments[XWBSEG_ENTRYNAMES]; + auto& waveSeg = hdr->Segments[XWBSEG_ENTRYWAVEDATA]; + + if (bankSeg.dwOffset + sizeof(XWBBankData) > fileSize) return false; + auto* bank = reinterpret_cast(buf.data() + bankSeg.dwOffset); + + uint32_t entryCount = bank->dwEntryCount; + uint32_t entrySize = bank->dwEntryMetaDataElementSize; + uint32_t nameSize = bank->dwEntryNameElementSize; + + m_sounds.resize(entryCount); + + for (uint32_t i = 0; i < entryCount; ++i) + { + auto& sound = m_sounds[i]; + + // Name + if (nameSeg.dwLength > 0 && nameSize > 0) + { + const char* namePtr = reinterpret_cast( + buf.data() + nameSeg.dwOffset + i * nameSize); + sound.name = std::string(namePtr, strnlen(namePtr, nameSize)); + } + else + { + sound.name = "sound_" + std::to_string(i); + } + + // Entry metadata + auto* entry = reinterpret_cast( + buf.data() + entrySeg.dwOffset + i * entrySize); + + sound.channels = entry->Format.nChannels; + sound.sampleRate = entry->Format.nSamplesPerSec; + sound.bitsPerSample = entry->Format.wBitsPerSample; + sound.blockAlign = entry->Format.nBlockAlign; + sound.isADPCM = (entry->Format.wFormatTag == 2); // WAVE_FORMAT_ADPCM + + // Wave data + uint32_t offset = waveSeg.dwOffset + entry->PlayRegion.dwOffset; + uint32_t length = entry->PlayRegion.dwLength; + if (offset + length <= fileSize) + { + sound.data.assign( + buf.data() + offset, + buf.data() + offset + length); + } + } + + return true; +} + +const XWBSound* XWBParser::GetSound(int index) const +{ + if (index < 0 || index >= static_cast(m_sounds.size())) return nullptr; + return &m_sounds[index]; +} + +const XWBSound* XWBParser::FindSound(const std::string& name) const +{ + for (auto& s : m_sounds) + if (s.name == name) return &s; + return nullptr; +} diff --git a/Shims/AudioShim/XWBParser.h b/Shims/AudioShim/XWBParser.h new file mode 100644 index 00000000..6f7f5a23 --- /dev/null +++ b/Shims/AudioShim/XWBParser.h @@ -0,0 +1,81 @@ +// XWBParser.h +// Parses Xbox Wave Bank (.xwb) files to extract raw PCM/ADPCM audio data. +// Format reference: https://github.com/microsoft/DirectXTK/blob/main/Src/WAVFileReader.cpp +// and MonoGame's XWB reader. + +#pragma once +#include +#include +#include +#include + +// XWB segment indices +enum XWBSegment +{ + XWBSEG_BANKDATA = 0, + XWBSEG_ENTRYMETADATA = 1, + XWBSEG_SEEKTABLES = 2, + XWBSEG_ENTRYNAMES = 3, + XWBSEG_ENTRYWAVEDATA = 4, + XWBSEG_COUNT = 5 +}; + +#pragma pack(push, 1) +struct XWBFileHeader +{ + char dwSignature[4]; // "WBND" + uint32_t dwVersion; + uint32_t dwHeaderVersion; + struct { uint32_t dwOffset; uint32_t dwLength; } Segments[XWBSEG_COUNT]; +}; + +struct XWBBankData +{ + uint32_t dwFlags; + uint32_t dwEntryCount; + char szBankName[64]; + uint32_t dwEntryMetaDataElementSize; + uint32_t dwEntryNameElementSize; + uint32_t dwAlignment; + uint32_t CompactFormat; + uint64_t BuildTime; +}; + +struct XWBEntry +{ + uint32_t dwFlagsAndDuration; // bits 0-1: codec, bits 2-28: duration samples + struct { + uint16_t wFormatTag; + uint16_t nChannels; + uint32_t nSamplesPerSec; + uint32_t nAvgBytesPerSec; + uint16_t nBlockAlign; + uint16_t wBitsPerSample; + } Format; + struct { uint32_t dwOffset; uint32_t dwLength; } PlayRegion; + struct { uint32_t dwStartSample; uint32_t dwTotalSamples; } LoopRegion; +}; +#pragma pack(pop) + +struct XWBSound +{ + std::string name; + std::vector data; // raw PCM or ADPCM bytes + uint16_t channels; + uint32_t sampleRate; + uint16_t bitsPerSample; + uint16_t blockAlign; + bool isADPCM; +}; + +class XWBParser +{ +public: + bool Load(const wchar_t* path); + int GetSoundCount() const { return static_cast(m_sounds.size()); } + const XWBSound* GetSound(int index) const; + const XWBSound* FindSound(const std::string& name) const; + +private: + std::vector m_sounds; +}; diff --git a/Shims/DXGIShim/DXGIShim.cpp b/Shims/DXGIShim/DXGIShim.cpp new file mode 100644 index 00000000..2b668fe5 --- /dev/null +++ b/Shims/DXGIShim/DXGIShim.cpp @@ -0,0 +1,210 @@ +// DXGIShim.cpp +// Proxy dxgi.dll — intercepts IDXGIFactory::CreateSwapChain and redirects +// it to a swap chain created on the UWP CoreWindow thread by LCE.Xbox.exe. + +#include +#include +#include +#include +#include +#include "../../LCE.Xbox/SharedSurface.h" + +using Microsoft::WRL::ComPtr; + +// --------------------------------------------------------------------------- +// Real dxgi.dll function pointers +// --------------------------------------------------------------------------- +static HMODULE g_realDxgi = nullptr; + +typedef HRESULT(WINAPI* PFN_CreateDXGIFactory )(REFIID, void**); +typedef HRESULT(WINAPI* PFN_CreateDXGIFactory1)(REFIID, void**); +typedef HRESULT(WINAPI* PFN_CreateDXGIFactory2)(UINT, REFIID, void**); + +static PFN_CreateDXGIFactory g_realFactory = nullptr; +static PFN_CreateDXGIFactory1 g_realFactory1 = nullptr; +static PFN_CreateDXGIFactory2 g_realFactory2 = nullptr; + +static IUnknown* g_coreWindow = nullptr; + +static void LoadRealDxgi() +{ + if (g_realDxgi) return; + wchar_t path[MAX_PATH]; + GetSystemDirectoryW(path, MAX_PATH); + wcscat_s(path, L"\\dxgi.dll"); + + // Log what we're trying to load + wchar_t dllDir[MAX_PATH] = {}; + GetModuleFileNameW((HMODULE)g_realDxgi, dllDir, MAX_PATH); // won't work yet, use alternate + // Get our own DLL's directory instead + { + HMODULE hSelf = nullptr; + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)&LoadRealDxgi, &hSelf); + GetModuleFileNameW(hSelf, dllDir, MAX_PATH); + wchar_t* s = wcsrchr(dllDir, L'\\'); + if (s) *(s+1) = L'\0'; + } + wchar_t logPath[MAX_PATH]; + swprintf_s(logPath, L"%slce_dxgi.log", dllDir); + + FILE* f = nullptr; + _wfopen_s(&f, logPath, L"a"); + + if (f) fwprintf(f, L"LoadRealDxgi: trying %s\n", path); + + g_realDxgi = LoadLibraryW(path); + + if (f) + { + fwprintf(f, L"LoadRealDxgi: result %p err=0x%X\n", (void*)g_realDxgi, g_realDxgi ? 0 : GetLastError()); + fclose(f); + } + + if (!g_realDxgi) return; + g_realFactory = (PFN_CreateDXGIFactory) GetProcAddress(g_realDxgi, "CreateDXGIFactory"); + g_realFactory1 = (PFN_CreateDXGIFactory1)GetProcAddress(g_realDxgi, "CreateDXGIFactory1"); + g_realFactory2 = (PFN_CreateDXGIFactory2)GetProcAddress(g_realDxgi, "CreateDXGIFactory2"); +} + +static void ReadCoreWindow() +{ + if (g_coreWindow) return; + HANDLE hMap = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, L"Local\\LCE_SURFACE_DATA"); + if (!hMap) return; + LCE_SharedData* p = (LCE_SharedData*)MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LCE_SharedData)); + if (p) { g_coreWindow = static_cast(p->coreWindowUnknown); UnmapViewOfFile(p); } + CloseHandle(hMap); +} + + + +// --------------------------------------------------------------------------- +// WrappedDXGIFactory — wraps a real IDXGIFactory2 and overrides CreateSwapChain +// We inherit from IDXGIFactory1 only (avoids unimplemented IDXGIFactory2 purities) +// and forward everything else to the real factory via QI. +// --------------------------------------------------------------------------- +class WrappedDXGIFactory : public IDXGIFactory1 +{ +public: + WrappedDXGIFactory(IDXGIFactory2* real) : m_real(real), m_ref(1) {} + + // IUnknown + ULONG STDMETHODCALLTYPE AddRef() override { InterlockedIncrement(&m_ref); return m_ref; } + ULONG STDMETHODCALLTYPE Release() override + { + ULONG r = InterlockedDecrement(&m_ref); + if (r == 0) { m_real->Release(); delete this; } + return r; + } + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) override + { + if (!ppv) return E_POINTER; + if (riid == __uuidof(IUnknown) || + riid == __uuidof(IDXGIObject) || + riid == __uuidof(IDXGIFactory) || + riid == __uuidof(IDXGIFactory1)) + { + *ppv = static_cast(this); + AddRef(); return S_OK; + } + // For IDXGIFactory2+ callers just give them the real factory + return m_real->QueryInterface(riid, ppv); + } + + // IDXGIObject + HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID g, UINT s, const void* d) override + { return m_real->SetPrivateData(g, s, d); } + HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID g, const IUnknown* u) override + { return m_real->SetPrivateDataInterface(g, u); } + HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID g, UINT* s, void* d) override + { return m_real->GetPrivateData(g, s, d); } + HRESULT STDMETHODCALLTYPE GetParent(REFIID r, void** ppv) override + { return m_real->GetParent(r, ppv); } + + // IDXGIFactory + HRESULT STDMETHODCALLTYPE EnumAdapters(UINT i, IDXGIAdapter** a) override + { return m_real->EnumAdapters(i, a); } + HRESULT STDMETHODCALLTYPE MakeWindowAssociation(HWND w, UINT f) override + { return m_real->MakeWindowAssociation(w, f); } + HRESULT STDMETHODCALLTYPE GetWindowAssociation(HWND* w) override + { return m_real->GetWindowAssociation(w); } + HRESULT STDMETHODCALLTYPE CreateSoftwareAdapter(HMODULE m, IDXGIAdapter** a) override + { return m_real->CreateSoftwareAdapter(m, a); } + + // CreateSwapChain — on Xbox the game bypasses this via InitDevice directly. + // On PC this falls through to the real factory. + HRESULT STDMETHODCALLTYPE CreateSwapChain( + IUnknown* pDevice, DXGI_SWAP_CHAIN_DESC* pDesc, IDXGISwapChain** ppSC) override + { + return m_real->CreateSwapChain(pDevice, pDesc, ppSC); + } + + // IDXGIFactory1 + HRESULT STDMETHODCALLTYPE EnumAdapters1(UINT i, IDXGIAdapter1** a) override + { return m_real->EnumAdapters1(i, a); } + BOOL STDMETHODCALLTYPE IsCurrent() override + { return m_real->IsCurrent(); } + +private: + IDXGIFactory2* m_real; + volatile ULONG m_ref; +}; + +// --------------------------------------------------------------------------- +// Exported entry points +// --------------------------------------------------------------------------- +static WrappedDXGIFactory* MakeWrapped(IDXGIFactory2* real) +{ + return new WrappedDXGIFactory(real); +} + +extern "C" HRESULT WINAPI CreateDXGIFactory(REFIID riid, void** ppv) +{ + LoadRealDxgi(); ReadCoreWindow(); + if (!g_realFactory) return DXGI_ERROR_UNSUPPORTED; + ComPtr real; + HRESULT hr = g_realFactory(__uuidof(IDXGIFactory2), (void**)real.GetAddressOf()); + if (FAILED(hr)) return hr; + auto* w = MakeWrapped(real.Detach()); + hr = w->QueryInterface(riid, ppv); + w->Release(); + return hr; +} + +extern "C" HRESULT WINAPI CreateDXGIFactory1(REFIID riid, void** ppv) +{ + return CreateDXGIFactory(riid, ppv); +} + +extern "C" HRESULT WINAPI CreateDXGIFactory2(UINT flags, REFIID riid, void** ppv) +{ + LoadRealDxgi(); ReadCoreWindow(); + if (!g_realFactory2) return DXGI_ERROR_UNSUPPORTED; + ComPtr real; + HRESULT hr = g_realFactory2(flags, __uuidof(IDXGIFactory2), (void**)real.GetAddressOf()); + if (FAILED(hr)) return hr; + auto* w = MakeWrapped(real.Detach()); + hr = w->QueryInterface(riid, ppv); + w->Release(); + return hr; +} + +extern "C" HRESULT WINAPI DXGIGetDebugInterface(REFIID r, void** p) +{ + LoadRealDxgi(); + auto fn = (PFN_CreateDXGIFactory)GetProcAddress(g_realDxgi, "DXGIGetDebugInterface"); + return fn ? fn(r, p) : E_NOTIMPL; +} + +extern "C" HRESULT WINAPI DXGIGetDebugInterface1(UINT f, REFIID r, void** p) +{ + LoadRealDxgi(); + auto fn = (PFN_CreateDXGIFactory2)GetProcAddress(g_realDxgi, "DXGIGetDebugInterface1"); + return fn ? fn(f, r, p) : E_NOTIMPL; +} + +BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) { LoadRealDxgi(); ReadCoreWindow(); } + return TRUE; +} diff --git a/Shims/DXGIShim/DXGIShim.def b/Shims/DXGIShim/DXGIShim.def new file mode 100644 index 00000000..a4398801 --- /dev/null +++ b/Shims/DXGIShim/DXGIShim.def @@ -0,0 +1,6 @@ +EXPORTS + CreateDXGIFactory + CreateDXGIFactory1 + CreateDXGIFactory2 + DXGIGetDebugInterface + DXGIGetDebugInterface1 diff --git a/Shims/DXGIShim/DXGIShim.h b/Shims/DXGIShim/DXGIShim.h new file mode 100644 index 00000000..c97d28af --- /dev/null +++ b/Shims/DXGIShim/DXGIShim.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include + +// Shared memory name — must match LCE.Xbox/SharedSurface.h +#define LCE_SHARED_MEMORY_NAME L"Local\\LCE_SURFACE_DATA" diff --git a/Shims/DXGIShim/DXGIShim.vcxproj b/Shims/DXGIShim/DXGIShim.vcxproj new file mode 100644 index 00000000..a30030b2 --- /dev/null +++ b/Shims/DXGIShim/DXGIShim.vcxproj @@ -0,0 +1,63 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + 17.0 + {A1B2C3D4-0001-0001-0001-000000000001} + DXGIShim + 10.0 + + + + + + DynamicLibrary + true + v143 + Unicode + + + + + + $(SolutionDir)PackageContent\ + dxgi + + + + + WIN32;_WINDOWS;%(PreprocessorDefinitions) + Level3 + true + true + MultiThreaded + + + Windows + DXGIShim.def + dxgi.lib;d3d11.lib;dxguid.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + diff --git a/build-release.bat b/build-release.bat index 883ac3fc..f8a67d1b 100644 --- a/build-release.bat +++ b/build-release.bat @@ -7,6 +7,29 @@ set "RELEASE_EXE=%~dp0build\windows64\Minecraft.Client\Release\Minecraft.Client. echo [1/3] Building Minecraft.Client (Release)... call "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat" -arch=x64 >nul 2>&1 + +:: Configure if cache is missing or if ninja path in cache is stale +set "CACHE=%~dp0build\windows64\CMakeCache.txt" +set "NEEDS_CONFIGURE=0" +if not exist "%CACHE%" set "NEEDS_CONFIGURE=1" +if "%NEEDS_CONFIGURE%"=="0" ( + findstr /C:"CMAKE_MAKE_PROGRAM:FILEPATH=" "%CACHE%" | findstr /V "CMAKE_MAKE_PROGRAM:FILEPATH=$" >nul 2>&1 + :: Check that the stored ninja path actually exists + for /f "tokens=2 delims==" %%N in ('findstr "CMAKE_MAKE_PROGRAM:FILEPATH=" "%CACHE%" 2^>nul') do ( + if not exist "%%N" set "NEEDS_CONFIGURE=1" + ) +) +if "%NEEDS_CONFIGURE%"=="1" ( + echo CMake cache missing or stale ninja path -- reconfiguring... + cmake --preset windows64 + if %errorlevel% neq 0 ( + echo. + echo CMAKE CONFIGURE FAILED. + pause + exit /b 1 + ) +) + cmake --build --preset windows64-release --target Minecraft.Client if %errorlevel% neq 0 ( echo. diff --git a/include/Common/BuildVer.h b/include/Common/BuildVer.h index eaa77d26..69ea0b06 100644 --- a/include/Common/BuildVer.h +++ b/include/Common/BuildVer.h @@ -1,7 +1,7 @@ #pragma once #define VER_PRODUCTBUILD 560 -#define VER_PRODUCTVERSION_STR_W L"DEV (unknown version)" +#define VER_PRODUCTVERSION_STR_W L"1.0.0.0" #define VER_FILEVERSION_STR_W VER_PRODUCTVERSION_STR_W #define VER_BRANCHVERSION_STR_W L"UNKNOWN BRANCH" #define VER_NETWORK VER_PRODUCTBUILD