tr2/screenshots: improve screenshots support

Resolves #1766.
Resolves #1773.
This commit is contained in:
Marcin Kurczewski 2024-10-29 10:48:51 +01:00
parent 7ebb2a0141
commit f8efbc9005
36 changed files with 376 additions and 402 deletions

View file

@ -1,12 +1,15 @@
## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-0.5...develop) - ××××-××-××
- improved FMV mode appearance - removed black scanlines (#1729)
- improved FMV mode behavior - stopped switching screen resolutions (#1729)
- improved screenshots: now saved in the screenshots/ directory with level titles and timestamps as JPG or PNG, similar to TR1X (#1773)
- improved switch object names
- Switch Type 1 renamed to "Airlock Switch"
- Switch Type 2 renamed to "Small Switch"
- Switch Type 3 renamed to "Switch Button"
- Switch Type 4 renamed to "Lever/Switch"
- Switch Type 5 renamed to "Underwater Lever/Switch"
- fixed screenshots not working in windowed mode (#1766)
- fixed screenshots key not getting debounced (#1773)
- fixed `/give` not working with weapons (regression from 0.5)
- fixed the camera being cut off after using the gong hammer in Ice Palace (#1580)
- fixed the audio not being in sync when Lara strikes the gong in Ice Palace (#1725)

15
src/libtrx/game/clock.c Normal file
View file

@ -0,0 +1,15 @@
#include "game/clock.h"
#include <stdio.h>
#include <time.h>
size_t Clock_GetDateTime(char *const buffer, const size_t size)
{
time_t lt = time(0);
struct tm *tptr = localtime(&lt);
return snprintf(
buffer, size, "%04d%02d%02d_%02d%02d%02d", tptr->tm_year + 1900,
tptr->tm_mon + 1, tptr->tm_mday, tptr->tm_hour, tptr->tm_min,
tptr->tm_sec);
}

View file

@ -1,3 +1,6 @@
#pragma once
#include <stddef.h>
size_t Clock_GetDateTime(char *buffer, size_t size);
extern double Clock_GetHighPrecisionCounter(void);

View file

@ -6,3 +6,4 @@
extern bool Game_IsPlayable(void);
extern GAMEFLOW_LEVEL_TYPE Game_GetCurrentLevelType(void);
extern int32_t Game_GetCurrentLevelNum(void);

View file

@ -0,0 +1,3 @@
#pragma once
extern bool Output_MakeScreenshot(const char *path);

View file

@ -0,0 +1,3 @@
#pragma once
extern Output_MakeScreenshot(const char const path);

View file

@ -0,0 +1,10 @@
#pragma once
#include <stdbool.h>
typedef enum {
SCREENSHOT_FORMAT_JPEG,
SCREENSHOT_FORMAT_PNG,
} SCREENSHOT_FORMAT;
bool Screenshot_Make(SCREENSHOT_FORMAT format);

View file

@ -68,6 +68,7 @@ sources = [
'event_manager.c',
'filesystem.c',
'game/backpack.c',
'game/clock.c',
'game/console/cmd/config.c',
'game/console/cmd/die.c',
'game/console/cmd/end_level.c',
@ -122,6 +123,7 @@ sources = [
'json/json_write.c',
'log.c',
'memory.c',
'screenshot.c',
'strings/common.c',
'strings/fuzzy_match.c',
'vector.c',

141
src/libtrx/screenshot.c Normal file
View file

@ -0,0 +1,141 @@
#include "screenshot.h"
#include "filesystem.h"
#include "game/clock.h"
#include "game/game.h"
#include "game/gameflow/common.h"
#include "game/output.h"
#include "memory.h"
#include <stdio.h>
#include <string.h>
#define SCREENSHOTS_DIR "screenshots"
static char *M_GetScreenshotTitle(void);
static char *M_CleanScreenshotTitle(const char *source);
static char *M_GetScreenshotBaseName(void);
static const char *M_GetScreenshotFileExt(SCREENSHOT_FORMAT format);
static char *M_GetScreenshotPath(SCREENSHOT_FORMAT format);
static char *M_CleanScreenshotTitle(const char *const source)
{
// Sanitize screenshot title.
// - Replace spaces with underscores
// - Remove all non-alphanumeric characters
// - Merge consecutive underscores together
// - Remove leading underscores
// - Remove trailing underscores
char *result = Memory_Alloc(strlen(source) + 1);
bool last_was_underscore = false;
char *out = result;
for (size_t i = 0; i < strlen(source); i++) {
if (source[i] == ' ' || source[i] == '_') {
if (!last_was_underscore && out > result) {
*out++ = '_';
last_was_underscore = true;
}
} else if (((source[i] >= 'A' && source[i] <= 'Z')
|| (source[i] >= 'a' && source[i] <= 'z')
|| (source[i] >= '0' && source[i] <= '9'))) {
*out++ = source[i];
last_was_underscore = false;
}
}
*out++ = '\0';
// Strip trailing underscores
while (out[-1] == '_' && out >= result) {
out--;
}
*out = '\0';
return result;
}
static char *M_GetScreenshotTitle(void)
{
const int32_t level_num = Game_GetCurrentLevelNum();
if (level_num < 0) {
return Memory_DupStr("Intro");
}
const char *const level_title = Gameflow_GetLevelTitle(level_num);
if (level_title != NULL && strlen(level_title) > 0) {
char *clean_level_title = M_CleanScreenshotTitle(level_title);
if (clean_level_title != NULL && strlen(clean_level_title) > 0) {
return clean_level_title;
}
Memory_FreePointer(clean_level_title);
}
// If title totally invalid, name it based on level number
const char *const fmt = "Level_%d";
const size_t result_size = snprintf(NULL, 0, fmt, level_num) + 1;
char *result = Memory_Alloc(result_size);
snprintf(result, result_size, fmt, level_num);
return result;
}
static char *M_GetScreenshotBaseName(void)
{
char *screenshot_title = M_GetScreenshotTitle();
// Get timestamp
char date_time[30];
Clock_GetDateTime(date_time, 30);
// Full screenshot name
const char *const fmt = "%s_%s";
const size_t out_size =
snprintf(NULL, 0, fmt, date_time, screenshot_title) + 1;
char *out = Memory_Alloc(out_size);
snprintf(out, out_size, "%s_%s", date_time, screenshot_title);
return out;
}
static const char *M_GetScreenshotFileExt(const SCREENSHOT_FORMAT format)
{
switch (format) {
case SCREENSHOT_FORMAT_JPEG:
return "jpg";
case SCREENSHOT_FORMAT_PNG:
return "png";
default:
return "jpg";
}
}
static char *M_GetScreenshotPath(const SCREENSHOT_FORMAT format)
{
char *base_name = M_GetScreenshotBaseName();
const char *const ext = M_GetScreenshotFileExt(format);
char *full_path = Memory_Alloc(
strlen(SCREENSHOTS_DIR) + strlen(base_name) + strlen(ext) + 6);
sprintf(full_path, "%s/%s.%s", SCREENSHOTS_DIR, base_name, ext);
if (File_Exists(full_path)) {
for (int i = 2; i < 100; i++) {
sprintf(
full_path, "%s/%s_%d.%s", SCREENSHOTS_DIR, base_name, i, ext);
if (!File_Exists(full_path)) {
break;
}
}
}
Memory_FreePointer(&base_name);
return full_path;
}
bool Screenshot_Make(const SCREENSHOT_FORMAT format)
{
File_CreateDirectory(SCREENSHOTS_DIR);
char *full_path = M_GetScreenshotPath(format);
const bool result = Output_MakeScreenshot(full_path);
Memory_FreePointer(&full_path);
return result;
}

View file

@ -4,6 +4,7 @@
#include <libtrx/config.h>
#include <libtrx/gfx/common.h>
#include <libtrx/screenshot.h>
#include <stdbool.h>
#include <stdint.h>

View file

@ -10,7 +10,6 @@
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <time.h>
static double M_GetElapsedUnit(CLOCK_TIMER *const timer, const double unit);
static bool M_CheckElapsedUnit(
@ -76,17 +75,6 @@ double Clock_GetSpeedMultiplier(void)
}
}
void Clock_GetDateTime(char *date_time)
{
time_t lt = time(0);
struct tm *tptr = localtime(&lt);
sprintf(
date_time, "%04d%02d%02d_%02d%02d%02d", tptr->tm_year + 1900,
tptr->tm_mon + 1, tptr->tm_mday, tptr->tm_hour, tptr->tm_min,
tptr->tm_sec);
}
int32_t Clock_GetFrameAdvance(void)
{
return g_Config.rendering.fps == 30 ? 2 : 1;

View file

@ -1,5 +1,7 @@
#pragma once
#include <libtrx/game/clock.h>
#include <stdbool.h>
#include <stdint.h>
@ -44,6 +46,4 @@ bool Clock_CheckElapsedMilliseconds(CLOCK_TIMER *timer, int32_t wait);
// by the turbo cheat multiplier.
bool Clock_CheckElapsedRawMilliseconds(CLOCK_TIMER *timer, int32_t how_often);
void Clock_GetDateTime(char *date_time);
int32_t Clock_GetFrameAdvance(void);

View file

@ -252,6 +252,11 @@ GAMEFLOW_LEVEL_TYPE Game_GetCurrentLevelType(void)
return g_GameInfo.current_level_type;
}
extern int32_t Game_GetCurrentLevelNum(void)
{
return g_CurrentLevel;
}
bool Game_IsPlayable(void)
{
if (g_GameInfo.current_level_type == GFL_TITLE

View file

@ -20,6 +20,7 @@
#include <libtrx/engine/image.h>
#include <libtrx/filesystem.h>
#include <libtrx/game/console/common.h>
#include <libtrx/gfx/context.h>
#include <libtrx/memory.h>
#include <libtrx/utils.h>
@ -1364,9 +1365,10 @@ void Output_ApplyTint(float *r, float *g, float *b)
}
}
bool Output_MakeScreenshot(const char *path)
bool Output_MakeScreenshot(const char *const path)
{
return S_Output_MakeScreenshot(path);
GFX_Context_ScheduleScreenshot(path);
return true;
}
int Output_GetObjectBounds(const BOUNDS_16 *const bounds)

View file

@ -73,7 +73,7 @@ static void M_End(void)
static PHASE_CONTROL M_Control(int32_t nframes)
{
if (m_Status == PS_ACTIVE) {
Shell_MakeScreenshot();
Screenshot_Make(g_Config.screenshot_format);
Sound_Effect(SFX_MENU_CHOOSE, NULL, SPM_ALWAYS);
m_Status = PS_COOLDOWN;
} else if (m_Status == PS_COOLDOWN) {

View file

@ -1248,7 +1248,7 @@ static JSON_OBJECT *M_DumpCurrentMusic(void)
{
JSON_OBJECT *current_music_obj = JSON_ObjectNew();
JSON_ObjectAppendInt(
current_music_obj, "current_track", Music_GetCurrentTrack());
current_music_obj, "current_track", Music_GetCurrentPlayingTrack());
JSON_ObjectAppendDouble(
current_music_obj, "timestamp", Music_GetTimestamp());

View file

@ -33,7 +33,6 @@
#include <stdio.h>
#include <string.h>
#define SCREENSHOTS_DIR "screenshots"
#define LEVEL_TITLE_SIZE 25
#define TIMESTAMP_SIZE 20
@ -41,79 +40,6 @@ static const char m_TR1XGameflowPath[] = "cfg/TR1X_gameflow.json5";
static const char m_TR1XGameflowGoldPath[] = "cfg/TR1X_gameflow_ub.json5";
static const char m_TR1XGameflowDemoPath[] = "cfg/TR1X_gameflow_demo_pc.json5";
static char *M_GetScreenshotName(void);
static char *M_GetScreenshotName(void)
{
// Get level title of unknown length
char level_title[100];
if (g_CurrentLevel < 0) {
strncpy(level_title, "Intro", LEVEL_TITLE_SIZE - 1);
} else {
strncpy(
level_title, g_GameFlow.levels[g_CurrentLevel].level_title,
LEVEL_TITLE_SIZE - 1);
}
level_title[LEVEL_TITLE_SIZE] = '\0';
// Prepare level title for screenshot
char *check = level_title;
bool prev_us = true; // '_' after timestamp before title
int idx = 0;
while (*check != '\0') {
if (*check == ' ') {
// Replace spaces with a single underscore
if (prev_us) {
memmove(
&level_title[idx], &level_title[idx + 1],
strlen(level_title) - idx);
} else {
*check++ = '_';
idx++;
prev_us = true;
}
} else if (((*check < 'A' || *check > 'Z')
&& (*check < 'a' || *check > 'z')
&& (*check < '0' || *check > '9'))) {
// Strip non alphanumeric chars
memmove(
&level_title[idx], &level_title[idx + 1],
strlen(level_title) - idx);
} else {
check++;
idx++;
prev_us = false;
}
}
// If title totally invalid, name it based on level number
if (strlen(level_title) == 0) {
sprintf(level_title, "Level_%d", g_CurrentLevel);
prev_us = false;
}
// Strip trailing underscores
if (prev_us) {
check--;
idx--;
memmove(
&level_title[idx], &level_title[idx + 1], strlen(level_title) - 1);
prev_us = false;
}
// Get timestamp
char date_time[TIMESTAMP_SIZE];
Clock_GetDateTime(date_time);
// Full screenshot name
size_t out_size = snprintf(NULL, 0, "%s_%s", date_time, level_title) + 1;
char *out = Memory_Alloc(out_size);
snprintf(out, out_size, "%s_%s", date_time, level_title);
return out;
}
void Shell_Init(const char *gameflow_path)
{
Text_Init();
@ -343,45 +269,3 @@ void Shell_ProcessInput(void)
Clock_CycleTurboSpeed(!g_Input.slow);
}
}
bool Shell_MakeScreenshot(void)
{
File_CreateDirectory(SCREENSHOTS_DIR);
char *filename = M_GetScreenshotName();
const char *ext;
switch (g_Config.screenshot_format) {
case SCREENSHOT_FORMAT_JPEG:
ext = "jpg";
break;
case SCREENSHOT_FORMAT_PNG:
ext = "png";
break;
default:
ext = "jpg";
break;
}
bool result = false;
char *full_path = Memory_Alloc(
strlen(SCREENSHOTS_DIR) + strlen(filename) + strlen(ext) + 6);
sprintf(full_path, "%s/%s.%s", SCREENSHOTS_DIR, filename, ext);
if (!File_Exists(full_path)) {
result = Output_MakeScreenshot(full_path);
} else {
// name already exists, so add a number to name
for (int i = 2; i < 100; i++) {
sprintf(
full_path, "%s/%s_%d.%s", SCREENSHOTS_DIR, filename, i, ext);
if (!File_Exists(full_path)) {
result = Output_MakeScreenshot(full_path);
break;
}
}
}
Memory_FreePointer(&filename);
Memory_FreePointer(&full_path);
return result;
}

View file

@ -8,4 +8,3 @@ void Shell_Shutdown(void);
void Shell_Main(void);
void Shell_ExitSystem(const char *message);
void Shell_ExitSystemFmt(const char *fmt, ...);
bool Shell_MakeScreenshot(void);

View file

@ -2,6 +2,7 @@
#include <libtrx/enum_map.h>
#include <libtrx/game/objects/ids.h>
#include <libtrx/screenshot.h>
void EnumMap_Init(void)
{

View file

@ -942,11 +942,6 @@ typedef enum {
TB_ON = 1,
} TRISTATE_BOOL;
typedef enum {
SCREENSHOT_FORMAT_JPEG,
SCREENSHOT_FORMAT_PNG,
} SCREENSHOT_FORMAT;
typedef enum {
UI_STYLE_PS1,
UI_STYLE_PC,

View file

@ -1272,12 +1272,6 @@ void S_Output_DownloadTextures(int32_t pages)
m_EnvMapTexture = GFX_3D_Renderer_RegisterEnvironmentMap(m_Renderer3D);
}
bool S_Output_MakeScreenshot(const char *path)
{
GFX_Context_ScheduleScreenshot(path);
return true;
}
void S_Output_ScreenBox(
int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 col_dark,
RGBA_8888 col_light, float thickness)

View file

@ -58,8 +58,6 @@ void S_Output_DrawLightningSegment(
int x1, int y1, int z1, int thickness1, int x2, int y2, int z2,
int thickness2);
bool S_Output_MakeScreenshot(const char *path);
void S_Output_ScreenBox(
int32_t sx, int32_t sy, int32_t w, int32_t h, RGBA_8888 col_dark,
RGBA_8888 col_light, float thickness);

View file

@ -193,7 +193,7 @@ void S_Shell_SpinMessageLoop(void)
case SDL_KEYUP:
if (event.key.keysym.sym == SDLK_PRINTSCREEN) {
Shell_MakeScreenshot();
Screenshot_Make(g_Config.screenshot_format);
break;
}

View file

@ -1,6 +1,7 @@
#pragma once
#include <libtrx/config.h>
#include <libtrx/screenshot.h>
#include <stdbool.h>
@ -10,6 +11,10 @@ typedef struct {
struct {
bool fix_m16_accuracy;
} gameplay;
struct {
SCREENSHOT_FORMAT screenshot_format;
} rendering;
} CONFIG;
extern CONFIG g_Config;

View file

@ -1 +1,2 @@
CFG_BOOL(g_Config, gameplay.fix_m16_accuracy, true)
CFG_ENUM(g_Config, rendering.screenshot_format, SCREENSHOT_FORMAT_JPEG, SCREENSHOT_FORMAT)

View file

@ -32,7 +32,11 @@
#include "lib/dinput.h"
#include "specific/s_flagged_string.h"
#include <libtrx/engine/image.h>
#include <libtrx/filesystem.h>
#include <libtrx/game/ui/common.h>
#include <libtrx/log.h>
#include <libtrx/memory.h>
#include <libtrx/utils.h>
#include <assert.h>
@ -274,245 +278,6 @@ int32_t __cdecl RenderErrorBox(int32_t error_code)
return UT_MessageBox(buffer, 0);
}
void __cdecl ScreenshotPCX(void)
{
LPDDS screen = g_SavedAppSettings.render_mode == RM_SOFTWARE
? g_RenderBufferSurface
: g_PrimaryBufferSurface;
DDSURFACEDESC desc = { .dwSize = sizeof(DDSURFACEDESC), 0 };
int32_t result;
while (true) {
result = IDirectDrawSurface_Lock(screen, 0, &desc, 0, 0);
if (result != DDERR_WASSTILLDRAWING) {
break;
}
}
if (result == DDERR_SURFACELOST) {
IDirectDrawSurface_Restore(screen);
}
if (FAILED(result)) {
return;
}
uint8_t *pcx_data;
int32_t pcx_size = CompPCX(
desc.lpSurface, desc.dwWidth, desc.dwHeight, g_GamePalette8, &pcx_data);
IDirectDrawSurface_Unlock(screen, &desc);
if (!pcx_size) {
return;
}
g_ScreenshotCounter++;
if (g_ScreenshotCounter > 9999) {
g_ScreenshotCounter = 1;
}
char file_name[20];
sprintf(file_name, "tomb%04d.pcx", g_ScreenshotCounter);
HANDLE handle = CreateFileA(
file_name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
NULL);
DWORD bytes_written;
WriteFile(handle, pcx_data, pcx_size, &bytes_written, 0);
CloseHandle(handle);
GlobalFree(pcx_data);
}
size_t __cdecl CompPCX(
uint8_t *bitmap, int32_t width, int32_t height, RGB_888 *palette,
uint8_t **pcx_data)
{
*pcx_data = (uint8_t *)GlobalAlloc(
GMEM_FIXED,
sizeof(PCX_HEADER) + sizeof(RGB_888) * 256 + width * height * 2);
if (*pcx_data == NULL) {
return 0;
}
PCX_HEADER *pcx_header = (PCX_HEADER *)*pcx_data;
pcx_header->manufacturer = 10;
pcx_header->version = 5;
pcx_header->rle = 1;
pcx_header->bpp = 8;
pcx_header->planes = 1;
pcx_header->x_min = 0;
pcx_header->y_min = 0;
pcx_header->x_max = width - 1;
pcx_header->y_max = height - 1;
pcx_header->h_dpi = width;
pcx_header->v_dpi = height;
pcx_header->bytes_per_line = width;
uint8_t *pic_data = *pcx_data + sizeof(PCX_HEADER);
for (int32_t y = 0; y < height; y++) {
pic_data += EncodeLinePCX(bitmap, width, pic_data);
bitmap += width;
}
*pic_data++ = 0x0C;
for (int32_t i = 0; i < 256; i++) {
*pic_data++ = palette[i].red;
*pic_data++ = palette[i].green;
*pic_data++ = palette[i].blue;
}
return pic_data - *pcx_data + sizeof(RGB_888) * 256;
}
size_t __cdecl EncodeLinePCX(
const uint8_t *src, const int32_t width, uint8_t *dst)
{
const uint8_t *const dst_start = dst;
int32_t run_count = 1;
uint8_t last = *src;
for (int32_t i = 1; i < width; i++) {
uint8_t current = *src++;
if (*src == last) {
run_count++;
if (run_count == 63) {
const size_t add = EncodePutPCX(last, 0x3Fu, dst);
if (add == 0) {
return 0;
}
dst += add;
run_count = 0;
}
} else {
if (run_count != 0) {
const size_t add = EncodePutPCX(last, run_count, dst);
if (add == 0) {
return 0;
}
dst += add;
}
last = current;
run_count = 1;
}
}
if (run_count != 0) {
const size_t add = EncodePutPCX(last, run_count, dst);
if (add == 0) {
return 0;
}
dst += add;
}
const size_t total = dst - dst_start;
return total;
}
size_t __cdecl EncodePutPCX(uint8_t value, uint8_t num, uint8_t *buffer)
{
if (num == 0 || num > 63) {
return 0;
}
if (num == 1 && (value & 0xC0) != 0xC0) {
buffer[0] = value;
return 1;
}
buffer[0] = num | 0xC0;
buffer[1] = value;
return 2;
}
void __cdecl ScreenshotTGA(IDirectDrawSurface3 *screen, int32_t bpp)
{
DDSURFACEDESC desc = {
.dwSize = sizeof(DDSURFACEDESC),
0,
};
if (FAILED(WinVidBufferLock(screen, &desc, 0x21u))) {
return;
}
const int32_t width = desc.dwWidth;
const int32_t height = desc.dwHeight;
g_ScreenshotCounter++;
char file_name[20];
sprintf(file_name, "tomb%04d.tga", g_ScreenshotCounter);
FILE *handle = fopen(file_name, "wb");
if (!handle) {
return;
}
const TGA_HEADER header = {
.id_length = 0,
.color_map_type = 0,
.data_type_code = 2, // Uncompressed, RGB images
.color_map_origin = 0,
.color_map_length = 0,
.color_map_depth = 0,
.x_origin = 0,
.y_origin = 0,
.width = width,
.height = height,
.bpp = 16,
.image_descriptor = 0,
};
fwrite(&header, sizeof(TGA_HEADER), 1, handle);
uint8_t *tga_pic =
(uint8_t *)GlobalAlloc(GMEM_FIXED, width * height * (bpp / 8));
uint8_t *src = desc.lpSurface + desc.lPitch * (height - 1);
uint8_t *dst = tga_pic;
for (int32_t y = 0; y < height; y++) {
if (desc.ddpfPixelFormat.dwRBitMask == 0xF800) {
// R5G6B5 - transform
for (int32_t x = 0; x < width; x++) {
const uint16_t sample = ((uint16_t *)src)[x];
((uint16_t *)dst)[x] =
((sample & 0xFFC0) >> 1) | (sample & 0x001F);
}
} else {
// X1R5G5B5 - good
memcpy(dst, src, sizeof(uint16_t) * width);
}
src -= desc.lPitch;
dst += sizeof(uint16_t) * width;
}
fwrite(tga_pic, 2 * height * width, 1, handle);
cleanup:
if (tga_pic) {
GlobalFree(tga_pic);
}
if (handle) {
fclose(handle);
}
WinVidBufferUnlock(screen, &desc);
}
void __cdecl Screenshot(LPDDS screen)
{
DDSURFACEDESC desc = { 0 };
desc.dwSize = sizeof(DDSURFACEDESC);
if (SUCCEEDED(IDirectDrawSurface_GetSurfaceDesc(screen, &desc))) {
if (desc.ddpfPixelFormat.dwRGBBitCount == 8) {
ScreenshotPCX();
} else if (desc.ddpfPixelFormat.dwRGBBitCount == 16) {
ScreenshotTGA(screen, 16);
}
}
}
bool __cdecl DInputCreate(void)
{
return SUCCEEDED(DirectInputCreate(g_GameModule, 1280, &g_DInput, NULL));

View file

@ -11,13 +11,6 @@ int32_t __stdcall WinMain(
int32_t nShowCmd);
const char *__cdecl DecodeErrorMessage(int32_t error_code);
int32_t __cdecl RenderErrorBox(int32_t error_code);
void __cdecl ScreenshotPCX(void);
size_t __cdecl CompPCX(
uint8_t *bitmap, int32_t width, int32_t height, RGB_888 *palette,
uint8_t **pcx_data);
size_t __cdecl EncodeLinePCX(const uint8_t *src, int32_t width, uint8_t *dst);
size_t __cdecl EncodePutPCX(uint8_t value, uint8_t num, uint8_t *buffer);
void __cdecl Screenshot(LPDDS screen);
bool __cdecl DInputCreate(void);
void __cdecl DInputRelease(void);
void __cdecl WinInReadKeyboard(uint8_t *input_data);

View file

@ -298,6 +298,11 @@ GAMEFLOW_LEVEL_TYPE Game_GetCurrentLevelType(void)
return g_GameInfo.current_level.type;
}
extern int32_t Game_GetCurrentLevelNum(void)
{
return g_CurrentLevel;
}
bool Game_IsPlayable(void)
{
if (g_GameInfo.current_level.type == GFL_TITLE

View file

@ -1,5 +1,6 @@
#include "game/output.h"
#include "decomp/decomp.h"
#include "game/hwr.h"
#include "game/math.h"
#include "game/matrix.h"
@ -8,8 +9,12 @@
#include "global/funcs.h"
#include "global/vars.h"
#include <libtrx/engine/image.h>
#include <libtrx/log.h>
#include <libtrx/utils.h>
#include <assert.h>
#define VBUF_VISIBLE(a, b, c) \
(((a).ys - (b).ys) * ((c).xs - (b).xs) \
>= ((c).ys - (b).ys) * ((a).xs - (b).xs))
@ -4481,3 +4486,125 @@ void __cdecl Output_DrawScaledSpriteC(const int16_t *const obj_ptr)
v_base += v_add;
}
}
bool __cdecl Output_MakeScreenshot(const char *const path)
{
LOG_INFO("Taking screenshot");
LPDDS screen = g_SavedAppSettings.render_mode == RM_SOFTWARE
? g_RenderBufferSurface
: g_BackBufferSurface;
DDSURFACEDESC desc = { .dwSize = sizeof(DDSURFACEDESC) };
HRESULT rc;
while (true) {
rc = IDirectDrawSurface_GetSurfaceDesc(screen, &desc);
if (rc != DDERR_WASSTILLDRAWING) {
break;
}
}
if (rc == DDERR_SURFACELOST) {
IDirectDrawSurface_Restore(screen);
}
if (FAILED(rc)) {
LOG_ERROR("Failed to get surface description: %x", rc);
return false;
}
rc = WinVidBufferLock(screen, &desc, DDLOCK_WRITEONLY | DDLOCK_WAIT);
if (FAILED(rc)) {
LOG_ERROR("Failed to lock surface: %x", rc);
return false;
}
int32_t src_x = 0;
int32_t src_y = 0;
int32_t width = g_GameWindowWidth;
int32_t height = g_GameWindowHeight;
IMAGE *const image = Image_Create(width, height);
for (int32_t y = 0; y < height; y++) {
uint8_t *src = desc.lpSurface + desc.lPitch * (y + src_y);
src += src_x * (desc.ddpfPixelFormat.dwRGBBitCount / 8);
IMAGE_PIXEL *dst = &image->data[width * y];
switch (desc.ddpfPixelFormat.dwRGBBitCount) {
case 8:
assert(desc.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8);
for (int32_t x = 0; x < width; x++) {
dst->r = g_GamePalette8[*src].red;
dst->g = g_GamePalette8[*src].green;
dst->b = g_GamePalette8[*src].blue;
src++;
dst++;
}
break;
case 16:
if (desc.ddpfPixelFormat.dwRBitMask == 0xF800) {
for (int32_t x = 0; x < width; x++) {
dst->r = (*(uint16_t *)src & 0xF800) >> 8;
dst->g = (*(uint16_t *)src & 0x07E0) >> 3;
dst->b = (*(uint16_t *)src & 0x001F) << 3;
dst++;
src += 2;
}
} else {
for (int32_t x = 0; x < width; x++) {
dst->r = (*(uint16_t *)src & 0x001F) << 3;
dst->g = (*(uint16_t *)src & 0x07E0) >> 3;
dst->b = (*(uint16_t *)src & 0xF800) >> 8;
dst++;
src += 2;
}
}
break;
case 24:
if (desc.ddpfPixelFormat.dwRBitMask == 255) {
for (int32_t x = 0; x < width; x++) {
dst->r = src[0];
dst->g = src[1];
dst->b = src[2];
dst++;
src += 3;
}
} else {
for (int32_t x = 0; x < width; x++) {
dst->r = src[2];
dst->g = src[1];
dst->b = src[0];
dst++;
src += 3;
}
}
break;
case 32:
if (desc.ddpfPixelFormat.dwRBitMask == 255) {
for (int32_t x = 0; x < width; x++) {
dst->r = src[0];
dst->g = src[1];
dst->b = src[2];
dst++;
src += 4;
}
} else {
for (int32_t x = 0; x < width; x++) {
dst->r = src[2];
dst->g = src[1];
dst->b = src[0];
dst++;
src += 4;
}
}
}
src -= desc.lPitch;
}
const bool ret = Image_SaveToFile(image, path);
Image_Free(image);
WinVidBufferUnlock(screen, &desc);
return ret;
}

View file

@ -171,3 +171,5 @@ void __cdecl Output_DrawScreenSprite(
int16_t sprite_idx, int16_t shade, uint16_t flags);
void __cdecl Output_DrawScaledSpriteC(const int16_t *obj_ptr);
bool __cdecl Output_MakeScreenshot(const char *path);

View file

@ -1,5 +1,6 @@
#include <libtrx/enum_map.h>
#include <libtrx/game/objects/ids.h>
#include <libtrx/screenshot.h>
void EnumMap_Init(void)
{

View file

@ -0,0 +1,3 @@
ENUM_MAP_DEFINE(SCREENSHOT_FORMAT, SCREENSHOT_FORMAT_JPEG, "jpg")
ENUM_MAP_DEFINE(SCREENSHOT_FORMAT, SCREENSHOT_FORMAT_JPEG, "jpeg")
ENUM_MAP_DEFINE(SCREENSHOT_FORMAT, SCREENSHOT_FORMAT_PNG, "png")

View file

@ -235,11 +235,6 @@ static void M_DecompGeneral(const bool enable)
INJECT(enable, 0x0044E520, WinMain);
INJECT(enable, 0x0044E700, GameInit);
INJECT(enable, 0x0044E7A0, WinGameStart);
INJECT(enable, 0x0044E8E0, ScreenshotPCX);
INJECT(enable, 0x0044E9F0, CompPCX);
INJECT(enable, 0x0044EAA0, EncodeLinePCX);
INJECT(enable, 0x0044EB80, EncodePutPCX);
INJECT(enable, 0x0044EBC0, Screenshot);
INJECT(enable, 0x00454C50, TitleSequence);
INJECT(enable, 0x004550C0, S_SaveSettings);
INJECT(enable, 0x00455140, S_LoadSettings);

View file

@ -1,5 +1,6 @@
#include "specific/s_input.h"
#include "config.h"
#include "decomp/decomp.h"
#include "game/console/common.h"
#include "game/gameflow/gameflow_new.h"
@ -10,6 +11,7 @@
#include "global/funcs.h"
#include "global/vars.h"
#include <libtrx/screenshot.h>
#include <libtrx/utils.h>
#include <dinput.h>
@ -53,6 +55,7 @@ INPUT_LAYOUT g_Layout[2] = {
} }
};
static bool m_IsScreenshotPressed = false;
static bool m_IsF3Pressed = false;
static bool m_IsF4Pressed = false;
static bool m_IsF7Pressed = false;
@ -235,7 +238,12 @@ bool __cdecl S_Input_Update(void)
}
if (KEY_DOWN(DIK_S)) {
Screenshot(g_PrimaryBufferSurface);
if (!m_IsScreenshotPressed) {
m_IsScreenshotPressed = true;
Screenshot_Make(g_Config.rendering.screenshot_format);
}
} else {
m_IsScreenshotPressed = false;
}
const bool is_shift_pressed = KEY_DOWN(DIK_LSHIFT) || KEY_DOWN(DIK_RSHIFT);

View file

@ -6,10 +6,20 @@
"window_title_about": "About TR2X Config Tool",
"label_about_details": "Visit the TR2X GitHub page for further information."
},
"Enums": {
"screenshot_format": {
"jpg": "JPEG",
"png": "PNG"
}
},
"Properties": {
"fix_m16_accuracy": {
"Title": "Fix M16 accuracy",
"Description": "Fixes the accuracy of the M16 while Lara is running."
},
"screenshot_format": {
"Title": "Screenshot format",
"Description": "Screenshot file format."
}
}
}

View file

@ -1,4 +1,10 @@
{
"Enums": {
"screenshot_format": [
"jpg",
"png"
]
},
"CategorisedProperties": [
{
"ID": "controls",
@ -36,7 +42,12 @@
"ID": "graphics",
"Image": "Graphics/graphic5.jpg",
"Properties": [
{
"Field": "screenshot_format",
"DataType": "Enum",
"EnumKey": "screenshot_format",
"DefaultValue": "jpg"
}
]
},
{