rpcsx/rpcs3/Emu/Cell/lv2/sys_time.cpp
Bevan Weiss 53212ba7a9 Core: Add date/time offset capability for console time
Provides a setting which can be applied per game, and allows for a custom date/time to be set.
The console time will then apply this as an offset to the computer wallclock.

This allows for games which look at the console time to determine their gameplay to be adjusted.
2020-09-23 21:15:45 +02:00

285 lines
6.9 KiB
C++

#include "stdafx.h"
#include "sys_time.h"
#include "Emu/system_config.h"
#include "Emu/Cell/ErrorCodes.h"
#include "Utilities/asm.h"
#ifdef _WIN32
#include <Windows.h>
struct time_aux_info_t
{
u64 perf_freq;
u64 start_time;
u64 start_ftime; // time in 100ns units since Epoch
};
// Initialize time-related values
const auto s_time_aux_info = []() -> time_aux_info_t
{
LARGE_INTEGER freq;
if (!QueryPerformanceFrequency(&freq))
{
MessageBox(0, L"Your hardware doesn't support a high-resolution performance counter", L"Error", MB_OK | MB_ICONERROR);
return {};
}
LARGE_INTEGER start;
QueryPerformanceCounter(&start); // get time in 1/perf_freq units from RDTSC
FILETIME ftime;
GetSystemTimeAsFileTime(&ftime); // get time in 100ns units since January 1, 1601 (UTC)
time_aux_info_t result;
result.perf_freq = freq.QuadPart;
result.start_time = start.QuadPart;
result.start_ftime = (ftime.dwLowDateTime | (u64)ftime.dwHighDateTime << 32) - 116444736000000000;
return result;
}();
#elif __APPLE__
// XXX only supports a single timer
#define TIMER_ABSTIME -1
// The opengroup spec isn't clear on the mapping from REALTIME to CALENDAR being appropriate or not.
// http://pubs.opengroup.org/onlinepubs/009695299/basedefs/time.h.html
#define CLOCK_REALTIME 1 // #define CALENDAR_CLOCK 1 from mach/clock_types.h
#define CLOCK_MONOTONIC 0 // #define SYSTEM_CLOCK 0
// the mach kernel uses struct mach_timespec, so struct timespec is loaded from <sys/_types/_timespec.h> for compatability
// struct timespec { time_t tv_sec; long tv_nsec; };
#include <sys/types.h>
#include <sys/_types/_timespec.h>
#include <mach/mach.h>
#include <mach/clock.h>
#include <mach/mach_time.h>
#undef CPU_STATE_MAX
#define MT_NANO (+1.0E-9)
#define MT_GIGA UINT64_C(1000000000)
// TODO create a list of timers,
static double mt_timebase = 0.0;
static uint64_t mt_timestart = 0;
static int clock_gettime(int clk_id, struct timespec* tp)
{
kern_return_t retval = KERN_SUCCESS;
if (clk_id == TIMER_ABSTIME)
{
if (!mt_timestart)
{
// only one timer, initilized on the first call to the TIMER
mach_timebase_info_data_t tb = {0};
mach_timebase_info(&tb);
mt_timebase = tb.numer;
mt_timebase /= tb.denom;
mt_timestart = mach_absolute_time();
}
double diff = (mach_absolute_time() - mt_timestart) * mt_timebase;
tp->tv_sec = diff * MT_NANO;
tp->tv_nsec = diff - (tp->tv_sec * MT_GIGA);
}
else // other clk_ids are mapped to the coresponding mach clock_service
{
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), clk_id, &cclock);
retval = clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
tp->tv_sec = mts.tv_sec;
tp->tv_nsec = mts.tv_nsec;
}
return retval;
}
#endif
#ifndef _WIN32
static struct timespec start_time = []()
{
struct timespec ts;
if (::clock_gettime(CLOCK_REALTIME, &ts) != 0)
{
// Fatal error
std::terminate();
}
return ts;
}();
#endif
LOG_CHANNEL(sys_time);
static constexpr u64 g_timebase_freq = /*79800000*/ 80000000ull; // 80 Mhz
// Auxiliary functions
u64 get_timebased_time()
{
#ifdef _WIN32
LARGE_INTEGER count;
verify(HERE), QueryPerformanceCounter(&count);
const u64 time = count.QuadPart;
const u64 freq = s_time_aux_info.perf_freq;
return (time / freq * g_timebase_freq + time % freq * g_timebase_freq / freq) * g_cfg.core.clocks_scale / 100u;
#else
struct timespec ts;
verify(HERE), ::clock_gettime(CLOCK_MONOTONIC, &ts) == 0;
return (static_cast<u64>(ts.tv_sec) * g_timebase_freq + static_cast<u64>(ts.tv_nsec) * g_timebase_freq / 1000000000ull) * g_cfg.core.clocks_scale / 100u;
#endif
}
// Returns some relative time in microseconds, don't change this fact
u64 get_system_time()
{
while (true)
{
#ifdef _WIN32
LARGE_INTEGER count;
verify(HERE), QueryPerformanceCounter(&count);
const u64 time = count.QuadPart;
const u64 freq = s_time_aux_info.perf_freq;
const u64 result = time / freq * 1000000ull + (time % freq) * 1000000ull / freq;
#else
struct timespec ts;
verify(HERE), ::clock_gettime(CLOCK_MONOTONIC, &ts) == 0;
const u64 result = static_cast<u64>(ts.tv_sec) * 1000000ull + static_cast<u64>(ts.tv_nsec) / 1000u;
#endif
if (result) return result;
}
}
// As get_system_time but obeys Clocks scaling setting
u64 get_guest_system_time()
{
return get_system_time() * g_cfg.core.clocks_scale / 100;
}
// Functions
error_code sys_time_get_timezone(vm::ptr<s32> timezone, vm::ptr<s32> summertime)
{
sys_time.warning("sys_time_get_timezone(timezone=*0x%x, summertime=*0x%x)", timezone, summertime);
*timezone = 180;
*summertime = 0;
return CELL_OK;
}
error_code sys_time_get_current_time(vm::ptr<s64> sec, vm::ptr<s64> nsec)
{
sys_time.trace("sys_time_get_current_time(sec=*0x%x, nsec=*0x%x)", sec, nsec);
if (!sec)
{
return CELL_EFAULT;
}
#ifdef _WIN32
LARGE_INTEGER count;
verify(HERE), QueryPerformanceCounter(&count);
const u64 diff_base = count.QuadPart - s_time_aux_info.start_time;
// Get time difference in nanoseconds (using 128 bit accumulator)
const u64 diff_sl = diff_base * 1000000000ull;
const u64 diff_sh = utils::umulh64(diff_base, 1000000000ull);
const u64 diff = utils::udiv128(diff_sh, diff_sl, s_time_aux_info.perf_freq);
// get time since Epoch in nanoseconds
const u64 time = s_time_aux_info.start_ftime * 100u + (diff * g_cfg.core.clocks_scale / 100u);
// scale to seconds, and add the console time offset (which might be negative)
*sec = (time / 1000000000ull) + g_cfg.sys.console_time_offset;
if (!nsec)
{
return CELL_EFAULT;
}
*nsec = time % 1000000000ull;
#else
struct timespec ts;
verify(HERE), ::clock_gettime(CLOCK_REALTIME, &ts) == 0;
if (g_cfg.core.clocks_scale == 100)
{
// get the seconds from the system clock, and add the console time offset (which might be negative)
*sec = ts.tv_sec + g_cfg.sys.console_time_offset;
if (!nsec)
{
return CELL_EFAULT;
}
*nsec = ts.tv_nsec;
return CELL_OK;
}
u64 tv_sec = ts.tv_sec, stv_sec = start_time.tv_sec;
u64 tv_nsec = ts.tv_nsec, stv_nsec = start_time.tv_nsec;
// Substruct time since Epoch and since start time
tv_sec -= stv_sec;
if (tv_nsec < stv_nsec)
{
// Correct value if borrow encountered
tv_sec -= 1;
tv_nsec = 1'000'000'000ull - (stv_nsec - tv_nsec);
}
else
{
tv_nsec -= stv_nsec;
}
// Scale nanocseconds
tv_nsec = stv_nsec + (tv_nsec * g_cfg.core.clocks_scale / 100);
// Scale seconds and add from nanoseconds / 1'000'000'000, and add the console time offset (which might be negative)
*sec = stv_sec + (tv_sec * g_cfg.core.clocks_scale / 100u) + (tv_nsec / 1000000000ull) + g_cfg.sys.console_time_offset;
if (!nsec)
{
return CELL_EFAULT;
}
// Set nanoseconds
*nsec = tv_nsec % 1000000000ull;
#endif
return CELL_OK;
}
u64 sys_time_get_timebase_frequency()
{
sys_time.trace("sys_time_get_timebase_frequency()");
return g_timebase_freq;
}
error_code sys_time_get_rtc(vm::ptr<u64> rtc)
{
sys_time.todo("sys_time_get_rtc(rtc=*0x%x)", rtc);
return CELL_OK;
}