2018-07-01 19:12:35 +02:00
|
|
|
#include "common.h"
|
2020-12-11 16:41:37 +01:00
|
|
|
#include "fs/FSUtils.h"
|
2018-07-01 19:12:35 +02:00
|
|
|
#include "retain_vars.hpp"
|
2020-12-11 16:41:37 +01:00
|
|
|
#include "screenshot_utils.h"
|
2023-01-22 14:45:20 +01:00
|
|
|
#include "thread.h"
|
2022-09-24 22:57:24 +02:00
|
|
|
#include "utils/input.h"
|
2020-12-11 16:41:37 +01:00
|
|
|
#include "utils/logger.h"
|
2023-01-22 14:45:20 +01:00
|
|
|
#include <coreinit/cache.h>
|
2020-12-11 16:41:37 +01:00
|
|
|
#include <gx2/surface.h>
|
2022-09-24 22:57:24 +02:00
|
|
|
#include <padscore/wpad.h>
|
2020-12-11 16:41:37 +01:00
|
|
|
#include <vpad/input.h>
|
|
|
|
#include <wups.h>
|
2018-07-01 19:12:35 +02:00
|
|
|
|
2023-01-22 14:52:47 +01:00
|
|
|
extern "C" uint32_t VPADGetButtonProcMode(uint32_t);
|
|
|
|
|
2023-01-22 16:38:23 +01:00
|
|
|
void AlreadyInProgressCallback(NotificationModuleHandle handle, void *context) {
|
|
|
|
auto scanTarget = (GX2ScanTarget) (uint32_t) context;
|
|
|
|
if (scanTarget == GX2_SCAN_TARGET_TV) {
|
|
|
|
gInProgressNotificationDisplayedTV = false;
|
|
|
|
} else if (scanTarget == GX2_SCAN_TARGET_DRC) {
|
|
|
|
gInProgressNotificationDisplayedDRC = false;
|
|
|
|
}
|
|
|
|
OSMemoryBarrier();
|
|
|
|
}
|
2023-01-22 14:45:20 +01:00
|
|
|
|
2023-01-22 16:38:23 +01:00
|
|
|
void NotAvailableCallback(NotificationModuleHandle handle, void *context) {
|
|
|
|
gNotAvailableNotificationDisplayed = false;
|
|
|
|
OSMemoryBarrier();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RequestScreenshot() {
|
|
|
|
NotificationModuleStatus err;
|
2023-01-26 13:18:21 +01:00
|
|
|
if (gBlockScreenshots) {
|
2023-01-22 16:38:23 +01:00
|
|
|
if (!gNotAvailableNotificationDisplayed) {
|
|
|
|
if ((err = NotificationModule_AddErrorNotificationWithCallback("Screenshots not available at the moment.",
|
|
|
|
NotAvailableCallback,
|
|
|
|
nullptr)) != NOTIFICATION_MODULE_RESULT_SUCCESS) {
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Failed to display \"Screenshots not available at the moment.\" notification");
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Error: %s,", NotificationModule_GetStatusStr(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
gNotAvailableNotificationDisplayed = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_TV) {
|
|
|
|
if (gTakeScreenshotTV == SCREENSHOT_STATE_READY) {
|
|
|
|
DEBUG_FUNCTION_LINE("Requested screenshot for TV!");
|
|
|
|
gTakeScreenshotTV = SCREENSHOT_STATE_REQUESTED;
|
|
|
|
} else if (!gInProgressNotificationDisplayedTV) {
|
|
|
|
if ((err = NotificationModule_AddErrorNotificationWithCallback("Screenshot of the TV already in progress.",
|
|
|
|
AlreadyInProgressCallback,
|
|
|
|
(void *) GX2_SCAN_TARGET_TV)) != NOTIFICATION_MODULE_RESULT_SUCCESS) {
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Failed to display \"Screenshot of the TV already in progress.\" notification");
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Error: %s,", NotificationModule_GetStatusStr(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
gInProgressNotificationDisplayedTV = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_DRC) {
|
2023-01-26 13:01:46 +01:00
|
|
|
if (gBlockDRCScreenshots) {
|
|
|
|
DEBUG_FUNCTION_LINE("Screenshots are blocked for DRC because it's not connected");
|
|
|
|
return;
|
|
|
|
}
|
2023-01-22 16:38:23 +01:00
|
|
|
if (gTakeScreenshotDRC == SCREENSHOT_STATE_READY) {
|
|
|
|
DEBUG_FUNCTION_LINE("Requested screenshot for DRC!");
|
|
|
|
gTakeScreenshotDRC = SCREENSHOT_STATE_REQUESTED;
|
|
|
|
} else if (!gInProgressNotificationDisplayedDRC) {
|
|
|
|
if ((err = NotificationModule_AddErrorNotificationWithCallback("Screenshot of the GamePad already in progress.",
|
|
|
|
AlreadyInProgressCallback,
|
|
|
|
(void *) GX2_SCAN_TARGET_DRC)) != NOTIFICATION_MODULE_RESULT_SUCCESS) {
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Failed to display \"Screenshot of the GamePad already in progress.\" notification");
|
|
|
|
DEBUG_FUNCTION_LINE_ERR("Error: %s,", NotificationModule_GetStatusStr(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
gInProgressNotificationDisplayedDRC = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
OSMemoryBarrier();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DECL_FUNCTION(int32_t, VPADRead, VPADChan chan, VPADStatus *buffer, uint32_t buffer_size, VPADReadError *error) {
|
2020-12-11 16:41:37 +01:00
|
|
|
VPADReadError real_error;
|
|
|
|
int32_t result = real_VPADRead(chan, buffer, buffer_size, &real_error);
|
2018-07-01 19:12:35 +02:00
|
|
|
|
2023-01-22 16:38:23 +01:00
|
|
|
if (gEnabled) {
|
2023-01-22 14:45:20 +01:00
|
|
|
if (result > 0 && real_error == VPAD_READ_SUCCESS) {
|
2023-01-22 14:52:47 +01:00
|
|
|
uint32_t end = 1;
|
|
|
|
// Fix games like TP HD
|
|
|
|
if (VPADGetButtonProcMode(chan) == 1) {
|
|
|
|
end = result;
|
|
|
|
}
|
|
|
|
for (uint32_t i = 0; i < end; i++) {
|
|
|
|
if (((buffer[i].trigger & 0x000FFFFF) == gButtonCombo)) {
|
2023-01-22 16:38:23 +01:00
|
|
|
RequestScreenshot();
|
2023-01-22 14:52:47 +01:00
|
|
|
break;
|
2023-01-22 14:45:20 +01:00
|
|
|
}
|
|
|
|
}
|
2020-12-11 16:41:37 +01:00
|
|
|
}
|
2018-07-01 19:12:35 +02:00
|
|
|
}
|
|
|
|
|
2020-12-11 16:41:37 +01:00
|
|
|
if (error) {
|
|
|
|
*error = real_error;
|
|
|
|
}
|
2018-07-01 19:12:35 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-01-22 16:19:37 +01:00
|
|
|
static uint32_t sWPADLastButtonHold[4];
|
2022-09-24 22:57:24 +02:00
|
|
|
DECL_FUNCTION(void, WPADRead, WPADChan chan, WPADStatusProController *data) {
|
|
|
|
real_WPADRead(chan, data);
|
|
|
|
|
2023-01-22 16:38:23 +01:00
|
|
|
if (gEnabled && chan >= 0 && chan < 4) {
|
2022-09-24 22:57:24 +02:00
|
|
|
if (data[0].err == 0) {
|
|
|
|
if (data[0].extensionType != 0xFF) {
|
2023-01-22 16:19:37 +01:00
|
|
|
uint32_t curButtonHold = 0;
|
|
|
|
uint32_t buttonComboConverted = 0;
|
2022-09-24 22:57:24 +02:00
|
|
|
if (data[0].extensionType == WPAD_EXT_CORE || data[0].extensionType == WPAD_EXT_NUNCHUK) {
|
2023-01-22 16:19:37 +01:00
|
|
|
buttonComboConverted = remapVPADtoWiimote(gButtonCombo);
|
2022-09-24 22:57:24 +02:00
|
|
|
// button data is in the first 2 bytes for wiimotes
|
2023-01-22 16:19:37 +01:00
|
|
|
curButtonHold = ((uint16_t *) data)[0];
|
2022-09-24 22:57:24 +02:00
|
|
|
} else if (data[0].extensionType == WPAD_EXT_CLASSIC) {
|
2023-01-22 16:19:37 +01:00
|
|
|
buttonComboConverted = remapVPADtoClassic(gButtonCombo);
|
|
|
|
curButtonHold = (((uint32_t *) data)[10] & 0xFFFF);
|
2022-09-24 22:57:24 +02:00
|
|
|
} else if (data[0].extensionType == WPAD_EXT_PRO_CONTROLLER) {
|
2023-01-22 16:19:37 +01:00
|
|
|
buttonComboConverted = remapVPADtoPro(gButtonCombo);
|
|
|
|
curButtonHold = data[0].buttons;
|
2022-09-24 22:57:24 +02:00
|
|
|
}
|
2023-01-22 16:19:37 +01:00
|
|
|
|
|
|
|
uint32_t curButtonTrigger = (curButtonHold & (~(sWPADLastButtonHold[chan])));
|
|
|
|
|
2023-01-22 16:36:40 +01:00
|
|
|
bool forceScreenshot = false;
|
|
|
|
if (gReservedBitUsage && data[0].extensionType == WPAD_EXT_PRO_CONTROLLER) {
|
|
|
|
if (curButtonTrigger == WPAD_PRO_RESERVED) {
|
|
|
|
forceScreenshot = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (forceScreenshot || (buttonComboConverted != 0 && curButtonTrigger == buttonComboConverted)) {
|
2023-01-22 16:38:23 +01:00
|
|
|
RequestScreenshot();
|
2022-09-24 22:57:24 +02:00
|
|
|
}
|
2023-01-22 16:19:37 +01:00
|
|
|
|
|
|
|
sWPADLastButtonHold[chan] = curButtonHold;
|
2022-09-24 22:57:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-11 16:41:37 +01:00
|
|
|
DECL_FUNCTION(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer *colorBuffer, GX2ScanTarget scan_target) {
|
2023-01-22 14:45:20 +01:00
|
|
|
if (gEnabled) {
|
|
|
|
if (scan_target == GX2_SCAN_TARGET_TV && colorBuffer != nullptr && gTakeScreenshotTV == SCREENSHOT_STATE_REQUESTED) {
|
2022-09-24 21:32:49 +02:00
|
|
|
DEBUG_FUNCTION_LINE("Lets take a screenshot from TV.");
|
2023-01-22 14:45:20 +01:00
|
|
|
if (!takeScreenshot((GX2ColorBuffer *) colorBuffer, scan_target, gTVSurfaceFormat, gOutputFormat, gQuality)) {
|
|
|
|
gTakeScreenshotTV = SCREENSHOT_STATE_READY;
|
|
|
|
} else {
|
|
|
|
gTakeScreenshotTV = SCREENSHOT_STATE_SAVING;
|
|
|
|
}
|
|
|
|
} else if (scan_target == GX2_SCAN_TARGET_DRC0 && colorBuffer != nullptr && gTakeScreenshotDRC == SCREENSHOT_STATE_REQUESTED) {
|
2022-09-24 21:32:49 +02:00
|
|
|
DEBUG_FUNCTION_LINE("Lets take a screenshot from DRC.");
|
2023-01-22 14:45:20 +01:00
|
|
|
if (!takeScreenshot((GX2ColorBuffer *) colorBuffer, scan_target, gDRCSurfaceFormat, gOutputFormat, gQuality)) {
|
|
|
|
gTakeScreenshotDRC = SCREENSHOT_STATE_READY;
|
|
|
|
} else {
|
|
|
|
gTakeScreenshotDRC = SCREENSHOT_STATE_SAVING;
|
|
|
|
}
|
2018-07-01 19:12:35 +02:00
|
|
|
}
|
|
|
|
}
|
2023-01-22 14:45:20 +01:00
|
|
|
|
2020-12-11 16:41:37 +01:00
|
|
|
real_GX2CopyColorBufferToScanBuffer(colorBuffer, scan_target);
|
|
|
|
}
|
|
|
|
|
|
|
|
DECL_FUNCTION(void, GX2SetTVBuffer, void *buffer, uint32_t buffer_size, int32_t tv_render_mode, GX2SurfaceFormat surface_format, GX2BufferingMode buffering_mode) {
|
|
|
|
DEBUG_FUNCTION_LINE_VERBOSE("Set TV Buffer format to 0x%08X", surface_format);
|
|
|
|
gTVSurfaceFormat = surface_format;
|
|
|
|
|
|
|
|
return real_GX2SetTVBuffer(buffer, buffer_size, tv_render_mode, surface_format, buffering_mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
DECL_FUNCTION(void, GX2SetDRCBuffer, void *buffer, uint32_t buffer_size, uint32_t drc_mode, GX2SurfaceFormat surface_format, GX2BufferingMode buffering_mode) {
|
|
|
|
DEBUG_FUNCTION_LINE_VERBOSE("Set DRC Buffer format to 0x%08X", surface_format);
|
|
|
|
gDRCSurfaceFormat = surface_format;
|
|
|
|
|
|
|
|
return real_GX2SetDRCBuffer(buffer, buffer_size, drc_mode, surface_format, buffering_mode);
|
2018-07-01 19:12:35 +02:00
|
|
|
}
|
|
|
|
|
2023-01-22 14:49:05 +01:00
|
|
|
GX2ColorBuffer lastTVColorBuffer;
|
|
|
|
GX2ColorBuffer lastDRCColorBuffer;
|
|
|
|
DECL_FUNCTION(void, GX2GetCurrentScanBuffer, GX2ScanTarget scanTarget, GX2ColorBuffer *cb) {
|
|
|
|
real_GX2GetCurrentScanBuffer(scanTarget, cb);
|
|
|
|
if (scanTarget == GX2_SCAN_TARGET_TV) {
|
|
|
|
memcpy(&lastTVColorBuffer, cb, sizeof(GX2ColorBuffer));
|
|
|
|
} else {
|
|
|
|
memcpy(&lastDRCColorBuffer, cb, sizeof(GX2ColorBuffer));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DECL_FUNCTION(void, GX2MarkScanBufferCopied, GX2ScanTarget scan_target) {
|
|
|
|
if (gEnabled) {
|
|
|
|
if (scan_target == GX2_SCAN_TARGET_TV && gTakeScreenshotTV == SCREENSHOT_STATE_REQUESTED) {
|
|
|
|
DEBUG_FUNCTION_LINE("Lets take a screenshot from TV.");
|
|
|
|
if (!takeScreenshot((GX2ColorBuffer *) &lastTVColorBuffer, scan_target, gTVSurfaceFormat, gOutputFormat, gQuality)) {
|
|
|
|
gTakeScreenshotTV = SCREENSHOT_STATE_READY;
|
|
|
|
} else {
|
|
|
|
gTakeScreenshotTV = SCREENSHOT_STATE_SAVING;
|
|
|
|
}
|
|
|
|
} else if (scan_target == GX2_SCAN_TARGET_DRC0 && gTakeScreenshotDRC == SCREENSHOT_STATE_REQUESTED) {
|
|
|
|
DEBUG_FUNCTION_LINE("Lets take a screenshot from DRC.");
|
|
|
|
if (!takeScreenshot((GX2ColorBuffer *) &lastDRCColorBuffer, scan_target, gDRCSurfaceFormat, gOutputFormat, gQuality)) {
|
|
|
|
gTakeScreenshotDRC = SCREENSHOT_STATE_READY;
|
|
|
|
} else {
|
|
|
|
gTakeScreenshotDRC = SCREENSHOT_STATE_SAVING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
real_GX2MarkScanBufferCopied(scan_target);
|
|
|
|
}
|
|
|
|
|
2020-12-11 16:41:37 +01:00
|
|
|
WUPS_MUST_REPLACE(VPADRead, WUPS_LOADER_LIBRARY_VPAD, VPADRead);
|
2023-01-22 14:49:05 +01:00
|
|
|
WUPS_MUST_REPLACE(GX2MarkScanBufferCopied, WUPS_LOADER_LIBRARY_GX2, GX2MarkScanBufferCopied);
|
|
|
|
WUPS_MUST_REPLACE(GX2GetCurrentScanBuffer, WUPS_LOADER_LIBRARY_GX2, GX2GetCurrentScanBuffer);
|
2020-12-11 16:41:37 +01:00
|
|
|
WUPS_MUST_REPLACE(GX2CopyColorBufferToScanBuffer, WUPS_LOADER_LIBRARY_GX2, GX2CopyColorBufferToScanBuffer);
|
|
|
|
WUPS_MUST_REPLACE(GX2SetTVBuffer, WUPS_LOADER_LIBRARY_GX2, GX2SetTVBuffer);
|
2022-09-24 22:57:24 +02:00
|
|
|
WUPS_MUST_REPLACE(GX2SetDRCBuffer, WUPS_LOADER_LIBRARY_GX2, GX2SetDRCBuffer);
|
|
|
|
WUPS_MUST_REPLACE(WPADRead, WUPS_LOADER_LIBRARY_PADSCORE, WPADRead);
|