new option to broadcast digital outputs over the network

Similar to MAME's "-output network" option. Should be compatible with
MAME Hooker and the like.

Limit boxart images loaded per frame to 10.
This commit is contained in:
Flyinghead 2022-06-16 16:50:26 +02:00
parent 7982fdac1c
commit 11ecb473b6
12 changed files with 223 additions and 13 deletions

View File

@ -845,6 +845,8 @@ target_sources(${PROJECT_NAME} PRIVATE
core/network/net_handshake.cpp
core/network/net_handshake.h
core/network/net_platform.h
core/network/output.cpp
core/network/output.h
core/network/picoppp.cpp
core/network/picoppp.h)

View File

@ -133,6 +133,7 @@ Option<int> GGPOAnalogAxes("GGPOAnalogAxes", 0, "network");
Option<bool> GGPOChat("GGPOChat", true, "network");
Option<bool> GGPOChatTimeoutToggle("GGPOChatTimeoutToggle", true, "network");
Option<int> GGPOChatTimeout("GGPOChatTimeout", 10, "network");
Option<bool> NetworkOutput("NetworkOutput", false, "network");
#ifdef SUPPORT_DISPMANX
Option<bool> DispmanxMaintainAspect("maintain_aspect", true, "dispmanx");

View File

@ -491,6 +491,7 @@ extern Option<int> GGPOAnalogAxes;
extern Option<bool> GGPOChat;
extern Option<bool> GGPOChatTimeoutToggle;
extern Option<int> GGPOChatTimeout;
extern Option<bool> NetworkOutput;
#ifdef SUPPORT_DISPMANX
extern Option<bool> DispmanxMaintainAspect;

View File

@ -24,6 +24,7 @@
#include "oslib/oslib.h"
#include "stdclass.h"
#include "cfg/option.h"
#include "network/output.h"
#define LOGJVS(...) DEBUG_LOG(JVS, __VA_ARGS__)
@ -195,7 +196,23 @@ protected:
}
}
virtual void write_digital_out(int count, u8 *data) { }
virtual void write_digital_out(int count, u8 *data)
{
u32 newOutput = digOutput;
for (int i = 0; i < count && i < 4; i++)
{
u32 mask = 0xff << (i * 8);
newOutput = (newOutput & ~mask) | (data[i] << (i * 8));
}
u32 changes = newOutput ^ digOutput;
for (int i = 0; i < 32; i++)
if (changes & (1 << i))
{
std::string name = "lamp" + std::to_string(i);
networkOutput.output(name.c_str(), (newOutput >> i) & 1);
}
digOutput = newOutput;
}
u32 player_count = 0;
u32 digital_in_count = 0;
@ -242,6 +259,7 @@ private:
std::array<u32, 32> cur_mapping;
std::array<u32, 32> p1_mapping;
std::array<u32, 32> p2_mapping;
u32 digOutput = 0;
};
// Most common JVS board
@ -1632,13 +1650,18 @@ u32 jvs_io_board::handle_jvs_message(u8 *buffer_in, u32 length_in, u8 *buffer_ou
break;
case 0x32: // switched outputs
case 0x33:
LOGJVS("output(%d) %x", buffer_in[cmdi + 1], buffer_in[cmdi + 2]);
write_digital_out(buffer_in[cmdi + 1], &buffer_in[cmdi + 2]);
JVS_STATUS1(); // report byte
cmdi += buffer_in[cmdi + 1] + 2;
break;
case 0x33: // Analog output
LOGJVS("analog output(%d) %x", buffer_in[cmdi + 1], buffer_in[cmdi + 2]);
JVS_STATUS1(); // report byte
cmdi += buffer_in[cmdi + 1] + 2;
break;
case 0x30: // substract coin
if (buffer_in[cmdi + 1] > 0 && first_player + buffer_in[cmdi + 1] - 1 < (int)ARRAY_SIZE(coin_count))
coin_count[first_player + buffer_in[cmdi + 1] - 1] -= (buffer_in[cmdi + 2] << 8) + buffer_in[cmdi + 3];

View File

@ -16,6 +16,7 @@
#include "naomi_regs.h"
#include "naomi_m3comm.h"
#include "serialize.h"
#include "network/output.h"
//#define NAOMI_COMM
@ -525,6 +526,7 @@ void naomi_reg_Init()
}
#endif
NaomiInit();
networkOutput.init();
}
void naomi_reg_Term()
@ -540,6 +542,7 @@ void naomi_reg_Term()
}
#endif
m3comm.closeNetwork();
networkOutput.term();
}
void naomi_reg_Reset(bool hard)
@ -578,7 +581,7 @@ void naomi_reg_Reset(bool hard)
static u8 aw_maple_devs;
static u64 coin_chute_time[4];
static u8 ffbOuput;
static u8 awDigitalOuput;
u32 libExtDevice_ReadMem_A0_006(u32 addr,u32 size) {
addr &= 0x7ff;
@ -636,7 +639,7 @@ u32 libExtDevice_ReadMem_A0_006(u32 addr,u32 size) {
// ??? Dolphin Blue
return 0;
case 0x28c:
return ffbOuput;
return awDigitalOuput;
}
INFO_LOG(NAOMI, "Unhandled read @ %x sz %d", addr, size);
return 0xFF;
@ -654,11 +657,27 @@ void libExtDevice_WriteMem_A0_006(u32 addr,u32 data,u32 size) {
case 0x288:
// ??? Dolphin Blue
return;
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);
case 0x28C: // Digital output
if ((u8)data != awDigitalOuput)
{
if (atomiswaveForceFeedback)
// Wheel force feedback:
// bit 0 direction (0 pos, 1 neg)
// bit 1-4 strength
networkOutput.output("awffb", (u8)data);
else
{
u8 changes = data ^ awDigitalOuput;
for (int i = 0; i < 8; i++)
if (changes & (1 << i))
{
std::string name = "lamp" + std::to_string(i);
networkOutput.output(name.c_str(), (data >> i) & 1);
}
}
awDigitalOuput = data;
DEBUG_LOG(NAOMI, "AW output %02x", data);
}
return;
default:
break;
@ -780,6 +799,8 @@ static void forceFeedbackMidiReceiver(u8 data)
// https://github.com/Boomslangnz/FFBArcadePlugin/blob/master/Game%20Files/Demul.cpp
if (midiTxBuf[0] == 0x85 && midiTxBuf[1] == 0x3f)
MapleConfigMap::UpdateVibration(0, std::max(0.f, (float)(midiTxBuf[2] - 1) / 24.f), 0.f, 5);
if (midiTxBuf[0] != 0xfd)
networkOutput.output("midiffb", (midiTxBuf[0] << 16) | (midiTxBuf[1]) << 8 | midiTxBuf[2]);
}
midiTxBufIndex = (midiTxBufIndex + 1) % ARRAY_SIZE(midiTxBuf);
}

View File

@ -49,6 +49,7 @@ InputDescriptors *NaomiGameInputs;
u8 *naomi_default_eeprom;
MaxSpeedNetPipe maxSpeedNetPipe;
bool atomiswaveForceFeedback;
extern MemChip *sys_rom;
@ -573,6 +574,7 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
else
loadDecryptedRom(file, progress);
atomiswaveForceFeedback = false;
RomBootID bootId;
if (CurrentCartridge->GetBootId(&bootId))
{
@ -593,6 +595,11 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
{
maxSpeedNetPipe.init();
configure_maxspeed_flash(config::NetworkEnable, config::ActAsServer);
atomiswaveForceFeedback = true;
}
else if (gameId == "FASTER THAN SPEED")
{
atomiswaveForceFeedback = true;
}
else if (gameId == "SAMPLE GAME MAX LONG NAME-") // Driving Simulator
{

View File

@ -166,3 +166,4 @@ struct InputDescriptors
};
extern InputDescriptors *NaomiGameInputs;
extern bool atomiswaveForceFeedback;

21
core/network/output.cpp Normal file
View File

@ -0,0 +1,21 @@
/*
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 <https://www.gnu.org/licenses/>.
*/
#include "output.h"
NetworkOutput networkOutput;

115
core/network/output.h Normal file
View File

@ -0,0 +1,115 @@
/*
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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"
#include "net_platform.h"
#include "emulator.h"
#include "cfg/option.h"
class NetworkOutput
{
public:
void init()
{
if (!config::NetworkOutput)
return;
server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int option = 1;
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, (const char *)&option, sizeof(option));
sockaddr_in saddr{};
socklen_t saddr_len = sizeof(saddr);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(8000);
if (::bind(server, (sockaddr *)&saddr, saddr_len) < 0)
{
perror("bind");
term();
return;
}
if (listen(server, 5) < 0)
{
perror("listen");
term();
return;
}
set_non_blocking(server);
EventManager::listen(Event::VBlank, vblankCallback, this);
}
void term()
{
EventManager::unlisten(Event::VBlank, vblankCallback, this);
for (sock_t sock : clients)
closesocket(sock);
clients.clear();
if (server != INVALID_SOCKET)
{
closesocket(server);
server = INVALID_SOCKET;
}
}
void output(const char *name, u32 value)
{
if (!config::NetworkOutput)
return;
char s[9];
sprintf(s, "%x", value);
std::string msg = std::string(name) + " = " + std::string(s) + "\n"; // mame uses \r
std::vector<sock_t> errorSockets;
for (sock_t sock : clients)
if (::send(sock, msg.c_str(), msg.length(), 0) < 0)
{
int error = get_last_error();
if (error != L_EWOULDBLOCK && error != L_EAGAIN)
errorSockets.push_back(sock);
}
for (sock_t sock : errorSockets)
{
closesocket(sock);
clients.erase(std::find(clients.begin(), clients.end(), sock));
}
}
private:
static void vblankCallback(Event event, void *param) {
((NetworkOutput *)param)->acceptConnections();
}
void acceptConnections()
{
sockaddr_in src_addr{};
socklen_t addr_len = sizeof(src_addr);
sock_t sockfd = accept(server, (sockaddr *)&src_addr, &addr_len);
if (sockfd != INVALID_SOCKET)
{
set_non_blocking(sockfd);
clients.push_back(sockfd);
}
}
sock_t server = INVALID_SOCKET;
std::vector<sock_t> clients;
};
extern NetworkOutput networkOutput;

View File

@ -2142,9 +2142,9 @@ static void gui_display_settings()
ShowHelpMarker("The local UDP port to use");
config::LocalPort.set(atoi(localPort));
}
OptionCheckbox("Enable UPnP", config::EnableUPnP);
ImGui::SameLine();
ShowHelpMarker("Automatically configure your network router for netplay");
OptionCheckbox("Enable UPnP", config::EnableUPnP, "Automatically configure your network router for netplay");
OptionCheckbox("Broadcast Digital Outputs", config::NetworkOutput, "Broadcast digital outputs and force-feedback state on TCP port 8000. "
"Compatible with the \"-output network\" MAME option. Arcade games only.");
}
ImGui::Spacing();
header("Other");
@ -2353,6 +2353,7 @@ static void gui_display_content()
gui_start_game("");
ImGui::PopID();
int counter = 1;
int loadedImages = 0;
{
scanner.get_mutex().lock();
for (const auto& game : scanner.get_game_list())
@ -2393,8 +2394,10 @@ static void gui_display_content()
{
// Get the boxart texture. Load it if needed.
textureId = imguiDriver->getTexture(art->boxartPath);
if (textureId == ImTextureID())
if (textureId == ImTextureID() && loadedImages < 10)
{
// Load 10 images max per frame
loadedImages++;
int width, height;
u8 *imgData = loadImage(art->boxartPath, width, height);
if (imgData != nullptr)

View File

@ -740,6 +740,20 @@ struct retro_core_option_v2_definition option_defs_us[] = {
},
"enabled",
},
{
CORE_OPTION_NAME "_network_output",
"Broadcast Digital Outputs",
NULL,
"Broadcast digital outputs and force-feedback state on TCP port 8000. Compatible with the \"-output network\" MAME option.",
NULL,
"input",
{
{ "disabled", NULL },
{ "enabled", NULL },
{ NULL, NULL },
},
"disabled",
},
{
CORE_OPTION_NAME "_show_lightgun_settings",
"Show Light Gun Settings",

View File

@ -117,6 +117,7 @@ Option<bool> GGPOEnable("", false);
Option<int> GGPODelay("", 0);
Option<bool> NetworkStats("", false);
Option<int> GGPOAnalogAxes("", 0);
Option<bool> NetworkOutput(CORE_OPTION_NAME "_network_output", false);
// Maple