diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt
index 57a9d11337..5756952061 100644
--- a/Source/Core/Common/CMakeLists.txt
+++ b/Source/Core/Common/CMakeLists.txt
@@ -30,6 +30,7 @@ set(SRCS Analytics.cpp
Version.cpp
x64ABI.cpp
x64Emitter.cpp
+ MD5.cpp
Crypto/bn.cpp
Crypto/ec.cpp
Logging/LogManager.cpp)
diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj
index 20edab8a65..07daa25fb8 100644
--- a/Source/Core/Common/Common.vcxproj
+++ b/Source/Core/Common/Common.vcxproj
@@ -114,6 +114,7 @@
+
@@ -158,6 +159,7 @@
+
diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h
index 916e711764..5c1cac999b 100644
--- a/Source/Core/Common/CommonPaths.h
+++ b/Source/Core/Common/CommonPaths.h
@@ -118,6 +118,8 @@
#define WII_STATE "state.dat"
+#define WII_SDCARD "sd.raw"
+
#define WII_SETTING "setting.txt"
#define GECKO_CODE_HANDLER "codehandler.bin"
diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp
index 2405a76809..dd28aad476 100644
--- a/Source/Core/Common/FileUtil.cpp
+++ b/Source/Core/Common/FileUtil.cpp
@@ -793,6 +793,7 @@ static void RebuildUserDirectories(unsigned int dir_index)
s_user_paths[F_ARAMDUMP_IDX] = s_user_paths[D_DUMP_IDX] + ARAM_DUMP;
s_user_paths[F_FAKEVMEMDUMP_IDX] = s_user_paths[D_DUMP_IDX] + FAKEVMEM_DUMP;
s_user_paths[F_GCSRAM_IDX] = s_user_paths[D_GCUSER_IDX] + GC_SRAM;
+ s_user_paths[F_WIISDCARD_IDX] = s_user_paths[D_WIIROOT_IDX] + DIR_SEP WII_SDCARD;
s_user_paths[D_MEMORYWATCHER_IDX] = s_user_paths[D_USER_IDX] + MEMORYWATCHER_DIR DIR_SEP;
s_user_paths[F_MEMORYWATCHERLOCATIONS_IDX] =
diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h
index b1168561c4..7beb4fca2e 100644
--- a/Source/Core/Common/FileUtil.h
+++ b/Source/Core/Common/FileUtil.h
@@ -55,6 +55,7 @@ enum
F_GCSRAM_IDX,
F_MEMORYWATCHERLOCATIONS_IDX,
F_MEMORYWATCHERSOCKET_IDX,
+ F_WIISDCARD_IDX,
NUM_PATH_INDICES
};
diff --git a/Source/Core/Common/MD5.cpp b/Source/Core/Common/MD5.cpp
new file mode 100644
index 0000000000..4746dbdebc
--- /dev/null
+++ b/Source/Core/Common/MD5.cpp
@@ -0,0 +1,52 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include
+#include
+#include
+#include
+
+#include "Common/MD5.h"
+#include "Common/StringUtil.h"
+#include "DiscIO/Blob.h"
+
+namespace MD5
+{
+std::string MD5Sum(const std::string& file_path, std::function report_progress)
+{
+ std::string output_string;
+ std::vector data(8 * 1024 * 1024);
+ u64 read_offset = 0;
+ mbedtls_md5_context ctx;
+
+ std::unique_ptr file(DiscIO::CreateBlobReader(file_path));
+ u64 game_size = file->GetDataSize();
+
+ mbedtls_md5_starts(&ctx);
+
+ while (read_offset < game_size)
+ {
+ size_t read_size = std::min(static_cast(data.size()), game_size - read_offset);
+ if (!file->Read(read_offset, read_size, data.data()))
+ return output_string;
+
+ mbedtls_md5_update(&ctx, data.data(), read_size);
+ read_offset += read_size;
+
+ int progress =
+ static_cast(static_cast(read_offset) / static_cast(game_size) * 100);
+ if (!report_progress(progress))
+ return output_string;
+ }
+
+ std::array output;
+ mbedtls_md5_finish(&ctx, output.data());
+
+ // Convert to hex
+ for (u8 n : output)
+ output_string += StringFromFormat("%02x", n);
+
+ return output_string;
+}
+}
\ No newline at end of file
diff --git a/Source/Core/Common/MD5.h b/Source/Core/Common/MD5.h
new file mode 100644
index 0000000000..3d23783ec2
--- /dev/null
+++ b/Source/Core/Common/MD5.h
@@ -0,0 +1,13 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include
+
+namespace MD5
+{
+std::string MD5Sum(const std::string& file_name, std::function progress);
+}
\ No newline at end of file
diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_sdio_slot0.cpp b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_sdio_slot0.cpp
index 1f22d6c722..e1d88b0db5 100644
--- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_sdio_slot0.cpp
+++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_sdio_slot0.cpp
@@ -62,7 +62,7 @@ void CWII_IPC_HLE_Device_sdio_slot0::EventNotify()
void CWII_IPC_HLE_Device_sdio_slot0::OpenInternal()
{
- const std::string filename = File::GetUserPath(D_WIIROOT_IDX) + "/sd.raw";
+ const std::string filename = File::GetUserPath(F_WIISDCARD_IDX);
m_Card.Open(filename, "r+b");
if (!m_Card)
{
diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp
index 586ca0494c..70d6b7ba14 100644
--- a/Source/Core/Core/NetPlayClient.cpp
+++ b/Source/Core/Core/NetPlayClient.cpp
@@ -4,10 +4,15 @@
#include "Core/NetPlayClient.h"
#include
+#include
+#include
#include
+#include
#include "Common/Common.h"
+#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/ENetUtil.h"
+#include "Common/MD5.h"
#include "Common/MsgHandler.h"
#include "Common/Timer.h"
#include "Core/ConfigManager.h"
@@ -19,6 +24,8 @@
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "Core/IPC_HLE/WII_IPC_HLE_Device_usb.h"
#include "Core/Movie.h"
+#include "Core/Movie.h"
+#include "Core/NetPlayClient.h"
#include "InputCommon/GCAdapter.h"
static std::mutex crit_netplay_client;
@@ -500,6 +507,55 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
}
break;
+ case NP_MSG_COMPUTE_MD5:
+ {
+ std::string file_identifier;
+ packet >> file_identifier;
+
+ ComputeMD5(file_identifier);
+ }
+ break;
+
+ case NP_MSG_MD5_PROGRESS:
+ {
+ PlayerId pid;
+ int progress;
+ packet >> pid;
+ packet >> progress;
+
+ m_dialog->SetMD5Progress(pid, progress);
+ }
+ break;
+
+ case NP_MSG_MD5_RESULT:
+ {
+ PlayerId pid;
+ std::string result;
+ packet >> pid;
+ packet >> result;
+
+ m_dialog->SetMD5Result(pid, result);
+ }
+ break;
+
+ case NP_MSG_MD5_ERROR:
+ {
+ PlayerId pid;
+ std::string error;
+ packet >> pid;
+ packet >> error;
+
+ m_dialog->SetMD5Result(pid, error);
+ }
+ break;
+
+ case NP_MSG_MD5_ABORT:
+ {
+ m_should_compute_MD5 = false;
+ m_dialog->AbortMD5();
+ }
+ break;
+
default:
PanicAlertT("Unknown message received with id : %d", mid);
break;
@@ -1142,6 +1198,47 @@ bool NetPlayClient::DoAllPlayersHaveGame()
[](auto entry) { return entry.second.game_status == PlayerGameStatus::Ok; });
}
+void NetPlayClient::ComputeMD5(const std::string& file_identifier)
+{
+ if (m_should_compute_MD5)
+ return;
+
+ m_dialog->ShowMD5Dialog(file_identifier);
+ m_should_compute_MD5 = true;
+
+ std::string file;
+ if (file_identifier == WII_SDCARD)
+ file = File::GetUserPath(F_WIISDCARD_IDX);
+ else
+ file = m_dialog->FindGame(file_identifier);
+
+ if (file.empty() || !File::Exists(file))
+ {
+ sf::Packet spac;
+ spac << static_cast(NP_MSG_MD5_ERROR);
+ spac << "file not found";
+ Send(spac);
+ return;
+ }
+
+ m_MD5_thread = std::thread([this, file]() {
+ std::string sum = MD5::MD5Sum(file, [&](int progress) {
+ sf::Packet spac;
+ spac << static_cast(NP_MSG_MD5_PROGRESS);
+ spac << progress;
+ Send(spac);
+
+ return m_should_compute_MD5;
+ });
+
+ sf::Packet spac;
+ spac << static_cast(NP_MSG_MD5_RESULT);
+ spac << sum;
+ Send(spac);
+ });
+ m_MD5_thread.detach();
+}
+
// stuff hacked into dolphin
// called from ---CPU--- thread
diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h
index 907df2e89f..196066d457 100644
--- a/Source/Core/Core/NetPlayClient.h
+++ b/Source/Core/Core/NetPlayClient.h
@@ -34,6 +34,10 @@ public:
virtual void OnMsgStopGame() = 0;
virtual bool IsRecording() = 0;
virtual std::string FindGame(const std::string& game) = 0;
+ virtual void ShowMD5Dialog(const std::string& file_identifier) = 0;
+ virtual void SetMD5Progress(int pid, int progress) = 0;
+ virtual void SetMD5Result(int pid, const std::string& result) = 0;
+ virtual void AbortMD5() = 0;
};
enum class PlayerGameStatus
@@ -152,6 +156,7 @@ private:
void Send(sf::Packet& packet);
void Disconnect();
bool Connect();
+ void ComputeMD5(const std::string& file_identifier);
bool m_is_connected = false;
ConnectionState m_connection_state = ConnectionState::Failure;
@@ -162,6 +167,8 @@ private:
std::string m_player_name;
bool m_connecting = false;
TraversalClient* m_traversal_client = nullptr;
+ std::thread m_MD5_thread;
+ bool m_should_compute_MD5 = false;
u32 m_timebase_frame = 0;
};
diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h
index 1454fb3678..ffaf47a2bd 100644
--- a/Source/Core/Core/NetPlayProto.h
+++ b/Source/Core/Core/NetPlayProto.h
@@ -58,6 +58,12 @@ enum
NP_MSG_TIMEBASE = 0xB0,
NP_MSG_DESYNC_DETECTED = 0xB1,
+ NP_MSG_COMPUTE_MD5 = 0xC0,
+ NP_MSG_MD5_PROGRESS = 0xC1,
+ NP_MSG_MD5_RESULT = 0xC2,
+ NP_MSG_MD5_ABORT = 0xC3,
+ NP_MSG_MD5_ERROR = 0xC4,
+
NP_MSG_READY = 0xD0,
NP_MSG_NOT_READY = 0xD1,
diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp
index d51074d66f..44224ce588 100644
--- a/Source/Core/Core/NetPlayServer.cpp
+++ b/Source/Core/Core/NetPlayServer.cpp
@@ -664,6 +664,49 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
}
}
break;
+
+ case NP_MSG_MD5_PROGRESS:
+ {
+ int progress;
+ packet >> progress;
+
+ sf::Packet spac;
+ spac << static_cast(NP_MSG_MD5_PROGRESS);
+ spac << player.pid;
+ spac << progress;
+
+ SendToClients(spac);
+ }
+ break;
+
+ case NP_MSG_MD5_RESULT:
+ {
+ std::string result;
+ packet >> result;
+
+ sf::Packet spac;
+ spac << static_cast(NP_MSG_MD5_RESULT);
+ spac << player.pid;
+ spac << result;
+
+ SendToClients(spac);
+ }
+ break;
+
+ case NP_MSG_MD5_ERROR:
+ {
+ std::string error;
+ packet >> error;
+
+ sf::Packet spac;
+ spac << static_cast(NP_MSG_MD5_ERROR);
+ spac << player.pid;
+ spac << error;
+
+ SendToClients(spac);
+ }
+ break;
+
default:
PanicAlertT("Unknown message with id:%d received from player:%d Kicking player!", mid,
player.pid);
@@ -685,8 +728,8 @@ void NetPlayServer::OnTraversalStateChanged()
void NetPlayServer::SendChatMessage(const std::string& msg)
{
auto spac = std::make_unique();
- *spac << (MessageId)NP_MSG_CHAT_MESSAGE;
- *spac << (PlayerId)0; // server id always 0
+ *spac << static_cast(NP_MSG_CHAT_MESSAGE);
+ *spac << static_cast(0); // server id always 0
*spac << msg;
SendAsyncToClients(std::move(spac));
@@ -701,7 +744,7 @@ bool NetPlayServer::ChangeGame(const std::string& game)
// send changed game to clients
auto spac = std::make_unique();
- *spac << (MessageId)NP_MSG_CHANGE_GAME;
+ *spac << static_cast(NP_MSG_CHANGE_GAME);
*spac << game;
SendAsyncToClients(std::move(spac));
@@ -709,6 +752,29 @@ bool NetPlayServer::ChangeGame(const std::string& game)
return true;
}
+// called from ---GUI--- thread
+bool NetPlayServer::ComputeMD5(const std::string& file_identifier)
+{
+ auto spac = std::make_unique();
+ *spac << static_cast(NP_MSG_COMPUTE_MD5);
+ *spac << file_identifier;
+
+ SendAsyncToClients(std::move(spac));
+
+ return true;
+}
+
+// called from ---GUI--- thread
+bool NetPlayServer::AbortMD5()
+{
+ auto spac = std::make_unique();
+ *spac << static_cast(NP_MSG_MD5_ABORT);
+
+ SendAsyncToClients(std::move(spac));
+
+ return true;
+}
+
// called from ---GUI--- thread
void NetPlayServer::SetNetSettings(const NetSettings& settings)
{
diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h
index 3fd72e38bf..f75431fcfa 100644
--- a/Source/Core/Core/NetPlayServer.h
+++ b/Source/Core/Core/NetPlayServer.h
@@ -20,6 +20,7 @@
enum class PlayerGameStatus;
class NetPlayUI;
+enum class PlayerGameStatus;
class NetPlayServer : public TraversalClientClient
{
@@ -31,6 +32,8 @@ public:
~NetPlayServer();
bool ChangeGame(const std::string& game);
+ bool ComputeMD5(const std::string& file_identifier);
+ bool AbortMD5();
void SendChatMessage(const std::string& msg);
void SetNetSettings(const NetSettings& settings);
diff --git a/Source/Core/DolphinWX/CMakeLists.txt b/Source/Core/DolphinWX/CMakeLists.txt
index fa9d08c272..9e89e73987 100644
--- a/Source/Core/DolphinWX/CMakeLists.txt
+++ b/Source/Core/DolphinWX/CMakeLists.txt
@@ -34,6 +34,7 @@ set(GUI_SRCS
Debugger/WatchView.cpp
Debugger/WatchWindow.cpp
NetPlay/ChangeGameDialog.cpp
+ NetPlay/MD5Dialog.cpp
NetPlay/NetPlaySetupFrame.cpp
NetPlay/NetWindow.cpp
NetPlay/PadMapDialog.cpp
diff --git a/Source/Core/DolphinWX/DolphinWX.vcxproj b/Source/Core/DolphinWX/DolphinWX.vcxproj
index 56a434ae64..56b5ed630a 100644
--- a/Source/Core/DolphinWX/DolphinWX.vcxproj
+++ b/Source/Core/DolphinWX/DolphinWX.vcxproj
@@ -89,6 +89,7 @@
+
@@ -127,6 +128,7 @@
+
diff --git a/Source/Core/DolphinWX/DolphinWX.vcxproj.filters b/Source/Core/DolphinWX/DolphinWX.vcxproj.filters
index 5534ec41f8..c65d43ef1b 100644
--- a/Source/Core/DolphinWX/DolphinWX.vcxproj.filters
+++ b/Source/Core/DolphinWX/DolphinWX.vcxproj.filters
@@ -360,6 +360,9 @@
GUI\NetPlay
+
+ GUI\NetPlay
+
GUI\NetPlay
diff --git a/Source/Core/DolphinWX/ISOProperties.cpp b/Source/Core/DolphinWX/ISOProperties.cpp
index b939b1db3c..74352fd5ca 100644
--- a/Source/Core/DolphinWX/ISOProperties.cpp
+++ b/Source/Core/DolphinWX/ISOProperties.cpp
@@ -51,6 +51,7 @@
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
+#include "Common/MD5.h"
#include "Common/StringUtil.h"
#include "Common/SysConf.h"
#include "Core/ActionReplay.h"
@@ -1304,42 +1305,13 @@ void CISOProperties::OnEditConfig(wxCommandEvent& WXUNUSED(event))
void CISOProperties::OnComputeMD5Sum(wxCommandEvent& WXUNUSED(event))
{
- u8 output[16];
- std::string output_string;
- std::vector data(8 * 1024 * 1024);
- u64 read_offset = 0;
- mbedtls_md5_context ctx;
-
- std::unique_ptr file(
- DiscIO::CreateBlobReader(OpenGameListItem.GetFileName()));
- u64 game_size = file->GetDataSize();
-
- wxProgressDialog progressDialog(_("Computing MD5 checksum"), _("Working..."), 1000, this,
+ wxProgressDialog progressDialog(_("Computing MD5 checksum"), _("Working..."), 100, this,
wxPD_APP_MODAL | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME |
wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME | wxPD_SMOOTH);
- mbedtls_md5_starts(&ctx);
-
- while (read_offset < game_size)
- {
- if (!progressDialog.Update((int)((double)read_offset / (double)game_size * 1000)))
- return;
-
- size_t read_size = std::min((u64)data.size(), game_size - read_offset);
- if (!file->Read(read_offset, read_size, data.data()))
- return;
-
- mbedtls_md5_update(&ctx, data.data(), read_size);
- read_offset += read_size;
- }
-
- mbedtls_md5_finish(&ctx, output);
-
- // Convert to hex
- for (int a = 0; a < 16; ++a)
- output_string += StringFromFormat("%02x", output[a]);
-
- m_MD5Sum->SetValue(output_string);
+ m_MD5Sum->SetValue(MD5::MD5Sum(OpenGameListItem.GetFileName(), [&progressDialog](int progress) {
+ return progressDialog.Update(progress);
+ }));
}
// Opens all pre-defined INIs for the game. If there are multiple ones,
diff --git a/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.cpp b/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.cpp
index 4c315ee44b..be1173e12e 100644
--- a/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.cpp
+++ b/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.cpp
@@ -10,7 +10,7 @@
#include "DolphinWX/NetPlay/NetWindow.h"
ChangeGameDialog::ChangeGameDialog(wxWindow* parent, const CGameListCtrl* const game_list)
- : wxDialog(parent, wxID_ANY, _("Change Game"))
+ : wxDialog(parent, wxID_ANY, _("Select Game"))
{
m_game_lbox =
new wxListBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SORT);
@@ -18,7 +18,7 @@ ChangeGameDialog::ChangeGameDialog(wxWindow* parent, const CGameListCtrl* const
NetPlayDialog::FillWithGameNames(m_game_lbox, *game_list);
- wxButton* const ok_btn = new wxButton(this, wxID_OK, _("Change"));
+ wxButton* const ok_btn = new wxButton(this, wxID_OK, _("Select"));
ok_btn->Bind(wxEVT_BUTTON, &ChangeGameDialog::OnPick, this);
wxBoxSizer* const szr = new wxBoxSizer(wxVERTICAL);
diff --git a/Source/Core/DolphinWX/NetPlay/MD5Dialog.cpp b/Source/Core/DolphinWX/NetPlay/MD5Dialog.cpp
new file mode 100644
index 0000000000..1d0f7a0817
--- /dev/null
+++ b/Source/Core/DolphinWX/NetPlay/MD5Dialog.cpp
@@ -0,0 +1,100 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "Common/StringUtil.h"
+#include "DolphinWX/NetPlay/MD5Dialog.h"
+#include "DolphinWX/NetPlay/NetWindow.h"
+
+MD5Dialog::MD5Dialog(wxWindow* parent, NetPlayServer* server, std::vector players,
+ const std::string& game)
+ : wxDialog(parent, wxID_ANY, _("MD5 Checksum")), m_parent(parent), m_netplay_server(server)
+{
+ Bind(wxEVT_CLOSE_WINDOW, &MD5Dialog::OnClose, this);
+ wxBoxSizer* const main_sizer = new wxBoxSizer(wxVERTICAL);
+
+ main_sizer->Add(new wxStaticText(this, wxID_ANY, _("Computing MD5 Checksum for:") + "\n" + game,
+ wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL),
+ 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5);
+
+ for (const Player* player : players)
+ {
+ wxStaticBoxSizer* const player_szr = new wxStaticBoxSizer(
+ wxVERTICAL, this, player->name + " (p" + std::to_string(player->pid) + ")");
+
+ wxGauge* gauge = new wxGauge(this, wxID_ANY, 100);
+ m_progress_bars[player->pid] = gauge;
+ player_szr->Add(gauge, 0, wxEXPAND | wxALL, 5);
+
+ m_result_labels[player->pid] =
+ new wxStaticText(this, wxID_ANY, _("Computing..."), wxDefaultPosition, wxSize(250, 20),
+ wxALIGN_CENTRE_HORIZONTAL);
+
+ m_result_labels[player->pid]->SetSize(250, 15);
+ player_szr->Add(m_result_labels[player->pid], 0, wxALL, 5);
+
+ main_sizer->Add(player_szr, 0, wxEXPAND | wxALL, 5);
+ }
+
+ m_final_result_label =
+ new wxStaticText(this, wxID_ANY,
+ " ", // so it takes space
+ wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL);
+ main_sizer->Add(m_final_result_label, 1, wxALL, 5);
+
+ wxButton* close_btn = new wxButton(this, wxID_ANY, _("Close"));
+ close_btn->Bind(wxEVT_BUTTON, &MD5Dialog::OnCloseBtnPressed, this);
+ main_sizer->Add(close_btn, 0, wxEXPAND | wxALL, 5);
+
+ SetSizerAndFit(main_sizer);
+ SetFocus();
+ Center();
+}
+
+void MD5Dialog::SetProgress(int pid, int progress)
+{
+ if (m_progress_bars[pid] == nullptr)
+ return;
+
+ m_progress_bars[pid]->SetValue(progress);
+ m_result_labels[pid]->SetLabel(_("Computing: ") + std::to_string(progress) + "%");
+ Update();
+}
+
+void MD5Dialog::SetResult(int pid, const std::string& result)
+{
+ if (m_result_labels[pid] == nullptr)
+ return;
+
+ m_result_labels[pid]->SetLabel(result);
+ m_hashes.push_back(result);
+
+ if (m_hashes.size() <= 1)
+ return;
+
+ wxString label = AllHashesMatch() ? _("Hashes match!") : _("Hashes do not match.");
+ m_final_result_label->SetLabel(label);
+}
+
+bool MD5Dialog::AllHashesMatch() const
+{
+ return std::adjacent_find(m_hashes.begin(), m_hashes.end(), std::not_equal_to<>()) ==
+ m_hashes.end();
+}
+
+void MD5Dialog::OnClose(wxCloseEvent& event)
+{
+ m_netplay_server->AbortMD5();
+}
+
+void MD5Dialog::OnCloseBtnPressed(wxCommandEvent&)
+{
+ Close();
+}
diff --git a/Source/Core/DolphinWX/NetPlay/MD5Dialog.h b/Source/Core/DolphinWX/NetPlay/MD5Dialog.h
new file mode 100644
index 0000000000..82b88714e9
--- /dev/null
+++ b/Source/Core/DolphinWX/NetPlay/MD5Dialog.h
@@ -0,0 +1,40 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include