Forza Horizon 2 Progress + Fixes

This commit is contained in:
Rodrigo Todescatto
2026-02-07 16:58:35 -03:00
parent d15e52a383
commit 3a40329a84
16 changed files with 734 additions and 128 deletions

View File

@@ -4,6 +4,8 @@ project(WinDurango.D3D11X VERSION 1.0.0)
set(VERSION_SUFFIX "-dev.1") # used for non-stable versions, otherwise blank
set(CMAKE_CXX_STANDARD 20)
find_package(directxtex CONFIG REQUIRED)
set(FILES
src/d3d11.x.cpp
src/IGraphicsUnknown.cpp
@@ -19,11 +21,14 @@ set(FILES
add_library(WinDurango.D3D11X SHARED ${FILES} "include/WinDurango.D3D11X/ID3D11DeviceChild.h" "src/ID3D11DeviceChild.cpp" "include/WinDurango.D3D11X/ID3D11Resource.h" "src/ID3D11Resource.cpp" "include/WinDurango.D3D11X/ID3D11Shader.h" "src/ID3D11Shader.cpp" "include/WinDurango.D3D11X/ID3D11State.h" "src/ID3D11State.cpp" "include/WinDurango.D3D11X/ID3D11View.h" "src/ID3D11View.cpp" "include/WinDurango.D3D11X/ID3D11DeviceContext.h" "src/ID3D11DeviceContext.cpp" "include/WinDurango.D3D11X/ID3D11DMAEngineContext.h" "src/ID3D11DMAEngineContext.cpp" "include/WinDurango.D3D11X/ID3D11Device.h" "src/ID3D11Device.cpp" "include/WinDurango.D3D11X/ID3D11Runtime.h" "src/dllmain.cpp" "include/WinDurango.D3D11X/IDXGIAdapter.h" "include/WinDurango.D3D11X/IDXGIDevice.h" "include/WinDurango.D3D11X/IDXGIFactory.h" "include/WinDurango.D3D11X/IDXGISwapChain.h" "src/IDXGIAdapter.cpp" "src/IDXGIDevice.cpp" "src/IDXGIFactory.cpp" "src/IDXGISwapChain.cpp" )
target_link_libraries(WinDurango.D3D11X PRIVATE WinDurango.Common)
target_link_libraries(WinDurango.D3D11X PRIVATE WinDurango.KernelX)
target_link_libraries(WinDurango.D3D11X PRIVATE Microsoft::DirectXTex)
target_include_directories(WinDurango.D3D11X PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include/WinDurango.D3D11X/
../WinDurango.Common/include/
../WinDurango.KernelX/include/WinDurango.KernelX/
${CMAKE_SOURCE_DIR}/vcpkg_installed/vcpkg/pkgs/directxtex_x64-windows/include
)
target_link_options(WinDurango.D3D11X

View File

@@ -20,7 +20,7 @@ template <abi_t ABI> struct D3D11Runtime : public ID3D11Runtime
D3D_FEATURE_LEVEL FeatureLevels[] = {D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0};
auto hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, FeatureLevels, 2, D3D11_SDK_VERSION,
auto hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_DEBUG, FeatureLevels, 2, D3D11_SDK_VERSION,
&pDevice, nullptr, &pContext);
if (FAILED(hr))

View File

@@ -12,6 +12,7 @@
#include <mutex>
#include <algorithm>
#include <cstdint>
#include <DirectXTex.h>
// We use that to know the OS version.
abi_t g_ABI{};
@@ -72,99 +73,12 @@ void GetCombaseVersion()
g_ABI.Revision = revision;
}
inline bool IsBlockCompressed(DXGI_FORMAT format)
{
switch (format)
{
case DXGI_FORMAT_BC1_TYPELESS:
case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC2_TYPELESS:
case DXGI_FORMAT_BC2_UNORM:
case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_TYPELESS:
case DXGI_FORMAT_BC3_UNORM:
case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT_BC4_SNORM:
case DXGI_FORMAT_BC5_TYPELESS:
case DXGI_FORMAT_BC5_UNORM:
case DXGI_FORMAT_BC5_SNORM:
case DXGI_FORMAT_BC6H_TYPELESS:
case DXGI_FORMAT_BC6H_UF16:
case DXGI_FORMAT_BC6H_SF16:
case DXGI_FORMAT_BC7_TYPELESS:
case DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT_BC7_UNORM_SRGB:
return true;
default:
return false;
}
}
inline uint32_t BytesPerBlock(DXGI_FORMAT format)
{
switch (format)
{
case DXGI_FORMAT_BC1_TYPELESS:
case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT_BC4_SNORM:
return 8;
default:
return 16;
}
}
inline uint32_t BytesPerPixel(DXGI_FORMAT format)
{
switch (format)
{
case DXGI_FORMAT_R8_UNORM:
return 1;
case DXGI_FORMAT_R8G8_UNORM:
return 2;
case DXGI_FORMAT_R8G8B8A8_UNORM:
return 4;
case DXGI_FORMAT_B8G8R8A8_UNORM:
return 4;
case DXGI_FORMAT_R16_FLOAT:
return 2;
case DXGI_FORMAT_R16G16_FLOAT:
return 4;
case DXGI_FORMAT_R16G16B16A16_FLOAT:
return 8;
case DXGI_FORMAT_R32_FLOAT:
return 4;
case DXGI_FORMAT_R32G32_FLOAT:
return 8;
case DXGI_FORMAT_R32G32B32A32_FLOAT:
return 16;
default:
return 0;
}
}
inline void CalculatePitch(uint32_t Width, uint32_t Height, DXGI_FORMAT Format, uint32_t* pRowPitch, uint32_t* pSlicePitch)
{;
if (IsBlockCompressed(Format))
{
uint32_t BlocksWide = (Width + 3) / 4;
uint32_t BlocksHigh = (Height + 3) / 4;
uint32_t BlockSize = BytesPerBlock(Format);
SIZE_T rowPitch = 0;
SIZE_T slicePitch = 0;
DirectX::ComputePitch(Format, Width, Height, rowPitch, slicePitch);
(*pRowPitch) = BlocksWide * BlockSize;
(*pSlicePitch) = (*pRowPitch) * BlocksHigh;
}
else
{
uint32_t bpp = BytesPerPixel(Format);
(*pRowPitch) = Width * bpp;
(*pSlicePitch) = (*pRowPitch) * Height;
}
(*pRowPitch) = rowPitch;
(*pSlicePitch) = slicePitch;
}

View File

@@ -899,6 +899,13 @@ HRESULT D3D11DeviceX<ABI>::CreatePlacementTexture2D(D3D11_TEXTURE2D_DESC const *
initialData.clear();
return hr;
}
else if (pVirtualAddress && pDesc2.SampleDesc.Count > 1)
{
HRESULT hr = CreateTexture2D(&pDesc2, 0, ppTexture2D);
(*ppTexture2D)->m_pAllocationStart = pVirtualAddress;
initialData.clear();
return hr;
}
else
{
HRESULT hr = CreateTexture2D(&pDesc2, initialData.data(), ppTexture2D);
@@ -982,7 +989,7 @@ HRESULT D3D11DeviceX<ABI>::CreateDeferredContextX(UINT Flags, gfx::ID3D11DeviceC
template <abi_t ABI> void D3D11DeviceX<ABI>::GarbageCollect(UINT Flags)
{
IMPLEMENT_STUB();
}
template <abi_t ABI>

View File

@@ -1838,7 +1838,7 @@ template <abi_t ABI>
void D3D11DeviceContextX<ABI>::PSSetPlacementConstantBuffer(UINT Slot, gfx::ID3D11Buffer<ABI> *pConstantBuffer,
void *pBaseAddress)
{
IMPLEMENT_STUB();
PSSetConstantBuffers(Slot, 1, &pConstantBuffer);
}
template <abi_t ABI>
@@ -1846,14 +1846,14 @@ void D3D11DeviceContextX<ABI>::PSSetPlacementShaderResource(UINT Slot,
gfx::ID3D11ShaderResourceView<ABI> *pShaderResourceView,
void *pBaseAddress)
{
IMPLEMENT_STUB();
PSSetShaderResources(Slot, 1, &pShaderResourceView);
}
template <abi_t ABI>
void D3D11DeviceContextX<ABI>::VSSetPlacementConstantBuffer(UINT Slot, gfx::ID3D11Buffer<ABI> *pConstantBuffer,
void *pBaseAddress)
{
IMPLEMENT_STUB();
VSSetConstantBuffers(Slot, 1, &pConstantBuffer);
}
template <abi_t ABI>
@@ -1861,14 +1861,14 @@ void D3D11DeviceContextX<ABI>::VSSetPlacementShaderResource(UINT Slot,
gfx::ID3D11ShaderResourceView<ABI> *pShaderResourceView,
void *pBaseAddress)
{
IMPLEMENT_STUB();
VSSetShaderResources(Slot, 1, &pShaderResourceView);
}
template <abi_t ABI>
void D3D11DeviceContextX<ABI>::GSSetPlacementConstantBuffer(UINT Slot, gfx::ID3D11Buffer<ABI> *pConstantBuffer,
void *pBaseAddress)
{
IMPLEMENT_STUB();
GSSetConstantBuffers(Slot, 1, &pConstantBuffer);
}
template <abi_t ABI>
@@ -1876,14 +1876,14 @@ void D3D11DeviceContextX<ABI>::GSSetPlacementShaderResource(UINT Slot,
gfx::ID3D11ShaderResourceView<ABI> *pShaderResourceView,
void *pBaseAddress)
{
IMPLEMENT_STUB();
GSSetShaderResources(Slot, 1, &pShaderResourceView);
}
template <abi_t ABI>
void D3D11DeviceContextX<ABI>::CSSetPlacementConstantBuffer(UINT Slot, gfx::ID3D11Buffer<ABI> *pConstantBuffer,
void *pBaseAddress)
{
IMPLEMENT_STUB();
CSSetConstantBuffers(Slot, 1, &pConstantBuffer);
}
template <abi_t ABI>
@@ -1891,14 +1891,14 @@ void D3D11DeviceContextX<ABI>::CSSetPlacementShaderResource(UINT Slot,
gfx::ID3D11ShaderResourceView<ABI> *pShaderResourceView,
void *pBaseAddress)
{
IMPLEMENT_STUB();
CSSetShaderResources(Slot, 1, &pShaderResourceView);
}
template <abi_t ABI>
void D3D11DeviceContextX<ABI>::HSSetPlacementConstantBuffer(UINT Slot, gfx::ID3D11Buffer<ABI> *pConstantBuffer,
void *pBaseAddress)
{
IMPLEMENT_STUB();
HSSetConstantBuffers(Slot, 1, &pConstantBuffer);
}
template <abi_t ABI>
@@ -1906,14 +1906,14 @@ void D3D11DeviceContextX<ABI>::HSSetPlacementShaderResource(UINT Slot,
gfx::ID3D11ShaderResourceView<ABI> *pShaderResourceView,
void *pBaseAddress)
{
IMPLEMENT_STUB();
HSSetShaderResources(Slot, 1, &pShaderResourceView);
}
template <abi_t ABI>
void D3D11DeviceContextX<ABI>::DSSetPlacementConstantBuffer(UINT Slot, gfx::ID3D11Buffer<ABI> *pConstantBuffer,
void *pBaseAddress)
{
IMPLEMENT_STUB();
DSSetConstantBuffers(Slot, 1, &pConstantBuffer);
}
template <abi_t ABI>
@@ -1921,21 +1921,24 @@ void D3D11DeviceContextX<ABI>::DSSetPlacementShaderResource(UINT Slot,
gfx::ID3D11ShaderResourceView<ABI> *pShaderResourceView,
void *pBaseAddress)
{
IMPLEMENT_STUB();
DSSetShaderResources(Slot, 1, &pShaderResourceView);
}
template <abi_t ABI>
void D3D11DeviceContextX<ABI>::IASetPlacementVertexBuffer(UINT Slot, gfx::ID3D11Buffer<ABI> *pVertexBuffer,
void *pBaseAddress, UINT Stride)
{
IMPLEMENT_STUB();
UINT Offset = 0;
IASetVertexBuffers(Slot, 1, &pVertexBuffer, &Stride, &Offset);
}
template <abi_t ABI>
void D3D11DeviceContextX<ABI>::IASetPlacementIndexBuffer(UINT HardwareIndexFormat, gfx::ID3D11Buffer<ABI> *pIndexBuffer,
void *pBaseAddress)
{
IMPLEMENT_STUB();
HardwareIndexFormat = HardwareIndexFormat != 0 ? DXGI_FORMAT_R32_UINT : DXGI_FORMAT_R16_UINT;
IASetIndexBuffer(pIndexBuffer, HardwareIndexFormat, 0);
}
template <abi_t ABI>
@@ -2026,7 +2029,255 @@ template <abi_t ABI> UINT32 *D3D11DeviceContextX<ABI>::MakeCeSpace()
template <abi_t ABI> void D3D11DeviceContextX<ABI>::SetFastResources_Debug(UINT *pTableStart, UINT *pTableEnd)
{
IMPLEMENT_STUB();
UINT v10 = 0;
UINT v11 = 0;
UINT v12 = 0;
UINT v14 = 0;
UINT v16 = 0;
UINT v17 = 0;
UINT64 v23 = 0;
UINT64 v26 = 0;
int v27 = 0;
int v51 = 0;
UINT64 BaseAddress = 0;
UINT64 BaseAddress2 = 0;
UINT64 *ResourcePtr{};
UINT v13 = 0;
UINT Slot = 0;
UINT64 Stride = 0;
UINT D3D11X_SET_FAST_VALUE = 0;
gfx::ID3D11ShaderResourceView<ABI> *SRV{};
gfx::ID3D11Buffer<ABI> *Buffer{};
gfx::ID3D11SamplerState<ABI> *Sampler{};
for (bool i = pTableStart < pTableEnd; i; i = pTableStart < pTableEnd)
{
v10 = *pTableStart++;
v11 = (*((WORD *)&(v10) + 0)) & 0x7FF;
v12 = (UINT8)v10;
v16 = (v10 >> 28) & 7;
v13 = v10 >> 8;
Slot = (UINT8)v13;
v14 = (v10 >> 27) & 1;
v17 = v10 >> 31;
D3D11X_SET_FAST_VALUE = ((BYTE *)&v10)[2] & 0xF;
if (v12)
{
while (1)
{
v23 = *((UINT64 *)pTableStart + 1);
ResourcePtr = *(UINT64 **)pTableStart;
pTableStart += 4;
Stride = (v23 >> 48) & 0xFFFF;
UINT64 Offset = v23 & 0x0000FFFFFFFFFFFF;
v26 = v23 - ((UINT64)(UINT)Stride << 48);
if (ResourcePtr)
{
if (!v16)
{
v27 = v11 & 0x100;
if (v17 == 1)
{
if (!v14)
{
if ((v11 & 0x40) != 0)
{
break;
}
BaseAddress = Offset;
if (v27)
{
switch (D3D11X_SET_FAST_VALUE)
{
case 0:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
VSSetPlacementShaderResource(Slot, SRV, (void *)BaseAddress);
break;
case 1:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
HSSetPlacementShaderResource(Slot, SRV, (void *)BaseAddress);
break;
case 2:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
DSSetPlacementShaderResource(Slot, SRV, (void *)BaseAddress);
break;
case 3:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
GSSetPlacementShaderResource(Slot, SRV, (void *)BaseAddress);
break;
case 4:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
PSSetPlacementShaderResource(Slot, SRV, (void *)BaseAddress);
break;
case 5:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
CSSetPlacementShaderResource(Slot, SRV, (void *)BaseAddress);
break;
}
}
else
{
switch (D3D11X_SET_FAST_VALUE)
{
case 0:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
VSSetFastShaderResource(Slot, SRV);
break;
case 1:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
HSSetFastShaderResource(Slot, SRV);
break;
case 2:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
DSSetFastShaderResource(Slot, SRV);
break;
case 3:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
GSSetFastShaderResource(Slot, SRV);
break;
case 4:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
PSSetFastShaderResource(Slot, SRV);
break;
case 5:
SRV = reinterpret_cast<gfx::ID3D11ShaderResourceView<ABI> *>(ResourcePtr);
CSSetFastShaderResource(Slot, SRV);
break;
}
}
}
}
return;
}
if (v16 == 1)
{
if (v17 == 1)
{
v51 = v11 & 0x100;
BaseAddress2 = Offset;
if (v51)
{
if (D3D11X_SET_FAST_VALUE)
{
switch (D3D11X_SET_FAST_VALUE)
{
case 1:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
HSSetPlacementConstantBuffer(Slot, Buffer, (void *)BaseAddress2);
break;
case 2:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
DSSetPlacementConstantBuffer(Slot, Buffer, (void *)BaseAddress2);
break;
case 3:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
GSSetPlacementConstantBuffer(Slot, Buffer, (void *)BaseAddress2);
break;
case 4:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
PSSetPlacementConstantBuffer(Slot, Buffer, (void *)BaseAddress2);
break;
case 5:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
CSSetPlacementConstantBuffer(Slot, Buffer, (void *)BaseAddress2);
break;
case 6:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
IASetPlacementVertexBuffer(Slot, Buffer, (void *)BaseAddress2, Stride);
break;
}
}
else
{
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
VSSetPlacementConstantBuffer(Slot, Buffer, (void *)BaseAddress2);
}
}
else if (D3D11X_SET_FAST_VALUE)
{
switch (D3D11X_SET_FAST_VALUE)
{
case 1:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
HSSetFastConstantBuffer(Slot, Buffer);
break;
case 2:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
DSSetFastConstantBuffer(Slot, Buffer);
break;
case 3:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
GSSetFastConstantBuffer(Slot, Buffer);
break;
case 4:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
PSSetFastConstantBuffer(Slot, Buffer);
break;
case 5:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
CSSetFastConstantBuffer(Slot, Buffer);
break;
case 6:
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
IASetFastVertexBuffer(Slot, Buffer, Stride);
break;
}
}
else
{
Buffer = reinterpret_cast<gfx::ID3D11Buffer<ABI> *>(ResourcePtr);
VSSetFastConstantBuffer(Slot, Buffer);
}
}
return;
}
if (v17 == 1)
{
if (D3D11X_SET_FAST_VALUE)
{
switch (D3D11X_SET_FAST_VALUE)
{
case 1:
Sampler = reinterpret_cast<gfx::ID3D11SamplerState<ABI> *>(ResourcePtr);
HSSetFastSampler(Slot, Sampler);
break;
case 2:
Sampler = reinterpret_cast<gfx::ID3D11SamplerState<ABI> *>(ResourcePtr);
DSSetFastSampler(Slot, Sampler);
break;
case 3:
Sampler = reinterpret_cast<gfx::ID3D11SamplerState<ABI> *>(ResourcePtr);
GSSetFastSampler(Slot, Sampler);
break;
case 4:
Sampler = reinterpret_cast<gfx::ID3D11SamplerState<ABI> *>(ResourcePtr);
PSSetFastSampler(Slot, Sampler);
break;
case 5:
Sampler = reinterpret_cast<gfx::ID3D11SamplerState<ABI> *>(ResourcePtr);
CSSetFastSampler(Slot, Sampler);
break;
}
}
else
{
Sampler = reinterpret_cast<gfx::ID3D11SamplerState<ABI> *>(ResourcePtr);
VSSetFastSampler(Slot, Sampler);
}
return;
}
}
return;
}
}
}
}
template <abi_t ABI> void D3D11DeviceContextX<ABI>::BeginResourceBatch(void *pBuffer, UINT BufferSize)

View File

@@ -146,9 +146,6 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
return DefWindowProc(hWnd, msg, wParam, lParam);
}
IDXGISwapChain1 *OldSwapChain{};
winrt::hstring GamePackage = winrt::Windows::ApplicationModel::Package::Current().Id().FamilyName();
template <abi_t ABI>
HRESULT DXGIFactory2<ABI>::CreateSwapChainForCoreWindow(xbox::IGraphicsUnknown<ABI> *pDevice, IUnknown *pWindow,
DXGI_SWAP_CHAIN_DESC1 *pDesc, IDXGIOutput *pRestrictToOutput,

View File

@@ -2,6 +2,7 @@
#include "IIDExports.h"
#include "d3d11_x.g.h"
#include "ID3D11Runtime.h"
#include "kernelx.h"
EXTERN_C HRESULT __stdcall EraD3D10CreateBlob()
{
@@ -77,8 +78,14 @@ EXTERN_C HRESULT __stdcall D3DFreeGraphicsMemory(void *pAddress)
EXTERN_C HRESULT __stdcall D3DMapEsramMemory(UINT Flags, void *pVirtualAddress, UINT NumPages, const UINT *pPageList)
{
IMPLEMENT_STUB();
return E_NOTIMPL;
DWORD flAllocationType = 0;
if ((Flags & 1) != 0)
flAllocationType = MEM_LARGE_PAGES;
else if ((Flags & 2) != 0)
flAllocationType = MEM_4MB_PAGES;
return MapTitleEsramPages(pVirtualAddress, NumPages, flAllocationType, pPageList);
}
struct DXGIX_FRAME_STATISTICS

View File

@@ -15,7 +15,7 @@ set(FILES
include/WinDurango.KernelX/Hooks.h
)
add_library(WinDurango.KernelX SHARED ${FILES} "include/WinDurango.KernelX/EraCoreApplication.h" "src/EraCoreApplication.cpp" "include/WinDurango.KernelX/EraCoreWindow.h" "src/EraCoreWindow.cpp")
add_library(WinDurango.KernelX SHARED ${FILES} "include/WinDurango.KernelX/EraCoreApplication.h" "src/EraCoreApplication.cpp" "include/WinDurango.KernelX/EraCoreWindow.h" "src/EraCoreWindow.cpp" "include/WinDurango.KernelX/MMDeviceEnumerator.h" "src/MMDeviceEnumerator.cpp")
target_link_libraries(WinDurango.KernelX PRIVATE
WinDurango.Common

View File

@@ -67,8 +67,20 @@ typedef HWND(WINAPI *PCreateWindowInBandEx)(DWORD dwExStyle, LPCWSTR lpClassName
int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu,
HINSTANCE hInstance, LPVOID lpParam, DWORD dwBand, DWORD dwTypeFlags);
static PCreateWindowInBandEx TrueCreateWindowInBandEx = 0;
PCreateWindowInBandEx TrueCreateWindowInBandEx = 0;
HWND WINAPI EraCreateWindowInBandEx(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int x,
int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
LPVOID lpParam, DWORD dwBand, DWORD dwTypeFlags);
LPVOID lpParam, DWORD dwBand, DWORD dwTypeFlags);
HRESULT(WINAPI *TrueCoCreateInstance)(_In_ REFCLSID rclsid, _In_opt_ LPUNKNOWN pUnkOuter, _In_ DWORD dwClsContext,
_In_ REFIID riid,
_COM_Outptr_ _At_(*ppv, _Post_readable_size_(_Inexpressible_(varies)))
LPVOID FAR *ppv) = CoCreateInstance;
HRESULT __stdcall EraCoCreateInstance(_In_ REFCLSID rclsid, _In_opt_ LPUNKNOWN pUnkOuter, _In_ DWORD dwClsContext,
_In_ REFIID riid,
_COM_Outptr_ _At_(*ppv, _Post_readable_size_(_Inexpressible_(varies)))
LPVOID FAR *ppv);
HMODULE User32 = nullptr;

View File

@@ -361,7 +361,7 @@ EXTERN_C BOOL __stdcall EraDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode
{
if (hDevice != LoganHandle)
{
return DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize,
return TrueDeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize,
lpBytesReturned, lpOverlapped);
}
else

View File

@@ -0,0 +1,111 @@
#pragma once
#include <mmdeviceapi.h>
class MMDeviceEnumeratorWrapper : IMMDeviceEnumerator
{
public:
MMDeviceEnumeratorWrapper(IMMDeviceEnumerator *realEnumerator) : m_realEnumerator(realEnumerator)
{
AddRef();
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject) override;
ULONG STDMETHODCALLTYPE AddRef(void) override;
ULONG STDMETHODCALLTYPE Release(void) override;
/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE EnumAudioEndpoints(
/* [annotation][in] */
_In_ EDataFlow dataFlow,
/* [annotation][in] */
_In_ DWORD dwStateMask,
/* [annotation][out] */
_Out_ IMMDeviceCollection **ppDevices) override;
/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetDefaultAudioEndpoint(
/* [annotation][in] */
_In_ EDataFlow dataFlow,
/* [annotation][in] */
_In_ ERole role,
/* [annotation][out] */
_Out_ IMMDevice **ppEndpoint) override;
/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetDevice(
/* [annotation][in] */
_In_ LPCWSTR pwstrId,
/* [annotation][out] */
_Out_ IMMDevice **ppDevice) override;
/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE RegisterEndpointNotificationCallback(
/* [annotation][in] */
_In_ IMMNotificationClient *pClient) override;
/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE UnregisterEndpointNotificationCallback(
/* [annotation][in] */
_In_ IMMNotificationClient *pClient) override;
IMMDeviceEnumerator *m_realEnumerator;
ULONG m_RefCount = 0;
};
MIDL_INTERFACE("8B557ADC-555E-47A0-B223-43477E481DAD")
IMMXboxDeviceEnumerator : public IMMDeviceEnumerator
{
public:
virtual HRESULT STDMETHODCALLTYPE GetHdAudioChannelCounts(UINT * pHdmiChannelCount, UINT * pSpdifChannelCount) = 0;
virtual HRESULT STDMETHODCALLTYPE RegisterChannelCountNotificationCallback(UINT * pClient) = 0;
virtual HRESULT STDMETHODCALLTYPE UnregisterChannelCountNotificationCallback(UINT * pClient) = 0;
};
class MMXboxDeviceEnumerator : IMMXboxDeviceEnumerator
{
public:
MMXboxDeviceEnumerator(IMMDeviceEnumerator *realEnumerator) : m_realEnumerator(realEnumerator)
{
AddRef();
}
HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject) override;
ULONG STDMETHODCALLTYPE AddRef(void) override;
ULONG STDMETHODCALLTYPE Release(void) override;
/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE EnumAudioEndpoints(
/* [annotation][in] */
_In_ EDataFlow dataFlow,
/* [annotation][in] */
_In_ DWORD dwStateMask,
/* [annotation][out] */
_Out_ IMMDeviceCollection **ppDevices) override;
/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetDefaultAudioEndpoint(
/* [annotation][in] */
_In_ EDataFlow dataFlow,
/* [annotation][in] */
_In_ ERole role,
/* [annotation][out] */
_Out_ IMMDevice **ppEndpoint) override;
/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetDevice(
/* [annotation][in] */
_In_ LPCWSTR pwstrId,
/* [annotation][out] */
_Out_ IMMDevice **ppDevice) override;
/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE RegisterEndpointNotificationCallback(
/* [annotation][in] */
_In_ IMMNotificationClient *pClient) override;
/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE UnregisterEndpointNotificationCallback(
/* [annotation][in] */
_In_ IMMNotificationClient *pClient) override;
HRESULT __stdcall GetHdAudioChannelCounts(UINT *pHdmiChannelCount, UINT *pSpdifChannelCount) override;
HRESULT __stdcall RegisterChannelCountNotificationCallback(UINT *pClient) override;
HRESULT __stdcall UnregisterChannelCountNotificationCallback(UINT *pClient) override;
IMMDeviceEnumerator *m_realEnumerator;
ULONG m_RefCount = 0;
};

View File

@@ -4,6 +4,7 @@
#include "CurrentApp.h"
#include <detours.h>
#include <wrl.h>
#include "MMDeviceEnumerator.h"
using namespace ABI::Windows::ApplicationModel::Store;
@@ -161,6 +162,22 @@ HWND __stdcall EraCreateWindowInBandEx(DWORD dwExStyle, LPCWSTR lpClassName, LPC
hMenu, hInstance, lpParam, dwBand, dwTypeFlags);
}
HRESULT __stdcall EraCoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid,
LPVOID *ppv)
{
HRESULT hr = TrueCoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, ppv);
if (FAILED(hr))
return hr;
if (riid == __uuidof(IMMDeviceEnumerator))
{
*ppv = new MMDeviceEnumeratorWrapper(static_cast<IMMDeviceEnumerator*>(*ppv));
return S_OK;
}
return hr;
}
inline HRESULT WINAPI EraRoGetActivationFactory(HSTRING classId, REFIID iid, void **factory)

View File

@@ -0,0 +1,121 @@
#include "MMDeviceEnumerator.h"
// MMDeviceEnumerator
HRESULT __stdcall MMDeviceEnumeratorWrapper::QueryInterface(REFIID riid, void **ppvObject)
{
if (riid == __uuidof(IMMXboxDeviceEnumerator))
{
*ppvObject = new MMXboxDeviceEnumerator(this);
AddRef();
return S_OK;
}
return m_realEnumerator->QueryInterface(riid, ppvObject);
}
ULONG __stdcall MMDeviceEnumeratorWrapper::AddRef(void)
{
m_realEnumerator->AddRef();
return InterlockedIncrement(&m_RefCount);
}
ULONG __stdcall MMDeviceEnumeratorWrapper::Release(void)
{
m_realEnumerator->Release();
ULONG RefCount = InterlockedDecrement(&m_RefCount);
if (!RefCount)
delete this;
return RefCount;
}
HRESULT __stdcall MMDeviceEnumeratorWrapper::EnumAudioEndpoints(EDataFlow dataFlow, DWORD dwStateMask,
IMMDeviceCollection **ppDevices)
{
return m_realEnumerator->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
}
HRESULT __stdcall MMDeviceEnumeratorWrapper::GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role,
IMMDevice **ppEndpoint)
{
return m_realEnumerator->GetDefaultAudioEndpoint(dataFlow, role, ppEndpoint);
}
HRESULT __stdcall MMDeviceEnumeratorWrapper::GetDevice(LPCWSTR pwstrId, IMMDevice **ppDevice)
{
return m_realEnumerator->GetDevice(pwstrId, ppDevice);
}
HRESULT __stdcall MMDeviceEnumeratorWrapper::RegisterEndpointNotificationCallback(IMMNotificationClient *pClient)
{
return m_realEnumerator->RegisterEndpointNotificationCallback(pClient);
}
HRESULT __stdcall MMDeviceEnumeratorWrapper::UnregisterEndpointNotificationCallback(IMMNotificationClient *pClient)
{
return m_realEnumerator->UnregisterEndpointNotificationCallback(pClient);
}
HRESULT __stdcall MMXboxDeviceEnumerator::QueryInterface(REFIID riid, void **ppvObject)
{
return m_realEnumerator->QueryInterface(riid, ppvObject);
}
// MMXboxDeviceEnumerator
ULONG __stdcall MMXboxDeviceEnumerator::AddRef(void)
{
m_realEnumerator->AddRef();
return InterlockedIncrement(&m_RefCount);
}
ULONG __stdcall MMXboxDeviceEnumerator::Release(void)
{
m_realEnumerator->Release();
ULONG RefCount = InterlockedDecrement(&m_RefCount);
if (!RefCount)
delete this;
return RefCount;
}
HRESULT __stdcall MMXboxDeviceEnumerator::EnumAudioEndpoints(EDataFlow dataFlow, DWORD dwStateMask,
IMMDeviceCollection **ppDevices)
{
return m_realEnumerator->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
}
HRESULT __stdcall MMXboxDeviceEnumerator::GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role,
IMMDevice **ppEndpoint)
{
return m_realEnumerator->GetDefaultAudioEndpoint(dataFlow, role, ppEndpoint);
}
HRESULT __stdcall MMXboxDeviceEnumerator::GetDevice(LPCWSTR pwstrId, IMMDevice **ppDevice)
{
return m_realEnumerator->GetDevice(pwstrId, ppDevice);
}
HRESULT __stdcall MMXboxDeviceEnumerator::RegisterEndpointNotificationCallback(IMMNotificationClient *pClient)
{
return m_realEnumerator->RegisterEndpointNotificationCallback(pClient);
}
HRESULT __stdcall MMXboxDeviceEnumerator::UnregisterEndpointNotificationCallback(IMMNotificationClient *pClient)
{
return m_realEnumerator->UnregisterEndpointNotificationCallback(pClient);
}
HRESULT __stdcall MMXboxDeviceEnumerator::GetHdAudioChannelCounts(UINT *pHdmiChannelCount, UINT *pSpdifChannelCount)
{
*pHdmiChannelCount = 8;
*pSpdifChannelCount = 2;
return S_OK;
}
HRESULT __stdcall MMXboxDeviceEnumerator::RegisterChannelCountNotificationCallback(UINT *pClient)
{
return S_OK;
}
HRESULT __stdcall MMXboxDeviceEnumerator::UnregisterChannelCountNotificationCallback(UINT *pClient)
{
return S_OK;
}

View File

@@ -1,5 +1,6 @@
#include "Hooks.h"
#include "kernelx.h"
#include "Logan.h"
static DWORD ReasonForCall = 0;
@@ -18,23 +19,41 @@ void KernelxInitialize(HINSTANCE hinstDLL)
if (ReasonForCall == DLL_PROCESS_ATTACH || ReasonForCall == DLL_THREAD_ATTACH)
{
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
XWinePatchImport(GetModuleHandleW(nullptr), GetRuntimeModule(),
"?GetActivationFactoryByPCWSTR@@YAJPEAXAEAVGuid@Platform@@PEAPEAX@Z",
GetActivationFactoryRedirect);
}
HMODULE User32 = LoadLibraryA("user32.dll");
if (User32)
//This is needed for DXVK.
if (!User32) User32 = LoadLibraryA("user32.dll");
if (User32)
{
FARPROC CreateWindowInBandEx = GetProcAddress(User32, "CreateWindowInBandEx");
TrueCreateWindowInBandEx = reinterpret_cast<PCreateWindowInBandEx>(CreateWindowInBandEx);
DetourAttach(&reinterpret_cast<PVOID &>(TrueCreateWindowInBandEx), EraCreateWindowInBandEx);
}
DetourAttach(&reinterpret_cast<PVOID &>(TrueCoCreateInstance), EraCoCreateInstance);
DetourAttach(&reinterpret_cast<PVOID &>(TrueDeviceIoControl), EraDeviceIoControl);
DetourTransactionCommit();
}
else if (ReasonForCall == DLL_PROCESS_DETACH || ReasonForCall == DLL_THREAD_DETACH)
{
FARPROC CreateWindowInBandEx = GetProcAddress(User32, "CreateWindowInBandEx");
TrueCreateWindowInBandEx = reinterpret_cast<PCreateWindowInBandEx>(CreateWindowInBandEx);
DetourAttach(&reinterpret_cast<PVOID &>(TrueCreateWindowInBandEx), EraCreateWindowInBandEx);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&reinterpret_cast<PVOID &>(TrueCreateWindowInBandEx), EraCreateWindowInBandEx);
DetourDetach(&reinterpret_cast<PVOID &>(TrueCoCreateInstance), EraCoCreateInstance);
DetourDetach(&reinterpret_cast<PVOID &>(TrueDeviceIoControl), EraDeviceIoControl);
DetourTransactionCommit();
}
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
InitializeCriticalSection(&XmpAllocationHookLock);
if (DetourIsHelperProcess()) return TRUE;
ReasonForCall = ul_reason_for_call;

View File

@@ -1,5 +1,8 @@
#include "kernelx.h"
#include "Logan.h"
#include <atlbase.h>
#include "Hooks.h"
EXTERN_C CONSOLE_TYPE __stdcall GetConsoleType()
{
return CONSOLE_TYPE_XBOX_ONE;
@@ -695,6 +698,65 @@ EXTERN_C void __stdcall XMemReleaseAuxiliaryTitleMemory(_In_opt_ HANDLE hHandle)
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
}
void FixRelativePath(LPCWSTR &lpFileName)
{
static std::wstring convert{};
std::wstring_view fileName(lpFileName);
if (fileName.size() == 0)
return;
int length = fileName.length();
if (fileName[0] == '.' && fileName[1] == '\\')
{
static std::wstring trimPath{};
trimPath = fileName.substr(2);
fileName = trimPath.data();
convert = std::filesystem::current_path().c_str();
if (length != '\\')
{
convert.append(L"\\");
}
convert.append(fileName);
lpFileName = convert.data();
return;
}
if (fileName[1] != ':')
{
convert = std::filesystem::current_path().c_str();
convert.append(L"\\");
convert.append(fileName);
lpFileName = convert.data();
}
else if ((fileName[0] == 'G' || fileName[0] == 'g') && fileName[1] == ':')
{
static std::wstring trimPath{};
trimPath = fileName.substr(2);
fileName = trimPath.data();
convert = std::filesystem::current_path().c_str();
convert.append(fileName);
lpFileName = convert.data();
}
else if ((fileName[0] == 'T' || fileName[0] == 't') && fileName[1] == ':')
{
static std::wstring trimPath{};
trimPath = fileName.substr(2);
fileName = trimPath.data();
convert = winrt::Windows::Storage::ApplicationData::Current().TemporaryFolder().Path();
convert.append(fileName);
lpFileName = convert.data();
}
}
EXTERN_C HANDLE __stdcall EraCreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
@@ -711,6 +773,37 @@ EXTERN_C HANDLE __stdcall EraCreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAcce
return LoganHandle;
}
static std::wstring convert{};
std::wstring_view fileName(lpFileName);
int length = fileName.length();
if (fileName[0] == '/')
{
static std::wstring trimPath{};
trimPath = fileName.substr(1);
fileName = trimPath.data();
convert = std::filesystem::current_path().c_str();
convert.append(L"\\");
convert.append(fileName);
lpFileName = convert.data();
}
else if (fileName[0] == '\\')
{
}
else if (fileName[length - 1] == '/')
{
convert = std::filesystem::current_path().c_str();
convert.append(L"\\");
convert.append(lpFileName);
std::replace(convert.begin(), convert.end(), L'/', L'\\');
lpFileName = convert.c_str();
}
else
{
FixRelativePath(lpFileName);
}
return CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition,
dwFlagsAndAttributes, hTemplateFile);
}
@@ -719,6 +812,10 @@ EXTERN_C HANDLE __stdcall EraCreateFileA(LPCSTR lpFileName, DWORD dwDesiredAcces
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
{
USES_CONVERSION;
LPCWSTR FileName = A2W(lpFileName);
FixRelativePath(FileName);
return CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition,
dwFlagsAndAttributes, hTemplateFile);
}
@@ -726,47 +823,94 @@ EXTERN_C HANDLE __stdcall EraCreateFileA(LPCSTR lpFileName, DWORD dwDesiredAcces
EXTERN_C HANDLE __stdcall EraCreateFile2(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams)
{
FixRelativePath(lpFileName);
return CreateFile2(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, pCreateExParams);
}
EXTERN_C BOOL __stdcall EraCreateDirectoryA(LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes)
{
return CreateDirectoryA(lpPathName, lpSecurityAttributes);
USES_CONVERSION;
LPCWSTR PathName = A2W(lpPathName);
FixRelativePath(PathName);
return CreateDirectoryW(PathName, lpSecurityAttributes);
}
EXTERN_C HMODULE __stdcall EraLoadLibraryExA(LPCSTR lpLibFileName, _Reserved_ HANDLE hFile, _In_ DWORD dwFlags)
{
return LoadLibraryExA(lpLibFileName, hFile, dwFlags);
USES_CONVERSION;
LPCWSTR LibName = A2W(lpLibFileName);
static std::wstring convert{};
std::wstring_view fileName(LibName);
if (fileName.size() != 0 && fileName[0] == 'G' && fileName[1] == ':')
{
static std::wstring trimPath{};
trimPath = fileName.substr(2);
fileName = trimPath.data();
convert = std::filesystem::current_path().c_str();
convert.append(fileName);
}
HMODULE result = LoadLibraryExW(LibName, hFile, dwFlags);
PatchNeededImports(result, GetRuntimeModule(), "?GetActivationFactoryByPCWSTR@@YAJPEAXAEAVGuid@Platform@@PEAPEAX@Z",
GetActivationFactoryRedirect);
return result;
}
EXTERN_C HMODULE __stdcall EraLoadLibraryW(LPCWSTR lpLibFileName)
{
return LoadLibraryW(lpLibFileName);
static std::wstring convert{};
std::wstring_view fileName(lpLibFileName);
if (fileName[0] == 'G' && fileName[1] == ':' && fileName.size() != 0)
{
static std::wstring trimPath{};
trimPath = fileName.substr(2);
fileName = trimPath.data();
convert = std::filesystem::current_path().c_str();
convert.append(fileName);
lpLibFileName = convert.data();
}
HMODULE result = LoadLibraryW(lpLibFileName);
PatchNeededImports(result, GetRuntimeModule(), "?GetActivationFactoryByPCWSTR@@YAJPEAXAEAVGuid@Platform@@PEAPEAX@Z",
GetActivationFactoryRedirect);
return result;
}
EXTERN_C DWORD __stdcall EraGetFileAttributesW(LPCWSTR lpFileName)
{
FixRelativePath(lpFileName);
return GetFileAttributesW(lpFileName);
}
EXTERN_C BOOL __stdcall EraGetFileAttributesExW(LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId,
LPVOID lpFileInformation)
{
FixRelativePath(lpFileName);
return GetFileAttributesExW(lpFileName, fInfoLevelId, lpFileInformation);
}
EXTERN_C HANDLE __stdcall EraFindFirstFileW(LPCWSTR lpFileName, LPWIN32_FIND_DATAW lpFindFileData)
{
FixRelativePath(lpFileName);
return FindFirstFileW(lpFileName, lpFindFileData);
}
EXTERN_C BOOL __stdcall EraDeleteFileW(LPCWSTR lpFileName)
{
FixRelativePath(lpFileName);
return DeleteFileW(lpFileName);
}
EXTERN_C HMODULE __stdcall EraLoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)
{
FixRelativePath(lpLibFileName);
return LoadLibraryExW(lpLibFileName, hFile, dwFlags);
}

View File

@@ -3,6 +3,7 @@
"version": "1.0",
"dependencies": [
"cppwinrt",
"detours"
"detours",
"directxtex"
]
}