new update?

This commit is contained in:
2026-01-05 23:49:04 -03:00
parent 3290ed80d8
commit cf4f813145
9 changed files with 846 additions and 118 deletions

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
@@ -141,6 +141,7 @@ struct System::Impl {
cpu_manager.SetMulticore(is_multicore);
cpu_manager.SetAsyncGpu(is_async_gpu);
// Start Legacy Online Service (UDP port 6000 + HTTP port 8080 for mobile app)
if (!legacy_online) {
legacy_online = std::make_unique<Network::LegacyOnlineService>();
legacy_online->Start();
@@ -571,7 +572,7 @@ struct System::Impl {
/// Network instance
Network::NetworkInstance network_instance;
/// Legacy Online Service
std::unique_ptr<Network::LegacyOnlineService> legacy_online;

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -134,7 +134,7 @@ VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute&
// Implement SD_Cache.XXXX logic for Cache Storage
if (meta.type == SaveDataType::Cache) {
const std::string sd_cache_name = fmt::format("SD_Cache.{:04}", meta.index);
const std::string sd_cache_name = fmt::format("SD_Cache.{:04X}", meta.index);
auto sd_cache_dir = commit_root->GetSubdirectory(sd_cache_name);
if (sd_cache_dir != nullptr) {
LOG_INFO(Service_FS, "Found SD_Cache directory: '{}'", sd_cache_name);
@@ -178,7 +178,7 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
default:
// ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
LOG_WARNING(Service_FS, "Unrecognized SaveDataSpaceId: {:02X}, defaulting to /user/", static_cast<u8>(space));
return "/user/";
return "/user/";
}
}
@@ -251,7 +251,7 @@ VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribut
if (commit_dir != nullptr) {
// Create SD_Cache.XXXX
const std::string sd_cache_name = fmt::format("SD_Cache.{:04}", meta.index);
const std::string sd_cache_name = fmt::format("SD_Cache.{:04X}", meta.index);
return commit_dir->CreateSubdirectory(sd_cache_name);
}
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -182,11 +182,11 @@ void ISaveDataInfoReader::FindCacheSaves(FileSys::SaveDataSpaceId space,
for (const auto& sd_cache_dir : commit_root->GetSubdirectories()) {
const std::string& name = sd_cache_dir->GetName();
if (name.find("SD_Cache.") == 0) {
// Parse index from "SD_Cache.XXXX"
// Parse index from "SD_Cache.XXXX" (hexadecimal)
u64 index = 0;
try {
if (name.size() > 9) {
index = std::stoull(name.substr(9));
index = std::stoull(name.substr(9), nullptr, 16); // Base 16 for hex
}
} catch(...) {
continue;

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -315,7 +315,15 @@ void BSD::Fcntl(HLERequestContext& ctx) {
const u32 cmd = rp.Pop<u32>();
const s32 arg = rp.Pop<s32>();
LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg);
// Log with more detail to understand non-blocking configuration
if (cmd == 4) { // SETFL
bool is_nonblock = (arg & 0x800) != 0; // O_NONBLOCK
LOG_INFO(Service, "Fcntl SETFL fd={} arg={} (non-blocking={})", fd, arg, is_nonblock);
} else if (cmd == 3) { // GETFL
LOG_INFO(Service, "Fcntl GETFL fd={}", fd);
} else {
LOG_INFO(Service, "Fcntl fd={} cmd={} arg={}", fd, cmd, arg);
}
const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast<FcntlCmd>(cmd), arg);
@@ -802,33 +810,40 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8
switch (optname) {
case OptName::REUSEADDR:
LOG_INFO(Service, "SetSockOpt fd={} REUSEADDR={}", fd, value);
if (value != 0 && value != 1) {
LOG_WARNING(Service, "Invalid REUSEADDR value: {}", value);
return Errno::INVAL;
}
return Translate(socket->SetReuseAddr(value != 0));
case OptName::KEEPALIVE:
LOG_INFO(Service, "SetSockOpt fd={} KEEPALIVE={}", fd, value);
if (value != 0 && value != 1) {
LOG_WARNING(Service, "Invalid KEEPALIVE value: {}", value);
return Errno::INVAL;
}
return Translate(socket->SetKeepAlive(value != 0));
case OptName::BROADCAST:
LOG_INFO(Service, "SetSockOpt fd={} BROADCAST={}", fd, value);
if (value != 0 && value != 1) {
LOG_WARNING(Service, "Invalid BROADCAST value: {}", value);
return Errno::INVAL;
}
return Translate(socket->SetBroadcast(value != 0));
case OptName::SNDBUF:
LOG_INFO(Service, "SetSockOpt fd={} SNDBUF={}", fd, value);
return Translate(socket->SetSndBuf(value));
case OptName::RCVBUF:
LOG_INFO(Service, "SetSockOpt fd={} RCVBUF={}", fd, value);
return Translate(socket->SetRcvBuf(value));
case OptName::SNDTIMEO:
LOG_INFO(Service, "SetSockOpt fd={} SNDTIMEO={}", fd, value);
return Translate(socket->SetSndTimeo(value));
case OptName::RCVTIMEO:
LOG_INFO(Service, "SetSockOpt fd={} RCVTIMEO={}", fd, value);
return Translate(socket->SetRcvTimeo(value));
case OptName::NOSIGPIPE:
LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value);
LOG_INFO(Service, "SetSockOpt fd={} NOSIGPIPE={}", fd, value);
return Errno::SUCCESS;
default:
LOG_WARNING(Service, "(STUBBED) Unimplemented optname={} (0x{:x}), returning INVAL",

View File

@@ -1,13 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/internal_network/legacy_online.h"
#include <cstring>
#include <iostream>
#include <sstream>
#include <regex>
#include <iomanip>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <netinet/in.h>
@@ -15,10 +19,153 @@
#include <unistd.h>
#endif
// For SHA1 and Base64
#include "common/logging/log.h"
namespace Network {
// Simple SHA1 implementation for WebSocket handshake
namespace {
class SHA1 {
public:
SHA1() { reset(); }
void update(const uint8_t* data, size_t len) {
while (len--) {
buffer[buffer_size++] = *data++;
if (buffer_size == 64) {
process_block();
buffer_size = 0;
}
total_bits += 8;
}
}
void update(const std::string& str) {
update(reinterpret_cast<const uint8_t*>(str.data()), str.size());
}
std::array<uint8_t, 20> finalize() {
// Padding
buffer[buffer_size++] = 0x80;
while (buffer_size != 56) {
if (buffer_size == 64) {
process_block();
buffer_size = 0;
}
buffer[buffer_size++] = 0;
}
// Append length in bits
for (int i = 7; i >= 0; --i) {
buffer[buffer_size++] = static_cast<uint8_t>(total_bits >> (i * 8));
}
process_block();
std::array<uint8_t, 20> result;
for (int i = 0; i < 5; ++i) {
result[i*4+0] = static_cast<uint8_t>(h[i] >> 24);
result[i*4+1] = static_cast<uint8_t>(h[i] >> 16);
result[i*4+2] = static_cast<uint8_t>(h[i] >> 8);
result[i*4+3] = static_cast<uint8_t>(h[i]);
}
return result;
}
private:
void reset() {
h[0] = 0x67452301;
h[1] = 0xEFCDAB89;
h[2] = 0x98BADCFE;
h[3] = 0x10325476;
h[4] = 0xC3D2E1F0;
buffer_size = 0;
total_bits = 0;
}
void process_block() {
uint32_t w[80];
for (int i = 0; i < 16; ++i) {
w[i] = (buffer[i*4+0] << 24) | (buffer[i*4+1] << 16) |
(buffer[i*4+2] << 8) | buffer[i*4+3];
}
for (int i = 16; i < 80; ++i) {
uint32_t t = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16];
w[i] = (t << 1) | (t >> 31);
}
uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];
for (int i = 0; i < 80; ++i) {
uint32_t f, k;
if (i < 20) {
f = (b & c) | ((~b) & d);
k = 0x5A827999;
} else if (i < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (i < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
uint32_t temp = ((a << 5) | (a >> 27)) + f + e + k + w[i];
e = d;
d = c;
c = (b << 30) | (b >> 2);
b = a;
a = temp;
}
h[0] += a;
h[1] += b;
h[2] += c;
h[3] += d;
h[4] += e;
}
uint32_t h[5];
uint8_t buffer[64];
size_t buffer_size;
uint64_t total_bits;
};
std::string base64_encode(const uint8_t* data, size_t len) {
static const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string result;
result.reserve((len + 2) / 3 * 4);
for (size_t i = 0; i < len; i += 3) {
uint32_t n = static_cast<uint32_t>(data[i]) << 16;
if (i + 1 < len) n |= static_cast<uint32_t>(data[i + 1]) << 8;
if (i + 2 < len) n |= static_cast<uint32_t>(data[i + 2]);
result += chars[(n >> 18) & 0x3F];
result += chars[(n >> 12) & 0x3F];
result += (i + 1 < len) ? chars[(n >> 6) & 0x3F] : '=';
result += (i + 2 < len) ? chars[n & 0x3F] : '=';
}
return result;
}
std::string compute_websocket_accept(const std::string& key) {
// WebSocket magic GUID
const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
std::string combined = key + magic;
SHA1 sha1;
sha1.update(combined);
auto hash = sha1.finalize();
return base64_encode(hash.data(), hash.size());
}
} // anonymous namespace
LegacyOnlineService::LegacyOnlineService() {
#ifdef _WIN32
WSADATA wsa_data;
@@ -53,7 +200,8 @@ void LegacyOnlineService::Start() {
#endif
is_running = true;
worker_thread = std::thread(&LegacyOnlineService::ServerLoop, this);
udp_worker_thread = std::thread(&LegacyOnlineService::UdpServerLoop, this);
http_worker_thread = std::thread(&LegacyOnlineService::HttpServerLoop, this);
}
void LegacyOnlineService::Stop() {
@@ -63,27 +211,34 @@ void LegacyOnlineService::Stop() {
is_running = false;
// Close socket to wake up the thread if it's blocked on recvfrom
if (socket_fd != ~0ULL) {
if (udp_socket_fd != ~0ULL) {
#ifdef _WIN32
closesocket(static_cast<SOCKET>(socket_fd));
closesocket(static_cast<SOCKET>(udp_socket_fd));
#else
close(static_cast<int>(socket_fd));
close(static_cast<int>(udp_socket_fd));
#endif
socket_fd = ~0ULL;
udp_socket_fd = ~0ULL;
}
if (worker_thread.joinable()) {
worker_thread.join();
if (http_socket_fd != ~0ULL) {
#ifdef _WIN32
closesocket(static_cast<SOCKET>(http_socket_fd));
#else
close(static_cast<int>(http_socket_fd));
#endif
http_socket_fd = ~0ULL;
}
if (udp_worker_thread.joinable()) {
udp_worker_thread.join();
}
if (http_worker_thread.joinable()) {
http_worker_thread.join();
}
}
void LegacyOnlineService::ServerLoop() {
LOG_INFO(Network, "Starting Legacy Online UDP Server on port {}", PORT);
// WSAStartup is now handled in the constructor
void LegacyOnlineService::UdpServerLoop() {
LOG_INFO(Network, "Starting Legacy Online UDP Server on port {}", UDP_PORT);
auto s = socket(AF_INET, SOCK_DGRAM, 0);
#ifdef _WIN32
@@ -91,89 +246,456 @@ void LegacyOnlineService::ServerLoop() {
#else
if (s == -1) {
#endif
LOG_ERROR(Network, "Failed to create socket");
is_running = false;
LOG_ERROR(Network, "Failed to create UDP socket");
return;
}
socket_fd = static_cast<uintptr_t>(s);
udp_socket_fd = static_cast<uintptr_t>(s);
int opt = 1;
#ifdef _WIN32
setsockopt(static_cast<SOCKET>(socket_fd), SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
setsockopt(static_cast<SOCKET>(udp_socket_fd), SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
#else
setsockopt(static_cast<int>(socket_fd), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(static_cast<int>(udp_socket_fd), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
server_addr.sin_port = htons(UDP_PORT);
int res = -1;
#ifdef _WIN32
res = bind(static_cast<SOCKET>(socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
res = bind(static_cast<SOCKET>(udp_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
#else
res = bind(static_cast<int>(socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
res = bind(static_cast<int>(udp_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
#endif
if (res < 0) {
#ifdef _WIN32
LOG_ERROR(Network, "Failed to bind to port {}: {}", PORT, WSAGetLastError());
closesocket(static_cast<SOCKET>(socket_fd));
LOG_ERROR(Network, "Failed to bind UDP to port {}: {}", UDP_PORT, WSAGetLastError());
closesocket(static_cast<SOCKET>(udp_socket_fd));
#else
LOG_ERROR(Network, "Failed to bind to port {}: {}", PORT, strerror(errno));
close(static_cast<int>(socket_fd));
LOG_ERROR(Network, "Failed to bind UDP to port {}: {}", UDP_PORT, strerror(errno));
close(static_cast<int>(udp_socket_fd));
#endif
socket_fd = ~0ULL;
is_running = false;
udp_socket_fd = ~0ULL;
return;
}
LOG_INFO(Network, "Legacy Online Server waiting for messages...");
// Set a timeout for recvfrom so check is_running periodically if not closed via socket
// Alternatively, closing the socket (as done in Stop) will cause recvfrom to return error
LOG_INFO(Network, "Legacy Online UDP Server waiting for messages...");
char buffer[2048];
while (is_running) {
sockaddr_in client_addr{};
#ifdef _WIN32
int client_len = sizeof(client_addr);
int len = recvfrom(static_cast<SOCKET>(udp_socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
#else
socklen_t client_len = sizeof(client_addr);
#endif
#ifdef _WIN32
int len = recvfrom(static_cast<SOCKET>(socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
#else
ssize_t len = recvfrom(static_cast<int>(socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
ssize_t len = recvfrom(static_cast<int>(udp_socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
#endif
if (!is_running) break;
if (len > 0) {
// Send ACK
const char* ack_msg = "ACK";
#ifdef _WIN32
sendto(static_cast<SOCKET>(socket_fd), ack_msg, static_cast<int>(strlen(ack_msg)), 0, (sockaddr*)&client_addr, client_len);
sendto(static_cast<SOCKET>(udp_socket_fd), ack_msg, static_cast<int>(strlen(ack_msg)), 0, (sockaddr*)&client_addr, client_len);
#else
sendto(static_cast<int>(socket_fd), ack_msg, strlen(ack_msg), 0, (sockaddr*)&client_addr, client_len);
sendto(static_cast<int>(udp_socket_fd), ack_msg, strlen(ack_msg), 0, (sockaddr*)&client_addr, client_len);
#endif
} else {
// Error or closed
// If we closed the socket in Stop(), this will likely trigger.
break;
break;
}
}
#ifdef _WIN32
if (socket_fd != ~0ULL) closesocket(static_cast<SOCKET>(socket_fd));
// WSACleanup is now handled in the destructor
if (udp_socket_fd != ~0ULL) closesocket(static_cast<SOCKET>(udp_socket_fd));
#else
if (socket_fd != ~0ULL) close(static_cast<int>(socket_fd));
if (udp_socket_fd != ~0ULL) close(static_cast<int>(udp_socket_fd));
#endif
socket_fd = ~0ULL;
LOG_INFO(Network, "Legacy Online Server stopped");
udp_socket_fd = ~0ULL;
LOG_INFO(Network, "Legacy Online UDP Server stopped");
}
void LegacyOnlineService::HttpServerLoop() {
LOG_INFO(Network, "Starting Mobile App HTTP/WebSocket Server on port {}", HTTP_PORT);
auto s = socket(AF_INET, SOCK_STREAM, 0);
#ifdef _WIN32
if (s == INVALID_SOCKET) {
#else
if (s == -1) {
#endif
LOG_ERROR(Network, "Failed to create HTTP socket");
return;
}
http_socket_fd = static_cast<uintptr_t>(s);
int opt = 1;
#ifdef _WIN32
setsockopt(static_cast<SOCKET>(http_socket_fd), SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
#else
setsockopt(static_cast<int>(http_socket_fd), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(HTTP_PORT);
int res = -1;
#ifdef _WIN32
res = bind(static_cast<SOCKET>(http_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
#else
res = bind(static_cast<int>(http_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
#endif
if (res < 0) {
#ifdef _WIN32
LOG_ERROR(Network, "Failed to bind HTTP to port {}: {}", HTTP_PORT, WSAGetLastError());
closesocket(static_cast<SOCKET>(http_socket_fd));
#else
LOG_ERROR(Network, "Failed to bind HTTP to port {}: {}", HTTP_PORT, strerror(errno));
close(static_cast<int>(http_socket_fd));
#endif
http_socket_fd = ~0ULL;
return;
}
#ifdef _WIN32
res = listen(static_cast<SOCKET>(http_socket_fd), 10);
#else
res = listen(static_cast<int>(http_socket_fd), 10);
#endif
if (res < 0) {
#ifdef _WIN32
LOG_ERROR(Network, "Failed to listen on HTTP port {}: {}", HTTP_PORT, WSAGetLastError());
closesocket(static_cast<SOCKET>(http_socket_fd));
#else
LOG_ERROR(Network, "Failed to listen on HTTP port {}: {}", HTTP_PORT, strerror(errno));
close(static_cast<int>(http_socket_fd));
#endif
http_socket_fd = ~0ULL;
return;
}
LOG_INFO(Network, "Mobile App HTTP/WebSocket Server listening on port {}...", HTTP_PORT);
while (is_running) {
sockaddr_in client_addr{};
#ifdef _WIN32
int client_len = sizeof(client_addr);
SOCKET client_fd = accept(static_cast<SOCKET>(http_socket_fd), (sockaddr*)&client_addr, &client_len);
if (client_fd == INVALID_SOCKET) {
#else
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(static_cast<int>(http_socket_fd), (sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
#endif
if (!is_running) break;
continue;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
LOG_INFO(Network, "HTTP/WebSocket connection from {}:{}", client_ip, ntohs(client_addr.sin_port));
// Read HTTP request
char buffer[4096];
memset(buffer, 0, sizeof(buffer));
#ifdef _WIN32
int bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
#else
ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
#endif
if (bytes_read > 0) {
std::string request(buffer, bytes_read);
LOG_INFO(Network, "Request:\n{}", request);
// Check if this is a WebSocket upgrade request
bool is_websocket = request.find("Upgrade: websocket") != std::string::npos;
if (is_websocket) {
// Extract Sec-WebSocket-Key
std::string ws_key;
std::regex key_regex("Sec-WebSocket-Key: ([^\r\n]+)");
std::smatch match;
if (std::regex_search(request, match, key_regex)) {
ws_key = match[1].str();
}
// Extract Sec-WebSocket-Protocol
std::string ws_protocol;
std::regex protocol_regex("Sec-WebSocket-Protocol: ([^\r\n]+)");
if (std::regex_search(request, match, protocol_regex)) {
ws_protocol = match[1].str();
}
LOG_INFO(Network, "WebSocket upgrade request - Key: {}, Protocol: {}", ws_key, ws_protocol);
// Compute accept key
std::string accept_key = compute_websocket_accept(ws_key);
LOG_INFO(Network, "WebSocket Accept Key: {}", accept_key);
// Build WebSocket handshake response
std::ostringstream ws_response;
ws_response << "HTTP/1.1 101 Switching Protocols\r\n";
ws_response << "Upgrade: websocket\r\n";
ws_response << "Connection: Upgrade\r\n";
ws_response << "Sec-WebSocket-Accept: " << accept_key << "\r\n";
if (!ws_protocol.empty()) {
ws_response << "Sec-WebSocket-Protocol: " << ws_protocol << "\r\n";
}
ws_response << "\r\n";
std::string response_str = ws_response.str();
#ifdef _WIN32
send(client_fd, response_str.c_str(), static_cast<int>(response_str.size()), 0);
#else
send(client_fd, response_str.c_str(), response_str.size(), 0);
#endif
LOG_INFO(Network, "WebSocket handshake completed! Response:\n{}", response_str);
// Now handle WebSocket messages
LOG_INFO(Network, "Mobile app WebSocket connected! Entering message loop...");
while (is_running) {
memset(buffer, 0, sizeof(buffer));
#ifdef _WIN32
bytes_read = recv(client_fd, buffer, sizeof(buffer), 0);
#else
bytes_read = recv(client_fd, buffer, sizeof(buffer), 0);
#endif
if (bytes_read <= 0) {
LOG_INFO(Network, "WebSocket connection closed");
break;
}
// Parse WebSocket frame
uint8_t* frame = reinterpret_cast<uint8_t*>(buffer);
uint8_t opcode = frame[0] & 0x0F;
bool masked = (frame[1] & 0x80) != 0;
uint64_t payload_len = frame[1] & 0x7F;
size_t header_len = 2;
if (payload_len == 126) {
payload_len = (frame[2] << 8) | frame[3];
header_len = 4;
} else if (payload_len == 127) {
header_len = 10;
payload_len = 0;
for (int i = 0; i < 8; ++i) {
payload_len = (payload_len << 8) | frame[2 + i];
}
}
uint8_t mask_key[4] = {0};
if (masked) {
memcpy(mask_key, frame + header_len, 4);
header_len += 4;
}
// Unmask payload
std::string payload;
for (size_t i = 0; i < payload_len && (header_len + i) < static_cast<size_t>(bytes_read); ++i) {
char c = frame[header_len + i];
if (masked) {
c ^= mask_key[i % 4];
}
payload += c;
}
// LOG_INFO(Network, "WebSocket message (opcode={}): {}", opcode, payload);
// Handle different opcodes
if (opcode == 0x08) {
// Close frame
// LOG_INFO(Network, "WebSocket close frame received");
break;
} else if (opcode == 0x09) {
// Ping - send pong. Theoretically should echo payload, but empty pong is usually fine.
// LOG_INFO(Network, "WebSocket PING received. Responding with PONG.");
uint8_t pong[2] = {0x8A, 0x00};
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(pong), 2, 0);
#else
send(client_fd, pong, 2, 0);
#endif
} else if (opcode == 0x0A) {
// Pong (keep-alive response from client)
// LOG_INFO(Network, "WebSocket PONG received from client.");
} else if (opcode == 0x01 || opcode == 0x02) {
// Text or Binary frame - process Just Dance protocol
std::string response;
// Check message type and respond appropriately
// Protocol flow based on JoyDance:
// 1. Phone -> Console: JD_PhoneDataCmdHandshakeHello
// 2. Console -> Phone: JD_PhoneDataCmdHandshakeContinue (with phoneID)
// 3. Phone -> Console: JD_PhoneDataCmdSync (with phoneID)
// 4. Console -> Phone: JD_PhoneDataCmdSyncEnd (with phoneID)
// 5. Connected!
if (payload.find("JD_PhoneDataCmdHandshakeHello") != std::string::npos) {
// Step 2: Respond with HandshakeContinue
// PROTOCOL FIX: No "root" wrapper. Authenticated ID=1 (Int).
// CRITICAL: App sent Freq=0. Providing configuration values.
// CLEANUP: Removed extra status fields to prevent parsing errors.
response = R"({"__class":"JD_PhoneDataCmdHandshakeContinue","phoneID":1,"accelAcquisitionFreqHz":50,"accelAcquisitionLatency":40,"accelMaxRange":8})";
// LOG_INFO(Network, "Sending HandshakeContinue (id=1, cfg=50Hz)");
// Send HandshakeContinue
std::vector<uint8_t> ws_frame;
ws_frame.push_back(0x81);
ws_frame.push_back(static_cast<uint8_t>(response.size()));
for (char c : response) {
ws_frame.push_back(static_cast<uint8_t>(c));
}
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
#else
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
#endif
// LOG_INFO(Network, "WebSocket response sent: {}", response);
// Removed proactive commands to verify if app accepts HandshakeContinue and sends Sync
response.clear();
} else if (payload.find("JD_PhoneDataCmdSync") != std::string::npos) {
// Step 4: Respond with SyncEnd (NO ROOT)
// Using phoneID 1 (Int)
response = R"({"__class":"JD_PhoneDataCmdSyncEnd","phoneID":1,"status":"ok"})";
// LOG_INFO(Network, "Sending SyncEnd (id=1, no root) - Connection complete!");
// Send SyncEnd
std::vector<uint8_t> ws_frame;
ws_frame.push_back(0x81);
ws_frame.push_back(static_cast<uint8_t>(response.size()));
for (char c : response) {
ws_frame.push_back(static_cast<uint8_t>(c));
}
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
#else
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
#endif
// Step 5: Send Activation Commands immediately to authorize input and accel
// 5a. Enable Input
std::string cmd1 = R"({"__class":"InputSetup_ConsoleCommandData","isEnabled":1,"inputSetup":{"isEnabled":1}})";
std::vector<uint8_t> f1; f1.push_back(0x81); f1.push_back(cmd1.size()); for(char c:cmd1) f1.push_back(c);
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(f1.data()), f1.size(), 0);
#else
send(client_fd, f1.data(), f1.size(), 0);
#endif
// 5b. Enable Accelerometer
std::string cmd2 = R"({"__class":"JD_EnableAccelValuesSending_ConsoleCommandData","isEnabled":1})";
std::vector<uint8_t> f2; f2.push_back(0x81); f2.push_back(cmd2.size()); for(char c:cmd2) f2.push_back(c);
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(f2.data()), f2.size(), 0);
#else
send(client_fd, f2.data(), f2.size(), 0);
#endif
// 5c. UI Setup (optional but good for safety)
std::string cmd3 = R"({"__class":"JD_PhoneUiSetupData","isPopup":0,"inputSetup":{"isEnabled":1}})";
std::vector<uint8_t> f3; f3.push_back(0x81); f3.push_back(cmd3.size()); for(char c:cmd3) f3.push_back(c);
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(f3.data()), f3.size(), 0);
#else
send(client_fd, f3.data(), f3.size(), 0);
#endif
// LOG_INFO(Network, "Sent Activation Commands (Input, Accel, UI)");
response.clear();
} else if (payload.find("JD_PhoneScoringData") != std::string::npos) {
// Accelerometer/scoring data - no response needed
// LOG_DEBUG(Network, "Received phone scoring data");
continue;
} else if (payload.find("JD_Input_PhoneCommandData") != std::string::npos) {
// Button input from phone - no response needed
// LOG_INFO(Network, "Received phone input command");
continue;
} else if (payload.find("JD_Pause_PhoneCommandData") != std::string::npos) {
// Pause command from phone
// LOG_INFO(Network, "Received pause command from phone");
continue;
} else if (payload.find("JD_Custom_PhoneCommandData") != std::string::npos) {
// Custom shortcut command
// LOG_INFO(Network, "Received custom command from phone");
continue;
} else if (payload.find("JD_CancelKeyboard_PhoneCommandData") != std::string::npos) {
// Keyboard cancelled
// LOG_INFO(Network, "Phone cancelled keyboard");
continue;
} else {
// Unknown message - log but don't respond
// LOG_INFO(Network, "Unknown phone message type");
continue;
}
if (!response.empty()) {
// Build WebSocket frame
std::vector<uint8_t> ws_frame;
ws_frame.push_back(0x81); // Text frame, FIN
if (response.size() < 126) {
ws_frame.push_back(static_cast<uint8_t>(response.size()));
} else if (response.size() < 65536) {
ws_frame.push_back(126);
ws_frame.push_back(static_cast<uint8_t>((response.size() >> 8) & 0xFF));
ws_frame.push_back(static_cast<uint8_t>(response.size() & 0xFF));
}
for (char c : response) {
ws_frame.push_back(static_cast<uint8_t>(c));
}
#ifdef _WIN32
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
#else
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
#endif
LOG_INFO(Network, "WebSocket response sent: {}", response);
}
}
}
} else {
// Regular HTTP request
std::string response_body = R"({"status":"ok","message":"Eden Mobile Bridge"})";
std::ostringstream http_response;
http_response << "HTTP/1.1 200 OK\r\n";
http_response << "Content-Type: application/json\r\n";
http_response << "Content-Length: " << response_body.size() << "\r\n";
http_response << "Connection: close\r\n";
http_response << "\r\n";
http_response << response_body;
std::string response_str = http_response.str();
#ifdef _WIN32
send(client_fd, response_str.c_str(), static_cast<int>(response_str.size()), 0);
#else
send(client_fd, response_str.c_str(), response_str.size(), 0);
#endif
}
}
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
}
#ifdef _WIN32
if (http_socket_fd != ~0ULL) closesocket(static_cast<SOCKET>(http_socket_fd));
#else
if (http_socket_fd != ~0ULL) close(static_cast<int>(http_socket_fd));
#endif
http_socket_fd = ~0ULL;
LOG_INFO(Network, "Mobile App HTTP/WebSocket Server stopped");
}
} // namespace Network

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -19,13 +19,18 @@ public:
void Stop();
private:
void ServerLoop();
void UdpServerLoop();
void HttpServerLoop();
std::atomic_bool is_running{false};
std::thread worker_thread;
uintptr_t socket_fd{~0ULL}; // ~0ULL is approx -1 equivalent for unsigned
std::thread udp_worker_thread;
std::thread http_worker_thread;
uintptr_t udp_socket_fd{~0ULL};
uintptr_t http_socket_fd{~0ULL};
bool winsock_initialized{false};
static constexpr int PORT = 6000;
static constexpr int UDP_PORT = 6000;
static constexpr int HTTP_PORT = 8080;
};
} // namespace Network

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -704,6 +704,10 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
fd = socket(TranslateDomainToNative(domain), TranslateTypeToNative(type),
TranslateProtocolToNative(protocol));
if (fd != INVALID_SOCKET) {
// Enable SO_REUSEADDR to allow port reuse after socket close
// This prevents "Address already in use" errors when rebinding
int reuse = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&reuse), sizeof(reuse));
return Errno::SUCCESS;
}
@@ -821,6 +825,35 @@ std::pair<s32, Errno> Socket::Recv(int flags, std::span<u8> message) {
ASSERT(flags == 0);
ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)()));
// If socket is blocking, use poll with interrupt socket to avoid infinite blocking
if (!is_non_blocking) {
std::vector<WSAPOLLFD> host_pollfds{
WSAPOLLFD{fd, POLLIN, 0},
WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
};
// Poll with a longer timeout (30 seconds) to wait for data
const int pollres = WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), 30000);
if (host_pollfds[1].revents != 0) {
// Interrupt signaled, return EAGAIN
return {-1, Errno::AGAIN};
}
if (pollres == 0) {
// Timeout - return AGAIN so the game can retry
LOG_DEBUG(Network, "Recv poll timeout after 30 seconds, returning EAGAIN");
return {-1, Errno::AGAIN};
}
if (pollres < 0) {
return {-1, GetAndLogLastError()};
}
// Data is available, proceed with recv
LOG_INFO(Network, "Recv poll detected data available!");
}
const auto result =
recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0);
if (result != SOCKET_ERROR) {
@@ -834,6 +867,35 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::span<u8> message, SockAdd
ASSERT(flags == 0);
ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)()));
// If socket is blocking, use poll with interrupt socket to avoid infinite blocking
if (!is_non_blocking) {
std::vector<WSAPOLLFD> host_pollfds{
WSAPOLLFD{fd, POLLIN, 0},
WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
};
// Poll with a short timeout (5ms) to emulate Switch non-blocking/interruptible behavior
// This prevents the game from freezing when waiting for UDP packets on the main thread
const int pollres = WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), 5);
if (host_pollfds[1].revents != 0) {
// Interrupt signaled, return EAGAIN
return {-1, Errno::AGAIN};
}
if (pollres == 0) {
// Timeout - return AGAIN so the game can continue its loop (rendering, input, etc.)
return {-1, Errno::AGAIN};
}
if (pollres < 0) {
return {-1, GetAndLogLastError()};
}
// Data is available, proceed with recvfrom
LOG_INFO(Network, "RecvFrom poll detected data available!");
}
sockaddr_in addr_in{};
socklen_t addrlen = sizeof(addr_in);
socklen_t* const p_addrlen = addr ? &addrlen : nullptr;
@@ -844,6 +906,10 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::span<u8> message, SockAdd
if (result != SOCKET_ERROR) {
if (addr) {
*addr = TranslateToSockAddrIn(addr_in, addrlen);
LOG_INFO(Network, "RecvFrom received {} bytes from {}:{}",
result, IPv4AddressToString(addr->ip), addr->portno);
} else {
LOG_INFO(Network, "RecvFrom received {} bytes", result);
}
return {static_cast<s32>(result), Errno::SUCCESS};
}
@@ -879,14 +945,55 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
if (addr) {
host_addr_in = TranslateFromSockAddrIn(*addr);
to = &host_addr_in;
LOG_INFO(Network, "SendTo sending {} bytes to {}:{}",
message.size(), IPv4AddressToString(addr->ip), addr->portno);
} else {
LOG_INFO(Network, "SendTo sending {} bytes (no addr)", message.size());
}
// Log packet content for debugging mobile app connection
if (message.size() > 0 && message.size() <= 200) {
std::string hex_dump;
std::string ascii_dump;
for (size_t i = 0; i < message.size(); ++i) {
char hex[4];
snprintf(hex, sizeof(hex), "%02X ", message[i]);
hex_dump += hex;
// Build ASCII representation
if (message[i] >= 32 && message[i] < 127) {
ascii_dump += static_cast<char>(message[i]);
} else {
ascii_dump += '.';
}
}
LOG_INFO(Network, "SendTo packet HEX: {}", hex_dump);
LOG_INFO(Network, "SendTo packet ASCII: {}", ascii_dump);
} else if (message.size() > 200) {
// Log first 200 bytes for large packets
std::string hex_dump;
std::string ascii_dump;
for (size_t i = 0; i < 200; ++i) {
char hex[4];
snprintf(hex, sizeof(hex), "%02X ", message[i]);
hex_dump += hex;
if (message[i] >= 32 && message[i] < 127) {
ascii_dump += static_cast<char>(message[i]);
} else {
ascii_dump += '.';
}
}
LOG_INFO(Network, "SendTo packet HEX (first 200): {}", hex_dump);
LOG_INFO(Network, "SendTo packet ASCII (first 200): {}", ascii_dump);
}
const auto result = sendto(fd, reinterpret_cast<const char*>(message.data()),
static_cast<int>(message.size()), 0, to, to_len);
if (result != SOCKET_ERROR) {
LOG_INFO(Network, "SendTo success: sent {} bytes", result);
return {static_cast<s32>(result), Errno::SUCCESS};
}
LOG_ERROR(Network, "SendTo failed!");
return {-1, GetAndLogLastError(CallType::Send)};
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -211,8 +211,16 @@ bool HardwareContext::InitializeWithType(AVHWDeviceType type) {
DecoderContext::DecoderContext(const Decoder& decoder) : m_decoder{decoder} {
m_codec_context = avcodec_alloc_context3(m_decoder.GetCodec());
av_opt_set(m_codec_context->priv_data, "tune", "zerolatency", 0);
m_codec_context->thread_count = 0;
m_codec_context->thread_type &= ~FF_THREAD_FRAME;
// Optimization: Use all available CPU cores for high bitrate decoding
m_codec_context->thread_count = std::thread::hardware_concurrency();
m_codec_context->thread_type = FF_THREAD_SLICE;
// EXTREME OPTIMIZATION: Aggressive fast decode for very high bitrates
// Trade-off: Small quality loss for significant speed gain (~40-50% faster)
m_codec_context->skip_loop_filter = AVDISCARD_ALL; // Skip deblock filter (20-30% faster)
m_codec_context->flags2 |= AV_CODEC_FLAG2_FAST; // Enable fast decoding mode (10-15% faster)
m_codec_context->flags |= AV_CODEC_FLAG_LOW_DELAY; // Minimize internal delays
}
DecoderContext::~DecoderContext() {
@@ -262,18 +270,19 @@ std::shared_ptr<Frame> DecoderContext::ReceiveFrame() {
return {};
}
m_final_frame = std::make_shared<Frame>();
// Optimization: Only create final_frame and do GPU transfer when actually using GPU
if (m_codec_context->hw_device_ctx) {
m_final_frame = std::make_shared<Frame>();
m_final_frame->SetFormat(PreferredGpuFormat);
if (const int ret = av_hwframe_transfer_data(m_final_frame->GetFrame(), intermediate_frame->GetFrame(), 0); ret < 0) {
LOG_ERROR(HW_GPU, "av_hwframe_transfer_data error: {}", AVError(ret));
return {};
}
} else {
m_final_frame = std::move(intermediate_frame);
return m_final_frame;
}
return std::move(m_final_frame);
// CPU decoding: just return the intermediate frame directly (no copy needed)
return intermediate_frame;
}
void DecodeApi::Reset() {

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -7,6 +7,8 @@
#include <array>
#include <tuple>
#include <stdint.h>
#include <thread>
#include <vector>
extern "C" {
#if defined(__GNUC__) || defined(__clang__)
@@ -105,18 +107,29 @@ void Vic::Execute() {
auto output_width{config.output_surface_config.out_surface_width + 1};
auto output_height{config.output_surface_config.out_surface_height + 1};
output_surface.resize_destructive(output_width * output_height);
// Initialize the surface with the appropriate black pixel
Pixel black_pixel{};
if (config.output_surface_config.out_pixel_format == VideoPixelFormat::Y8__V8U8_N420) {
// Y=0, U=512, V=512 (10-bit), A=0
black_pixel = {0, 512, 512, 0};
} else {
// R=0, G=0, B=0, A=0
black_pixel = {0, 0, 0, 0};
// Only resize when dimensions change (huge performance boost for 1080p)
const auto required_size = output_width * output_height;
const bool size_changed = output_surface.size() != required_size;
if (size_changed) {
// Optimization: Only clear on first allocation, not on every resize
// This avoids expensive std::fill on large buffers (1080p = ~8MB)
const bool first_allocation = output_surface.size() == 0;
output_surface.resize_destructive(required_size);
if (first_allocation) {
// Initialize the surface with the appropriate black pixel
Pixel black_pixel{};
if (config.output_surface_config.out_pixel_format == VideoPixelFormat::Y8__V8U8_N420) {
// Y=0, U=512, V=512 (10-bit), A=0
black_pixel = {0, 512, 512, 0};
} else {
// R=0, G=0, B=0, A=0
black_pixel = {0, 0, 0, 0};
}
std::fill(output_surface.begin(), output_surface.end(), black_pixel);
}
}
std::fill(output_surface.begin(), output_surface.end(), black_pixel);
if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Off) [[unlikely]] {
@@ -190,8 +203,16 @@ void Vic::ReadProgressiveY8__V8U8_N420(const SlotStruct& slot, std::span<const P
out_luma_height *= 2;
}
slot_surface.resize_destructive(out_luma_width * out_luma_height);
std::fill(slot_surface.begin(), slot_surface.end(), Pixel{0, 512, 512, 0});
// Only resize when dimensions change (avoids expensive reallocation)
const auto required_size = out_luma_width * out_luma_height;
if (slot_surface.size() != required_size) {
// Optimization: Only clear on first allocation to avoid expensive std::fill
const bool first_allocation = slot_surface.size() == 0;
slot_surface.resize_destructive(required_size);
if (first_allocation) {
std::fill(slot_surface.begin(), slot_surface.end(), Pixel{0, 512, 512, 0});
}
}
const auto in_luma_width{(std::min)(frame->GetWidth(), s32(out_luma_width))};
const auto in_luma_height{(std::min)(frame->GetHeight(), s32(out_luma_height))};
@@ -212,21 +233,32 @@ void Vic::ReadProgressiveY8__V8U8_N420(const SlotStruct& slot, std::span<const P
out_luma_height, out_luma_stride);
const auto alpha{u16(slot.config.planar_alpha.Value())};
// Optimization: Separate luma and chroma processing for better cache locality
// Process entire scanlines at once for vectorization
for (s32 y = 0; y < in_luma_height; y++) {
const auto src_luma{y * in_luma_stride};
const auto src_chroma{(y / 2) * in_chroma_stride};
const auto dst{y * out_luma_stride};
// Vectorized luma processing (compiler can auto-vectorize this)
for (s32 x = 0; x < in_luma_width; x++) {
slot_surface[dst + x].r = u16(luma_buffer[src_luma + x] << 2);
// Chroma samples are duplicated horizontally and vertically.
if(planar) {
auto& pixel = slot_surface[dst + x];
pixel.r = u16(luma_buffer[src_luma + x] << 2);
pixel.a = alpha;
}
// Vectorized chroma processing (separate loop for better cache)
if(planar) {
for (s32 x = 0; x < in_luma_width; x++) {
slot_surface[dst + x].g = u16(chroma_u_buffer[src_chroma + x / 2] << 2);
slot_surface[dst + x].b = u16(chroma_v_buffer[src_chroma + x / 2] << 2);
} else {
}
} else {
for (s32 x = 0; x < in_luma_width; x++) {
slot_surface[dst + x].g = u16(chroma_u_buffer[src_chroma + (x & ~1) + 0] << 2);
slot_surface[dst + x].b = u16(chroma_u_buffer[src_chroma + (x & ~1) + 1] << 2);
}
slot_surface[dst + x].a = alpha;
}
}
}
@@ -240,8 +272,16 @@ void Vic::ReadInterlacedY8__V8U8_N420(const SlotStruct& slot, std::span<const Pl
const auto out_luma_height{(slot.surface_config.slot_surface_height + 1) * 2};
const auto out_luma_stride{out_luma_width};
slot_surface.resize_destructive(out_luma_width * out_luma_height);
std::fill(slot_surface.begin(), slot_surface.end(), Pixel{0, 512, 512, 0});
// Only resize when dimensions change (avoids expensive reallocation)
const auto required_size = out_luma_width * out_luma_height;
if (slot_surface.size() != required_size) {
// Optimization: Only clear on first allocation to avoid expensive std::fill
const bool first_allocation = slot_surface.size() == 0;
slot_surface.resize_destructive(required_size);
if (first_allocation) {
std::fill(slot_surface.begin(), slot_surface.end(), Pixel{0, 512, 512, 0});
}
}
const auto in_luma_width{(std::min)(frame->GetWidth(), s32(out_luma_width))};
[[maybe_unused]] const auto in_luma_height{
@@ -266,21 +306,33 @@ void Vic::ReadInterlacedY8__V8U8_N420(const SlotStruct& slot, std::span<const Pl
auto DecodeBobField = [&]() {
const auto alpha{u16(slot.config.planar_alpha.Value())};
// Optimization: Vectorized interlaced processing like progressive
for (s32 y = s32(top_field == false); y < in_chroma_height * 2; y += 2) {
const auto src_luma{y * in_luma_stride};
const auto src_chroma{(y / 2) * in_chroma_stride};
const auto dst{y * out_luma_stride};
// Vectorized luma + alpha
for (s32 x = 0; x < in_luma_width; x++) {
slot_surface[dst + x].r = u16(luma_buffer[src_luma + x] << 2);
if(planar) {
auto& pixel = slot_surface[dst + x];
pixel.r = u16(luma_buffer[src_luma + x] << 2);
pixel.a = alpha;
}
// Vectorized chroma
if(planar) {
for (s32 x = 0; x < in_luma_width; x++) {
slot_surface[dst + x].g = u16(chroma_u_buffer[src_chroma + x / 2] << 2);
slot_surface[dst + x].b = u16(chroma_v_buffer[src_chroma + x / 2] << 2);
} else {
}
} else {
for (s32 x = 0; x < in_luma_width; x++) {
slot_surface[dst + x].g = u16(chroma_u_buffer[src_chroma + (x & ~1) + 0] << 2);
slot_surface[dst + x].b = u16(chroma_u_buffer[src_chroma + (x & ~1) + 1] << 2);
}
slot_surface[dst + x].a = alpha;
}
s32 other_line = (top_field ? y + 1 : y - 1) * out_luma_stride;
std::memcpy(&slot_surface[other_line], &slot_surface[dst], out_luma_width * sizeof(Pixel));
}
@@ -460,20 +512,26 @@ void Vic::WriteY8__V8U8_N420(const OutputSurfaceConfig& output_surface_config) {
surface_height = (std::min)(surface_height, out_luma_height);
auto Decode = [&](std::span<u8> out_luma, std::span<u8> out_chroma) {
// Optimization: Process entire scanlines at once for better vectorization
// Separate luma and chroma processing for better cache locality
for (u32 y = 0; y < surface_height; ++y) {
const auto src_luma = y * surface_stride;
const auto dst_luma = y * out_luma_stride;
// Vectorized luma extraction (compiler can auto-vectorize with SSE/AVX)
for (u32 x = 0; x < surface_width; x++) {
out_luma[dst_luma + x] = u8(output_surface[src_luma + x].r >> 2);
}
}
// Vectorized chroma extraction (process every other line for 4:2:0 subsampling)
for (u32 y = 0; y < surface_height; y += 2) {
const auto src_chroma = y * surface_stride;
const auto dst_chroma = (y / 2) * out_chroma_stride;
for (u32 x = 0; x < surface_width; x += 2) {
out_luma[dst_luma + x + 0] =
u8(output_surface[src_luma + x + 0].r >> 2);
out_luma[dst_luma + x + 1] =
u8(output_surface[src_luma + x + 1].r >> 2);
out_chroma[dst_chroma + x + 0] =
u8(output_surface[src_chroma + x].g >> 2);
out_chroma[dst_chroma + x + 1] =
u8(output_surface[src_chroma + x].b >> 2);
out_chroma[dst_chroma + x + 0] = u8(output_surface[src_chroma + x].g >> 2);
out_chroma[dst_chroma + x + 1] = u8(output_surface[src_chroma + x].b >> 2);
}
}
};
@@ -568,20 +626,31 @@ void Vic::WriteABGR(const OutputSurfaceConfig& output_surface_config, VideoPixel
surface_height = (std::min)(surface_height, out_luma_height);
auto Decode = [&](std::span<u8> out_buffer) {
// Optimization: Better memory access pattern for vectorization
// Process entire scanlines with reduced array indirection
for (u32 y = 0; y < surface_height; y++) {
const auto src = y * surface_stride;
const auto dst = y * out_luma_stride;
for (u32 x = 0; x < surface_width; x++) {
if(format == VideoPixelFormat::A8R8G8B8) {
out_buffer[dst + x * 4 + 0] = u8(output_surface[src + x].b >> 2);
out_buffer[dst + x * 4 + 1] = u8(output_surface[src + x].g >> 2);
out_buffer[dst + x * 4 + 2] = u8(output_surface[src + x].r >> 2);
out_buffer[dst + x * 4 + 3] = u8(output_surface[src + x].a >> 2);
} else {
out_buffer[dst + x * 4 + 0] = u8(output_surface[src + x].r >> 2);
out_buffer[dst + x * 4 + 1] = u8(output_surface[src + x].g >> 2);
out_buffer[dst + x * 4 + 2] = u8(output_surface[src + x].b >> 2);
out_buffer[dst + x * 4 + 3] = u8(output_surface[src + x].a >> 2);
if(format == VideoPixelFormat::A8R8G8B8) {
// Vectorized ARGB processing
for (u32 x = 0; x < surface_width; x++) {
const auto& pixel = output_surface[src + x];
auto* out_pixel = &out_buffer[dst + x * 4];
out_pixel[0] = u8(pixel.b >> 2);
out_pixel[1] = u8(pixel.g >> 2);
out_pixel[2] = u8(pixel.r >> 2);
out_pixel[3] = u8(pixel.a >> 2);
}
} else {
// Vectorized ABGR processing
for (u32 x = 0; x < surface_width; x++) {
const auto& pixel = output_surface[src + x];
auto* out_pixel = &out_buffer[dst + x * 4];
out_pixel[0] = u8(pixel.r >> 2);
out_pixel[1] = u8(pixel.g >> 2);
out_pixel[2] = u8(pixel.b >> 2);
out_pixel[3] = u8(pixel.a >> 2);
}
}
}