-
Notifications
You must be signed in to change notification settings - Fork 99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reverse JPegCompress plugin #782
base: master
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,126 @@ | ||
#include "StdInc.h" | ||
|
||
// depends on libjpeg | ||
#pragma comment(lib, "Shlwapi.lib") | ||
#include <Shlwapi.h> | ||
#include <Wincodecsdk.h> | ||
|
||
// 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 | ||
|
||
// TODO: WIL would be nice. | ||
IWICImagingFactory* factory{}; | ||
IWICBitmapEncoder* encoder{}; | ||
IWICBitmapFrameEncode* frame{}; | ||
GUID format{ GUID_WICPixelFormat24bppBGR }; | ||
|
||
#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))); | ||
HRCHK(frame->SetPixelFormat(&format)); | ||
|
||
{ | ||
// Convert raw RGBA to JPEG friendly BGR. | ||
IWICBitmap* source{}; | ||
IWICBitmapSource* converted{}; | ||
IWICFormatConverter* 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, format, WICBitmapDitherTypeNone, NULL, 0.0, WICBitmapPaletteTypeCustom)); | ||
HRCHK(fmtConverter->QueryInterface(__uuidof(IWICBitmapSource), (void**)&converted)); | ||
|
||
HRCHK(frame->WriteSource(converted, NULL)); | ||
|
||
SAFE_RELEASE(source); | ||
SAFE_RELEASE(converted); | ||
SAFE_RELEASE(fmtConverter); | ||
yukani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
HRCHK(frame->Commit()); | ||
HRCHK(encoder->Commit()); | ||
|
||
SAFE_RELEASE(frame); | ||
SAFE_RELEASE(encoder); | ||
SAFE_RELEASE(factory); | ||
CoUninitialize(); | ||
yukani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#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. | ||
IStream* stream{}; | ||
if (SUCCEEDED(SHCreateStreamOnFileEx( | ||
UTF8ToUnicode(path).c_str(), | ||
STGM_CREATE | STGM_WRITE, | ||
FILE_ATTRIBUTE_NORMAL, | ||
true, | ||
NULL, | ||
&stream) | ||
)) { | ||
JPegCompressScreen(camera, stream); | ||
stream->Release(); | ||
} | ||
} | ||
|
||
// 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<std::array<uint8, 204'800>>(0xBD0B78); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to use GTA's buffer here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can but why |
||
|
||
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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. might as well just use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Couldn't encode screenshot." might be misleading. I'd also include the status in the message.
Or perhaps just use
VERIFY
(or WIN_VERIFY? not sure)