#include "Render/DeviceOwnerDX7.h" #include namespace { HRESULT DeviceError(unsigned code) { return static_cast(0x82000000u | code); } // OFFSET: 0x00548C00 // FUNCTION: fn_01862 DDSURFACEDESC2 SurfaceDescription(DWORD flags, DWORD caps) { DDSURFACEDESC2 result{}; result.dwSize = sizeof(result); result.dwFlags = flags; result.ddsCaps.dwCaps = caps; result.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT); return result; } // OFFSET: 0x0054AA50 // FUNCTION: fn_01900 RECT ClientRectangle(HWND window, const RECT& monitor) { RECT result; GetClientRect(window, &result); POINT point{result.left, result.top}; ClientToScreen(window, &point); result.left = point.x - monitor.left; result.top = point.y - monitor.top; point = {result.right, result.bottom}; ClientToScreen(window, &point); result.right = point.x - monitor.left; result.bottom = point.y - monitor.top; return result; } // OFFSET: 0x0054A970 // FUNCTION: fn_01899 HRESULT CALLBACK SelectDepthFormat(DDPIXELFORMAT* candidate, void* context) { auto* selected = static_cast(context); if (!candidate || !selected) { return D3DENUMRET_CANCEL; } if (candidate->dwFlags != selected->dwFlags || candidate->dwZBufferBitDepth <= selected->dwZBufferBitDepth) { return D3DENUMRET_OK; } *selected = *candidate; return candidate->dwZBufferBitDepth < 32 ? D3DENUMRET_OK : D3DENUMRET_CANCEL; } void ChannelMask(DeviceOwnerDX7::Channel& channel, DWORD mask) { channel = {0, 8}; if (!mask) { return; } while (!(mask & 1)) { ++channel.shift; mask >>= 1; } do { --channel.expansion; mask >>= 1; } while (mask); } HRESULT SurfaceError(HRESULT status, unsigned code) { return status == DDERR_OUTOFVIDEOMEMORY ? status : DeviceError(code); } } // OFFSET: 0x005493B0 // FUNCTION: fn_01888 DeviceOwnerDX7::DeviceOwnerDX7() : window(nullptr), width(0), height(0), primary(nullptr), backBuffer(nullptr), renderTarget(nullptr), depthBuffer(nullptr), draw(nullptr), direct3D(nullptr), device(nullptr), depthMemory(0), fullscreen(0), presentationOption(0), captureSurface(nullptr), red{0, 8}, green{0, 8}, blue{0, 8}, alpha{0, 8}, bitDepth(0), redMask(0), greenMask(0), blueMask(0), alphaMask(0), surfacesLost(false) { } // OFFSET: 0x005498A0 // FUNCTION: fn_01894 HRESULT DeviceOwnerDX7::CreateSurfaces(const DDSURFACEDESC2* mode, DWORD flags, const RECT& monitor) { const bool buffered = (flags & 2) != 0; DDSURFACEDESC2 description = SurfaceDescription(DDSD_CAPS, 0); HRESULT status; if (flags & 1) { SetRect(&this->source, 0, 0, mode->dwWidth, mode->dwHeight); this->width = this->source.right; this->height = this->source.bottom; this->destination = this->source; const DWORD modeFlags = this->width == 320 && this->height == 200 && mode->ddpfPixelFormat.dwRGBBitCount == 8 ? DDSDM_STANDARDVGAMODE : 0; if ( FAILED(this->draw->SetDisplayMode( this->width, this->height, mode->ddpfPixelFormat.dwRGBBitCount, mode->dwRefreshRate, modeFlags )) ) { return DeviceError(9); } description.ddsCaps.dwCaps = 0x2200; if (buffered) { description.dwFlags |= DDSD_BACKBUFFERCOUNT; description.ddsCaps.dwCaps = 0x2218; description.dwBackBufferCount = 1; } status = this->draw->CreateSurface(&description, &this->primary, nullptr); if (FAILED(status)) { return SurfaceError(status, 7); } if (buffered) { DDSCAPS2 caps{DDSCAPS_BACKBUFFER}; status = this->primary->GetAttachedSurface(&caps, &this->backBuffer); if (FAILED(status)) { return SurfaceError(status, 10); } } } else { GetClientRect(this->window, &this->source); this->width = this->source.right; this->height = this->source.bottom; this->destination = ClientRectangle(this->window, monitor); description.ddsCaps.dwCaps = buffered ? 0x200 : 0x2200; status = this->draw->CreateSurface(&description, &this->primary, nullptr); if (FAILED(status)) { return SurfaceError(status, 7); } } IDirectDrawClipper* clipper; if (FAILED(this->draw->CreateClipper(0, &clipper, nullptr))) { return DeviceError(8); } clipper->SetHWnd(0, this->window); this->primary->SetClipper(clipper); if (clipper) { clipper->Release(); } if (!(flags & 1)) { if (buffered) { description.dwWidth = this->width; description.dwHeight = this->height; description.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; description.ddsCaps.dwCaps = 0x2040; status = this->draw->CreateSurface(&description, &this->backBuffer, nullptr); if (FAILED(status)) { return SurfaceError(status, 10); } } else { ClientToScreen(this->window, reinterpret_cast(&this->source.left)); ClientToScreen(this->window, reinterpret_cast(&this->source.right)); } } this->primaryDescription = SurfaceDescription(DDSD_CAPS, 0); this->primary->GetSurfaceDesc(&this->primaryDescription); const auto& format = this->primaryDescription.ddpfPixelFormat; this->bitDepth = format.dwRGBBitCount; this->redMask = format.dwRBitMask; this->greenMask = format.dwGBitMask; this->blueMask = format.dwBBitMask; this->alphaMask = format.dwRGBAlphaBitMask; ChannelMask(this->red, this->redMask); ChannelMask(this->green, this->greenMask); ChannelMask(this->blue, this->blueMask); ChannelMask(this->alpha, this->alphaMask); this->renderTarget = buffered ? this->backBuffer : this->primary; this->renderTarget->AddRef(); description = SurfaceDescription(DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT, 0x840); description.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; description.dwWidth = this->width; description.dwHeight = this->height; status = this->draw->CreateSurface(&description, &this->captureSurface, nullptr); return FAILED(status) ? E_OUTOFMEMORY : S_OK; } // OFFSET: 0x0054A000 // FUNCTION: fn_01895 HRESULT DeviceOwnerDX7::CreateDevice( GUID* drawIdentity, const GUID* renderIdentity, const DDSURFACEDESC2* mode, DWORD flags, const RECT& monitor ) { this->depthMemory = std::memcmp(renderIdentity, &IID_IDirect3DHALDevice, sizeof(GUID)) == 0 || std::memcmp(renderIdentity, &IID_IDirect3DTnLHalDevice, sizeof(GUID)) == 0 ? DDSCAPS_VIDEOMEMORY : DDSCAPS_SYSTEMMEMORY; if (FAILED(DirectDrawCreateEx(drawIdentity, reinterpret_cast(&this->draw), IID_IDirectDraw7, nullptr))) { return DeviceError(1); } auto description = SurfaceDescription(0, 0); this->draw->GetDisplayMode(&description); this->desktopBitDepth = description.ddpfPixelFormat.dwRGBBitCount; DWORD cooperative = this->fullscreen ? 19 : 8; if (!(flags & 16)) { cooperative |= 0x800; } if (FAILED(this->draw->SetCooperativeLevel(this->window, cooperative | 0x400))) { return DeviceError(2); } const HRESULT status = this->CreateSurfaces(mode, flags, monitor); if (FAILED(status)) { return status; } if (renderIdentity) { if (FAILED(this->draw->QueryInterface(IID_IDirect3D7, reinterpret_cast(&this->direct3D)))) { return DeviceError(3); } if (FAILED(this->direct3D->CreateDevice(*renderIdentity, this->renderTarget, &this->device))) { return DeviceError(4); } D3DVIEWPORT7 viewport{0, 0, this->width, this->height, 0.0f, 1.0f}; if (FAILED(this->device->SetViewport(&viewport))) { return DeviceError(6); } this->depthFormat = {}; this->depthFormat.dwFlags = ((flags & 8) << 11) | DDPF_ZBUFFER; this->direct3D->EnumZBufferFormats(*renderIdentity, SelectDepthFormat, &this->depthFormat); // The original tests the descriptor size, not its depth-bit count. if (this->depthFormat.dwSize != 32) { return DeviceError(5); } this->device->GetCaps(&this->caps); if (!(this->caps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_ZBUFFERLESSHSR)) { description = SurfaceDescription( DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT, this->depthMemory | DDSCAPS_ZBUFFER ); description.dwWidth = this->width; description.dwHeight = this->height; description.ddpfPixelFormat = this->depthFormat; const HRESULT depthStatus = this->draw->CreateSurface(&description, &this->depthBuffer, nullptr); if (FAILED(depthStatus)) { return SurfaceError(depthStatus, 5); } if ( FAILED(this->renderTarget->AddAttachedSurface(this->depthBuffer)) || FAILED(this->device->SetRenderTarget(this->renderTarget, 0)) ) { return DeviceError(5); } } } return S_OK; } // OFFSET: 0x0054A7D0 // FUNCTION: fn_01897 HRESULT DeviceOwnerDX7::Close() { ULONG deviceReferences = 0; if (this->device) { deviceReferences = this->device->Release(); if (static_cast(deviceReferences) > 0) { do { deviceReferences = this->device->Release(); } while (deviceReferences); } } this->device = nullptr; if (!this->fullscreen && this->backBuffer) { this->backBuffer->Release(); this->backBuffer = nullptr; } if (this->depthBuffer) { this->depthBuffer->Release(); this->depthBuffer = nullptr; } if (this->renderTarget) { this->renderTarget->Release(); this->renderTarget = nullptr; } if (this->primary) { this->primary->Release(); this->primary = nullptr; } if (this->direct3D) { this->direct3D->Release(); this->direct3D = nullptr; } if (this->captureSurface) { this->captureSurface->Release(); this->captureSurface = nullptr; } ULONG drawReferences = 0; if (this->draw) { this->draw->SetCooperativeLevel(this->window, DDSCL_NORMAL); drawReferences = this->draw->Release(); } this->draw = nullptr; return drawReferences || deviceReferences ? DeviceError(11) : S_OK; } // OFFSET: 0x0054AAD0 // FUNCTION: fn_01901 HRESULT DeviceOwnerDX7::Initialize( HWND value, GUID* drawIdentity, const GUID* renderIdentity, const DDSURFACEDESC2* mode, DWORD flags, HWND applicationWindow, const RECT& monitor ) { this->window = value; if (!value || !renderIdentity || (!mode && (flags & 1))) { return E_INVALIDARG; } this->fullscreen = flags & 1; if (this->fullscreen) { SetWindowLongA(applicationWindow, GWL_STYLE, static_cast(WS_POPUP)); ShowWindow(applicationWindow, SW_MAXIMIZE); } const HRESULT status = this->CreateDevice(drawIdentity, renderIdentity, mode, flags, monitor); if (FAILED(status)) { this->Close(); if (status == E_FAIL) { return DeviceError(0); } } if (this->device) { const struct { D3DRENDERSTATETYPE state; DWORD value; } states[] = { {D3DRENDERSTATE_CULLMODE, 1}, {D3DRENDERSTATE_ZFUNC, 4}, {D3DRENDERSTATE_TEXTUREPERSPECTIVE, 1}, {D3DRENDERSTATE_ALPHAFUNC, 7}, {D3DRENDERSTATE_ALPHAREF, 240}, {D3DRENDERSTATE_COLORKEYENABLE, 1}, {D3DRENDERSTATE_ZENABLE, 1}, {D3DRENDERSTATE_ZWRITEENABLE, 1}, {D3DRENDERSTATE_SRCBLEND, 5}, {D3DRENDERSTATE_DESTBLEND, 6} }; for (const auto& state : states) { this->device->SetRenderState(state.state, state.value); } const struct { DWORD stage; D3DTEXTURESTAGESTATETYPE state; DWORD value; } stages[] = {{0, D3DTSS_TEXCOORDINDEX, 0}, {0, D3DTSS_COLOROP, 4}, {0, D3DTSS_COLORARG1, 2}, {0, D3DTSS_COLORARG2, 0}, {0, D3DTSS_ALPHAOP, 4}, {0, D3DTSS_ALPHAARG1, 2}, {0, D3DTSS_ALPHAARG2, 0}, {1, D3DTSS_TEXCOORDINDEX, 1}, {1, D3DTSS_COLORARG1, 2}, {1, D3DTSS_COLOROP, 18}, {1, D3DTSS_COLORARG2, 1}, {1, D3DTSS_ALPHAOP, 1}, {2, D3DTSS_COLOROP, 1}, {2, D3DTSS_ALPHAOP, 1}, {0, D3DTSS_MAGFILTER, 2}, {0, D3DTSS_MINFILTER, 2}, {1, D3DTSS_MAGFILTER, 2}, {1, D3DTSS_MINFILTER, 2}, {0, D3DTSS_ADDRESS, 3}, {1, D3DTSS_ADDRESS, 4}, {1, D3DTSS_BORDERCOLOR, 0xFF000000}}; for (const auto& state : stages) { this->device->SetTextureStageState(state.stage, state.state, state.value); } } return status; } // OFFSET: 0x005496D0 // FUNCTION: fn_01892 HRESULT DeviceOwnerDX7::Clear(DWORD color) { D3DRECT rectangle{0, 0, static_cast(this->width), static_cast(this->height)}; return this->device->Clear(1, &rectangle, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, color, 1.0f, 0); } // OFFSET: 0x00549730 // FUNCTION: fn_01893 HRESULT DeviceOwnerDX7::ClearDepth() { D3DRECT rectangle{0, 0, static_cast(this->width), static_cast(this->height)}; return this->device->Clear(1, &rectangle, D3DCLEAR_ZBUFFER, 0, 1.0f, 0); } // OFFSET: 0x0054AF10 // FUNCTION: fn_01903 void DeviceOwnerDX7::MarkSurfacesLost() { this->surfacesLost = true; } // OFFSET: 0x005495C0 // FUNCTION: fn_01891 bool DeviceOwnerDX7::RestoreLostSurfaces() { if (!this->surfacesLost) { return false; } const HRESULT primaryStatus = this->primary->IsLost(); const HRESULT depthStatus = this->depthBuffer ? this->depthBuffer->IsLost() : S_OK; if (primaryStatus == DDERR_SURFACELOST && depthStatus == DDERR_SURFACELOST) { if (this->depthBuffer) { this->depthBuffer->Restore(); } this->draw->RestoreAllSurfaces(); this->surfacesLost = false; } return true; } // OFFSET: 0x0054AF20 // FUNCTION: fn_01904 HRESULT DeviceOwnerDX7::Present(bool wait, const RECT& monitor) { if (!this->primary) { return DeviceError(14); } if (!this->backBuffer) { return this->primary->IsLost(); } if (wait && (!this->fullscreen || this->presentationOption)) { this->destination = ClientRectangle(this->window, monitor); this->draw->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN, nullptr); return this->primary->Blt(&this->destination, this->backBuffer, &this->source, DDBLT_WAIT, nullptr); } GetClientRect(this->window, &this->destination); ClientToScreen(this->window, reinterpret_cast(&this->destination.left)); ClientToScreen(this->window, reinterpret_cast(&this->destination.right)); if (wait) { this->draw->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN, nullptr); return this->primary->Blt(nullptr, this->backBuffer, nullptr, DDBLT_WAIT, nullptr); } return this->primary->Blt(&this->destination, this->backBuffer, &this->source, DDBLT_WAIT, nullptr); } // OFFSET: 0x005494C0 // FUNCTION: fn_01890 void DeviceOwnerDX7::AdjustTextureDimensions(std::uint32_t& width, std::uint32_t& height) const { std::uint32_t selectedWidth = 1; while (selectedWidth < width) { selectedWidth *= 2; } if (selectedWidth < this->minTextureWidth) { selectedWidth = this->minTextureWidth; } if (selectedWidth > this->maxTextureWidth) { selectedWidth = this->maxTextureWidth; } std::uint32_t selectedHeight = 1; while (selectedHeight < height) { selectedHeight *= 2; } if (selectedHeight < this->minTextureHeight) { selectedHeight = this->minTextureHeight; } if (selectedHeight > this->maxTextureHeight) { selectedHeight = this->maxTextureHeight; } const auto aspect = this->maxTextureAspectRatio; if (aspect) { if (selectedWidth / selectedHeight > aspect) { selectedHeight = selectedWidth / aspect; } if (selectedHeight / selectedWidth > aspect) { selectedWidth = selectedHeight / aspect; } } width = selectedWidth; height = selectedHeight; } // OFFSET: 0x0054AE50 // FUNCTION: fn_01902 bool DeviceOwnerDX7::SupportsTextureDimensions(std::uint32_t width, std::uint32_t height) const { return this->minTextureWidth <= width && width <= this->maxTextureWidth && this->minTextureHeight <= height && height <= this->maxTextureHeight; } // OFFSET: 0x0056B890 // FUNCTION: fn_02324 void DeviceOwnerDX7::PrepareErrorDialog() { if (this->direct3D) { this->direct3D->EvictManagedTextures(); } } // OFFSET: 0x0056B8B0 // FUNCTION: fn_02325 void DeviceOwnerDX7::RestoreDisplayMode() { if (this->draw) { if (this->backBuffer) { this->backBuffer->Unlock(nullptr); } if (this->primary) { this->primary->Unlock(nullptr); } this->draw->FlipToGDISurface(); } } // OFFSET: 0x0056C4B0 // FUNCTION: fn_02336 HRESULT DeviceOwnerDX7::RestoreAllSurfaces() { return this->draw->RestoreAllSurfaces(); } // OFFSET: 0x0054A8D0 // FUNCTION: fn_45420 void DeviceOwnerDX7::SetDialogClipper(int enabled) { if (!this->fullscreen) { return; } IDirectDrawClipper* clipper = nullptr; if (enabled == 1) { this->draw->CreateClipper(0, &clipper, nullptr); clipper->SetHWnd(0, this->window); } this->primary->SetClipper(clipper); if (clipper) { clipper->Release(); } }