Merge branch 'stenzek:master' into master
This commit is contained in:
commit
0a8b81a0f0
|
@ -141,6 +141,6 @@ private:
|
|||
void AddPrefixFmtArgs(fmt::string_view fmt, fmt::format_args args);
|
||||
void AddSuffixFmtArgs(fmt::string_view fmt, fmt::format_args args);
|
||||
|
||||
Type m_type = Type::None;
|
||||
std::string m_description;
|
||||
Type m_type = Type::None;
|
||||
};
|
||||
|
|
|
@ -354,10 +354,13 @@ std::string Achievements::GetLocalImagePath(const std::string_view image_name, i
|
|||
|
||||
void Achievements::DownloadImage(std::string url, std::string cache_filename)
|
||||
{
|
||||
auto callback = [cache_filename](s32 status_code, const std::string& content_type,
|
||||
auto callback = [cache_filename](s32 status_code, const Error& error, const std::string& content_type,
|
||||
HTTPDownloader::Request::Data data) {
|
||||
if (status_code != HTTPDownloader::HTTP_STATUS_OK)
|
||||
{
|
||||
ERROR_LOG("Failed to download badge '{}': {}", Path::GetFileName(cache_filename), error.GetDescription());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FileSystem::WriteBinaryFile(cache_filename.c_str(), data.data(), data.size()))
|
||||
{
|
||||
|
@ -753,18 +756,22 @@ uint32_t Achievements::ClientReadMemory(uint32_t address, uint8_t* buffer, uint3
|
|||
void Achievements::ClientServerCall(const rc_api_request_t* request, rc_client_server_callback_t callback,
|
||||
void* callback_data, rc_client_t* client)
|
||||
{
|
||||
HTTPDownloader::Request::Callback hd_callback =
|
||||
[callback, callback_data](s32 status_code, const std::string& content_type, HTTPDownloader::Request::Data data) {
|
||||
rc_api_server_response_t rr;
|
||||
rr.http_status_code = (status_code <= 0) ? (status_code == HTTPDownloader::HTTP_STATUS_CANCELLED ?
|
||||
RC_API_SERVER_RESPONSE_CLIENT_ERROR :
|
||||
RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR) :
|
||||
status_code;
|
||||
rr.body_length = data.size();
|
||||
rr.body = reinterpret_cast<const char*>(data.data());
|
||||
HTTPDownloader::Request::Callback hd_callback = [callback, callback_data](s32 status_code, const Error& error,
|
||||
const std::string& content_type,
|
||||
HTTPDownloader::Request::Data data) {
|
||||
if (status_code != HTTPDownloader::HTTP_STATUS_OK)
|
||||
ERROR_LOG("Server call failed: {}", error.GetDescription());
|
||||
|
||||
callback(&rr, callback_data);
|
||||
};
|
||||
rc_api_server_response_t rr;
|
||||
rr.http_status_code = (status_code <= 0) ? (status_code == HTTPDownloader::HTTP_STATUS_CANCELLED ?
|
||||
RC_API_SERVER_RESPONSE_CLIENT_ERROR :
|
||||
RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR) :
|
||||
status_code;
|
||||
rr.body_length = data.size();
|
||||
rr.body = reinterpret_cast<const char*>(data.data());
|
||||
|
||||
callback(&rr, callback_data);
|
||||
};
|
||||
|
||||
HTTPDownloader* http = static_cast<HTTPDownloader*>(rc_client_get_userdata(client));
|
||||
|
||||
|
|
|
@ -84,28 +84,64 @@ bool AnalogController::DoState(StateWrapper& sw, bool apply_input_state)
|
|||
return false;
|
||||
|
||||
const bool old_analog_mode = m_analog_mode;
|
||||
|
||||
sw.Do(&m_analog_mode);
|
||||
sw.Do(&m_dualshock_enabled);
|
||||
sw.DoEx(&m_legacy_rumble_unlocked, 44, false);
|
||||
sw.Do(&m_configuration_mode);
|
||||
sw.Do(&m_command_param);
|
||||
sw.DoEx(&m_status_byte, 55, static_cast<u8>(0x5A));
|
||||
|
||||
u16 button_state = m_button_state;
|
||||
sw.DoEx(&button_state, 44, static_cast<u16>(0xFFFF));
|
||||
if (apply_input_state)
|
||||
m_button_state = button_state;
|
||||
|
||||
sw.Do(&m_command);
|
||||
|
||||
sw.DoEx(&m_rumble_config, 45, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||
sw.DoEx(&m_rumble_config_large_motor_index, 45, -1);
|
||||
sw.DoEx(&m_rumble_config_small_motor_index, 45, -1);
|
||||
sw.DoEx(&m_analog_toggle_queued, 45, false);
|
||||
|
||||
MotorState motor_state = m_motor_state;
|
||||
sw.Do(&motor_state);
|
||||
|
||||
if (sw.GetVersion() < 76) [[unlikely]]
|
||||
{
|
||||
u8 unused_command_param = 0;
|
||||
bool unused_legacy_rumble_unlocked = false;
|
||||
|
||||
sw.Do(&m_analog_mode);
|
||||
sw.Do(&m_dualshock_enabled);
|
||||
sw.DoEx(&unused_legacy_rumble_unlocked, 44, false);
|
||||
sw.Do(&m_configuration_mode);
|
||||
sw.Do(&unused_command_param);
|
||||
sw.DoEx(&m_status_byte, 55, static_cast<u8>(0x5A));
|
||||
|
||||
u16 button_state = m_button_state;
|
||||
sw.DoEx(&button_state, 44, static_cast<u16>(0xFFFF));
|
||||
if (apply_input_state)
|
||||
m_button_state = button_state;
|
||||
|
||||
sw.Do(&m_command);
|
||||
|
||||
int unused_rumble_config_large_motor_index = -1;
|
||||
int unused_rumble_config_small_motor_index = -1;
|
||||
|
||||
sw.DoEx(&m_rumble_config, 45, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||
sw.DoEx(&unused_rumble_config_large_motor_index, 45, -1);
|
||||
sw.DoEx(&unused_rumble_config_small_motor_index, 45, -1);
|
||||
sw.DoEx(&m_analog_toggle_queued, 45, false);
|
||||
|
||||
sw.Do(&motor_state);
|
||||
}
|
||||
else
|
||||
{
|
||||
sw.Do(&m_command);
|
||||
sw.Do(&m_command_step);
|
||||
sw.Do(&m_response_length);
|
||||
sw.DoBytes(m_rx_buffer.data(), m_rx_buffer.size());
|
||||
sw.DoBytes(m_tx_buffer.data(), m_tx_buffer.size());
|
||||
sw.Do(&m_analog_mode);
|
||||
sw.Do(&m_analog_locked);
|
||||
sw.Do(&m_dualshock_enabled);
|
||||
sw.Do(&m_configuration_mode);
|
||||
sw.DoBytes(m_rumble_config.data(), m_rumble_config.size());
|
||||
sw.Do(&m_status_byte);
|
||||
// sw.Do(&m_digital_mode_extra_halfwords); // always zero
|
||||
|
||||
auto axis_state = m_axis_state;
|
||||
u16 button_state = m_button_state;
|
||||
sw.DoBytes(axis_state.data(), axis_state.size());
|
||||
sw.Do(&button_state);
|
||||
sw.Do(&motor_state);
|
||||
|
||||
if (apply_input_state)
|
||||
{
|
||||
m_axis_state = axis_state;
|
||||
m_button_state = button_state;
|
||||
}
|
||||
}
|
||||
|
||||
if (sw.IsReading())
|
||||
{
|
||||
|
@ -368,8 +404,11 @@ void AnalogController::UpdateHostVibration()
|
|||
std::array<float, NUM_MOTORS> hvalues;
|
||||
for (u32 motor = 0; motor < NUM_MOTORS; motor++)
|
||||
{
|
||||
// Small motor is only 0/1.
|
||||
const u8 state =
|
||||
(motor == SmallMotor) ? (((m_motor_state[SmallMotor] & 0x01) != 0x00) ? 255 : 0) : m_motor_state[LargeMotor];
|
||||
|
||||
// Curve from https://github.com/KrossX/Pokopom/blob/master/Pokopom/Input_XInput.cpp#L210
|
||||
const u8 state = m_motor_state[motor];
|
||||
const double x = static_cast<double>(std::clamp<s32>(static_cast<s32>(state) + m_vibration_bias[motor], 0, 255));
|
||||
const double strength = 0.006474549734772402 * std::pow(x, 3.0) - 1.258165252213538 * std::pow(x, 2.0) +
|
||||
156.82454281087692 * x + 3.637978807091713e-11;
|
||||
|
@ -377,7 +416,8 @@ void AnalogController::UpdateHostVibration()
|
|||
hvalues[motor] = (state != 0) ? static_cast<float>(strength / 65535.0) : 0.0f;
|
||||
}
|
||||
|
||||
InputManager::SetPadVibrationIntensity(m_index, hvalues[0], hvalues[1]);
|
||||
WARNING_LOG("Set small to {}, large to {}", hvalues[SmallMotor], hvalues[LargeMotor]);
|
||||
InputManager::SetPadVibrationIntensity(m_index, hvalues[LargeMotor], hvalues[SmallMotor]);
|
||||
}
|
||||
|
||||
u8 AnalogController::GetExtraButtonMaskLSB() const
|
||||
|
@ -402,20 +442,8 @@ u8 AnalogController::GetExtraButtonMaskLSB() const
|
|||
void AnalogController::ResetRumbleConfig()
|
||||
{
|
||||
m_rumble_config.fill(0xFF);
|
||||
|
||||
m_rumble_config_large_motor_index = -1;
|
||||
m_rumble_config_small_motor_index = -1;
|
||||
|
||||
SetMotorState(LargeMotor, 0);
|
||||
SetMotorState(SmallMotor, 0);
|
||||
}
|
||||
|
||||
void AnalogController::SetMotorStateForConfigIndex(int index, u8 value)
|
||||
{
|
||||
if (m_rumble_config_small_motor_index == index)
|
||||
SetMotorState(SmallMotor, ((value & 0x01) != 0) ? 255 : 0);
|
||||
else if (m_rumble_config_large_motor_index == index)
|
||||
SetMotorState(LargeMotor, value);
|
||||
SetMotorState(LargeMotor, 0);
|
||||
}
|
||||
|
||||
u8 AnalogController::GetResponseNumHalfwords() const
|
||||
|
@ -442,6 +470,29 @@ u8 AnalogController::GetIDByte() const
|
|||
return Truncate8((GetModeID() << 4) | GetResponseNumHalfwords());
|
||||
}
|
||||
|
||||
void AnalogController::Poll()
|
||||
{
|
||||
// m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
m_tx_buffer[0] = GetIDByte();
|
||||
m_tx_buffer[1] = m_status_byte;
|
||||
m_tx_buffer[2] = Truncate8(m_button_state) & GetExtraButtonMaskLSB();
|
||||
m_tx_buffer[3] = Truncate8(m_button_state >> 8);
|
||||
if (m_analog_mode || m_configuration_mode)
|
||||
{
|
||||
m_tx_buffer[4] = m_axis_state[static_cast<u8>(Axis::RightX)];
|
||||
m_tx_buffer[5] = m_axis_state[static_cast<u8>(Axis::RightY)];
|
||||
m_tx_buffer[6] = m_axis_state[static_cast<u8>(Axis::LeftX)];
|
||||
m_tx_buffer[7] = m_axis_state[static_cast<u8>(Axis::LeftY)];
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tx_buffer[4] = 0;
|
||||
m_tx_buffer[5] = 0;
|
||||
m_tx_buffer[6] = 0;
|
||||
m_tx_buffer[7] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool AnalogController::Transfer(const u8 data_in, u8* data_out)
|
||||
{
|
||||
bool ack;
|
||||
|
@ -472,14 +523,17 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out)
|
|||
Assert(m_command_step == 0);
|
||||
m_response_length = (GetResponseNumHalfwords() + 1) * 2;
|
||||
m_command = Command::ReadPad;
|
||||
m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
Poll();
|
||||
}
|
||||
else if (data_in == 0x43)
|
||||
{
|
||||
Assert(m_command_step == 0);
|
||||
m_response_length = (GetResponseNumHalfwords() + 1) * 2;
|
||||
m_command = Command::ConfigModeSetMode;
|
||||
m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
if (!m_configuration_mode)
|
||||
Poll();
|
||||
else
|
||||
m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
}
|
||||
else if (m_configuration_mode && data_in == 0x44)
|
||||
{
|
||||
|
@ -524,9 +578,6 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out)
|
|||
m_response_length = (GetResponseNumHalfwords() + 1) * 2;
|
||||
m_command = Command::GetSetRumble;
|
||||
m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
m_rumble_config_large_motor_index = -1;
|
||||
m_rumble_config_small_motor_index = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -541,136 +592,25 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out)
|
|||
|
||||
case Command::ReadPad:
|
||||
{
|
||||
const int rumble_index = m_command_step - 2;
|
||||
|
||||
switch (m_command_step)
|
||||
if (m_dualshock_enabled)
|
||||
{
|
||||
case 2:
|
||||
if (m_command_step >= 2 && m_command_step < 7)
|
||||
{
|
||||
m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB();
|
||||
|
||||
if (m_dualshock_enabled)
|
||||
SetMotorStateForConfigIndex(rumble_index, data_in);
|
||||
const u8 motor_to_set = m_rumble_config[m_command_step - 2];
|
||||
if (motor_to_set <= LargeMotor)
|
||||
SetMotorState(motor_to_set, data_in);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
{
|
||||
m_tx_buffer[m_command_step] = Truncate8(m_button_state >> 8);
|
||||
|
||||
if (m_dualshock_enabled)
|
||||
{
|
||||
SetMotorStateForConfigIndex(rumble_index, data_in);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool legacy_rumble_on = (m_rx_buffer[2] & 0xC0) == 0x40 && (m_rx_buffer[3] & 0x01) != 0;
|
||||
SetMotorState(SmallMotor, legacy_rumble_on ? 255 : 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
{
|
||||
if (m_configuration_mode || m_analog_mode)
|
||||
m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::RightX)];
|
||||
|
||||
if (m_dualshock_enabled)
|
||||
SetMotorStateForConfigIndex(rumble_index, data_in);
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
{
|
||||
if (m_configuration_mode || m_analog_mode)
|
||||
m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::RightY)];
|
||||
|
||||
if (m_dualshock_enabled)
|
||||
SetMotorStateForConfigIndex(rumble_index, data_in);
|
||||
}
|
||||
break;
|
||||
|
||||
case 6:
|
||||
{
|
||||
if (m_configuration_mode || m_analog_mode)
|
||||
m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::LeftX)];
|
||||
|
||||
if (m_dualshock_enabled)
|
||||
SetMotorStateForConfigIndex(rumble_index, data_in);
|
||||
}
|
||||
break;
|
||||
|
||||
case 7:
|
||||
{
|
||||
if (m_configuration_mode || m_analog_mode)
|
||||
m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::LeftY)];
|
||||
|
||||
if (m_dualshock_enabled)
|
||||
SetMotorStateForConfigIndex(rumble_index, data_in);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (m_command_step == 3)
|
||||
{
|
||||
const bool legacy_rumble_on = (m_rx_buffer[2] & 0xC0) == 0x40 && (m_rx_buffer[3] & 0x01) != 0;
|
||||
SetMotorState(SmallMotor, legacy_rumble_on ? 255 : 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Command::ConfigModeSetMode:
|
||||
{
|
||||
if (!m_configuration_mode)
|
||||
{
|
||||
switch (m_command_step)
|
||||
{
|
||||
case 2:
|
||||
{
|
||||
m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB();
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
{
|
||||
m_tx_buffer[m_command_step] = Truncate8(m_button_state >> 8);
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
{
|
||||
if (m_configuration_mode || m_analog_mode)
|
||||
m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::RightX)];
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
{
|
||||
if (m_configuration_mode || m_analog_mode)
|
||||
m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::RightY)];
|
||||
}
|
||||
break;
|
||||
|
||||
case 6:
|
||||
{
|
||||
if (m_configuration_mode || m_analog_mode)
|
||||
m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::LeftX)];
|
||||
}
|
||||
break;
|
||||
|
||||
case 7:
|
||||
{
|
||||
if (m_configuration_mode || m_analog_mode)
|
||||
m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::LeftY)];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_command_step == (static_cast<s32>(m_response_length) - 1))
|
||||
{
|
||||
m_configuration_mode = (m_rx_buffer[2] == 1);
|
||||
|
@ -759,25 +699,31 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out)
|
|||
|
||||
case Command::GetSetRumble:
|
||||
{
|
||||
int rumble_index = m_command_step - 2;
|
||||
if (rumble_index >= 0)
|
||||
if (m_command_step >= 2 && m_command_step < 7)
|
||||
{
|
||||
m_tx_buffer[m_command_step] = m_rumble_config[rumble_index];
|
||||
m_rumble_config[rumble_index] = data_in;
|
||||
const u8 index = m_command_step - 2;
|
||||
m_tx_buffer[m_command_step] = m_rumble_config[index];
|
||||
m_rumble_config[index] = data_in;
|
||||
|
||||
if (data_in == 0x00)
|
||||
m_rumble_config_small_motor_index = rumble_index;
|
||||
else if (data_in == 0x01)
|
||||
m_rumble_config_large_motor_index = rumble_index;
|
||||
if (data_in == LargeMotor)
|
||||
WARNING_LOG("Large mapped to byte index {}", index);
|
||||
else if (data_in == SmallMotor)
|
||||
WARNING_LOG("Small mapped to byte index {}", index);
|
||||
}
|
||||
|
||||
if (m_command_step == 7)
|
||||
else if (m_command_step == 7)
|
||||
{
|
||||
if (m_rumble_config_large_motor_index == -1)
|
||||
SetMotorState(LargeMotor, 0);
|
||||
|
||||
if (m_rumble_config_small_motor_index == -1)
|
||||
// reset motor value if we're no longer mapping it
|
||||
bool has_small = false;
|
||||
bool has_large = false;
|
||||
for (size_t i = 0; i < m_rumble_config.size(); i++)
|
||||
{
|
||||
has_small |= (m_rumble_config[i] == SmallMotor);
|
||||
has_large |= (m_rumble_config[i] == LargeMotor);
|
||||
}
|
||||
if (!has_small)
|
||||
SetMotorState(SmallMotor, 0);
|
||||
if (!has_large)
|
||||
SetMotorState(LargeMotor, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -813,14 +759,14 @@ std::unique_ptr<AnalogController> AnalogController::Create(u32 index)
|
|||
|
||||
static const Controller::ControllerBindingInfo s_binding_info[] = {
|
||||
#define BUTTON(name, display_name, icon_name, button, genb) \
|
||||
{ \
|
||||
name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb \
|
||||
}
|
||||
{name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb}
|
||||
#define AXIS(name, display_name, icon_name, halfaxis, genb) \
|
||||
{ \
|
||||
name, display_name, icon_name, static_cast<u32>(AnalogController::Button::Count) + static_cast<u32>(halfaxis), \
|
||||
InputBindingInfo::Type::HalfAxis, genb \
|
||||
}
|
||||
{name, \
|
||||
display_name, \
|
||||
icon_name, \
|
||||
static_cast<u32>(AnalogController::Button::Count) + static_cast<u32>(halfaxis), \
|
||||
InputBindingInfo::Type::HalfAxis, \
|
||||
genb}
|
||||
|
||||
// clang-format off
|
||||
BUTTON("Up", TRANSLATE_NOOP("AnalogController", "D-Pad Up"), ICON_PF_DPAD_UP, AnalogController::Button::Up, GenericInputBinding::DPadUp),
|
||||
|
|
|
@ -103,13 +103,13 @@ private:
|
|||
static constexpr s16 DEFAULT_LARGE_MOTOR_VIBRATION_BIAS = 8;
|
||||
|
||||
Command m_command = Command::Idle;
|
||||
int m_command_step = 0;
|
||||
u8 m_command_step = 0;
|
||||
u8 m_response_length = 0;
|
||||
|
||||
// Transmit and receive buffers, not including the first Hi-Z/ack response byte
|
||||
static constexpr u32 MAX_RESPONSE_LENGTH = 8;
|
||||
std::array<u8, MAX_RESPONSE_LENGTH> m_rx_buffer;
|
||||
std::array<u8, MAX_RESPONSE_LENGTH> m_tx_buffer;
|
||||
u32 m_response_length = 0;
|
||||
|
||||
// Get number of response halfwords (excluding the initial controller info halfword)
|
||||
u8 GetResponseNumHalfwords() const;
|
||||
|
@ -123,7 +123,7 @@ private:
|
|||
void UpdateHostVibration();
|
||||
u8 GetExtraButtonMaskLSB() const;
|
||||
void ResetRumbleConfig();
|
||||
void SetMotorStateForConfigIndex(int index, u8 value);
|
||||
void Poll();
|
||||
|
||||
float m_analog_deadzone = 0.0f;
|
||||
float m_analog_sensitivity = 1.33f;
|
||||
|
@ -143,13 +143,11 @@ private:
|
|||
|
||||
enum : u8
|
||||
{
|
||||
LargeMotor = 0,
|
||||
SmallMotor = 1
|
||||
SmallMotor = 0,
|
||||
LargeMotor = 1,
|
||||
};
|
||||
|
||||
std::array<u8, 6> m_rumble_config{};
|
||||
int m_rumble_config_large_motor_index = -1;
|
||||
int m_rumble_config_small_motor_index = -1;
|
||||
|
||||
bool m_analog_toggle_queued = false;
|
||||
u8 m_status_byte = 0;
|
||||
|
@ -164,8 +162,4 @@ private:
|
|||
|
||||
// both directions of axis state, merged to m_axis_state
|
||||
std::array<u8, static_cast<u32>(HalfAxis::Count)> m_half_axis_state{};
|
||||
|
||||
// Member variables that are no longer used, but kept and serialized for compatibility with older save states
|
||||
u8 m_command_param = 0;
|
||||
bool m_legacy_rumble_unlocked = false;
|
||||
};
|
||||
|
|
|
@ -198,7 +198,8 @@ using EnableCodeList = std::vector<std::string>;
|
|||
static std::string GetChtTemplate(const std::string_view serial, std::optional<GameHash> hash, bool add_wildcard);
|
||||
static std::vector<std::string> FindChtFilesOnDisk(const std::string_view serial, std::optional<GameHash> hash,
|
||||
bool cheats);
|
||||
static void ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bool from_database);
|
||||
static bool ExtractCodeInfo(CodeInfoList* dst, const std::string_view file_data, bool from_database, bool stop_on_error,
|
||||
Error* error);
|
||||
static void AppendCheatToList(CodeInfoList* dst, CodeInfo code);
|
||||
static std::string FormatCodeForFile(const CodeInfo& code);
|
||||
|
||||
|
@ -511,7 +512,7 @@ Cheats::CodeInfoList Cheats::GetCodeInfoList(const std::string_view serial, std:
|
|||
|
||||
EnumerateChtFiles(serial, hash, cheats, true, true, load_from_database,
|
||||
[&ret](const std::string& filename, const std::string& data, bool from_database) {
|
||||
ExtractCodeInfo(&ret, data, from_database);
|
||||
ExtractCodeInfo(&ret, data, from_database, false, nullptr);
|
||||
});
|
||||
|
||||
if (sort_by_name)
|
||||
|
@ -605,7 +606,7 @@ bool Cheats::UpdateCodeInFile(const char* path, const std::string_view name, con
|
|||
if (!file_contents.empty() && !name.empty())
|
||||
{
|
||||
CodeInfoList existing_codes_in_file;
|
||||
ExtractCodeInfo(&existing_codes_in_file, file_contents, false);
|
||||
ExtractCodeInfo(&existing_codes_in_file, file_contents, false, false, nullptr);
|
||||
|
||||
const CodeInfo* existing_code = FindCodeInInfoList(existing_codes_in_file, name);
|
||||
if (existing_code)
|
||||
|
@ -664,7 +665,7 @@ bool Cheats::SaveCodesToFile(const char* path, const CodeInfoList& codes, Error*
|
|||
if (!file_contents.empty())
|
||||
{
|
||||
CodeInfoList existing_codes_in_file;
|
||||
ExtractCodeInfo(&existing_codes_in_file, file_contents, false);
|
||||
ExtractCodeInfo(&existing_codes_in_file, file_contents, false, false, nullptr);
|
||||
|
||||
const CodeInfo* existing_code = FindCodeInInfoList(existing_codes_in_file, code.name);
|
||||
if (existing_code)
|
||||
|
@ -980,7 +981,8 @@ u32 Cheats::GetActiveCheatCount()
|
|||
// File Parsing
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bool from_database)
|
||||
bool Cheats::ExtractCodeInfo(CodeInfoList* dst, std::string_view file_data, bool from_database, bool stop_on_error,
|
||||
Error* error)
|
||||
{
|
||||
CodeInfo current_code;
|
||||
|
||||
|
@ -988,17 +990,24 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
|
|||
std::optional<CodeType> legacy_type;
|
||||
std::optional<CodeActivation> legacy_activation;
|
||||
|
||||
const auto finish_code = [&dst, &file_data, ¤t_code]() {
|
||||
CheatFileReader reader(file_data);
|
||||
|
||||
const auto finish_code = [&dst, &file_data, &stop_on_error, &error, ¤t_code, &reader]() {
|
||||
if (current_code.file_offset_end > current_code.file_offset_body_start)
|
||||
{
|
||||
current_code.body = std::string_view(file_data).substr(
|
||||
current_code.file_offset_body_start, current_code.file_offset_end - current_code.file_offset_body_start);
|
||||
current_code.body = file_data.substr(current_code.file_offset_body_start,
|
||||
current_code.file_offset_end - current_code.file_offset_body_start);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!reader.LogError(error, stop_on_error, "Empty body for cheat '{}'", current_code.name))
|
||||
return false;
|
||||
}
|
||||
|
||||
AppendCheatToList(dst, std::move(current_code));
|
||||
return true;
|
||||
};
|
||||
|
||||
CheatFileReader reader(file_data);
|
||||
std::string_view line;
|
||||
while (reader.GetLine(&line))
|
||||
{
|
||||
|
@ -1016,15 +1025,23 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
|
|||
{
|
||||
legacy_type = ParseTypeName(StringUtil::StripWhitespace(linev.substr(6)));
|
||||
if (!legacy_type.has_value()) [[unlikely]]
|
||||
WARNING_LOG("Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line);
|
||||
continue;
|
||||
{
|
||||
if (!reader.LogError(error, stop_on_error, "Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line))
|
||||
return false;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (linev.starts_with("#activation="))
|
||||
{
|
||||
legacy_activation = ParseActivationName(StringUtil::StripWhitespace(linev.substr(12)));
|
||||
if (!legacy_activation.has_value()) [[unlikely]]
|
||||
WARNING_LOG("Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line);
|
||||
continue;
|
||||
{
|
||||
if (!reader.LogError(error, stop_on_error, "Unknown type at line {}: {}", reader.GetCurrentLineNumber(), line))
|
||||
return false;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// skip comments
|
||||
|
@ -1035,7 +1052,24 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
|
|||
{
|
||||
if (linev.size() < 3 || linev.back() != ']')
|
||||
{
|
||||
WARNING_LOG("Malformed code at line {}: {}", reader.GetCurrentLineNumber(), line);
|
||||
if (!reader.LogError(error, stop_on_error, "Malformed code at line {}: {}", reader.GetCurrentLineNumber(),
|
||||
line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string_view name = StringUtil::StripWhitespace(linev.substr(1, linev.length() - 2));
|
||||
if (name.empty())
|
||||
{
|
||||
if (!reader.LogError(error, stop_on_error, "Empty code name at line {}: {}", reader.GetCurrentLineNumber(),
|
||||
line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1047,7 +1081,6 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
|
|||
current_code = CodeInfo();
|
||||
}
|
||||
|
||||
const std::string_view name = linev.substr(1, linev.length() - 2);
|
||||
current_code.name =
|
||||
legacy_group.has_value() ? fmt::format("{}\\{}", legacy_group.value(), name) : std::string(name);
|
||||
current_code.type = legacy_type.value_or(CodeType::Gameshark);
|
||||
|
@ -1074,7 +1107,12 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
|
|||
std::string_view key, value;
|
||||
if (!StringUtil::ParseAssignmentString(linev, &key, &value))
|
||||
{
|
||||
WARNING_LOG("Malformed code at line {}: {}", reader.GetCurrentLineNumber(), line);
|
||||
if (!reader.LogError(error, stop_on_error, "Malformed code at line {}: {}", reader.GetCurrentLineNumber(),
|
||||
line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1090,29 +1128,57 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
|
|||
{
|
||||
const std::optional<CodeType> type = ParseTypeName(value);
|
||||
if (type.has_value()) [[unlikely]]
|
||||
{
|
||||
current_code.type = type.value();
|
||||
}
|
||||
else
|
||||
WARNING_LOG("Unknown code type at line {}: {}", reader.GetCurrentLineNumber(), line);
|
||||
{
|
||||
if (!reader.LogError(error, stop_on_error, "Unknown code type at line {}: {}", reader.GetCurrentLineNumber(),
|
||||
line))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (key == "Activation")
|
||||
{
|
||||
const std::optional<CodeActivation> activation = ParseActivationName(value);
|
||||
if (activation.has_value()) [[unlikely]]
|
||||
{
|
||||
current_code.activation = activation.value();
|
||||
}
|
||||
else
|
||||
WARNING_LOG("Unknown code activation at line {}: {}", reader.GetCurrentLineNumber(), line);
|
||||
{
|
||||
if (!reader.LogError(error, stop_on_error, "Unknown code activation at line {}: {}",
|
||||
reader.GetCurrentLineNumber(), line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (key == "Option")
|
||||
{
|
||||
if (std::optional<Cheats::CodeOption> opt = ParseOption(value))
|
||||
{
|
||||
current_code.options.push_back(std::move(opt.value()));
|
||||
}
|
||||
else
|
||||
WARNING_LOG("Invalid option declaration at line {}: {}", reader.GetCurrentLineNumber(), line);
|
||||
{
|
||||
if (!reader.LogError(error, stop_on_error, "Invalid option declaration at line {}: {}",
|
||||
reader.GetCurrentLineNumber(), line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (key == "OptionRange")
|
||||
{
|
||||
if (!ParseOptionRange(value, ¤t_code.option_range_start, ¤t_code.option_range_end))
|
||||
WARNING_LOG("Invalid option range declaration at line {}: {}", reader.GetCurrentLineNumber(), line);
|
||||
{
|
||||
if (!reader.LogError(error, stop_on_error, "Invalid option range declaration at line {}: {}",
|
||||
reader.GetCurrentLineNumber(), line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ignore other keys when we're only grabbing info
|
||||
|
@ -1121,7 +1187,12 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
|
|||
|
||||
if (current_code.name.empty())
|
||||
{
|
||||
WARNING_LOG("Code data specified without name at line {}: {}", reader.GetCurrentLineNumber(), line);
|
||||
if (!reader.LogError(error, stop_on_error, "Code data specified without name at line {}: {}",
|
||||
reader.GetCurrentLineNumber(), line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1134,7 +1205,9 @@ void Cheats::ExtractCodeInfo(CodeInfoList* dst, const std::string& file_data, bo
|
|||
|
||||
// last code.
|
||||
if (!current_code.name.empty())
|
||||
finish_code();
|
||||
return finish_code();
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void Cheats::AppendCheatToList(CodeInfoList* dst, CodeInfo code)
|
||||
|
@ -1246,11 +1319,17 @@ void Cheats::ParseFile(CheatCodeList* dst_list, const std::string_view file_cont
|
|||
continue;
|
||||
}
|
||||
|
||||
const std::string_view name = StringUtil::StripWhitespace(linev.substr(1, linev.length() - 2));
|
||||
if (name.empty())
|
||||
{
|
||||
WARNING_LOG("Empty cheat code name at line {}: {}", reader.GetCurrentLineNumber(), line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!next_code_metadata.name.empty())
|
||||
finish_code();
|
||||
|
||||
// new code.
|
||||
const std::string_view name = linev.substr(1, linev.length() - 2);
|
||||
next_code_metadata.name =
|
||||
next_code_group.empty() ? std::string(name) : fmt::format("{}\\{}", next_code_group, name);
|
||||
continue;
|
||||
|
@ -1418,7 +1497,12 @@ bool Cheats::ImportCodesFromString(CodeInfoList* dst, const std::string_view fil
|
|||
if (file_format == FileFormat::Unknown)
|
||||
file_format = DetectFileFormat(file_contents);
|
||||
|
||||
if (file_format == FileFormat::PCSX)
|
||||
if (file_format == FileFormat::DuckStation)
|
||||
{
|
||||
if (!ExtractCodeInfo(dst, file_contents, false, stop_on_error, error))
|
||||
return false;
|
||||
}
|
||||
else if (file_format == FileFormat::PCSX)
|
||||
{
|
||||
if (!ImportPCSXFile(dst, file_contents, stop_on_error, error))
|
||||
return false;
|
||||
|
@ -1462,6 +1546,10 @@ Cheats::FileFormat Cheats::DetectFileFormat(const std::string_view file_contents
|
|||
if (linev.starts_with("cheats"))
|
||||
return FileFormat::Libretro;
|
||||
|
||||
// native if we see brackets and a type string
|
||||
if (linev[0] == '[' && file_contents.find("\nType ="))
|
||||
return FileFormat::DuckStation;
|
||||
|
||||
// pcsxr if we see brackets
|
||||
if (linev[0] == '[')
|
||||
return FileFormat::PCSX;
|
||||
|
@ -1514,12 +1602,26 @@ bool Cheats::ImportPCSXFile(CodeInfoList* dst, const std::string_view file_conte
|
|||
continue;
|
||||
}
|
||||
|
||||
std::string_view name_part = StringUtil::StripWhitespace(linev.substr(1, linev.length() - 2));
|
||||
if (!name_part.empty() && name_part.front() == '*')
|
||||
name_part = name_part.substr(1);
|
||||
if (name_part.empty())
|
||||
{
|
||||
if (!reader.LogError(error, stop_on_error, "Empty code name at line {}: {}", reader.GetCurrentLineNumber(),
|
||||
line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// new code.
|
||||
if (!current_code.name.empty() && !finish_code())
|
||||
return false;
|
||||
|
||||
current_code = CodeInfo();
|
||||
current_code.name = (linev[1] == '*') ? linev.substr(2, linev.length() - 3) : linev.substr(1, linev.length() - 2);
|
||||
current_code.name = name_part;
|
||||
current_code.file_offset_start = static_cast<u32>(reader.GetCurrentLineOffset());
|
||||
current_code.file_offset_end = current_code.file_offset_start;
|
||||
current_code.file_offset_body_start = current_code.file_offset_start;
|
||||
|
@ -1683,12 +1785,24 @@ bool Cheats::ImportEPSXeFile(CodeInfoList* dst, const std::string_view file_cont
|
|||
continue;
|
||||
}
|
||||
|
||||
const std::string_view name_part = StringUtil::StripWhitespace(linev.substr(1));
|
||||
if (name_part.empty())
|
||||
{
|
||||
if (!reader.LogError(error, stop_on_error, "Empty code name at line {}: {}", reader.GetCurrentLineNumber(),
|
||||
line))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!current_code.name.empty() && !finish_code())
|
||||
return false;
|
||||
|
||||
// new code.
|
||||
current_code = CodeInfo();
|
||||
current_code.name = linev.substr(1);
|
||||
current_code.name = name_part;
|
||||
current_code.file_offset_start = static_cast<u32>(reader.GetCurrentOffset());
|
||||
current_code.file_offset_end = current_code.file_offset_start;
|
||||
current_code.file_offset_body_start = current_code.file_offset_start;
|
||||
|
@ -2360,7 +2474,7 @@ void Cheats::GamesharkCheatCode::Apply() const
|
|||
index++;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case InstructionCode::ExtConstantWriteIfMatchWithRestore8:
|
||||
{
|
||||
const u8 value = DoMemoryRead<u8>(inst.address);
|
||||
|
@ -2372,7 +2486,7 @@ void Cheats::GamesharkCheatCode::Apply() const
|
|||
index++;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case InstructionCode::ExtConstantForceRange8:
|
||||
{
|
||||
const u8 value = DoMemoryRead<u8>(inst.address);
|
||||
|
@ -3950,14 +4064,14 @@ void Cheats::GamesharkCheatCode::ApplyOnDisable() const
|
|||
index++;
|
||||
}
|
||||
break;
|
||||
|
||||
[[unlikely]] default:
|
||||
{
|
||||
ERROR_LOG("Unhandled instruction code 0x{:02X} ({:08X} {:08X})", static_cast<u8>(inst.code.GetValue()),
|
||||
inst.first, inst.second);
|
||||
index++;
|
||||
}
|
||||
break;
|
||||
|
||||
[[unlikely]] default:
|
||||
{
|
||||
ERROR_LOG("Unhandled instruction code 0x{:02X} ({:08X} {:08X})", static_cast<u8>(inst.code.GetValue()),
|
||||
inst.first, inst.second);
|
||||
index++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ enum class CodeActivation : u8
|
|||
enum class FileFormat : u8
|
||||
{
|
||||
Unknown,
|
||||
DuckStation,
|
||||
PCSX,
|
||||
Libretro,
|
||||
EPSXe,
|
||||
|
|
|
@ -634,7 +634,7 @@ void GameList::ApplyCustomAttributes(const std::string& path, Entry* entry,
|
|||
if (custom_language_str.has_value())
|
||||
{
|
||||
const std::optional<GameDatabase::Language> custom_region =
|
||||
GameDatabase::ParseLanguageName(custom_region_str.value());
|
||||
GameDatabase::ParseLanguageName(custom_language_str.value());
|
||||
if (custom_region.has_value())
|
||||
{
|
||||
entry->custom_language = custom_region.value();
|
||||
|
@ -1454,10 +1454,11 @@ bool GameList::DownloadCovers(const std::vector<std::string>& url_templates, boo
|
|||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<HTTPDownloader> downloader(HTTPDownloader::Create(Host::GetHTTPUserAgent()));
|
||||
Error error;
|
||||
std::unique_ptr<HTTPDownloader> downloader(HTTPDownloader::Create(Host::GetHTTPUserAgent(), &error));
|
||||
if (!downloader)
|
||||
{
|
||||
progress->DisplayError("Failed to create HTTP downloader.");
|
||||
progress->DisplayError(fmt::format("Failed to create HTTP downloader:\n{}", error.GetDescription()));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1484,39 +1485,43 @@ bool GameList::DownloadCovers(const std::vector<std::string>& url_templates, boo
|
|||
|
||||
// we could actually do a few in parallel here...
|
||||
std::string filename = Path::URLDecode(url);
|
||||
downloader->CreateRequest(
|
||||
std::move(url), [use_serial, &save_callback, entry_path = std::move(entry_path), filename = std::move(filename)](
|
||||
s32 status_code, const std::string& content_type, HTTPDownloader::Request::Data data) {
|
||||
if (status_code != HTTPDownloader::HTTP_STATUS_OK || data.empty())
|
||||
return;
|
||||
downloader->CreateRequest(std::move(url), [use_serial, &save_callback, entry_path = std::move(entry_path),
|
||||
filename = std::move(filename)](s32 status_code, const Error& error,
|
||||
const std::string& content_type,
|
||||
HTTPDownloader::Request::Data data) {
|
||||
if (status_code != HTTPDownloader::HTTP_STATUS_OK || data.empty())
|
||||
{
|
||||
ERROR_LOG("Download for {} failed: {}", Path::GetFileName(filename), error.GetDescription());
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock(s_mutex);
|
||||
const GameList::Entry* entry = GetEntryForPath(entry_path);
|
||||
if (!entry || !GetCoverImagePathForEntry(entry).empty())
|
||||
return;
|
||||
std::unique_lock lock(s_mutex);
|
||||
const GameList::Entry* entry = GetEntryForPath(entry_path);
|
||||
if (!entry || !GetCoverImagePathForEntry(entry).empty())
|
||||
return;
|
||||
|
||||
// prefer the content type from the response for the extension
|
||||
// otherwise, if it's missing, and the request didn't have an extension.. fall back to jpegs.
|
||||
std::string template_filename;
|
||||
std::string content_type_extension(HTTPDownloader::GetExtensionForContentType(content_type));
|
||||
// prefer the content type from the response for the extension
|
||||
// otherwise, if it's missing, and the request didn't have an extension.. fall back to jpegs.
|
||||
std::string template_filename;
|
||||
std::string content_type_extension(HTTPDownloader::GetExtensionForContentType(content_type));
|
||||
|
||||
// don't treat the domain name as an extension..
|
||||
const std::string::size_type last_slash = filename.find('/');
|
||||
const std::string::size_type last_dot = filename.find('.');
|
||||
if (!content_type_extension.empty())
|
||||
template_filename = fmt::format("cover.{}", content_type_extension);
|
||||
else if (last_slash != std::string::npos && last_dot != std::string::npos && last_dot > last_slash)
|
||||
template_filename = Path::GetFileName(filename);
|
||||
else
|
||||
template_filename = "cover.jpg";
|
||||
// don't treat the domain name as an extension..
|
||||
const std::string::size_type last_slash = filename.find('/');
|
||||
const std::string::size_type last_dot = filename.find('.');
|
||||
if (!content_type_extension.empty())
|
||||
template_filename = fmt::format("cover.{}", content_type_extension);
|
||||
else if (last_slash != std::string::npos && last_dot != std::string::npos && last_dot > last_slash)
|
||||
template_filename = Path::GetFileName(filename);
|
||||
else
|
||||
template_filename = "cover.jpg";
|
||||
|
||||
std::string write_path(GetNewCoverImagePathForEntry(entry, template_filename.c_str(), use_serial));
|
||||
if (write_path.empty())
|
||||
return;
|
||||
std::string write_path(GetNewCoverImagePathForEntry(entry, template_filename.c_str(), use_serial));
|
||||
if (write_path.empty())
|
||||
return;
|
||||
|
||||
if (FileSystem::WriteBinaryFile(write_path.c_str(), data.data(), data.size()) && save_callback)
|
||||
save_callback(entry, std::move(write_path));
|
||||
});
|
||||
if (FileSystem::WriteBinaryFile(write_path.c_str(), data.data(), data.size()) && save_callback)
|
||||
save_callback(entry, std::move(write_path));
|
||||
});
|
||||
downloader->WaitForAllRequests();
|
||||
progress->IncrementProgressValue();
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "common/types.h"
|
||||
|
||||
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
||||
static constexpr u32 SAVE_STATE_VERSION = 75;
|
||||
static constexpr u32 SAVE_STATE_VERSION = 76;
|
||||
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
|
||||
|
||||
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);
|
||||
|
|
|
@ -88,9 +88,10 @@ AutoUpdaterDialog::AutoUpdaterDialog(QWidget* parent /* = nullptr */) : QDialog(
|
|||
connect(m_ui.skipThisUpdate, &QPushButton::clicked, this, &AutoUpdaterDialog::skipThisUpdateClicked);
|
||||
connect(m_ui.remindMeLater, &QPushButton::clicked, this, &AutoUpdaterDialog::remindMeLaterClicked);
|
||||
|
||||
m_http = HTTPDownloader::Create(Host::GetHTTPUserAgent());
|
||||
Error error;
|
||||
m_http = HTTPDownloader::Create(Host::GetHTTPUserAgent(), &error);
|
||||
if (!m_http)
|
||||
ERROR_LOG("Failed to create HTTP downloader, auto updater will not be available.");
|
||||
ERROR_LOG("Failed to create HTTP downloader, auto updater will not be available:\n{}", error.GetDescription());
|
||||
}
|
||||
|
||||
AutoUpdaterDialog::~AutoUpdaterDialog() = default;
|
||||
|
@ -277,7 +278,7 @@ void AutoUpdaterDialog::queueUpdateCheck(bool display_message)
|
|||
}
|
||||
|
||||
m_http->CreateRequest(LATEST_TAG_URL, std::bind(&AutoUpdaterDialog::getLatestTagComplete, this, std::placeholders::_1,
|
||||
std::placeholders::_3));
|
||||
std::placeholders::_2, std::placeholders::_4));
|
||||
#else
|
||||
emit updateCheckCompleted();
|
||||
#endif
|
||||
|
@ -294,11 +295,11 @@ void AutoUpdaterDialog::queueGetLatestRelease()
|
|||
|
||||
std::string url = fmt::format(fmt::runtime(LATEST_RELEASE_URL), getCurrentUpdateTag());
|
||||
m_http->CreateRequest(std::move(url), std::bind(&AutoUpdaterDialog::getLatestReleaseComplete, this,
|
||||
std::placeholders::_1, std::placeholders::_3));
|
||||
std::placeholders::_1, std::placeholders::_2, std::placeholders::_4));
|
||||
#endif
|
||||
}
|
||||
|
||||
void AutoUpdaterDialog::getLatestTagComplete(s32 status_code, std::vector<u8> response)
|
||||
void AutoUpdaterDialog::getLatestTagComplete(s32 status_code, const Error& error, std::vector<u8> response)
|
||||
{
|
||||
#ifdef UPDATE_CHECKER_SUPPORTED
|
||||
const std::string selected_tag(getCurrentUpdateTag());
|
||||
|
@ -351,14 +352,14 @@ void AutoUpdaterDialog::getLatestTagComplete(s32 status_code, std::vector<u8> re
|
|||
else
|
||||
{
|
||||
if (m_display_messages)
|
||||
reportError(fmt::format("Failed to download latest tag info: HTTP {}", status_code));
|
||||
reportError(fmt::format("Failed to download latest tag info: {}", error.GetDescription()));
|
||||
}
|
||||
|
||||
emit updateCheckCompleted();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, std::vector<u8> response)
|
||||
void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, const Error& error, std::vector<u8> response)
|
||||
{
|
||||
#ifdef UPDATE_CHECKER_SUPPORTED
|
||||
if (status_code == HTTPDownloader::HTTP_STATUS_OK)
|
||||
|
@ -415,7 +416,7 @@ void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, std::vector<u8
|
|||
}
|
||||
else
|
||||
{
|
||||
reportError(fmt::format("Failed to download latest release info: HTTP {}", status_code));
|
||||
reportError(fmt::format("Failed to download latest release info: {}", error.GetDescription()));
|
||||
}
|
||||
|
||||
emit updateCheckCompleted();
|
||||
|
@ -430,11 +431,11 @@ void AutoUpdaterDialog::queueGetChanges()
|
|||
|
||||
std::string url = fmt::format(fmt::runtime(CHANGES_URL), g_scm_hash_str, getCurrentUpdateTag());
|
||||
m_http->CreateRequest(std::move(url), std::bind(&AutoUpdaterDialog::getChangesComplete, this, std::placeholders::_1,
|
||||
std::placeholders::_3));
|
||||
std::placeholders::_2, std::placeholders::_4));
|
||||
#endif
|
||||
}
|
||||
|
||||
void AutoUpdaterDialog::getChangesComplete(s32 status_code, std::vector<u8> response)
|
||||
void AutoUpdaterDialog::getChangesComplete(s32 status_code, const Error& error, std::vector<u8> response)
|
||||
{
|
||||
#ifdef UPDATE_CHECKER_SUPPORTED
|
||||
if (status_code == HTTPDownloader::HTTP_STATUS_OK)
|
||||
|
@ -503,7 +504,7 @@ void AutoUpdaterDialog::getChangesComplete(s32 status_code, std::vector<u8> resp
|
|||
}
|
||||
else
|
||||
{
|
||||
reportError(fmt::format("Failed to download change list: HTTP {}", status_code));
|
||||
reportError(fmt::format("Failed to download change list: {}", error.GetDescription()));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -527,13 +528,13 @@ void AutoUpdaterDialog::downloadUpdateClicked()
|
|||
|
||||
m_http->CreateRequest(
|
||||
m_download_url.toStdString(),
|
||||
[this, &download_result](s32 status_code, const std::string&, std::vector<u8> response) {
|
||||
[this, &download_result](s32 status_code, const Error& error, const std::string&, std::vector<u8> response) {
|
||||
if (status_code == HTTPDownloader::HTTP_STATUS_CANCELLED)
|
||||
return;
|
||||
|
||||
if (status_code != HTTPDownloader::HTTP_STATUS_OK)
|
||||
{
|
||||
reportError(fmt::format("Download failed: HTTP status code {}", status_code));
|
||||
reportError(fmt::format("Download failed: {}", error.GetDescription()));
|
||||
download_result = false;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -58,11 +58,11 @@ private:
|
|||
bool updateNeeded() const;
|
||||
std::string getCurrentUpdateTag() const;
|
||||
|
||||
void getLatestTagComplete(s32 status_code, std::vector<u8> response);
|
||||
void getLatestReleaseComplete(s32 status_code, std::vector<u8> response);
|
||||
void getLatestTagComplete(s32 status_code, const Error& error, std::vector<u8> response);
|
||||
void getLatestReleaseComplete(s32 status_code, const Error& error, std::vector<u8> response);
|
||||
|
||||
void queueGetChanges();
|
||||
void getChangesComplete(s32 status_code, std::vector<u8> response);
|
||||
void getChangesComplete(s32 status_code, const Error& error, std::vector<u8> response);
|
||||
|
||||
bool processUpdate(const std::vector<u8>& update_data);
|
||||
|
||||
|
|
|
@ -281,7 +281,7 @@
|
|||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>75</height>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
|
@ -446,7 +446,7 @@
|
|||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
|
@ -583,7 +583,7 @@
|
|||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>215</height>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
|
@ -749,7 +749,7 @@
|
|||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>164</height>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
|
@ -1062,7 +1062,7 @@
|
|||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>295</height>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
|
@ -1246,7 +1246,7 @@
|
|||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
|
@ -1400,7 +1400,7 @@
|
|||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>244</height>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
|
|
|
@ -2036,8 +2036,8 @@ void MainWindow::connectSignals()
|
|||
Settings::DEFAULT_LOG_LEVEL, Log::Level::MaxCount);
|
||||
connect(m_ui.menuLogChannels, &QMenu::aboutToShow, this, &MainWindow::onDebugLogChannelsMenuAboutToShow);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDisableAllEnhancements, "Main",
|
||||
"DisableAllEnhancements", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableSafeMode, "Main", "DisableAllEnhancements",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugDumpCPUtoVRAMCopies, "Debug",
|
||||
"DumpCPUToVRAMCopies", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugDumpVRAMtoCPUCopies, "Debug",
|
||||
|
|
|
@ -166,7 +166,7 @@
|
|||
<addaction name="menuCPUExecutionMode"/>
|
||||
<addaction name="menuRenderer"/>
|
||||
<addaction name="menuCropMode"/>
|
||||
<addaction name="actionDisableAllEnhancements"/>
|
||||
<addaction name="actionEnableSafeMode"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuLogLevel"/>
|
||||
<addaction name="menuLogChannels"/>
|
||||
|
@ -634,12 +634,12 @@
|
|||
<string>Dump VRAM to CPU Copies</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDisableAllEnhancements">
|
||||
<action name="actionEnableSafeMode">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Disable All Enhancements</string>
|
||||
<string>Enable Safe Mode</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDumpRAM">
|
||||
|
|
|
@ -261,11 +261,13 @@ std::optional<bool> QtHost::DownloadFile(QWidget* parent, const QString& title,
|
|||
{
|
||||
static constexpr u32 HTTP_POLL_INTERVAL = 10;
|
||||
|
||||
std::unique_ptr<HTTPDownloader> http = HTTPDownloader::Create(Host::GetHTTPUserAgent());
|
||||
Error error;
|
||||
std::unique_ptr<HTTPDownloader> http = HTTPDownloader::Create(Host::GetHTTPUserAgent(), &error);
|
||||
if (!http)
|
||||
{
|
||||
QMessageBox::critical(parent, qApp->translate("QtHost", "Error"),
|
||||
qApp->translate("QtHost", "Failed to create HTTPDownloader."));
|
||||
qApp->translate("QtHost", "Failed to create HTTPDownloader:\n%1")
|
||||
.arg(QString::fromStdString(error.GetDescription())));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -281,14 +283,16 @@ std::optional<bool> QtHost::DownloadFile(QWidget* parent, const QString& title,
|
|||
|
||||
http->CreateRequest(
|
||||
std::move(url),
|
||||
[parent, data, &download_result](s32 status_code, const std::string&, std::vector<u8> hdata) {
|
||||
[parent, data, &download_result](s32 status_code, const Error& error, const std::string&, std::vector<u8> hdata) {
|
||||
if (status_code == HTTPDownloader::HTTP_STATUS_CANCELLED)
|
||||
return;
|
||||
|
||||
if (status_code != HTTPDownloader::HTTP_STATUS_OK)
|
||||
{
|
||||
QMessageBox::critical(parent, qApp->translate("QtHost", "Error"),
|
||||
qApp->translate("QtHost", "Download failed with HTTP status code %1.").arg(status_code));
|
||||
qApp->translate("QtHost", "Download failed with HTTP status code %1:\n%2")
|
||||
.arg(status_code)
|
||||
.arg(QString::fromStdString(error.GetDescription())));
|
||||
download_result = false;
|
||||
return;
|
||||
}
|
||||
|
@ -1913,11 +1917,13 @@ std::string Host::GetClipboardText()
|
|||
{
|
||||
// Hope this doesn't deadlock...
|
||||
std::string ret;
|
||||
QtHost::RunOnUIThread([&ret]() {
|
||||
QClipboard* clipboard = QGuiApplication::clipboard();
|
||||
if (clipboard)
|
||||
ret = clipboard->text().toStdString();
|
||||
}, true);
|
||||
QtHost::RunOnUIThread(
|
||||
[&ret]() {
|
||||
QClipboard* clipboard = QGuiApplication::clipboard();
|
||||
if (clipboard)
|
||||
ret = clipboard->text().toStdString();
|
||||
},
|
||||
true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -151,6 +151,7 @@
|
|||
<file>icons/flags/en@2x.png</file>
|
||||
<file>icons/flags/en.png</file>
|
||||
<file>icons/flags/es-ES@2x.png</file>
|
||||
<file>icons/flags/es.png</file>
|
||||
<file>icons/flags/es-ES.png</file>
|
||||
<file>icons/flags/fr@2x.png</file>
|
||||
<file>icons/flags/fr.png</file>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
|
@ -224,7 +224,6 @@ if(WIN32)
|
|||
dinput_source.cpp
|
||||
dinput_source.h
|
||||
http_downloader_winhttp.cpp
|
||||
http_downloader_winhttp.h
|
||||
platform_misc_win32.cpp
|
||||
win32_raw_input_source.cpp
|
||||
win32_raw_input_source.h
|
||||
|
@ -275,7 +274,6 @@ endif()
|
|||
if(NOT WIN32 AND NOT ANDROID)
|
||||
target_sources(util PRIVATE
|
||||
http_downloader_curl.cpp
|
||||
http_downloader_curl.h
|
||||
)
|
||||
target_link_libraries(util PRIVATE
|
||||
CURL::libcurl
|
||||
|
|
|
@ -109,7 +109,8 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock<std::mutex>& lock)
|
|||
m_pending_http_requests.erase(m_pending_http_requests.begin() + index);
|
||||
lock.unlock();
|
||||
|
||||
req->callback(HTTP_STATUS_TIMEOUT, std::string(), Request::Data());
|
||||
req->error.SetStringFmt("Request timed out after {} seconds.", m_timeout);
|
||||
req->callback(HTTP_STATUS_TIMEOUT, req->error, std::string(), Request::Data());
|
||||
|
||||
CloseRequest(req);
|
||||
|
||||
|
@ -126,7 +127,8 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock<std::mutex>& lock)
|
|||
m_pending_http_requests.erase(m_pending_http_requests.begin() + index);
|
||||
lock.unlock();
|
||||
|
||||
req->callback(HTTP_STATUS_CANCELLED, std::string(), Request::Data());
|
||||
req->error.SetStringView("Request was cancelled.");
|
||||
req->callback(HTTP_STATUS_CANCELLED, req->error, std::string(), Request::Data());
|
||||
|
||||
CloseRequest(req);
|
||||
|
||||
|
@ -159,7 +161,9 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock<std::mutex>& lock)
|
|||
|
||||
// run callback with lock unheld
|
||||
lock.unlock();
|
||||
req->callback(req->status_code, req->content_type, std::move(req->data));
|
||||
if (req->status_code != HTTP_STATUS_OK)
|
||||
req->error.SetStringFmt("Request failed with HTTP status code {}", req->status_code);
|
||||
req->callback(req->status_code, req->error, req->content_type, std::move(req->data));
|
||||
CloseRequest(req);
|
||||
lock.lock();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#include <atomic>
|
||||
|
@ -29,7 +30,8 @@ public:
|
|||
struct Request
|
||||
{
|
||||
using Data = std::vector<u8>;
|
||||
using Callback = std::function<void(s32 status_code, const std::string& content_type, Data data)>;
|
||||
using Callback =
|
||||
std::function<void(s32 status_code, const Error& error, const std::string& content_type, Data data)>;
|
||||
|
||||
enum class Type
|
||||
{
|
||||
|
@ -53,6 +55,7 @@ public:
|
|||
std::string post_data;
|
||||
std::string content_type;
|
||||
Data data;
|
||||
Error error;
|
||||
u64 start_time;
|
||||
s32 status_code = 0;
|
||||
u32 content_length = 0;
|
||||
|
@ -64,7 +67,7 @@ public:
|
|||
HTTPDownloader();
|
||||
virtual ~HTTPDownloader();
|
||||
|
||||
static std::unique_ptr<HTTPDownloader> Create(std::string user_agent = DEFAULT_USER_AGENT);
|
||||
static std::unique_ptr<HTTPDownloader> Create(std::string user_agent = DEFAULT_USER_AGENT, Error* error = nullptr);
|
||||
static std::string GetExtensionForContentType(const std::string& content_type);
|
||||
|
||||
void SetTimeout(float timeout);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "http_downloader_curl.h"
|
||||
#include "http_downloader.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/log.h"
|
||||
|
@ -9,12 +9,41 @@
|
|||
#include "common/timer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <curl/curl.h>
|
||||
#include <functional>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
|
||||
LOG_CHANNEL(HTTPDownloader);
|
||||
|
||||
namespace {
|
||||
class HTTPDownloaderCurl final : public HTTPDownloader
|
||||
{
|
||||
public:
|
||||
HTTPDownloaderCurl();
|
||||
~HTTPDownloaderCurl() override;
|
||||
|
||||
bool Initialize(std::string user_agent, Error* error);
|
||||
|
||||
protected:
|
||||
Request* InternalCreateRequest() override;
|
||||
void InternalPollRequests() override;
|
||||
bool StartRequest(HTTPDownloader::Request* request) override;
|
||||
void CloseRequest(HTTPDownloader::Request* request) override;
|
||||
|
||||
private:
|
||||
struct Request : HTTPDownloader::Request
|
||||
{
|
||||
CURL* handle = nullptr;
|
||||
};
|
||||
|
||||
static size_t WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata);
|
||||
|
||||
CURLM* m_multi_handle = nullptr;
|
||||
std::string m_user_agent;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
HTTPDownloaderCurl::HTTPDownloaderCurl() : HTTPDownloader()
|
||||
{
|
||||
}
|
||||
|
@ -25,11 +54,11 @@ HTTPDownloaderCurl::~HTTPDownloaderCurl()
|
|||
curl_multi_cleanup(m_multi_handle);
|
||||
}
|
||||
|
||||
std::unique_ptr<HTTPDownloader> HTTPDownloader::Create(std::string user_agent)
|
||||
std::unique_ptr<HTTPDownloader> HTTPDownloader::Create(std::string user_agent, Error* error)
|
||||
{
|
||||
std::unique_ptr<HTTPDownloaderCurl> instance(std::make_unique<HTTPDownloaderCurl>());
|
||||
if (!instance->Initialize(std::move(user_agent)))
|
||||
return {};
|
||||
std::unique_ptr<HTTPDownloaderCurl> instance = std::make_unique<HTTPDownloaderCurl>();
|
||||
if (!instance->Initialize(std::move(user_agent), error))
|
||||
instance.reset();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
@ -37,7 +66,7 @@ std::unique_ptr<HTTPDownloader> HTTPDownloader::Create(std::string user_agent)
|
|||
static bool s_curl_initialized = false;
|
||||
static std::once_flag s_curl_initialized_once_flag;
|
||||
|
||||
bool HTTPDownloaderCurl::Initialize(std::string user_agent)
|
||||
bool HTTPDownloaderCurl::Initialize(std::string user_agent, Error* error)
|
||||
{
|
||||
if (!s_curl_initialized)
|
||||
{
|
||||
|
@ -53,7 +82,7 @@ bool HTTPDownloaderCurl::Initialize(std::string user_agent)
|
|||
});
|
||||
if (!s_curl_initialized)
|
||||
{
|
||||
ERROR_LOG("curl_global_init() failed");
|
||||
Error::SetStringView(error, "curl_global_init() failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +90,7 @@ bool HTTPDownloaderCurl::Initialize(std::string user_agent)
|
|||
m_multi_handle = curl_multi_init();
|
||||
if (!m_multi_handle)
|
||||
{
|
||||
ERROR_LOG("curl_multi_init() failed");
|
||||
Error::SetStringView(error, "curl_multi_init() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -153,6 +182,7 @@ void HTTPDownloaderCurl::InternalPollRequests()
|
|||
else
|
||||
{
|
||||
ERROR_LOG("Request for '{}' returned error {}", req->url, static_cast<int>(msg->data.result));
|
||||
req->error.SetStringFmt("Request failed: {}", curl_easy_strerror(msg->data.result));
|
||||
}
|
||||
|
||||
req->state.store(Request::State::Complete, std::memory_order_release);
|
||||
|
@ -187,7 +217,8 @@ bool HTTPDownloaderCurl::StartRequest(HTTPDownloader::Request* request)
|
|||
if (err != CURLM_OK)
|
||||
{
|
||||
ERROR_LOG("curl_multi_add_handle() returned {}", static_cast<int>(err));
|
||||
req->callback(HTTP_STATUS_ERROR, std::string(), req->data);
|
||||
req->error.SetStringFmt("curl_multi_add_handle() failed: {}", curl_multi_strerror(err));
|
||||
req->callback(HTTP_STATUS_ERROR, req->error, std::string(), req->data);
|
||||
curl_easy_cleanup(req->handle);
|
||||
delete req;
|
||||
return false;
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#pragma once
|
||||
#include "http_downloader.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <curl/curl.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
class HTTPDownloaderCurl final : public HTTPDownloader
|
||||
{
|
||||
public:
|
||||
HTTPDownloaderCurl();
|
||||
~HTTPDownloaderCurl() override;
|
||||
|
||||
bool Initialize(std::string user_agent);
|
||||
|
||||
protected:
|
||||
Request* InternalCreateRequest() override;
|
||||
void InternalPollRequests() override;
|
||||
bool StartRequest(HTTPDownloader::Request* request) override;
|
||||
void CloseRequest(HTTPDownloader::Request* request) override;
|
||||
|
||||
private:
|
||||
struct Request : HTTPDownloader::Request
|
||||
{
|
||||
CURL* handle = nullptr;
|
||||
};
|
||||
|
||||
static size_t WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata);
|
||||
|
||||
CURLM* m_multi_handle = nullptr;
|
||||
std::string m_user_agent;
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "http_downloader_winhttp.h"
|
||||
#include "http_downloader.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/log.h"
|
||||
|
@ -10,6 +10,41 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/windows_headers.h"
|
||||
|
||||
#include <winhttp.h>
|
||||
|
||||
namespace {
|
||||
class HTTPDownloaderWinHttp final : public HTTPDownloader
|
||||
{
|
||||
public:
|
||||
HTTPDownloaderWinHttp();
|
||||
~HTTPDownloaderWinHttp() override;
|
||||
|
||||
bool Initialize(std::string user_agent, Error* error);
|
||||
|
||||
protected:
|
||||
Request* InternalCreateRequest() override;
|
||||
void InternalPollRequests() override;
|
||||
bool StartRequest(HTTPDownloader::Request* request) override;
|
||||
void CloseRequest(HTTPDownloader::Request* request) override;
|
||||
|
||||
private:
|
||||
struct Request : HTTPDownloader::Request
|
||||
{
|
||||
std::wstring object_name;
|
||||
HINTERNET hConnection = NULL;
|
||||
HINTERNET hRequest = NULL;
|
||||
u32 io_position = 0;
|
||||
};
|
||||
|
||||
static void CALLBACK HTTPStatusCallback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus,
|
||||
LPVOID lpvStatusInformation, DWORD dwStatusInformationLength);
|
||||
|
||||
HINTERNET m_hSession = NULL;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
LOG_CHANNEL(HTTPDownloader);
|
||||
|
||||
HTTPDownloaderWinHttp::HTTPDownloaderWinHttp() : HTTPDownloader()
|
||||
|
@ -25,16 +60,16 @@ HTTPDownloaderWinHttp::~HTTPDownloaderWinHttp()
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<HTTPDownloader> HTTPDownloader::Create(std::string user_agent)
|
||||
std::unique_ptr<HTTPDownloader> HTTPDownloader::Create(std::string user_agent, Error* error)
|
||||
{
|
||||
std::unique_ptr<HTTPDownloaderWinHttp> instance(std::make_unique<HTTPDownloaderWinHttp>());
|
||||
if (!instance->Initialize(std::move(user_agent)))
|
||||
return {};
|
||||
if (!instance->Initialize(std::move(user_agent), error))
|
||||
instance.reset();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool HTTPDownloaderWinHttp::Initialize(std::string user_agent)
|
||||
bool HTTPDownloaderWinHttp::Initialize(std::string user_agent, Error* error)
|
||||
{
|
||||
static constexpr DWORD dwAccessType = WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY;
|
||||
|
||||
|
@ -42,7 +77,7 @@ bool HTTPDownloaderWinHttp::Initialize(std::string user_agent)
|
|||
WINHTTP_FLAG_ASYNC);
|
||||
if (m_hSession == NULL)
|
||||
{
|
||||
ERROR_LOG("WinHttpOpen() failed: {}", GetLastError());
|
||||
Error::SetWin32(error, "WinHttpOpen() failed: ", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -51,7 +86,7 @@ bool HTTPDownloaderWinHttp::Initialize(std::string user_agent)
|
|||
if (WinHttpSetStatusCallback(m_hSession, HTTPStatusCallback, notification_flags, NULL) ==
|
||||
WINHTTP_INVALID_STATUS_CALLBACK)
|
||||
{
|
||||
ERROR_LOG("WinHttpSetStatusCallback() failed: {}", GetLastError());
|
||||
Error::SetWin32(error, "WinHttpSetStatusCallback() failed: ", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -91,6 +126,7 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
|
|||
const WINHTTP_ASYNC_RESULT* res = reinterpret_cast<const WINHTTP_ASYNC_RESULT*>(lpvStatusInformation);
|
||||
ERROR_LOG("WinHttp async function {} returned error {}", res->dwResult, res->dwError);
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->error.SetStringFmt("WinHttp async function {} returned error {}", res->dwResult, res->dwError);
|
||||
req->state.store(Request::State::Complete);
|
||||
return;
|
||||
}
|
||||
|
@ -99,8 +135,10 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
|
|||
DEV_LOG("SendRequest complete");
|
||||
if (!WinHttpReceiveResponse(hRequest, nullptr))
|
||||
{
|
||||
ERROR_LOG("WinHttpReceiveResponse() failed: {}", GetLastError());
|
||||
const DWORD err = GetLastError();
|
||||
ERROR_LOG("WinHttpReceiveResponse() failed: {}", err);
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->error.SetWin32("WinHttpReceiveResponse() failed: ", err);
|
||||
req->state.store(Request::State::Complete);
|
||||
}
|
||||
|
||||
|
@ -114,8 +152,10 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
|
|||
if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX, &req->status_code, &buffer_size, WINHTTP_NO_HEADER_INDEX))
|
||||
{
|
||||
ERROR_LOG("WinHttpQueryHeaders() for status code failed: {}", GetLastError());
|
||||
const DWORD err = GetLastError();
|
||||
ERROR_LOG("WinHttpQueryHeaders() for status code failed: {}", err);
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->error.SetWin32("WinHttpQueryHeaders() failed: ", err);
|
||||
req->state.store(Request::State::Complete);
|
||||
return;
|
||||
}
|
||||
|
@ -125,8 +165,9 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
|
|||
WINHTTP_HEADER_NAME_BY_INDEX, &req->content_length, &buffer_size,
|
||||
WINHTTP_NO_HEADER_INDEX))
|
||||
{
|
||||
if (GetLastError() != ERROR_WINHTTP_HEADER_NOT_FOUND)
|
||||
WARNING_LOG("WinHttpQueryHeaders() for content length failed: {}", GetLastError());
|
||||
const DWORD err = GetLastError();
|
||||
if (err != ERROR_WINHTTP_HEADER_NOT_FOUND)
|
||||
WARNING_LOG("WinHttpQueryHeaders() for content length failed: {}", err);
|
||||
|
||||
req->content_length = 0;
|
||||
}
|
||||
|
@ -152,8 +193,10 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
|
|||
// start reading
|
||||
if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", GetLastError());
|
||||
const DWORD err = GetLastError();
|
||||
ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", err);
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->error.SetWin32("WinHttpQueryDataAvailable() failed: ", err);
|
||||
req->state.store(Request::State::Complete);
|
||||
}
|
||||
|
||||
|
@ -178,8 +221,10 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
|
|||
if (!WinHttpReadData(hRequest, req->data.data() + req->io_position, bytes_available, nullptr) &&
|
||||
GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
ERROR_LOG("WinHttpReadData() failed: {}", GetLastError());
|
||||
const DWORD err = GetLastError();
|
||||
ERROR_LOG("WinHttpReadData() failed: {}", err);
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->error.SetWin32("WinHttpReadData() failed: ", err);
|
||||
req->state.store(Request::State::Complete);
|
||||
}
|
||||
|
||||
|
@ -196,8 +241,10 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
|
|||
|
||||
if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", GetLastError());
|
||||
const DWORD err = GetLastError();
|
||||
ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", err);
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->error.SetWin32("WinHttpQueryDataAvailable() failed: ", err);
|
||||
req->state.store(Request::State::Complete);
|
||||
}
|
||||
|
||||
|
@ -238,8 +285,10 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request)
|
|||
const std::wstring url_wide(StringUtil::UTF8StringToWideString(req->url));
|
||||
if (!WinHttpCrackUrl(url_wide.c_str(), static_cast<DWORD>(url_wide.size()), 0, &uc))
|
||||
{
|
||||
ERROR_LOG("WinHttpCrackUrl() failed: {}", GetLastError());
|
||||
req->callback(HTTP_STATUS_ERROR, std::string(), req->data);
|
||||
const DWORD err = GetLastError();
|
||||
ERROR_LOG("WinHttpCrackUrl() failed: {}", err);
|
||||
req->error.SetWin32("WinHttpCrackUrl() failed: ", err);
|
||||
req->callback(HTTP_STATUS_ERROR, req->error, std::string(), req->data);
|
||||
delete req;
|
||||
return false;
|
||||
}
|
||||
|
@ -250,8 +299,10 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request)
|
|||
req->hConnection = WinHttpConnect(m_hSession, host_name.c_str(), uc.nPort, 0);
|
||||
if (!req->hConnection)
|
||||
{
|
||||
ERROR_LOG("Failed to start HTTP request for '{}': {}", req->url, GetLastError());
|
||||
req->callback(HTTP_STATUS_ERROR, std::string(), req->data);
|
||||
const DWORD err = GetLastError();
|
||||
ERROR_LOG("Failed to start HTTP request for '{}': {}", req->url, err);
|
||||
req->error.SetWin32("WinHttpConnect() failed: ", err);
|
||||
req->callback(HTTP_STATUS_ERROR, req->error, std::string(), req->data);
|
||||
delete req;
|
||||
return false;
|
||||
}
|
||||
|
@ -262,7 +313,9 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request)
|
|||
req->object_name.c_str(), NULL, NULL, NULL, request_flags);
|
||||
if (!req->hRequest)
|
||||
{
|
||||
ERROR_LOG("WinHttpOpenRequest() failed: {}", GetLastError());
|
||||
const DWORD err = GetLastError();
|
||||
ERROR_LOG("WinHttpOpenRequest() failed: {}", err);
|
||||
req->error.SetWin32("WinHttpSendRequest() failed: ", err);
|
||||
WinHttpCloseHandle(req->hConnection);
|
||||
return false;
|
||||
}
|
||||
|
@ -283,8 +336,10 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request)
|
|||
|
||||
if (!result && GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
ERROR_LOG("WinHttpSendRequest() failed: {}", GetLastError());
|
||||
const DWORD err = GetLastError();
|
||||
ERROR_LOG("WinHttpSendRequest() failed: {}", err);
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->error.SetWin32("WinHttpSendRequest() failed: ", err);
|
||||
req->state.store(Request::State::Complete);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#pragma once
|
||||
#include "http_downloader.h"
|
||||
|
||||
#include "common/windows_headers.h"
|
||||
|
||||
#include <winhttp.h>
|
||||
|
||||
class HTTPDownloaderWinHttp final : public HTTPDownloader
|
||||
{
|
||||
public:
|
||||
HTTPDownloaderWinHttp();
|
||||
~HTTPDownloaderWinHttp() override;
|
||||
|
||||
bool Initialize(std::string user_agent);
|
||||
|
||||
protected:
|
||||
Request* InternalCreateRequest() override;
|
||||
void InternalPollRequests() override;
|
||||
bool StartRequest(HTTPDownloader::Request* request) override;
|
||||
void CloseRequest(HTTPDownloader::Request* request) override;
|
||||
|
||||
private:
|
||||
struct Request : HTTPDownloader::Request
|
||||
{
|
||||
std::wstring object_name;
|
||||
HINTERNET hConnection = NULL;
|
||||
HINTERNET hRequest = NULL;
|
||||
u32 io_position = 0;
|
||||
};
|
||||
|
||||
static void CALLBACK HTTPStatusCallback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus,
|
||||
LPVOID lpvStatusInformation, DWORD dwStatusInformationLength);
|
||||
|
||||
HINTERNET m_hSession = NULL;
|
||||
};
|
|
@ -28,10 +28,6 @@
|
|||
<ClInclude Include="gpu_texture.h" />
|
||||
<ClInclude Include="host.h" />
|
||||
<ClInclude Include="http_downloader.h" />
|
||||
<ClInclude Include="http_downloader_curl.h">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="http_downloader_winhttp.h" />
|
||||
<ClInclude Include="imgui_fullscreen.h" />
|
||||
<ClInclude Include="imgui_manager.h" />
|
||||
<ClInclude Include="ini_settings_interface.h" />
|
||||
|
|
|
@ -57,8 +57,6 @@
|
|||
<ClInclude Include="host.h" />
|
||||
<ClInclude Include="postprocessing_shader_fx.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="http_downloader_curl.h" />
|
||||
<ClInclude Include="http_downloader_winhttp.h" />
|
||||
<ClInclude Include="http_downloader.h" />
|
||||
<ClInclude Include="gpu_framebuffer_manager.h" />
|
||||
<ClInclude Include="imgui_animated.h" />
|
||||
|
|
Loading…
Reference in New Issue