diff --git a/core/hw/naomi/naomi.cpp b/core/hw/naomi/naomi.cpp
index 32ed12b12..00630562e 100644
--- a/core/hw/naomi/naomi.cpp
+++ b/core/hw/naomi/naomi.cpp
@@ -578,6 +578,7 @@ void naomi_reg_Reset(bool hard)
static u8 aw_maple_devs;
static u64 coin_chute_time[4];
+static u8 ffbOuput;
u32 libExtDevice_ReadMem_A0_006(u32 addr,u32 size) {
addr &= 0x7ff;
@@ -634,7 +635,8 @@ u32 libExtDevice_ReadMem_A0_006(u32 addr,u32 size) {
case 0x288:
// ??? Dolphin Blue
return 0;
-
+ case 0x28c:
+ return ffbOuput;
}
INFO_LOG(NAOMI, "Unhandled read @ %x sz %d", addr, size);
return 0xFF;
@@ -652,7 +654,12 @@ void libExtDevice_WriteMem_A0_006(u32 addr,u32 data,u32 size) {
case 0x288:
// ??? Dolphin Blue
return;
- //case 0x28C: // Wheel force feedback?
+ case 0x28C: // Wheel force feedback
+ // bit 0 direction (0 pos, 1 neg)
+ // bit 1-4 strength
+ ffbOuput = data;
+ DEBUG_LOG(NAOMI, "AW output %02x", data);
+ return;
default:
break;
}
diff --git a/core/hw/naomi/naomi_cart.cpp b/core/hw/naomi/naomi_cart.cpp
index d57d15543..b3ac0c1ad 100644
--- a/core/hw/naomi/naomi_cart.cpp
+++ b/core/hw/naomi/naomi_cart.cpp
@@ -39,6 +39,7 @@
#include "serialize.h"
#include "card_reader.h"
#include "naomi_flashrom.h"
+#include "network/net_serial_maxspeed.h"
Cartridge *CurrentCartridge;
bool bios_loaded = false;
@@ -47,6 +48,8 @@ char naomi_game_id[33];
InputDescriptors *NaomiGameInputs;
u8 *naomi_default_eeprom;
+MaxSpeedNetPipe maxSpeedNetPipe;
+
extern MemChip *sys_rom;
static bool loadBios(const char *filename, Archive *child_archive, Archive *parent_archive, int region)
@@ -583,6 +586,11 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
card_reader::initialDCardReader.init();
initdFFBInit();
}
+ else if (gameId == "MAXIMUM SPEED")
+ {
+ maxSpeedNetPipe.init();
+ configure_maxspeed_flash(config::NetworkEnable, config::ActAsServer);
+ }
}
else
NOTICE_LOG(NAOMI, "NAOMI GAME ID [%s]", naomi_game_id);
@@ -607,6 +615,7 @@ void naomi_cart_Close()
CurrentCartridge = nullptr;
NaomiGameInputs = nullptr;
bios_loaded = false;
+ maxSpeedNetPipe.shutdown();
}
int naomi_cart_GetPlatform(const char *path)
diff --git a/core/hw/naomi/naomi_flashrom.cpp b/core/hw/naomi/naomi_flashrom.cpp
index 1a4412d05..0a8ee292e 100644
--- a/core/hw/naomi/naomi_flashrom.cpp
+++ b/core/hw/naomi/naomi_flashrom.cpp
@@ -264,3 +264,47 @@ void configure_naomi_eeprom(const RomBootID *bootId)
if (config::ForceFreePlay)
write_naomi_eeprom(9, 27 - 1);
}
+
+static u32 aw_crc32(const void *data, size_t len)
+{
+ constexpr u32 POLY = 0xEDB88320;
+ const u8 *buffer = (const u8 *)data;
+ u32 crc = -1;
+
+ while (len--)
+ {
+ crc = crc ^ *buffer++;
+ for (int bit = 0; bit < 8; bit++)
+ {
+ if (crc & 1)
+ crc = (crc >> 1) ^ POLY;
+ else
+ crc = crc >> 1;
+ }
+ }
+ return ~crc;
+}
+
+void configure_maxspeed_flash(bool enableNetwork, bool master)
+{
+ if (enableNetwork)
+ {
+ sys_nvmem->data[0x3358] = 0;
+ sys_nvmem->data[0x46ac] = 0;
+ sys_nvmem->data[0x335c] = !master;
+ sys_nvmem->data[0x46b0] = !master;
+ }
+ else
+ {
+ sys_nvmem->data[0x3358] = 1;
+ sys_nvmem->data[0x46ac] = 1;
+ }
+ u32 crc = aw_crc32(&sys_nvmem->data[0x2200], 0x1354);
+ *(u32 *)&sys_nvmem->data[0x34] = crc;
+ *(u32 *)&sys_nvmem->data[0x38] = crc;
+ *(u32 *)&sys_nvmem->data[0x84] = crc;
+ *(u32 *)&sys_nvmem->data[0x88] = crc;
+ crc = aw_crc32(&sys_nvmem->data[0x20], 0x44);
+ *(u32 *)&sys_nvmem->data[0x64] = crc;
+ *(u32 *)&sys_nvmem->data[0xb4] = crc;
+}
diff --git a/core/hw/naomi/naomi_flashrom.h b/core/hw/naomi/naomi_flashrom.h
index 218681ff6..e914a7b0a 100644
--- a/core/hw/naomi/naomi_flashrom.h
+++ b/core/hw/naomi/naomi_flashrom.h
@@ -25,3 +25,4 @@
void write_naomi_flash(u32 addr, u8 value);
void write_naomi_eeprom(u32 offset, u8 value);
void configure_naomi_eeprom(const RomBootID *bootId);
+void configure_maxspeed_flash(bool enableNetwork, bool master);
diff --git a/core/hw/sh4/sh4_mem.cpp b/core/hw/sh4/sh4_mem.cpp
index b4a9cfd6d..2555bd4fb 100644
--- a/core/hw/sh4/sh4_mem.cpp
+++ b/core/hw/sh4/sh4_mem.cpp
@@ -189,7 +189,7 @@ void mem_Reset(bool hard)
//Reset registers
sh4_area0_Reset(hard);
- sh4_mmr_reset(true);
+ sh4_mmr_reset(hard);
}
void mem_Term()
diff --git a/core/hw/sh4/sh4_mmr.cpp b/core/hw/sh4/sh4_mmr.cpp
index 74504c291..e4480b526 100644
--- a/core/hw/sh4/sh4_mmr.cpp
+++ b/core/hw/sh4/sh4_mmr.cpp
@@ -781,39 +781,37 @@ void sh4_mmr_init()
void sh4_mmr_reset(bool hard)
{
- if (hard)
- {
- for (auto& reg : CCN)
- reg.reset();
- for (auto& reg : UBC)
- reg.reset();
- for (auto& reg : BSC)
- reg.reset();
- for (auto& reg : DMAC)
- reg.reset();
- for (auto& reg : CPG)
- reg.reset();
- for (auto& reg : RTC)
- reg.reset();
- for (auto& reg : INTC)
- reg.reset();
- for (auto& reg : TMU)
- reg.reset();
- for (auto& reg : SCI)
- reg.reset();
- for (auto& reg : SCIF)
- reg.reset();
- }
+ for (auto& reg : CCN)
+ reg.reset();
+ for (auto& reg : UBC)
+ reg.reset();
+ for (auto& reg : BSC)
+ reg.reset();
+ for (auto& reg : DMAC)
+ reg.reset();
+ for (auto& reg : CPG)
+ reg.reset();
+ for (auto& reg : RTC)
+ reg.reset();
+ for (auto& reg : INTC)
+ reg.reset();
+ for (auto& reg : TMU)
+ reg.reset();
+ for (auto& reg : SCI)
+ reg.reset();
+ for (auto& reg : SCIF)
+ reg.reset();
+
OnChipRAM = {};
//Reset register values
- bsc_reset(hard);
- ccn_reset(hard);
+ bsc_reset(true);
+ ccn_reset(true);
cpg_reset();
dmac_reset();
intc_reset();
rtc_reset();
serial_reset(hard);
- tmu_reset(hard);
+ tmu_reset(true);
ubc_reset();
MMU_reset();
diff --git a/core/network/net_serial_maxspeed.h b/core/network/net_serial_maxspeed.h
new file mode 100644
index 000000000..1591f5783
--- /dev/null
+++ b/core/network/net_serial_maxspeed.h
@@ -0,0 +1,250 @@
+/*
+ Copyright 2022 flyinghead
+
+ This file is part of Flycast.
+
+ Flycast is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ Flycast is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Flycast. If not, see .
+ */
+#include "hw/sh4/modules/modules.h"
+#include "net_platform.h"
+#include "cfg/option.h"
+#include "miniupnp.h"
+#include "hw/sh4/sh4_sched.h"
+#include
+
+struct MaxSpeedNetPipe : public SerialPipe
+{
+ class Exception : public FlycastException
+ {
+ public:
+ Exception(const std::string& reason) : FlycastException(reason) {}
+ };
+
+ // Serial TX
+ void write(u8 data) override
+ {
+ txBuffer.push_back(data);
+ parseMaxspeedPacket(data);
+ if (txPacketSize != 0 && txBuffer.size() == txPacketSize)
+ {
+ sendto(sock, &txBuffer[0], txPacketSize, 0, (const sockaddr *)&peerAddress, sizeof(peerAddress));
+ txBuffer.clear();
+ }
+ }
+
+ // RX buffer Size
+ int available() override {
+ poll();
+ return rxBuffer.size();
+ }
+
+ // Serial RX
+ u8 read() override
+ {
+ poll();
+ if (rxBuffer.empty()) {
+ WARN_LOG(NETWORK, "NetPipe: empty read");
+ return 0;
+ }
+ u8 b = rxBuffer.front();
+ rxBuffer.pop_front();
+
+ return b;
+ }
+
+ ~MaxSpeedNetPipe() {
+ shutdown();
+ }
+
+ bool init()
+ {
+ if (!config::NetworkEnable)
+ return false;
+#ifdef _WIN32
+ WSADATA wsaData;
+ if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0)
+ {
+ ERROR_LOG(NETWORK, "WSAStartup failed. errno=%d", get_last_error());
+ throw Exception("WSAStartup failed");
+ }
+#endif
+ if (config::EnableUPnP)
+ {
+ miniupnp.Init();
+ miniupnp.AddPortMapping(config::LocalPort, true);
+ }
+
+ createSocket();
+
+ return true;
+ }
+
+ void shutdown() {
+ if (VALID(sock))
+ closesocket(sock);
+ sock = INVALID_SOCKET;
+ }
+
+private:
+ void parseMaxspeedPacket(u8 b)
+ {
+ switch (txPacketState)
+ {
+ case 0:
+ if (b == 0x4d)
+ txPacketState = 1;
+ else
+ txPacketSize = 1;
+ break;
+ case 1:
+ if (b == 0x41)
+ txPacketState = 2;
+ else {
+ txPacketState = 0;
+ txPacketSize = 2;
+ }
+ break;
+ case 2:
+ if (b == 0x58)
+ txPacketState = 3;
+ else {
+ txPacketState = 0;
+ txPacketSize = 3;
+ }
+ break;
+ case 3:
+ txPacketSize = b;
+ if (txPacketSize < 3)
+ {
+ // invalid size
+ txPacketState = 0;
+ txPacketSize = 4;
+ }
+ else
+ {
+ txPacketState = 4;
+ txPacketSize += 4;
+ }
+ break;
+ case 4: // payload then crc (lsb), crc (msb), 1
+ if (txPacketSize == txBuffer.size())
+ txPacketState = 0;
+ break;
+ }
+ }
+
+ void poll()
+ {
+ if (lastPoll == sh4_sched_now64())
+ return;
+ lastPoll = sh4_sched_now64();
+ u8 data[0x100];
+ sockaddr_in addr;
+ while (true)
+ {
+ socklen_t len = sizeof(addr);
+ int rc = recvfrom(sock, (char *)data, sizeof(data), 0, (sockaddr *)&addr, &len);
+ if (rc == -1)
+ {
+ int error = get_last_error();
+ if (error == L_EWOULDBLOCK || error == L_EAGAIN)
+ break;
+#ifdef _WIN32
+ if (error == WSAECONNRESET)
+ // Happens if the previous send resulted in an ICMP Port Unreachable message
+ break;
+#endif
+ throw Exception("Receive error: errno " + std::to_string(error));
+ }
+ rxBuffer.insert(rxBuffer.end(), &data[0], &data[rc]);
+
+ if (peerAddress.sin_addr.s_addr == INADDR_BROADCAST
+ && (addr.sin_port != htons(config::LocalPort) || !is_local_address(addr.sin_addr.s_addr)))
+ {
+ peerAddress.sin_addr.s_addr = addr.sin_addr.s_addr;
+ peerAddress.sin_port = addr.sin_port;
+ }
+ }
+ }
+
+ void createSocket()
+ {
+ sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock == INVALID_SOCKET)
+ {
+ ERROR_LOG(NETWORK, "Socket creation failed: errno %d", get_last_error());
+ throw Exception("Socket creation failed");
+ }
+ int option = 1;
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&option, sizeof(option));
+
+ sockaddr_in serveraddr{};
+ serveraddr.sin_family = AF_INET;
+ serveraddr.sin_port = htons(config::LocalPort);
+
+ if (::bind(sock, (sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
+ {
+ ERROR_LOG(NETWORK, "NaomiServer: bind() failed. errno=%d", get_last_error());
+ closesocket(sock);
+
+ throw Exception("Socket bind failed");
+ }
+ set_non_blocking(sock);
+
+ // Allow broadcast packets to be sent
+ int broadcast = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)) == -1)
+ WARN_LOG(NETWORK, "setsockopt(SO_BROADCAST) failed. errno=%d", get_last_error());
+
+ peerAddress.sin_family = AF_INET;
+ peerAddress.sin_addr.s_addr = INADDR_BROADCAST;
+ peerAddress.sin_port = htons(config::LocalPort);
+ if (!config::NetworkServer.get().empty())
+ {
+ auto pos = config::NetworkServer.get().find_last_of(':');
+ std::string server;
+ if (pos != std::string::npos)
+ {
+ peerAddress.sin_port = htons(atoi(config::NetworkServer.get().substr(pos + 1).c_str()));
+ server = config::NetworkServer.get().substr(0, pos);
+ }
+ else
+ server = config::NetworkServer;
+ addrinfo *resultAddr;
+ if (getaddrinfo(server.c_str(), 0, nullptr, &resultAddr))
+ WARN_LOG(NETWORK, "Server %s is unknown", server.c_str());
+ else
+ {
+ for (addrinfo *ptr = resultAddr; ptr != nullptr; ptr = ptr->ai_next)
+ if (ptr->ai_family == AF_INET)
+ {
+ peerAddress.sin_addr.s_addr = ((sockaddr_in *)ptr->ai_addr)->sin_addr.s_addr;
+ break;
+ }
+ freeaddrinfo(resultAddr);
+ }
+ }
+
+ serial_setPipe(this);
+ }
+
+ sock_t sock = INVALID_SOCKET;
+ MiniUPnP miniupnp;
+ std::deque rxBuffer;
+ std::vector txBuffer;
+ u32 txPacketSize = 0;
+ int txPacketState = 0;
+ sockaddr_in peerAddress{};
+ u64 lastPoll = 0;
+};