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:
parent
7982fdac1c
commit
11ecb473b6
|
@ -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)
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -166,3 +166,4 @@ struct InputDescriptors
|
|||
};
|
||||
|
||||
extern InputDescriptors *NaomiGameInputs;
|
||||
extern bool atomiswaveForceFeedback;
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue