Merge remote-tracking branch 'origin/dev'

This commit is contained in:
Flyinghead 2025-02-18 09:27:07 +01:00
commit 0bf9c64b1b
104 changed files with 5621 additions and 1338 deletions

View File

@ -16,7 +16,7 @@ jobs:
version: '14.2'
pkginstall: sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libevdev libudev-devd libzip lua54 miniupnpc ninja pkgconf pulseaudio sdl2 libcdio
- operating_system: netbsd
version: '10.0'
version: '10.1'
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache cmake gcc12 git libao libzip lua54 miniupnpc ninja-build pkgconf pulseaudio SDL2 libcdio && export PATH=/usr/pkg/gcc12/bin:$PATH
- operating_system: openbsd
version: '7.6'
@ -36,7 +36,7 @@ jobs:
key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ github.sha }}
restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-
- uses: cross-platform-actions/action@v0.26.0
- uses: cross-platform-actions/action@v0.27.0
with:
operating_system: ${{ matrix.operating_system }}
architecture: ${{ matrix.architecture }}

2
.gitmodules vendored
View File

@ -43,4 +43,4 @@
url = https://github.com/google/googletest.git
[submodule "core/deps/asio"]
path = core/deps/asio
url = https://github.com/chriskohlhoff/asio.git
url = https://github.com/flyinghead/asio.git

View File

@ -147,6 +147,13 @@ if(NINTENDO_SWITCH)
if(USE_GLES)
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES)
endif()
# asio
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASIO_DISABLE_LOCAL_SOCKETS
ASIO_DISABLE_SERIAL_PORT
ESHUTDOWN=110
SA_RESTART=0
SA_NOCLDWAIT=0)
elseif(LIBRETRO)
add_library(${PROJECT_NAME} SHARED core/emulator.cpp)
@ -220,6 +227,10 @@ else()
add_executable(${PROJECT_NAME} core/emulator.cpp)
endif()
set_target_properties(${PROJECT_NAME} PROPERTIES
CMAKE_CXX_STANDARD 20
CMAKE_CXX_STANDARD_REQUIRED ON)
if(WINDOWS_STORE)
set(USE_OPENGL OFF)
set(USE_VULKAN OFF)
@ -255,6 +266,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE
$<$<BOOL:${MSVC}>:NOMINMAX>
$<$<BOOL:${TEST_AUTOMATION}>:TEST_AUTOMATION>
$<$<BOOL:${WINDOWS_STORE}>:NOCRYPT>
$<$<BOOL:${WINDOWS_STORE}>:_WIN32_WINNT=0x0A00>
$<$<OR:$<BOOL:${MINGW}>,$<BOOL:${MSVC}>>:_USE_MATH_DEFINES>)
if(UNIX AND NOT ANDROID AND NOT APPLE)
@ -846,6 +858,9 @@ if(LIBRETRO)
if(APPLE)
target_sources(${PROJECT_NAME} PRIVATE shell/libretro/oslib_apple.mm)
endif()
if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE mswsock)
endif()
endif()
target_sources(${PROJECT_NAME} PRIVATE
@ -1181,7 +1196,11 @@ target_sources(${PROJECT_NAME} PRIVATE
core/network/output.cpp
core/network/output.h
core/network/picoppp.cpp
core/network/picoppp.h)
core/network/picoppp.h
core/network/netservice.cpp
core/network/netservice.h
core/network/dcnet.cpp
core/network/dcnet.h)
if(ANDROID)
target_sources(${PROJECT_NAME} PRIVATE
@ -1904,7 +1923,10 @@ if(BUILD_TESTING)
tests/src/serialize_test.cpp
tests/src/AicaArmTest.cpp
tests/src/Sh4InterpreterTest.cpp
tests/src/MmuTest.cpp)
tests/src/MmuTest.cpp
tests/src/util/PeriodicThreadTest.cpp
tests/src/util/TsQueueTest.cpp
tests/src/util/WorkerThreadTest.cpp)
endif()
if(NINTENDO_SWITCH)

View File

@ -37,8 +37,9 @@
#include <atomic>
#include <utility>
#include <xxhash.h>
#include <thread>
#include <functional>
#include "util/worker_thread.h"
#include "util/periodic_thread.h"
namespace achievements
{
@ -74,10 +75,8 @@ private:
std::string getOrDownloadImage(const char *url);
std::pair<std::string, bool> getCachedImage(const char *url);
void diskChange();
void asyncTask(std::function<void()> f);
void startThread();
void stopThread();
void backgroundThread();
void asyncTask(std::function<void()>&& f);
void stopThreads();
static void clientLoginWithTokenCallback(int result, const char *error_message, rc_client_t *client, void *userdata);
static void clientLoginWithPasswordCallback(int result, const char *error_message, rc_client_t *client, void *userdata);
@ -112,11 +111,12 @@ private:
std::string cachePath;
std::unordered_map<u64, std::string> cacheMap;
std::mutex cacheMutex;
std::vector<std::function<void()>> tasks;
std::mutex taskMutex;
std::thread taskThread;
cResetEvent resetEvent;
bool threadRunning = false;
WorkerThread taskThread {"RA-background"};
PeriodicThread idleThread { "RA-idle", [this]() {
if (active)
rc_client_idle(rc_client);
}};
};
bool init() {
@ -176,6 +176,7 @@ Achievements::Achievements()
EventManager::listen(Event::Pause, emuEventCallback, this);
EventManager::listen(Event::Resume, emuEventCallback, this);
EventManager::listen(Event::DiskChange, emuEventCallback, this);
idleThread.setPeriod(1000);
}
Achievements::~Achievements()
@ -188,44 +189,13 @@ Achievements::~Achievements()
term();
}
void Achievements::asyncTask(std::function<void()> f)
{
{
std::lock_guard<std::mutex> _(taskMutex);
tasks.emplace_back(f);
}
resetEvent.Set();
void Achievements::asyncTask(std::function<void()>&& f) {
taskThread.run(std::move(f));
}
void Achievements::startThread()
{
threadRunning = true;
taskThread = std::thread(&Achievements::backgroundThread, this);
}
void Achievements::stopThread()
{
threadRunning = false;
resetEvent.Set();
if (taskThread.joinable())
taskThread.join();
}
void Achievements::backgroundThread()
{
ThreadName _("RA-background");
while (threadRunning)
{
if (!resetEvent.Wait(1000) && active && paused)
rc_client_idle(rc_client);
std::vector<std::function<void()>> localTasks;
{
std::lock_guard<std::mutex> _(taskMutex);
std::swap(tasks, localTasks);
}
for (auto& f : localTasks)
f();
}
void Achievements::stopThreads() {
taskThread.stop();
idleThread.stop();
}
bool Achievements::init()
@ -243,7 +213,6 @@ bool Achievements::init()
//rc_client_set_unofficial_enabled(rc_client, 0);
//rc_client_set_spectator_mode_enabled(rc_client, 0);
loadCache();
startThread();
if (!config::AchievementsUserName.get().empty() && !config::AchievementsToken.get().empty())
{
@ -357,7 +326,7 @@ void Achievements::term()
if (rc_client == nullptr)
return;
unloadGame();
stopThread();
stopThreads();
rc_client_destroy(rc_client);
rc_client = nullptr;
}
@ -813,8 +782,11 @@ std::string Achievements::getGameHash()
return hash;
}
void Achievements::pauseGame() {
void Achievements::pauseGame()
{
paused = true;
if (active)
idleThread.start();
}
void Achievements::resumeGame()
@ -822,6 +794,7 @@ void Achievements::resumeGame()
paused = false;
if (config::EnableAchievements && !settings.naomi.slave)
{
idleThread.stop();
loadGame();
if (settings.raHardcoreMode && !config::AchievementsHardcoreMode)
{
@ -955,8 +928,7 @@ void Achievements::unloadGame()
paused = false;
EventManager::unlisten(Event::VBlank, emuEventCallback, this);
// wait for all async tasks before unloading the game
stopThread();
startThread();
stopThreads();
rc_client_unload_game(rc_client);
settings.raHardcoreMode = false;
}

View File

@ -105,7 +105,11 @@ Option<int> TextureFiltering("rend.TextureFiltering", 0); // Default
Option<bool> ThreadedRendering("rend.ThreadedRendering", true);
Option<bool> DupeFrames("rend.DupeFrames", false);
Option<int> PerPixelLayers("rend.PerPixelLayers", 32);
#ifdef TARGET_UWP
Option<bool> NativeDepthInterpolation("rend.NativeDepthInterpolation", true);
#else
Option<bool> NativeDepthInterpolation("rend.NativeDepthInterpolation", false);
#endif
Option<bool> EmulateFramebuffer("rend.EmulateFramebuffer", false);
Option<bool> FixUpscaleBleedingEdge("rend.FixUpscaleBleedingEdge", true);
Option<bool> CustomGpuDriver("rend.CustomGpuDriver", false);
@ -135,6 +139,7 @@ Option<bool, false> DiscordPresence("DiscordPresence", true);
#if defined(__ANDROID__) && !defined(LIBRETRO)
Option<bool, false> UseSafFilePicker("UseSafFilePicker", true);
#endif
OptionString LogServer("LogServer", "", "log");
// Profiler
Option<bool> ProfilerEnabled("Profiler.Enabled");
@ -161,6 +166,8 @@ Option<int> GGPOChatTimeout("GGPOChatTimeout", 10, "network");
Option<bool> NetworkOutput("NetworkOutput", false, "network");
Option<int> MultiboardSlaves("MultiboardSlaves", 1, "network");
Option<bool> BattleCableEnable("BattleCable", false, "network");
Option<bool> UseDCNet("DCNet", false, "network");
OptionString ISPUsername("ISPUsername", "flycast1", "network");
#ifdef USE_OMX
Option<int> OmxAudioLatency("audio_latency", 100, "omx");

View File

@ -495,6 +495,7 @@ extern Option<bool, false> DiscordPresence;
#if defined(__ANDROID__) && !defined(LIBRETRO)
extern Option<bool, false> UseSafFilePicker;
#endif
extern OptionString LogServer;
// Profiling
extern Option<bool> ProfilerEnabled;
@ -521,6 +522,8 @@ extern Option<int> GGPOChatTimeout;
extern Option<bool> NetworkOutput;
extern Option<int> MultiboardSlaves;
extern Option<bool> BattleCableEnable;
extern Option<bool> UseDCNet;
extern OptionString ISPUsername;
#ifdef USE_OMX
extern Option<int> OmxAudioLatency;

View File

@ -83,7 +83,7 @@ const WidescreenCheat CheatManager::widescreen_cheats[] =
{ "T30006M", nullptr, { 0x4CF42C, 0x4CF45C, 0x3E1A36, 0x3E1A34, 0x3E1A3C, 0x3E1A54, 0x3E1A5C },
{ 0x43F00000, 0x3F400000, 0x08010000, 0, 0, 0, 0 } },
{ "MK-5103750", nullptr, { 0x1FE270 }, { 0x43700000 } }, // Daytona USA (PAL)
{ "MK-51037", nullptr, { 0x1FC6D0 }, { 0x43700000 } }, // Daytona USA (USA)
// breaks online connection { "MK-51037", nullptr, { 0x1FC6D0 }, { 0x43700000 } }, // Daytona USA (USA)
{ "T9501N-50", nullptr, { 0x9821D4 }, { 0x3F400000 } }, // Deadly Skies (PAL)
{ "T8116D 50", nullptr, { 0x2E5530 }, { 0x43700000 } }, // Dead or Alive 2 (PAL)
{ "T3601N", nullptr, { 0x2F0670 }, { 0x43700000 } }, // Dead or Alive 2 (USA)
@ -489,6 +489,51 @@ void CheatManager::reset(const std::string& gameId)
// force logging on to use more cycles
cheats.emplace_back(Cheat::Type::setValue, "enable logging", true, 32, 0x00314228, 1, true);
}
// Dricas auth bypass
else if (gameId == "T6807M") // Aero Dancing i
{
// modem
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x0004b7a0, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x0004b7a0, 0xe000000b, true); // rts, _mov #0, r0
// BBA
cheats.emplace_back(Cheat::Type::runNextIfEq, "bba bypass auth ifeq", true, 32, 0x0004af5c, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bba bypass dricas auth", true, 32, 0x0004af5c, 0xe000000b, true);
// IP check
cheats.emplace_back(Cheat::Type::runNextIfEq, "ip check ifeq", true, 32, 0x00020860, 0x4f222fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "ip check ok", true, 32, 0x00020860, 0xe000000b, true);
}
else if (gameId == "T6809M") // Aero Dancing i - Jikai Saku Made Matemasen
{
// modem
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x0004b940, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x0004b940, 0xe000000b, true);
// BBA
cheats.emplace_back(Cheat::Type::runNextIfEq, "bba bypass auth ifeq", true, 32, 0x0004f848, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bba bypass dricas auth", true, 32, 0x0004f848, 0xe000000b, true);
// IP check
cheats.emplace_back(Cheat::Type::runNextIfEq, "ip check ifeq", true, 32, 0x00020980, 0x4f222fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "ip check ok", true, 32, 0x00020980, 0xe000000b, true);
}
else if (gameId == "HDR-0106") { // Daytona USA (JP)
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x0003ad30, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x0003ad30, 0xe000000b, true);
}
else if (gameId == "HDR-0073") { // Sega Tetris
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x000a56f8, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x000a56f8, 0xe000000b, true);
}
else if (gameId == "T44501M") { // Golf Shiyou Yo 2
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x0013f150, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x0013f150, 0xe000000b, true);
}
else if (gameId == "HDR-0124") { // Hundred Swords
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x006558ac, 0x1f414f22, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x006558ac, 0xe000000b, true);
}
else if (gameId == "T43903M") { // Culdcept II
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x00800524, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x00800524, 0xe000000b, true);
}
if (cheats.size() > cheatCount)
setActive(true);

@ -1 +1 @@
Subproject commit 9c821dc21ccbd69b2bda421fdb35cb4ae2da8f5e
Subproject commit 7a44b1ab002cee6efa56d3b4c0e146b7fbaed80b

@ -1 +1 @@
Subproject commit 03ae834edbace31a96157b89bf50e5ee464e5ef9
Subproject commit d3402006e84efb6114ff93e4f2b8508412ed80d5

View File

@ -13,7 +13,7 @@
template<class T, int N> class RingBuffer
{
public:
RingBuffer<T, N>() :
RingBuffer() :
_head(0),
_tail(0),
_size(0) {

View File

@ -13,7 +13,7 @@
template<class T, int N> class StaticBuffer
{
public:
StaticBuffer<T, N>() :
StaticBuffer() :
_size(0) {
}

@ -1 +1 @@
Subproject commit fab7b33b896a42dcc865ba5ecdbacd9f409137f8
Subproject commit 5d21e35633a1f87ed08af115b07d3386096f792b

View File

@ -338,7 +338,7 @@ receiveDevicesFromMiniSSDPD(int s, int * error)
#ifdef DEBUG
printf(" usnsize=%u\n", usnsize);
#endif /* DEBUG */
tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize+usnsize);
tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize+usnsize+3);
if(tmp == NULL) {
if (error)
*error = MINISSDPC_MEMORY_ERROR;

View File

@ -9,7 +9,7 @@
#include "pico_frame.h"
#include "pico_constants.h"
#define PICO_MAX_TIMERS 20
#define PICO_MAX_TIMERS 50
#define PICO_ETH_MRU (1514u)
#define PICO_IP_MRU (1500u)

View File

@ -190,7 +190,6 @@ static int socket_tcp_do_deliver(struct pico_socket *s, struct pico_frame *f)
return 0;
}
dbg("TCP SOCKET> Not s.\n");
return -1;
}

View File

@ -19,7 +19,7 @@
#include "bba.h"
#include "rtl8139c.h"
#include "hw/holly/holly_intc.h"
#include "network/picoppp.h"
#include "network/netservice.h"
#include "serialize.h"
static RTL8139State *rtl8139device;
@ -79,7 +79,7 @@ void bba_Term()
{
if (rtl8139device != nullptr)
{
stop_pico();
net::modbba::stop();
rtl8139_destroy(rtl8139device);
rtl8139device = nullptr;
}
@ -179,9 +179,9 @@ void bba_WriteMem(u32 addr, u32 data, u32 sz)
case GAPS_RESET:
if (data & 1)
{
DEBUG_LOG(NETWORK, "GAPS reset");
INFO_LOG(NETWORK, "BBA: GAPS reset");
rtl8139_reset(rtl8139device);
start_pico();
net::modbba::stop();
}
break;
@ -208,12 +208,12 @@ void bba_WriteMem(u32 addr, u32 data, u32 sz)
ssize_t qemu_send_packet(RTL8139State *s, const uint8_t *buf, int size)
{
pico_receive_eth_frame(buf, size);
net::modbba::receiveEthFrame(buf, size);
return size;
}
int pico_send_eth_frame(const u8 *data, u32 len)
int bba_recv_frame(const u8 *data, u32 len)
{
if (!rtl8139_can_receive(rtl8139device))
return 0;
@ -249,7 +249,7 @@ void bba_Deserialize(Deserializer& deser)
deser >> interruptPending;
// returns true if the receiver is enabled and the network stack must be started
if (rtl8139_deserialize(rtl8139device, deser))
start_pico();
net::modbba::start();
}
#define POLYNOMIAL_BE 0x04c11db6

View File

@ -26,3 +26,4 @@ u32 bba_ReadMem(u32 addr, u32 sz);
void bba_WriteMem(u32 addr, u32 data, u32 sz);
void bba_Serialize(Serializer& ser);
void bba_Deserialize(Deserializer& deser);
int bba_recv_frame(const u8 *data, u32 len);

View File

@ -1728,8 +1728,7 @@ static void rtl8139_io_writeb(void *opaque, uint8_t addr, uint32_t val)
break;
default:
INFO_LOG(NETWORK, "not implemented write(b) addr=0x%x val=0x%02x", addr,
val);
DEBUG_LOG(NETWORK, "not implemented write(b) addr=0x%x val=0x%02x", addr, val);
break;
}
}

View File

@ -49,7 +49,7 @@ static std::string getRomPrefix()
}
}
static void add_isp_to_nvmem(DCFlashChip *flash)
static void add_isp_to_nvmem(DCFlashChip *flash, const char *username)
{
u8 block[64];
if (!flash->ReadBlock(FLASH_PT_USER, FLASH_USER_INET, block))
@ -79,11 +79,14 @@ static void add_isp_to_nvmem(DCFlashChip *flash)
flash_isp1_block isp1{};
isp1._unknown[3] = 1;
memcpy(isp1.sega, "SEGA", 4);
strcpy(isp1.username, "flycast1");
if (username == nullptr || username[0] == '\0')
strcpy(isp1.username, "flycast1");
else
strncpy(isp1.username, username, sizeof(isp1.username) - 1);
strcpy(isp1.password, "password");
strcpy(isp1.phone, "1234567");
if (flash->WriteBlock(FLASH_PT_USER, FLASH_USER_ISP1, &isp1) != 1)
WARN_LOG(FLASHROM, "Failed to save ISP information to flash RAM");
WARN_LOG(FLASHROM, "Failed to save ISP1 information to flash RAM");
memset(block, 0, sizeof(block));
flash->WriteBlock(FLASH_PT_USER, FLASH_USER_ISP1 + 1, block);
@ -97,11 +100,14 @@ static void add_isp_to_nvmem(DCFlashChip *flash)
flash_isp2_block isp2{};
memcpy(isp2.sega, "SEGA", 4);
strcpy(isp2.username, "flycast2");
if (username == nullptr || username[0] == '\0')
strcpy(isp2.username, "flycast2");
else
strncpy(isp2.username, username, sizeof(isp2.username) - 1);
strcpy(isp2.password, "password");
strcpy(isp2.phone, "1234567");
if (flash->WriteBlock(FLASH_PT_USER, FLASH_USER_ISP2, &isp2) != 1)
WARN_LOG(FLASHROM, "Failed to save ISP information to flash RAM");
WARN_LOG(FLASHROM, "Failed to save ISP2 information to flash RAM");
u8 block[64];
memset(block, 0, sizeof(block));
for (u32 i = FLASH_USER_ISP2 + 1; i <= 0xEA; i++)
@ -113,6 +119,22 @@ static void add_isp_to_nvmem(DCFlashChip *flash)
flash->WriteBlock(FLASH_PT_USER, i, block);
}
}
else if (username != nullptr && username[0] != '\0')
{
flash_isp1_block isp1{};
flash->ReadBlock(FLASH_PT_USER, FLASH_USER_ISP1, &isp1);
memset(isp1.username, 0, sizeof(isp1.username));
strncpy(isp1.username, username, sizeof(isp1.username) - 1);
if (flash->WriteBlock(FLASH_PT_USER, FLASH_USER_ISP1, &isp1) != 1)
WARN_LOG(FLASHROM, "Failed to save ISP1 information to flash RAM");
flash_isp2_block isp2{};
flash->ReadBlock(FLASH_PT_USER, FLASH_USER_ISP2, &isp2);
memset(isp2.username, 0, sizeof(isp2.username));
strncpy(isp2.username, username, sizeof(isp2.username) - 1);
if (flash->WriteBlock(FLASH_PT_USER, FLASH_USER_ISP2, &isp2) != 1)
WARN_LOG(FLASHROM, "Failed to save ISP2 information to flash RAM");
}
}
static void fixUpDCFlash()
@ -162,7 +184,7 @@ static void fixUpDCFlash()
if (static_cast<DCFlashChip*>(sys_nvmem)->WriteBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg) != 1)
WARN_LOG(FLASHROM, "Failed to save time and language to flash RAM");
add_isp_to_nvmem(static_cast<DCFlashChip*>(sys_nvmem));
add_isp_to_nvmem(static_cast<DCFlashChip*>(sys_nvmem), config::ISPUsername.get().c_str());
// Check the console ID used by some network games (chuchu rocket)
u8 *console_id = &sys_nvmem->data[0x1A058];

View File

@ -140,6 +140,10 @@ void DmaBuffer::deserialize(Deserializer& deser)
{
deser >> index;
deser >> size;
if (index >= sizeof(cache))
clear();
else
size = std::min<u32>(size, sizeof(cache) - index);
deser >> cache;
deser.skip(2352 * 16);
}
@ -1209,9 +1213,12 @@ static int getGDROMTicks()
return 512;
u32 len = SB_GDLEN == 0 ? 0x02000000 : SB_GDLEN;
if (len - SB_GDLEND > 10240)
return 1100000; // Large transfers: GD-ROM transfer rate 1.8 MB/s
// Large transfers: GD-ROM transfer rate 1.8 MB/s
return sh4CyclesForXfer(10240, 1'800'000);
else
return std::min((u32)10240, len - SB_GDLEND) * 2; // Small transfers: Max G1 bus rate: 50 MHz x 16 bits
// Small transfers: Max G1 bus rate: 50 MHz x 16 bits
// ...slowed down to 25 MB/s for wsb2k2
return sh4CyclesForXfer(std::min<u32>(10240, len - SB_GDLEND), 25'000'000);
}
else
return 0;

View File

@ -213,8 +213,8 @@ bool maple_atomiswave_coin_chute(int slot)
static void mcfg_Create(MapleDeviceType type, u32 bus, u32 port, s32 player_num = -1)
{
delete MapleDevices[bus][port];
maple_device* dev = maple_Create(type);
MapleDevices[bus][port].reset();
std::shared_ptr<maple_device> dev = maple_Create(type);
dev->Setup(bus, port, player_num);
}
@ -364,7 +364,7 @@ static void vmuDigest()
for (int i = 0; i < MAPLE_PORTS; i++)
for (int j = 0; j < 6; j++)
{
const maple_device* device = MapleDevices[i][j];
std::shared_ptr<const maple_device> device = MapleDevices[i][j];
if (device != nullptr)
{
size_t size;
@ -414,8 +414,7 @@ void mcfg_DestroyDevices(bool full)
if (MapleDevices[i][j] != nullptr
&& (full || MapleDevices[i][j]->get_device_type() != MDT_NaomiJamma))
{
delete MapleDevices[i][j];
MapleDevices[i][j] = nullptr;
MapleDevices[i][j].reset();
}
}
}
@ -435,7 +434,7 @@ void mcfg_SerializeDevices(Serializer& ser)
for (int j = 0; j < 6; j++)
{
u8 deviceType = MDT_None;
maple_device* device = MapleDevices[i][j];
std::shared_ptr<maple_device> device = MapleDevices[i][j];
if (device != nullptr)
deviceType = device->get_device_type();
ser << deviceType;
@ -490,9 +489,9 @@ void mcfg_DeserializeDevices(Deserializer& deser)
memcpy(EEPROM, eeprom, sizeof(eeprom));
}
maple_naomi_jamma *getMieDevice()
std::shared_ptr<maple_naomi_jamma> getMieDevice()
{
if (MapleDevices[0][5] == nullptr || MapleDevices[0][5]->get_device_type() != MDT_NaomiJamma)
return nullptr;
return (maple_naomi_jamma *)MapleDevices[0][5];
return std::static_pointer_cast<maple_naomi_jamma>(MapleDevices[0][5]);
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "types.h"
#include <cstring>
#include <memory>
enum MapleDeviceType
{
@ -123,4 +124,4 @@ const u8 *getRfidCardData(int playerNum);
void setRfidCardData(int playerNum, u8 *data);
struct maple_naomi_jamma;
maple_naomi_jamma *getMieDevice();
std::shared_ptr<maple_naomi_jamma> getMieDevice();

View File

@ -44,7 +44,7 @@ void maple_device::Setup(u32 bus, u32 port, int playerNum)
config = new MapleConfigMap(this);
OnSetup();
MapleDevices[bus][port] = this;
MapleDevices[bus][port] = shared_from_this();
}
maple_device::~maple_device()
{
@ -368,7 +368,7 @@ struct maple_sega_vmu: maple_base
}
fullSaveNeeded = true;
}
bool fullSave()
{
if (file == nullptr)
@ -402,7 +402,7 @@ struct maple_sega_vmu: maple_base
{
memset(flash_data, 0, sizeof(flash_data));
memset(lcd_data, 0, sizeof(lcd_data));
// Load existing vmu file if found
std::string rpath = hostfs::getVmuPath(logical_port, false);
// this might be a storage url
@ -2047,56 +2047,56 @@ struct RFIDReaderWriter : maple_base
void insertRfidCard(int playerNum)
{
maple_device *mapleDev = MapleDevices[1 + playerNum][5];
std::shared_ptr<maple_device> mapleDev = MapleDevices[1 + playerNum][5];
if (mapleDev != nullptr && mapleDev->get_device_type() == MDT_RFIDReaderWriter)
((RFIDReaderWriter *)mapleDev)->insertCard();
std::static_pointer_cast<RFIDReaderWriter>(mapleDev)->insertCard();
}
void setRfidCardData(int playerNum, u8 *data)
{
maple_device *mapleDev = MapleDevices[1 + playerNum][5];
std::shared_ptr<maple_device> mapleDev = MapleDevices[1 + playerNum][5];
if (mapleDev != nullptr && mapleDev->get_device_type() == MDT_RFIDReaderWriter)
((RFIDReaderWriter *)mapleDev)->setCardData(data);
std::static_pointer_cast<RFIDReaderWriter>(mapleDev)->setCardData(data);
}
const u8 *getRfidCardData(int playerNum)
{
maple_device *mapleDev = MapleDevices[1 + playerNum][5];
std::shared_ptr<maple_device> mapleDev = MapleDevices[1 + playerNum][5];
if (mapleDev != nullptr && mapleDev->get_device_type() == MDT_RFIDReaderWriter)
return ((RFIDReaderWriter *)mapleDev)->getCardData();
return std::static_pointer_cast<RFIDReaderWriter>(mapleDev)->getCardData();
else
return nullptr;
}
maple_device* maple_Create(MapleDeviceType type)
std::shared_ptr<maple_device> maple_Create(MapleDeviceType type)
{
switch(type)
{
case MDT_SegaController:
if (!settings.platform.isAtomiswave())
return new maple_sega_controller();
return std::make_shared<maple_sega_controller>();
else
return new maple_atomiswave_controller();
case MDT_Microphone: return new maple_microphone();
case MDT_SegaVMU: return new maple_sega_vmu();
case MDT_PurupuruPack: return new maple_sega_purupuru();
case MDT_Keyboard: return new maple_keyboard();
case MDT_Mouse: return new maple_mouse();
return std::make_shared<maple_atomiswave_controller>();
case MDT_Microphone: return std::make_shared<maple_microphone>();
case MDT_SegaVMU: return std::make_shared<maple_sega_vmu>();
case MDT_PurupuruPack: return std::make_shared<maple_sega_purupuru>();
case MDT_Keyboard: return std::make_shared<maple_keyboard>();
case MDT_Mouse: return std::make_shared<maple_mouse>();
case MDT_LightGun:
if (!settings.platform.isAtomiswave())
return new maple_lightgun();
return std::make_shared<maple_lightgun>();
else
return new atomiswave_lightgun();
case MDT_NaomiJamma: return new maple_naomi_jamma();
case MDT_TwinStick: return new maple_sega_twinstick();
case MDT_AsciiStick: return new maple_ascii_stick();
case MDT_MaracasController: return new maple_maracas_controller();
case MDT_FishingController: return new maple_fishing_controller();
case MDT_PopnMusicController: return new maple_popnmusic_controller();
case MDT_RacingController: return new maple_racing_controller();
case MDT_DenshaDeGoController: return new maple_densha_controller();
case MDT_SegaControllerXL: return new FullController();
case MDT_RFIDReaderWriter: return new RFIDReaderWriter();
return std::make_shared<atomiswave_lightgun>();
case MDT_NaomiJamma: return std::make_shared<maple_naomi_jamma>();
case MDT_TwinStick: return std::make_shared<maple_sega_twinstick>();
case MDT_AsciiStick: return std::make_shared<maple_ascii_stick>();
case MDT_MaracasController: return std::make_shared<maple_maracas_controller>();
case MDT_FishingController: return std::make_shared<maple_fishing_controller>();
case MDT_PopnMusicController: return std::make_shared<maple_popnmusic_controller>();
case MDT_RacingController: return std::make_shared<maple_racing_controller>();
case MDT_DenshaDeGoController: return std::make_shared<maple_densha_controller>();
case MDT_SegaControllerXL: return std::make_shared<FullController>();
case MDT_RFIDReaderWriter: return std::make_shared<RFIDReaderWriter>();
default:
ERROR_LOG(MAPLE, "Invalid device type %d", type);
@ -2106,8 +2106,10 @@ maple_device* maple_Create(MapleDeviceType type)
return nullptr;
}
#if defined(_WIN32) && !defined(TARGET_UWP) && defined(USE_SDL) && !defined(LIBRETRO)
#if (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))) && !defined(TARGET_UWP) && defined(USE_SDL) && !defined(LIBRETRO)
#include "sdl/dreamconn.h"
#include <list>
#include <memory>
struct DreamConnVmu : public maple_sega_vmu
{
@ -2131,7 +2133,7 @@ struct DreamConnVmu : public maple_sega_vmu
return maple_sega_vmu::dma(cmd);
}
void copy(maple_sega_vmu *other)
void copyIn(std::shared_ptr<maple_sega_vmu> other)
{
memcpy(flash_data, other->flash_data, sizeof(flash_data));
memcpy(lcd_data, other->lcd_data, sizeof(lcd_data));
@ -2139,6 +2141,14 @@ struct DreamConnVmu : public maple_sega_vmu
fullSaveNeeded = other->fullSaveNeeded;
}
void copyOut(std::shared_ptr<maple_sega_vmu> other)
{
memcpy(other->flash_data, flash_data, sizeof(other->flash_data));
memcpy(other->lcd_data, lcd_data, sizeof(other->lcd_data));
memcpy(other->lcd_data_decoded, lcd_data_decoded, sizeof(other->lcd_data_decoded));
other->fullSaveNeeded = fullSaveNeeded;
}
void updateScreen()
{
MapleMsg msg;
@ -2170,32 +2180,111 @@ struct DreamConnPurupuru : public maple_sega_purupuru
}
};
static std::list<std::shared_ptr<DreamConnVmu>> dreamConnVmus;
static std::list<std::shared_ptr<DreamConnPurupuru>> dreamConnPurupurus;
void createDreamConnDevices(std::shared_ptr<DreamConn> dreamconn, bool gameStart)
{
const int bus = dreamconn->getBus();
bool vmuFound = false;
bool rumbleFound = false;
if (dreamconn->hasVmu())
{
maple_device *dev = MapleDevices[bus][0];
std::shared_ptr<DreamConnVmu> vmu;
for (const std::shared_ptr<DreamConnVmu>& vmuIter : dreamConnVmus)
{
if (vmuIter->dreamconn.get() == dreamconn.get())
{
vmuFound = true;
vmu = vmuIter;
break;
}
}
std::shared_ptr<maple_device> dev = MapleDevices[bus][0];
if (gameStart || (dev != nullptr && dev->get_device_type() == MDT_SegaVMU))
{
DreamConnVmu *vmu = new DreamConnVmu(dreamconn);
vmu->Setup(bus, 0);
if (!gameStart) {
// if loading a state, copy data from the regular vmu and send a screen update
vmu->copy(static_cast<maple_sega_vmu*>(dev));
vmu->updateScreen();
bool vmuCreated = false;
if (!vmu)
{
vmu = std::make_shared<DreamConnVmu>(dreamconn);
vmuCreated = true;
}
delete dev;
vmu->Setup(bus, 0);
if ((!gameStart || !vmuCreated) && dev) {
// if loading a state or DreamConnVmu existed, copy data from the regular vmu and send a screen update
vmu->copyIn(std::static_pointer_cast<maple_sega_vmu>(dev));
if (!gameStart) {
vmu->updateScreen();
}
}
if (!vmuFound) dreamConnVmus.push_back(vmu);
}
}
if (dreamconn->hasRumble())
{
maple_device *dev = MapleDevices[bus][1];
std::shared_ptr<DreamConnPurupuru> rumble;
for (const std::shared_ptr<DreamConnPurupuru>& purupuru : dreamConnPurupurus)
{
if (purupuru->dreamconn.get() == dreamconn.get())
{
rumbleFound = true;
rumble = purupuru;
break;
}
}
std::shared_ptr<maple_device> dev = MapleDevices[bus][1];
if (gameStart || (dev != nullptr && dev->get_device_type() == MDT_PurupuruPack))
{
delete dev;
DreamConnPurupuru *rumble = new DreamConnPurupuru(dreamconn);
if (!rumble)
{
rumble = std::make_shared<DreamConnPurupuru>(dreamconn);
}
rumble->Setup(bus, 1);
if (!rumbleFound) dreamConnPurupurus.push_back(rumble);
}
}
}
void tearDownDreamConnDevices(std::shared_ptr<DreamConn> dreamconn)
{
const int bus = dreamconn->getBus();
for (std::list<std::shared_ptr<DreamConnVmu>>::const_iterator iter = dreamConnVmus.begin();
iter != dreamConnVmus.end();)
{
if ((*iter)->dreamconn.get() == dreamconn.get())
{
std::shared_ptr<maple_device> dev = maple_Create(MDT_SegaVMU);
dev->Setup(bus, 0);
(*iter)->copyOut(std::static_pointer_cast<maple_sega_vmu>(dev));
iter = dreamConnVmus.erase(iter);
break;
}
else
{
++iter;
}
}
for (std::list<std::shared_ptr<DreamConnPurupuru>>::const_iterator iter = dreamConnPurupurus.begin();
iter != dreamConnPurupurus.end();)
{
if ((*iter)->dreamconn.get() == dreamconn.get())
{
std::shared_ptr<maple_device> dev = maple_Create(MDT_PurupuruPack);
dev->Setup(bus, 1);
iter = dreamConnPurupurus.erase(iter);
break;
}
else
{
++iter;
}
}
}

View File

@ -121,7 +121,7 @@ enum AWAVE_KEYS
AWAVE_TRIGGER_KEY = 1 << 17,
};
struct maple_device
struct maple_device : public std::enable_shared_from_this<maple_device>
{
u8 maple_port; //raw maple port
u8 bus_port; //0 .. 5
@ -150,7 +150,7 @@ struct maple_device
virtual const void *getData(size_t& size) const { size = 0; return nullptr; }
};
maple_device* maple_Create(MapleDeviceType type);
std::shared_ptr<maple_device> maple_Create(MapleDeviceType type);
#define MAPLE_PORTS 4

View File

@ -3,12 +3,12 @@
u32 maple_GetAttachedDevices(u32 bus)
{
verify(MapleDevices[bus][5]!=0);
verify(MapleDevices[bus][5]!=nullptr);
u32 rv=0;
for (int i=0;i<5;i++)
rv|=(MapleDevices[bus][i]!=0?1:0)<<i;
rv|=(MapleDevices[bus][i]!=nullptr?1:0)<<i;
return rv;
}

View File

@ -8,6 +8,8 @@
#include "network/ggpo.h"
#include "hw/naomi/card_reader.h"
#include <memory>
enum MaplePattern
{
MP_Start,
@ -17,7 +19,7 @@ enum MaplePattern
MP_NOP = 7
};
maple_device* MapleDevices[MAPLE_PORTS][6];
std::shared_ptr<maple_device> MapleDevices[MAPLE_PORTS][6];
int maple_schid;
@ -163,7 +165,8 @@ static void maple_DoDma()
}
const bool swap_msb = (SB_MMSEL == 0);
u32 xfer_count = 0;
u32 xferOut = 0;
u32 xferIn = 0;
bool last = false;
while (!last)
{
@ -201,13 +204,13 @@ static void maple_DoDma()
}
const u32 frame_header = swap_msb ? SWAP32(p_data[0]) : p_data[0];
//Command code
//Command code
u32 command = frame_header & 0xFF;
//Recipient address
//Recipient address
u32 reci = (frame_header >> 8) & 0xFF;//0-5;
//Sender address
//Sender address
//u32 send = (frame_header >> 16) & 0xFF;
//Number of additional words in frame
//Number of additional words in frame
u32 inlen = (frame_header >> 24) & 0xFF;
u32 port = getPort(reci);
@ -226,7 +229,8 @@ static void maple_DoDma()
inlen = (inlen + 1) * 4;
u32 outbuf[1024 / 4];
u32 outlen = MapleDevices[bus][port]->RawDma(&p_data[0], inlen, outbuf);
xfer_count += inlen + 3 + outlen + 3; // start, parity and stop bytes
xferIn += inlen + 3; // start, parity and stop bytes
xferOut += outlen + 3;
#ifdef STRICT_MODE
if (!check_mdapro(header_2 + outlen - 1))
{
@ -258,7 +262,7 @@ static void maple_DoDma()
u32 bus = (header_1 >> 16) & 3;
if (MapleDevices[bus][5]) {
SDCKBOccupied = SDCKBOccupied || MapleDevices[bus][5]->get_lightgun_pos();
xfer_count++;
xferIn++;
}
addr += 1 * 4;
}
@ -271,7 +275,7 @@ static void maple_DoDma()
case MP_Reset:
addr += 1 * 4;
xfer_count++;
xferIn++;
break;
case MP_NOP:
@ -285,9 +289,17 @@ static void maple_DoDma()
}
// Maple bus max speed: 2 Mb/s, actual speed: 1 Mb/s
//printf("Maple XFER size %d bytes - %.2f ms\n", xfer_count, xfer_count * 1000.0f / (128 * 1024));
// actual measured speed with protocol analyzer for devices (vmu?) is 724-738Kb/s
// See https://github.com/OrangeFox86/DreamcastControllerUsbPico/blob/main/measurements/Dreamcast-Power-Up-Digital-and-Analog-Player1-Controller-VMU-JumpPack.sal
if (!SDCKBOccupied)
sh4_sched_request(maple_schid, std::min((u64)xfer_count * (SH4_MAIN_CLOCK / (256 * 1024)), (u64)SH4_MAIN_CLOCK));
{
// 2 Mb/s from console
u32 cycles = sh4CyclesForXfer(xferIn, 2'000'000 / 8);
// 740 Kb/s from devices
cycles += sh4CyclesForXfer(xferOut, 740'000 / 8);
cycles = std::min<u32>(cycles, SH4_MAIN_CLOCK);
sh4_sched_request(maple_schid, cycles);
}
}
static int maple_schd(int tag, int cycles, int jitter, void *arg)

View File

@ -1,7 +1,8 @@
#pragma once
#include "maple_devs.h"
#include <memory>
extern maple_device* MapleDevices[MAPLE_PORTS][6];
extern std::shared_ptr<maple_device> MapleDevices[MAPLE_PORTS][6];
void maple_Init();
void maple_Reset(bool Manual);

View File

@ -627,6 +627,9 @@ protected:
in = (in & 0xAA) >> 1 | (in & 0x55) << 1;
out = process(in);
// The rest of the bits are for lamps
u8 lamps[2] = { data[0], (u8)(data[1] & 0xfc) };
jvs_837_13844::write_digital_out(2, lamps);
}
virtual u8 process(u8 in) = 0;

View File

@ -25,7 +25,7 @@
#include "modem_regs.h"
#include "hw/holly/holly_intc.h"
#include "hw/sh4/sh4_sched.h"
#include "network/picoppp.h"
#include "network/netservice.h"
#include "serialize.h"
#include "cfg/option.h"
#include "stdclass.h"
@ -127,8 +127,6 @@ static bool data_sent;
static u64 last_comm_stats;
static int sent_bytes;
static int recvd_bytes;
static FILE *recv_fp;
static FILE *sent_fp;
#endif
static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
@ -263,7 +261,7 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
dspram[0x208] = 0xff; // 2.4 - 19.2 kpbs supported
dspram[0x209] = 0xbf; // 21.6 - 33.6 kpbs supported, asymmetric supported
start_pico();
net::modbba::start();
connect_state = CONNECTED;
callback_cycles = SH4_MAIN_CLOCK / 1000000 * 238; // 238 us
data_sent = false;
@ -291,7 +289,7 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
// Let WinCE send data first to avoid choking it
if (!modem_regs.reg1e.RDBF && data_sent)
{
int c = read_pico();
int c = net::modbba::readModem();
if (c >= 0)
{
//LOG("pppd received %02x", c);
@ -328,7 +326,7 @@ void ModemInit()
void ModemReset()
{
stop_pico();
net::modbba::stop();
}
void ModemTerm()
@ -436,12 +434,12 @@ static void modem_reset(u32 v)
memset(&modem_regs, 0, sizeof(modem_regs));
state = MS_RESET;
LOG("Modem reset start ...");
net::modbba::stop();
}
else
{
if (state == MS_RESET)
{
stop_pico();
memset(&modem_regs, 0, sizeof(modem_regs));
state = MS_RESETING;
ControllerTestStart();
@ -470,13 +468,6 @@ static u8 download_crc;
static void ModemNormalWrite(u32 reg, u32 data)
{
#ifndef NDEBUG
if (recv_fp == NULL)
{
recv_fp = fopen("ppp_recv.dump", "w");
sent_fp = fopen("ppp_sent.dump", "w");
}
#endif
//if (!module_download && reg != 0x10)
// LOG("ModemNormalWrite : %03X=%X", reg,data);
u32 old = modem_regs.ptr[reg];
@ -525,10 +516,8 @@ static void ModemNormalWrite(u32 reg, u32 data)
data_sent = true;
#ifndef NDEBUG
sent_bytes++;
if (sent_fp)
fputc(data, sent_fp);
#endif
write_pico(data);
net::modbba::writeModem(data);
modem_regs.reg1e.TDBE = 0;
}
break;
@ -686,10 +675,6 @@ u32 ModemReadMem_A0_006(u32 addr, u32 size)
modem_regs.reg1e.RDBF = 0;
SET_STATUS_BIT(0x0c, modem_regs.reg0c.RXFNE, 0);
SET_STATUS_BIT(0x01, modem_regs.reg01.RXHF, 0);
#ifndef NDEBUG
if (connect_state == CONNECTED && recv_fp)
fputc(data, recv_fp);
#endif
update_interrupt();
}
else if (reg == 0x16 || reg == 0x17)

View File

@ -201,8 +201,6 @@ static void Naomi_DmaEnable(u32 addr, u32 data)
void naomi_reg_Init()
{
networkOutput.init();
static const u8 romSerialData[0x84] = {
0x19, 0x00, 0xaa, 0x55,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

View File

@ -586,7 +586,6 @@ const Game Games[] =
{ "opr-23968.ic20", 0x2000002, 0x800000, 0x0000000, InterleavedWord },
{ "opr-23969.ic21s", 0x3000000, 0x800000, 0x0000000, InterleavedWord },
{ "opr-23970.ic22", 0x3000002, 0x800000, 0x0000000, InterleavedWord },
{ NULL, 0, 0 },
}
},
// Soreike! Anpanman Popcorn Koujou 2 (Rev C)

View File

@ -101,6 +101,7 @@ void FinishRender(TA_context* ctx)
}
static std::mutex mtx_pool;
using Lock = std::lock_guard<std::mutex>;
static std::vector<TA_context*> ctx_pool;
static std::vector<TA_context*> ctx_list;
@ -108,17 +109,15 @@ static std::vector<TA_context*> ctx_list;
TA_context *tactx_Alloc()
{
TA_context *ctx = nullptr;
mtx_pool.lock();
if (!ctx_pool.empty())
{
ctx = ctx_pool.back();
ctx_pool.pop_back();
Lock _(mtx_pool);
if (!ctx_pool.empty()) {
ctx = ctx_pool.back();
ctx_pool.pop_back();
}
}
mtx_pool.unlock();
if (ctx == nullptr)
{
if (ctx == nullptr) {
ctx = new TA_context();
ctx->Alloc();
}
@ -129,17 +128,14 @@ static void tactx_Recycle(TA_context* ctx)
{
if (ctx->nextContext != nullptr)
tactx_Recycle(ctx->nextContext);
mtx_pool.lock();
if (ctx_pool.size() > 3)
{
Lock _(mtx_pool);
if (ctx_pool.size() > 3) {
delete ctx;
}
else
{
else {
ctx->Reset();
ctx_pool.push_back(ctx);
}
mtx_pool.unlock();
}
static TA_context *tactx_Find(u32 addr, bool allocnew)
@ -147,8 +143,7 @@ static TA_context *tactx_Find(u32 addr, bool allocnew)
TA_context *oldCtx = nullptr;
for (TA_context *ctx : ctx_list)
{
if (ctx->Address == addr)
{
if (ctx->Address == addr) {
ctx->lastFrameUsed = FrameCount;
return ctx;
}
@ -205,11 +200,10 @@ void tactx_Term()
delete ctx;
ctx_list.clear();
mtx_pool.lock();
Lock _(mtx_pool);
for (TA_context *ctx : ctx_pool)
delete ctx;
ctx_pool.clear();
mtx_pool.unlock();
}
const u32 NULL_CONTEXT = ~0u;

View File

@ -141,19 +141,11 @@ shil_compile( \
template<int Stride = 1>
static inline float innerProduct(const float *f1, const float *f2)
{
#if HOST_CPU == CPU_X86 || HOST_CPU == CPU_X64 || HOST_CPU == CPU_ARM64
const double f = (double)f1[0] * f2[Stride * 0]
+ (double)f1[1] * f2[Stride * 1]
+ (double)f1[2] * f2[Stride * 2]
+ (double)f1[3] * f2[Stride * 3];
return fixNaN((float)f);
#else
const float f = f1[0] * f2[Stride * 0]
+ f1[1] * f2[Stride * 1]
+ f1[2] * f2[Stride * 2]
+ f1[3] * f2[Stride * 3];
return fixNaN(f);
#endif
}
#endif
@ -727,27 +719,33 @@ shil_opc(cvt_f2i_t)
shil_canonical
(
u32,f1,(f32 f1),
if (f1 > 2147483520.0f) // IEEE 754: 0x4effffff
return 0x7fffffff;
else
{
s32 res = (s32)f1;
// Fix result sign for Intel CPUs
if ((u32)res == 0x80000000 && f1 == f1 && *(s32 *)&f1 > 0)
res = 0x7fffffff;
return res;
s32 res;
if (f1 > 2147483520.0f) { // IEEE 754: 0x4effffff
res = 0x7fffffff;
}
else {
res = (s32)f1;
// Fix result sign for Intel CPUs
if ((u32)res == 0x80000000 && f1 > 0)
res = 0x7fffffff;
}
return res;
)
#else
#elif HOST_CPU == CPU_ARM || HOST_CPU == CPU_ARM64
shil_canonical
(
u32,f1,(f32 f1),
if (f1 > 2147483520.0f) // IEEE 754: 0x4effffff
return 0x7fffffff;
else
return (s32)f1;
s32 res;
if (f1 > 2147483520.0f) { // IEEE 754: 0x4effffff
res = 0x7fffffff;
}
else {
res = (s32)f1;
// conversion of NaN returns 0 on ARM
if (std::isnan(f1))
res = 0x80000000;
}
return res;
)
#endif

View File

@ -519,28 +519,35 @@ sh4op(i1111_nnnn_0011_1101)
if (ctx->fpscr.PR == 0)
{
u32 n = GetN(op);
ctx->fpul = (u32)(s32)ctx->fr[n];
if ((s32)ctx->fpul > 0x7fffff80)
ctx->fpul = 0x7fffffff;
// Intel CPUs convert out of range float numbers to 0x80000000. Manually set the correct sign
else if (ctx->fpul == 0x80000000 && ctx->fr[n] == ctx->fr[n])
if (std::isnan(ctx->fr[n])) {
ctx->fpul = 0x80000000;
}
else
{
if (*(int *)&ctx->fr[n] > 0) // Using integer math to avoid issues with Inf and NaN
ctx->fpul = (u32)(s32)ctx->fr[n];
if ((s32)ctx->fpul > 0x7fffff80)
ctx->fpul = 0x7fffffff;
#if HOST_CPU == CPU_X86 || HOST_CPU == CPU_X64
// Intel CPUs convert out of range float numbers to 0x80000000. Manually set the correct sign
else if (ctx->fpul == 0x80000000 && ctx->fr[n] > 0)
ctx->fpul--;
#endif
}
}
else
{
f64 f = getDRn(ctx, op);
ctx->fpul = (u32)(s32)f;
// TODO saturate
// Intel CPUs convert out of range float numbers to 0x80000000. Manually set the correct sign
if (ctx->fpul == 0x80000000 && f == f)
if (std::isnan(f)) {
ctx->fpul = 0x80000000;
}
else
{
if (*(s64 *)&f > 0) // Using integer math to avoid issues with Inf and NaN
ctx->fpul = (u32)(s32)f;
#if HOST_CPU == CPU_X86 || HOST_CPU == CPU_X64
// Intel CPUs convert out of range float numbers to 0x80000000. Manually set the correct sign
if (ctx->fpul == 0x80000000 && f > 0)
ctx->fpul--;
#endif
}
}
}

View File

@ -53,3 +53,7 @@ void sh4_sched_serialize(Serializer& ser);
void sh4_sched_deserialize(Deserializer& deser);
void sh4_sched_serialize(Serializer& ser, int id);
void sh4_sched_deserialize(Deserializer& deser, int id);
constexpr u32 sh4CyclesForXfer(u32 sizeUnit, u32 unitPerSecond) {
return (u64)SH4_MAIN_CLOCK * sizeUnit / unitPerSecond;
}

View File

@ -329,7 +329,7 @@ bool GamepadDevice::gamepad_axis_input(u32 code, int value)
int threshold = 16384;
if (code == leftTrigger || code == rightTrigger )
threshold = 100;
if (std::abs(v) < threshold)
kcode[port] |= key; // button released
else
@ -554,6 +554,9 @@ void GamepadDevice::Register(const std::shared_ptr<GamepadDevice>& gamepad)
Lock _(_gamepads_mutex);
_gamepads.push_back(gamepad);
MapleConfigMap::UpdateVibration = updateVibration;
gamepad->_is_registered = true;
gamepad->registered();
}
void GamepadDevice::Unregister(const std::shared_ptr<GamepadDevice>& gamepad)

View File

@ -40,7 +40,7 @@ public:
virtual bool gamepad_btn_input(u32 code, bool pressed);
virtual bool gamepad_axis_input(u32 code, int value);
virtual ~GamepadDevice() = default;
void detect_btn_input(input_detected_cb button_pressed);
void detect_axis_input(input_detected_cb axis_moved);
void detectButtonOrAxisInput(input_detected_cb input_changed);
@ -91,6 +91,7 @@ public:
save_mapping();
}
}
bool is_registered() const { return _is_registered; }
static void Register(const std::shared_ptr<GamepadDevice>& gamepad);
static void Unregister(const std::shared_ptr<GamepadDevice>& gamepad);
@ -145,6 +146,7 @@ protected:
u32 rightTrigger = ~0;
private:
virtual void registered() {}
bool handleButtonInput(int port, DreamcastKey key, bool pressed);
std::string make_mapping_filename(bool instance, int system, bool perGame = false);
@ -189,6 +191,7 @@ private:
u64 _detection_start_time = 0;
input_detected_cb _input_detected;
bool _remappable;
bool _is_registered = false;
u32 digitalToAnalogState[4];
std::map<DreamcastKey, int> lastAxisValue[4];
bool perGameMapping = false;

View File

@ -87,6 +87,9 @@ void os_InstallFaultHandler()
#ifndef __SWITCH__
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, nullptr);
act.sa_sigaction = fault_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;

View File

@ -15,6 +15,7 @@
#include "ConsoleListener.h"
#include "InMemoryListener.h"
#include "NetworkListener.h"
#include "Log.h"
#include "StringUtil.h"
#include "cfg/cfg.h"
@ -118,8 +119,6 @@ LogManager::LogManager()
m_log[LogTypes::SAVESTATE] = {"SAVESTATE", "Save States"};
m_log[LogTypes::SH4] = {"SH4", "SH4 Modules"};
RegisterListener(LogListener::CONSOLE_LISTENER, new ConsoleListener());
// Set up log listeners
int verbosity = cfgLoadInt("log", "Verbosity", LogTypes::LDEBUG);
@ -128,47 +127,56 @@ LogManager::LogManager()
verbosity = 1;
if (verbosity > MAX_LOGLEVEL)
verbosity = MAX_LOGLEVEL;
SetLogLevel(static_cast<LogTypes::LOG_LEVELS>(verbosity));
if (cfgLoadBool("log", "LogToFile", false))
{
#if defined(__ANDROID__) || defined(__APPLE__) || defined(TARGET_UWP)
std::string logPath = get_writable_data_path("flycast.log");
#else
std::string logPath = "flycast.log";
#endif
FileLogListener *listener = new FileLogListener(logPath);
if (!listener->IsValid())
{
const char *home = nowide::getenv("HOME");
if (home != nullptr)
{
delete listener;
listener = new FileLogListener(home + ("/" + logPath));
}
}
RegisterListener(LogListener::FILE_LISTENER, listener);
EnableListener(LogListener::FILE_LISTENER, true);
}
RegisterListener(LogListener::CONSOLE_LISTENER, new ConsoleListener());
EnableListener(LogListener::CONSOLE_LISTENER, cfgLoadBool("log", "LogToConsole", true));
// EnableListener(LogListener::LOG_WINDOW_LISTENER, Config::Get(LOGGER_WRITE_TO_WINDOW));
RegisterListener(LogListener::IN_MEMORY_LISTENER, new InMemoryListener());
EnableListener(LogListener::IN_MEMORY_LISTENER, true);
for (LogContainer& container : m_log)
{
container.m_enable = cfgLoadBool("log", container.m_short_name, true);
}
m_path_cutoff_point = DeterminePathCutOffPoint();
UpdateConfig();
}
LogManager::~LogManager()
void LogManager::UpdateConfig()
{
// The log window listener pointer is owned by the GUI code.
delete m_listeners[LogListener::CONSOLE_LISTENER];
delete m_listeners[LogListener::FILE_LISTENER];
delete m_listeners[LogListener::IN_MEMORY_LISTENER];
bool logToFile = cfgLoadBool("log", "LogToFile", false);
if (logToFile != IsListenerEnabled(LogListener::FILE_LISTENER))
{
if (!logToFile) {
m_listeners[LogListener::FILE_LISTENER].reset();
}
else {
#if defined(__ANDROID__) || defined(__APPLE__) || defined(TARGET_UWP)
std::string logPath = get_writable_data_path("flycast.log");
#else
std::string logPath = "flycast.log";
#endif
FileLogListener *listener = new FileLogListener(logPath);
if (!listener->IsValid())
{
const char *home = nowide::getenv("HOME");
if (home != nullptr)
{
delete listener;
listener = new FileLogListener(home + ("/" + logPath));
}
}
RegisterListener(LogListener::FILE_LISTENER, listener);
}
EnableListener(LogListener::FILE_LISTENER, logToFile);
}
std::string newLogServer = cfgLoadStr("log", "LogServer", "");
if (logServer != newLogServer)
{
logServer = newLogServer;
RegisterListener(LogListener::NETWORK_LISTENER, new NetworkListener(logServer));
EnableListener(LogListener::NETWORK_LISTENER, !logServer.empty());
}
}
// Return the current time formatted as Minutes:Seconds:Milliseconds
@ -241,7 +249,7 @@ const char* LogManager::GetFullName(LogTypes::LOG_TYPE type) const
void LogManager::RegisterListener(LogListener::LISTENER id, LogListener* listener)
{
m_listeners[id] = listener;
m_listeners[id] = std::unique_ptr<LogListener>(listener);
}
void LogManager::EnableListener(LogListener::LISTENER id, bool enable)

View File

@ -6,6 +6,7 @@
#include <array>
#include <cstdarg>
#include <memory>
#include "BitSet.h"
#include "Log.h"
@ -23,6 +24,7 @@ public:
CONSOLE_LISTENER,
LOG_WINDOW_LISTENER,
IN_MEMORY_LISTENER,
NETWORK_LISTENER,
NUMBER_OF_LISTENERS // Must be last
};
@ -52,6 +54,7 @@ public:
void RegisterListener(LogListener::LISTENER id, LogListener* listener);
void EnableListener(LogListener::LISTENER id, bool enable);
bool IsListenerEnabled(LogListener::LISTENER id) const;
void UpdateConfig();
private:
struct LogContainer
@ -66,7 +69,6 @@ private:
};
LogManager();
~LogManager();
LogManager(const LogManager&) = delete;
LogManager& operator=(const LogManager&) = delete;
@ -75,7 +77,8 @@ private:
LogTypes::LOG_LEVELS m_level;
std::array<LogContainer, LogTypes::NUMBER_OF_LOGS> m_log{};
std::array<LogListener*, LogListener::NUMBER_OF_LISTENERS> m_listeners{};
std::array<std::unique_ptr<LogListener>, LogListener::NUMBER_OF_LISTENERS> m_listeners{};
BitSet32 m_listener_ids;
size_t m_path_cutoff_point = 0;
std::string logServer;
};

View File

@ -0,0 +1,88 @@
/*
Copyright 2025 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 <asio.hpp>
#include <stdio.h>
#include "LogManager.h"
class NetworkListener : public LogListener
{
public:
NetworkListener(const std::string& dest)
{
if (dest.empty())
return;
std::string host;
std::string port("31667");
auto colon = dest.find(':');
if (colon != std::string::npos) {
port = dest.substr(colon + 1);
host = dest.substr(0, colon);
}
else {
host = dest;
}
asio::ip::udp::resolver resolver(io_context);
asio::error_code ec;
auto it = resolver.resolve(host, port, ec);
if (ec || it.empty()) {
fprintf(stderr, "Unknown hostname %s: %s\n", host.c_str(), ec.message().c_str());
}
else
{
asio::ip::udp::endpoint endpoint = *it.begin();
socket.connect(endpoint, ec);
if (ec)
fprintf(stderr, "Connect to log server failed: %s\n", ec.message().c_str());
}
}
void Log(LogTypes::LOG_LEVELS level, const char* msg) override
{
if (!socket.is_open())
return;
const char *reset_attr = "\x1b[0m";
std::string color_attr;
switch (level)
{
case LogTypes::LOG_LEVELS::LNOTICE:
// light green
color_attr = "\x1b[92m";
break;
case LogTypes::LOG_LEVELS::LERROR:
// light red
color_attr = "\x1b[91m";
break;
case LogTypes::LOG_LEVELS::LWARNING:
// light yellow
color_attr = "\x1b[93m";
break;
default:
break;
}
std::string str = color_attr + msg + reset_attr;
asio::error_code ec;
socket.send(asio::buffer(str), 0, ec);
}
private:
asio::io_context io_context;
asio::ip::udp::socket socket { io_context };
};

View File

@ -19,7 +19,7 @@
#include "alienfnt_modem.h"
#include "hw/sh4/sh4_sched.h"
#include "hw/sh4/modules/modules.h"
#include "picoppp.h"
#include "netservice.h"
#include <vector>
#include <deque>
@ -34,7 +34,7 @@ struct ModemEmu : public SerialPort::Pipe
~ModemEmu() override {
sh4_sched_unregister(schedId);
stop_pico();
net::modbba::stop();
SCIFSerialPort::Instance().setPipe(nullptr);
}
@ -47,7 +47,7 @@ struct ModemEmu : public SerialPort::Pipe
return c;
}
else if (dataMode)
return read_pico();
return net::modbba::readModem();
else
return 0;
}
@ -57,7 +57,7 @@ struct ModemEmu : public SerialPort::Pipe
if (!toSend.empty())
return toSend.size();
else if (dataMode)
return pico_available();
return net::modbba::modemAvailable();
else
return 0;
}
@ -76,10 +76,10 @@ struct ModemEmu : public SerialPort::Pipe
}
else
{
write_pico('+');
write_pico('+');
write_pico('+');
write_pico(data);
net::modbba::writeModem('+');
net::modbba::writeModem('+');
net::modbba::writeModem('+');
net::modbba::writeModem(data);
}
pluses = 0;
plusTime = 0;
@ -92,10 +92,10 @@ struct ModemEmu : public SerialPort::Pipe
else
{
while (pluses > 0) {
write_pico('+');
net::modbba::writeModem('+');
pluses--;
}
write_pico(data);
net::modbba::writeModem(data);
}
}
else if (data == '\r' || data == '\n')
@ -113,13 +113,13 @@ private:
recvBuf.clear();
if (line.substr(0, 4) == "ATDT") {
send("CONNECT 14400");
start_pico();
net::modbba::start();
dataMode = true;
sh4_sched_request(schedId, SH4_MAIN_CLOCK / 60);
}
if (line.substr(0, 3) == "ATH")
{
stop_pico();
net::modbba::stop();
send("OK");
}
else if (line.substr(0, 2) == "AT")

504
core/network/dcnet.cpp Normal file
View File

@ -0,0 +1,504 @@
/*
Copyright 2025 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 "types.h"
#include <asio.hpp>
#include "netservice.h"
#include "util/tsqueue.h"
#include "oslib/oslib.h"
#include "emulator.h"
#include "hw/bba/bba.h"
#include "cfg/option.h"
#include "stdclass.h"
#include <thread>
#include <memory>
#include <array>
#ifndef __ANDROID__
//#define WIRESHARK_DUMP 1
#endif
namespace net::modbba
{
static TsQueue<u8> toModem;
class DCNetService : public Service
{
public:
bool start() override;
void stop() override;
void writeModem(u8 b) override;
int readModem() override;
int modemAvailable() override;
void receiveEthFrame(const u8 *frame, u32 size) override;
};
template<typename SocketT>
class PPPSocket
{
public:
PPPSocket(asio::io_context& io_context, const typename SocketT::endpoint_type& endpoint)
: socket(io_context)
{
asio::error_code ec;
socket.connect(endpoint, ec);
if (ec)
throw FlycastException(ec.message().c_str());
os_notify("Connected to DCNet with modem", 5000);
receive();
}
~PPPSocket() {
if (dumpfp != nullptr)
fclose(dumpfp);
}
void send(u8 b)
{
if (sendBufSize == sendBuffer.size()) {
WARN_LOG(NETWORK, "PPP output buffer overflow");
return;
}
sendBuffer[sendBufSize++] = b;
doSend();
}
private:
void receive()
{
socket.async_read_some(asio::buffer(recvBuffer),
[this](const std::error_code& ec, size_t len)
{
if (ec || len == 0)
{
if (ec)
ERROR_LOG(NETWORK, "Receive error: %s", ec.message().c_str());
close();
return;
}
pppdump(recvBuffer.data(), len, false);
for (size_t i = 0; i < len; i++)
toModem.push(recvBuffer[i]);
receive();
});
}
void doSend()
{
if (sending)
return;
if ((sendBufSize > 1 && sendBuffer[sendBufSize - 1] == 0x7e)
|| sendBufSize == sendBuffer.size())
{
pppdump(sendBuffer.data(), sendBufSize, true);
sending = true;
asio::async_write(socket, asio::buffer(sendBuffer, sendBufSize),
[this](const std::error_code& ec, size_t len)
{
if (ec)
{
ERROR_LOG(NETWORK, "Send error: %s", ec.message().c_str());
close();
return;
}
sending = false;
sendBufSize -= len;
if (sendBufSize > 0) {
memmove(&sendBuffer[0], &sendBuffer[len], sendBufSize);
doSend();
}
});
}
}
void close() {
std::error_code ignored;
socket.close(ignored);
}
void pppdump(uint8_t *buf, int len, bool egress)
{
#ifdef WIRESHARK_DUMP
if (!len)
return;
if (dumpfp == nullptr)
{
dumpfp = fopen("ppp.dump", "a");
if (dumpfp == nullptr)
return;
time_t now;
time(&now);
u32 reset_time = ntohl((u32)now);
fputc(7, dumpfp); // Reset time
fwrite(&reset_time, sizeof(reset_time), 1, dumpfp);
dump_last_time_ms = getTimeMs();
}
u32 delta = getTimeMs() / 100 - dump_last_time_ms / 100;
if (delta < 256) {
fputc(6, dumpfp); // Time step (short)
fwrite(&delta, 1, 1, dumpfp);
}
else
{
delta = ntohl(delta);
fputc(5, dumpfp); // Time step (long)
fwrite(&delta, sizeof(delta), 1, dumpfp);
}
dump_last_time_ms = getTimeMs();
fputc(egress ? 1 : 2, dumpfp); // Sent/received data
uint16_t slen = htons(len);
fwrite(&slen, 2, 1, dumpfp);
fwrite(buf, 1, len, dumpfp);
#endif
}
SocketT socket;
std::array<u8, 1542> recvBuffer;
std::array<u8, 1542> sendBuffer;
u32 sendBufSize = 0;
bool sending = false;
FILE *dumpfp = nullptr;
u64 dump_last_time_ms;
};
using PPPTcpSocket = PPPSocket<asio::ip::tcp::socket>;
class EthSocket
{
public:
EthSocket(asio::io_context& io_context, const asio::ip::tcp::endpoint& endpoint)
: socket(io_context)
{
asio::error_code ec;
socket.connect(endpoint, ec);
if (ec)
throw FlycastException(ec.message().c_str());
os_notify("Connected to DCNet with Ethernet", 5000);
receive();
u8 prolog[] = { 'D', 'C', 'N', 'E', 'T', 1 };
send(prolog, sizeof(prolog));
}
~EthSocket() {
if (dumpfp != nullptr)
fclose(dumpfp);
}
void send(const u8 *frame, u32 size)
{
if (sendBufferIdx + size >= sendBuffer.size()) {
WARN_LOG(NETWORK, "Dropped out frame (buffer:%d + %d bytes). Increase send buffer size\n", sendBufferIdx, size);
return;
}
if (size >= 32) // skip prolog
ethdump(frame, size);
*(u16 *)&sendBuffer[sendBufferIdx] = size;
sendBufferIdx += 2;
memcpy(&sendBuffer[sendBufferIdx], frame, size);
sendBufferIdx += size;
doSend();
}
private:
using iterator = asio::buffers_iterator<asio::const_buffers_1>;
std::pair<iterator, bool>
static packetMatcher(iterator begin, iterator end)
{
if (end - begin < 3)
return std::make_pair(begin, false);
iterator i = begin;
uint16_t len = (uint8_t)*i++;
len |= uint8_t(*i++) << 8;
len += 2;
if (end - begin < len)
return std::make_pair(begin, false);
return std::make_pair(begin + len, true);
}
void receive()
{
asio::async_read_until(socket, asio::dynamic_vector_buffer(recvBuffer), packetMatcher,
[this](const std::error_code& ec, size_t len)
{
if (ec || len == 0)
{
if (ec)
ERROR_LOG(NETWORK, "Receive error: %s", ec.message().c_str());
std::error_code ignored;
socket.close(ignored);
return;
}
/*
verify(len - 2 == *(u16 *)&recvBuffer[0]);
printf("In frame: dest %02x:%02x:%02x:%02x:%02x:%02x "
"src %02x:%02x:%02x:%02x:%02x:%02x, ethertype %04x, size %d bytes\n",
recvBuffer[2], recvBuffer[3], recvBuffer[4], recvBuffer[5], recvBuffer[6], recvBuffer[7],
recvBuffer[8], recvBuffer[9], recvBuffer[10], recvBuffer[11], recvBuffer[12], recvBuffer[13],
*(u16 *)&recvBuffer[14], (int)len - 2);
*/
ethdump(&recvBuffer[2], len - 2);
bba_recv_frame(&recvBuffer[2], len - 2);
if (len < recvBuffer.size())
recvBuffer.erase(recvBuffer.begin(), recvBuffer.begin() + len);
else
recvBuffer.clear();
receive();
});
}
void doSend()
{
if (sending)
return;
sending = true;
asio::async_write(socket, asio::buffer(sendBuffer, sendBufferIdx),
[this](const std::error_code& ec, size_t len)
{
sending = false;
if (ec)
{
ERROR_LOG(NETWORK, "Send error: %s", ec.message().c_str());
std::error_code ignored;
socket.close(ignored);
return;
}
sendBufferIdx -= len;
if (sendBufferIdx != 0) {
memmove(sendBuffer.data(), sendBuffer.data() + len, sendBufferIdx);
doSend();
}
});
}
void ethdump(const uint8_t *frame, int size)
{
#ifdef WIRESHARK_DUMP
if (dumpfp == nullptr)
{
dumpfp = fopen("bba.pcapng", "wb");
if (dumpfp == nullptr)
{
const char *home = getenv("HOME");
if (home != nullptr)
{
std::string path = home + std::string("/bba.pcapng");
dumpfp = fopen(path.c_str(), "wb");
}
if (dumpfp == nullptr)
return;
}
u32 blockType = 0x0A0D0D0A; // Section Header Block
fwrite(&blockType, sizeof(blockType), 1, dumpfp);
u32 blockLen = 28;
fwrite(&blockLen, sizeof(blockLen), 1, dumpfp);
u32 magic = 0x1A2B3C4D;
fwrite(&magic, sizeof(magic), 1, dumpfp);
u32 version = 1; // 1.0
fwrite(&version, sizeof(version), 1, dumpfp);
u64 sectionLength = ~0; // unspecified
fwrite(&sectionLength, sizeof(sectionLength), 1, dumpfp);
fwrite(&blockLen, sizeof(blockLen), 1, dumpfp);
blockType = 1; // Interface Description Block
fwrite(&blockType, sizeof(blockType), 1, dumpfp);
blockLen = 20;
fwrite(&blockLen, sizeof(blockLen), 1, dumpfp);
const u32 linkType = 1; // Ethernet
fwrite(&linkType, sizeof(linkType), 1, dumpfp);
const u32 snapLen = 0; // no limit
fwrite(&snapLen, sizeof(snapLen), 1, dumpfp);
// TODO options? if name, ip/mac address
fwrite(&blockLen, sizeof(blockLen), 1, dumpfp);
}
const u32 blockType = 6; // Extended Packet Block
fwrite(&blockType, sizeof(blockType), 1, dumpfp);
u32 roundedSize = ((size + 3) & ~3) + 32;
fwrite(&roundedSize, sizeof(roundedSize), 1, dumpfp);
u32 ifId = 0;
fwrite(&ifId, sizeof(ifId), 1, dumpfp);
u64 now = getTimeMs() * 1000;
fwrite((u32 *)&now + 1, 4, 1, dumpfp);
fwrite(&now, 4, 1, dumpfp);
fwrite(&size, sizeof(size), 1, dumpfp);
fwrite(&size, sizeof(size), 1, dumpfp);
fwrite(frame, 1, size, dumpfp);
fwrite(frame, 1, roundedSize - size - 32, dumpfp);
fwrite(&roundedSize, sizeof(roundedSize), 1, dumpfp);
#endif
}
asio::ip::tcp::socket socket;
std::vector<u8> recvBuffer;
std::array<u8, 1600> sendBuffer;
u32 sendBufferIdx = 0;
bool sending = false;
FILE *dumpfp = nullptr;
};
class DCNetThread
{
public:
void start()
{
if (thread.joinable())
return;
io_context = std::make_unique<asio::io_context>();
thread = std::thread(&DCNetThread::run, this);
}
void stop()
{
if (!thread.joinable())
return;
io_context->stop();
thread.join();
pppSocket.reset();
ethSocket.reset();
io_context.reset();
os_notify("DCNet disconnected", 3000);
}
void sendModem(u8 v)
{
if (io_context == nullptr || pppSocket == nullptr)
return;
io_context->post([this, v]() {
pppSocket->send(v);
});
}
void sendEthFrame(const u8 *frame, u32 len)
{
if (io_context != nullptr && ethSocket != nullptr)
{
std::vector<u8> vbuf(frame, frame + len);
io_context->post([this, vbuf]() {
ethSocket->send(vbuf.data(), vbuf.size());
});
}
else {
// restart the thread if previously stopped
start();
}
}
private:
void run();
std::thread thread;
std::unique_ptr<asio::io_context> io_context;
std::unique_ptr<PPPTcpSocket> pppSocket;
std::unique_ptr<EthSocket> ethSocket;
friend DCNetService;
};
static DCNetThread thread;
bool DCNetService::start()
{
emu.setNetworkState(true);
thread.start();
return true;
}
void DCNetService::stop() {
thread.stop();
emu.setNetworkState(false);
}
void DCNetService::writeModem(u8 b) {
thread.sendModem(b);
}
int DCNetService::readModem()
{
if (toModem.empty())
return -1;
else
return toModem.pop();
}
int DCNetService::modemAvailable() {
return toModem.size();
}
void DCNetService::receiveEthFrame(u8 const *frame, unsigned int len)
{
/*
printf("Out frame: dest %02x:%02x:%02x:%02x:%02x:%02x "
"src %02x:%02x:%02x:%02x:%02x:%02x, ethertype %04x, size %d bytes %s\n",
frame[0], frame[1], frame[2], frame[3], frame[4], frame[5],
frame[6], frame[7], frame[8], frame[9], frame[10], frame[11],
*(u16 *)&frame[12], len, EthSocket::Instance == nullptr ? "LOST" : "");
*/
// Stop DCNet on DHCP Release
if (len >= 0x11d
&& *(u16 *)&frame[0xc] == 0x0008 // type: IPv4
&& frame[0x17] == 0x11 // UDP
&& ntohs(*(u16 *)&frame[0x22]) == 68 // src port: dhcpc
&& ntohs(*(u16 *)&frame[0x24]) == 67) // dest port: dhcps
{
const u8 *options = &frame[0x11a];
while (options - frame < len && *options != 0xff)
{
if (*options == 53 // message type
&& options[2] == 7) // release
{
stop();
return;
}
options += options[1] + 2;
}
}
thread.sendEthFrame(frame, len);
}
void DCNetThread::run()
{
try {
std::string port;
if (config::EmulateBBA)
port = "7655";
else
port = "7654";
asio::ip::tcp::resolver resolver(*io_context);
asio::error_code ec;
auto it = resolver.resolve("dcnet.flyca.st", port, ec);
if (ec)
throw FlycastException(ec.message());
asio::ip::tcp::endpoint endpoint = *it.begin();
if (config::EmulateBBA)
ethSocket = std::make_unique<EthSocket>(*io_context, endpoint);
else
pppSocket = std::make_unique<PPPTcpSocket>(*io_context, endpoint);
io_context->run();
} catch (const FlycastException& e) {
ERROR_LOG(NETWORK, "DCNet connection error: %s", e.what());
os_notify("Can't connect to DCNet", 8000, e.what());
} catch (const std::runtime_error& e) {
ERROR_LOG(NETWORK, "DCNetThread::run error: %s", e.what());
}
}
}

38
core/network/dcnet.h Normal file
View File

@ -0,0 +1,38 @@
/*
Copyright 2025 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 "netservice.h"
namespace net::modbba
{
class DCNetService : public Service
{
public:
bool start() override;
void stop() override;
void writeModem(u8 b) override;
int readModem() override;
int modemAvailable() override;
void receiveEthFrame(const u8 *frame, u32 size) override;
};
}

View File

@ -34,10 +34,8 @@ extern "C" {
#endif
}
void get_host_by_name(const char *name, struct pico_ip4 dnsaddr);
int get_dns_answer(struct pico_ip4 *address, struct pico_ip4 dnsaddr);
char *read_name(char *reader, char *buffer, int *count);
void set_non_blocking(sock_t fd);
u32 makeDnsQueryPacket(void *buf, const char *host);
pico_ip4 parseDnsResponsePacket(const void *buf, size_t len);
static sock_t sock_fd = INVALID_SOCKET;
static unsigned short qid = PICO_TIME_MS();
@ -59,7 +57,15 @@ void get_host_by_name(const char *host, struct pico_ip4 dnsaddr)
// DNS Packet header
char buf[1024];
pico_dns_packet *dns = (pico_dns_packet *)&buf;
u32 len = makeDnsQueryPacket(buf, host);
if (sendto(sock_fd, buf, len, 0, (struct sockaddr *)&dest, sizeof(dest)) < 0)
perror("DNS sendto failed");
}
u32 makeDnsQueryPacket(void *buf, const char *host)
{
pico_dns_packet *dns = (pico_dns_packet *)buf;
dns->id = qid++;
dns->qr = PICO_DNS_QR_QUERY;
@ -75,18 +81,25 @@ void get_host_by_name(const char *host, struct pico_ip4 dnsaddr)
dns->nscount = 0;
dns->arcount = 0;
char *qname = &buf[sizeof(pico_dns_packet)];
char *qname = (char *)buf + sizeof(pico_dns_packet);
strcpy(qname + 1, host);
pico_dns_name_to_dns_notation(qname, 128);
qname_len = strlen(qname) + 1;
struct pico_dns_question_suffix *qinfo = (struct pico_dns_question_suffix *) &buf[sizeof(pico_dns_packet) + qname_len]; //fill it
pico_dns_question_suffix *qinfo = (pico_dns_question_suffix *)(qname + qname_len); //fill it
qinfo->qtype = htons(PICO_DNS_TYPE_A); // Address record
qinfo->qclass = htons(PICO_DNS_CLASS_IN);
if (sendto(sock_fd, buf, sizeof(pico_dns_packet) + qname_len + sizeof(struct pico_dns_question_suffix), 0, (struct sockaddr *)&dest, sizeof(dest)) < 0)
perror("DNS sendto failed");
return sizeof(pico_dns_packet) + qname_len + sizeof(pico_dns_question_suffix);
}
static int dnsNameLen(const char *s)
{
if ((uint8_t)s[0] & 0xC0)
return 2;
else
return strlen(s) + 1;
}
int get_dns_answer(struct pico_ip4 *address, struct pico_ip4 dnsaddr)
@ -105,50 +118,39 @@ int get_dns_answer(struct pico_ip4 *address, struct pico_ip4 dnsaddr)
if (peer.sin_addr.s_addr != dnsaddr.addr)
return -1;
pico_dns_packet *dns = (pico_dns_packet*) buf;
pico_ip4 addr = parseDnsResponsePacket(buf, r);
if (addr.addr == ~0u)
return -1;
address->addr = addr.addr;
return 0;
}
pico_ip4 parseDnsResponsePacket(const void *buf, size_t len)
{
const pico_dns_packet *dns = (const pico_dns_packet *)buf;
// move to the first answer
char *reader = &buf[sizeof(pico_dns_packet) + qname_len + sizeof(struct pico_dns_question_suffix)];
int stop = 0;
const char *reader = (const char *)buf + sizeof(pico_dns_packet);
reader += strlen(reader) + 1 + sizeof(pico_dns_question_suffix);
for (int i = 0; i < ntohs(dns->ancount); i++)
{
// FIXME Check name?
free(read_name(reader, buf, &stop));
reader = reader + stop;
struct pico_dns_record_suffix *record = (struct pico_dns_record_suffix *)reader;
reader = reader + sizeof(struct pico_dns_record_suffix);
// TODO Check name?
reader += dnsNameLen(reader);
const pico_dns_record_suffix *record = (const pico_dns_record_suffix *)reader;
reader += sizeof(pico_dns_record_suffix);
if (ntohs(record->rtype) == PICO_DNS_TYPE_A) // Address record
{
memcpy(&address->addr, reader, 4);
pico_ip4 address;
memcpy(&address.addr, reader, 4);
return 0;
return address;
}
reader = reader + ntohs(record->rdlength);
}
return -1;
}
char *read_name(char *reader, char *buffer, int *count)
{
char *name = (char *)malloc(128);
if ((uint8_t)reader[0] & 0xC0)
{
int offset = (((uint8_t)reader[0] & ~0xC0) << 8) + (uint8_t)reader[1];
reader = &buffer[offset];
*count = 2;
}
else
{
*count = strlen(reader) + 1;
}
pico_dns_notation_to_name(reader, 128);
strcpy(name, reader + 1);
return name;
return { ~0u };
}
#if !defined(_WIN32) && !defined(__SWITCH__)

View File

@ -54,16 +54,13 @@ bool MiniUPnP::Init()
WARN_LOG(NETWORK, "Internet Gateway not found: error %d", error);
return false;
}
wanAddress[0] = 0;
initialized = true;
if (UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, wanAddress) != 0)
WARN_LOG(NETWORK, "Cannot determine external IP address");
DEBUG_LOG(NETWORK, "MiniUPnP: public IP is %s", wanAddress);
return true;
}
void MiniUPnP::Term()
{
std::lock_guard<std::mutex> _(mutex);
if (!initialized)
return;
DEBUG_LOG(NETWORK, "MiniUPnP::Term");
@ -92,7 +89,10 @@ bool MiniUPnP::AddPortMapping(int port, bool tcp)
WARN_LOG(NETWORK, "Port %d redirection failed: error %d", port, error);
return false;
}
mappedPorts.emplace_back(portStr, tcp);
{
std::lock_guard<std::mutex> _(mutex);
mappedPorts.emplace_back(portStr, tcp);
}
DEBUG_LOG(NETWORK, "MiniUPnP: forwarding %s port %d", tcp ? "TCP" : "UDP", port);
return true;
}

View File

@ -24,29 +24,28 @@
#include <string>
#include <utility>
#include <vector>
#include <mutex>
class MiniUPnP
{
public:
MiniUPnP() {
lanAddress[0] = 0;
wanAddress[0] = 0;
memset(&urls, 0, sizeof(urls));
memset(&data, 0, sizeof(data));
}
bool Init();
void Term();
bool AddPortMapping(int port, bool tcp);
const char *localAddress() const { return lanAddress; }
const char *externalAddress() const { return wanAddress; }
bool isInitialized() const { return initialized; }
private:
UPNPUrls urls;
IGDdatas data;
char lanAddress[32];
char wanAddress[32];
std::vector<std::pair<std::string, bool>> mappedPorts;
bool initialized = false;
std::mutex mutex;
};
#else
@ -57,8 +56,7 @@ public:
bool Init() { return true; }
void Term() {}
bool AddPortMapping(int port, bool tcp) { return true; }
const char *localAddress() const { return ""; }
const char *externalAddress() const { return ""; }
bool isInitialized() const { return false; }
};
#endif

View File

@ -55,7 +55,7 @@ typedef int sock_t;
#define L_EINPROGRESS EINPROGRESS
#define get_last_error() (errno)
#define INVALID_SOCKET (-1)
#define perror(s) do { INFO_LOG(NETWORK, "%s: %s", (s) != NULL ? (s) : "", strerror(get_last_error())); } while (false)
#define perror(s) do { ERROR_LOG(NETWORK, "%s: %s", (s) != NULL ? (s) : "", strerror(get_last_error())); } while (false)
#else
typedef SOCKET sock_t;
#define VALID(s) ((s) != INVALID_SOCKET)
@ -63,7 +63,7 @@ typedef SOCKET sock_t;
#define L_EAGAIN WSAEWOULDBLOCK
#define L_EINPROGRESS WSAEINPROGRESS
#define get_last_error() (WSAGetLastError())
#define perror(s) do { INFO_LOG(NETWORK, "%s: Winsock error: %d", (s) != NULL ? (s) : "", WSAGetLastError()); } while (false)
#define perror(s) do { ERROR_LOG(NETWORK, "%s: Winsock error: %d", (s) != NULL ? (s) : "", WSAGetLastError()); } while (false)
#ifndef SHUT_WR
#define SHUT_WR SD_SEND
#endif

View File

@ -0,0 +1,68 @@
/*
Copyright 2025 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 "netservice.h"
#include "picoppp.h"
#include "dcnet.h"
#include "emulator.h"
#include "cfg/option.h"
namespace net::modbba
{
static Service *service;
static bool usingDCNet;
bool start()
{
if (service == nullptr || usingDCNet != config::UseDCNet)
{
delete service;
if (config::UseDCNet)
service = new DCNetService();
else
service = new PicoTcpService();
usingDCNet = config::UseDCNet;
}
return service->start();
}
void stop() {
if (service != nullptr)
service->stop();
}
void writeModem(u8 b) {
verify(service != nullptr);
service->writeModem(b);
}
int readModem() {
verify(service != nullptr);
return service->readModem();
}
int modemAvailable() {
verify(service != nullptr);
return service->modemAvailable();
}
void receiveEthFrame(const u8 *frame, u32 size) {
start();
service->receiveEthFrame(frame, size);
}
}

48
core/network/netservice.h Normal file
View File

@ -0,0 +1,48 @@
/*
Copyright 2025 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"
namespace net::modbba
{
bool start();
void stop();
void writeModem(u8 b);
int readModem();
int modemAvailable();
void receiveEthFrame(const u8 *frame, u32 size);
class Service
{
public:
virtual ~Service() = default;
virtual bool start() = 0;
virtual void stop() = 0;
virtual void writeModem(u8 b) = 0;
virtual int readModem() = 0;
virtual int modemAvailable() = 0;
virtual void receiveEthFrame(const u8 *frame, u32 size) = 0;
};
}

View File

@ -27,10 +27,14 @@
class NetworkOutput
{
public:
void init()
{
if (!config::NetworkOutput || settings.naomi.slave || settings.naomi.drivingSimSlave == 1)
if (!config::NetworkOutput || settings.naomi.slave || settings.naomi.drivingSimSlave == 1) {
term();
return;
}
if (server != INVALID_SOCKET)
// already done
return;
server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
@ -44,13 +48,13 @@ public:
saddr.sin_port = htons(8000 + settings.naomi.drivingSimSlave);
if (::bind(server, (sockaddr *)&saddr, saddr_len) < 0)
{
perror("bind");
perror("Network output: bind failed");
term();
return;
}
if (listen(server, 5) < 0)
{
perror("listen");
perror("Network output: listen failed");
term();
return;
}
@ -58,27 +62,27 @@ public:
EventManager::listen(Event::VBlank, vblankCallback, this);
}
public:
void term()
{
EventManager::unlisten(Event::VBlank, vblankCallback, this);
for (sock_t sock : clients)
closesocket(sock);
clients.clear();
if (server != INVALID_SOCKET)
{
if (server != INVALID_SOCKET) {
closesocket(server);
server = INVALID_SOCKET;
}
}
void reset()
{
void reset() {
init();
gameNameSent = false;
}
void output(const char *name, u32 value)
{
if (!config::NetworkOutput || clients.empty())
if (clients.empty())
return;
if (!gameNameSent)
{
@ -104,6 +108,18 @@ private:
if (sockfd != INVALID_SOCKET)
{
set_non_blocking(sockfd);
if (gameNameSent)
{
std::string msg = "game = " + settings.content.gameId + "\n";
if (::send(sockfd, msg.c_str(), msg.length(), 0) < 0)
{
int error = get_last_error();
if (error != L_EWOULDBLOCK && error != L_EAGAIN) {
closesocket(sockfd);
return;
}
}
}
clients.push_back(sockfd);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -19,13 +19,20 @@
along with reicast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "netservice.h"
bool start_pico();
void stop_pico();
void write_pico(u8 b);
int read_pico();
int pico_available();
namespace net::modbba
{
void pico_receive_eth_frame(const u8 *frame, u32 size);
// implemented in bba
int pico_send_eth_frame(const u8 *data, u32 len);
class PicoTcpService : public Service
{
public:
bool start() override;
void stop() override;
void writeModem(u8 b) override;
int readModem() override;
int modemAvailable() override;
void receiveEthFrame(const u8 *frame, u32 size) override;
};
}

View File

@ -110,6 +110,7 @@ void SaveSettings()
void SaveAndroidSettings();
SaveAndroidSettings();
#endif
LogManager::GetInstance()->UpdateConfig();
}
void flycast_term()

View File

@ -1901,7 +1901,6 @@ void Arm32Assembler::compileOp(RuntimeBlockInfo* block, shil_opcode* op, bool op
unaryFpOp(op, &MacroAssembler::Vsqrt);
break;
case shop_fmac:
{
SRegister rd = reg.mapFReg(op->rd);
@ -1945,7 +1944,7 @@ void Arm32Assembler::compileOp(RuntimeBlockInfo* block, shil_opcode* op, bool op
}
if (!rd.Is(rs1))
Vmov(rd, rs1);
Vmla(rd, rs2, rs3);
Vfma(rd, rs2, rs3);
}
break;
@ -2001,7 +2000,7 @@ void Arm32Assembler::compileOp(RuntimeBlockInfo* block, shil_opcode* op, bool op
Vstr(d0, MemOperand(r8, op->rd.reg_nofs()));
}
break;
/* fall back to the canonical implementations for better precision
case shop_fipr:
{
QRegister _r1 = q0;
@ -2098,7 +2097,7 @@ void Arm32Assembler::compileOp(RuntimeBlockInfo* block, shil_opcode* op, bool op
#endif
}
break;
*/
case shop_frswap:
Sub(r0, r8, -op->rs1.reg_nofs());
Sub(r1, r8, -op->rd.reg_nofs());
@ -2111,8 +2110,19 @@ void Arm32Assembler::compileOp(RuntimeBlockInfo* block, shil_opcode* op, bool op
break;
case shop_cvt_f2i_t:
Vcvt(S32, F32, s0, reg.mapFReg(op->rs1));
Vmov(reg.mapReg(op->rd), s0);
{
SRegister from = reg.mapFReg(op->rs1);
Register to = reg.mapReg(op->rd);
Vcvt(S32, F32, s0, from);
Vmov(to, s0);
Mvn(r0, 127);
Sub(r0, r0, 0x80000000);
Cmp(to, r0);
Mvn(gt, to, 0xf8000000);
Vcmp(from, from);
Vmrs(RegisterOrAPSR_nzcv(APSR_nzcv), FPSCR);
Mov(ne, to, 0x80000000);
}
break;
case shop_cvt_i2f_n: // may be some difference should be made ?

View File

@ -952,8 +952,20 @@ public:
break;
case shop_cvt_f2i_t:
Fcvtzs(regalloc.MapRegister(op.rd), regalloc.MapVRegister(op.rs1));
{
const VRegister& from = regalloc.MapVRegister(op.rs1);
const Register& to = regalloc.MapRegister(op.rd);
Fcvtzs(to, from);
Mov(w0, 0x7FFFFF80);
Cmp(to, w0);
Mov(w0, 0x7FFFFFF);
Csel(to, w0, to, gt);
Fcmp(from, from);
Mov(w0, 0x80000000);
Csel(to, to, w0, vc);
}
break;
case shop_cvt_i2f_n:
case shop_cvt_i2f_z:
Scvtf(regalloc.MapVRegister(op.rd), regalloc.MapRegister(op.rs1));

View File

@ -1371,11 +1371,11 @@ public:
if (codeBuffer == nullptr)
// init() not called yet
return false;
void* protStart = codeBuffer->get();
size_t protSize = codeBuffer->getFreeSpace();
virtmem::jit_set_exec(protStart, protSize, false);
u8 *retAddr = *(u8**)context.rsp - 5;
if (retAddr < (u8*)codeBuffer->getBase() || retAddr >= (u8*)codeBuffer->getBase() + codeBuffer->getSize())
return false;
virtmem::jit_set_exec(retAddr, 16, false);
u8 *retAddr = *(u8 **)context.rsp - 5;
BlockCompiler compiler(*sh4ctx, *codeBuffer, retAddr);
bool rc = false;
try {
@ -1383,7 +1383,7 @@ public:
} catch (const Xbyak::Error& e) {
ERROR_LOG(DYNAREC, "Fatal xbyak error: %s", e.what());
}
virtmem::jit_set_exec(protStart, protSize, true);
virtmem::jit_set_exec(retAddr, 16, true);
return rc;
}

View File

@ -22,6 +22,8 @@
#include "oslib/storage.h"
#include "cfg/option.h"
#include "oslib/oslib.h"
#include "stdclass.h"
#include "util/worker_thread.h"
#include <sstream>
#define STB_IMAGE_IMPLEMENTATION
@ -32,58 +34,33 @@
#include <stb_image_write.h>
CustomTexture custom_texture;
static WorkerThread loader_thread {"CustomTexLoader"};
void CustomTexture::LoaderThread()
void CustomTexture::loadTexture(BaseTextureCacheData *texture)
{
LoadMap();
while (initialized)
{
BaseTextureCacheData *texture;
do {
texture = nullptr;
{
std::unique_lock<std::mutex> lock(work_queue_mutex);
if (!work_queue.empty())
{
texture = work_queue.back();
work_queue.pop_back();
}
}
if (texture != nullptr)
{
texture->ComputeHash();
if (texture->custom_image_data != nullptr)
{
free(texture->custom_image_data);
texture->custom_image_data = nullptr;
}
if (!texture->dirty)
{
int width, height;
u8 *image_data = LoadCustomTexture(texture->texture_hash, width, height);
if (image_data == nullptr && texture->old_vqtexture_hash != 0)
image_data = LoadCustomTexture(texture->old_vqtexture_hash, width, height);
if (image_data == nullptr)
image_data = LoadCustomTexture(texture->old_texture_hash, width, height);
if (image_data != nullptr)
{
texture->custom_width = width;
texture->custom_height = height;
texture->custom_image_data = image_data;
}
}
texture->custom_load_in_progress--;
}
} while (texture != nullptr);
wakeup_thread.Wait();
if (texture->custom_image_data != nullptr) {
free(texture->custom_image_data);
texture->custom_image_data = nullptr;
}
if (!texture->dirty)
{
int width, height;
u8 *image_data = loadTexture(texture->texture_hash, width, height);
if (image_data == nullptr && texture->old_vqtexture_hash != 0)
image_data = loadTexture(texture->old_vqtexture_hash, width, height);
if (image_data == nullptr)
image_data = loadTexture(texture->old_texture_hash, width, height);
if (image_data != nullptr)
{
texture->custom_width = width;
texture->custom_height = height;
texture->custom_image_data = image_data;
}
}
texture->custom_load_in_progress--;
}
std::string CustomTexture::GetGameId()
std::string CustomTexture::getGameId()
{
std::string game_id(settings.content.gameId);
const size_t str_end = game_id.find_last_not_of(' ');
@ -95,12 +72,12 @@ std::string CustomTexture::GetGameId()
return game_id;
}
bool CustomTexture::Init()
bool CustomTexture::init()
{
if (!initialized)
{
initialized = true;
std::string game_id = GetGameId();
std::string game_id = getGameId();
if (game_id.length() > 0)
{
textures_path = hostfs::getTextureLoadPath(game_id);
@ -113,7 +90,9 @@ bool CustomTexture::Init()
{
NOTICE_LOG(RENDERER, "Found custom textures directory: %s", textures_path.c_str());
custom_textures_available = true;
loader_thread.Start();
loader_thread.run([this]() {
loadMap();
});
}
} catch (const FlycastException& e) {
}
@ -125,20 +104,12 @@ bool CustomTexture::Init()
void CustomTexture::Terminate()
{
if (initialized)
{
initialized = false;
{
std::unique_lock<std::mutex> lock(work_queue_mutex);
work_queue.clear();
}
wakeup_thread.Set();
loader_thread.WaitToEnd();
texture_map.clear();
}
loader_thread.stop();
texture_map.clear();
initialized = false;
}
u8* CustomTexture::LoadCustomTexture(u32 hash, int& width, int& height)
u8* CustomTexture::loadTexture(u32 hash, int& width, int& height)
{
auto it = texture_map.find(hash);
if (it == texture_map.end())
@ -156,15 +127,13 @@ u8* CustomTexture::LoadCustomTexture(u32 hash, int& width, int& height)
void CustomTexture::LoadCustomTextureAsync(BaseTextureCacheData *texture_data)
{
if (!Init())
if (!init())
return;
texture_data->custom_load_in_progress++;
{
std::unique_lock<std::mutex> lock(work_queue_mutex);
work_queue.insert(work_queue.begin(), texture_data);
}
wakeup_thread.Set();
loader_thread.run([this, texture_data]() {
loadTexture(texture_data);
});
}
void CustomTexture::DumpTexture(u32 hash, int w, int h, TextureType textype, void *src_buffer)
@ -172,7 +141,7 @@ void CustomTexture::DumpTexture(u32 hash, int w, int h, TextureType textype, voi
std::string base_dump_dir = hostfs::getTextureDumpPath();
if (!file_exists(base_dump_dir))
make_directory(base_dump_dir);
std::string game_id = GetGameId();
std::string game_id = getGameId();
if (game_id.length() == 0)
return;
@ -299,7 +268,7 @@ void CustomTexture::DumpTexture(u32 hash, int w, int h, TextureType textype, voi
free(dst_buffer);
}
void CustomTexture::LoadMap()
void CustomTexture::loadMap()
{
texture_map.clear();
hostfs::DirectoryTree tree(textures_path);

View File

@ -17,41 +17,30 @@
along with reicast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "texconv.h"
#include "stdclass.h"
#include <string>
#include <vector>
#include <map>
#include <mutex>
class BaseTextureCacheData;
class CustomTexture {
class CustomTexture
{
public:
CustomTexture() : loader_thread(loader_thread_func, this, "CustomTexLoader") {}
~CustomTexture() { Terminate(); }
u8* LoadCustomTexture(u32 hash, int& width, int& height);
void LoadCustomTextureAsync(BaseTextureCacheData *texture_data);
void DumpTexture(u32 hash, int w, int h, TextureType textype, void *src_buffer);
void Terminate();
private:
bool Init();
void LoaderThread();
std::string GetGameId();
void LoadMap();
static void *loader_thread_func(void *param) { ((CustomTexture *)param)->LoaderThread(); return NULL; }
bool init();
u8* loadTexture(u32 hash, int& width, int& height);
void loadTexture(BaseTextureCacheData *texture);
std::string getGameId();
void loadMap();
bool initialized = false;
bool custom_textures_available = false;
std::string textures_path;
cThread loader_thread;
cResetEvent wakeup_thread;
std::vector<BaseTextureCacheData *> work_queue;
std::mutex work_queue_mutex;
std::map<u32, std::string> texture_map;
};

View File

@ -496,7 +496,18 @@ bool BaseTextureCacheData::Update()
}
}
if (config::CustomTextures)
{
u32 oldHash = texture_hash;
ComputeHash();
if (Updates > 1 && oldHash == texture_hash)
{
// Texture hasn't changed so skip the update.
protectVRam();
size = originalSize;
return true;
}
custom_texture.LoadCustomTextureAsync(this);
}
void *temp_tex_buffer = NULL;
u32 upscaled_w = width;
@ -544,7 +555,10 @@ bool BaseTextureCacheData::Update()
{
PixelBuffer<u32> pb0;
pb0.init(2, 2 ,false);
texconv32(&pb0, (u8*)&vram[vram_addr], 2, 2);
if (tcw.PixelFmt == PixelYUV)
// Use higher LoD mipmap
vram_addr = startAddress + VQMipPoint[1];
texconv32(&pb0, &vram[vram_addr], 2, 2);
*pb32.data() = *pb0.data(1, 1);
continue;
}

View File

@ -216,11 +216,6 @@ void DX11Context::term()
pDeviceContext.reset();
pDevice.reset();
d3dcompiler = nullptr;
if (d3dcompilerHandle != NULL)
{
FreeLibrary(d3dcompilerHandle);
d3dcompilerHandle = NULL;
}
}
void DX11Context::Present()
@ -359,15 +354,15 @@ const pD3DCompile DX11Context::getCompiler()
if (d3dcompiler == nullptr)
{
#ifndef TARGET_UWP
d3dcompilerHandle = LoadLibraryA("d3dcompiler_47.dll");
if (d3dcompilerHandle == NULL)
d3dcompilerHandle = LoadLibraryA("d3dcompiler_46.dll");
if (d3dcompilerHandle == NULL)
if (!d3dcompilerLib.load("d3dcompiler_47.dll"))
{
WARN_LOG(RENDERER, "Neither d3dcompiler_47.dll or d3dcompiler_46.dll can be loaded");
return D3DCompile;
if (!d3dcompilerLib.load("d3dcompiler_46.dll"))
{
WARN_LOG(RENDERER, "Neither d3dcompiler_47.dll or d3dcompiler_46.dll can be loaded");
return D3DCompile;
}
}
d3dcompiler = (pD3DCompile)GetProcAddress(d3dcompilerHandle, "D3DCompile");
d3dcompiler = d3dcompilerLib.getFunc("D3DCompile", d3dcompiler);
#endif
if (d3dcompiler == nullptr)
d3dcompiler = D3DCompile;

View File

@ -26,6 +26,7 @@
#include <d3d11.h>
#include <dxgi1_2.h>
#include "windows/comptr.h"
#include "windows/dynlink.h"
#include "dx11_overlay.h"
#include "wsi/context.h"
@ -97,7 +98,7 @@ private:
Samplers samplers;
D3D_FEATURE_LEVEL featureLevel{};
bool supportedTexFormats[5] {}; // indexed by TextureType enum
HMODULE d3dcompilerHandle = NULL;
WinLibLoader d3dcompilerLib;
pD3DCompile d3dcompiler = nullptr;
static constexpr UINT VENDOR_INTEL = 0x8086;

View File

@ -457,19 +457,18 @@ void D3DShaders::init(const ComPtr<IDirect3DDevice9>& device)
for (int ver = 43; ver >= 24; ver--)
{
std::string dllname = "d3dx9_" + std::to_string(ver) + ".dll";
d3dx9Library = LoadLibraryA(dllname.c_str());
if (d3dx9Library != NULL) {
if (d3dx9Library.load(dllname.c_str())) {
DEBUG_LOG(RENDERER, "Loaded %s", dllname.c_str());
break;
}
}
if (d3dx9Library == NULL) {
if (!d3dx9Library.loaded()) {
ERROR_LOG(RENDERER, "Cannot load d3dx9_??.dll");
throw FlycastException("Cannot load d3dx9_??.dll");
}
pD3DXCompileShader = (decltype(D3DXCompileShader) *)GetProcAddress(d3dx9Library, "D3DXCompileShader");
pD3DXGetVertexShaderProfile = (decltype(D3DXGetVertexShaderProfile) *)GetProcAddress(d3dx9Library, "D3DXGetVertexShaderProfile");
pD3DXGetPixelShaderProfile = (decltype(D3DXGetPixelShaderProfile) *)GetProcAddress(d3dx9Library, "D3DXGetPixelShaderProfile");
pD3DXCompileShader = d3dx9Library.getFunc("D3DXCompileShader", pD3DXCompileShader);
pD3DXGetVertexShaderProfile = d3dx9Library.getFunc("D3DXGetVertexShaderProfile", pD3DXGetVertexShaderProfile);
pD3DXGetPixelShaderProfile = d3dx9Library.getFunc("D3DXGetPixelShaderProfile", pD3DXGetPixelShaderProfile);
if (pD3DXCompileShader == nullptr || pD3DXGetVertexShaderProfile == nullptr || pD3DXGetPixelShaderProfile == nullptr) {
ERROR_LOG(RENDERER, "Cannot find entry point in d3dx9_??.dll");
throw FlycastException("Cannot load d3dx9_??.dll");
@ -484,7 +483,4 @@ void D3DShaders::term()
for (auto& shader : modVolShaders)
shader.reset();
device.reset();
if (d3dx9Library != NULL)
FreeLibrary(d3dx9Library);
d3dx9Library = NULL;
}

View File

@ -19,6 +19,7 @@
#pragma once
#include <unordered_map>
#include "dxcontext.h"
#include "windows/dynlink.h"
#include <d3dx9shader.h>
class D3DShaders
@ -41,7 +42,7 @@ private:
std::unordered_map<u32, ComPtr<IDirect3DPixelShader9>> shaders;
ComPtr<IDirect3DVertexShader9> vertexShaders[4];
ComPtr<IDirect3DPixelShader9> modVolShaders[2];
HMODULE d3dx9Library = NULL;
WinLibLoader d3dx9Library;
decltype(D3DXCompileShader) *pD3DXCompileShader = nullptr;
decltype(D3DXGetVertexShaderProfile) *pD3DXGetVertexShaderProfile = nullptr;
decltype(D3DXGetPixelShaderProfile) *pD3DXGetPixelShaderProfile = nullptr;

View File

@ -40,17 +40,10 @@ bool DXContext::init(bool keepCurrentWindow)
}
#endif
d3d9Library = LoadLibraryA("D3D9.DLL");
if (d3d9Library == NULL)
{
ERROR_LOG(RENDERER, "Cannot load D3D9.DLL");
term();
return false;
}
decltype(Direct3DCreate9) *pDirect3DCreate9 = (decltype(Direct3DCreate9) *)GetProcAddress(d3d9Library, "Direct3DCreate9");
decltype(Direct3DCreate9) *pDirect3DCreate9 = d3d9Library.getFunc("Direct3DCreate9", pDirect3DCreate9);
if (pDirect3DCreate9 == nullptr)
{
ERROR_LOG(RENDERER, "Cannot find entry point Direct3DCreate9");
ERROR_LOG(RENDERER, "Cannot load D3D9.DLL");
term();
return false;
}
@ -123,9 +116,6 @@ void DXContext::term()
imguiDriver.reset();
pDevice.reset();
pD3D.reset();
if (d3d9Library != NULL)
FreeLibrary(d3d9Library);
d3d9Library = NULL;
deviceReady = false;
}

View File

@ -23,6 +23,7 @@
#include <windows.h>
#include <d3d9.h>
#include "windows/comptr.h"
#include "windows/dynlink.h"
#include "d3d_overlay.h"
#include "wsi/context.h"
@ -63,7 +64,7 @@ public:
private:
void resetDevice();
HMODULE d3d9Library = NULL;
WinLibLoader d3d9Library{ "D3D9.DLL" };
ComPtr<IDirect3D9> pD3D;
ComPtr<IDirect3DDevice9> pDevice;
D3DPRESENT_PARAMETERS d3dpp{};

View File

@ -202,10 +202,12 @@ lowp vec4 getPaletteEntry(highp float colorIndex)
lowp vec4 palettePixel(highp vec3 coords)
{
#if TARGET_GL != GLES2 && TARGET_GL != GL2 && DIV_POS_Z != 1
coords.xy /= coords.z;
#endif
#if TARGET_GL == GLES2 || TARGET_GL == GL2 || DIV_POS_Z == 1
return getPaletteEntry(texture(tex, coords.xy).FOG_CHANNEL);
#else
return getPaletteEntry(textureProj(tex, coords).FOG_CHANNEL);
#endif
}
#elif pp_Palette == 2 // Bi-linear filtering

View File

@ -42,6 +42,7 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
#endif
#include <memory>
#include <vulkan/vulkan_format_traits.hpp>
void ReInitOSD();
@ -632,23 +633,28 @@ void VulkanContext::CreateSwapChain()
for (auto& img : imageViews)
img.reset();
// get the supported VkFormats
std::vector<vk::SurfaceFormatKHR> formats = physicalDevice.getSurfaceFormatsKHR(GetSurface());
assert(!formats.empty());
for (const auto& f : formats)
{
DEBUG_LOG(RENDERER, "Supported surface format: %s", vk::to_string(f.format).c_str());
// Try to find an non-sRGB color format
if (f.format == vk::Format::eB8G8R8A8Unorm || f.format == vk::Format::eR8G8B8A8Unorm)
// Determine surface format and color-space
std::vector<vk::SurfaceFormatKHR> surfaceFormats = physicalDevice.getSurfaceFormatsKHR(GetSurface());
// Prefer a non-sRGB image format
std::stable_partition(surfaceFormats.begin(), surfaceFormats.end(),
[](const vk::SurfaceFormatKHR& surfaceFormat) -> bool
{
colorFormat = f.format;
break;
return std::string_view("SRGB").compare(vk::componentNumericFormat(surfaceFormat.format, 0)) != 0;
}
}
if (colorFormat == vk::Format::eUndefined)
{
colorFormat = (formats[0].format == vk::Format::eUndefined) ? vk::Format::eB8G8R8A8Unorm : formats[0].format;
}
);
// Prefer an sRGB presentation color-space
std::stable_partition(surfaceFormats.begin(), surfaceFormats.end(),
[](const vk::SurfaceFormatKHR& surfaceFormat) -> bool
{
return surfaceFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear;
}
);
// Top of the list is the best candidate surface format/color-space
const vk::SurfaceFormatKHR& targetSurfaceFormat = surfaceFormats[0];
presentFormat = targetSurfaceFormat.format;
int tries = 0;
do {
@ -708,7 +714,7 @@ void VulkanContext::CreateSwapChain()
// for final screenshot or Syphon
usage |= vk::ImageUsageFlagBits::eTransferSrc;
#endif
vk::SwapchainCreateInfoKHR swapChainCreateInfo(vk::SwapchainCreateFlagsKHR(), GetSurface(), imageCount, colorFormat, vk::ColorSpaceKHR::eSrgbNonlinear,
vk::SwapchainCreateInfoKHR swapChainCreateInfo(vk::SwapchainCreateFlagsKHR(), GetSurface(), imageCount, targetSurfaceFormat.format, targetSurfaceFormat.colorSpace,
swapchainExtent, 1, usage, vk::SharingMode::eExclusive, 0, nullptr, preTransform, vk::CompositeAlphaFlagBitsKHR::eOpaque, swapchainPresentMode, true, nullptr);
u32 queueFamilyIndices[2] = { graphicsQueueIndex, presentQueueIndex };
@ -744,7 +750,7 @@ void VulkanContext::CreateSwapChain()
u32 imageIdx = 0;
for (auto image : swapChainImages)
{
vk::ImageViewCreateInfo imageViewCreateInfo(vk::ImageViewCreateFlags(), image, vk::ImageViewType::e2D, colorFormat, componentMapping, subResourceRange);
vk::ImageViewCreateInfo imageViewCreateInfo(vk::ImageViewCreateFlags(), image, vk::ImageViewType::e2D, presentFormat, componentMapping, subResourceRange);
imageViews[imageIdx++] = device->createImageViewUnique(imageViewCreateInfo);
// create a UniqueCommandPool to allocate a CommandBuffer from
@ -757,7 +763,7 @@ void VulkanContext::CreateSwapChain()
depthFormat = findDepthFormat(physicalDevice);
// Render pass
vk::AttachmentDescription attachmentDescription = vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), colorFormat, vk::SampleCountFlagBits::e1,
vk::AttachmentDescription attachmentDescription = vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), presentFormat, vk::SampleCountFlagBits::e1,
vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare,
vk::ImageLayout::eUndefined, vk::ImageLayout::ePresentSrcKHR);
@ -1340,9 +1346,22 @@ bool VulkanContext::GetLastFrame(std::vector<u8>& data, int& width, int& height)
else
width = w;
}
vk::Format imageFormat = vk::Format::eR8G8B8A8Unorm;
const vk::ImageUsageFlags imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc;
// Test if RGB8 is natively supported to avoid having to do a format conversion
bool nativeRgb8 = false;
vk::ImageFormatProperties rgb8Properties{};
if (physicalDevice.getImageFormatProperties(vk::Format::eR8G8B8Unorm, vk::ImageType::e2D, vk::ImageTiling::eOptimal, imageUsage, {}, &rgb8Properties) == vk::Result::eSuccess)
{
nativeRgb8 = true;
imageFormat = vk::Format::eR8G8B8Unorm;
}
// color attachment
FramebufferAttachment attachment(physicalDevice, *device);
attachment.Init(width, height, vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc, "screenshot");
attachment.Init(width, height, imageFormat, imageUsage, "screenshot");
// command buffer
vk::UniqueCommandBuffer commandBuffer = std::move(device->allocateCommandBuffersUnique(
vk::CommandBufferAllocateInfo(*commandPools.back(), vk::CommandBufferLevel::ePrimary, 1)).front());
@ -1352,7 +1371,7 @@ bool VulkanContext::GetLastFrame(std::vector<u8>& data, int& width, int& height)
CommandBufferDebugScope _(commandBuffer.get(), "GetLastFrame", scopeColor);
// render pass
vk::AttachmentDescription attachmentDescription = vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), vk::Format::eR8G8B8A8Unorm, vk::SampleCountFlagBits::e1,
vk::AttachmentDescription attachmentDescription = vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), imageFormat, vk::SampleCountFlagBits::e1,
vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare,
vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferSrcOptimal);
vk::AttachmentReference colorReference(0, vk::ImageLayout::eColorAttachmentOptimal);
@ -1417,15 +1436,25 @@ bool VulkanContext::GetLastFrame(std::vector<u8>& data, int& width, int& height)
const u8 *img = (const u8 *)attachment.GetBufferData()->MapMemory();
data.clear();
data.reserve(width * height * 3);
for (int y = 0; y < height; y++)
if (nativeRgb8)
{
for (int x = 0; x < width; x++)
// Format is already RGB, can be directly copied
data.resize(width * height * 3);
std::memcpy(data.data(), img, width* height * 3);
}
else
{
data.reserve(width * height * 3);
// RGBA -> RGB
for (int y = 0; y < height; y++)
{
data.push_back(*img++);
data.push_back(*img++);
data.push_back(*img++);
img++;
for (int x = 0; x < width; x++)
{
data.push_back(*img++);
data.push_back(*img++);
data.push_back(*img++);
img++;
}
}
}
attachment.GetBufferData()->UnmapMemory();

View File

@ -240,7 +240,7 @@ private:
vk::UniqueSwapchainKHR swapChain;
std::vector<vk::UniqueImageView> imageViews;
u32 currentImage = 0;
vk::Format colorFormat = vk::Format::eUndefined;
vk::Format presentFormat = vk::Format::eUndefined;
vk::Queue graphicsQueue;
vk::Queue presentQueue;

File diff suppressed because it is too large Load Diff

View File

@ -20,10 +20,13 @@
#include "types.h"
#include "emulator.h"
#include "sdl_gamepad.h"
#if defined(_WIN32) && !defined(TARGET_UWP)
#define USE_DREAMCONN 1
#if (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))) && !defined(TARGET_UWP)
#define USE_DREAMCASTCONTROLLER 1
#define TYPE_DREAMCONN 1
#define TYPE_DREAMPORT 2
#include <asio.hpp>
#endif
#include <memory>
struct MapleMsg
{
@ -47,23 +50,24 @@ static_assert(sizeof(MapleMsg) == 1028);
class DreamConn
{
const int bus;
#ifdef USE_DREAMCONN
asio::ip::tcp::iostream iostream;
int bus = -1;
const int dreamcastControllerType;
#ifdef USE_DREAMCASTCONTROLLER
std::unique_ptr<class DreamcastControllerConnection> dcConnection;
#endif
bool maple_io_connected = false;
u8 expansionDevs = 0;
static constexpr u16 BASE_PORT = 37393;
public:
DreamConn(int bus) : bus(bus) {
connect();
}
~DreamConn() {
disconnect();
}
DreamConn(int bus, int dreamcastControllerType, int joystick_idx, SDL_Joystick* sdl_joystick);
~DreamConn();
bool send(const MapleMsg& msg);
// When called, do teardown stuff like reset screen
void gameTermination();
int getBus() const {
return bus;
}
@ -74,7 +78,12 @@ public:
return expansionDevs & 2;
}
private:
int getDefaultBus();
void changeBus(int newBus);
std::string getName();
void connect();
void disconnect();
};
@ -86,9 +95,10 @@ public:
~DreamConnGamepad();
void set_maple_port(int port) override;
void registered() override;
bool gamepad_btn_input(u32 code, bool pressed) override;
bool gamepad_axis_input(u32 code, int value) override;
static bool isDreamConn(int deviceIndex);
static bool isDreamcastController(int deviceIndex);
private:
static void handleEvent(Event event, void *arg);

View File

@ -83,7 +83,7 @@ static void sdl_open_joystick(int index)
std::shared_ptr<SDLGamepad> gamepad = std::make_shared<SwitchGamepad>(index < MAPLE_PORTS ? index : -1, index, pJoystick);
#else
std::shared_ptr<SDLGamepad> gamepad;
if (DreamConnGamepad::isDreamConn(index))
if (DreamConnGamepad::isDreamcastController(index))
gamepad = std::make_shared<DreamConnGamepad>(index < MAPLE_PORTS ? index : -1, index, pJoystick);
else
gamepad = std::make_shared<SDLGamepad>(index < MAPLE_PORTS ? index : -1, index, pJoystick);
@ -266,6 +266,15 @@ void input_sdl_init()
if (settings.input.keyboardLangId == KeyboardLayout::US)
settings.input.keyboardLangId = detectKeyboardLayout();
barcode.clear();
// Add MacOS and Windows mappings for Dreamcast Controller USB
// Linux mappings are OK by default
// Can be removed once mapping is merged into SDL, see https://github.com/libsdl-org/SDL/pull/12039
#if (defined(__APPLE__) && defined(TARGET_OS_MAC))
SDL_GameControllerAddMapping("0300000009120000072f000000010000,OrangeFox86 DreamPort,a:b0,b:b1,x:b3,y:b4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,dpdown:h0.4,leftx:a0,lefty:a1,lefttrigger:a2,righttrigger:a5,start:b11");
#elif defined(_WIN32)
SDL_GameControllerAddMapping("0300000009120000072f000000000000,OrangeFox86 DreamPort,a:b0,b:b1,x:b3,y:b4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,dpdown:h0.4,leftx:a0,lefty:a1,lefttrigger:-a2,righttrigger:-a5,start:b11");
#endif
}
void input_sdl_quit()
@ -538,7 +547,7 @@ void input_sdl_handle()
case SDL_JOYDEVICEREMOVED:
sdl_close_joystick((SDL_JoystickID)event.jdevice.which);
break;
case SDL_DROPFILE:
gui_start_game(event.drop.file);
break;
@ -593,7 +602,7 @@ static inline void get_window_state()
windowPos.h /= hdpiScaling;
SDL_GetWindowPosition(window, &windowPos.x, &windowPos.y);
}
}
#if defined(_WIN32) && !defined(TARGET_UWP)
@ -620,14 +629,14 @@ bool sdl_recreate_window(u32 flags)
PROCESS_SYSTEM_DPI_AWARE = 1,
PROCESS_PER_MONITOR_DPI_AWARE = 2
} PROCESS_DPI_AWARENESS;
HRESULT(WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS dpiAwareness); // Windows 8.1 and later
void* shcoreDLL = SDL_LoadObject("SHCORE.DLL");
if (shcoreDLL) {
SetProcessDpiAwareness = (HRESULT(WINAPI *)(PROCESS_DPI_AWARENESS)) SDL_LoadFunction(shcoreDLL, "SetProcessDpiAwareness");
if (SetProcessDpiAwareness) {
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
if (SDL_GetDisplayDPI(0, &settings.display.dpi, NULL, NULL) != -1){ //SDL_WINDOWPOS_UNDEFINED is Display 0
//When using HiDPI mode, set correct DPI scaling
hdpiScaling = settings.display.dpi / 96.f;
@ -636,7 +645,7 @@ bool sdl_recreate_window(u32 flags)
SDL_UnloadObject(shcoreDLL);
}
#endif
#ifdef __SWITCH__
AppletOperationMode om = appletGetOperationMode();
if (om == AppletOperationMode_Handheld)

View File

@ -10,7 +10,6 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <vector>
#include <chrono>
#ifdef _WIN32
#include <algorithm>

View File

@ -12,6 +12,11 @@
#else
#define DYNACALL
#endif
#ifdef _MSC_VER
// conversion from 't1' to 't2', possible loss of data
#pragma warning(disable: 4267)
#pragma warning(disable: 4244)
#endif
#include <cstdint>
#include <cstddef>

View File

@ -1609,6 +1609,8 @@ static void contentpath_warning_popup()
}
}
#if !defined(NDEBUG) || defined(DEBUGFAST) || FC_PROFILER
static void gui_debug_tab()
{
header("Logging");
@ -1640,6 +1642,9 @@ static void gui_debug_tab()
}
ImGui::EndCombo();
}
ImGui::InputText("Log Server", &config::LogServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr);
ImGui::SameLine();
ShowHelpMarker("Log to this hostname[:port] with UDP. Default port is 31667.");
}
#if FC_PROFILER
ImGui::Spacing();
@ -1663,6 +1668,7 @@ static void gui_debug_tab()
}
#endif
}
#endif
static void addContentPathCallback(const std::string& path)
{
@ -2789,6 +2795,11 @@ static void gui_settings_network()
OptionCheckbox("Broadband Adapter Emulation", config::EmulateBBA,
"Emulate the Ethernet Broadband Adapter (BBA) instead of the Modem");
}
OptionCheckbox("Use DCNet (Experimental)", config::UseDCNet, "Connect to the experimental DCNet cloud service.");
ImGui::InputText("ISP User Name", &config::ISPUsername.get(), ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CallbackCharFilter,
[](ImGuiInputTextCallbackData *data) { return static_cast<int>(data->EventChar <= ' ' || data->EventChar > '~'); }, nullptr);
ImGui::SameLine();
ShowHelpMarker("The ISP user name stored in the console Flash RAM. Used by some online games as the player name. Leave blank to keep the current Flash RAM value.");
}
#ifdef NAOMI_MULTIBOARD
ImGui::Spacing();
@ -2839,14 +2850,8 @@ static void gui_settings_advanced()
"Dump all textures into data/texdump/<game id>");
bool logToFile = cfgLoadBool("log", "LogToFile", false);
bool newLogToFile = logToFile;
ImGui::Checkbox("Log to File", &newLogToFile);
if (logToFile != newLogToFile)
{
cfgSaveBool("log", "LogToFile", newLogToFile);
LogManager::Shutdown();
LogManager::Init();
}
if (ImGui::Checkbox("Log to File", &logToFile))
cfgSaveBool("log", "LogToFile", logToFile);
ImGui::SameLine();
ShowHelpMarker("Log debug information to flycast.log");
#ifdef SENTRY_UPLOAD

117
core/util/periodic_thread.h Normal file
View File

@ -0,0 +1,117 @@
/*
Copyright 2025 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 "stdclass.h"
#include "oslib/oslib.h"
#include <thread>
#include <atomic>
#include <mutex>
#include <stdexcept>
#include <functional>
#include "log/Log.h"
class VPeriodicThread
{
public:
virtual ~VPeriodicThread() {
stop();
}
void start()
{
LockGuard _(mutex);
if (thread.joinable())
return;
running = true;
event.Reset();
thread = std::thread([this]() {
ThreadName _(name);
try {
init();
while (true)
{
if (period != 0)
event.Wait(period);
else
event.Wait();
if (!running)
break;
doWork();
}
term();
} catch (const std::runtime_error& e) {
ERROR_LOG(COMMON, "PeriodicThread %s: runtime error %s", name, e.what());
} catch (...) {
ERROR_LOG(COMMON, "PeriodicThread %s: uncaught unknown exception", name);
}
});
}
void stop()
{
LockGuard _(mutex);
running = false;
event.Set();
if (thread.joinable())
thread.join();
}
void setPeriod(int period) {
this->period = period;
}
void notify() {
event.Set();
}
protected:
VPeriodicThread(const char *name, int periodMS = 0)
: name(name), period(periodMS)
{ }
virtual void doWork() = 0;
virtual void init() {}
virtual void term() {}
private:
using LockGuard = std::lock_guard<std::mutex>;
const char *name;
int period;
cResetEvent event;
std::thread thread;
std::atomic<bool> running = false;
std::mutex mutex;
};
class PeriodicThread : public VPeriodicThread
{
public:
template<class F, class... Args>
PeriodicThread(const char *name, F&& f, Args&&... args)
: VPeriodicThread(name, 0)
{
work = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
}
private:
void doWork() override {
work();
}
std::function<void()> work;
};

71
core/util/tsqueue.h Normal file
View File

@ -0,0 +1,71 @@
/*
Copyright 2025 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 <queue>
#include <mutex>
#include <condition_variable>
template<typename T>
class TsQueue
{
public:
void push(const T& t)
{
std::lock_guard<std::mutex> _(mutex);
queue.emplace(t);
condVar.notify_one();
}
void push(T&& t)
{
std::lock_guard<std::mutex> _(mutex);
queue.push(std::move(t));
condVar.notify_one();
}
T pop()
{
std::unique_lock<std::mutex> lock(mutex);
condVar.wait(lock, [this]() { return !queue.empty(); });
T t = std::move(queue.front());
queue.pop();
return t;
}
size_t size() const {
std::lock_guard<std::mutex> _(mutex);
return queue.size();
}
bool empty() const {
std::lock_guard<std::mutex> _(mutex);
return queue.empty();
}
void clear()
{
std::queue<T> empty;
std::lock_guard<std::mutex> _(mutex);
std::swap(queue, empty);
}
// TODO bool tryPop(T& t, std::chrono::duration timeout) ?
private:
std::queue<T> queue;
mutable std::mutex mutex;
std::condition_variable condVar;
};

95
core/util/worker_thread.h Normal file
View File

@ -0,0 +1,95 @@
/*
Copyright 2025 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 "tsqueue.h"
#include "oslib/oslib.h"
#include <variant>
#include <thread>
#include <memory>
#include <functional>
#include <future>
class WorkerThread
{
public:
using Function = std::function<void()>;
WorkerThread(const char *name) : name(name) {
}
~WorkerThread() {
stop();
}
void stop()
{
std::lock_guard<std::mutex> _(mutex);
if (thread != nullptr && thread->joinable())
{
queue.push(Exit());
thread->join();
thread.reset();
}
}
void run(Function&& task) {
start();
queue.push(std::move(task));
}
template<class F, class... Args>
auto runFuture(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>
{
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
run([task]() {
(*task)();
});
return task->get_future();
}
private:
void start()
{
std::lock_guard<std::mutex> _(mutex);
if (thread != nullptr && thread->joinable())
return;
queue.clear();
thread = std::make_unique<std::thread>([this]()
{
ThreadName _(name);
while (true)
{
Task t = queue.pop();
if (std::get_if<Exit>(&t) != nullptr)
break;
Function& func = std::get<Function>(t);
func();
}
});
}
const char * const name;
using Exit = std::monostate;
using Task = std::variant<Exit, Function>;
TsQueue<Task> queue;
std::unique_ptr<std::thread> thread;
std::mutex mutex;
};

64
core/windows/dynlink.h Executable file
View File

@ -0,0 +1,64 @@
/*
Copyright 2025 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 <windows.h>
namespace detail
{
template <typename Ret, typename... Args>
struct ProxyTraits {
using funcType = Ret (WINAPI *)(Args...);
};
}
class WinLibLoader
{
public:
WinLibLoader(const char* name = nullptr) : name(name) {
}
~WinLibLoader() {
if (hinst != NULL)
FreeLibrary(hinst);
}
template <typename Ret, typename... Args>
auto getFunc(const char* functionName, Ret(WINAPI * const funcPtr)(Args...))
{
using funcType = typename detail::ProxyTraits<Ret, Args...>::funcType;
if (!loaded()) {
if (!load(name))
return static_cast<funcType>(nullptr);
}
return reinterpret_cast<funcType>(GetProcAddress(hinst, functionName));
}
bool load(const char* name)
{
if (hinst != NULL)
FreeLibrary(hinst);
hinst = LoadLibraryA(name);
return hinst != NULL;
}
bool loaded() const { return hinst != NULL; }
private:
const char* name;
HINSTANCE hinst = NULL;
};

View File

@ -24,6 +24,14 @@
static PVOID vectoredHandler;
static LONG (WINAPI *prevExceptionHandler)(EXCEPTION_POINTERS *ep);
#ifndef LIBRETRO
const char *getThreadName();
#else
// TODO
static const char *getThreadName() {
return "";
}
#endif
static void readContext(const EXCEPTION_POINTERS *ep, host_context_t &context)
{
@ -145,7 +153,7 @@ static LONG WINAPI exceptionHandler(EXCEPTION_POINTERS *ep)
}
#endif
ERROR_LOG(COMMON, "[GPF] PC %p unhandled access to %p", (void *)context.pc, address);
ERROR_LOG(COMMON, "[GPF] Thread:%s PC %p unhandled access to %p", getThreadName(), (void *)context.pc, address);
if (prevExceptionHandler != nullptr)
prevExceptionHandler(ep);

View File

@ -3,6 +3,7 @@
#include "oslib/virtmem.h"
#include <windows.h>
#include "dynlink.h"
namespace virtmem
{
@ -54,12 +55,22 @@ static std::vector<void *> mapped_regions;
// Implement vmem initialization for RAM, ARAM, VRAM and SH4 context, fpcb etc.
#ifdef TARGET_UWP
static WinLibLoader kernel32("Kernel32.dll");
static LPVOID(*MapViewOfFileEx)(HANDLE, DWORD, DWORD, DWORD, SIZE_T, LPVOID);
#endif
// Please read the POSIX implementation for more information. On Windows this is
// rather straightforward.
bool init(void **vmem_base_addr, void **sh4rcb_addr, size_t ramSize)
{
#ifdef TARGET_UWP
return false;
if (MapViewOfFileEx == nullptr)
{
MapViewOfFileEx = kernel32.getFunc("MapViewOfFileEx", MapViewOfFileEx);
if (MapViewOfFileEx == nullptr)
return false;
}
#endif
unmapped_regions.reserve(32);
mapped_regions.reserve(32);
@ -112,7 +123,6 @@ void ondemand_page(void *address, unsigned size_bytes) {
void create_mappings(const Mapping *vmem_maps, unsigned nummaps) {
// Since this is tricky to get right in Windows (in posix one can just unmap sections and remap later)
// we unmap the whole thing only to remap it later.
#ifndef TARGET_UWP
// Unmap the whole section
for (void *p : mapped_regions)
UnmapViewOfFile(p);
@ -148,7 +158,6 @@ void create_mappings(const Mapping *vmem_maps, unsigned nummaps) {
}
}
}
#endif
}
template<typename Mapper>

View File

@ -37,6 +37,7 @@
#include "emulator.h"
#include "ui/mainui.h"
#include "oslib/directory.h"
#include "dynlink.h"
#ifdef USE_BREAKPAD
#include "breakpad/client/windows/handler/exception_handler.h"
#include "version.h"
@ -432,24 +433,39 @@ void os_RunInstance(int argc, const char *argv[])
}
}
static WinLibLoader kernelBaseLib("KernelBase.dll");
void os_SetThreadName(const char *name)
{
#ifndef TARGET_UWP
nowide::wstackstring wname;
if (wname.convert(name))
{
static HRESULT (*SetThreadDescription)(HANDLE, PCWSTR);
if (SetThreadDescription == nullptr)
{
// supported in Windows 10, version 1607 or Windows Server 2016
HINSTANCE libh = LoadLibraryW(L"KernelBase.dll");
if (libh != NULL)
SetThreadDescription = (HRESULT (*)(HANDLE, PCWSTR))GetProcAddress(libh, "SetThreadDescription");
}
static HRESULT (WINAPI *SetThreadDescription)(HANDLE, PCWSTR) = kernelBaseLib.getFunc("SetThreadDescription", SetThreadDescription);
if (SetThreadDescription != nullptr)
SetThreadDescription(GetCurrentThread(), wname.get());
}
#endif
}
const char *getThreadName()
{
static HRESULT (WINAPI *GetThreadDescription)(HANDLE, PWSTR *) = kernelBaseLib.getFunc("GetThreadDescription", GetThreadDescription);
if (GetThreadDescription == nullptr)
return "?";
PWSTR wname = nullptr;
if (SUCCEEDED(GetThreadDescription(GetCurrentThread(), &wname)))
{
nowide::stackstring stname;
thread_local std::string name;
if (stname.convert(wname))
name = stname.get();
else
name = "?";
LocalFree(wname);
return name.c_str();
}
else {
return "?";
}
}
#ifdef VIDEO_ROUTING

View File

@ -1,5 +1,5 @@
[versions]
agp = "8.7.0"
agp = "8.8.0"
appcompat = "1.3.1"
commonsLang3 = "3.12.0"
documentfile = "1.0.1"

View File

@ -1,6 +1,6 @@
#Sat Oct 12 11:37:50 CEST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -245,6 +245,20 @@ struct retro_core_option_v2_definition option_defs_us[] = {
},
"enabled",
},
{
CORE_OPTION_NAME "_dcnet",
"Use DCNet (Experimental)",
NULL,
"Connect to the experimental DCNet cloud service.",
NULL,
"system",
{
{ "disabled", NULL },
{ "enabled", NULL },
{ NULL, NULL },
},
"disabled",
},
{
CORE_OPTION_NAME "_internal_resolution",

View File

@ -122,6 +122,8 @@ Option<int> GGPOAnalogAxes("", 0);
Option<bool> NetworkOutput(CORE_OPTION_NAME "_network_output", false);
Option<int> MultiboardSlaves("", 0);
Option<bool> BattleCableEnable("", false);
Option<bool> UseDCNet(CORE_OPTION_NAME "_dcnet", false);
OptionString ISPUsername("", "flycast1");
// Maple

View File

@ -1,5 +1,8 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
// Seems to be missing in newlib, dumb stub (file permissions is not a thing on fat32 anyways)
mode_t umask(mode_t mask)
@ -7,3 +10,32 @@ mode_t umask(mode_t mask)
return mask;
}
int pause()
{
sleep(0xffffffff);
return -1;
}
// FIXME always failing stub
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
{
switch (how)
{
case SIG_BLOCK:
case SIG_UNBLOCK:
case SIG_SETMASK:
break;
default:
errno = EINVAL;
return -1;
}
errno = ENOSYS;
return -1;
}
// Map an interface index into its name.
char *if_indextoname(unsigned ifindex, char *ifname)
{
errno = ENXIO;
return NULL;
}

28
shell/switch/sys/uio.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef SYS_UIO_H_
#define SYS_UIO_H_
#include <sys/types.h>
#include <sys/_iovec.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Read data from file descriptor FD, and put the result in the
buffers described by IOVEC, which is a vector of COUNT 'struct iovec's.
The buffers are filled in the order specified.
Operates just like 'read' (see <unistd.h>) except that data are
put in IOVEC instead of a contiguous buffer. */
extern ssize_t readv (int __fd, const struct iovec *__iovec, int __count);
/* Write data pointed by the buffers described by IOVEC, which
is a vector of COUNT 'struct iovec's, to file descriptor FD.
The data is written in the order specified.
Operates just like 'write' (see <unistd.h>) except that the data
are taken from IOVEC instead of a contiguous buffer. */
extern ssize_t writev (int __fd, const struct iovec *__iovec, int __count);
#ifdef __cplusplus
}
#endif
#endif /* SYS_UIO_H_ */

50
shell/switch/sys/un.h Normal file
View File

@ -0,0 +1,50 @@
/* Copyright (C) 1991-2020 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#ifndef _SYS_UN_H
#define _SYS_UN_H 1
#include <sys/socket.h>
#include <netinet/in.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Structure describing the address of an AF_LOCAL (aka AF_UNIX) socket. */
struct sockaddr_un
{
sa_family_t sun_family;
char sun_path[108]; /* Path name. */
};
/* Should be defined in sockets.h */
struct ipv6_mreq
{
struct in6_addr ipv6mr_multiaddr;
unsigned int ipv6mr_interface;
};
/* Should be declared in net/if.h */
char* if_indextoname(unsigned int, char*);
unsigned int if_nametoindex(const char*);
#ifdef __cplusplus
}
#endif
#endif /* sys/un.h */

View File

@ -0,0 +1,26 @@
#include "gtest/gtest.h"
#include "util/periodic_thread.h"
#include <atomic>
class PeriodicThreadTest : public ::testing::Test
{
};
TEST_F(PeriodicThreadTest, Basic)
{
std::atomic<int> counter = 0;
PeriodicThread thread = PeriodicThread("Test", [&]() {
counter++;
});
thread.setPeriod(10);
thread.start();
usleep(15'000);
ASSERT_LT(0, counter);
int copy = counter;
usleep(15'000);
ASSERT_LT(copy, counter);
thread.stop();
copy = counter;
usleep(15'000);
ASSERT_EQ(copy, counter);
}

View File

@ -0,0 +1,97 @@
#include "gtest/gtest.h"
#include "util/tsqueue.h"
#include <atomic>
#include <future>
class TsQueueTest : public ::testing::Test
{
};
TEST_F(TsQueueTest, Basic)
{
TsQueue<int> queue;
ASSERT_TRUE(queue.empty());
ASSERT_EQ(0, queue.size());
queue.push(42);
ASSERT_FALSE(queue.empty());
ASSERT_EQ(1, queue.size());
ASSERT_EQ(42, queue.pop());
queue.push(1);
queue.push(2);
queue.push(3);
ASSERT_FALSE(queue.empty());
ASSERT_EQ(3, queue.size());
ASSERT_EQ(1, queue.pop());
ASSERT_EQ(2, queue.pop());
ASSERT_EQ(3, queue.pop());
}
TEST_F(TsQueueTest, MultiThread)
{
TsQueue<bool> queue;
std::atomic<bool> gotResult = false;
std::future<bool> future = std::async(std::launch::async, [&]() {
bool res = queue.pop();
gotResult = true;
return res;
});
usleep(500'000);
ASSERT_FALSE(gotResult);
ASSERT_EQ(std::future_status::timeout, future.wait_for(std::chrono::seconds(0)));
queue.push(true);
ASSERT_TRUE(future.get());
}
TEST_F(TsQueueTest, Class)
{
struct T1 {
float f;
};
TsQueue<T1> q1;
q1.push({ 3.14f });
T1 r1 = q1.pop();
ASSERT_EQ(3.14f, r1.f);
class T2
{
public:
std::string s;
};
TsQueue<T2> q2;
q2.push({ "pi" });
T2 r2 = q2.pop();
ASSERT_EQ(std::string("pi"), r2.s);
// Non copyable, but moveable
class T3
{
public:
T3(const char *s) : s(s) {}
T3(const T3&) = delete;
T3(T3&& other) {
std::swap(s, other.s);
}
T3& operator=(const T3& other) = delete;
T3& operator=(T3&& other) {
std::swap(s, other.s);
return *this;
}
const char *s;
};
TsQueue<T3> q3;
q3.push(T3("pi"));
T3 r3 = q3.pop();
ASSERT_EQ("pi", r3.s);
}
TEST_F(TsQueueTest, Clear)
{
TsQueue<const char*> q;
q.push("a");
q.push("b");
q.clear();
q.push("c");
ASSERT_EQ(0, strcmp("c", q.pop()));
}

View File

@ -0,0 +1,81 @@
#include "gtest/gtest.h"
#include "util/worker_thread.h"
#include <atomic>
#include <future>
class WorkerThreadTest : public ::testing::Test
{
};
TEST_F(WorkerThreadTest, Basic)
{
WorkerThread worker{"Test"};
std::atomic<bool> done = false;
const auto& task = [&]() {
done = true;
};
worker.run(task);
usleep(100'000);
ASSERT_TRUE(done);
// test restart
worker.stop();
done = false;
worker.run(task);
usleep(100'000);
ASSERT_TRUE(done);
}
TEST_F(WorkerThreadTest, MultiThread)
{
WorkerThread worker{"Test"};
std::atomic<int> counter = 0;
const auto& task = [&]() {
++counter;
};
const auto& consumer = [&]() {
for (int i = 0; i < 100; i++)
worker.run(task);
};
std::future<void> futures[4];
for (auto& f : futures)
f = std::async(std::launch::async, consumer);
for (auto& f : futures)
f.get();
worker.stop(); // force all tasks to be executed before stopping
ASSERT_EQ(std::size(futures) * 100, counter);
}
// There's no guarantee that tasks submitted while the worker is being stopped will
// be executed. But it shouldn't crash.
TEST_F(WorkerThreadTest, StartStop)
{
WorkerThread worker{"Test"};
std::atomic<int> counter = 0;
const auto& task = [&]() {
++counter;
};
const auto& consumer = [&]() {
for (int i = 0; i < 100; i++)
worker.run(task);
};
std::future<void> future = std::async(std::launch::async, consumer);
std::future<void> future2 = std::async(std::launch::async, [&]() {
for (int i = 0; i < 100; i++)
worker.stop();
});
future.get();
future2.get();
worker.stop();
//ASSERT_EQ(100, counter);
}
TEST_F(WorkerThreadTest, Future)
{
WorkerThread worker{"Test"};
const auto& task = [](u32 v) -> u32 {
return v;
};
std::future<u32> f = worker.runFuture(task, 42);
ASSERT_EQ(42, f.get());
}

18
tools/dreampi/Makefile Normal file
View File

@ -0,0 +1,18 @@
#
# Utility to connect to DCNet using dreampi.
# To install:
# make install
# Make a copy of /home/pi/dreampi/dreampi.py
# Copy the included dreampi.py into /home/pi/dreampi
#
CFLAGS=-O3 -Wall -Wconversion
dcnet: dcnet.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f dcnet.o dcnet
install: dcnet
cp dcnet /home/pi/dreampi/dcnet.rpi

308
tools/dreampi/dcnet.c Normal file
View File

@ -0,0 +1,308 @@
/*
Copyright 2025 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 <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <termios.h>
#include <netdb.h>
#include <fcntl.h>
#include <stdlib.h>
#define DEFAULT_TTY "/dev/ttyACM0"
#define DEFAULT_HOST "dcnet.flyca.st"
#define DEFAULT_PORT 7654
char ttyName[512] = DEFAULT_TTY;
int useUdp;
char hostName[64] = DEFAULT_HOST;
uint16_t port = DEFAULT_PORT;
struct termios tbufsave;
int setNonBlocking(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1)
flags = 0;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) != 0) {
perror("fcntl(O_NONBLOCK)");
return -1;
}
return 0;
}
int configureTty(int fd, int local)
{
if (tcgetattr(fd, &tbufsave) == -1)
perror("tcgetattr");
struct termios tbuf;
memcpy(&tbuf, &tbufsave, sizeof(tbuf));
/* 8-bit, one stop bit, no parity, carrier detect, no hang up on close,
disable RTS/CTS flow control.
*/
tbuf.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CLOCAL | HUPCL | CRTSCTS);
tbuf.c_cflag |= CS8 | CREAD;
if (local)
// ignore CD
tbuf.c_cflag |= CLOCAL;
/* don't translate NL to CR or CR to NL on input, get all 8 bits of input
disable xon/xoff flow control on output, no interrupt on break signal,
ignore parity, ignore break
*/
tbuf.c_iflag = IGNBRK | IGNPAR;
/* disable all output processing */
tbuf.c_oflag = 0;
/* non-canonical, ignore signals and no echoing on output */
tbuf.c_lflag = 0;
tbuf.c_cc[VMIN] = 1;
tbuf.c_cc[VTIME] = 0;
/* set the parameters associated with the terminal port */
if (tcsetattr(fd, TCSANOW, &tbuf) == -1) {
perror("tcsetattr");
return 1;
}
return 0;
}
int main(int argc, char *argv[])
{
int opt;
while ((opt = getopt(argc, argv, "t:h:p:")) != -1)
{
switch (opt)
{
/*
case 'u':
useUdp = 1;
break;
*/
case 't':
strcpy(ttyName, optarg);
break;
case 'h':
strcpy(hostName, optarg);
break;
case 'p':
port = (uint16_t)atoi(optarg);
break;
default:
fprintf(stderr, "Usage: %s [-t <tty>] [-h <server name] [-p <server port>]\n", argv[0]);
fprintf(stderr, "Default tty is %s. Default host:port is %s:%d\n", DEFAULT_TTY, DEFAULT_HOST, DEFAULT_PORT);
exit(1);
}
}
fprintf(stderr, "DCNet starting\n");
/* Resolve server name */
struct addrinfo hints, *result;
memset(&hints, 0, sizeof (hints));
hints.ai_family = AF_INET;
hints.ai_socktype = useUdp ? SOCK_DGRAM : SOCK_STREAM;
hints.ai_flags |= AI_CANONNAME;
int errcode = getaddrinfo(hostName, NULL, &hints, &result);
if (errcode != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(errcode));
return -1;
}
if (result == NULL) {
fprintf(stderr, "%s: host not found\n", hostName);
return -1;
}
char s[100];
struct sockaddr_in *serverAddress = (struct sockaddr_in *)result->ai_addr;
inet_ntop(result->ai_family, &serverAddress->sin_addr, s, 100);
printf("%s is %s (%s)\n", hostName, s, result->ai_canonname);
/* TTY */
int ttyfd = open(ttyName, O_RDWR);
if (ttyfd == -1) {
perror("Can't open tty");
return 1;
}
if (configureTty(ttyfd, 0))
return 1;
/* SOCKET */
int sockfd = socket(AF_INET, useUdp ? SOCK_DGRAM : SOCK_STREAM, 0);
if (sockfd == -1) {
fprintf(stderr, "socket() failed: %d\n", errno);
return 1;
}
serverAddress->sin_port = htons(port);
if (connect(sockfd, (struct sockaddr *)serverAddress, sizeof(*serverAddress)) != 0) {
fprintf(stderr, "connect() failed: %d\n", errno);
return 1;
}
freeaddrinfo(result);
if (setNonBlocking(sockfd) || setNonBlocking(ttyfd))
return -1;
char outbuf[1504];
ssize_t outbuflen = 0;
char inbuf[1504];
ssize_t inbuflen = 0;
for (;;)
{
#ifdef DEBUG
ssize_t old_olen = outbuflen;
ssize_t old_ilen = inbuflen;
#endif
fd_set readfds;
FD_ZERO(&readfds);
if (inbuflen < sizeof(inbuf))
FD_SET(sockfd, &readfds);
if (outbuflen < sizeof(outbuf))
FD_SET(ttyfd, &readfds);
fd_set writefds;
FD_ZERO(&writefds);
ssize_t outbufReady = 0;
if (outbuflen > 0)
{
outbufReady = outbuflen;
/*
if (outbuf[0] != 0x7e) {
outbufReady = outbuflen;
}
else {
for (int i = 1; i < outbuflen; i++)
{
if (outbuf[i] == 0x7e) {
outbufReady = i + 1;
break;
}
}
}
*/
if (outbufReady != 0)
FD_SET(sockfd, &writefds);
}
if (inbuflen > 0)
FD_SET(ttyfd, &writefds);
int nfds = (sockfd > ttyfd ? sockfd : ttyfd) + 1;
if (select(nfds, &readfds, &writefds, NULL, NULL) == -1)
{
if (errno == EINTR)
continue;
fprintf(stderr, "select() failed: %d\n", errno);
close(sockfd);
return 1;
}
if (FD_ISSET(ttyfd, &readfds))
{
ssize_t ret = read(ttyfd, outbuf + outbuflen, sizeof(outbuf) - (size_t)outbuflen);
if (ret < 0) {
if (errno != EINTR && errno != EWOULDBLOCK) {
fprintf(stderr, "read from tty failed: %d\n", errno);
break;
}
ret = 0;
}
else if (ret == 0) {
fprintf(stderr, "modem hang up\n");
break;
}
if (ret > 0) {
outbuflen += ret;
FD_SET(sockfd, &writefds);
}
}
if (FD_ISSET(sockfd, &readfds))
{
ssize_t ret = read(sockfd, inbuf + inbuflen, sizeof(inbuf) - (size_t)inbuflen);
if (ret < 0) {
if (errno != EINTR && errno != EWOULDBLOCK) {
fprintf(stderr, "read from socket failed: %d\n", errno);
break;
}
ret = 0;
}
else if (ret == 0) {
fprintf(stderr, "socket read EOF\n");
break;
}
if (ret > 0) {
inbuflen += ret;
FD_SET(ttyfd, &writefds);
}
}
if (FD_ISSET(ttyfd, &writefds))
{
ssize_t ret = write(ttyfd, inbuf, (size_t)inbuflen);
if (ret < 0) {
if (errno != EINTR && errno != EWOULDBLOCK) {
fprintf(stderr, "write to tty failed: %d\n", errno);
break;
}
ret = 0;
}
if (ret > 0)
{
inbuflen -= ret;
if (inbuflen > 0)
memmove(inbuf, inbuf + ret, (size_t)inbuflen);
}
}
if (FD_ISSET(sockfd, &writefds))
{
ssize_t ret = write(sockfd, outbuf, (size_t)outbufReady);
if (ret < 0) {
if (errno == EINTR && errno != EWOULDBLOCK) {
fprintf(stderr, "write to socket failed: %d\n", errno);
break;
}
ret = 0;
}
if (ret > 0)
{
outbuflen -= ret;
if (outbuflen > 0)
memmove(outbuf, outbuf + ret, (size_t)outbuflen);
}
}
#ifdef DEBUG
printf("OUT %03zd%c IN %03zd%c\r",
outbuflen, outbuflen > old_olen ? '+' : outbuflen < old_olen ? '-' : ' ',
inbuflen, inbuflen > old_ilen ? '+' : inbuflen < old_ilen ? '-' : ' ');
fflush(stdout);
#endif
}
close(ttyfd);
ttyfd = open(ttyName, O_RDWR);
if (ttyfd == -1) {
perror("Can't reopen tty");
}
else {
tcsetattr(ttyfd, TCSANOW, &tbufsave);
close(ttyfd);
}
close(sockfd);
fprintf(stderr, "DCNet stopped\n");
return 0;
}

BIN
tools/dreampi/dcnet.rpi Executable file

Binary file not shown.

1113
tools/dreampi/dreampi.py Executable file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More