summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorJMC47 <JMC4789@gmail.com>2026-03-12 21:49:08 -0400
committerGitHub <noreply@github.com>2026-03-12 21:49:08 -0400
commitf0096310bf0f5192b95ceaaf321536b3d215fa5d (patch)
tree51d3c998aafbf446bacc8e379e27b94562a6fc70 /Source/Core
parent12d790a54ab7814d26dd4bd4383d8fb65f280c1d (diff)
parentcca880de1662db2436568d3bd8b2887c50f8dc60 (diff)
Merge pull request #14452 from jordan-woyak/triforce-avalon-touchscreen
Triforce: Implement The Key of Avalon's touchscreen.
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/Core/CMakeLists.txt4
-rw-r--r--Source/Core/Core/HW/MagCard/MagneticCardReader.cpp76
-rw-r--r--Source/Core/Core/HW/MagCard/MagneticCardReader.h16
-rw-r--r--Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp76
-rw-r--r--Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h17
-rw-r--r--Source/Core/Core/HW/Triforce/SerialDevice.cpp55
-rw-r--r--Source/Core/Core/HW/Triforce/SerialDevice.h60
-rw-r--r--Source/Core/Core/HW/Triforce/Touchscreen.cpp71
-rw-r--r--Source/Core/Core/HW/Triforce/Touchscreen.h18
-rw-r--r--Source/Core/Core/State.cpp2
-rw-r--r--Source/Core/DolphinLib.props4
11 files changed, 308 insertions, 91 deletions
diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index b81558e274..821080afca 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -294,6 +294,10 @@ add_library(core
HW/StreamADPCM.h
HW/SystemTimers.cpp
HW/SystemTimers.h
+ HW/Triforce/SerialDevice.cpp
+ HW/Triforce/SerialDevice.h
+ HW/Triforce/Touchscreen.cpp
+ HW/Triforce/Touchscreen.h
HW/VideoInterface.cpp
HW/VideoInterface.h
HW/WII_IPC.cpp
diff --git a/Source/Core/Core/HW/MagCard/MagneticCardReader.cpp b/Source/Core/Core/HW/MagCard/MagneticCardReader.cpp
index 107ccb391e..89ae1534d5 100644
--- a/Source/Core/Core/HW/MagCard/MagneticCardReader.cpp
+++ b/Source/Core/Core/HW/MagCard/MagneticCardReader.cpp
@@ -597,11 +597,15 @@ void MagneticCardReader::SetSError(S error_code)
FinishCommand();
}
-void MagneticCardReader::Process(std::vector<u8>* read, std::vector<u8>* write)
+void MagneticCardReader::Update()
{
- while (!read->empty())
+ while (true)
{
- const u8 first_byte = read->front();
+ const auto read = GetRxByteSpan();
+ if (read.empty())
+ break;
+
+ const u8 first_byte = read.front();
if (first_byte == ENQUIRY)
{
// ENQUIRY
@@ -617,16 +621,16 @@ void MagneticCardReader::Process(std::vector<u8>* read, std::vector<u8>* write)
StepStateMachine(elapsed_time);
StepStatePerson(elapsed_time);
- BuildPacket(*write);
+ BuildPacket();
- read->erase(read->begin()); // TODO: SLOW !
+ ConsumeRxBytes(1);
continue;
}
if (first_byte != START_OF_TEXT)
{
ERROR_LOG_FMT(SERIALINTERFACE_CARD, "Process: Unexpected {:02x}", first_byte);
- read->erase(read->begin()); // TODO: SLOW !
+ ConsumeRxBytes(1);
continue;
}
@@ -634,25 +638,25 @@ void MagneticCardReader::Process(std::vector<u8>* read, std::vector<u8>* write)
// This is a command packet. The next byte provides the size.
// Upon read they ACK or NACK and start processing of a command.
- if (read->size() < 2)
+ if (read.size() < 2)
break; // Wait for more data.
- const std::size_t packet_size = (*read)[1];
- if (packet_size > read->size() - 2)
+ const std::size_t packet_size = read[1];
+ if (packet_size > read.size() - 2)
break; // Wait for more data.
- if (ReceivePacket(std::span{*read}.subspan(2, packet_size)))
+ if (ReceivePacket(read.subspan(2, packet_size)))
{
DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Writing ACK");
- write->emplace_back(ACK);
+ WriteTxByte(ACK);
}
else
{
WARN_LOG_FMT(SERIALINTERFACE_CARD, "Writing NACK");
- write->emplace_back(NACK);
+ WriteTxByte(NACK);
}
- read->erase(read->begin(), read->begin() + packet_size + 2); // TODO: SLOW !
+ ConsumeRxBytes(packet_size + 2);
}
}
@@ -858,41 +862,31 @@ void MagneticCardReader::StepStateMachine(DT elapsed_time)
++m_current_step;
}
-void MagneticCardReader::BuildPacket(std::vector<u8>& write_buffer)
+void MagneticCardReader::BuildPacket()
{
// Header and footer add 6 bytes.
const u8 payload_size = u8(m_command_payload.size() + 6);
- // + START_OF_TEXT + the count byte
- const auto total_write_size = payload_size + 2;
-
- const auto prev_buffer_size = write_buffer.size();
- write_buffer.resize(prev_buffer_size + total_write_size);
- auto* out_ptr = write_buffer.data() + prev_buffer_size;
- u8 packet_checksum = 0;
+ WriteTxByte(START_OF_TEXT); // Not included in the checksum.
- const auto write_and_checksum = [&](u8 value) {
- *(out_ptr++) = value;
- packet_checksum ^= value;
- };
-
- // Write the header.
- *(out_ptr++) = START_OF_TEXT;
- write_and_checksum(payload_size);
- write_and_checksum(GetCurrentCommand());
- write_and_checksum(GetPositionValue());
- write_and_checksum(u8(m_status.p));
- write_and_checksum(u8(m_status.s));
+ const auto lead_in = std::to_array<u8>({
+ payload_size,
+ GetCurrentCommand(),
+ GetPositionValue(),
+ u8(m_status.p),
+ u8(m_status.s),
+ });
+ WriteTxBytes(lead_in);
- // Write the payload.
- std::ranges::for_each(m_command_payload, write_and_checksum);
+ WriteTxBytes(m_command_payload);
- // Write the footer.
- write_and_checksum(END_OF_TEXT);
- *(out_ptr++) = packet_checksum;
+ WriteTxByte(END_OF_TEXT);
- DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "BuildPacket: {}",
- HexDump(std::span{write_buffer}.subspan(write_buffer.size() - payload_size - 2)));
+ // Checksum is XOR of bytes after START_OF_TEXT.
+ const u8 packet_checksum = std::accumulate(lead_in.begin(), lead_in.end(), u8{}, std::bit_xor{}) ^
+ std::accumulate(m_command_payload.begin(), m_command_payload.end(),
+ END_OF_TEXT, std::bit_xor{});
+ WriteTxByte(packet_checksum);
}
bool MagneticCardReader::IsRunningCommand() const
@@ -983,6 +977,8 @@ bool MagneticCardReader::IsReadyForCard()
void MagneticCardReader::DoState(PointerWrap& p)
{
+ SerialDevice::DoState(p);
+
// Outgoing packet.
p.Do(m_status);
p.Do(m_current_command);
diff --git a/Source/Core/Core/HW/MagCard/MagneticCardReader.h b/Source/Core/Core/HW/MagCard/MagneticCardReader.h
index b60514a6ab..dd301d4590 100644
--- a/Source/Core/Core/HW/MagCard/MagneticCardReader.h
+++ b/Source/Core/Core/HW/MagCard/MagneticCardReader.h
@@ -16,10 +16,12 @@
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
+#include "Core/HW/Triforce/SerialDevice.h"
+
namespace MagCard
{
-class MagneticCardReader
+class MagneticCardReader : public Triforce::SerialDevice
{
public:
static constexpr std::size_t TRACK_SIZE = 69; // A nice amount of data.
@@ -36,20 +38,16 @@ public:
};
explicit MagneticCardReader(Settings* settings);
- virtual ~MagneticCardReader();
+ ~MagneticCardReader() override;
MagneticCardReader(const MagneticCardReader&) = delete;
MagneticCardReader& operator=(const MagneticCardReader&) = delete;
MagneticCardReader(MagneticCardReader&&) = delete;
MagneticCardReader& operator=(MagneticCardReader&&) = delete;
- // TODO: This std:vector buffer interface is a bit funky..
-
- // read = MagCard <-- Baseboard.
- // write = Magcard --> Baseboard.
- void Process(std::vector<u8>* read, std::vector<u8>* write);
+ void Update() override;
- virtual void DoState(PointerWrap& p);
+ void DoState(PointerWrap& p) override;
protected:
// Status bytes:
@@ -114,7 +112,7 @@ protected:
};
bool ReceivePacket(std::span<const u8> packet);
- void BuildPacket(std::vector<u8>& write_buffer);
+ void BuildPacket();
void StepStateMachine(DT elapsed_time);
void StepStatePerson(DT elapsed_time);
diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp
index 9209b36461..4651454321 100644
--- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp
+++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp
@@ -27,6 +27,7 @@
#include "Core/HW/SI/SI.h"
#include "Core/HW/SI/SI_Device.h"
#include "Core/HW/SystemTimers.h"
+#include "Core/HW/Triforce/Touchscreen.h"
#include "Core/Movie.h"
#include "Core/System.h"
@@ -163,12 +164,16 @@ CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices dev
switch (AMMediaboard::GetGameType())
{
case FZeroAX:
- m_mag_card_reader = std::make_unique<MagCard::C1231BR>(&m_mag_card_settings);
+ m_serial_device_b = std::make_unique<MagCard::C1231BR>(&m_mag_card_settings);
break;
case MarioKartGP:
case MarioKartGP2:
- m_mag_card_reader = std::make_unique<MagCard::C1231LR>(&m_mag_card_settings);
+ m_serial_device_b = std::make_unique<MagCard::C1231LR>(&m_mag_card_settings);
+ break;
+
+ case KeyOfAvalon:
+ m_serial_device_b = std::make_unique<Triforce::Touchscreen>();
break;
default:
@@ -1121,43 +1126,20 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
}
case GCAMCommand::SerialB:
{
- DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)");
-
if (!validate_data_in_out(1, 0, "SerialB"))
break;
const u32 in_length = *data_in++;
- static constexpr u32 max_packet_size = 0x2f;
-
- // Also accounting for the 2-byte header.
- if (!validate_data_in_out(in_length, max_packet_size + 2, "SerialB"))
+ if (!validate_data_in_out(in_length, 0, "SerialB"))
break;
- if (m_mag_card_reader)
+ if (m_serial_device_b != nullptr)
{
- // Append the data to our buffer.
- const auto prev_size = m_mag_card_in_buffer.size();
- m_mag_card_in_buffer.resize(prev_size + in_length);
- std::ranges::copy(std::span{data_in, in_length}, m_mag_card_in_buffer.data() + prev_size);
-
- // Send and receive data with the magnetic card reader.
- m_mag_card_reader->Process(&m_mag_card_in_buffer, &m_mag_card_out_buffer);
+ m_serial_device_b->WriteRxBytes({data_in, in_length});
}
data_in += in_length;
- const auto out_length = std::min(u32(m_mag_card_out_buffer.size()), max_packet_size);
-
- // Write the 2-byte header.
- data_out[data_offset++] = gcam_command;
- data_out[data_offset++] = u8(out_length);
-
- // Write the data.
- std::copy_n(m_mag_card_out_buffer.data(), out_length, data_out.data() + data_offset);
- data_offset += out_length;
- // Remove the data from our buffer.
- m_mag_card_out_buffer.erase(m_mag_card_out_buffer.begin(),
- m_mag_card_out_buffer.begin() + s32(out_length));
break;
}
case GCAMCommand::JVSIOA:
@@ -2004,6 +1986,33 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
}
}
+ // Update attached serial devices and read data into our response buffer.
+ // This is done regardless of the game having just sent a SerialA/B write.
+ if (m_serial_device_b != nullptr)
+ {
+ m_serial_device_b->Update();
+
+ const auto out_length =
+ std::min(u32(m_serial_device_b->GetTxByteCount()), SERIAL_PORT_MAX_READ_SIZE);
+
+ if (out_length != 0)
+ {
+ // Also accounting for the 2-byte header.
+ if (!validate_data_in_out(0, out_length + 2, "SerialB"))
+ break;
+
+ // Write the 2-byte header.
+ data_out[data_offset++] = GCAMCommand::SerialB;
+ data_out[data_offset++] = u8(out_length);
+
+ const auto out_span = std::span{data_out}.subspan(data_offset, out_length);
+
+ m_serial_device_b->TakeTxBytes(out_span);
+
+ data_offset += out_length;
+ }
+ }
+
data_out[0] = 0x01; // Status code ?
data_out[1] = data_offset - response_header_size; // Length
@@ -2071,14 +2080,9 @@ void CSIDevice_AMBaseboard::DoState(PointerWrap& p)
p.Do(m_ic_write_offset);
p.Do(m_ic_write_size);
- // Magnetic Card Reader
- if (m_mag_card_reader)
- {
- m_mag_card_reader->DoState(p);
-
- p.Do(m_mag_card_in_buffer);
- p.Do(m_mag_card_out_buffer);
- }
+ // Serial B
+ if (m_serial_device_b != nullptr)
+ m_serial_device_b->DoState(p);
// Serial
p.Do(m_wheel_init);
diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h
index 9e130160ce..85a321b424 100644
--- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h
+++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h
@@ -7,6 +7,11 @@
#include "Core/HW/SI/SI.h"
#include "Core/HW/SI/SI_Device.h"
+namespace Triforce
+{
+class SerialDevice;
+}
+
namespace SerialInterface
{
@@ -175,6 +180,11 @@ private:
static constexpr u32 RESPONSE_SIZE = SerialInterfaceManager::BUFFER_SIZE;
+ // This value prevents F-Zero AX mag card breakage.
+ // It's now used for serial port reads in general.
+ // TODO: Verify how the hardware actually works.
+ static constexpr u32 SERIAL_PORT_MAX_READ_SIZE = 0x1f;
+
// Reply has to be delayed due a bug in the parser
std::array<std::array<u8, RESPONSE_SIZE>, 2> m_response_buffers{};
u8 m_current_response_buffer_index = 0;
@@ -196,12 +206,9 @@ private:
// Magnetic Card Reader
MagCard::MagneticCardReader::Settings m_mag_card_settings;
- std::vector<u8> m_mag_card_in_buffer;
- std::vector<u8> m_mag_card_out_buffer;
-
- std::unique_ptr<MagCard::MagneticCardReader> m_mag_card_reader;
+ // Serial B
+ std::unique_ptr<Triforce::SerialDevice> m_serial_device_b;
- // Serial
u32 m_wheel_init = 0;
u32 m_motor_init = 0;
diff --git a/Source/Core/Core/HW/Triforce/SerialDevice.cpp b/Source/Core/Core/HW/Triforce/SerialDevice.cpp
new file mode 100644
index 0000000000..678b4e34c6
--- /dev/null
+++ b/Source/Core/Core/HW/Triforce/SerialDevice.cpp
@@ -0,0 +1,55 @@
+// Copyright 2026 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "Core/HW/Triforce/SerialDevice.h"
+
+#include "Common/Assert.h"
+#include "Common/ChunkFile.h"
+
+namespace Triforce
+{
+
+void SerialDevice::WriteRxBytes(std::span<const u8> bytes)
+{
+#if defined(__cpp_lib_containers_ranges)
+ m_rx_buffer.append_range(bytes);
+#else
+ const auto prev_size = m_rx_buffer.size();
+ m_rx_buffer.resize(prev_size + bytes.size());
+ std::ranges::copy(bytes, m_rx_buffer.begin() + prev_size);
+#endif
+}
+
+void SerialDevice::TakeTxBytes(std::span<u8> bytes)
+{
+ DEBUG_ASSERT(m_tx_buffer.size() >= bytes.size());
+
+ std::copy_n(m_tx_buffer.begin(), bytes.size(), bytes.data());
+ m_tx_buffer.erase(m_tx_buffer.begin(), m_tx_buffer.begin() + bytes.size());
+}
+
+void SerialDevice::ConsumeRxBytes(std::size_t count)
+{
+ DEBUG_ASSERT(m_rx_buffer.size() >= count);
+
+ m_rx_buffer.erase(m_rx_buffer.begin(), m_rx_buffer.begin() + count);
+}
+
+void SerialDevice::WriteTxBytes(std::span<const u8> bytes)
+{
+#if defined(__cpp_lib_containers_ranges)
+ m_tx_buffer.append_range(bytes);
+#else
+ const auto prev_size = m_tx_buffer.size();
+ m_tx_buffer.resize(prev_size + bytes.size());
+ std::ranges::copy(bytes, m_tx_buffer.begin() + prev_size);
+#endif
+}
+
+void SerialDevice::DoState(PointerWrap& p)
+{
+ p.Do(m_rx_buffer);
+ p.Do(m_tx_buffer);
+}
+
+} // namespace Triforce
diff --git a/Source/Core/Core/HW/Triforce/SerialDevice.h b/Source/Core/Core/HW/Triforce/SerialDevice.h
new file mode 100644
index 0000000000..e3aba79635
--- /dev/null
+++ b/Source/Core/Core/HW/Triforce/SerialDevice.h
@@ -0,0 +1,60 @@
+// Copyright 2026 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <deque>
+#include <span>
+#include <vector>
+
+#include "Common/CommonTypes.h"
+
+class PointerWrap;
+
+namespace Triforce
+{
+
+// Base class for devices that attach to the SerialA/B ports on the Triforce Baseboard.
+class SerialDevice
+{
+public:
+ SerialDevice() = default;
+ virtual ~SerialDevice() = default;
+
+ SerialDevice(const SerialDevice&) = delete;
+ SerialDevice& operator=(const SerialDevice&) = delete;
+ SerialDevice(SerialDevice&&) = delete;
+ SerialDevice& operator=(SerialDevice&&) = delete;
+
+ void WriteRxBytes(std::span<const u8> bytes);
+
+ std::size_t GetTxByteCount() const { return m_tx_buffer.size(); }
+
+ // Caller should ensure GetTxByteCount() >= byte.size().
+ void TakeTxBytes(std::span<u8> bytes);
+
+ virtual void Update() = 0;
+
+ virtual void DoState(PointerWrap& p);
+
+protected:
+ std::span<const u8> GetRxByteSpan() const { return m_rx_buffer; }
+
+ void ConsumeRxBytes(std::size_t count);
+
+ void WriteTxByte(u8 byte) { m_tx_buffer.emplace_back(byte); }
+ void WriteTxBytes(std::span<const u8> bytes);
+
+private:
+ // The stream of bytes from the baseboard to the device.
+ // FYI: Current device implementations tend to empty the entire buffer in one go,
+ // so std::vector's O(n) erase-at-front should be a non-issue.
+ // The contiguous data of std::vector is convenient for packet parsing.
+ std::vector<u8> m_rx_buffer;
+
+ // The stream of bytes from the device to the baseboard.
+ // It may be read in chunks so std::vector would be less appropriate here.
+ std::deque<u8> m_tx_buffer;
+};
+
+} // namespace Triforce
diff --git a/Source/Core/Core/HW/Triforce/Touchscreen.cpp b/Source/Core/Core/HW/Triforce/Touchscreen.cpp
new file mode 100644
index 0000000000..eab93b741e
--- /dev/null
+++ b/Source/Core/Core/HW/Triforce/Touchscreen.cpp
@@ -0,0 +1,71 @@
+// Copyright 2026 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "Core/HW/Triforce/Touchscreen.h"
+
+#include <numeric>
+
+#include "Common/BitUtils.h"
+#include "Common/Logging/Log.h"
+#include "Core/HW/GCPad.h"
+#include "InputCommon/GCPadStatus.h"
+
+namespace
+{
+#pragma pack(push, 1)
+// This is the "SmartSet Data Protocol".
+struct SmartSetDataPacket
+{
+ u8 lead_in = 0x55;
+ u8 cmd = 0x54; // Always 0x54.
+ u8 status = 0xff; // Seems to be ignored by the game.
+ u16 x{}; // Little endian (0-4095).
+ u16 y{}; // Little endian (0-4095).
+ u8 pressure{};
+ u8 unused{};
+ u8 checksum{}; // All previous bytes + 0xaa.
+};
+#pragma pack(pop)
+static_assert(sizeof(SmartSetDataPacket) == 10);
+} // namespace
+
+namespace Triforce
+{
+
+void Touchscreen::Update()
+{
+ if (const auto input = GetRxByteSpan(); !input.empty())
+ {
+ // The Key of Avalon doesn't write to the device, it only reads.
+ WARN_LOG_FMT(AMMEDIABOARD, "Unexpected write of {} bytes to touchscreen.", input.size());
+ ConsumeRxBytes(input.size());
+ }
+
+ // Our touchscreen conveniently produces exactly one packet every Update cycle.
+ // I'm guessing the real hardware doesn't produce ~60hz input perfectly in-sync with SI updates,
+ // but Avalon doesn't seem to mind.
+
+ // We currently feed the touch screen from c-stick and right-trigger just to make it usable.
+ // TODO: Expose it in a better way.
+
+ const auto pad_status = Pad::GetStatus(0);
+
+ // For reference, the game does something like this to scale the values from 0-4095.
+ // Note the offsets of 4.
+ // Someone who cares more might want to compensate for that.
+ //
+ // x = s32(0.15625f * x) + 4;
+ // y = s32(480.f - (0.1171875f * y)) + 4;
+
+ SmartSetDataPacket packet{
+ .x = Common::ExpandValue(u16(pad_status.substickX), 4),
+ .y = Common::ExpandValue(u16(pad_status.substickY), 4),
+ .pressure = pad_status.triggerRight,
+ };
+
+ packet.checksum = std::accumulate(&packet.lead_in, &packet.checksum, u8{0xaa});
+
+ WriteTxBytes(Common::AsU8Span(packet));
+}
+
+} // namespace Triforce
diff --git a/Source/Core/Core/HW/Triforce/Touchscreen.h b/Source/Core/Core/HW/Triforce/Touchscreen.h
new file mode 100644
index 0000000000..1be3382572
--- /dev/null
+++ b/Source/Core/Core/HW/Triforce/Touchscreen.h
@@ -0,0 +1,18 @@
+// Copyright 2026 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "Core/HW/Triforce/SerialDevice.h"
+
+namespace Triforce
+{
+
+// The touchscreen input used by The Key of Avalon games.
+class Touchscreen final : public SerialDevice
+{
+public:
+ void Update() override;
+};
+
+} // namespace Triforce
diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp
index c41dc9e46b..5b284d1060 100644
--- a/Source/Core/Core/State.cpp
+++ b/Source/Core/Core/State.cpp
@@ -95,7 +95,7 @@ struct CompressAndDumpStateArgs
static Common::WorkQueueThreadSP<CompressAndDumpStateArgs> s_compress_and_dump_thread;
// Don't forget to increase this after doing changes on the savestate system
-constexpr u32 STATE_VERSION = 179; // Last changed in PR 14406
+constexpr u32 STATE_VERSION = 180; // Last changed in PR 14452
// Increase this if the StateExtendedHeader definition changes
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217
diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index c6c9c485dd..7d63c5a4b9 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -341,6 +341,8 @@
<ClInclude Include="Core\HW\Sram.h" />
<ClInclude Include="Core\HW\StreamADPCM.h" />
<ClInclude Include="Core\HW\SystemTimers.h" />
+ <ClInclude Include="Core\HW\Triforce\SerialDevice.h" />
+ <ClInclude Include="Core\HW\Triforce\Touchscreen.h" />
<ClInclude Include="Core\HW\VideoInterface.h" />
<ClInclude Include="Core\HW\WII_IPC.h" />
<ClInclude Include="Core\HW\Wiimote.h" />
@@ -1041,6 +1043,8 @@
<ClCompile Include="Core\HW\Sram.cpp" />
<ClCompile Include="Core\HW\StreamADPCM.cpp" />
<ClCompile Include="Core\HW\SystemTimers.cpp" />
+ <ClCompile Include="Core\HW\Triforce\SerialDevice.cpp" />
+ <ClCompile Include="Core\HW\Triforce\Touchscreen.cpp" />
<ClCompile Include="Core\HW\VideoInterface.cpp" />
<ClCompile Include="Core\HW\WII_IPC.cpp" />
<ClCompile Include="Core\HW\Wiimote.cpp" />