This commit is contained in:
Connor McLaughlin 2022-08-09 01:14:22 +10:00
parent 73a80d3a1d
commit 33590d58a1
21 changed files with 426 additions and 132 deletions

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,97 @@
/*
*
* Created: 29.03.2018
*
* Authors:
*
* Assembled from the code released on Stackoverflow by:
* Dennis (instructable.com/member/nqtronix) | https://stackoverflow.com/questions/23032002/c-c-how-to-get-integer-unix-timestamp-of-build-time-not-string
* and
* Alexis Wilke | https://stackoverflow.com/questions/10538444/do-you-know-of-a-c-macro-to-compute-unix-time-and-date
*
* Assembled by Jean Rabault
*
* BUILD_UNIX_TIMESTAMP gives the UNIX timestamp (unsigned long integer of seconds since 1st Jan 1970) of compilation from macros using the compiler defined __TIME__ macro.
* This should include Gregorian calendar leap days, in particular the 29ths of February, 100 and 400 years modulo leaps.
*
* Careful: __TIME__ is the local time of the computer, NOT the UTC time in general!
*
*/
#ifndef COMPILE_TIME_H_
#define COMPILE_TIME_H_
// Some definitions for calculation
#define SEC_PER_MIN 60UL
#define SEC_PER_HOUR 3600UL
#define SEC_PER_DAY 86400UL
#define SEC_PER_YEAR (SEC_PER_DAY*365)
// extracts 1..4 characters from a string and interprets it as a decimal value
#define CONV_STR2DEC_1(str, i) (str[i]>'0'?str[i]-'0':0)
#define CONV_STR2DEC_2(str, i) (CONV_STR2DEC_1(str, i)*10 + str[i+1]-'0')
#define CONV_STR2DEC_3(str, i) (CONV_STR2DEC_2(str, i)*10 + str[i+2]-'0')
#define CONV_STR2DEC_4(str, i) (CONV_STR2DEC_3(str, i)*10 + str[i+3]-'0')
// Custom "glue logic" to convert the month name to a usable number
#define GET_MONTH(str, i) (str[i]=='J' && str[i+1]=='a' && str[i+2]=='n' ? 1 : \
str[i]=='F' && str[i+1]=='e' && str[i+2]=='b' ? 2 : \
str[i]=='M' && str[i+1]=='a' && str[i+2]=='r' ? 3 : \
str[i]=='A' && str[i+1]=='p' && str[i+2]=='r' ? 4 : \
str[i]=='M' && str[i+1]=='a' && str[i+2]=='y' ? 5 : \
str[i]=='J' && str[i+1]=='u' && str[i+2]=='n' ? 6 : \
str[i]=='J' && str[i+1]=='u' && str[i+2]=='l' ? 7 : \
str[i]=='A' && str[i+1]=='u' && str[i+2]=='g' ? 8 : \
str[i]=='S' && str[i+1]=='e' && str[i+2]=='p' ? 9 : \
str[i]=='O' && str[i+1]=='c' && str[i+2]=='t' ? 10 : \
str[i]=='N' && str[i+1]=='o' && str[i+2]=='v' ? 11 : \
str[i]=='D' && str[i+1]=='e' && str[i+2]=='c' ? 12 : 0)
// extract the information from the time string given by __TIME__ and __DATE__
#define __TIME_SECONDS__ CONV_STR2DEC_2(__TIME__, 6)
#define __TIME_MINUTES__ CONV_STR2DEC_2(__TIME__, 3)
#define __TIME_HOURS__ CONV_STR2DEC_2(__TIME__, 0)
#define __TIME_DAYS__ CONV_STR2DEC_2(__DATE__, 4)
#define __TIME_MONTH__ GET_MONTH(__DATE__, 0)
#define __TIME_YEARS__ CONV_STR2DEC_4(__DATE__, 7)
// Days in February
#define _UNIX_TIMESTAMP_FDAY(year) \
(((year) % 400) == 0UL ? 29UL : \
(((year) % 100) == 0UL ? 28UL : \
(((year) % 4) == 0UL ? 29UL : \
28UL)))
// Days in the year
#define _UNIX_TIMESTAMP_YDAY(year, month, day) \
( \
/* January */ day \
/* February */ + (month >= 2 ? 31UL : 0UL) \
/* March */ + (month >= 3 ? _UNIX_TIMESTAMP_FDAY(year) : 0UL) \
/* April */ + (month >= 4 ? 31UL : 0UL) \
/* May */ + (month >= 5 ? 30UL : 0UL) \
/* June */ + (month >= 6 ? 31UL : 0UL) \
/* July */ + (month >= 7 ? 30UL : 0UL) \
/* August */ + (month >= 8 ? 31UL : 0UL) \
/* September */+ (month >= 9 ? 31UL : 0UL) \
/* October */ + (month >= 10 ? 30UL : 0UL) \
/* November */ + (month >= 11 ? 31UL : 0UL) \
/* December */ + (month >= 12 ? 30UL : 0UL) \
)
// get the UNIX timestamp from a digits representation
#define _UNIX_TIMESTAMP(year, month, day, hour, minute, second) \
( /* time */ second \
+ minute * SEC_PER_MIN \
+ hour * SEC_PER_HOUR \
+ /* year day (month + day) */ (_UNIX_TIMESTAMP_YDAY(year, month, day) - 1) * SEC_PER_DAY \
+ /* year */ (year - 1970UL) * SEC_PER_YEAR \
+ ((year - 1969UL) / 4UL) * SEC_PER_DAY \
- ((year - 1901UL) / 100UL) * SEC_PER_DAY \
+ ((year - 1601UL) / 400UL) * SEC_PER_DAY \
)
// the UNIX timestamp
#define BUILD_UNIX_TIMESTAMP (_UNIX_TIMESTAMP(__TIME_YEARS__, __TIME_MONTH__, __TIME_DAYS__, __TIME_HOURS__, __TIME_MINUTES__, __TIME_SECONDS__))
#endif

View File

@ -6,6 +6,7 @@
<ClInclude Include="assert.h" />
<ClInclude Include="bitfield.h" />
<ClInclude Include="bitutils.h" />
<ClInclude Include="build_timestamp.h" />
<ClInclude Include="byte_stream.h" />
<ClInclude Include="crash_handler.h" />
<ClInclude Include="d3d11\shader_cache.h" />

View File

@ -137,6 +137,7 @@
<ClInclude Include="memory_settings_interface.h" />
<ClInclude Include="threading.h" />
<ClInclude Include="scoped_guard.h" />
<ClInclude Include="build_timestamp.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="gl\program.cpp">

View File

@ -186,8 +186,21 @@ void Timer::SleepUntil(Value value, bool exact)
{
if (exact)
{
while (GetCurrentValue() < value)
SleepUntil(value, false);
for (;;)
{
Value current = GetCurrentValue();
if (current >= value)
break;
static constexpr Value min_sleep_time = 1 * 1000000;
// spin for the last 1ms
if ((value - current) > min_sleep_time)
{
SleepUntil(value, false);
continue;
}
}
}
else
{

View File

@ -1,4 +1,5 @@
#include "analog_controller.h"
#include "IconsFontAwesome5.h"
#include "common/log.h"
#include "common/string_util.h"
#include "host.h"
@ -10,8 +11,9 @@ Log_SetChannel(AnalogController);
AnalogController::AnalogController(u32 index) : Controller(index)
{
m_status_byte = 0x5A;
m_axis_state.fill(0x80);
Reset();
m_rumble_config.fill(0xFF);
}
AnalogController::~AnalogController() = default;
@ -45,14 +47,16 @@ void AnalogController::Reset()
{
if (g_settings.controller_disable_analog_mode_forcing)
{
Host::AddKeyedOSDMessage(
"analog_controller_mode_ignored",
Host::AddIconOSDMessage(
fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD,
Host::TranslateStdString(
"OSDMessage", "Analog mode forcing is disabled by game settings. Controller will start in digital mode."),
10.0f);
}
else
SetAnalogMode(true);
{
SetAnalogMode(true, false);
}
}
}
@ -94,11 +98,14 @@ bool AnalogController::DoState(StateWrapper& sw, bool apply_input_state)
if (old_analog_mode != m_analog_mode)
{
Host::AddFormattedOSDMessage(
5.0f,
m_analog_mode ? Host::TranslateString("AnalogController", "Controller %u switched to analog mode.") :
Host::TranslateString("AnalogController", "Controller %u switched to digital mode."),
m_index + 1u);
Host::AddIconOSDMessage(
fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD,
fmt::format((m_analog_mode ?
Host::TranslateString("AnalogController", "Controller {} switched to analog mode.") :
Host::TranslateString("AnalogController", "Controller {} switched to digital mode."))
.GetCharArray(),
m_index + 1u),
5.0f);
}
}
return true;
@ -230,17 +237,22 @@ void AnalogController::ResetTransferState()
m_command_step = 0;
}
void AnalogController::SetAnalogMode(bool enabled)
void AnalogController::SetAnalogMode(bool enabled, bool show_message)
{
if (m_analog_mode == enabled)
return;
Log_InfoPrintf("Controller %u switched to %s mode.", m_index + 1u, enabled ? "analog" : "digital");
Host::AddFormattedOSDMessage(5.0f,
enabled ?
Host::TranslateString("AnalogController", "Controller %u switched to analog mode.") :
Host::TranslateString("AnalogController", "Controller %u switched to digital mode."),
m_index + 1u);
if (show_message)
{
Host::AddIconOSDMessage(
fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD,
fmt::format((enabled ? Host::TranslateString("AnalogController", "Controller {} switched to analog mode.") :
Host::TranslateString("AnalogController", "Controller {} switched to digital mode."))
.GetCharArray(),
m_index + 1u),
5.0f);
}
m_analog_mode = enabled;
}
@ -248,15 +260,18 @@ void AnalogController::ProcessAnalogModeToggle()
{
if (m_analog_locked)
{
Host::AddFormattedOSDMessage(
5.0f,
m_analog_mode ? Host::TranslateString("AnalogController", "Controller %u is locked to analog mode by the game.") :
Host::TranslateString("AnalogController", "Controller %u is locked to digital mode by the game."),
m_index + 1u);
Host::AddIconOSDMessage(
fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD,
fmt::format((m_analog_mode ?
Host::TranslateString("AnalogController", "Controller %u is locked to analog mode by the game.") :
Host::TranslateString("AnalogController", "Controller %u is locked to digital mode by the game."))
.GetCharArray(),
m_index + 1u),
5.0f);
}
else
{
SetAnalogMode(!m_analog_mode);
SetAnalogMode(!m_analog_mode, true);
ResetRumbleConfig();
if (m_dualshock_enabled)
@ -604,7 +619,7 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out)
Log_DevPrintf("analog mode val 0x%02x", data_in);
if (data_in == 0x00 || data_in == 0x01)
SetAnalogMode((data_in == 0x01));
SetAnalogMode((data_in == 0x01), true);
}
else if (m_command_step == 3)
{
@ -777,17 +792,17 @@ static const SettingInfo s_settings[] = {
{SettingInfo::Type::Float, "AnalogDeadzone", TRANSLATABLE("AnalogController", "Analog Deadzone"),
TRANSLATABLE("AnalogController",
"Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored."),
"0.00f", "0.00f", "1.00f", "0.01f"},
"0.00f", "0.00f", "1.00f", "0.01f", "%.0f%%", 100.0f},
{SettingInfo::Type::Float, "AnalogSensitivity", TRANSLATABLE("AnalogController", "Analog Sensitivity"),
TRANSLATABLE(
"AnalogController",
"Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent "
"Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent "
"controllers, e.g. DualShock 4, Xbox One Controller."),
"1.33f", "0.01f", "2.00f", "0.01f"},
"1.33f", "0.01f", "2.00f", "0.01f", "%.0f%%", 100.0f},
{SettingInfo::Type::Integer, "VibrationBias", TRANSLATABLE("AnalogController", "Vibration Bias"),
TRANSLATABLE("AnalogController", "Sets the rumble bias value. If rumble in some games is too weak or not "
"functioning, try increasing this value."),
"8", "0", "255", "1"}};
"8", "0", "255", "1", "%d", 1.0f}};
const Controller::ControllerInfo AnalogController::INFO = {ControllerType::AnalogController,
"AnalogController",

View File

@ -108,7 +108,7 @@ private:
u8 GetModeID() const;
u8 GetIDByte() const;
void SetAnalogMode(bool enabled);
void SetAnalogMode(bool enabled, bool show_message);
void ProcessAnalogModeToggle();
void SetMotorState(u32 motor, u8 value);
void UpdateHostVibration();
@ -140,7 +140,7 @@ private:
int m_rumble_config_small_motor_index = -1;
bool m_analog_toggle_queued = false;
u8 m_status_byte = 0x5A;
u8 m_status_byte = 0;
// TODO: Set this with command 0x4D and increase response length in digital mode accordingly
u8 m_digital_mode_extra_halfwords = 0;

View File

@ -328,13 +328,13 @@ static const SettingInfo s_settings[] = {
{SettingInfo::Type::Float, "AnalogDeadzone", TRANSLATABLE("AnalogJoystick", "Analog Deadzone"),
TRANSLATABLE("AnalogJoystick",
"Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored."),
"1.00f", "0.00f", "1.00f", "0.01f"},
"1.00f", "0.00f", "1.00f", "0.01f", "%.0f%%", 100.0f},
{SettingInfo::Type::Float, "AnalogSensitivity", TRANSLATABLE("AnalogJoystick", "Analog Sensitivity"),
TRANSLATABLE(
"AnalogJoystick",
"Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent "
"Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent "
"controllers, e.g. DualShock 4, Xbox One Controller."),
"1.33f", "0.01f", "2.00f", "0.01f"}};
"1.33f", "0.01f", "2.00f", "0.01f", "%.0f%%", 100.0f}};
const Controller::ControllerInfo AnalogJoystick::INFO = {ControllerType::AnalogJoystick,
"AnalogJoystick",

View File

@ -542,6 +542,12 @@ bool GameDatabase::LoadFromCache()
return false;
}
if (gamedb_ts != file_gamedb_ts || gamesettings_ts != file_gamesettings_ts || compat_ts != file_compat_ts)
{
Log_DevPrintf("Cache is out of date, recreating.");
return false;
}
s_entries.reserve(num_entries);
for (u32 i = 0; i < num_entries; i++)

View File

@ -220,10 +220,10 @@ static const SettingInfo s_settings[] = {
{SettingInfo::Type::Path, "CrosshairImagePath", TRANSLATABLE("GunCon", "Crosshair Image Path"),
TRANSLATABLE("GunCon", "Path to an image to use as a crosshair/cursor.")},
{SettingInfo::Type::Float, "CrosshairScale", TRANSLATABLE("GunCon", "Crosshair Image Scale"),
TRANSLATABLE("GunCon", "Scale of crosshair image on screen."), "1.0", "0.0001", "100.0", "0.10"},
TRANSLATABLE("GunCon", "Scale of crosshair image on screen."), "1.0", "0.0001", "100.0", "0.10", "%.0f%%", 100.0f},
{SettingInfo::Type::Float, "XScale", TRANSLATABLE("GunCon", "X Scale"),
TRANSLATABLE("GunCon", "Scales X coordinates relative to the center of the screen."), "1.0", "0.01", "2.0",
"0.01"}};
"0.01", "%.0f%%", 100.0f}};
const Controller::ControllerInfo GunCon::INFO = {ControllerType::GunCon,
"GunCon",

View File

@ -251,7 +251,7 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
static const SettingInfo s_settings[] = {
{SettingInfo::Type::Float, "SteeringDeadzone", TRANSLATABLE("NeGcon", "Steering Axis Deadzone"),
TRANSLATABLE("NeGcon", "Sets deadzone size for steering axis."), "0.00f", "0.00f", "0.99f", "0.01f"}};
TRANSLATABLE("NeGcon", "Sets deadzone size for steering axis."), "0.00f", "0.00f", "0.99f", "0.01f", "%.0f%%", 100.0f}};
const Controller::ControllerInfo NeGcon::INFO = {ControllerType::NeGcon,
"NeGcon",

View File

@ -21,13 +21,15 @@ struct SettingInfo
};
Type type;
const char* key;
const char* visible_name;
const char* name;
const char* display_name;
const char* description;
const char* default_value;
const char* min_value;
const char* max_value;
const char* step_value;
const char* format;
float multiplier;
const char* StringDefaultValue() const;
bool BooleanDefaultValue() const;

View File

@ -862,8 +862,13 @@ bool System::UpdateGameSettingsLayer()
}
std::string input_profile_name;
bool use_game_settings_for_controller = false;
if (new_interface)
new_interface->GetStringValue("ControllerPorts", "InputProfileName", &input_profile_name);
{
new_interface->GetBoolValue("ControllerPorts", "UseGameSettingsForController", &use_game_settings_for_controller);
if (!use_game_settings_for_controller)
new_interface->GetStringValue("ControllerPorts", "InputProfileName", &input_profile_name);
}
if (!s_game_settings_interface && !new_interface && s_input_profile_name == input_profile_name)
return false;
@ -872,31 +877,39 @@ bool System::UpdateGameSettingsLayer()
s_game_settings_interface = std::move(new_interface);
std::unique_ptr<INISettingsInterface> input_interface;
if (!input_profile_name.empty())
if (!use_game_settings_for_controller)
{
const std::string filename(GetInputProfilePath(input_profile_name));
if (FileSystem::FileExists(filename.c_str()))
if (!input_profile_name.empty())
{
Log_InfoPrintf("Loading input profile from '%s'...", filename.c_str());
input_interface = std::make_unique<INISettingsInterface>(std::move(filename));
if (!input_interface->Load())
const std::string filename(GetInputProfilePath(input_profile_name));
if (FileSystem::FileExists(filename.c_str()))
{
Log_ErrorPrintf("Failed to parse input profile ini '%s'", input_interface->GetFileName().c_str());
input_interface.reset();
Log_InfoPrintf("Loading input profile from '%s'...", filename.c_str());
input_interface = std::make_unique<INISettingsInterface>(std::move(filename));
if (!input_interface->Load())
{
Log_ErrorPrintf("Failed to parse input profile ini '%s'", input_interface->GetFileName().c_str());
input_interface.reset();
input_profile_name = {};
}
}
else
{
Log_InfoPrintf("No input profile found (tried '%s')", filename.c_str());
input_profile_name = {};
}
}
else
{
Log_InfoPrintf("No input profile found (tried '%s')", filename.c_str());
input_profile_name = {};
}
Host::Internal::SetInputSettingsLayer(input_interface.get());
}
else
{
// using game settings for bindings too
Host::Internal::SetInputSettingsLayer(s_game_settings_interface.get());
}
Host::Internal::SetInputSettingsLayer(input_interface.get());
s_input_settings_interface = std::move(input_interface);
s_input_profile_name = std::move(input_profile_name);
return true;
}
@ -930,7 +943,7 @@ void System::PauseSystem(bool paused)
else
{
Host::OnSystemResumed();
g_host_display->SetVSync(ShouldUseVSync());
UpdateDisplaySync();
ResetPerformanceCounters();
ResetThrottler();
}
@ -1249,6 +1262,7 @@ bool System::Initialize(bool force_software_renderer)
s_frame_number = 1;
s_internal_frame_number = 1;
s_target_speed = g_settings.emulation_speed;
s_throttle_frequency = 60.0f;
s_frame_period = 0;
s_next_frame_time = 0;
@ -2064,6 +2078,7 @@ void System::ResetThrottler()
void System::Throttle()
{
#if 0
// Allow variance of up to 40ms either way.
#ifndef __ANDROID__
static constexpr double MAX_VARIANCE_TIME_NS = 40 * 1000000;
@ -2085,6 +2100,7 @@ void System::Throttle()
ResetThrottler();
}
else
#endif
{
Common::Timer::SleepUntil(s_next_frame_time, true);
}
@ -2210,12 +2226,7 @@ void System::UpdateSpeedLimiterState()
}
}
const bool video_sync_enabled = ShouldUseVSync();
const float max_display_fps = (!IsRunning() || m_throttler_enabled) ? 0.0f : g_settings.display_max_fps;
Log_InfoPrintf("Target speed: %f%%", target_speed * 100.0f);
Log_InfoPrintf("Using vsync: %s", video_sync_enabled ? "YES" : "NO");
Log_InfoPrintf("Max display fps: %f (%s)", max_display_fps,
m_display_all_frames ? "displaying all frames" : "skipping displaying frames when needed");
Log_VerbosePrintf("Target speed: %f%%", s_target_speed * 100.0f);
if (IsValid())
{
@ -2235,23 +2246,36 @@ void System::UpdateSpeedLimiterState()
ResetThrottler();
}
g_host_display->SetDisplayMaxFPS(max_display_fps);
g_host_display->SetVSync(video_sync_enabled);
// Defer vsync update until we unpause, in case of fullscreen UI.
if (IsRunning())
UpdateDisplaySync();
if (g_settings.increase_timer_resolution)
SetTimerResolutionIncreased(m_throttler_enabled);
// When syncing to host and using vsync, we don't need to sleep.
if (syncing_to_host && video_sync_enabled && m_display_all_frames)
if (syncing_to_host && ShouldUseVSync() && m_display_all_frames)
{
Log_InfoPrintf("Using host vsync for throttling.");
m_throttler_enabled = false;
}
}
void System::UpdateDisplaySync()
{
const bool video_sync_enabled = ShouldUseVSync();
const float max_display_fps = m_throttler_enabled ? 0.0f : g_settings.display_max_fps;
Log_VerbosePrintf("Using vsync: %s", video_sync_enabled ? "YES" : "NO");
Log_VerbosePrintf("Max display fps: %f (%s)", max_display_fps,
m_display_all_frames ? "displaying all frames" : "skipping displaying frames when needed");
g_host_display->SetDisplayMaxFPS(max_display_fps);
g_host_display->SetVSync(video_sync_enabled);
}
bool System::ShouldUseVSync()
{
return (!IsRunning() || (m_throttler_enabled && g_settings.video_sync_enabled && !IsRunningAtNonStandardSpeed()));
return g_settings.video_sync_enabled && !IsRunningAtNonStandardSpeed();
}
bool System::IsFastForwardEnabled()

View File

@ -230,6 +230,9 @@ void Throttle();
void UpdatePerformanceCounters();
void ResetPerformanceCounters();
/// Resets vsync/max present fps state.
void UpdateDisplaySync();
// Access controllers for simulating input.
Controller* GetController(u32 slot);
void UpdateControllers();

View File

@ -521,14 +521,14 @@ void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidge
for (u32 i = 0; i < cinfo->num_settings; i++)
{
const SettingInfo& si = cinfo->settings[i];
std::string key_name = si.key;
std::string key_name = si.name;
switch (si.type)
{
case SettingInfo::Type::Boolean:
{
QCheckBox* cb = new QCheckBox(qApp->translate(cinfo->name, si.visible_name), this);
cb->setObjectName(QString::fromUtf8(si.key));
QCheckBox* cb = new QCheckBox(qApp->translate(cinfo->name, si.display_name), this);
cb->setObjectName(QString::fromUtf8(si.name));
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, cb, section, std::move(key_name),
si.BooleanDefaultValue());
layout->addWidget(cb, current_row, 0, 1, 4);
@ -539,12 +539,12 @@ void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidge
case SettingInfo::Type::Integer:
{
QSpinBox* sb = new QSpinBox(this);
sb->setObjectName(QString::fromUtf8(si.key));
sb->setObjectName(QString::fromUtf8(si.name));
sb->setMinimum(si.IntegerMinValue());
sb->setMaximum(si.IntegerMaxValue());
sb->setSingleStep(si.IntegerStepValue());
SettingWidgetBinder::BindWidgetToIntSetting(sif, sb, section, std::move(key_name), si.IntegerDefaultValue());
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), this), current_row, 0);
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.display_name), this), current_row, 0);
layout->addWidget(sb, current_row, 1, 1, 3);
current_row++;
}
@ -553,12 +553,12 @@ void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidge
case SettingInfo::Type::Float:
{
QDoubleSpinBox* sb = new QDoubleSpinBox(this);
sb->setObjectName(QString::fromUtf8(si.key));
sb->setObjectName(QString::fromUtf8(si.name));
sb->setMinimum(si.FloatMinValue());
sb->setMaximum(si.FloatMaxValue());
sb->setSingleStep(si.FloatStepValue());
SettingWidgetBinder::BindWidgetToFloatSetting(sif, sb, section, std::move(key_name), si.FloatDefaultValue());
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), this), current_row, 0);
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.display_name), this), current_row, 0);
layout->addWidget(sb, current_row, 1, 1, 3);
current_row++;
}
@ -567,9 +567,9 @@ void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidge
case SettingInfo::Type::String:
{
QLineEdit* le = new QLineEdit(this);
le->setObjectName(QString::fromUtf8(si.key));
le->setObjectName(QString::fromUtf8(si.name));
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, section, std::move(key_name), si.StringDefaultValue());
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), this), current_row, 0);
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.display_name), this), current_row, 0);
layout->addWidget(le, current_row, 1, 1, 3);
current_row++;
}
@ -578,7 +578,7 @@ void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidge
case SettingInfo::Type::Path:
{
QLineEdit* le = new QLineEdit(this);
le->setObjectName(QString::fromUtf8(si.key));
le->setObjectName(QString::fromUtf8(si.name));
QPushButton* browse_button = new QPushButton(tr("Browse..."), this);
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, section, std::move(key_name), si.StringDefaultValue());
connect(browse_button, &QPushButton::clicked, [this, le]() {
@ -591,7 +591,7 @@ void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidge
hbox->addWidget(le, 1);
hbox->addWidget(browse_button);
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), this), current_row, 0);
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.display_name), this), current_row, 0);
layout->addLayout(hbox, current_row, 1, 1, 3);
current_row++;
}
@ -615,13 +615,13 @@ void ControllerCustomSettingsWidget::restoreDefaults()
for (u32 i = 0; i < cinfo->num_settings; i++)
{
const SettingInfo& si = cinfo->settings[i];
const QString key(QString::fromStdString(si.key));
const QString key(QString::fromStdString(si.name));
switch (si.type)
{
case SettingInfo::Type::Boolean:
{
QCheckBox* widget = findChild<QCheckBox*>(QString::fromStdString(si.key));
QCheckBox* widget = findChild<QCheckBox*>(QString::fromStdString(si.name));
if (widget)
widget->setChecked(si.BooleanDefaultValue());
}
@ -629,7 +629,7 @@ void ControllerCustomSettingsWidget::restoreDefaults()
case SettingInfo::Type::Integer:
{
QSpinBox* widget = findChild<QSpinBox*>(QString::fromStdString(si.key));
QSpinBox* widget = findChild<QSpinBox*>(QString::fromStdString(si.name));
if (widget)
widget->setValue(si.IntegerDefaultValue());
}
@ -637,7 +637,7 @@ void ControllerCustomSettingsWidget::restoreDefaults()
case SettingInfo::Type::Float:
{
QDoubleSpinBox* widget = findChild<QDoubleSpinBox*>(QString::fromStdString(si.key));
QDoubleSpinBox* widget = findChild<QDoubleSpinBox*>(QString::fromStdString(si.name));
if (widget)
widget->setValue(si.FloatDefaultValue());
}
@ -645,7 +645,7 @@ void ControllerCustomSettingsWidget::restoreDefaults()
case SettingInfo::Type::String:
{
QLineEdit* widget = findChild<QLineEdit*>(QString::fromStdString(si.key));
QLineEdit* widget = findChild<QLineEdit*>(QString::fromStdString(si.name));
if (widget)
widget->setText(QString::fromUtf8(si.StringDefaultValue()));
}
@ -653,7 +653,7 @@ void ControllerCustomSettingsWidget::restoreDefaults()
case SettingInfo::Type::Path:
{
QLineEdit* widget = findChild<QLineEdit*>(QString::fromStdString(si.key));
QLineEdit* widget = findChild<QLineEdit*>(QString::fromStdString(si.name));
if (widget)
widget->setText(QString::fromUtf8(si.StringDefaultValue()));
}

View File

@ -63,7 +63,7 @@ static void DeactivateAchievement(Achievement* achievement);
static void SendPing();
static void SendPlaying();
static void UpdateRichPresence();
static Achievement* GetAchievementByID(u32 id);
static Achievement* GetMutableAchievementByID(u32 id);
static void ClearGameInfo(bool clear_achievements = true, bool clear_leaderboards = true);
static void ClearGameHash();
static std::string GetUserAgent();
@ -80,6 +80,7 @@ static void GetLbInfoCallback(s32 status_code, Common::HTTPDownloader::Request::
static void GetPatches(u32 game_id);
static std::string GetGameHash(CDImage* image);
static void SetChallengeMode(bool enabled);
static void SendGetGameId();
static void GetGameIdCallback(s32 status_code, Common::HTTPDownloader::Request::Data data);
static void SendPlayingCallback(s32 status_code, Common::HTTPDownloader::Request::Data data);
static void UpdateRichPresence();
@ -205,6 +206,18 @@ public:
Achievements::DownloadImage(api_request.url, std::move(cache_filename));
return true;
}
std::string GetURL()
{
const int error = InitFunc(&api_request, this);
if (error != RC_OK)
{
FormattedError("%s failed: error %d (%s)", RAPIStructName<T>(), error, rc_error_str(error));
return std::string();
}
return api_request.url;
}
};
template<typename T, int (*ParseFunc)(T*, const char*), void (*DestroyFunc)(T*)>
@ -278,7 +291,18 @@ void Achievements::LogFailedResponseJSON(const Common::HTTPDownloader::Request::
Log_ErrorPrintf("API call failed. Response JSON was:\n%s", str_data.c_str());
}
static Achievements::Achievement* Achievements::GetAchievementByID(u32 id)
const Achievements::Achievement* Achievements::GetAchievementByID(u32 id)
{
for (const Achievement& ach : s_achievements)
{
if (ach.id == id)
return &ach;
}
return nullptr;
}
Achievements::Achievement* Achievements::GetMutableAchievementByID(u32 id)
{
for (Achievement& ach : s_achievements)
{
@ -420,7 +444,7 @@ void Achievements::Initialize()
s_api_token = Host::GetBaseStringSettingValue("Cheevos", "Token");
s_logged_in = (!s_username.empty() && !s_api_token.empty());
if (IsLoggedIn() && System::IsValid())
if (System::IsValid())
GameChanged(System::GetRunningPath(), nullptr);
}
@ -742,6 +766,7 @@ bool Achievements::DoState(StateWrapper& sw)
}
}
else
#endif
{
// internally this happens twice.. not great.
const int size = rc_runtime_progress_size(&s_rcheevos_runtime, nullptr);
@ -757,7 +782,6 @@ bool Achievements::DoState(StateWrapper& sw)
data_size = 0;
}
}
#endif
sw.Do(&data_size);
if (data_size > 0)
@ -829,8 +853,8 @@ void Achievements::LoginCallback(s32 status_code, Common::HTTPDownloader::Reques
s_logged_in = true;
// If we have a game running, set it up.
if (System::IsValid())
GameChanged(System::GetRunningPath(), nullptr);
if (!s_game_hash.empty())
SendGetGameId();
}
}
@ -968,7 +992,10 @@ void Achievements::DisplayAchievementSummary()
}
}
ImGuiFullscreen::AddNotification(10.0f, std::move(title), std::move(summary), s_game_icon);
Host::RunOnCPUThread([title = std::move(title), summary = std::move(summary), icon = s_game_icon]() {
if (FullscreenUI::IsInitialized())
ImGuiFullscreen::AddNotification(10.0f, std::move(title), std::move(summary), std::move(icon));
});
}
void Achievements::GetUserUnlocksCallback(s32 status_code, Common::HTTPDownloader::Request::Data data)
@ -990,7 +1017,7 @@ void Achievements::GetUserUnlocksCallback(s32 status_code, Common::HTTPDownloade
// flag achievements as unlocked
for (u32 i = 0; i < response.num_achievement_ids; i++)
{
Achievement* cheevo = GetAchievementByID(response.achievement_ids[i]);
Achievement* cheevo = GetMutableAchievementByID(response.achievement_ids[i]);
if (!cheevo)
{
Log_ErrorPrintf("Server returned unknown achievement %u", response.achievement_ids[i]);
@ -1075,7 +1102,7 @@ void Achievements::GetPatchesCallback(s32 status_code, Common::HTTPDownloader::R
continue;
}
if (GetAchievementByID(defn.id))
if (GetMutableAchievementByID(defn.id))
{
Log_ErrorPrintf("Achievement %u already exists", defn.id);
continue;
@ -1351,6 +1378,12 @@ void Achievements::GameChanged(const std::string& path, CDImage* image)
return;
}
if (IsLoggedIn())
SendGetGameId();
}
void Achievements::SendGetGameId()
{
RAPIRequest<rc_api_resolve_hash_request_t, rc_api_init_resolve_hash_request> request;
request.username = s_username.c_str();
request.api_token = s_api_token.c_str();
@ -1643,7 +1676,7 @@ void Achievements::SubmitLeaderboardCallback(s32 status_code, Common::HTTPDownlo
void Achievements::UnlockAchievement(u32 achievement_id, bool add_notification /* = true*/)
{
Achievement* achievement = GetAchievementByID(achievement_id);
Achievement* achievement = GetMutableAchievementByID(achievement_id);
if (!achievement)
{
Log_ErrorPrintf("Attempting to unlock unknown achievement %u", achievement_id);
@ -1740,7 +1773,7 @@ std::string Achievements::GetAchievementProgressText(const Achievement& achievem
return buf;
}
const std::string& Achievements::GetAchievementBadgePath(const Achievement& achievement)
const std::string& Achievements::GetAchievementBadgePath(const Achievement& achievement, bool download_if_missing)
{
std::string& badge_path = achievement.locked ? achievement.locked_badge_path : achievement.unlocked_badge_path;
if (!badge_path.empty() || achievement.badge_name.empty())
@ -1754,11 +1787,23 @@ const std::string& Achievements::GetAchievementBadgePath(const Achievement& achi
return badge_path;
// need to download it
if (download_if_missing)
{
RAPIRequest<rc_api_fetch_image_request_t, rc_api_init_fetch_image_request> request;
request.image_name = achievement.badge_name.c_str();
request.image_type = achievement.locked ? RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED : RC_IMAGE_TYPE_ACHIEVEMENT;
request.DownloadImage(badge_path);
}
return badge_path;
}
std::string Achievements::GetAchievementBadgeURL(const Achievement& achievement)
{
RAPIRequest<rc_api_fetch_image_request_t, rc_api_init_fetch_image_request> request;
request.image_name = achievement.badge_name.c_str();
request.image_type = achievement.locked ? RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED : RC_IMAGE_TYPE_ACHIEVEMENT;
request.DownloadImage(badge_path);
return badge_path;
return request.GetURL();
}
void Achievements::CheevosEventHandler(const rc_runtime_event_t* runtime_event)

View File

@ -143,9 +143,11 @@ const Leaderboard* GetLeaderboardByID(u32 id);
u32 GetLeaderboardCount();
bool IsLeaderboardTimeType(const Leaderboard& leaderboard);
const Achievement* GetAchievementByID(u32 id);
std::pair<u32, u32> GetAchievementProgress(const Achievement& achievement);
std::string GetAchievementProgressText(const Achievement& achievement);
const std::string& GetAchievementBadgePath(const Achievement& achievement);
const std::string& GetAchievementBadgePath(const Achievement& achievement, bool download_if_missing = true);
std::string GetAchievementBadgeURL(const Achievement& achievement);
void UnlockAchievement(u32 achievement_id, bool add_notification = true);
void SubmitLeaderboard(u32 leaderboard_id, int value);

View File

@ -358,7 +358,7 @@ void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/,
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoBackground))
{
HostDisplayTexture* tex = ImGuiFullscreen::GetCachedTexture("fullscreenui/duck.png");
HostDisplayTexture* tex = ImGuiFullscreen::GetCachedTexture("images/duck.png");
if (tex)
ImGui::Image(tex->GetHandle(), ImVec2(logo_width, logo_height));
}

View File

@ -116,6 +116,7 @@ using ImGuiFullscreen::ThreeWayToggleButton;
using ImGuiFullscreen::ToggleButton;
using ImGuiFullscreen::WantsToCloseMenu;
#ifndef __ANDROID__
namespace FullscreenUI {
enum class MainWindowType
{
@ -159,11 +160,6 @@ enum class SettingsPage
Count
};
//////////////////////////////////////////////////////////////////////////
// Utility
//////////////////////////////////////////////////////////////////////////
static std::string TimeToPrintableString(time_t t);
//////////////////////////////////////////////////////////////////////////
// Main
//////////////////////////////////////////////////////////////////////////
@ -388,24 +384,6 @@ static std::optional<u32> s_open_leaderboard_id;
#endif
} // namespace FullscreenUI
//////////////////////////////////////////////////////////////////////////
// Utility
//////////////////////////////////////////////////////////////////////////
std::string FullscreenUI::TimeToPrintableString(time_t t)
{
struct tm lt = {};
#ifdef _MSC_VER
localtime_s(&lt, &t);
#else
localtime_r(&t, &lt);
#endif
char buf[256];
std::strftime(buf, sizeof(buf), "%c", &lt);
return std::string(buf);
}
//////////////////////////////////////////////////////////////////////////
// Main
//////////////////////////////////////////////////////////////////////////
@ -421,7 +399,7 @@ bool FullscreenUI::Initialize()
ImGuiFullscreen::SetTheme();
ImGuiFullscreen::UpdateLayoutScale();
if (!ImGuiManager::AddFullscreenFontsIfMissing() || !ImGuiFullscreen::Initialize("fullscreenui/placeholder.png") ||
if (!ImGuiManager::AddFullscreenFontsIfMissing() || !ImGuiFullscreen::Initialize("images/placeholder.png") ||
!LoadResources())
{
DestroyResources();
@ -437,7 +415,6 @@ bool FullscreenUI::Initialize()
s_was_paused_on_quick_menu_open = false;
s_about_window_open = false;
s_hotkey_list_cache = InputManager::GetHotkeyList();
// GetMTGS().SetRunIdle(true);
if (!System::IsValid())
SwitchToLanding();
@ -467,15 +444,14 @@ void FullscreenUI::OnSystemStarted()
void FullscreenUI::OnSystemPaused()
{
if (!IsInitialized())
return;
g_host_display->SetVSync(true);
// noop
}
void FullscreenUI::OnSystemResumed()
{
// noop
// get rid of pause menu if we unpaused another way
if (s_current_main_window == MainWindowType::PauseMenu)
ClosePauseMenu();
}
void FullscreenUI::OnSystemDestroyed()
@ -505,7 +481,15 @@ void FullscreenUI::PauseForMenuOpen()
{
s_was_paused_on_quick_menu_open = (System::GetState() == System::State::Paused);
if (g_settings.pause_on_menu && !s_was_paused_on_quick_menu_open)
Host::RunOnCPUThread([]() { System::PauseSystem(true); });
{
Host::RunOnCPUThread([]() {
System::PauseSystem(true);
// force vsync on when pausing
if (g_host_display)
g_host_display->SetVSync(true);
});
}
s_pause_menu_was_open = true;
}
@ -635,7 +619,7 @@ void FullscreenUI::ReturnToMainWindow()
bool FullscreenUI::LoadResources()
{
s_app_icon_texture = LoadTexture("fullscreenui/duck.png");
s_app_icon_texture = LoadTexture("images/duck.png");
s_fallback_disc_texture = LoadTexture("fullscreenui/media-cdrom.png");
s_fallback_exe_texture = LoadTexture("fullscreenui/applications-system.png");
@ -5796,3 +5780,104 @@ void DrawDebugDebugMenu()
}
}
#endif
#else
// "Lightweight" version with only notifications for Android.
namespace FullscreenUI {
static bool s_initialized = false;
static bool s_tried_to_initialize = false;
}
bool FullscreenUI::Initialize()
{
if (s_initialized)
return true;
if (s_tried_to_initialize)
return false;
ImGuiFullscreen::SetTheme();
ImGuiFullscreen::UpdateLayoutScale();
if (!ImGuiManager::AddFullscreenFontsIfMissing() || !ImGuiFullscreen::Initialize("images/placeholder.png"))
{
ImGuiFullscreen::Shutdown();
s_tried_to_initialize = true;
return false;
}
s_initialized = true;
return true;
}
bool FullscreenUI::IsInitialized()
{
return s_initialized;
}
bool FullscreenUI::HasActiveWindow()
{
return false;
}
void FullscreenUI::OnSystemStarted()
{
// noop
}
void FullscreenUI::OnSystemPaused()
{
// noop
}
void FullscreenUI::OnSystemResumed()
{
// noop
}
void FullscreenUI::OnSystemDestroyed()
{
// noop
}
void FullscreenUI::OnRunningGameChanged()
{
// noop
}
void FullscreenUI::OpenPauseMenu()
{
// noop
}
bool FullscreenUI::OpenAchievementsWindow()
{
return false;
}
bool FullscreenUI::OpenLeaderboardsWindow()
{
return false;
}
void FullscreenUI::Shutdown()
{
ImGuiFullscreen::Shutdown();
s_initialized = false;
s_tried_to_initialize = false;
}
void FullscreenUI::Render()
{
if (!s_initialized)
return;
ImGuiFullscreen::UploadAsyncTextures();
ImGuiFullscreen::BeginLayout();
ImGuiFullscreen::EndLayout();
ImGuiFullscreen::ResetCloseMenuIfNeeded();
}
#endif // __ANDROID__

View File

@ -114,7 +114,7 @@ bool GameList::GetExeListEntry(const std::string& path, GameList::Entry* entry)
const std::string display_name(FileSystem::GetDisplayNameFromPath(path));
entry->serial.clear();
entry->title = Path::StripExtension(display_name);
entry->title = Path::GetFileTitle(display_name);
entry->region = BIOS::GetPSExeDiscRegion(header);
entry->total_size = ZeroExtend64(file_size);
entry->type = EntryType::PSExe;
@ -156,7 +156,7 @@ bool GameList::GetPsfListEntry(const std::string& path, Entry* entry)
else
{
const std::string display_name(FileSystem::GetDisplayNameFromPath(path));
entry->title += Path::StripExtension(display_name);
entry->title += Path::GetFileTitle(display_name);
}
return true;
@ -193,7 +193,7 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry)
}
else
{
const std::string display_name(Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)));
const std::string display_name(FileSystem::GetDisplayNameFromPath(path));
// no game code, so use the filename title
entry->serial = System::GetGameCodeForImage(cdi.get(), true);