mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-03-18 19:32:41 +00:00
Merge pull request #14452 from jordan-woyak/triforce-avalon-touchscreen
Triforce: Implement The Key of Avalon's touchscreen.
This commit is contained in:
commit
f0096310bf
@ -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
|
||||
|
||||
@ -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);
|
||||
WriteTxByte(START_OF_TEXT); // Not included in the checksum.
|
||||
|
||||
auto* out_ptr = write_buffer.data() + prev_buffer_size;
|
||||
u8 packet_checksum = 0;
|
||||
const auto lead_in = std::to_array<u8>({
|
||||
payload_size,
|
||||
GetCurrentCommand(),
|
||||
GetPositionValue(),
|
||||
u8(m_status.p),
|
||||
u8(m_status.s),
|
||||
});
|
||||
WriteTxBytes(lead_in);
|
||||
|
||||
const auto write_and_checksum = [&](u8 value) {
|
||||
*(out_ptr++) = value;
|
||||
packet_checksum ^= value;
|
||||
};
|
||||
WriteTxBytes(m_command_payload);
|
||||
|
||||
// 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));
|
||||
WriteTxByte(END_OF_TEXT);
|
||||
|
||||
// Write the payload.
|
||||
std::ranges::for_each(m_command_payload, write_and_checksum);
|
||||
|
||||
// Write the footer.
|
||||
write_and_checksum(END_OF_TEXT);
|
||||
*(out_ptr++) = packet_checksum;
|
||||
|
||||
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);
|
||||
|
||||
@ -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..
|
||||
void Update() override;
|
||||
|
||||
// read = MagCard <-- Baseboard.
|
||||
// write = Magcard --> Baseboard.
|
||||
void Process(std::vector<u8>* read, std::vector<u8>* write);
|
||||
|
||||
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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
// Serial B
|
||||
std::unique_ptr<Triforce::SerialDevice> m_serial_device_b;
|
||||
|
||||
std::unique_ptr<MagCard::MagneticCardReader> m_mag_card_reader;
|
||||
|
||||
// Serial
|
||||
u32 m_wheel_init = 0;
|
||||
|
||||
u32 m_motor_init = 0;
|
||||
|
||||
55
Source/Core/Core/HW/Triforce/SerialDevice.cpp
Normal file
55
Source/Core/Core/HW/Triforce/SerialDevice.cpp
Normal file
@ -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
|
||||
60
Source/Core/Core/HW/Triforce/SerialDevice.h
Normal file
60
Source/Core/Core/HW/Triforce/SerialDevice.h
Normal file
@ -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
|
||||
71
Source/Core/Core/HW/Triforce/Touchscreen.cpp
Normal file
71
Source/Core/Core/HW/Triforce/Touchscreen.cpp
Normal file
@ -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
|
||||
18
Source/Core/Core/HW/Triforce/Touchscreen.h
Normal file
18
Source/Core/Core/HW/Triforce/Touchscreen.h
Normal file
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user