Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dll/dll/local_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class Local_Storage {
uint64_t file_timestamp(std::string folder, std::string file);
std::string get_global_settings_path();
std::string get_path(std::string folder);
bool dir_exists(std::string folder);

bool update_save_filenames(std::string folder);

Expand Down
17 changes: 17 additions & 0 deletions dll/dll/screenshot_format.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <vector>
#include <cstdint>

#ifdef EMU_OVERLAY
#include "InGameOverlay/RendererHook.h"

namespace ScreenshotFormat {

// Converts from any ScreenshotDataFormat_t to RGBA format.
// Returns a std::vector<uint8_t> containing the RGBA image data.
// OutChannels can be 3 (RGB) or 4 (RGBA).
std::vector<uint8_t> ConvertToRGBA(const InGameOverlay::ScreenshotCallbackParameter_t* screenshot, int outChannels = 4);

}
#endif // EMU_OVERLAY
2 changes: 2 additions & 0 deletions dll/dll/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ struct Overlay_Appearance {
uint32 notification_duration_achievement = 7000; // achievement unlocked duration (millisec)
uint32 notification_duration_invitation = 8000; // friend invitation duration (millisec)
uint32 notification_duration_chat = 4000; // sliding animation duration duration (millisec)
uint32 notification_duration_screenshot = 1000; // screenshot saved duration (millisec)

std::string ach_unlock_datetime_format = "%Y/%m/%d - %H:%M:%S";

Expand Down Expand Up @@ -379,6 +380,7 @@ class Settings {
bool overlay_always_show_playtime = false;
// keys used to toggle the overlay, default = Shift + Tab
std::vector<std::string> overlay_toggle_keys{};
std::vector<std::string> overlay_screenshot_keys{};
// minimum time interval between achievement notifications (in milliseconds)
int achievement_notification_delay_ms = 0;

Expand Down
6 changes: 6 additions & 0 deletions dll/dll/steam_screenshots.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#define __INCLUDED_STEAM_SCRNSHOTS_H__

#include "base.h"
#include <functional>

struct screenshot_infos_t {
std::string screenshot_name{};
Expand All @@ -33,7 +34,12 @@ public ISteamScreenshots
class Local_Storage *local_storage{};
class SteamCallBacks *callbacks{};

public:
bool hooked = false;
// Callback set by Steam_Overlay to delegate overlay screenshots to the renderer hook
std::function<void()> overlay_take_screenshot{};

private:
std::map<ScreenshotHandle, screenshot_infos_t> _screenshots{};

ScreenshotHandle create_screenshot_handle();
Expand Down
8 changes: 8 additions & 0 deletions dll/local_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,14 @@ std::string Local_Storage::get_path(std::string folder)
return path;
}

bool Local_Storage::dir_exists(std::string folder)
{
if (folder.size() && folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
return common_helpers::dir_exist(save_directory + appid + folder);
}

std::string Local_Storage::get_global_settings_path()
{
return save_directory + settings_storage_folder + PATH_SEPARATOR;
Expand Down
202 changes: 202 additions & 0 deletions dll/screenshot_format.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#include "dll/screenshot_format.h"
#include <algorithm>
#include <cmath>

#ifdef EMU_OVERLAY

namespace ScreenshotFormat {

// Simple float helper to uint8
inline uint8_t FloatToByte(float val) {
if (val < 0.0f) val = 0.0f;
if (val > 1.0f) val = 1.0f;
return static_cast<uint8_t>(val * 255.0f);
}

inline uint8_t Unorm16ToByte(uint16_t val) {
return static_cast<uint8_t>(val >> 8);
}

std::vector<uint8_t> ConvertToRGBA(const InGameOverlay::ScreenshotCallbackParameter_t* screenshot, int outChannels) {
std::vector<uint8_t> outData;
if (!screenshot || !screenshot->Data || screenshot->Width == 0 || screenshot->Height == 0) {
return outData;
}

uint32_t width = screenshot->Width;
uint32_t height = screenshot->Height;
outData.resize(width * height * outChannels);

uint8_t* srcRow = static_cast<uint8_t*>(screenshot->Data);
uint8_t* dst = outData.data();

for (uint32_t y = 0; y < height; ++y) {
uint8_t* srcPixel = srcRow;
for (uint32_t x = 0; x < width; ++x) {
uint8_t r = 0, g = 0, b = 0, a = 255;

switch (screenshot->Format) {
case InGameOverlay::ScreenshotDataFormat_t::R8G8B8:
r = srcPixel[0];
g = srcPixel[1];
b = srcPixel[2];
break;
case InGameOverlay::ScreenshotDataFormat_t::R8G8B8A8:
r = srcPixel[0];
g = srcPixel[1];
b = srcPixel[2];
a = srcPixel[3];
break;
case InGameOverlay::ScreenshotDataFormat_t::B8G8R8A8:
b = srcPixel[0];
g = srcPixel[1];
r = srcPixel[2];
a = srcPixel[3];
break;
case InGameOverlay::ScreenshotDataFormat_t::B8G8R8X8:
b = srcPixel[0];
g = srcPixel[1];
r = srcPixel[2];
a = 255;
break;
case InGameOverlay::ScreenshotDataFormat_t::A8R8G8B8:
case InGameOverlay::ScreenshotDataFormat_t::X8R8G8B8:
// D3D/little-endian format: ARGB DWORD -> memory order BGRA
b = srcPixel[0];
g = srcPixel[1];
r = srcPixel[2];
a = (screenshot->Format == InGameOverlay::ScreenshotDataFormat_t::A8R8G8B8) ? srcPixel[3] : 255;
break;

case InGameOverlay::ScreenshotDataFormat_t::A2R10G10B10: {
uint32_t pixel = *reinterpret_cast<uint32_t*>(srcPixel);
// 10 bits per channel in ARGB layout
b = static_cast<uint8_t>((pixel & 0x3FF) >> 2);
g = static_cast<uint8_t>(((pixel >> 10) & 0x3FF) >> 2);
r = static_cast<uint8_t>(((pixel >> 20) & 0x3FF) >> 2);
a = static_cast<uint8_t>((pixel >> 30) * 85); // 2 bits to 8 bits (0-3 -> 0-255)
break;
}
case InGameOverlay::ScreenshotDataFormat_t::A2B10G10R10: {
uint32_t pixel = *reinterpret_cast<uint32_t*>(srcPixel);
r = static_cast<uint8_t>((pixel & 0x3FF) >> 2);
g = static_cast<uint8_t>(((pixel >> 10) & 0x3FF) >> 2);
b = static_cast<uint8_t>(((pixel >> 20) & 0x3FF) >> 2);
a = static_cast<uint8_t>((pixel >> 30) * 85);
break;
}
case InGameOverlay::ScreenshotDataFormat_t::R10G10B10A2: {
uint32_t pixel = *reinterpret_cast<uint32_t*>(srcPixel);
r = static_cast<uint8_t>((pixel & 0x3FF) >> 2);
g = static_cast<uint8_t>(((pixel >> 10) & 0x3FF) >> 2);
b = static_cast<uint8_t>(((pixel >> 20) & 0x3FF) >> 2);
a = static_cast<uint8_t>((pixel >> 30) * 85);
break;
}

case InGameOverlay::ScreenshotDataFormat_t::R5G6B5: {
uint16_t pixel = *reinterpret_cast<uint16_t*>(srcPixel);
r = ((pixel >> 11) & 0x1F) << 3;
g = ((pixel >> 5) & 0x3F) << 2;
b = (pixel & 0x1F) << 3;
break;
}
case InGameOverlay::ScreenshotDataFormat_t::B5G6R5: {
uint16_t pixel = *reinterpret_cast<uint16_t*>(srcPixel);
b = ((pixel >> 11) & 0x1F) << 3;
g = ((pixel >> 5) & 0x3F) << 2;
r = (pixel & 0x1F) << 3;
break;
}
case InGameOverlay::ScreenshotDataFormat_t::A1R5G5B5:
case InGameOverlay::ScreenshotDataFormat_t::X1R5G5B5: {
uint16_t pixel = *reinterpret_cast<uint16_t*>(srcPixel);
b = (pixel & 0x1F) << 3;
g = ((pixel >> 5) & 0x1F) << 3;
r = ((pixel >> 10) & 0x1F) << 3;
a = (screenshot->Format == InGameOverlay::ScreenshotDataFormat_t::A1R5G5B5 && (pixel >> 15)) ? 255 : 0;
break;
}
case InGameOverlay::ScreenshotDataFormat_t::B5G5R5A1: {
uint16_t pixel = *reinterpret_cast<uint16_t*>(srcPixel);
a = (pixel & 0x01) ? 255 : 0;
r = ((pixel >> 1) & 0x1F) << 3;
g = ((pixel >> 6) & 0x1F) << 3;
b = ((pixel >> 11) & 0x1F) << 3;
break;
}

case InGameOverlay::ScreenshotDataFormat_t::R16G16B16A16_FLOAT: {
// Half float (16-bit float) format. We can rough-cast half to float.
// To keep it simple, we can convert half-precision float bits to single-precision float.
auto halfToFloat = [](uint16_t h) -> float {
uint32_t sign = (h & 0x8000) << 16;
uint32_t exp = (h & 0x7C00) >> 10;
uint32_t mant = h & 0x03FF;
if (exp == 0) {
if (mant == 0) return 0.0f;
while ((mant & 0x0400) == 0) {
mant <<= 1;
exp--;
}
exp++;
mant &= ~0x0400;
} else if (exp == 31) {
exp = 255;
} else {
exp = exp - 15 + 127;
}
uint32_t f = sign | (exp << 23) | (mant << 13);
return *reinterpret_cast<float*>(&f);
};
uint16_t* hf = reinterpret_cast<uint16_t*>(srcPixel);
r = FloatToByte(halfToFloat(hf[0]));
g = FloatToByte(halfToFloat(hf[1]));
b = FloatToByte(halfToFloat(hf[2]));
a = FloatToByte(halfToFloat(hf[3]));
break;
}
case InGameOverlay::ScreenshotDataFormat_t::R16G16B16A16_UNORM: {
uint16_t* un = reinterpret_cast<uint16_t*>(srcPixel);
r = Unorm16ToByte(un[0]);
g = Unorm16ToByte(un[1]);
b = Unorm16ToByte(un[2]);
a = Unorm16ToByte(un[3]);
break;
}
case InGameOverlay::ScreenshotDataFormat_t::R32G32B32A32_FLOAT: {
float* fl = reinterpret_cast<float*>(srcPixel);
r = FloatToByte(fl[0]);
g = FloatToByte(fl[1]);
b = FloatToByte(fl[2]);
a = FloatToByte(fl[3]);
break;
}

default:
// Unknown or unsupported format: default to standard RGBA (4 bytes)
r = srcPixel[0];
g = srcPixel[1];
b = srcPixel[2];
a = srcPixel[3];
break;
}

dst[0] = r;
dst[1] = g;
dst[2] = b;
if (outChannels == 4) {
dst[3] = 255;
}
dst += outChannels;
srcPixel += screenshot->PixelSize;
}
srcRow += screenshot->Pitch;
}

return outData;
}

} // namespace ScreenshotFormat

#endif // EMU_OVERLAY
21 changes: 21 additions & 0 deletions dll/settings_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,23 @@ static void parse_overlay_hotkeys(class Settings *settings_client, class Setting
settings_client->overlay_toggle_keys = combo;
settings_server->overlay_toggle_keys = combo;
}

auto screenshot_str = ini.GetValue("overlay::hotkeys", "screenshot_combo", "f12");
if (screenshot_str && screenshot_str[0]) {
auto combo = common_helpers::str_split(screenshot_str, "+");
std::for_each(combo.begin(), combo.end(),
[](std::string &item){ item = common_helpers::str_strip(item); }
);
combo.erase(
std::remove_if(
combo.begin(), combo.end(),
[](const std::string& item) { return item.empty(); }
),
combo.end()
);
settings_client->overlay_screenshot_keys = combo;
settings_server->overlay_screenshot_keys = combo;
}
}

// overlay::appearance
Expand Down Expand Up @@ -374,6 +391,10 @@ static void load_overlay_appearance(class Settings *settings_client, class Setti
uint32 time = (uint32)(std::stof(value, NULL) * 1000.0f); // convert sec to milli
settings_client->overlay_appearance.notification_duration_chat = time;
settings_server->overlay_appearance.notification_duration_chat = time;
} else if (name.compare("Notification_Duration_Screenshot") == 0) {
uint32 time = (uint32)(std::stof(value, NULL) * 1000.0f); // convert sec to milli
settings_client->overlay_appearance.notification_duration_screenshot = time;
settings_server->overlay_appearance.notification_duration_screenshot = time;
} else if (name.compare("Achievement_Unlock_Datetime_Format") == 0) {
settings_client->overlay_appearance.ach_unlock_datetime_format = value;
settings_server->overlay_appearance.ach_unlock_datetime_format = value;
Expand Down
4 changes: 3 additions & 1 deletion dll/steam_screenshots.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,10 @@ void Steam_Screenshots::TriggerScreenshot()
if (hooked) {
ScreenshotRequested_t data;
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
} else if (overlay_take_screenshot) {
overlay_take_screenshot();
} else {
PRINT_DEBUG(" TODO: Make the overlay take a screenshot");
PRINT_DEBUG(" No overlay available to take screenshot");
}
}

Expand Down
Loading