From 6b14d4f86ea67baa44d7541f1527364d219b0662 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Thu, 10 Feb 2022 13:39:31 +0800 Subject: [PATCH] Fixed the screenshot for sfall DX9 mode * Now the screenshots are saved in PNG format. [TEST] Changed the DirectX 9 device creation mode. --- artifacts/ddraw.ini | 1 + sfall/FalloutEngine/FunctionOffsets_def.h | 1 + sfall/Game/render.cpp | 2 +- sfall/Modules/Graphics.cpp | 134 +++++++++++++++++++++- sfall/Modules/HookScripts.cpp | 4 - sfall/Modules/Movies.cpp | 33 +++--- sfall/Modules/Movies.h | 2 + 7 files changed, 150 insertions(+), 27 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index a01a0276..e0ea6f6b 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -76,6 +76,7 @@ Use32BitHeadGraphics=0 ;Set to 1 to automatically search for alternative AVI video files when Fallout tries to play the game movies ;Set to 2 to force AVI videos to fit the screen width ;Requires DX9 graphics mode +;The recommended video codec is Xvid AllowDShowMovies=0 ;Fade effect time percentage modifier diff --git a/sfall/FalloutEngine/FunctionOffsets_def.h b/sfall/FalloutEngine/FunctionOffsets_def.h index 610f8e39..7a6160fe 100644 --- a/sfall/FalloutEngine/FunctionOffsets_def.h +++ b/sfall/FalloutEngine/FunctionOffsets_def.h @@ -235,6 +235,7 @@ FUNC(game_exit_, 0x442C34) FUNC(game_get_global_var_, 0x443C68) FUNC(game_help_, 0x443F74) FUNC(game_reset_, 0x442B84) +FUNC(game_screendump_, 0x443EF0) FUNC(game_set_global_var_, 0x443C98) FUNC(game_time_, 0x4A3330) FUNC(game_time_date_, 0x4A3338) diff --git a/sfall/Game/render.cpp b/sfall/Game/render.cpp index e3695364..030d02a6 100644 --- a/sfall/Game/render.cpp +++ b/sfall/Game/render.cpp @@ -183,7 +183,7 @@ void Render::init() { // Custom implementation of the GNW_win_refresh function sf::MakeJump(0x4D6FD9, GNW_win_refresh_hack, 1); - // Replace _screendump_buf with _screen_buffer for creating screenshots + // Replace _screendump_buf with _screen_buffer for creating screenshots (for sfall DX9 mode with HRP by Mash) const DWORD scrdumpBufAddr[] = {0x4C8FD1, 0x4C900D}; sf::SafeWriteBatch(FO_VAR_screen_buffer, scrdumpBufAddr); } diff --git a/sfall/Modules/Graphics.cpp b/sfall/Modules/Graphics.cpp index e6b7bd40..c9464759 100644 --- a/sfall/Modules/Graphics.cpp +++ b/sfall/Modules/Graphics.cpp @@ -22,6 +22,7 @@ #include "..\version.h" #include "LoadGameHook.h" #include "ScriptShaders.h" +#include "Movies.h" #include "..\Game\render.h" @@ -64,6 +65,7 @@ DWORD Graphics::mode; bool Graphics::PlayAviMovie = false; bool Graphics::AviMovieWidthFit = false; +static bool dShowMovies; static bool DeviceLost = false; static char textureFilter; // 1 - auto, 2 - force @@ -199,12 +201,14 @@ static void ResetDevice(bool create) { static D3DFORMAT textureFormat = D3DFMT_X8R8G8B8; if (create) { + DWORD mThreadFlags = (dShowMovies) ? D3DCREATE_MULTITHREADED : 0; + dlog("Creating D3D9 Device...", DL_MAIN); - if (FAILED(d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window, D3DCREATE_PUREDEVICE | D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE, ¶ms, &d3d9Device))) { + if (FAILED(d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window, D3DCREATE_HARDWARE_VERTEXPROCESSING | mThreadFlags, ¶ms, &d3d9Device))) { //D3DCREATE_PUREDEVICE | D3DCREATE_FPU_PRESERVE MessageBoxA(window, "Failed to create hardware vertex processing device.\nUsing software vertex processing instead.", - "sfall DX9", MB_TASKMODAL | MB_ICONWARNING); + "sfall DirectX 9", MB_TASKMODAL | MB_ICONWARNING); software = true; - d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window, D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE, ¶ms, &d3d9Device); + d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window, D3DCREATE_SOFTWARE_VERTEXPROCESSING | mThreadFlags, ¶ms, &d3d9Device); //D3DCREATE_FPU_PRESERVE } D3DCAPS9 caps; @@ -234,7 +238,7 @@ static void ResetDevice(bool create) { textureFormat = (A8IsSupported) ? D3DFMT_A8 : D3DFMT_L8; // D3DFMT_A8 - not supported on some older video cards } else { MessageBoxA(window, "Failed to create shader effects.\nSwitching to CPU for the palette conversion.", - "sfall DX9", MB_TASKMODAL | MB_ICONWARNING); + "sfall DirectX 9", MB_TASKMODAL | MB_ICONWARNING); if (mainTex) SAFERELEASE(mainTex); // release D3DFMT_A8 format texture Graphics::GPUBlt = 0; A8IsSupported = false; @@ -246,7 +250,7 @@ static void ResetDevice(bool create) { d3d9Device->CreateTexture(ResWidth, ResHeight, 1, 0, textureFormat, D3DPOOL_SYSTEMMEM, &mainTex, 0); MessageBoxA(window, "Texture format error.\nGPU does not support the D3DFMT_L8 texture format.\nNow CPU is used to convert the palette.\n" "Set 'GPUBlt' option to CPU to bypass this warning message.", - "sfall DX9", MB_TASKMODAL | MB_ICONWARNING); + "sfall DirectX 9", MB_TASKMODAL | MB_ICONWARNING); Graphics::GPUBlt = 0; } if (Graphics::GPUBlt == 0) palette = new PALCOLOR[256]; @@ -1333,6 +1337,118 @@ static __declspec(naked) void palette_fade_to_hook() { jmp fo::funcoffs::fadeSystemPalette_; } } +/* +#pragma pack(push, 1) +struct BMPHEADER { + BITMAPFILEHEADER bFile; + BITMAPINFOHEADER bInfo; +}; +#pragma pack(pop) +*/ +long __stdcall SaveScreen(const char* file) { + IDirect3DSurface9* surface; + d3d9Device->CreateOffscreenPlainSurface(gWidth, gHeight, D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, &surface, 0); + d3d9Device->GetRenderTargetData(backBuffer, surface); + + LPD3DXBUFFER buffer; + D3DXCreateBuffer(gWidth * gHeight * 2, &buffer); + + D3DXSaveSurfaceToFileInMemory(&buffer, D3DXIFF_PNG, surface, 0, 0); + //D3DXSaveSurfaceToFileA(file, D3DXIFF_PNG, surface, 0, 0); // slow save + + HANDLE hFile = CreateFileA(file, GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0); + bool resultOK = (hFile != INVALID_HANDLE_VALUE); + if (resultOK) { + DWORD dwWritten; + WriteFile(hFile, buffer->GetBufferPointer(), buffer->GetBufferSize(), &dwWritten, 0); + CloseHandle(hFile); + } + + surface->Release(); + buffer->Release(); + + /* BMP 24-bit + long bmpExtraSize = gWidth * 3 % 4; + if (bmpExtraSize != 0) bmpExtraSize = 4 - bmpExtraSize; + + DWORD sizeImage = gWidth * gHeight * 3; + sizeImage += gHeight * bmpExtraSize; + + BMPHEADER bmpHeader; + std::memset(&bmpHeader, 0, sizeof(BMPHEADER)); + + bmpHeader.bFile.bfType = 0x4D42; + bmpHeader.bFile.bfOffBits = sizeof(BMPHEADER); + bmpHeader.bInfo.biSize = sizeof(BITMAPINFOHEADER); + bmpHeader.bInfo.biWidth = gWidth; + bmpHeader.bInfo.biHeight = 0 - gHeight; + bmpHeader.bInfo.biPlanes = 1; + bmpHeader.bInfo.biBitCount = 24; + bmpHeader.bInfo.biCompression = BI_RGB; + bmpHeader.bInfo.biSizeImage = sizeImage; + + BYTE* bmpImageData = new BYTE[sizeImage]; + BYTE* dData = bmpImageData; + + D3DLOCKED_RECT lockRect; + surface->LockRect(&lockRect, 0, 0); + BYTE* lockData = (BYTE*)lockRect.pBits; + + // 32-bit to 24-bit + for (size_t h = 0; h < gHeight; h++) { + BYTE* sData = lockData; + for (size_t x = 0; x < gWidth; x++) { + *dData++ = *sData++; + *dData++ = *sData++; + *dData++ = *sData++; + sData++; + } + lockData += lockRect.Pitch; + dData += bmpExtraSize; + } + + surface->UnlockRect(); + surface->Release(); + + HANDLE hFile = CreateFileA(file, GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0); + bool resultOK = (hFile != INVALID_HANDLE_VALUE); + + if (resultOK) { + DWORD dwWritten; + WriteFile(hFile, &bmpHeader, sizeof(BMPHEADER), &dwWritten, 0); + WriteFile(hFile, bmpImageData, sizeImage, &dwWritten, 0); + CloseHandle(hFile); + } + delete[] bmpImageData;*/ + + return (resultOK) ? 0 : 1; +} + +long __stdcall game_screendump_hook() { + char fileName[16]; + + for (size_t i = 0; i < 100000; i++) { + std::sprintf(fileName, "scr%.5d.png", i); // scr#####.png + + HANDLE hFile = CreateFileA(fileName, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) { + return SaveScreen(fileName); + } + CloseHandle(hFile); + } + return 1; +} + +static __declspec(naked) void dump_screen_hack_replacement() { + __asm { + push ecx; + push edx; + call fo::funcoffs::game_screendump_; // call ds:[FO_VAR_screendump_func]; + pop edx; + pop ecx; + retn; + } +} void Graphics::init() { Graphics::mode = IniReader::GetConfigInt("Graphics", "Mode", 0); @@ -1347,7 +1463,7 @@ void Graphics::init() { if (!h) { dlogr(" Failed", DL_INIT); MessageBoxA(0, "You have selected DirectX graphics mode, but " _DLL_NAME " is missing.\n" - "Switch back to mode 0, or install an up to date version of DirectX 9.0c.", 0, MB_TASKMODAL | MB_ICONERROR); + "Switch back to DirectDraw (Mode=0), or install an up to date version of DirectX 9.0c.", 0, MB_TASKMODAL | MB_ICONERROR); #undef _DLL_NAME ExitProcess(-1); } @@ -1359,6 +1475,10 @@ void Graphics::init() { MakeJump(fo::funcoffs::GNW95_SetPaletteEntries_ + 1, GNW95_SetPaletteEntries_replacement); // 0x4CB311 MakeJump(fo::funcoffs::GNW95_SetPalette_, GNW95_SetPalette_replacement); // 0x4CB568 + // Replace the screenshot saving implementation for sfall DirectX 9 + HookCall(0x443EF3, game_screendump_hook); + MakeJump(0x4C8F4C, dump_screen_hack_replacement); + if (hrpVersionValid) { // Patch HRP to show the mouse cursor over the window title if (Graphics::mode == 5) SafeWrite8(HRPAddress(0x10027142), CodeType::JumpShort); @@ -1369,6 +1489,8 @@ void Graphics::init() { textureFilter = IniReader::GetConfigInt("Graphics", "TextureFilter", 1); dlogr(" Done", DL_INIT); + + dShowMovies = Movies::DirectShowMovies(); } if (Graphics::mode == 5) { moveWindowKey[0] = IniReader::GetConfigInt("Input", "WindowScrollKey", 0); diff --git a/sfall/Modules/HookScripts.cpp b/sfall/Modules/HookScripts.cpp index f43c7a04..304e4d6d 100644 --- a/sfall/Modules/HookScripts.cpp +++ b/sfall/Modules/HookScripts.cpp @@ -239,10 +239,6 @@ void HookScripts::HookScriptClear() { void HookScripts::init() { injectAllHooks = isDebug && (IniReader::GetIntDefaultConfig("Debugging", "InjectAllGameHooks", 0) != 0); if (injectAllHooks) dlogr("Injecting all game hooks.", DL_HOOK|DL_INIT); - -// for (int i = 0; i < HOOK_COUNT; i++) { -// dlog_f("injectHooks[%d]: id(%d), func(0x%x), state(%d)\n", DL_INIT, i, injectHooks[i].id, injectHooks[i].inject, injectHooks[i].injectState); -// } } } diff --git a/sfall/Modules/Movies.cpp b/sfall/Modules/Movies.cpp index bce1d73f..d283c720 100644 --- a/sfall/Modules/Movies.cpp +++ b/sfall/Modules/Movies.cpp @@ -86,6 +86,7 @@ private: lpAllocInfo->dwFlags |= VMR9AllocFlag_TextureSurface; // | VMR9AllocFlag_DXVATarget; lpAllocInfo->Pool = D3DPOOL_SYSTEMMEM; + //lpAllocInfo->Format = D3DFMT_X8R8G8B8; // Ask the VMR-9 to allocate the surfaces for us. for (std::vector::iterator it = surfaces.begin(); it != surfaces.end(); ++it) { @@ -562,6 +563,22 @@ static __declspec(naked) void LostFocus() { } } +/* + WIP: Task + Implement subtitle output from the need to play an mve file in the background. +*/ +bool Movies::DirectShowMovies() { + int allowDShowMovies = IniReader::GetConfigInt("Graphics", "AllowDShowMovies", 0); + if (allowDShowMovies > 0) { + Graphics::AviMovieWidthFit = (allowDShowMovies >= 2); + MakeJump(0x44E690, gmovie_play_hack); + HookCall(0x44E993, gmovie_play_hook_stop); + /* NOTE: At this address 0x487781 (movieStart_), HRP changes the callback procedure to display mve frames. */ + return true; + } + return false; +} + void Movies::init() { dlog("Applying movie patch.", DL_INIT); @@ -581,26 +598,10 @@ void Movies::init() { IniReader::GetConfigString("Misc", optName, "", &MoviePaths[index], 65); } } - dlog(".", DL_INIT); const DWORD movieListAddr[] = {0x44E6AE, 0x44E721, 0x44E75E, 0x44E78A}; // gmovie_play_ SafeWriteBatch((DWORD)MoviePtrs, movieListAddr); MakeCall(0x44E896, gmovie_play_hack_subpal, 2); MakeCall(0x45A1C9, op_play_gmovie_hack); - dlog(".", DL_INIT); - - /* - WIP: Task - Implement subtitle output from the need to play an mve file in the background. - */ - if (Graphics::mode != 0) { - int allowDShowMovies = IniReader::GetConfigInt("Graphics", "AllowDShowMovies", 0); - if (allowDShowMovies > 0) { - Graphics::AviMovieWidthFit = (allowDShowMovies >= 2); - MakeJump(0x44E690, gmovie_play_hack); - HookCall(0x44E993, gmovie_play_hook_stop); - /* NOTE: At this address 0x487781 (movieStart_), HRP changes the callback procedure to display mve frames. */ - } - } dlogr(" Done", DL_INIT); DWORD days = SimplePatch(0x4A36EC, "Misc", "MovieTimer_artimer4", 360, 0); diff --git a/sfall/Modules/Movies.h b/sfall/Modules/Movies.h index f2884164..92a64e99 100644 --- a/sfall/Modules/Movies.h +++ b/sfall/Modules/Movies.h @@ -26,6 +26,8 @@ public: static const char* name() { return "Movies"; } static void init(); //static void exit(); + + static bool DirectShowMovies(); }; static const int MaxMovies = 32;