Skip to content

Commit

Permalink
New Plugin: FrameTime Logger
Browse files Browse the repository at this point in the history
  • Loading branch information
illusion0001 committed Aug 6, 2023
1 parent d3bd48e commit 6c3a06f
Show file tree
Hide file tree
Showing 3 changed files with 361 additions and 1 deletion.
14 changes: 13 additions & 1 deletion .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Plugins allows you to customize your games like never before!

While we make every effort to deliver high quality products, we do not guarantee that our products are free from defects. Our software is provided **as is** and you use the software at your own risk.

# Getting Started
# Quick Start

- Load [GoldHEN 2.3](https://github.com/GoldHEN/GoldHEN/releases/latest) or newer on your PS4.
- Enable option to load plugins in Plugins Menu.
Expand Down Expand Up @@ -94,6 +94,18 @@ Author(s):

Removes framerate limit for games using system function `sceVideoOutSetFlipRate`.

### FrameTime Logger

Plugin filename: `frame_logger.prx`

Author(s):
- [illusion](https://github.com/illusion0001)

- Log frametime statistics.
- Press `L3 + L1 + Triangle` to start capturing data.
- Press `L3 + R3 + L1 + R1 + Square` to stop plugin.
- Logs data to `/data/frame_logger/`.

### GamePad helper Plugin

Plugin filename: `gamepad_helper.prx`
Expand Down
99 changes: 99 additions & 0 deletions plugin_src/frame_logger/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Library metadata.

DEBUG_FLAGS = -D__FINAL__=1
LOG_TYPE = -D__USE_PRINTF__
BUILD_TYPE = _final

ifeq ($(DEBUG),1)
DEBUG_FLAGS = -D__FINAL__=0
BUILD_TYPE = _debug
endif

TYPE := $(BUILD_TYPE)
FINAL := $(DEBUG_FLAGS)
BUILD_FOLDER := $(shell pwd)/../../bin/plugins
OUTPUT_PRX := $(shell basename $(CURDIR))
TARGET := $(BUILD_FOLDER)/prx$(TYPE)/$(OUTPUT_PRX)
TARGET_ELF := $(BUILD_FOLDER)/elf$(TYPE)/$(OUTPUT_PRX)
TARGETSTUB := $(OUTPUT_PRX).so

# Libraries linked into the ELF.
LIBS := -lSceLibcInternal -lSceGnmDriver -lScePad -lGoldHEN_Hook -lkernel -lSceSysmodule -lSceSystemService -lSceUserService

EXTRAFLAGS := $(DEBUG_FLAGS) $(LOG_TYPE) -fcolor-diagnostics -Wall

# You likely won't need to touch anything below this point.
# Root vars
TOOLCHAIN := $(OO_PS4_TOOLCHAIN)
GH_SDK := $(GOLDHEN_SDK)
PROJDIR := ../$(shell basename $(CURDIR))/source
INTDIR := ../$(shell basename $(CURDIR))/build
INCLUDEDIR := ../$(shell basename $(CURDIR))/include
COMMON_DIR := ../../common

# Define objects to build
CFILES := $(wildcard $(PROJDIR)/*.c)
CPPFILES := $(wildcard $(PROJDIR)/*.cpp)
COMMONFILES := $(wildcard $(COMMONDIR)/*.cpp)
OBJS := $(patsubst $(PROJDIR)/%.c, $(INTDIR)/%.o, $(CFILES)) $(patsubst $(PROJDIR)/%.cpp, $(INTDIR)/%.o, $(CPPFILES)) $(patsubst $(COMMONDIR)/%.cpp, $(INTDIR)/%.o, $(COMMONFILES))
STUBOBJS := $(patsubst $(PROJDIR)/%.c, $(INTDIR)/%.o, $(CFILES)) $(patsubst $(PROJDIR)/%.cpp, $(INTDIR)/%.o.stub, $(CPPFILES)) $(patsubst $(COMMONDIR)/%.cpp, $(INTDIR)/%.o.stub, $(COMMONFILES))

# Define final C/C++ flags
CFLAGS := $(FINAL) --target=x86_64-pc-freebsd12-elf -fPIC -funwind-tables -c $(EXTRAFLAGS) -isysroot $(TOOLCHAIN) -isystem $(TOOLCHAIN)/include -I$(GH_SDK)/include -I$(INCLUDEDIR) -I$(COMMON_DIR) $(O_FLAG)
CXXFLAGS := $(CFLAGS) -isystem $(TOOLCHAIN)/$(INCLUDEDIR)/c++/v1
LDFLAGS := -m elf_x86_64 -pie --script $(TOOLCHAIN)/link.x -e _init --eh-frame-hdr -L$(TOOLCHAIN)/lib -L$(GH_SDK) $(LIBS)

# Create the intermediate directory incase it doesn't already exist.
_unused := $(shell mkdir -p $(INTDIR))

# Check for linux vs macOS and account for clang/ld path
UNAME_S := $(shell uname -s)

ifeq ($(UNAME_S),Linux)
CC := clang
CCX := clang++
LD := ld.lld
CDIR := linux
endif
ifeq ($(UNAME_S),Darwin)
CC := /usr/local/opt/llvm/bin/clang
CCX := /usr/local/opt/llvm/bin/clang++
LD := /usr/local/opt/llvm/bin/ld.lld
CDIR := macos
endif

$(TARGET): $(INTDIR) $(OBJS)
$(LD) $(GH_SDK)/build/crtprx.o $(INTDIR)/*.o -o $(TARGET_ELF).elf $(LDFLAGS)
$(TOOLCHAIN)/bin/$(CDIR)/create-fself -in=$(TARGET_ELF).elf -out=$(TARGET_ELF).oelf --lib=$(TARGET).prx --paid 0x3800000000000011

$(TARGETSTUB): $(INTDIR) $(STUBOBJS)
$(CC) $(INTDIR)/*.o.stub -o $(TARGETSTUB) -target x86_64-pc-linux-gnu -shared -fuse-ld=lld -ffreestanding -nostdlib -fno-builtin -L$(TOOLCHAIN)/lib $(LIBS)

$(INTDIR)/%.o: $(PROJDIR)/%.c
$(CC) $(CFLAGS) -o $@ $<

$(INTDIR)/%.o: $(PROJDIR)/%.cpp
$(CCX) $(CXXFLAGS) -o $@ $<

$(INTDIR)/%.o.stub: $(PROJDIR)/%.c
$(CC) -target x86_64-pc-linux-gnu -ffreestanding -nostdlib -fno-builtin -fPIC $(O_FLAG) -s -c -o $@ $<

$(INTDIR)/%.o.stub: $(PROJDIR)/%.cpp
$(CCX) -target x86_64-pc-linux-gnu -ffreestanding -nostdlib -fno-builtin -fPIC $(O_FLAG) -s -c -o $@ $<

build-info:
$(shell echo "#define GIT_COMMIT \"$(shell git rev-parse HEAD)\"" > $(COMMON_DIR)/git_ver.h)
$(shell echo "#define GIT_VER \"$(shell git branch --show-current)\"" >> $(COMMON_DIR)/git_ver.h)
$(shell echo "#define GIT_NUM $(shell git rev-list HEAD --count)" >> $(COMMON_DIR)/git_ver.h)
$(shell echo "#define BUILD_DATE \"$(shell date '+%b %d %Y @ %T')\"" >> $(COMMON_DIR)/git_ver.h)

plugin_common:
$(CC) $(CFLAGS) -o $(INTDIR)/plugin_common.o $(COMMON_DIR)/plugin_common.c

.PHONY: clean
.DEFAULT_GOAL := all

all: build-info plugin_common $(TARGET)

clean:
rm -rf $(TARGET) $(TARGETSTUB) $(INTDIR) $(OBJS)
249 changes: 249 additions & 0 deletions plugin_src/frame_logger/source/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// FrameTime Logger: Log frametime statistics.
// Author: illusion0001 @ https://github.com/illusion0001
// Repository: https://github.com/GoldHEN/GoldHEN_Plugins_Repository

#include <Common.h>
#include <stdbool.h>
#include <orbis/Pad.h>
#include <time.h>
#include <orbis/UserService.h>
#include <orbis/SystemService.h>
#include <orbis/Sysmodule.h>
#include "plugin_common.h"

#define PLUGIN_NAME "frame_logger"
#define LOG_FOLDER "/data/" PLUGIN_NAME

attr_public const char *g_pluginName = PLUGIN_NAME;
attr_public const char *g_pluginDesc = "Log frametime statistics.";
attr_public const char *g_pluginAuth = "illusion";
attr_public u32 g_pluginVersion = 0x00000100; // 1.00

int32_t sceGnmSubmitAndFlipCommandBuffers(uint32_t count, void *dcbGpuAddrs[], uint32_t *dcbSizesInBytes, void *ccbGpuAddrs[], uint32_t *ccbSizesInBytes, uint32_t videoOutHandle, uint32_t displayBufferIndex, uint32_t flipMode, int64_t flipArg);

HOOK_INIT(sceGnmSubmitAndFlipCommandBuffers);

FILE *g_LogFILE = NULL;
bool g_isRecording = false;
uint64_t g_TimeStart = 0;
uint64_t g_CurrentDelta = 0;
double g_TscTick = 0;
bool g_GnmHook = false;
bool g_RunningThread = false;

void doStats(void)
{
if (!g_GnmHook)
{
return;
}
uint64_t current_time = sceKernelGetProcessTimeCounter();
if (g_isRecording && g_LogFILE)
{
fprintf(g_LogFILE, "%lf,%lf\n", ((current_time - g_TimeStart) / g_TscTick), ((current_time - g_CurrentDelta) / g_TscTick));
}
g_CurrentDelta = current_time;
}

int32_t sceGnmSubmitAndFlipCommandBuffers_hook(uint32_t count, void *dcbGpuAddrs[], uint32_t *dcbSizesInBytes, void *ccbGpuAddrs[], uint32_t *ccbSizesInBytes, uint32_t videoOutHandle, uint32_t displayBufferIndex, uint32_t flipMode, int64_t flipArg)
{
if (!g_GnmHook)
{
g_GnmHook = true;
}
doStats();
return HOOK_CONTINUE(sceGnmSubmitAndFlipCommandBuffers,
int32_t(*)(uint32_t, void **, uint32_t *, void **, uint32_t *, uint32_t, uint32_t, uint32_t, int64_t),
count, dcbGpuAddrs, dcbSizesInBytes, ccbGpuAddrs, ccbSizesInBytes, videoOutHandle, displayBufferIndex, flipMode, flipArg);
}

struct tm get_local_time(void)
{
int32_t tz_offset = 0;
int32_t tz_dst = 0;
int32_t ret = 0;

if ((ret = sceSystemServiceParamGetInt(ORBIS_SYSTEM_SERVICE_PARAM_ID_TIME_ZONE, &tz_offset)) < 0)
{
final_printf("Failed to obtain ORBIS_SYSTEM_SERVICE_PARAM_ID_TIME_ZONE! Setting timezone offset to 0\n");
final_printf("sceSystemServiceParamGetInt: 0x%08x\n", ret);
tz_offset = 0;
}

if ((ret = sceSystemServiceParamGetInt(ORBIS_SYSTEM_SERVICE_PARAM_ID_SUMMERTIME, &tz_dst)) < 0)
{
final_printf("Failed to obtain ORBIS_SYSTEM_SERVICE_PARAM_ID_SUMMERTIME! Setting timezone daylight time savings to 0\n");
final_printf("sceSystemServiceParamGetInt: 0x%08x\n", ret);
tz_dst = 0;
}

time_t modifiedTime = time(NULL) + ((tz_offset + (tz_dst * 60)) * 60);
return (*gmtime(&modifiedTime));
}

void toggleRecording(void)
{
g_isRecording = !g_isRecording;
if (!g_GnmHook)
{
NotifyStatic(TEX_ICON_SYSTEM, "g_GnmHook is false! cannot start data capture\nPlugin (" PLUGIN_NAME ") will now quit.");
g_isRecording = false;
g_RunningThread = false;
return;
}
if (g_isRecording)
{
sceKernelMkdir(LOG_FOLDER "/", 0777);
char LogFilePath[MAX_PATH_] = {0};
g_LogFILE = NULL;
struct tm t = get_local_time();
snprintf(LogFilePath, sizeof(LogFilePath), LOG_FOLDER "/" PLUGIN_NAME "-data-%d-%02d-%02d_%02d-%02d-%02d.csv", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
g_LogFILE = fopen(LogFilePath, "w");
if (g_LogFILE)
{
g_TscTick = (double)sceKernelGetProcessTimeCounterFrequency();
g_TimeStart = sceKernelGetProcessTimeCounter();
g_CurrentDelta = g_TimeStart;
Notify(TEX_ICON_SYSTEM, "Recording start:\n%s", LogFilePath);
fputs("//Ignore=true\n"
"TimeInSeconds,msBetweenPresents\n",
g_LogFILE);
}
else
{
Notify(TEX_ICON_SYSTEM, "Failed to create:\n%s", LogFilePath);
g_isRecording = false;
}
}
else
{
fflush(g_LogFILE);
fclose(g_LogFILE);
NotifyStatic(TEX_ICON_SYSTEM, "Recording stop");
}
}

bool checkRecordingButton(OrbisPadData *pData)
{
return (pData->buttons & ORBIS_PAD_BUTTON_L3) &&
(pData->buttons & ORBIS_PAD_BUTTON_L1) &&
(pData->buttons & ORBIS_PAD_BUTTON_TRIANGLE);
}

bool checkKillButton(OrbisPadData *pData)
{
return (pData->buttons & ORBIS_PAD_BUTTON_L3) &&
(pData->buttons & ORBIS_PAD_BUTTON_R3) &&
(pData->buttons & ORBIS_PAD_BUTTON_L1) &&
(pData->buttons & ORBIS_PAD_BUTTON_R1) &&
(pData->buttons & ORBIS_PAD_BUTTON_SQUARE);
}

int32_t GetHandle(void)
{
int32_t padHandle = 0;
int32_t userID = 0;
OrbisUserServiceInitializeParams param;
param.priority = ORBIS_KERNEL_PRIO_FIFO_LOWEST;
sceUserServiceInitialize(&param);
sceUserServiceGetInitialUser(&userID);
if ((padHandle = scePadOpen(userID, 0, 0, 0)) == ORBIS_PAD_ERROR_ALREADY_OPENED)
{
padHandle = scePadGetHandle(userID, 0, 0);
}
else if ((padHandle = scePadOpen(userID, 0, 0, 0)) < 0)
{
final_printf("scePadOpen: return 0x%08x\n", padHandle);
padHandle = 0;
}
return padHandle;
}

void *frame_logger_input_thread(void *args)
{
final_printf("%s: Started\n", __func__);
final_printf("%s: Waiting 10 seconds to start input lisener\n", __func__);
sleep(10);
int32_t ret = 0;
bool prevTogglePressed = false;
if ((ret = scePadInit() < 0))
{
NotifyStatic(TEX_ICON_SYSTEM, "Failed to init pad");
g_RunningThread = false;
}
OrbisPadData pData;
final_printf("Start listening for controller inputs\n");
while (g_RunningThread)
{
int32_t PadHandle = GetHandle();
int32_t ret = scePadReadState(PadHandle, &pData);
debug_printf("scePadReadState: 0x%08x\n", ret);
if (ret == 0 && PadHandle > 0 && pData.connected)
{
bool currentTogglePressed = checkRecordingButton(&pData);
if (currentTogglePressed && !prevTogglePressed)
{
toggleRecording();
}
prevTogglePressed = currentTogglePressed;
if (checkKillButton(&pData))
{
NotifyStatic(TEX_ICON_SYSTEM, "User requested exit for Plugin (" PLUGIN_NAME ")");
g_RunningThread = false;
}
}
else
{
final_printf(STRINGIFY(PadHandle)": 0x%08x\n", PadHandle);
final_printf(STRINGIFY(ret)": 0x%08x\n", ret);
final_printf(STRINGIFY(pData.connected)": 0x%02x (%s)\n", pData.connected, pData.connected ? "true" : "false");
}
// periodically flush the stream every second to avoid data loss
if (g_LogFILE)
{
#if (__FINAL__) == 0
int32_t flush_ret = fflush(g_LogFILE);
debug_printf("fflush: 0x%08x\n", flush_ret);
#else
fflush(g_LogFILE);
#endif
}
sleep(1);
}
final_printf("%s: Exit\n", __func__);
scePthreadExit(NULL);
return NULL;
}

s32 attr_public plugin_load(s32 argc, const char *argv[])
{
final_printf("[GoldHEN] <%s\\Ver.0x%08x> %s\n", g_pluginName, g_pluginVersion, __func__);
final_printf("[GoldHEN] Plugin Author(s): %s\n", g_pluginAuth);
boot_ver();
g_GnmHook = false;
g_RunningThread = true;
g_TimeStart = sceKernelGetProcessTimeCounter();
g_TscTick = (double)sceKernelGetProcessTimeCounterFrequency();

OrbisPthread thread;
scePthreadCreate(&thread, NULL, frame_logger_input_thread, NULL, STRINGIFY(frame_logger_input_thread));
HOOK32(sceGnmSubmitAndFlipCommandBuffers);
return 0;
}

s32 attr_public plugin_unload(s32 argc, const char *argv[])
{
final_printf("[GoldHEN] <%s\\Ver.0x%08x> %s\n", g_pluginName, g_pluginVersion, __func__);
UNHOOK(sceGnmSubmitAndFlipCommandBuffers);
return 0;
}

s32 attr_module_hidden module_start(s64 argc, const void *args)
{
return 0;
}

s32 attr_module_hidden module_stop(s64 argc, const void *args)
{
return 0;
}

0 comments on commit 6c3a06f

Please sign in to comment.