Emulate Skylanders within Dolphin

Ported the code from RPCS3, with improvements made to the handling of control messages and audio transfers, Co-Authored with @mandar1jn

Missing new line chars

Co-Authored-By: mandar1jn <49076509+mandar1jn@users.noreply.github.com>
This commit is contained in:
Joshua de Reeper
2023-01-22 14:50:30 +13:00
co-authored by mandar1jn
parent 9fe1d80920
commit f76a6789a0
18 changed files with 1994 additions and 0 deletions
+4
View File
@@ -399,6 +399,10 @@ add_library(core
IOS/USB/Bluetooth/WiimoteHIDAttr.h
IOS/USB/Common.cpp
IOS/USB/Common.h
IOS/USB/EmulatedUSBDevice.cpp
IOS/USB/EmulatedUSBDevice.h
IOS/USB/Emulated/Skylander.cpp
IOS/USB/Emulated/Skylander.h
IOS/USB/Host.cpp
IOS/USB/Host.h
IOS/USB/OH0/OH0.cpp
+10
View File
@@ -551,6 +551,16 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices)
Config::SetBase(Config::MAIN_USB_PASSTHROUGH_DEVICES, SaveUSBWhitelistToString(devices));
}
// Main.EmulatedUSBDevices
const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL{
{System::Main, "EmulatedUSBDevices", "EmulateSkylanderPortal"}, false};
bool EmulateSkylanderPortal()
{
return Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL);
}
// The reason we need this function is because some memory card code
// expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii.
DiscIO::Region ToGameCubeRegion(DiscIO::Region region)
+5
View File
@@ -342,6 +342,11 @@ extern const Info<std::string> MAIN_USB_PASSTHROUGH_DEVICES;
std::set<std::pair<u16, u16>> GetUSBDeviceWhitelist();
void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices);
// Main.EmulatedUSBDevices
extern const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL;
bool EmulateSkylanderPortal();
// GameCube path utility functions
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions
+4
View File
@@ -112,6 +112,10 @@ struct TransferCommand
std::unique_ptr<u8[]> MakeBuffer(size_t size) const;
void FillBuffer(const u8* src, size_t size) const;
// Fake Transfers
u64 expected_time;
u32 expected_count;
private:
Kernel& m_ios;
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,102 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <map>
#include <mutex>
#include <queue>
#include <vector>
#include "Common/IOFile.h"
#include "Core/IOS/USB/EmulatedUSBDevice.h"
// The maximum possible characters the portal can handle.
// The status array is 32 bits and every character takes 2 bits.
// 32/2 = 16
constexpr u8 MAX_SKYLANDERS = 16;
namespace IOS::HLE::USB
{
class SkylanderUSB final : public EmulatedUSBDevice
{
public:
SkylanderUSB(Kernel& ios, const std::string& device_name);
~SkylanderUSB();
DeviceDescriptor GetDeviceDescriptor() const override;
std::vector<ConfigDescriptor> GetConfigurations() const override;
std::vector<InterfaceDescriptor> GetInterfaces(u8 config) const override;
std::vector<EndpointDescriptor> GetEndpoints(u8 config, u8 interface, u8 alt) const override;
bool Attach() override;
bool AttachAndChangeInterface(u8 interface) override;
int CancelTransfer(u8 endpoint) override;
int ChangeInterface(u8 interface) override;
int GetNumberOfAltSettings(u8 interface) override;
int SetAltSetting(u8 alt_setting) override;
int SubmitTransfer(std::unique_ptr<CtrlMessage> message) override;
int SubmitTransfer(std::unique_ptr<BulkMessage> message) override;
int SubmitTransfer(std::unique_ptr<IntrMessage> message) override;
int SubmitTransfer(std::unique_ptr<IsoMessage> message) override;
protected:
std::queue<std::array<u8, 64>> q_queries;
private:
u16 m_vid = 0;
u16 m_pid = 0;
u8 m_active_interface = 0;
bool m_device_attached = false;
DeviceDescriptor deviceDesc;
std::vector<ConfigDescriptor> configDesc;
std::vector<InterfaceDescriptor> interfaceDesc;
std::vector<EndpointDescriptor> endpointDesc;
bool m_has_initialised = false;
};
struct Skylander final
{
File::IOFile sky_file;
u8 status = 0;
std::queue<u8> queued_status;
std::array<u8, 0x40 * 0x10> data{};
u32 last_id = 0;
void save();
};
struct LedColor final
{
u8 r = 0, g = 0, b = 0;
};
class SkylanderPortal final
{
public:
void Activate();
void Deactivate();
bool IsActivated();
void UpdateStatus();
void SetLEDs(u8 side, u8 r, u8 g, u8 b);
std::array<u8, 64> GetStatus();
void QueryBlock(u8 sky_num, u8 block, u8* reply_buf);
void WriteBlock(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf);
bool RemoveSkylander(u8 sky_num);
u8 LoadSkylander(u8* buf, File::IOFile in_file);
protected:
std::mutex sky_mutex;
bool activated = true;
bool status_updated = false;
u8 interrupt_counter = 0;
LedColor color_right = {};
LedColor color_left = {};
LedColor color_trap = {};
Skylander skylanders[MAX_SKYLANDERS];
};
extern SkylanderPortal g_skyportal;
} // namespace IOS::HLE::USB
@@ -0,0 +1,82 @@
#include "Core/IOS/USB/EmulatedUSBDevice.h"
#include <thread>
#include "Common/Thread.h"
#include "Common/Timer.h"
#include "Core/Core.h"
namespace IOS::HLE::USB
{
EmulatedUSBDevice::EmulatedUSBDevice(Kernel& ios, const std::string& device_name) : m_ios(ios)
{
}
EmulatedUSBDevice::~EmulatedUSBDevice()
{
}
EmulatedUSBDevice::FakeTransferThread::~FakeTransferThread()
{
Stop();
}
void EmulatedUSBDevice::FakeTransferThread::Start()
{
if (Core::WantsDeterminism())
return;
if (m_thread_running.TestAndSet())
{
m_thread = std::thread([this] {
Common::SetCurrentThreadName("Fake Transfer Thread");
while (m_thread_running.IsSet())
{
if (!m_transfers.empty())
{
std::lock_guard lk{m_transfers_mutex};
u64 timestamp = Common::Timer::NowUs();
for (auto iterator = m_transfers.begin(); iterator != m_transfers.end();)
{
auto* command = iterator->second.get();
if (command->expected_time > timestamp)
{
++iterator;
continue;
}
command->FillBuffer(iterator->first.data(), command->expected_count);
command->OnTransferComplete(command->expected_count);
iterator = m_transfers.erase(iterator);
}
}
}
});
}
}
void EmulatedUSBDevice::FakeTransferThread::Stop()
{
if (m_thread_running.TestAndClear())
m_thread.join();
}
void EmulatedUSBDevice::FakeTransferThread::AddTransfer(std::unique_ptr<TransferCommand> command,
std::array<u8, 64> data)
{
std::lock_guard lk{m_transfers_mutex};
m_transfers.emplace(data, std::move(command));
}
void EmulatedUSBDevice::FakeTransferThread::ClearTransfers()
{
std::lock_guard lk{m_transfers_mutex};
m_transfers.clear();
}
bool EmulatedUSBDevice::FakeTransferThread::GetTransfers()
{
std::lock_guard lk{m_transfers_mutex};
return m_transfers.empty();
}
} // namespace IOS::HLE::USB
@@ -0,0 +1,43 @@
#pragma once
#include <string>
#include <thread>
#include "Common/CommonTypes.h"
#include "Core/IOS/USB/Common.h"
namespace IOS::HLE::USB
{
class EmulatedUSBDevice : public Device
{
public:
EmulatedUSBDevice(Kernel& ios, const std::string& device_name);
virtual ~EmulatedUSBDevice();
protected:
class FakeTransferThread final
{
public:
explicit FakeTransferThread(EmulatedUSBDevice* device) : m_device(device) {}
~FakeTransferThread();
void Start();
void Stop();
void AddTransfer(std::unique_ptr<TransferCommand> command, std::array<u8, 64> data);
void ClearTransfers();
bool GetTransfers();
private:
EmulatedUSBDevice* m_device = nullptr;
Common::Flag m_thread_running;
std::thread m_thread;
Common::Flag m_is_initialized;
std::map<std::array<u8, 64>, std::unique_ptr<TransferCommand>> m_transfers;
std::mutex m_transfers_mutex;
};
FakeTransferThread m_transfer_thread{this};
FakeTransferThread& GetTransferThread() { return m_transfer_thread; }
private:
Kernel& m_ios;
};
} // namespace IOS::HLE::USB
+13
View File
@@ -22,6 +22,7 @@
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
#include "Core/IOS/USB/Common.h"
#include "Core/IOS/USB/Emulated/Skylander.h"
#include "Core/IOS/USB/LibusbDevice.h"
namespace IOS::HLE
@@ -115,6 +116,18 @@ bool USBHost::UpdateDevices(const bool always_add_hooks)
bool USBHost::AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
const bool always_add_hooks)
{
if (Config::EmulateSkylanderPortal())
{
auto skylanderportal = std::make_unique<USB::SkylanderUSB>(m_ios, "Skylander Portal");
if (!ShouldAddDevice(*skylanderportal))
return true;
const u64 skyid = skylanderportal->GetId();
new_devices.insert(skyid);
if (AddDevice(std::move(skylanderportal)))
{
hooks.emplace(GetDeviceById(skyid), ChangeEvent::Inserted);
}
}
#ifdef __LIBUSB__
auto whitelist = Config::GetUSBDeviceWhitelist();
if (whitelist.empty())
+4
View File
@@ -373,6 +373,8 @@
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.h" />
<ClInclude Include="Core\IOS\USB\Common.h" />
<ClInclude Include="Core\IOS\USB\EmulatedUSBDevice.h" />
<ClInclude Include="Core\IOS\USB\Emulated\Skylander.h" />
<ClInclude Include="Core\IOS\USB\Host.h" />
<ClInclude Include="Core\IOS\USB\LibusbDevice.h" />
<ClInclude Include="Core\IOS\USB\OH0\OH0.h" />
@@ -985,6 +987,8 @@
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteDevice.cpp" />
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
<ClCompile Include="Core\IOS\USB\Common.cpp" />
<ClCompile Include="Core\IOS\USB\EmulatedUSBDevice.cpp" />
<ClCompile Include="Core\IOS\USB\Emulated\Skylander.cpp" />
<ClCompile Include="Core\IOS\USB\Host.cpp" />
<ClCompile Include="Core\IOS\USB\LibusbDevice.cpp" />
<ClCompile Include="Core\IOS\USB\OH0\OH0.cpp" />
+2
View File
@@ -325,6 +325,8 @@ add_executable(dolphin-emu
Settings/USBDeviceAddToWhitelistDialog.h
Settings/WiiPane.cpp
Settings/WiiPane.h
SkylanderPortal/SkylanderPortalWindow.cpp
SkylanderPortal/SkylanderPortalWindow.h
TAS/GCTASInputWindow.cpp
TAS/GCTASInputWindow.h
TAS/GBATASInputWindow.cpp
+2
View File
@@ -200,6 +200,7 @@
<ClCompile Include="Settings\PathPane.cpp" />
<ClCompile Include="Settings\USBDeviceAddToWhitelistDialog.cpp" />
<ClCompile Include="Settings\WiiPane.cpp" />
<ClCompile Include="SkylanderPortal\SkylanderPortalWindow.cpp" />
<ClCompile Include="TAS\GCTASInputWindow.cpp" />
<ClCompile Include="TAS\GBATASInputWindow.cpp" />
<ClCompile Include="TAS\IRWidget.cpp" />
@@ -379,6 +380,7 @@
<QtMoc Include="Settings\PathPane.h" />
<QtMoc Include="Settings\USBDeviceAddToWhitelistDialog.h" />
<QtMoc Include="Settings\WiiPane.h" />
<QtMoc Include="SkylanderPortal\SkylanderPortalWindow.h" />
<QtMoc Include="TAS\GCTASInputWindow.h" />
<QtMoc Include="TAS\GBATASInputWindow.h" />
<QtMoc Include="TAS\IRWidget.h" />
+14
View File
@@ -111,6 +111,7 @@
#include "DolphinQt/RiivolutionBootWidget.h"
#include "DolphinQt/SearchBar.h"
#include "DolphinQt/Settings.h"
#include "DolphinQt/SkylanderPortal/SkylanderPortalWindow.h"
#include "DolphinQt/TAS/GBATASInputWindow.h"
#include "DolphinQt/TAS/GCTASInputWindow.h"
#include "DolphinQt/TAS/WiiTASInputWindow.h"
@@ -520,6 +521,7 @@ void MainWindow::ConnectMenuBar()
connect(m_menu_bar, &MenuBar::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog);
connect(m_menu_bar, &MenuBar::BrowseNetPlay, this, &MainWindow::ShowNetPlayBrowser);
connect(m_menu_bar, &MenuBar::ShowFIFOPlayer, this, &MainWindow::ShowFIFOPlayer);
connect(m_menu_bar, &MenuBar::ShowSkylanderPortal, this, &MainWindow::ShowSkylanderPortal);
connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
// Movie
@@ -1301,6 +1303,18 @@ void MainWindow::ShowFIFOPlayer()
m_fifo_window->activateWindow();
}
void MainWindow::ShowSkylanderPortal()
{
if (!m_skylander_window)
{
m_skylander_window = new SkylanderPortalWindow;
}
m_skylander_window->show();
m_skylander_window->raise();
m_skylander_window->activateWindow();
}
void MainWindow::StateLoad()
{
QString path =
+3
View File
@@ -43,6 +43,7 @@ class RegisterWidget;
class RenderWidget;
class SearchBar;
class SettingsWindow;
class SkylanderPortalWindow;
class ThreadWidget;
class ToolBar;
class WatchWidget;
@@ -159,6 +160,7 @@ private:
void ShowNetPlaySetupDialog();
void ShowNetPlayBrowser();
void ShowFIFOPlayer();
void ShowSkylanderPortal();
void ShowMemcardManager();
void ShowResourcePackManager();
void ShowCheatsManager();
@@ -222,6 +224,7 @@ private:
SettingsWindow* m_settings_window = nullptr;
GraphicsWindow* m_graphics_window = nullptr;
FIFOPlayerWindow* m_fifo_window = nullptr;
SkylanderPortalWindow* m_skylander_window = nullptr;
MappingWindow* m_hotkey_window = nullptr;
FreeLookWindow* m_freelook_window = nullptr;
+2
View File
@@ -221,6 +221,8 @@ void MenuBar::AddToolsMenu()
tools_menu->addAction(tr("FIFO Player"), this, &MenuBar::ShowFIFOPlayer);
tools_menu->addAction(tr("&Skylanders Portal"), this, [this] { emit ShowSkylanderPortal(); });
tools_menu->addSeparator();
tools_menu->addAction(tr("Start &NetPlay..."), this, &MenuBar::StartNetPlay);
+1
View File
@@ -89,6 +89,7 @@ signals:
void ShowAboutDialog();
void ShowCheatsManager();
void ShowResourcePackManager();
void ShowSkylanderPortal();
void ConnectWiiRemote(int id);
// Options
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,60 @@
#pragma once
#include <optional>
#include <QCheckBox>
#include <QDialog>
#include <QGroupBox>
#include <QLineEdit>
#include <QWidget>
#include "Core/Core.h"
#include "Core/IOS/USB/Emulated/Skylander.h"
class QDialogButtonBox;
class QLabel;
class QPushButton;
class QSpinBox;
class QTabWidget;
class SkylanderPortalWindow : public QWidget
{
Q_OBJECT
public:
explicit SkylanderPortalWindow(QWidget* parent = nullptr);
~SkylanderPortalWindow();
protected:
QLineEdit* edit_skylanders[MAX_SKYLANDERS]{};
static std::optional<std::tuple<u8, u16, u16>> sky_slots[MAX_SKYLANDERS];
private:
void CreateMainWindow();
void OnEmulationStateChanged(Core::State state);
void CreateSkylander(u8 slot);
void ClearSkylander(u8 slot);
void EmulatePortal(bool emulate);
void LoadSkylander(u8 slot);
void LoadSkylanderPath(u8 slot, const QString& path);
void UpdateEdits();
void closeEvent(QCloseEvent* bar) override;
static SkylanderPortalWindow* inst;
QCheckBox* checkbox;
QGroupBox* group_skylanders;
bool eventFilter(QObject* object, QEvent* event) final override;
};
class CreateSkylanderDialog : public QDialog
{
Q_OBJECT
public:
explicit CreateSkylanderDialog(QWidget* parent);
QString GetFilePath() const;
protected:
QString file_path;
};