diff --git a/.gitmodules b/.gitmodules index e86e8fd27a..cbc1636936 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,3 +17,6 @@ [submodule "libs/json"] path = libs/json url = https://github.com/nlohmann/json.git +[submodule "libs/wil"] + path = libs/wil + url = https://github.com/microsoft/wil.git diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 866c6ca300..3e6b6513dd 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -16,6 +16,13 @@ add_subdirectory(spdlog EXCLUDE_FROM_ALL) add_subdirectory(json EXCLUDE_FROM_ALL) +if (WIN32) + option(FAST_BUILD ON) + option(WIL_BUILD_PACKAGING OFF) + option(WIL_BUILD_TESTS OFF) + add_subdirectory(wil EXCLUDE_FROM_ALL) +endif() + ############################################################################################# ############# create the imgui project ############################################################################################# diff --git a/libs/premake5.lua b/libs/premake5.lua index 34e0d39fcd..58a2b2aa67 100644 --- a/libs/premake5.lua +++ b/libs/premake5.lua @@ -247,3 +247,24 @@ project "json" includedirs { "json/include", } + +project "wil" + language "C++" + kind "None" + targetname "wil" + warnings "Off" + + filter "system:windows" + vpaths { + ["Headers/*"] = {"wil/include/wil/**.hpp",}, + ["Sources/*"] = {"wil/include/wil/**.c*",}, + ["*"] = {"premake5.lua", "CMakeLists.txt"} + } + + files { + "wil/include/wil*.*", + } + + includedirs { + "wil/include/wil", + } \ No newline at end of file diff --git a/libs/wil b/libs/wil new file mode 160000 index 0000000000..6f60a1b76f --- /dev/null +++ b/libs/wil @@ -0,0 +1 @@ +Subproject commit 6f60a1b76fb812c6af5db1bc7abdec0001edd43f diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 96bd444d28..e8de95e8c0 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -32,6 +32,10 @@ function(supress_deps_warnings targets) endfunction() supress_deps_warnings(ogg imgui vorbisfile spdlog nlohmann_json) +if (MSVC) + supress_deps_warnings(WIL) +endif() + set_target_properties(${RE_PROJECT_LIB_NAME} PROPERTIES SUFFIX ".asi" @@ -74,7 +78,10 @@ target_link_libraries(${RE_PROJECT_LIB_NAME} PRIVATE nlohmann_json ) -if(WIN32) +if(WIN32) + target_link_libraries(${RE_PROJECT_LIB_NAME} PRIVATE + WIL + ) if(MSVC) # MSVC knows how to find dbghelp on it's own target_link_libraries(${RE_PROJECT_LIB_NAME} PRIVATE dbghelp diff --git a/source/game_sa/MenuManager.h b/source/game_sa/MenuManager.h index 316fe45f95..6036a0a2a3 100644 --- a/source/game_sa/MenuManager.h +++ b/source/game_sa/MenuManager.h @@ -116,7 +116,7 @@ class CMenuManager { bool field_8C; int32 field_90; // controller related int32 field_94; // unused - char* m_pJPegBuffer; //!< +0x98 \see JPegCompress file + char* m_GalleryImageBuffer; //!< +0x98 \see JPegCompress file char field_9C[16]; int32 m_nUserTrackIndex; int8 m_nRadioMode; diff --git a/source/game_sa/Plugins/JPegCompressPlugin/JPegCompress.cpp b/source/game_sa/Plugins/JPegCompressPlugin/JPegCompress.cpp index aeb79d66cd..45a371d213 100644 --- a/source/game_sa/Plugins/JPegCompressPlugin/JPegCompress.cpp +++ b/source/game_sa/Plugins/JPegCompressPlugin/JPegCompress.cpp @@ -1,12 +1,120 @@ #include "StdInc.h" -// depends on libjpeg +#pragma comment(lib, "Shlwapi.lib") +#include +#include + +#include + +// Vanilla used the old D3DX library, which requires old the DirectX SDK. Nasty stuff. +// Implemented with the new WIC API. No Windows XP support. void JPegPlugin::InjectHooks() { RH_ScopedNamespace(JPegPlugin); RH_ScopedCategory("Plugins"); + + // These functions are not ABI and API compatible with SA. + // You'll be awarded a free crash if you leave one of them hooked yet others unhooked. + RH_ScopedGlobalInstall(JPegCompressScreen, 0x5D0470, {.locked = true}); + RH_ScopedGlobalInstall(JPegCompressScreenToFile, 0x5D0820, {.locked = true}); + RH_ScopedGlobalInstall(JPegCompressScreenToBuffer, 0x5D0740, {.locked = true}); + + RH_ScopedGlobalInstall(JPegDecompressToRaster, 0x5D05F0); + RH_ScopedGlobalInstall(JPegDecompressToVramFromBuffer, 0x5D07A0); } +// 0x5D0470 +void JPegCompressScreen(RwCamera* camera, IStream* stream) { + const auto scr = RsGrabScreen(camera); + + // Some parts are from: https://stackoverflow.com/a/30138664 + + wil::com_ptr factory{}; + wil::com_ptr encoder{}; + wil::com_ptr frame{}; + +#define HRCHK(hr) if (FAILED((hr))) { NOTSA_LOG_ERR("Couldn't encode screenshot. Your capture is not saved."); return; } + + HRCHK(CoInitialize(NULL)); + HRCHK(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory))); + + HRCHK(factory->CreateEncoder(GUID_ContainerFormatJpeg, nullptr, &encoder)); + HRCHK(encoder->Initialize(stream, WICBitmapEncoderNoCache)); + HRCHK(encoder->CreateNewFrame(&frame, nullptr)); + HRCHK(frame->Initialize(nullptr)); + HRCHK(frame->SetSize(RwImageGetWidth(scr), RwImageGetHeight(scr))); + + GUID format{ GUID_WICPixelFormat24bppBGR }; + HRCHK(frame->SetPixelFormat(&format)); + + { + // Convert raw RGBA to JPEG friendly BGR. + wil::com_ptr source{}; + wil::com_ptr converted{}; + wil::com_ptr fmtConverter{}; + + HRCHK(factory->CreateBitmapFromMemory( + RwImageGetWidth(scr), + RwImageGetHeight(scr), + GUID_WICPixelFormat32bppRGBA, + RwImageGetStride(scr), + RwImageGetWidth(scr) * RwImageGetHeight(scr) * sizeof(RwRGBA), + RwImageGetPixels(scr), + &source + )); + HRCHK(factory->CreateFormatConverter(&fmtConverter)); + HRCHK(fmtConverter->Initialize(source.get(), format, WICBitmapDitherTypeNone, NULL, 0.0, WICBitmapPaletteTypeCustom)); + HRCHK(fmtConverter->QueryInterface(__uuidof(IWICBitmapSource), converted.put_void())); + + HRCHK(frame->WriteSource(converted.get(), NULL)); + } + HRCHK(frame->Commit()); + HRCHK(encoder->Commit()); + + CoUninitialize(); + +#undef HR_FAIL_RET +} + +// 0x5D0820 void JPegCompressScreenToFile(RwCamera* camera, const char* path) { - plugin::Call<0x5D0820, RwCamera*, const char*>(camera, path); + // Originally used CFileMgr::OpenFile with its handle saved to a static variable + // for passing it to libjpeg callbacks. + // + // I could've done the same thing for the IStream* but it seemed unnecessary. + wil::com_ptr stream{}; + if (SUCCEEDED(SHCreateStreamOnFileEx( + UTF8ToUnicode(path).c_str(), + STGM_CREATE | STGM_WRITE, + FILE_ATTRIBUTE_NORMAL, + true, + NULL, + stream.addressof()) + )) { + JPegCompressScreen(camera, stream.get()); + } +} + +// 0x5D0740 - unused +void JPegCompressScreenToBuffer(RwCamera* camera, uint8*& buffer, uint32& size) { + // Originally same thing on JPegCompressScreenToFile but for in memory buffers. + JPegCompressScreen(camera, SHCreateMemStream(buffer, size)); +} + +// 0x5D05F0 - unused, maybe remnant of PS2 where you can see the gallery in game. +void JPegDecompressToRaster(RwRaster* raster, IStream* stream) { + // it can be implemented with wic as well but whatever +} + +// 0x5D07A0 - unused +bool JPegDecompressToVramFromBuffer(RwRaster* raster, uint8** unk) { + static auto s_ScreenshotFileBuf = StaticRef>(0xBD0B78); + + if (!unk) { + return false; + } + + std::memcpy(s_ScreenshotFileBuf.data(), FrontEndMenuManager.m_GalleryImageBuffer, s_ScreenshotFileBuf.size()); + JPegDecompressToRaster(raster, SHCreateMemStream(s_ScreenshotFileBuf.data(), s_ScreenshotFileBuf.size())); + return true; } diff --git a/source/game_sa/Plugins/JPegCompressPlugin/JPegCompress.h b/source/game_sa/Plugins/JPegCompressPlugin/JPegCompress.h index 1d165b7fdf..c7bf6517f4 100644 --- a/source/game_sa/Plugins/JPegCompressPlugin/JPegCompress.h +++ b/source/game_sa/Plugins/JPegCompressPlugin/JPegCompress.h @@ -15,19 +15,25 @@ namespace JPegPlugin void InjectHooks(); } -void JPegCompressScreen(RwCamera* camera, struct jpeg_destination_mgr& dst); + +void JPegCompressScreen(RwCamera* camera, IStream* stream); /** * Compress camera screen and save at the given file path * * @addr 0x5D0820 */ -extern void JPegCompressScreenToFile(RwCamera* camera, const char* path); +void JPegCompressScreenToFile(RwCamera* camera, const char* path); /** * Compress camera screen to the given buffer + * + * @addr 0x5D0740 */ -extern void JPegCompressScreenToBuffer(char** buffer, uint32* size); +void JPegCompressScreenToBuffer(RwCamera* camera, uint8*& buffer, uint32& size); + +// 0x5D05F0 +void JPegDecompressToRaster(RwRaster* raster, IStream* stream); -void JPegDecompressToRaster(RwRaster* raster, struct jpeg_source_mgr& src); -void JPegDecompressToVramFromBuffer(RwRaster* raster, RwInt8** unk); +// 0x5D07A0 +bool JPegDecompressToVramFromBuffer(RwRaster* raster, uint8** unk); diff --git a/source/premake5.lua b/source/premake5.lua index 5586c233cb..256e21c2d7 100644 --- a/source/premake5.lua +++ b/source/premake5.lua @@ -13,6 +13,10 @@ project "gta_sa_modern" filter "configurations:Debug*" floatingpoint "strict" + filter "system:windows" + includedirs { "../libs/wil/include" } + links { "wil" } + filter {} -- Clear filter vpaths { @@ -39,7 +43,7 @@ project "gta_sa_modern" "../libs/dxsdk", "../libs/spdlog/include", "../libs/tracy/public", - "../libs/json/include" + "../libs/json/include", } filter "options:script-tracing" @@ -78,7 +82,7 @@ project "gta_sa_modern" "dsound.lib", "d3d9.lib", "dbghelp", - "json" + "json", } libdirs {