naomi: network code for model 3 comm board

This commit is contained in:
Flyinghead 2020-04-14 17:43:11 +02:00
parent 6215623640
commit a41a81f5dd
15 changed files with 981 additions and 104 deletions

View File

@ -12,7 +12,7 @@ RZDCY_MODULES := cfg/ hw/arm7/ hw/aica/ hw/holly/ hw/ hw/gdrom/ hw/maple/ \
hw/mem/ hw/pvr/ hw/sh4/ hw/sh4/interpr/ hw/sh4/modules/ plugins/ profiler/ oslib/ \
hw/extdev/ hw/arm/ hw/naomi/ imgread/ ./ deps/coreio/ deps/zlib/ deps/chdr/ deps/crypto/ \
deps/libelf/ deps/chdpsr/ arm_emitter/ rend/ reios/ deps/xbrz/ \
deps/libzip/ deps/imgui/ archive/ input/ log/ wsi/
deps/libzip/ deps/imgui/ archive/ input/ log/ wsi/ network/
ifndef NOT_ARM
RZDCY_MODULES += rec-ARM/

View File

@ -1,56 +0,0 @@
# Reicast Modem Configuration
You need to assign an IP address from your local LAN to the Dreamcast. Do not use the address of the computer on which reicast is running.
Add it to your `emu.cfg` file in the `[network]` section.
For example:
```
[network]
IP = 192.168.1.99
```
Make sure this address is not being used on your network. Ping it just in case...
## Windows (7)
1. Install TAP-Windows
Navigate to [OpenVPN Community Downloads](https://openvpn.net/index.php/download/community-downloads.html) and scroll all the way to the bottom. Download and install the NDIS 6 version.
2. In your Network Connections you should see a new network with a TAP-Windows Adapter V9 device.
*Make sure it is enabled.*
3. Run `reicast.exe` as an administrator (right-click on the program and select "Run as administrator")
4. That's it! Now you need to configure internet access in the Dreamcast itself.
## Linux
All these commands must be run as root.
1. Create the IP tunnel. If your username is not joe, put your username there.
```
# ip tuntap add mode tun user joe
```
2. Bring the interface up
```
# ip link set tun0 up
```
3. Add a route to this IP.
```
# ip route add 192.168.1.99/32 dev tun0
```
Replace 192.168.1.99 by the IP set in your emu.cfg file.
4. Enable proxy ARP for all interfaces.
```
# echo '1' >/proc/sys/net/ipv4/conf/all/proxy_arp
```
5. Follow up to the next section
## Dreamcast
You need to configure the ISP settings in the Dreamcast. Some games allow to do it within the game itself and will save the configuration. Other games can then use it.
You can put any name, password and phone number in the ISP settings as they are ignored. Do not change or set any other option.
Some games require a DMZ or port forwarding to be configured on your Internet router. Refer to the [Dreamcast Live web site](https://www.dreamcastlive.net/connection-guide.html) for details about each game.

View File

@ -25,7 +25,7 @@
#include <cstdio>
#include <cerrno>
#include "net_platform.h"
#include "network/net_platform.h"
extern "C" {
#include <pico_stack.h>
@ -44,6 +44,7 @@ static int qname_len;
void get_host_by_name(const char *host, struct pico_ip4 dnsaddr)
{
DEBUG_LOG(MODEM, "get_host_by_name: %s", host);
if (!VALID(sock_fd))
{
sock_fd = socket(AF_INET , SOCK_DGRAM , IPPROTO_UDP);
@ -149,4 +150,4 @@ char *read_name(char *reader, char *buffer, int *count)
return name;
}
#endif // !COMPILER_VC_OR_CLANG_WIN32
#endif // !COMPILER_VC_OR_CLANG_WIN32

View File

@ -36,7 +36,7 @@ extern "C" {
#include <pico_tcp.h>
}
#include "net_platform.h"
#include "network/net_platform.h"
#include "types.h"
#include "cfg/cfg.h"
@ -165,31 +165,6 @@ int read_pico()
}
}
void set_non_blocking(sock_t fd)
{
#ifndef _WIN32
fcntl(fd, F_SETFL, O_NONBLOCK);
#else
u_long optl = 1;
ioctlsocket(fd, FIONBIO, &optl);
#endif
}
void set_tcp_nodelay(sock_t fd)
{
int optval = 1;
socklen_t optlen = sizeof(optval);
#if defined(_WIN32)
struct protoent *tcp_proto = getprotobyname("TCP");
setsockopt(fd, tcp_proto->p_proto, TCP_NODELAY, (const char *)&optval, optlen);
#elif !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__NetBSD__)
setsockopt(fd, SOL_TCP, TCP_NODELAY, (const void *)&optval, optlen);
#else
struct protoent *tcp_proto = getprotobyname("TCP");
setsockopt(fd, tcp_proto->p_proto, TCP_NODELAY, &optval, optlen);
#endif
}
static void read_from_dc_socket(pico_socket *pico_sock, sock_t nat_sock)
{
char buf[1510];
@ -716,8 +691,7 @@ static void *pico_thread_func(void *)
pico_string_to_ipv4("192.168.167.1", &ipaddr.addr);
pico_ppp_set_ip(ppp, ipaddr);
std::string dns_ip = cfgLoadStr("network", "DNS", "46.101.91.123"); // Dreamcast Live DNS
pico_string_to_ipv4(dns_ip.c_str(), &dnsaddr.addr);
pico_string_to_ipv4(settings.network.dns.c_str(), &dnsaddr.addr);
pico_ppp_set_dns1(ppp, dnsaddr);
pico_udp_socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &udp_callback);

View File

@ -13,9 +13,12 @@
#include "naomi.h"
#include "naomi_cart.h"
#include "naomi_regs.h"
#include "naomi_m3comm.h"
//#define NAOMI_COMM
static NaomiM3Comm m3comm;
static const u32 BoardID=0x980055AA;
u32 GSerialBuffer=0,BSerialBuffer=0;
int GBufPos=0,BBufPos=0;
@ -395,25 +398,31 @@ void naomi_process(u32 command, u32 offsetl, u32 parameterl, u32 parameterh)
}
}
u32 ReadMem_naomi(u32 Addr, u32 sz)
u32 ReadMem_naomi(u32 address, u32 size)
{
verify(sz!=1);
verify(size != 1);
if (unlikely(CurrentCartridge == NULL))
{
INFO_LOG(NAOMI, "called without cartridge");
return 0xFFFF;
}
return CurrentCartridge->ReadMem(Addr, sz);
if (address >= NAOMI_COMM2_CTRL_addr && address <= NAOMI_COMM2_STATUS1_addr)
return m3comm.ReadMem(address, size);
else
return CurrentCartridge->ReadMem(address, size);
}
void WriteMem_naomi(u32 Addr, u32 data, u32 sz)
void WriteMem_naomi(u32 address, u32 data, u32 size)
{
if (unlikely(CurrentCartridge == NULL))
{
INFO_LOG(NAOMI, "called without cartridge");
return;
}
CurrentCartridge->WriteMem(Addr, data, sz);
if (address >= NAOMI_COMM2_CTRL_addr && address <= NAOMI_COMM2_STATUS1_addr)
m3comm.WriteMem(address, data, size);
else
CurrentCartridge->WriteMem(address, data, size);
}
//Dma Start
@ -429,16 +438,11 @@ void Naomi_DmaStart(u32 addr, u32 data)
if (SB_GDST==1)
{
verify(1 == SB_GDDIR );
DEBUG_LOG(NAOMI, "NAOMI-DMA start addr %08X len %d", SB_GDSTAR, SB_GDLEN);
SB_GDSTARD = SB_GDSTAR + SB_GDLEN;
SB_GDLEND = SB_GDLEN;
SB_GDST = 0;
if (CurrentCartridge != NULL)
if (!m3comm.DmaStart(addr, data) && CurrentCartridge != NULL)
{
u32 len = SB_GDLEN;
DEBUG_LOG(NAOMI, "NAOMI-DMA start addr %08X len %d", SB_GDSTAR, SB_GDLEN);
verify(1 == SB_GDDIR);
u32 len = (SB_GDLEN + 30) & ~30;
u32 offset = 0;
while (len > 0)
{
@ -455,7 +459,9 @@ void Naomi_DmaStart(u32 addr, u32 data)
offset += block_len;
}
}
SB_GDSTARD = SB_GDSTAR + SB_GDLEN;
SB_GDLEND = SB_GDLEN;
SB_GDST = 0;
asic_RaiseInterrupt(holly_GDROM_DMA);
}
}
@ -522,7 +528,7 @@ void naomi_reg_Init()
void naomi_reg_Term()
{
#ifdef NAOMI_COMM
#ifdef NAOMI_COMM
if (CommSharedMem)
{
UnmapViewOfFile(CommSharedMem);
@ -531,8 +537,10 @@ void naomi_reg_Term()
{
CloseHandle(CommMapFile);
}
#endif
#endif
m3comm.closeNetwork();
}
void naomi_reg_Reset(bool hard)
{
sb_rio_register(SB_GDST_addr, RIO_WF, 0, &Naomi_DmaStart);
@ -562,6 +570,7 @@ void naomi_reg_Reset(bool hard)
reg_dimm_parameterl = 0;
reg_dimm_parameterh = 0;
reg_dimm_status = 0x11;
m3comm.closeNetwork();
}
static u8 aw_maple_devs;

View File

@ -219,6 +219,8 @@ static void naomi_cart_LoadZip(const char *filename)
u32 region_flag = settings.dreamcast.region;
if (region_flag > game->region_flag)
region_flag = game->region_flag;
if (game->region_flag == REGION_EXPORT_ONLY)
region_flag = REGION_EXPORT;
if (!naomi_LoadBios(bios, archive.get(), parent_archive.get(), region_flag))
{
WARN_LOG(NAOMI, "Warning: Region %d bios not found in %s", region_flag, bios);

View File

@ -0,0 +1,300 @@
/*
Created on: Mar 15, 2020
Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
#include "naomi_m3comm.h"
#include "naomi_regs.h"
#include "hw/holly/sb.h"
#include "hw/sh4/sh4_mem.h"
#include <chrono>
static inline u16 swap16(u16 w)
{
return (w >> 8) | (w << 8);
}
void NaomiM3Comm::closeNetwork()
{
network_stopping = true;
network.shutdown();
if (thread && thread->joinable())
thread->join();
}
void NaomiM3Comm::connectNetwork()
{
if (network.startNetwork())
{
slot_count = network.slotCount();
slot_id = network.slotId();
connectedState(true);
}
else
{
connectedState(false);
network_stopping = true;
network.shutdown();
}
}
void NaomiM3Comm::receiveNetwork()
{
const u32 slot_size = swap16(*(u16*)&m68k_ram[0x204]);
const u32 packet_size = slot_size * slot_count;
u8 buf[packet_size];
if (network.receive(buf, packet_size))
{
*(u16*)&comm_ram[6] = swap16(network.packetNumber());
std::unique_lock<std::mutex> lock(mem_mutex);
memcpy(&comm_ram[0x100 + slot_size], buf, packet_size);
}
}
void NaomiM3Comm::sendNetwork()
{
if (network.hasToken())
{
const u32 packet_size = swap16(*(u16*)&m68k_ram[0x204]) * slot_count;
std::unique_lock<std::mutex> lock(mem_mutex);
network.send(&comm_ram[0x100], packet_size);
*(u16*)&comm_ram[6] = swap16(network.packetNumber());
}
}
NaomiM3Comm::~NaomiM3Comm()
{
closeNetwork();
network.terminate();
}
u32 NaomiM3Comm::ReadMem(u32 address, u32 size)
{
switch (address & 255)
{
case NAOMI_COMM2_CTRL_addr & 255:
//DEBUG_LOG(NAOMI, "NAOMI_COMM2_CTRL read");
return comm_ctrl;
case NAOMI_COMM2_OFFSET_addr & 255:
//DEBUG_LOG(NAOMI, "NAOMI_COMM2_OFFSET read");
return comm_offset;
case NAOMI_COMM2_DATA_addr & 255:
{
u16 value;
if (comm_ctrl & 1)
value = *(u16*)&m68k_ram[comm_offset];
else
// TODO u16 *commram = (u16*)membank("comm_ram")->base();
value = *(u16*)&comm_ram[comm_offset];
value = swap16(value);
DEBUG_LOG(NAOMI, "NAOMI_COMM2_DATA %s read @ %04x: %x", (comm_ctrl & 1) ? "m68k ram" : "comm ram", comm_offset, value);
comm_offset += 2;
return value;
}
case NAOMI_COMM2_STATUS0_addr & 255:
DEBUG_LOG(NAOMI, "NAOMI_COMM2_STATUS0 read %x", comm_status0);
return comm_status0;
case NAOMI_COMM2_STATUS1_addr & 255:
DEBUG_LOG(NAOMI, "NAOMI_COMM2_STATUS1 read %x", comm_status1);
return comm_status1;
default:
DEBUG_LOG(NAOMI, "NaomiM3Comm::ReadMem unmapped: %08x sz %d", address, size);
return 0xffffffff;
}
}
void NaomiM3Comm::connectedState(bool success)
{
if (!success)
return;
memset(&comm_ram[0xf000], 0, 16);
comm_ram[0xf000] = 1;
comm_ram[0xf001] = 1;
comm_ram[0xf002] = m68k_ram[0x204];
comm_ram[0xf003] = m68k_ram[0x205];
u32 slot_size = swap16(*(u16*)&m68k_ram[0x204]);
memset(&comm_ram[0], 0, 32);
// 80000
comm_ram[0] = 0;
comm_ram[1] = slot_id == 0 ? 0 : 1;
// 80002
comm_ram[2] = 0x01;
comm_ram[3] = 0x01;
// 80004
if (slot_id == 0)
{
comm_ram[4] = 0;
comm_ram[5] = 0;
}
else
{
comm_ram[4] = 1;
comm_ram[5] = 1;
}
// 80006: packet number
comm_ram[6] = 0;
comm_ram[7] = 0;
// 80008
comm_ram[8] = slot_id == 0 ? 0x78 : 0x73;
comm_ram[9] = slot_id == 0 ? 0x30 : 0xa2;
// 8000A
*(u16 *)(comm_ram + 10) = 0x100 + slot_size; // offset of recvd data
// 8000C
*(u16 *)(comm_ram + 12) = slot_size * slot_count; // recvd data size
// 8000E
*(u16 *)(comm_ram + 14) = 0x100; // offset of sent data
// 80010
*(u16 *)(comm_ram + 16) = 0x80 + slot_size * slot_count; // sent data size
// FIXME wrungp uses 100, others 80
comm_status0 = 0xff01; // But 1 at connect time before f000 is read
comm_status1 = (slot_count << 8) | slot_id;
}
void NaomiM3Comm::WriteMem(u32 address, u32 data, u32 size)
{
switch (address & 255)
{
case NAOMI_COMM2_CTRL_addr & 255:
// bit 0: access RAM is 0 - communication RAM / 1 - M68K RAM
// bit 1: comm RAM bank (seems R/O for SH4)
// bit 5: M68K Reset
// bit 6: ???
// bit 7: might be M68K IRQ 5 or 2
// bit 14: G1 DMA bus master 0 - active / 1 - disabled
// bit 15: 0 - enable / 1 - disable this device ???
if (data & (1 << 5))
{
DEBUG_LOG(NAOMI, "NAOMI_COMM2_CTRL m68k reset");
closeNetwork();
memset(&comm_ram[0], 0, 32);
comm_status0 = 0; // varies...
comm_status1 = 0;
startThread();
}
comm_ctrl = (u16)(data & ~(1 << 5));
//DEBUG_LOG(NAOMI, "NAOMI_COMM2_CTRL set to %x", comm_ctrl);
return;
case NAOMI_COMM2_OFFSET_addr & 255:
comm_offset = (u16)data;
//DEBUG_LOG(NAOMI, "NAOMI_COMM2_OFFSET set to %x", comm_offset);
return;
case NAOMI_COMM2_DATA_addr & 255:
DEBUG_LOG(NAOMI, "NAOMI_COMM2_DATA written @ %04x %04x", comm_offset, (u16)data);
data = swap16(data);
if (comm_ctrl & 1)
*(u16*)&m68k_ram[comm_offset] = (u16)data;
else
*(u16*)&comm_ram[comm_offset] = (u16)data;
comm_offset += 2;
return;
case NAOMI_COMM2_STATUS0_addr & 255:
comm_status0 = (u16)data;
//DEBUG_LOG(NAOMI, "NAOMI_COMM2_STATUS0 set to %x", comm_status0);
return;
case NAOMI_COMM2_STATUS1_addr & 255:
comm_status1 = (u16)data;
//DEBUG_LOG(NAOMI, "NAOMI_COMM2_STATUS1 set to %x", comm_status1);
return;
default:
break;
}
DEBUG_LOG(NAOMI, "NaomiM3Comm::WriteMem: %x <= %x sz %d", address, data, size);
}
bool NaomiM3Comm::DmaStart(u32 addr, u32 data)
{
if (comm_ctrl & 0x4000)
return false;
DEBUG_LOG(NAOMI, "NaomiM3Comm: DMA addr %08X <-> %04x len %d %s", SB_GDSTAR, comm_offset, SB_GDLEN, SB_GDDIR == 0 ? "OUT" : "IN");
std::unique_lock<std::mutex> lock(mem_mutex);
if (SB_GDDIR == 0)
{
// Network write
for (u32 i = 0; i < SB_GDLEN; i++)
comm_ram[comm_offset++] = ReadMem8_nommu(SB_GDSTAR + i);
}
else
{
// Network read
if (SB_GDLEN == 32 && (comm_ctrl & 1) == 0)
{
char buf[32 * 5 + 1];
buf[0] = 0;
for (u32 i = 0; i < SB_GDLEN; i++)
{
u8 value = comm_ram[comm_offset + i];
sprintf(buf + strlen(buf), "%02x ", value);
}
DEBUG_LOG(NAOMI, "Comm RAM read @%x: %s", comm_offset, buf);
}
for (u32 i = 0; i < SB_GDLEN; i++)
WriteMem8_nommu(SB_GDSTAR + i, comm_ram[comm_offset++]);
}
return true;
}
void NaomiM3Comm::startThread()
{
network_stopping = false;
thread = std::unique_ptr<std::thread>(new std::thread([this]() {
using the_clock = std::chrono::high_resolution_clock;
connectNetwork();
the_clock::time_point token_time = the_clock::now();
while (!network_stopping)
{
network.pipeSlaves();
receiveNetwork();
if (slot_id == 0 && network.hasToken())
{
const auto target_duration = std::chrono::milliseconds(10);
auto duration = the_clock::now() - token_time;
if (duration < target_duration)
{
DEBUG_LOG(NAOMI, "Sleeping for %ld ms", std::chrono::duration_cast<std::chrono::milliseconds>(target_duration - duration).count());
std::this_thread::sleep_for(target_duration - duration);
}
token_time = the_clock::now();
}
sendNetwork();
}
DEBUG_LOG(NAOMI, "Network thread exiting");
}));
}

View File

@ -0,0 +1,60 @@
/*
Created on: Mar 15, 2020
Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"
#include <atomic>
#include <memory>
#include <mutex>
#include <thread>
#include "network/naomi_network.h"
class NaomiM3Comm
{
public:
~NaomiM3Comm();
u32 ReadMem(u32 address, u32 size);
void WriteMem(u32 address, u32 data, u32 size);
bool DmaStart(u32 addr, u32 data);
void closeNetwork();
private:
void initNetwork();
void connectNetwork();
void receiveNetwork();
void sendNetwork();
void connectedState(bool success);
void startThread();
u16 comm_ctrl = 0xC000;
u16 comm_offset = 0;
u16 comm_status0 = 0;
u16 comm_status1 = 0;
u8 m68k_ram[128 * 1024];
u8 comm_ram[128 * 1024];
int slot_count = 0;
int slot_id = 0;
std::atomic<bool> network_stopping{ false };
std::unique_ptr<std::thread> thread;
std::mutex mem_mutex;
NaomiNetwork network;
};

View File

@ -0,0 +1,465 @@
/*
Created on: Apr 12, 2020
Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
#include "naomi_network.h"
#include "types.h"
#include <chrono>
#include <thread>
#include "rend/gui.h"
sock_t NaomiNetwork::createAndBind(int protocol)
{
sock_t sock = socket(AF_INET, protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM, protocol);
if (sock == INVALID_SOCKET)
{
ERROR_LOG(NETWORK, "Cannot create server socket");
return INVALID_SOCKET;
}
int option = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVER_PORT);
if (::bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
ERROR_LOG(NETWORK, "NaomiServer: bind() failed. errno=%d", get_last_error());
closesocket(sock);
sock = INVALID_SOCKET;
}
else
set_non_blocking(sock);
return sock;
}
bool NaomiNetwork::init()
{
if (settings.network.ActAsServer)
return createBeaconSocket() && createServerSocket();
else
return true;
}
bool NaomiNetwork::createServerSocket()
{
if (server_sock != INVALID_SOCKET)
return true;
server_sock = createAndBind(IPPROTO_TCP);
if (server_sock == INVALID_SOCKET)
return false;
if (listen(server_sock, 5) < 0)
{
ERROR_LOG(NETWORK, "NaomiServer: listen() failed. errno=%d", get_last_error());
closesocket(server_sock);
server_sock = INVALID_SOCKET;
return false;
}
return true;
}
bool NaomiNetwork::createBeaconSocket()
{
if (beacon_sock == INVALID_SOCKET)
beacon_sock = createAndBind(IPPROTO_UDP);
return beacon_sock != INVALID_SOCKET;
}
void NaomiNetwork::processBeacon()
{
// Receive broadcast queries on beacon socket and reply
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
memset(&addr, 0, sizeof(addr));
char buf[6];
ssize_t n;
do {
memset(buf, '\0', sizeof(buf));
if ((n = recvfrom(beacon_sock, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &addrlen)) == -1)
{
if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
WARN_LOG(NETWORK, "NaomiServer: Error receiving datagram. errno=%d", get_last_error());
}
else
{
DEBUG_LOG(NETWORK, "NaomiServer: beacon received %ld bytes", n);
if (n == sizeof(buf) && !strncmp(buf, "flycast", n))
sendto(beacon_sock, buf, n, 0, (const struct sockaddr *)&addr, addrlen);
}
} while (n != -1);
}
bool NaomiNetwork::findServer()
{
// Automatically find the adhoc server on the local network using broadcast
sock_t sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == INVALID_SOCKET)
{
ERROR_LOG(NETWORK, "Datagram socket creation error. errno=%d", get_last_error());
return false;
}
// Allow broadcast packets to be sent
int broadcast = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)) == -1)
{
ERROR_LOG(NETWORK, "setsockopt(SO_BROADCAST) failed. errno=%d", get_last_error());
closesocket(sockfd);
return false;
}
// Set a 1 sec timeout on recv call
if (!set_recv_timeout(sockfd, 1000))
{
ERROR_LOG(NETWORK, "setsockopt(SO_RCVTIMEO) failed. errno=%d", get_last_error());
closesocket(sockfd);
return false;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET; // host byte order
addr.sin_port = htons(SERVER_PORT); // short, network byte order
addr.sin_addr.s_addr = INADDR_BROADCAST;
memset(addr.sin_zero, '\0', sizeof(addr.sin_zero));
struct sockaddr server_addr;
for (int i = 0; i < 3; i++)
{
if (sendto(sockfd, "flycast", 6, 0, (struct sockaddr *)&addr, sizeof addr) == -1)
{
WARN_LOG(NETWORK, "Send datagram failed. errno=%d", get_last_error());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
char buf[6];
memset(&server_addr, '\0', sizeof(server_addr));
socklen_t addrlen = sizeof(server_addr);
if (recvfrom(sockfd, buf, sizeof(buf), 0, &server_addr, &addrlen) == -1)
{
if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
WARN_LOG(NETWORK, "Recv datagram failed. errno=%d", get_last_error());
else
INFO_LOG(NETWORK, "Recv datagram timeout. i=%d", i);
continue;
}
server_ip = ((struct sockaddr_in *)&server_addr)->sin_addr;
char addressBuffer[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &server_ip, addressBuffer, INET_ADDRSTRLEN);
server_name = addressBuffer;
break;
}
closesocket(sockfd);
if (server_ip.s_addr == INADDR_NONE)
{
WARN_LOG(NETWORK, "Network Error: Can't find ad-hoc server on local network");
gui_display_notification("No server found", 8000);
return false;
}
INFO_LOG(NETWORK, "Found ad-hoc server at %s", server_name.c_str());
return true;
}
bool NaomiNetwork::startNetwork()
{
network_stopping = false;
if (!init())
return false;
slot_id = 0;
slot_count = 0;
packet_number = 0;
slaves.clear();
got_token = false;
using namespace std::chrono;
const auto timeout = seconds(10);
if (settings.network.ActAsServer)
{
NOTICE_LOG(NETWORK, "Waiting for slave connections");
steady_clock::time_point start_time = steady_clock::now();
while (steady_clock::now() - start_time < timeout)
{
if (network_stopping)
{
for (auto clientSock : slaves)
if (clientSock != INVALID_SOCKET)
closesocket(clientSock);
return false;
}
std::string notif = slaves.empty() ? "Waiting for players..."
: std::to_string(slaves.size()) + " player(s) connected. Waiting...";
gui_display_notification(notif.c_str(), timeout.count() * 2000);
processBeacon();
struct sockaddr_in src_addr;
socklen_t addr_len = sizeof(src_addr);
memset(&src_addr, 0, addr_len);
sock_t clientSock = accept(server_sock, (struct sockaddr *)&src_addr, &addr_len);
if (clientSock == INVALID_SOCKET)
{
if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
perror("accept");
}
else
{
NOTICE_LOG(NETWORK, "Slave connection accepted");
std::lock_guard<std::mutex> lock(mutex);
slaves.push_back(clientSock);
if (slaves.size() == 3)
break;
}
std::this_thread::sleep_for(milliseconds(100));
}
slot_id = 0;
slot_count = slaves.size() + 1;
u8 buf[2] = { (u8)slot_count, 0 };
int slot_num = 1;
{
for (int socket : slaves)
{
buf[1] = { (u8)slot_num };
slot_num++;
write(socket, buf, 2);
set_non_blocking(socket);
set_tcp_nodelay(socket);
}
}
NOTICE_LOG(NETWORK, "Master starting: %zd slaves", slaves.size());
if (slot_count > 1)
gui_display_notification("Starting game", 2000);
else
gui_display_notification("No player connected", 8000);
return !slaves.empty();
}
else
{
if (!settings.network.server.empty())
{
struct addrinfo *resultAddr;
if (getaddrinfo(settings.network.server.c_str(), 0, nullptr, &resultAddr))
WARN_LOG(NETWORK, "Server %s is unknown", settings.network.server.c_str());
else
for (struct addrinfo *ptr = resultAddr; ptr != nullptr; ptr = ptr->ai_next)
if (ptr->ai_family == AF_INET)
{
server_ip = ((sockaddr_in *)ptr->ai_addr)->sin_addr;
break;
}
}
NOTICE_LOG(NETWORK, "Connecting to server");
gui_display_notification("Connecting to server", 10000);
steady_clock::time_point start_time = steady_clock::now();
while (client_sock == INVALID_SOCKET && !network_stopping
&& steady_clock::now() - start_time < timeout)
{
if (server_ip.s_addr == INADDR_NONE && !findServer())
continue;
client_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in src_addr;
src_addr.sin_family = AF_INET;
src_addr.sin_addr = server_ip;
src_addr.sin_port = htons(SERVER_PORT);
if (::connect(client_sock, (struct sockaddr *)&src_addr, sizeof(src_addr)) < 0)
{
ERROR_LOG(NETWORK, "Socket connect failed");
closesocket(client_sock);
client_sock = INVALID_SOCKET;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
else
{
gui_display_notification("Waiting for server to start", 10000);
set_recv_timeout(client_sock, (int)std::chrono::milliseconds(timeout * 2).count());
u8 buf[2];
if (read(client_sock, buf, 2) < 2)
{
ERROR_LOG(NETWORK, "Connection failed: errno=%d", get_last_error());
closesocket(client_sock);
client_sock = -1;
gui_display_notification("Connection failed", 10000);
return false;
}
slot_count = buf[0];
slot_id = buf[1];
got_token = slot_id == 1;
set_tcp_nodelay(client_sock);
set_non_blocking(client_sock);
std::string notif = "Connected as slot " + std::to_string(slot_id);
gui_display_notification(notif.c_str(), 2000);
return true;
}
}
return false;
}
}
void NaomiNetwork::pipeSlaves()
{
if (isMaster() || slot_count < 3)
return;
u8 buf[16384];
for (auto it = slaves.begin(); it != slaves.end() - 1; it++)
{
ssize_t l = read(*it, buf, sizeof(buf));
if (l > 0)
write(*(it + 1), buf, l);
// TODO handle errors
}
}
bool NaomiNetwork::receive(u8 *data, u32 size)
{
sock_t sockfd = INVALID_SOCKET;
if (isMaster())
sockfd = slaves.empty() ? INVALID_SOCKET : slaves.back();
else
sockfd = client_sock;
if (sockfd == INVALID_SOCKET)
return false;
u16 pktnum;
ssize_t l = read(sockfd, &pktnum, sizeof(pktnum));
if (l <= 0)
{
if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
{
WARN_LOG(NETWORK, "receiveNetwork: read failed. errno=%d", get_last_error());
if (isMaster())
{
slaves.back() = -1;
closesocket(sockfd);
got_token = false;
}
}
return false;
}
packet_number = pktnum;
ssize_t received = 0;
while (received != size && !network_stopping)
{
l = read(sockfd, data + received, size - received);
if (l <= 0)
{
if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
{
WARN_LOG(NETWORK, "receiveNetwork: read failed. errno=%d", get_last_error());
if (isMaster())
{
slaves.back() = -1;
closesocket(sockfd);
got_token = false;
}
return false;
}
}
else
received += l;
}
DEBUG_LOG(NETWORK, "[%d] Received %d bytes", slot_id, size);
got_token = true;
return true;
}
void NaomiNetwork::send(u8 *data, u32 size)
{
if (!got_token)
return;
sock_t sockfd;
if (isMaster())
sockfd = slaves.empty() ? INVALID_SOCKET : slaves.front();
else
sockfd = client_sock;
if (sockfd == INVALID_SOCKET)
return;
u16 pktnum = packet_number + 1;
struct iovec iov[] = { { &pktnum, sizeof(pktnum) }, { data, size } };
struct msghdr msg{};
msg.msg_iov = iov;
msg.msg_iovlen = ARRAY_SIZE(iov);
if (sendmsg(sockfd, &msg, 0) < 0)
{
if (errno != L_EAGAIN && errno != L_EWOULDBLOCK)
{
WARN_LOG(NETWORK, "sendmsg failed. errno=%d", get_last_error());
if (isMaster())
{
slaves.front() = -1;
closesocket(sockfd);
}
}
}
else
{
DEBUG_LOG(NETWORK, "[%d] Sent %d bytes", slot_id, size);
got_token = false;
packet_number = pktnum;
}
}
void NaomiNetwork::shutdown()
{
network_stopping = true;
{
std::lock_guard<std::mutex> lock(mutex);
for (auto& clientSock : slaves)
{
closesocket(clientSock);
clientSock = -1;
}
}
if (client_sock != INVALID_SOCKET)
closesocket(client_sock);
}
void NaomiNetwork::terminate()
{
shutdown();
if (beacon_sock != INVALID_SOCKET)
{
closesocket(beacon_sock);
beacon_sock = INVALID_SOCKET;
}
if (server_sock != INVALID_SOCKET)
{
closesocket(server_sock);
server_sock = INVALID_SOCKET;
}
}

View File

@ -0,0 +1,70 @@
/*
Created on: Apr 12, 2020
Copyright 2020 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"
#include <cstdint>
#include <atomic>
#include <mutex>
#include <vector>
#include "net_platform.h"
class NaomiNetwork
{
public:
~NaomiNetwork() { terminate(); }
bool init();
bool startNetwork();
void pipeSlaves();
bool receive(u8 *data, u32 size);
void send(u8 *data, u32 size);
void shutdown(); // thread-safe
void terminate(); // thread-safe
int slotCount() const { return slot_count; }
int slotId() const { return slot_id; }
u16 packetNumber() const { return packet_number; }
bool hasToken() const { return got_token; }
private:
bool createServerSocket();
bool createBeaconSocket();
void processBeacon();
bool findServer();
int createAndBind(int protocol);
bool isMaster() const { return slot_id == 0; }
struct in_addr server_ip{ INADDR_NONE };
std::string server_name;
// server stuff
sock_t server_sock = INVALID_SOCKET;
sock_t beacon_sock = INVALID_SOCKET;
std::vector<sock_t> slaves;
// client stuff
sock_t client_sock = INVALID_SOCKET;
// common stuff
int slot_count = 0;
int slot_id = 0;
bool got_token = false;
u16 packet_number = 0;
std::atomic<bool> network_stopping{ false };
std::mutex mutex;
static const uint16_t SERVER_PORT = 37391;
};

View File

@ -790,6 +790,9 @@ void InitSettings()
settings.input.maple_expansion_devices[i][0] = i == 0 ? MDT_SegaVMU : MDT_None;
settings.input.maple_expansion_devices[i][1] = i == 0 ? MDT_SegaVMU : MDT_None;
}
settings.network.ActAsServer = false;
settings.network.dns = "46.101.91.123"; // Dreamcast Live DNS
settings.network.server = "";
#if SUPPORT_DISPMANX
settings.dispmanx.Width = 640;
@ -893,6 +896,9 @@ void LoadSettings(bool game_specific)
sprintf(device_name, "device%d.2", i + 1);
settings.input.maple_expansion_devices[i][1] = (MapleDeviceType)cfgLoadInt(input_section, device_name, settings.input.maple_expansion_devices[i][1]);
}
settings.network.ActAsServer = cfgLoadBool("network", "ActAsServer", settings.network.ActAsServer);
settings.network.dns = cfgLoadStr("network", "DNS", settings.network.dns.c_str());
settings.network.server = cfgLoadStr("network", "server", settings.network.server.c_str());
#if SUPPORT_DISPMANX
settings.dispmanx.Width = cfgLoadInt(game_specific ? cfgGetGameId() : "dispmanx", "width", settings.dispmanx.Width);
@ -1049,6 +1055,9 @@ void SaveSettings()
}
cfgSaveStr("config", "Dreamcast.ContentPath", paths.c_str());
cfgSaveBool("config", "Dreamcast.HideLegacyNaomiRoms", settings.dreamcast.HideLegacyNaomiRoms);
cfgSaveBool("network", "ActAsServer", settings.network.ActAsServer);
cfgSaveStr("network", "DNS", settings.network.dns.c_str());
cfgSaveStr("network", "server", settings.network.server.c_str());
GamepadDevice::SaveMaplePorts();

View File

@ -39,6 +39,7 @@
extern void dc_loadstate();
extern void dc_savestate();
extern void dc_stop();
extern void dc_reset(bool hard);
extern void dc_resume();
extern void dc_start_game(const char *path);
extern void UpdateInputState(u32 port);
@ -363,6 +364,7 @@ static void gui_display_commands()
gui_state = Main;
game_started = false;
settings.imgread.ImagePath[0] = '\0';
dc_reset(true);
}
ImGui::End();
@ -1282,6 +1284,18 @@ static void gui_display_settings()
ImGui::SameLine();
ShowHelpMarker("Skip wait loops. Recommended");
}
if (ImGui::CollapsingHeader("Network", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Checkbox("Act as Server", &settings.network.ActAsServer);
ImGui::SameLine();
ShowHelpMarker("Create a local server for Naomi network games");
char server_name[256];
strcpy(server_name, settings.network.server.c_str());
ImGui::InputText("Server", server_name, sizeof(server_name), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr);
ImGui::SameLine();
ShowHelpMarker("The server to connect to. Leave blank to find a server automatically");
settings.network.server = server_name;
}
if (ImGui::CollapsingHeader("Other", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Checkbox("HLE BIOS", &settings.bios.UseReios);

View File

@ -533,6 +533,12 @@ struct settings_t
int maple_expansion_devices[4][2];
int VirtualGamepadVibration;
} input;
struct {
bool ActAsServer;
std::string dns;
std::string server;
} network;
};
extern settings_t settings;

View File

@ -238,6 +238,8 @@
AE7BCB5E243DDCD1007285F8 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE7BCB5D243DDCD1007285F8 /* AudioToolbox.framework */; };
AE7BCB60243DDD49007285F8 /* libiconv.2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = AE7BCB5F243DDD3A007285F8 /* libiconv.2.tbd */; };
AE7BCB62243DDD92007285F8 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE7BCB61243DDD92007285F8 /* Carbon.framework */; };
AE7BCB6B244608B6007285F8 /* naomi_network.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AE7BCB69244608B5007285F8 /* naomi_network.cpp */; };
AE7BCB6E24460910007285F8 /* naomi_m3comm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AE7BCB6C24460910007285F8 /* naomi_m3comm.cpp */; };
AE80EDB72157D4D500F7800F /* serialize.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AE80EDB62157D4D500F7800F /* serialize.cpp */; };
AE80EDBE2157D4E600F7800F /* naomi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AE80EDB92157D4E600F7800F /* naomi.cpp */; };
AE80EDBF2157D4E600F7800F /* naomi_cart.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AE80EDBB2157D4E600F7800F /* naomi_cart.cpp */; };
@ -792,6 +794,11 @@
AE7BCB5D243DDCD1007285F8 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
AE7BCB5F243DDD3A007285F8 /* libiconv.2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.2.tbd; path = usr/lib/libiconv.2.tbd; sourceTree = SDKROOT; };
AE7BCB61243DDD92007285F8 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
AE7BCB68244608B5007285F8 /* net_platform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = net_platform.h; sourceTree = "<group>"; };
AE7BCB69244608B5007285F8 /* naomi_network.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = naomi_network.cpp; sourceTree = "<group>"; };
AE7BCB6A244608B6007285F8 /* naomi_network.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = naomi_network.h; sourceTree = "<group>"; };
AE7BCB6C24460910007285F8 /* naomi_m3comm.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = naomi_m3comm.cpp; sourceTree = "<group>"; };
AE7BCB6D24460910007285F8 /* naomi_m3comm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = naomi_m3comm.h; sourceTree = "<group>"; };
AE80EDB62157D4D500F7800F /* serialize.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = serialize.cpp; path = ../../../core/serialize.cpp; sourceTree = "<group>"; };
AE80EDB92157D4E600F7800F /* naomi.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = naomi.cpp; sourceTree = "<group>"; };
AE80EDBA2157D4E600F7800F /* naomi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = naomi.h; sourceTree = "<group>"; };
@ -1151,6 +1158,7 @@
84B7BE4D1B72720100F9733F /* khronos */,
84B7BE641B72720100F9733F /* linux */,
AE43536822C9420C005E19CE /* log */,
AE7BCB6724460875007285F8 /* network */,
84B7BE6F1B72720200F9733F /* oslib */,
84B7BE7C1B72720200F9733F /* profiler */,
AE1E29392095FB1600FC6BA2 /* rec-cpp */,
@ -2037,6 +2045,17 @@
path = ../../../core/wsi;
sourceTree = "<group>";
};
AE7BCB6724460875007285F8 /* network */ = {
isa = PBXGroup;
children = (
AE7BCB69244608B5007285F8 /* naomi_network.cpp */,
AE7BCB6A244608B6007285F8 /* naomi_network.h */,
AE7BCB68244608B5007285F8 /* net_platform.h */,
);
name = network;
path = ../../../core/network;
sourceTree = "<group>";
};
AE80EDB82157D4E600F7800F /* naomi */ = {
isa = PBXGroup;
children = (
@ -2051,6 +2070,8 @@
AE2A2D5021D6846F004B308D /* m1cartridge.h */,
AE2A2D5621D68470004B308D /* m4cartridge.cpp */,
AE2A2D5421D68470004B308D /* m4cartridge.h */,
AE7BCB6C24460910007285F8 /* naomi_m3comm.cpp */,
AE7BCB6D24460910007285F8 /* naomi_m3comm.h */,
AE2A2D5821D68470004B308D /* naomi_roms_input.h */,
AE2A2D5521D68470004B308D /* naomi_roms.h */,
AE80EDB92157D4E600F7800F /* naomi.cpp */,
@ -2747,6 +2768,7 @@
84B7BF391B72720200F9733F /* drkPvr.cpp in Sources */,
84B7BF621B72720200F9733F /* context.cpp in Sources */,
AEFF7F72214D9D590068CE11 /* pico_protocol.c in Sources */,
AE7BCB6E24460910007285F8 /* naomi_m3comm.cpp in Sources */,
84B7BF6A1B72720200F9733F /* audiobackend_pulseaudio.cpp in Sources */,
AED73E8A2348E45000ECDB64 /* doc.cpp in Sources */,
84B7BF3F1B72720200F9733F /* ta.cpp in Sources */,
@ -2754,6 +2776,7 @@
AED73DC42303E19200ECDB64 /* sdl.cpp in Sources */,
84B7BF751B72720200F9733F /* gdrom_hle.cpp in Sources */,
84B7BEFF1B72720200F9733F /* zip_name_locate.c in Sources */,
AE7BCB6B244608B6007285F8 /* naomi_network.cpp in Sources */,
AED73E662348E45000ECDB64 /* iomapper.cpp in Sources */,
AE649BFD218C552500EF4A81 /* lpc_intrin_avx2.c in Sources */,
AE649BF8218C552500EF4A81 /* fixed_intrin_sse2.c in Sources */,