mirror of
https://github.com/LostArtefacts/TRX.git
synced 2025-04-28 12:47:58 +03:00
tr2/screenshots: improve screenshots support
Resolves #1766. Resolves #1773.
This commit is contained in:
parent
7ebb2a0141
commit
f8efbc9005
36 changed files with 376 additions and 402 deletions
|
@ -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
15
src/libtrx/game/clock.c
Normal 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(<);
|
||||
|
||||
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);
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
size_t Clock_GetDateTime(char *buffer, size_t size);
|
||||
extern double Clock_GetHighPrecisionCounter(void);
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
|
||||
extern bool Game_IsPlayable(void);
|
||||
extern GAMEFLOW_LEVEL_TYPE Game_GetCurrentLevelType(void);
|
||||
extern int32_t Game_GetCurrentLevelNum(void);
|
||||
|
|
3
src/libtrx/include/libtrx/game/output.h
Normal file
3
src/libtrx/include/libtrx/game/output.h
Normal file
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
extern bool Output_MakeScreenshot(const char *path);
|
3
src/libtrx/include/libtrx/output.h
Normal file
3
src/libtrx/include/libtrx/output.h
Normal file
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
extern Output_MakeScreenshot(const char const path);
|
10
src/libtrx/include/libtrx/screenshot.h
Normal file
10
src/libtrx/include/libtrx/screenshot.h
Normal 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);
|
|
@ -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
141
src/libtrx/screenshot.c
Normal 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;
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <libtrx/config.h>
|
||||
#include <libtrx/gfx/common.h>
|
||||
#include <libtrx/screenshot.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
|
|
@ -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(<);
|
||||
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <libtrx/enum_map.h>
|
||||
#include <libtrx/game/objects/ids.h>
|
||||
#include <libtrx/screenshot.h>
|
||||
|
||||
void EnumMap_Init(void)
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
CFG_BOOL(g_Config, gameplay.fix_m16_accuracy, true)
|
||||
CFG_ENUM(g_Config, rendering.screenshot_format, SCREENSHOT_FORMAT_JPEG, SCREENSHOT_FORMAT)
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <libtrx/enum_map.h>
|
||||
#include <libtrx/game/objects/ids.h>
|
||||
#include <libtrx/screenshot.h>
|
||||
|
||||
void EnumMap_Init(void)
|
||||
{
|
||||
|
|
|
@ -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")
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue