Merge pull request #13066 from DacoTaco/feature/networking
IOS/Net: implement ioctlv that retrieves the network routing table that libogc now uses on network init
This commit is contained in:
commit
24e9fc120c
|
@ -57,6 +57,11 @@
|
||||||
#include "jni/AndroidCommon/AndroidCommon.h"
|
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <linux/netlink.h>
|
||||||
|
#include <linux/rtnetlink.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace IOS::HLE
|
namespace IOS::HLE
|
||||||
{
|
{
|
||||||
enum SOResultCode : s32
|
enum SOResultCode : s32
|
||||||
|
@ -162,25 +167,189 @@ static s32 MapWiiSockOptNameToNative(u32 optname)
|
||||||
return optname;
|
return optname;
|
||||||
}
|
}
|
||||||
|
|
||||||
// u32 values are in little endian (i.e. 0x0100007f means 127.0.0.1)
|
struct InterfaceRouting
|
||||||
struct DefaultInterface
|
|
||||||
{
|
{
|
||||||
u32 inet; // IPv4 address
|
u32 index;
|
||||||
u32 netmask; // IPv4 subnet mask
|
in_addr destination;
|
||||||
u32 broadcast; // IPv4 broadcast address
|
in_addr netmask;
|
||||||
|
in_addr gateway;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::optional<DefaultInterface> GetSystemDefaultInterface()
|
struct DefaultInterface
|
||||||
{
|
{
|
||||||
|
in_addr inet; // IPv4 address
|
||||||
|
in_addr netmask; // IPv4 subnet mask
|
||||||
|
in_addr broadcast; // IPv4 broadcast address
|
||||||
|
std::vector<InterfaceRouting> routing_table; // IPv4 routing table
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<InterfaceRouting> GetSystemInterfaceRouting()
|
||||||
|
{
|
||||||
|
std::vector<InterfaceRouting> routing_table;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
std::unique_ptr<MIB_IPFORWARDTABLE> forward_table;
|
|
||||||
DWORD forward_table_size = 0;
|
DWORD forward_table_size = 0;
|
||||||
|
std::unique_ptr<MIB_IPFORWARDTABLE> forward_table;
|
||||||
if (GetIpForwardTable(nullptr, &forward_table_size, FALSE) == ERROR_INSUFFICIENT_BUFFER)
|
if (GetIpForwardTable(nullptr, &forward_table_size, FALSE) == ERROR_INSUFFICIENT_BUFFER)
|
||||||
{
|
{
|
||||||
forward_table =
|
forward_table =
|
||||||
std::unique_ptr<MIB_IPFORWARDTABLE>((PMIB_IPFORWARDTABLE) operator new(forward_table_size));
|
std::unique_ptr<MIB_IPFORWARDTABLE>((PMIB_IPFORWARDTABLE) operator new(forward_table_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DWORD result = GetIpForwardTable(forward_table.get(), &forward_table_size, FALSE);
|
||||||
|
// can return ERROR_MORE_DATA on XP even after the first call
|
||||||
|
while (result == NO_ERROR || result == ERROR_MORE_DATA)
|
||||||
|
{
|
||||||
|
const std::span<MIB_IPFORWARDROW> table(forward_table->table, forward_table->dwNumEntries);
|
||||||
|
for (const auto& entry : table)
|
||||||
|
{
|
||||||
|
routing_table.emplace_back(entry.dwForwardIfIndex,
|
||||||
|
std::bit_cast<in_addr>(entry.dwForwardDest),
|
||||||
|
std::bit_cast<in_addr>(entry.dwForwardMask),
|
||||||
|
std::bit_cast<in_addr>(entry.dwForwardNextHop));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == NO_ERROR)
|
||||||
|
break;
|
||||||
|
|
||||||
|
result = GetIpForwardTable(forward_table.get(), &forward_table_size, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(__linux__)
|
||||||
|
constexpr int BUFF_SIZE = 8192;
|
||||||
|
constexpr timeval socket_timeout{.tv_sec = 2, .tv_usec = 0};
|
||||||
|
unsigned int msg_seq = 0;
|
||||||
|
const int sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
||||||
|
if (sock < 0)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_NET, "Failed to open netlink socket, error {}", Common::StrNetworkError());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, static_cast<const void*>(&socket_timeout),
|
||||||
|
sizeof(socket_timeout)) < 0)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_NET, "Failed to set netlink socket recv timeout: {}",
|
||||||
|
Common::StrNetworkError());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ScopeGuard socket_guard{[sock] { close(sock); }};
|
||||||
|
std::array<char, BUFF_SIZE> msg_buffer{};
|
||||||
|
auto nl_msg = reinterpret_cast<nlmsghdr*>(msg_buffer.data());
|
||||||
|
auto rt_msg = reinterpret_cast<rtmsg*>(NLMSG_DATA(nl_msg));
|
||||||
|
const unsigned int pid = getpid();
|
||||||
|
|
||||||
|
// prepare command/netlink packet
|
||||||
|
nl_msg->nlmsg_len = NLMSG_LENGTH(sizeof(rtmsg)); // Length of message.
|
||||||
|
nl_msg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
|
||||||
|
|
||||||
|
nl_msg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
|
||||||
|
nl_msg->nlmsg_seq = msg_seq++; // Sequence of the message packet.
|
||||||
|
nl_msg->nlmsg_pid = pid; // PID of process sending the request.
|
||||||
|
|
||||||
|
// ship it
|
||||||
|
if (send(sock, nl_msg, nl_msg->nlmsg_len, 0) < 0)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_NET, "Failed to send netlink request ({})", Common::StrNetworkError());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// read response
|
||||||
|
int msg_len = 0;
|
||||||
|
msg_buffer.fill(0);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
auto buf_ptr = msg_buffer.data() + msg_len;
|
||||||
|
const int read_len = recv(sock, buf_ptr, BUFF_SIZE - msg_len, 0);
|
||||||
|
if (read_len < 0)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_NET, "Failed to receive netlink response ({})", Common::StrNetworkError());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
nl_msg = reinterpret_cast<nlmsghdr*>(buf_ptr);
|
||||||
|
if (NLMSG_OK(nl_msg, read_len) == 0)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_NET, "Received netlink error response ({})", NLMSG_OK(nl_msg, read_len));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nl_msg->nlmsg_type == NLMSG_ERROR)
|
||||||
|
{
|
||||||
|
auto err = reinterpret_cast<nlmsgerr*>(NLMSG_DATA(nl_msg));
|
||||||
|
if (nl_msg->nlmsg_len < NLMSG_LENGTH(sizeof(nlmsgerr)))
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_NET, "Netlink message was truncated");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_NET, "Received netlink error {}: {}", err->error,
|
||||||
|
Common::DecodeNetworkError(-err->error));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// if its the last msg, we are done
|
||||||
|
if (nl_msg->nlmsg_type == NLMSG_DONE)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_len += read_len;
|
||||||
|
if ((nl_msg->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (msg_len <= BUFF_SIZE && (nl_msg->nlmsg_seq != msg_seq || nl_msg->nlmsg_pid != pid));
|
||||||
|
|
||||||
|
// parse the response
|
||||||
|
nl_msg = reinterpret_cast<nlmsghdr*>(msg_buffer.data());
|
||||||
|
for (; NLMSG_OK(nl_msg, msg_len); nl_msg = NLMSG_NEXT(nl_msg, msg_len))
|
||||||
|
{
|
||||||
|
rt_msg = reinterpret_cast<rtmsg*>(NLMSG_DATA(nl_msg));
|
||||||
|
|
||||||
|
// only parse AF_INET, as the wii is ipv4 only
|
||||||
|
if (rt_msg->rtm_family != AF_INET)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto rt_attr = reinterpret_cast<rtattr*>(RTM_RTA(rt_msg));
|
||||||
|
auto rt_len = RTM_PAYLOAD(nl_msg);
|
||||||
|
InterfaceRouting route = {};
|
||||||
|
|
||||||
|
// get netmask from the destination ip length and the rest from the response
|
||||||
|
for (; RTA_OK(rt_attr, rt_len); rt_attr = RTA_NEXT(rt_attr, rt_len))
|
||||||
|
{
|
||||||
|
switch (rt_attr->rta_type)
|
||||||
|
{
|
||||||
|
case RTA_GATEWAY:
|
||||||
|
route.gateway.s_addr = *reinterpret_cast<unsigned int*>(RTA_DATA(rt_attr));
|
||||||
|
break;
|
||||||
|
case RTA_DST:
|
||||||
|
route.destination.s_addr = *reinterpret_cast<unsigned int*>(RTA_DATA(rt_attr));
|
||||||
|
break;
|
||||||
|
case RTA_OIF:
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto mask = (route.destination.s_addr == 0 && rt_msg->rtm_dst_len == 0) ?
|
||||||
|
0 :
|
||||||
|
(1 << rt_msg->rtm_dst_len) - 1;
|
||||||
|
route.netmask.s_addr = mask;
|
||||||
|
routing_table.push_back(route);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return routing_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<DefaultInterface> GetSystemDefaultInterface()
|
||||||
|
{
|
||||||
|
auto routing_table = GetSystemInterfaceRouting();
|
||||||
|
#ifdef _WIN32
|
||||||
std::unique_ptr<MIB_IPADDRTABLE> ip_table;
|
std::unique_ptr<MIB_IPADDRTABLE> ip_table;
|
||||||
DWORD ip_table_size = 0;
|
DWORD ip_table_size = 0;
|
||||||
if (GetIpAddrTable(nullptr, &ip_table_size, FALSE) == ERROR_INSUFFICIENT_BUFFER)
|
if (GetIpAddrTable(nullptr, &ip_table_size, FALSE) == ERROR_INSUFFICIENT_BUFFER)
|
||||||
|
@ -189,34 +358,28 @@ static std::optional<DefaultInterface> GetSystemDefaultInterface()
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the interface IP used for the default route and use that
|
// find the interface IP used for the default route and use that
|
||||||
NET_IFINDEX ifIndex = NET_IFINDEX_UNSPECIFIED;
|
NET_IFINDEX if_index = NET_IFINDEX_UNSPECIFIED;
|
||||||
DWORD result = GetIpForwardTable(forward_table.get(), &forward_table_size, FALSE);
|
for (InterfaceRouting route : routing_table)
|
||||||
// can return ERROR_MORE_DATA on XP even after the first call
|
|
||||||
while (result == NO_ERROR || result == ERROR_MORE_DATA)
|
|
||||||
{
|
{
|
||||||
for (DWORD i = 0; i < forward_table->dwNumEntries; ++i)
|
if (route.destination.s_addr != 0)
|
||||||
{
|
continue;
|
||||||
if (forward_table->table[i].dwForwardDest == 0)
|
|
||||||
{
|
|
||||||
ifIndex = forward_table->table[i].dwForwardIfIndex;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == NO_ERROR || ifIndex != NET_IFINDEX_UNSPECIFIED)
|
if_index = route.index;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
result = GetIpForwardTable(forward_table.get(), &forward_table_size, FALSE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ifIndex != NET_IFINDEX_UNSPECIFIED &&
|
if (if_index != NET_IFINDEX_UNSPECIFIED &&
|
||||||
GetIpAddrTable(ip_table.get(), &ip_table_size, FALSE) == NO_ERROR)
|
GetIpAddrTable(ip_table.get(), &ip_table_size, FALSE) == NO_ERROR)
|
||||||
{
|
{
|
||||||
for (DWORD i = 0; i < ip_table->dwNumEntries; ++i)
|
for (DWORD i = 0; i < ip_table->dwNumEntries; ++i)
|
||||||
{
|
{
|
||||||
const auto& entry = ip_table->table[i];
|
const auto& entry = ip_table->table[i];
|
||||||
if (entry.dwIndex == ifIndex)
|
if (entry.dwIndex == if_index)
|
||||||
return DefaultInterface{entry.dwAddr, entry.dwMask, entry.dwBCastAddr};
|
{
|
||||||
|
return DefaultInterface{std::bit_cast<in_addr>(entry.dwAddr),
|
||||||
|
std::bit_cast<in_addr>(entry.dwMask),
|
||||||
|
std::bit_cast<in_addr>(entry.dwBCastAddr), routing_table};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#elif defined(__ANDROID__)
|
#elif defined(__ANDROID__)
|
||||||
|
@ -224,8 +387,12 @@ static std::optional<DefaultInterface> GetSystemDefaultInterface()
|
||||||
const u32 prefix_length = GetNetworkPrefixLength();
|
const u32 prefix_length = GetNetworkPrefixLength();
|
||||||
const u32 netmask = (1 << prefix_length) - 1;
|
const u32 netmask = (1 << prefix_length) - 1;
|
||||||
const u32 gateway = GetNetworkGateway();
|
const u32 gateway = GetNetworkGateway();
|
||||||
|
// this isnt fully correct, but this will make calls to get the routing table at least return the
|
||||||
|
// gateway
|
||||||
|
if (routing_table.empty())
|
||||||
|
routing_table = {{0, 0, 0, gateway}};
|
||||||
if (addr || netmask || gateway)
|
if (addr || netmask || gateway)
|
||||||
return DefaultInterface{addr, netmask, gateway};
|
return DefaultInterface{addr, netmask, gateway, routing_table};
|
||||||
#else
|
#else
|
||||||
// Assume that the address that is used to access the Internet corresponds
|
// Assume that the address that is used to access the Internet corresponds
|
||||||
// to the default interface.
|
// to the default interface.
|
||||||
|
@ -265,8 +432,13 @@ static std::optional<DefaultInterface> GetSystemDefaultInterface()
|
||||||
if (iface->ifa_addr && iface->ifa_addr->sa_family == AF_INET &&
|
if (iface->ifa_addr && iface->ifa_addr->sa_family == AF_INET &&
|
||||||
get_addr(iface->ifa_addr) == default_interface_address->s_addr)
|
get_addr(iface->ifa_addr) == default_interface_address->s_addr)
|
||||||
{
|
{
|
||||||
|
// this isnt fully correct, but this will make calls to get the routing table at least return
|
||||||
|
// the gateway
|
||||||
|
if (routing_table.empty())
|
||||||
|
routing_table = {{0, 0, 0, get_addr(iface->ifa_dstaddr)}};
|
||||||
|
|
||||||
return DefaultInterface{get_addr(iface->ifa_addr), get_addr(iface->ifa_netmask),
|
return DefaultInterface{get_addr(iface->ifa_addr), get_addr(iface->ifa_netmask),
|
||||||
get_addr(iface->ifa_broadaddr)};
|
get_addr(iface->ifa_broadaddr), routing_table};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -275,10 +447,13 @@ static std::optional<DefaultInterface> GetSystemDefaultInterface()
|
||||||
|
|
||||||
static DefaultInterface GetSystemDefaultInterfaceOrFallback()
|
static DefaultInterface GetSystemDefaultInterfaceOrFallback()
|
||||||
{
|
{
|
||||||
static const u32 FALLBACK_IP = inet_addr("10.0.1.30");
|
static const in_addr FALLBACK_IP = std::bit_cast<in_addr>(inet_addr("10.0.1.30"));
|
||||||
static const u32 FALLBACK_NETMASK = inet_addr("255.255.255.0");
|
static const in_addr FALLBACK_NETMASK = std::bit_cast<in_addr>(inet_addr("255.255.255.0"));
|
||||||
static const u32 FALLBACK_GATEWAY = inet_addr("10.0.255.255");
|
static const in_addr FALLBACK_BROADCAST = std::bit_cast<in_addr>(inet_addr("10.0.1.255"));
|
||||||
static const DefaultInterface FALLBACK_VALUES{FALLBACK_IP, FALLBACK_NETMASK, FALLBACK_GATEWAY};
|
static const in_addr FALLBACK_GATEWAY = std::bit_cast<in_addr>(inet_addr("10.0.1.1"));
|
||||||
|
static const InterfaceRouting FALLBACK_ROUTING = {.gateway = FALLBACK_GATEWAY};
|
||||||
|
static const DefaultInterface FALLBACK_VALUES = {
|
||||||
|
FALLBACK_IP, FALLBACK_NETMASK, FALLBACK_BROADCAST, {FALLBACK_ROUTING}};
|
||||||
return GetSystemDefaultInterface().value_or(FALLBACK_VALUES);
|
return GetSystemDefaultInterface().value_or(FALLBACK_VALUES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -611,7 +786,7 @@ IPCReply NetIPTopDevice::HandleGetPeerNameRequest(const IOCtlRequest& request)
|
||||||
IPCReply NetIPTopDevice::HandleGetHostIDRequest(const IOCtlRequest& request)
|
IPCReply NetIPTopDevice::HandleGetHostIDRequest(const IOCtlRequest& request)
|
||||||
{
|
{
|
||||||
const DefaultInterface interface = GetSystemDefaultInterfaceOrFallback();
|
const DefaultInterface interface = GetSystemDefaultInterfaceOrFallback();
|
||||||
const u32 host_ip = Common::swap32(interface.inet);
|
const u32 host_ip = ntohl(interface.inet.s_addr);
|
||||||
INFO_LOG_FMT(IOS_NET, "IOCTL_SO_GETHOSTID = {}.{}.{}.{}", host_ip >> 24, (host_ip >> 16) & 0xFF,
|
INFO_LOG_FMT(IOS_NET, "IOCTL_SO_GETHOSTID = {}.{}.{}.{}", host_ip >> 24, (host_ip >> 16) & 0xFF,
|
||||||
(host_ip >> 8) & 0xFF, host_ip & 0xFF);
|
(host_ip >> 8) & 0xFF, host_ip & 0xFF);
|
||||||
return IPCReply(host_ip);
|
return IPCReply(host_ip);
|
||||||
|
@ -830,6 +1005,7 @@ IPCReply NetIPTopDevice::HandleGetInterfaceOptRequest(const IOCtlVRequest& reque
|
||||||
auto& system = GetSystem();
|
auto& system = GetSystem();
|
||||||
auto& memory = system.GetMemory();
|
auto& memory = system.GetMemory();
|
||||||
|
|
||||||
|
const DefaultInterface interface = GetSystemDefaultInterfaceOrFallback();
|
||||||
const u32 param = memory.Read_U32(request.in_vectors[0].address);
|
const u32 param = memory.Read_U32(request.in_vectors[0].address);
|
||||||
const u32 param2 = memory.Read_U32(request.in_vectors[0].address + 4);
|
const u32 param2 = memory.Read_U32(request.in_vectors[0].address + 4);
|
||||||
const u32 param3 = memory.Read_U32(request.io_vectors[0].address);
|
const u32 param3 = memory.Read_U32(request.io_vectors[0].address);
|
||||||
|
@ -980,20 +1156,42 @@ IPCReply NetIPTopDevice::HandleGetInterfaceOptRequest(const IOCtlVRequest& reque
|
||||||
|
|
||||||
case 0x4003: // ip addr table
|
case 0x4003: // ip addr table
|
||||||
{
|
{
|
||||||
// XXX: this isn't exactly right; the buffer can be larger than 12 bytes, in which case
|
// XXX: this isn't exactly right; the buffer can be larger than 12 bytes,
|
||||||
// SO can write 12 more bytes.
|
// in which case, depending on some interface settings, SO can write 12 more bytes
|
||||||
memory.Write_U32(0xC, request.io_vectors[1].address);
|
memory.Write_U32(0xC, request.io_vectors[1].address);
|
||||||
const DefaultInterface interface = GetSystemDefaultInterfaceOrFallback();
|
memory.Write_U32(ntohl(interface.inet.s_addr), request.io_vectors[0].address);
|
||||||
memory.Write_U32(Common::swap32(interface.inet), request.io_vectors[0].address);
|
memory.Write_U32(ntohl(interface.netmask.s_addr), request.io_vectors[0].address + 4);
|
||||||
memory.Write_U32(Common::swap32(interface.netmask), request.io_vectors[0].address + 4);
|
memory.Write_U32(ntohl(interface.broadcast.s_addr), request.io_vectors[0].address + 8);
|
||||||
memory.Write_U32(Common::swap32(interface.broadcast), request.io_vectors[0].address + 8);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x4005: // hardcoded value
|
case 0x4005:
|
||||||
|
// get routing table size, which is almost always hardcoded to be 0x20 in IOS
|
||||||
|
// on pc its often around 0x20 too so... meh
|
||||||
memory.Write_U32(0x20, request.io_vectors[0].address);
|
memory.Write_U32(0x20, request.io_vectors[0].address);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 0x4006: // get routing table
|
||||||
|
for (InterfaceRouting route : interface.routing_table)
|
||||||
|
{
|
||||||
|
memory.Write_U32(ntohl(route.destination.s_addr), request.io_vectors[0].address + param5);
|
||||||
|
memory.Write_U32(ntohl(route.netmask.s_addr), request.io_vectors[0].address + param5 + 4);
|
||||||
|
memory.Write_U32(ntohl(route.gateway.s_addr), request.io_vectors[0].address + param5 + 8);
|
||||||
|
|
||||||
|
// write flags. unknown what they do but when gateway was 0 bit 0 was always 0
|
||||||
|
memory.Write_U32(route.gateway.s_addr == 0 ? 0 : 1,
|
||||||
|
request.io_vectors[0].address + param5 + 12);
|
||||||
|
|
||||||
|
// some unknown
|
||||||
|
memory.Write_U64(0x00, request.io_vectors[0].address + param5 + 16);
|
||||||
|
param5 += 24;
|
||||||
|
if (param5 >= param4)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
memory.Write_U32(param5, request.io_vectors[1].address);
|
||||||
|
break;
|
||||||
|
|
||||||
case 0x6003: // hardcoded value
|
case 0x6003: // hardcoded value
|
||||||
memory.Write_U32(0x80, request.io_vectors[0].address);
|
memory.Write_U32(0x80, request.io_vectors[0].address);
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue