diff --git a/README.md b/README.md
index 7e75d4aad..3ed22abc3 100644
--- a/README.md
+++ b/README.md
@@ -104,13 +104,9 @@ You will need a device with armv7 (32-bit ARM), AArch64 (64-bit ARM), or x86_64
Google Play is the preferred distribution mechanism and will result in smaller download sizes: https://play.google.com/store/apps/details?id=com.github.stenzek.duckstation
-**No support is provided for the Android app**, it is free and your expectations should be in line with that. Please **do not** email me about issues about it, they will be ignored. This repository should also not be used to raise issues about the app, as it does not contain the app code, only the desktop versions.
+**No support is provided for the Android app**, it is free and your expectations should be in line with that. Please **do not** email me about issues about it, they will be ignored.
-If you must use an APK, download links are:
-
-Download link: https://www.duckstation.org/android/duckstation-android.apk
-
-Changelog link: https://www.duckstation.org/android/changelog.txt
+If you must use an APK, download links are listed in https://www.duckstation.org/android/
To use:
1. Install and run the app for the first time.
@@ -215,22 +211,6 @@ Hotkeys:
- **Tab:** Temporarily disable speed limiter
- **Space:** Pause/resume emulation
-## Screenshots
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
## Disclaimers
Icon by icons8: https://icons8.com/icon/74847/platforms.undefined.short-title
diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp
index bfe67fb05..2e1288214 100644
--- a/src/core/analog_controller.cpp
+++ b/src/core/analog_controller.cpp
@@ -53,7 +53,7 @@ void AnalogController::Reset()
if (m_force_analog_on_reset)
{
- if (g_settings.controller_disable_analog_mode_forcing || System::IsRunningBIOS())
+ if (g_settings.controller_disable_analog_mode_forcing || System::IsRunningUnknownGame())
{
Host::AddIconOSDMessage(
fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD,
@@ -835,8 +835,7 @@ static const char* s_invert_settings[] = {TRANSLATABLE("AnalogController", "Not
static const SettingInfo s_settings[] = {
{SettingInfo::Type::Boolean, "ForceAnalogOnReset", TRANSLATABLE("AnalogController", "Force Analog Mode on Reset"),
- TRANSLATABLE("AnalogController", "Forces the controller to analog mode when the console is reset/powered on. May "
- "cause issues with games, so it is recommended to leave this option off."),
+ TRANSLATABLE("AnalogController", "Forces the controller to analog mode when the console is reset/powered on."),
"true"},
{SettingInfo::Type::Boolean, "AnalogDPadInDigitalMode",
TRANSLATABLE("AnalogController", "Use Analog Sticks for D-Pad in Digital Mode"),
diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp
index 74b0aa48a..aa9728b17 100644
--- a/src/core/netplay.cpp
+++ b/src/core/netplay.cpp
@@ -46,6 +46,7 @@ enum class ControlMessage : u32
ConnectResponse,
SynchronizeSession,
SynchronizeComplete,
+ ChatMessage,
};
#pragma pack(push, 1)
@@ -94,6 +95,15 @@ struct ControlSynchronizeCompleteMessage
static ControlMessage MessageType() { return ControlMessage::SynchronizeComplete; }
};
+
+struct ControlChatMessage
+{
+ ControlMessageHeader header;
+
+ u32 chat_message_size;
+
+ static ControlMessage MessageType() { return ControlMessage::ChatMessage; }
+};
#pragma pack(pop)
using SaveStateBuffer = std::unique_ptr;
@@ -141,6 +151,7 @@ static void HandleControlMessage(s32 player_id, const ENetPacket* pkt);
static void HandleConnectResponseMessage(s32 player_id, const ENetPacket* pkt);
static void HandleSynchronizeSessionMessage(s32 player_id, const ENetPacket* pkt);
static void HandleSynchronizeCompleteMessage(s32 player_id, const ENetPacket* pkt);
+static void HandleControlChatMessage(s32 player_id, const ENetPacket* pkt);
// l = local, r = remote
static bool CreateGGPOSession();
@@ -170,7 +181,6 @@ static void Throttle();
// Desync Detection
static void GenerateChecksumForFrame(int* checksum, int frame, unsigned char* buffer, int buffer_size);
-static void GenerateDesyncReport(s32 desync_frame);
//////////////////////////////////////////////////////////////////////////
// Variables
@@ -252,7 +262,6 @@ static bool SendControlPacket(s32 player_id, const PacketWrapper& pkt)
DebugAssert(player_id >= 0 && player_id < MAX_PLAYERS && s_peers[player_id].peer);
return SendControlPacket(s_peers[player_id].peer, pkt);
}
-
} // namespace Netplay
// Netplay Impl
@@ -505,6 +514,10 @@ void Netplay::PollEnet(Common::Timer::Value until_time)
const u32 enet_timeout = (current_time >= until_time) ?
0 :
static_cast(Common::Timer::ConvertValueToMilliseconds(until_time - current_time));
+
+ // make sure s_enet_host exists
+ Assert(s_enet_host);
+
const int res = enet_host_service(s_enet_host, &event, enet_timeout);
if (res > 0)
{
@@ -514,7 +527,6 @@ void Netplay::PollEnet(Common::Timer::Value until_time)
current_time = Common::Timer::GetCurrentValue();
continue;
}
-
// exit once we're nonblocking
current_time = Common::Timer::GetCurrentValue();
if (enet_timeout == 0 || current_time >= until_time)
@@ -693,6 +705,10 @@ void Netplay::HandleControlMessage(s32 player_id, const ENetPacket* pkt)
HandleSynchronizeCompleteMessage(player_id, pkt);
break;
+ case ControlMessage::ChatMessage:
+ HandleControlChatMessage(player_id, pkt);
+ break;
+
default:
Log_ErrorPrintf("Unhandled control packet %u from player %d of size %zu", hdr->type, player_id, pkt->dataLength);
break;
@@ -1003,6 +1019,22 @@ void Netplay::HandleSynchronizeCompleteMessage(s32 player_id, const ENetPacket*
CheckForCompleteResynchronize();
}
+void Netplay::HandleControlChatMessage(s32 player_id, const ENetPacket* pkt)
+{
+ const ControlChatMessage* msg = reinterpret_cast(pkt->data);
+ if (pkt->dataLength < sizeof(ControlChatMessage) ||
+ pkt->dataLength < (sizeof(ControlChatMessage) + msg->chat_message_size))
+ {
+ // invalid chat message. ignore.
+ return;
+ }
+
+ std::string message(pkt->data + sizeof(ControlChatMessage),
+ pkt->data + sizeof(ControlChatMessage) + msg->chat_message_size);
+
+ Host::OnNetplayMessage(fmt::format("Player {}: {}", PlayerIdToGGPOHandle(player_id), message));
+}
+
void Netplay::CheckForCompleteResynchronize()
{
if (s_synchronized_players == s_num_players)
@@ -1065,6 +1097,9 @@ void Netplay::UpdateThrottlePeriod()
void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval)
{
+ // only activate timesync if its worth correcting.
+ if (std::abs(frame_delta) < 1.0f)
+ return;
// Distribute the frame difference over the next N * 0.75 frames.
// only part of the interval time is used since we want to come back to normal speed.
// otherwise we will keep spiraling into unplayable gameplay.
@@ -1140,37 +1175,6 @@ void Netplay::GenerateChecksumForFrame(int* checksum, int frame, unsigned char*
// Log_VerbosePrintf("Netplay Checksum: f:%d wf:%d c:%u", frame, frame % num_group_of_pages, *checksum);
}
-void Netplay::GenerateDesyncReport(s32 desync_frame)
-{
- std::string path = "\\netplaylogs\\desync_frame_" + std::to_string(desync_frame) + "_p" +
- std::to_string(s_local_handle) + "_" + System::GetRunningSerial() + "_.txt";
- std::string filename = EmuFolders::Dumps + path;
-
- std::unique_ptr stream =
- ByteStream::OpenFile(filename.c_str(), BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_TRUNCATE |
- BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED);
- if (!stream)
- {
- Log_VerbosePrint("desync log creation failed to create stream");
- return;
- }
-
- if (!ByteStream::WriteBinaryToStream(stream.get(),
- s_save_buffer_pool.back().get()->state_stream.get()->GetMemoryPointer(),
- s_save_buffer_pool.back().get()->state_stream.get()->GetMemorySize()))
- {
- Log_VerbosePrint("desync log creation failed to write the stream");
- stream->Discard();
- return;
- }
- /* stream->Write(s_save_buffer_pool.back().get()->state_stream.get()->GetMemoryPointer(),
- s_save_buffer_pool.back().get()->state_stream.get()->GetMemorySize());*/
-
- stream->Commit();
-
- Log_VerbosePrintf("desync log created for frame %d", desync_frame);
-}
-
void Netplay::AdvanceFrame()
{
ggpo_advance_frame(s_ggpo, 0);
@@ -1178,19 +1182,13 @@ void Netplay::AdvanceFrame()
void Netplay::RunFrame()
{
+ PollEnet(0);
+
+ if (!s_ggpo)
+ return;
// housekeeping
- // TODO: get rid of double polling
- PollEnet(0);
- if (!s_ggpo)
- return;
-
ggpo_network_idle(s_ggpo);
- PollEnet(0);
- if (!s_ggpo)
- return;
-
ggpo_idle(s_ggpo);
-
// run game
auto result = GGPO_OK;
int disconnect_flags = 0;
@@ -1238,7 +1236,36 @@ Netplay::Input Netplay::ReadLocalInput()
return inp;
}
-void Netplay::SendMsg(const char* msg) {}
+void Netplay::SendMsg(std::string msg)
+{
+ ControlChatMessage header{};
+ const size_t msg_size = msg.size();
+
+ header.header.type = ControlMessage::ChatMessage;
+ header.header.size = sizeof(ControlChatMessage) + msg_size;
+ header.chat_message_size = msg_size;
+
+ ENetPacket* pkt = enet_packet_create(nullptr, sizeof(header) + msg_size, ENET_PACKET_FLAG_RELIABLE);
+ std::memcpy(pkt->data, &header, sizeof(header));
+ std::memcpy(pkt->data + sizeof(header), msg.c_str(), msg_size);
+
+ for (s32 i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (!s_peers[i].peer)
+ continue;
+
+ const int err = enet_peer_send(s_peers[i].peer, ENET_CHANNEL_CONTROL, pkt);
+ if (err != 0)
+ {
+ // failed to send netplay message? just clean it up.
+ Log_ErrorPrint("Failed to send netplay message");
+ enet_packet_destroy(pkt);
+ }
+ }
+
+ // add own netplay message locally to netplay messages
+ Host::OnNetplayMessage(fmt::format("Player {}: {}", s_local_handle, msg));
+}
GGPOErrorCode Netplay::SyncInput(Netplay::Input inputs[2], int* disconnect_flags)
{
@@ -1288,9 +1315,10 @@ void Netplay::StartNetplaySession(s32 local_handle, u16 local_port, std::string&
Log_ErrorPrint("Failed to Create Netplay Session!");
System::ShutdownSystem(false);
}
- else
+ else if (IsHost())
{
- // Load savestate if available
+ // Load savestate if available and only when you are the host.
+ // the other peers will get state from the host
std::string save = EmuFolders::SaveStates + "/netplay/" + System::GetRunningSerial() + ".sav";
System::LoadState(save.c_str());
}
@@ -1377,8 +1405,7 @@ bool Netplay::NpAdvFrameCb(void* ctx, int flags)
bool Netplay::NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* checksum, int frame)
{
SaveStateBuffer our_buffer;
- // min size is 2 because otherwise the desync logger doesnt have enough time to dump the state.
- if (s_save_buffer_pool.size() < 2)
+ if (s_save_buffer_pool.empty())
{
our_buffer = std::make_unique();
}
@@ -1465,7 +1492,6 @@ bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev)
CurrentFrame(), ev->u.desync.nFrameOfDesync,
CurrentFrame() - ev->u.desync.nFrameOfDesync, ev->u.desync.ourCheckSum,
ev->u.desync.remoteChecksum));
- GenerateDesyncReport(ev->u.desync.nFrameOfDesync);
break;
default:
Host::OnNetplayMessage(fmt::format("Netplay Event Code: {}", static_cast(ev->code)));
diff --git a/src/core/netplay.h b/src/core/netplay.h
index 7456a748b..985d5cd41 100644
--- a/src/core/netplay.h
+++ b/src/core/netplay.h
@@ -8,7 +8,7 @@ namespace Netplay {
enum : s32
{
// Maximum number of emulated controllers.
- MAX_PLAYERS = 2,
+ MAX_PLAYERS = 2,
// Maximum netplay prediction frames
MAX_ROLLBACK_FRAMES = 8,
};
@@ -36,7 +36,7 @@ void ExecuteNetplay();
void CollectInput(u32 slot, u32 bind, float value);
-void SendMsg(const char* msg);
+void SendMsg(std::string msg);
s32 GetPing();
u32 GetMaxPrediction();
diff --git a/src/core/system.cpp b/src/core/system.cpp
index 3b1dd2e7c..a32ac95d2 100644
--- a/src/core/system.cpp
+++ b/src/core/system.cpp
@@ -138,7 +138,7 @@ static BIOS::Hash s_bios_hash = {};
static std::string s_running_game_path;
static std::string s_running_game_serial;
static std::string s_running_game_title;
-static bool s_running_bios;
+static bool s_running_unknown_game;
static float s_throttle_frequency = 60.0f;
static float s_target_speed = 1.0f;
@@ -320,9 +320,9 @@ const std::string& System::GetRunningTitle()
return s_running_game_title;
}
-bool System::IsRunningBIOS()
+bool System::IsRunningUnknownGame()
{
- return s_running_bios;
+ return s_running_unknown_game;
}
const BIOS::ImageInfo* System::GetBIOSImageInfo()
@@ -958,9 +958,6 @@ void System::ResetSystem()
ResetPerformanceCounters();
ResetThrottler();
Host::AddOSDMessage(Host::TranslateStdString("OSDMessage", "System reset."));
-
- // need to clear this here, because of eject disc -> reset.
- s_running_bios = !s_running_game_path.empty();
}
void System::PauseSystem(bool paused)
@@ -1239,9 +1236,6 @@ bool System::BootSystem(SystemBootParameters parameters)
return false;
}
- // Allow controller analog mode for EXEs and PSFs.
- s_running_bios = s_running_game_path.empty() && exe_boot.empty() && psf_boot.empty();
-
UpdateControllers();
UpdateMemoryCardTypes();
UpdateMultitaps();
@@ -1510,7 +1504,7 @@ void System::ClearRunningGame()
s_running_game_serial.clear();
s_running_game_path.clear();
s_running_game_title.clear();
- s_running_bios = false;
+ s_running_unknown_game = false;
s_cheat_list.reset();
s_state = State::Shutdown;
@@ -3053,6 +3047,7 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting)
s_running_game_path.clear();
s_running_game_serial.clear();
s_running_game_title.clear();
+ s_running_unknown_game = true;
if (path && std::strlen(path) > 0)
{
@@ -3070,6 +3065,7 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting)
{
s_running_game_serial = entry->serial;
s_running_game_title = entry->title;
+ s_running_unknown_game = false;
}
else
{
diff --git a/src/core/system.h b/src/core/system.h
index 6233b1dda..0cd4206ca 100644
--- a/src/core/system.h
+++ b/src/core/system.h
@@ -184,7 +184,7 @@ const std::string& GetRunningPath();
const std::string& GetRunningSerial();
const std::string& GetRunningTitle();
-bool IsRunningBIOS();
+bool IsRunningUnknownGame();
const BIOS::ImageInfo* GetBIOSImageInfo();
const BIOS::Hash& GetBIOSHash();
diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp
index 5d7d61cf4..5ae89e482 100644
--- a/src/duckstation-qt/mainwindow.cpp
+++ b/src/duckstation-qt/mainwindow.cpp
@@ -1568,10 +1568,6 @@ void MainWindow::setupAdditionalUi()
m_status_vps_widget->setFixedSize(120, 16);
m_status_vps_widget->hide();
- m_status_ping_widget = new QLabel(m_ui.statusBar);
- m_status_ping_widget->setFixedSize(110, 16);
- m_status_ping_widget->hide();
-
m_settings_toolbar_menu = new QMenu(m_ui.toolBar);
m_settings_toolbar_menu->addAction(m_ui.actionSettings);
m_settings_toolbar_menu->addAction(m_ui.actionViewGameProperties);
@@ -1777,7 +1773,6 @@ void MainWindow::updateStatusBarWidgetVisibility()
Update(m_status_resolution_widget, s_system_valid && !s_system_paused, 0);
Update(m_status_fps_widget, s_system_valid && !s_system_paused, 0);
Update(m_status_vps_widget, s_system_valid && !s_system_paused, 0);
- Update(m_status_ping_widget, s_system_valid && !s_system_paused && m_netplay_window != nullptr, 0);
}
void MainWindow::updateWindowTitle()
diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h
index 6aa3ecc0c..20ce67b66 100644
--- a/src/duckstation-qt/mainwindow.h
+++ b/src/duckstation-qt/mainwindow.h
@@ -90,7 +90,6 @@ public:
ALWAYS_INLINE QLabel* getStatusResolutionWidget() const { return m_status_resolution_widget; }
ALWAYS_INLINE QLabel* getStatusFPSWidget() const { return m_status_fps_widget; }
ALWAYS_INLINE QLabel* getStatusVPSWidget() const { return m_status_vps_widget; }
- ALWAYS_INLINE QLabel* getStatusPingWidget() const { return m_status_ping_widget; }
public Q_SLOTS:
/// Updates debug menu visibility (hides if disabled).
@@ -266,7 +265,6 @@ private:
QLabel* m_status_renderer_widget = nullptr;
QLabel* m_status_fps_widget = nullptr;
QLabel* m_status_vps_widget = nullptr;
- QLabel* m_status_ping_widget = nullptr;
QLabel* m_status_resolution_widget = nullptr;
QMenu* m_settings_toolbar_menu = nullptr;
diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp
index 9570a48a9..c68aea10b 100644
--- a/src/duckstation-qt/qthost.cpp
+++ b/src/duckstation-qt/qthost.cpp
@@ -1079,12 +1079,6 @@ void EmuThread::startNetplaySession(int local_handle, quint16 local_port, const
Q_ARG(quint16, remote_port), Q_ARG(int, input_delay), Q_ARG(const QString&, game_path));
return;
}
- // disable block linking and disable rewind and runahead during a netplay session
- g_settings.cpu_recompiler_block_linking = false;
- g_settings.rewind_enable = false;
- g_settings.runahead_frames = 0;
-
- Log_WarningPrintf("Disabling block linking, runahead and rewind due to rollback.");
auto remAddr = remote_addr.trimmed().toStdString();
auto gamePath = game_path.trimmed().toStdString();
@@ -1101,7 +1095,8 @@ void EmuThread::sendNetplayMessage(const QString& message)
QMetaObject::invokeMethod(this, "sendNetplayMessage", Qt::QueuedConnection, Q_ARG(const QString&, message));
return;
}
- Netplay::SendMsg(message.toStdString().c_str());
+ // TODO REDO NETPLAY UI
+ // Netplay::SendMsg(message.toStdString().c_str());
}
void EmuThread::stopNetplaySession()
@@ -1714,14 +1709,6 @@ void EmuThread::updatePerformanceCounters()
m_last_speed = speed;
m_last_video_fps = vfps;
}
-
- const s32 ping = Netplay::GetPing();
- if (m_last_ping != ping)
- {
- QMetaObject::invokeMethod(g_main_window->getStatusPingWidget(), "setText", Qt::QueuedConnection,
- Q_ARG(const QString&, tr("Netplay Ping: %1 ").arg(ping, 0, 'f', 0)));
- m_last_ping = ping;
- }
}
void EmuThread::resetPerformanceCounters()
diff --git a/src/frontend-common/imgui_netplay.cpp b/src/frontend-common/imgui_netplay.cpp
index 8560ee2ab..09a4d7ce2 100644
--- a/src/frontend-common/imgui_netplay.cpp
+++ b/src/frontend-common/imgui_netplay.cpp
@@ -175,8 +175,10 @@ void ImGuiManager::DrawNetplayChatDialog()
const bool close_chat =
send_message || (s_netplay_chat_message.empty() && (ImGui::IsKeyPressed(ImGuiKey_Backspace)) ||
ImGui::IsKeyPressed(ImGuiKey_Escape));
+
+ // sending netplay message
if (send_message && !s_netplay_chat_message.empty())
- Netplay::SendMsg(s_netplay_chat_message.c_str());
+ Netplay::SendMsg(s_netplay_chat_message);
const ImGuiIO& io = ImGui::GetIO();
const ImGuiStyle& style = ImGui::GetStyle();