mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
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:
co-authored by
mandar1jn
parent
9fe1d80920
commit
f76a6789a0
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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())
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user