Merge branch 'master' into feature/savestates-2

This commit is contained in:
Hamish Milne 2020-03-07 21:23:08 +00:00
commit da3ab3d56e
80 changed files with 7297 additions and 2608 deletions

View file

@ -1,7 +1,13 @@
#!/bin/bash -e
# Setup RC file for tx
echo $'[https://www.transifex.com]\nhostname = https://www.transifex.com\nusername = api\npassword = '"$TRANSIFEX_API_TOKEN"$'\n' > ~/.transifexrc
cat << EOF > ~/.transifexrc
[https://www.transifex.com]
hostname = https://www.transifex.com
username = api
password = $TRANSIFEX_API_TOKEN
EOF
set -x

View file

@ -40,6 +40,8 @@ CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ON "
option(USE_SYSTEM_BOOST "Use the system Boost libs (instead of the bundled ones)" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_FDK "Use FDK AAC decoder" OFF "NOT ENABLE_FFMPEG_AUDIO_DECODER;NOT ENABLE_MF" OFF)
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
message(STATUS "Copying pre-commit hook")
file(COPY hooks/pre-commit
@ -218,6 +220,12 @@ if (ENABLE_FFMPEG_VIDEO_DUMPER)
add_definitions(-DENABLE_FFMPEG_VIDEO_DUMPER)
endif()
if (ENABLE_FDK)
find_library(FDK_AAC fdk-aac DOC "The path to fdk_aac library")
if(FDK_AAC STREQUAL "FDK_AAC-NOTFOUND")
message(FATAL_ERROR "fdk_aac library not found.")
endif()
endif()
# Platform-specific library requirements
# ======================================

View file

@ -2,7 +2,7 @@
Citra
==============
[![Travis CI Build Status](https://travis-ci.org/citra-emu/citra.svg?branch=master)](https://travis-ci.org/citra-emu/citra)
[![Travis CI Build Status](https://travis-ci.com/citra-emu/citra.svg?branch=master)](https://travis-ci.com/citra-emu/citra)
[![AppVeyor CI Build Status](https://ci.appveyor.com/api/projects/status/sdf1o4kh3g1e68m9?svg=true)](https://ci.appveyor.com/project/bunnei/citra)
[![Bitrise CI Build Status](https://app.bitrise.io/app/4ccd8e5720f0d13b/status.svg?token=H32TmbCwxb3OQ-M66KbAyw&branch=master)](https://app.bitrise.io/app/4ccd8e5720f0d13b)
@ -16,13 +16,13 @@ Check out our [website](https://citra-emu.org/)!
Need help? Check out our [asking for help](https://citra-emu.org/help/reference/asking/) guide.
For development discussion, please join us at #citra-dev on freenode.
For development discussion, please join us on our [Discord server](https://citra-emu.org/discord/) or at #citra-dev on freenode.
### Development
Most of the development happens on GitHub. It's also where [our central repository](https://github.com/citra-emu/citra) is hosted.
If you want to contribute please take a look at the [Contributor's Guide](https://github.com/citra-emu/citra/wiki/Contributing) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information). You should as well contact any of the developers in the forum in order to know about the current state of the emulator because the [TODO list](https://docs.google.com/document/d/1SWIop0uBI9IW8VGg97TAtoT_CHNoP42FzYmvG1F4QDA) isn't maintained anymore.
If you want to contribute please take a look at the [Contributor's Guide](https://github.com/citra-emu/citra/wiki/Contributing) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information). You should also contact any of the developers in the forum in order to know about the current state of the emulator because the [TODO list](https://docs.google.com/document/d/1SWIop0uBI9IW8VGg97TAtoT_CHNoP42FzYmvG1F4QDA) isn't maintained anymore.
If you want to contribute to the user interface translation, please checkout [citra project on transifex](https://www.transifex.com/citra/citra). We centralize the translation work there, and periodically upstream translation.
@ -39,6 +39,5 @@ We happily accept monetary donations or donated games and hardware. Please see o
* 3DS games for testing
* Any equipment required for homebrew
* Infrastructure setup
* Eventually 3D displays to get proper 3D output working
We also more than gladly accept used 3DS consoles, preferably ones with firmware 4.5 or lower! If you would like to give yours away, don't hesitate to join our IRC channel #citra on [Freenode](http://webchat.freenode.net/?channels=citra) and talk to neobrain or bunnei. Mind you, IRC is slow-paced, so it might be a while until people reply. If you're in a hurry you can just leave contact details in the channel or via private message and we'll get back to you.
We also more than gladly accept used 3DS consoles! If you would like to give yours away, don't hesitate to join our [Discord server](https://citra-emu.org/discord/) and talk to bunnei.

View file

@ -52,7 +52,7 @@ workflows:
sudo apt remove cmake -y
sudo apt purge --auto-remove cmake -y
sudo apt install ninja-build -y
version=3.8
version=3.10
build=2
mkdir ~/temp
cd ~/temp
@ -97,7 +97,7 @@ workflows:
sudo apt remove cmake -y
sudo apt purge --auto-remove cmake -y
sudo apt install ninja-build -y
version=3.8
version=3.10
build=2
mkdir ~/temp
cd ~/temp

View file

@ -115,9 +115,14 @@ if (ENABLE_WEB_SERVICE)
# lurlparser
add_subdirectory(lurlparser EXCLUDE_FROM_ALL)
if(ANDROID)
add_subdirectory(android-ifaddrs)
endif()
# httplib
add_library(httplib INTERFACE)
target_include_directories(httplib INTERFACE ./httplib)
target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
# cpp-jwt
add_library(cpp-jwt INTERFACE)

View file

@ -0,0 +1,8 @@
add_library(ifaddrs
ifaddrs.c
ifaddrs.h
)
create_target_directory_groups(ifaddrs)
target_include_directories(ifaddrs INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

600
externals/android-ifaddrs/ifaddrs.c vendored Normal file
View file

@ -0,0 +1,600 @@
/*
Copyright (c) 2013, Kenneth MacKay
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "ifaddrs.h"
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
typedef struct NetlinkList
{
struct NetlinkList *m_next;
struct nlmsghdr *m_data;
unsigned int m_size;
} NetlinkList;
static int netlink_socket(void)
{
int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if(l_socket < 0)
{
return -1;
}
struct sockaddr_nl l_addr;
memset(&l_addr, 0, sizeof(l_addr));
l_addr.nl_family = AF_NETLINK;
if(bind(l_socket, (struct sockaddr *)&l_addr, sizeof(l_addr)) < 0)
{
close(l_socket);
return -1;
}
return l_socket;
}
static int netlink_send(int p_socket, int p_request)
{
char l_buffer[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))];
memset(l_buffer, 0, sizeof(l_buffer));
struct nlmsghdr *l_hdr = (struct nlmsghdr *)l_buffer;
struct rtgenmsg *l_msg = (struct rtgenmsg *)NLMSG_DATA(l_hdr);
l_hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*l_msg));
l_hdr->nlmsg_type = p_request;
l_hdr->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
l_hdr->nlmsg_pid = 0;
l_hdr->nlmsg_seq = p_socket;
l_msg->rtgen_family = AF_UNSPEC;
struct sockaddr_nl l_addr;
memset(&l_addr, 0, sizeof(l_addr));
l_addr.nl_family = AF_NETLINK;
return (sendto(p_socket, l_hdr, l_hdr->nlmsg_len, 0, (struct sockaddr *)&l_addr, sizeof(l_addr)));
}
static int netlink_recv(int p_socket, void *p_buffer, size_t p_len)
{
struct msghdr l_msg;
struct iovec l_iov = { p_buffer, p_len };
struct sockaddr_nl l_addr;
int l_result;
for(;;)
{
l_msg.msg_name = (void *)&l_addr;
l_msg.msg_namelen = sizeof(l_addr);
l_msg.msg_iov = &l_iov;
l_msg.msg_iovlen = 1;
l_msg.msg_control = NULL;
l_msg.msg_controllen = 0;
l_msg.msg_flags = 0;
int l_result = recvmsg(p_socket, &l_msg, 0);
if(l_result < 0)
{
if(errno == EINTR)
{
continue;
}
return -2;
}
if(l_msg.msg_flags & MSG_TRUNC)
{ // buffer was too small
return -1;
}
return l_result;
}
}
static struct nlmsghdr *getNetlinkResponse(int p_socket, int *p_size, int *p_done)
{
size_t l_size = 4096;
void *l_buffer = NULL;
for(;;)
{
free(l_buffer);
l_buffer = malloc(l_size);
int l_read = netlink_recv(p_socket, l_buffer, l_size);
*p_size = l_read;
if(l_read == -2)
{
free(l_buffer);
return NULL;
}
if(l_read >= 0)
{
pid_t l_pid = getpid();
struct nlmsghdr *l_hdr;
for(l_hdr = (struct nlmsghdr *)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); l_hdr = (struct nlmsghdr *)NLMSG_NEXT(l_hdr, l_read))
{
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
{
continue;
}
if(l_hdr->nlmsg_type == NLMSG_DONE)
{
*p_done = 1;
break;
}
if(l_hdr->nlmsg_type == NLMSG_ERROR)
{
free(l_buffer);
return NULL;
}
}
return l_buffer;
}
l_size *= 2;
}
}
static NetlinkList *newListItem(struct nlmsghdr *p_data, unsigned int p_size)
{
NetlinkList *l_item = malloc(sizeof(NetlinkList));
l_item->m_next = NULL;
l_item->m_data = p_data;
l_item->m_size = p_size;
return l_item;
}
static void freeResultList(NetlinkList *p_list)
{
NetlinkList *l_cur;
while(p_list)
{
l_cur = p_list;
p_list = p_list->m_next;
free(l_cur->m_data);
free(l_cur);
}
}
static NetlinkList *getResultList(int p_socket, int p_request)
{
if(netlink_send(p_socket, p_request) < 0)
{
return NULL;
}
NetlinkList *l_list = NULL;
NetlinkList *l_end = NULL;
int l_size;
int l_done = 0;
while(!l_done)
{
struct nlmsghdr *l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done);
if(!l_hdr)
{ // error
freeResultList(l_list);
return NULL;
}
NetlinkList *l_item = newListItem(l_hdr, l_size);
if(!l_list)
{
l_list = l_item;
}
else
{
l_end->m_next = l_item;
}
l_end = l_item;
}
return l_list;
}
static size_t maxSize(size_t a, size_t b)
{
return (a > b ? a : b);
}
static size_t calcAddrLen(sa_family_t p_family, int p_dataSize)
{
switch(p_family)
{
case AF_INET:
return sizeof(struct sockaddr_in);
case AF_INET6:
return sizeof(struct sockaddr_in6);
case AF_PACKET:
return maxSize(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize);
default:
return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize);
}
}
static void makeSockaddr(sa_family_t p_family, struct sockaddr *p_dest, void *p_data, size_t p_size)
{
switch(p_family)
{
case AF_INET:
memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size);
break;
case AF_INET6:
memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size);
break;
case AF_PACKET:
memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size);
((struct sockaddr_ll*)p_dest)->sll_halen = p_size;
break;
default:
memcpy(p_dest->sa_data, p_data, p_size);
break;
}
p_dest->sa_family = p_family;
}
static void addToEnd(struct ifaddrs **p_resultList, struct ifaddrs *p_entry)
{
if(!*p_resultList)
{
*p_resultList = p_entry;
}
else
{
struct ifaddrs *l_cur = *p_resultList;
while(l_cur->ifa_next)
{
l_cur = l_cur->ifa_next;
}
l_cur->ifa_next = p_entry;
}
}
static void interpretLink(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList)
{
struct ifinfomsg *l_info = (struct ifinfomsg *)NLMSG_DATA(p_hdr);
size_t l_nameSize = 0;
size_t l_addrSize = 0;
size_t l_dataSize = 0;
size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg));
struct rtattr *l_rta;
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
{
void *l_rtaData = RTA_DATA(l_rta);
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
switch(l_rta->rta_type)
{
case IFLA_ADDRESS:
case IFLA_BROADCAST:
l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize));
break;
case IFLA_IFNAME:
l_nameSize += NLMSG_ALIGN(l_rtaSize + 1);
break;
case IFLA_STATS:
l_dataSize += NLMSG_ALIGN(l_rtaSize);
break;
default:
break;
}
}
struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize + l_dataSize);
memset(l_entry, 0, sizeof(struct ifaddrs));
l_entry->ifa_name = "";
char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs);
char *l_addr = l_name + l_nameSize;
char *l_data = l_addr + l_addrSize;
l_entry->ifa_flags = l_info->ifi_flags;
l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg));
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
{
void *l_rtaData = RTA_DATA(l_rta);
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
switch(l_rta->rta_type)
{
case IFLA_ADDRESS:
case IFLA_BROADCAST:
{
size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize);
makeSockaddr(AF_PACKET, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize);
((struct sockaddr_ll *)l_addr)->sll_ifindex = l_info->ifi_index;
((struct sockaddr_ll *)l_addr)->sll_hatype = l_info->ifi_type;
if(l_rta->rta_type == IFLA_ADDRESS)
{
l_entry->ifa_addr = (struct sockaddr *)l_addr;
}
else
{
l_entry->ifa_broadaddr = (struct sockaddr *)l_addr;
}
l_addr += NLMSG_ALIGN(l_addrLen);
break;
}
case IFLA_IFNAME:
strncpy(l_name, l_rtaData, l_rtaDataSize);
l_name[l_rtaDataSize] = '\0';
l_entry->ifa_name = l_name;
break;
case IFLA_STATS:
memcpy(l_data, l_rtaData, l_rtaDataSize);
l_entry->ifa_data = l_data;
break;
default:
break;
}
}
addToEnd(p_resultList, l_entry);
p_links[l_info->ifi_index - 1] = l_entry;
}
static void interpretAddr(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList)
{
struct ifaddrmsg *l_info = (struct ifaddrmsg *)NLMSG_DATA(p_hdr);
size_t l_nameSize = 0;
size_t l_addrSize = 0;
int l_addedNetmask = 0;
size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg));
struct rtattr *l_rta;
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
{
void *l_rtaData = RTA_DATA(l_rta);
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
if(l_info->ifa_family == AF_PACKET)
{
continue;
}
switch(l_rta->rta_type)
{
case IFA_ADDRESS:
case IFA_LOCAL:
if((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask)
{ // make room for netmask
l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize));
l_addedNetmask = 1;
}
case IFA_BROADCAST:
l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize));
break;
case IFA_LABEL:
l_nameSize += NLMSG_ALIGN(l_rtaSize + 1);
break;
default:
break;
}
}
struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize);
memset(l_entry, 0, sizeof(struct ifaddrs));
l_entry->ifa_name = p_links[l_info->ifa_index - 1]->ifa_name;
char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs);
char *l_addr = l_name + l_nameSize;
l_entry->ifa_flags = l_info->ifa_flags | p_links[l_info->ifa_index - 1]->ifa_flags;
l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg));
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
{
void *l_rtaData = RTA_DATA(l_rta);
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
switch(l_rta->rta_type)
{
case IFA_ADDRESS:
case IFA_BROADCAST:
case IFA_LOCAL:
{
size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize);
makeSockaddr(l_info->ifa_family, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize);
if(l_info->ifa_family == AF_INET6)
{
if(IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)l_rtaData) || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr *)l_rtaData))
{
((struct sockaddr_in6 *)l_addr)->sin6_scope_id = l_info->ifa_index;
}
}
if(l_rta->rta_type == IFA_ADDRESS)
{ // apparently in a point-to-point network IFA_ADDRESS contains the dest address and IFA_LOCAL contains the local address
if(l_entry->ifa_addr)
{
l_entry->ifa_dstaddr = (struct sockaddr *)l_addr;
}
else
{
l_entry->ifa_addr = (struct sockaddr *)l_addr;
}
}
else if(l_rta->rta_type == IFA_LOCAL)
{
if(l_entry->ifa_addr)
{
l_entry->ifa_dstaddr = l_entry->ifa_addr;
}
l_entry->ifa_addr = (struct sockaddr *)l_addr;
}
else
{
l_entry->ifa_broadaddr = (struct sockaddr *)l_addr;
}
l_addr += NLMSG_ALIGN(l_addrLen);
break;
}
case IFA_LABEL:
strncpy(l_name, l_rtaData, l_rtaDataSize);
l_name[l_rtaDataSize] = '\0';
l_entry->ifa_name = l_name;
break;
default:
break;
}
}
if(l_entry->ifa_addr && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6))
{
unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128);
unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen);
char l_mask[16] = {0};
unsigned i;
for(i=0; i<(l_prefix/8); ++i)
{
l_mask[i] = 0xff;
}
l_mask[i] = 0xff << (8 - (l_prefix % 8));
makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr *)l_addr, l_mask, l_maxPrefix / 8);
l_entry->ifa_netmask = (struct sockaddr *)l_addr;
}
addToEnd(p_resultList, l_entry);
}
static void interpret(int p_socket, NetlinkList *p_netlinkList, struct ifaddrs **p_links, struct ifaddrs **p_resultList)
{
pid_t l_pid = getpid();
for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next)
{
unsigned int l_nlsize = p_netlinkList->m_size;
struct nlmsghdr *l_hdr;
for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize))
{
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
{
continue;
}
if(l_hdr->nlmsg_type == NLMSG_DONE)
{
break;
}
if(l_hdr->nlmsg_type == RTM_NEWLINK)
{
interpretLink(l_hdr, p_links, p_resultList);
}
else if(l_hdr->nlmsg_type == RTM_NEWADDR)
{
interpretAddr(l_hdr, p_links, p_resultList);
}
}
}
}
static unsigned countLinks(int p_socket, NetlinkList *p_netlinkList)
{
unsigned l_links = 0;
pid_t l_pid = getpid();
for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next)
{
unsigned int l_nlsize = p_netlinkList->m_size;
struct nlmsghdr *l_hdr;
for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize))
{
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
{
continue;
}
if(l_hdr->nlmsg_type == NLMSG_DONE)
{
break;
}
if(l_hdr->nlmsg_type == RTM_NEWLINK)
{
++l_links;
}
}
}
return l_links;
}
int getifaddrs(struct ifaddrs **ifap)
{
if(!ifap)
{
return -1;
}
*ifap = NULL;
int l_socket = netlink_socket();
if(l_socket < 0)
{
return -1;
}
NetlinkList *l_linkResults = getResultList(l_socket, RTM_GETLINK);
if(!l_linkResults)
{
close(l_socket);
return -1;
}
NetlinkList *l_addrResults = getResultList(l_socket, RTM_GETADDR);
if(!l_addrResults)
{
close(l_socket);
freeResultList(l_linkResults);
return -1;
}
unsigned l_numLinks = countLinks(l_socket, l_linkResults) + countLinks(l_socket, l_addrResults);
struct ifaddrs *l_links[l_numLinks];
memset(l_links, 0, l_numLinks * sizeof(struct ifaddrs *));
interpret(l_socket, l_linkResults, l_links, ifap);
interpret(l_socket, l_addrResults, l_links, ifap);
freeResultList(l_linkResults);
freeResultList(l_addrResults);
close(l_socket);
return 0;
}
void freeifaddrs(struct ifaddrs *ifa)
{
struct ifaddrs *l_cur;
while(ifa)
{
l_cur = ifa;
ifa = ifa->ifa_next;
free(l_cur);
}
}

54
externals/android-ifaddrs/ifaddrs.h vendored Normal file
View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 1995, 1999
* Berkeley Software Design, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* BSDI ifaddrs.h,v 2.5 2000/02/23 14:51:59 dab Exp
*/
#ifndef _IFADDRS_H_
#define _IFADDRS_H_
struct ifaddrs {
struct ifaddrs *ifa_next;
char *ifa_name;
unsigned int ifa_flags;
struct sockaddr *ifa_addr;
struct sockaddr *ifa_netmask;
struct sockaddr *ifa_dstaddr;
void *ifa_data;
};
/*
* This may have been defined in <net/if.h>. Note that if <net/if.h> is
* to be included it must be included before this header file.
*/
#ifndef ifa_broadaddr
#define ifa_broadaddr ifa_dstaddr /* broadcast address interface */
#endif
#include <sys/cdefs.h>
__BEGIN_DECLS
extern int getifaddrs(struct ifaddrs **ifap);
extern void freeifaddrs(struct ifaddrs *ifa);
__END_DECLS
#endif

2
externals/boost vendored

@ -1 +1 @@
Subproject commit 6d7edc593be8e47c8de7bc5f7d6b32971fad0c24
Subproject commit 727f616b6e5cafaba072131c077a3b8fea87b8be

View file

@ -1,4 +1,4 @@
From https://github.com/yhirose/cpp-httplib/commit/d9479bc0b12e8a1e8bce2d34da4feeef488581f3
From https://github.com/yhirose/cpp-httplib/commit/b251668522dd459d2c6a75c10390a11b640be708
MIT License
@ -13,3 +13,4 @@ It's extremely easy to setup. Just include httplib.h file in your code!
Inspired by Sinatra and express.
© 2017 Yuji Hirose

File diff suppressed because it is too large Load diff

View file

@ -62,6 +62,13 @@ elseif(ENABLE_FFMPEG_AUDIO_DECODER)
target_include_directories(audio_core PRIVATE ${FFMPEG_DIR}/include)
endif()
target_compile_definitions(audio_core PUBLIC HAVE_FFMPEG)
elseif(ENABLE_FDK)
target_sources(audio_core PRIVATE
hle/fdk_decoder.cpp
hle/fdk_decoder.h
)
target_link_libraries(audio_core PRIVATE ${FDK_AAC})
target_compile_definitions(audio_core PUBLIC HAVE_FDK)
endif()
if(SDL2_FOUND)

View file

@ -0,0 +1,233 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fdk-aac/aacdecoder_lib.h>
#include "audio_core/hle/fdk_decoder.h"
namespace AudioCore::HLE {
class FDKDecoder::Impl {
public:
explicit Impl(Memory::MemorySystem& memory);
~Impl();
std::optional<BinaryResponse> ProcessRequest(const BinaryRequest& request);
bool IsValid() const {
return decoder != nullptr;
}
private:
std::optional<BinaryResponse> Initalize(const BinaryRequest& request);
std::optional<BinaryResponse> Decode(const BinaryRequest& request);
void Clear();
Memory::MemorySystem& memory;
HANDLE_AACDECODER decoder = nullptr;
};
FDKDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) {
// allocate an array of LIB_INFO structures
// if we don't pre-fill the whole segment with zeros, when we call `aacDecoder_GetLibInfo`
// it will segfault, upon investigation, there is some code in fdk_aac depends on your initial
// values in this array
LIB_INFO decoder_info[FDK_MODULE_LAST] = {};
// get library information and fill the struct
if (aacDecoder_GetLibInfo(decoder_info) != 0) {
LOG_ERROR(Audio_DSP, "Failed to retrieve fdk_aac library information!");
return;
}
// This segment: identify the broken fdk_aac implementation
// and refuse to initialize if identified as broken (check for module IDs)
// although our AAC samples do not contain SBC feature, this is a way to detect
// watered down version of fdk_aac implementations
if (FDKlibInfo_getCapabilities(decoder_info, FDK_SBRDEC) == 0) {
LOG_ERROR(Audio_DSP, "Bad fdk_aac library found! Initialization aborted!");
return;
}
LOG_INFO(Audio_DSP, "Using fdk_aac version {} (build date: {})", decoder_info[0].versionStr,
decoder_info[0].build_date);
// choose the input format when initializing: 1 layer of ADTS
decoder = aacDecoder_Open(TRANSPORT_TYPE::TT_MP4_ADTS, 1);
// set maximum output channel to two (stereo)
// if the input samples have more channels, fdk_aac will perform a downmix
AAC_DECODER_ERROR ret = aacDecoder_SetParam(decoder, AAC_PCM_MAX_OUTPUT_CHANNELS, 2);
if (ret != AAC_DEC_OK) {
// unable to set this parameter reflects the decoder implementation might be broken
// we'd better shuts down everything
aacDecoder_Close(decoder);
decoder = nullptr;
LOG_ERROR(Audio_DSP, "Unable to set downmix parameter: {}", ret);
return;
}
}
std::optional<BinaryResponse> FDKDecoder::Impl::Initalize(const BinaryRequest& request) {
BinaryResponse response;
std::memcpy(&response, &request, sizeof(response));
response.unknown1 = 0x0;
if (decoder) {
LOG_INFO(Audio_DSP, "FDK Decoder initialized");
Clear();
} else {
LOG_ERROR(Audio_DSP, "Decoder not initialized");
}
return response;
}
FDKDecoder::Impl::~Impl() {
if (decoder)
aacDecoder_Close(decoder);
}
void FDKDecoder::Impl::Clear() {
s16 decoder_output[8192];
// flush and re-sync the decoder, discarding the internal buffer
// we actually don't care if this succeeds or not
// FLUSH - flush internal buffer
// INTR - treat the current internal buffer as discontinuous
// CONCEAL - try to interpolate and smooth out the samples
if (decoder)
aacDecoder_DecodeFrame(decoder, decoder_output, 8192,
AACDEC_FLUSH & AACDEC_INTR & AACDEC_CONCEAL);
}
std::optional<BinaryResponse> FDKDecoder::Impl::ProcessRequest(const BinaryRequest& request) {
if (request.codec != DecoderCodec::AAC) {
LOG_ERROR(Audio_DSP, "FDK AAC Decoder cannot handle such codec: {}",
static_cast<u16>(request.codec));
return {};
}
switch (request.cmd) {
case DecoderCommand::Init: {
return Initalize(request);
}
case DecoderCommand::Decode: {
return Decode(request);
}
case DecoderCommand::Unknown: {
BinaryResponse response;
std::memcpy(&response, &request, sizeof(response));
response.unknown1 = 0x0;
return response;
}
default:
LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", static_cast<u16>(request.cmd));
return {};
}
}
std::optional<BinaryResponse> FDKDecoder::Impl::Decode(const BinaryRequest& request) {
BinaryResponse response;
response.codec = request.codec;
response.cmd = request.cmd;
response.size = request.size;
if (!decoder) {
LOG_DEBUG(Audio_DSP, "Decoder not initalized");
// This is a hack to continue games that are not compiled with the aac codec
response.num_channels = 2;
response.num_samples = 1024;
return response;
}
if (request.src_addr < Memory::FCRAM_PADDR ||
request.src_addr + request.size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}", request.src_addr);
return {};
}
u8* data = memory.GetFCRAMPointer(request.src_addr - Memory::FCRAM_PADDR);
std::array<std::vector<s16>, 2> out_streams;
std::size_t data_size = request.size;
// decoding loops
AAC_DECODER_ERROR result = AAC_DEC_OK;
// 8192 units of s16 are enough to hold one frame of AAC-LC or AAC-HE/v2 data
s16 decoder_output[8192];
// note that we don't free this pointer as it is automatically freed by fdk_aac
CStreamInfo* stream_info;
// how many bytes to be queued into the decoder, decrementing from the buffer size
u32 buffer_remaining = data_size;
// alias the data_size as an u32
u32 input_size = data_size;
while (buffer_remaining) {
// queue the input buffer, fdk_aac will automatically slice out the buffer it needs
// from the input buffer
result = aacDecoder_Fill(decoder, &data, &input_size, &buffer_remaining);
if (result != AAC_DEC_OK) {
// there are some issues when queuing the input buffer
LOG_ERROR(Audio_DSP, "Failed to enqueue the input samples");
return std::nullopt;
}
// get output from decoder
result = aacDecoder_DecodeFrame(decoder, decoder_output, 8192, 0);
if (result == AAC_DEC_OK) {
// get the stream information
stream_info = aacDecoder_GetStreamInfo(decoder);
// fill the stream information for binary response
response.num_channels = stream_info->aacNumChannels;
response.num_samples = stream_info->frameSize;
// fill the output
// the sample size = frame_size * channel_counts
for (int sample = 0; sample < (stream_info->frameSize * 2); sample++) {
for (int ch = 0; ch < stream_info->aacNumChannels; ch++) {
out_streams[ch].push_back(decoder_output[(sample * 2) + 1]);
}
}
} else if (result == AAC_DEC_TRANSPORT_SYNC_ERROR) {
// decoder has some synchronization problems, try again with new samples,
// using old samples might trigger this error again
continue;
} else {
LOG_ERROR(Audio_DSP, "Error decoding the sample: {}", result);
return std::nullopt;
}
}
// transfer the decoded buffer from vector to the FCRAM
if (out_streams[0].size() != 0) {
if (request.dst_addr_ch0 < Memory::FCRAM_PADDR ||
request.dst_addr_ch0 + out_streams[0].size() >
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch0 {:08x}", request.dst_addr_ch0);
return {};
}
std::memcpy(memory.GetFCRAMPointer(request.dst_addr_ch0 - Memory::FCRAM_PADDR),
out_streams[0].data(), out_streams[0].size());
}
if (out_streams[1].size() != 0) {
if (request.dst_addr_ch1 < Memory::FCRAM_PADDR ||
request.dst_addr_ch1 + out_streams[1].size() >
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch1 {:08x}", request.dst_addr_ch1);
return {};
}
std::memcpy(memory.GetFCRAMPointer(request.dst_addr_ch1 - Memory::FCRAM_PADDR),
out_streams[1].data(), out_streams[1].size());
}
return response;
}
FDKDecoder::FDKDecoder(Memory::MemorySystem& memory) : impl(std::make_unique<Impl>(memory)) {}
FDKDecoder::~FDKDecoder() = default;
std::optional<BinaryResponse> FDKDecoder::ProcessRequest(const BinaryRequest& request) {
return impl->ProcessRequest(request);
}
bool FDKDecoder::IsValid() const {
return impl->IsValid();
}
} // namespace AudioCore::HLE

View file

@ -0,0 +1,23 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "audio_core/hle/decoder.h"
namespace AudioCore::HLE {
class FDKDecoder final : public DecoderBase {
public:
explicit FDKDecoder(Memory::MemorySystem& memory);
~FDKDecoder() override;
std::optional<BinaryResponse> ProcessRequest(const BinaryRequest& request) override;
bool IsValid() const override;
private:
class Impl;
std::unique_ptr<Impl> impl;
};
} // namespace AudioCore::HLE

View file

@ -13,6 +13,8 @@
#include "audio_core/hle/wmf_decoder.h"
#elif HAVE_FFMPEG
#include "audio_core/hle/ffmpeg_decoder.h"
#elif HAVE_FDK
#include "audio_core/hle/fdk_decoder.h"
#endif
#include "audio_core/hle/common.h"
#include "audio_core/hle/decoder.h"
@ -124,6 +126,8 @@ DspHle::Impl::Impl(DspHle& parent_, Memory::MemorySystem& memory) : parent(paren
decoder = std::make_unique<HLE::WMFDecoder>(memory);
#elif defined(HAVE_FFMPEG)
decoder = std::make_unique<HLE::FFMPEGDecoder>(memory);
#elif defined(HAVE_FDK)
decoder = std::make_unique<HLE::FDKDecoder>(memory);
#else
LOG_WARNING(Audio_DSP, "No decoder found, this could lead to missing audio");
decoder = std::make_unique<HLE::NullDecoder>();

View file

@ -25,10 +25,6 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
ConfigureGeneral::~ConfigureGeneral() = default;
void ConfigureGeneral::SetConfiguration() {
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
ui->frame_limit->setValue(Settings::values.frame_limit);
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background);
@ -57,9 +53,6 @@ void ConfigureGeneral::ResetDefaults() {
}
void ConfigureGeneral::ApplyConfiguration() {
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
Settings::values.frame_limit = ui->frame_limit->value();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();

View file

@ -114,7 +114,7 @@ void IPCRecorderWidget::SetEnabled(bool enabled) {
}
void IPCRecorderWidget::Clear() {
id_offset = records.size() + 1;
id_offset += records.size();
records.clear();
ui->main->invisibleRootItem()->takeChildren();

View file

@ -61,13 +61,14 @@ void RegistersWidget::OnDebugModeEntered() {
if (!Core::System::GetInstance().IsPoweredOn())
return;
// Todo: Handle all cores
for (int i = 0; i < core_registers->childCount(); ++i)
core_registers->child(i)->setText(
1, QStringLiteral("0x%1").arg(Core::CPU().GetReg(i), 8, 16, QLatin1Char('0')));
1, QStringLiteral("0x%1").arg(Core::GetCore(0).GetReg(i), 8, 16, QLatin1Char('0')));
for (int i = 0; i < vfp_registers->childCount(); ++i)
vfp_registers->child(i)->setText(
1, QStringLiteral("0x%1").arg(Core::CPU().GetVFPReg(i), 8, 16, QLatin1Char('0')));
1, QStringLiteral("0x%1").arg(Core::GetCore(0).GetVFPReg(i), 8, 16, QLatin1Char('0')));
UpdateCPSRValues();
UpdateVFPSystemRegisterValues();
@ -127,7 +128,8 @@ void RegistersWidget::CreateCPSRChildren() {
}
void RegistersWidget::UpdateCPSRValues() {
const u32 cpsr_val = Core::CPU().GetCPSR();
// Todo: Handle all cores
const u32 cpsr_val = Core::GetCore(0).GetCPSR();
cpsr->setText(1, QStringLiteral("0x%1").arg(cpsr_val, 8, 16, QLatin1Char('0')));
cpsr->child(0)->setText(
@ -191,10 +193,11 @@ void RegistersWidget::CreateVFPSystemRegisterChildren() {
}
void RegistersWidget::UpdateVFPSystemRegisterValues() {
const u32 fpscr_val = Core::CPU().GetVFPSystemReg(VFP_FPSCR);
const u32 fpexc_val = Core::CPU().GetVFPSystemReg(VFP_FPEXC);
const u32 fpinst_val = Core::CPU().GetVFPSystemReg(VFP_FPINST);
const u32 fpinst2_val = Core::CPU().GetVFPSystemReg(VFP_FPINST2);
// Todo: handle all cores
const u32 fpscr_val = Core::GetCore(0).GetVFPSystemReg(VFP_FPSCR);
const u32 fpexc_val = Core::GetCore(0).GetVFPSystemReg(VFP_FPEXC);
const u32 fpinst_val = Core::GetCore(0).GetVFPSystemReg(VFP_FPINST);
const u32 fpinst2_val = Core::GetCore(0).GetVFPSystemReg(VFP_FPINST2);
QTreeWidgetItem* const fpscr = vfp_system_registers->child(0);
fpscr->setText(1, QStringLiteral("0x%1").arg(fpscr_val, 8, 16, QLatin1Char('0')));

View file

@ -12,6 +12,7 @@
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/timer.h"
#include "core/hle/kernel/wait_object.h"
#include "core/settings.h"
WaitTreeItem::~WaitTreeItem() = default;
@ -51,12 +52,16 @@ std::size_t WaitTreeItem::Row() const {
}
std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() {
const auto& threads = Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList();
u32 num_cores = Core::GetNumCores();
std::vector<std::unique_ptr<WaitTreeThread>> item_list;
item_list.reserve(threads.size());
for (std::size_t i = 0; i < threads.size(); ++i) {
item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i]));
item_list.back()->row = i;
for (u32 i = 0; i < num_cores; ++i) {
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList();
item_list.reserve(item_list.size() + threads.size());
for (std::size_t i = 0; i < threads.size(); ++i) {
item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i]));
item_list.back()->row = i;
}
}
return item_list;
}

View file

@ -468,6 +468,8 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
QAction* open_texture_dump_location = context_menu.addAction(tr("Open Texture Dump Location"));
QAction* open_texture_load_location =
context_menu.addAction(tr("Open Custom Texture Location"));
QAction* open_mods_location = context_menu.addAction(tr("Open Mods Location"));
QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
const bool is_application =
@ -497,6 +499,8 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
open_texture_dump_location->setVisible(is_application);
open_texture_load_location->setVisible(is_application);
open_mods_location->setVisible(is_application);
dump_romfs->setVisible(is_application);
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end());
@ -526,6 +530,15 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
emit OpenFolderRequested(program_id, GameListOpenTarget::TEXTURE_LOAD);
}
});
connect(open_mods_location, &QAction::triggered, [this, program_id] {
if (FileUtil::CreateFullPath(fmt::format("{}mods/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
program_id))) {
emit OpenFolderRequested(program_id, GameListOpenTarget::MODS);
}
});
connect(dump_romfs, &QAction::triggered,
[this, path, program_id] { emit DumpRomFSRequested(path, program_id); });
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
});

View file

@ -35,7 +35,8 @@ enum class GameListOpenTarget {
APPLICATION = 2,
UPDATE_DATA = 3,
TEXTURE_DUMP = 4,
TEXTURE_LOAD = 5
TEXTURE_LOAD = 5,
MODS = 6,
};
class GameList : public QWidget {
@ -81,6 +82,7 @@ signals:
void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
void NavigateToGamedbEntryRequested(u64 program_id,
const CompatibilityList& compatibility_list);
void DumpRomFSRequested(QString game_path, u64 program_id);
void OpenDirectory(const QString& directory);
void AddDirectory();
void ShowList(bool show);

View file

@ -597,6 +597,7 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
&GMainWindow::OnGameListNavigateToGamedbEntry);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory);
connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
&GMainWindow::OnGameListAddDirectory);
@ -1231,6 +1232,11 @@ void GMainWindow::OnGameListOpenFolder(u64 data_id, GameListOpenTarget target) {
path = fmt::format("{}textures/{:016X}/",
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), data_id);
break;
case GameListOpenTarget::MODS:
open_target = "Mods";
path = fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
data_id);
break;
default:
LOG_ERROR(Frontend, "Unexpected target {}", static_cast<int>(target));
return;
@ -1262,6 +1268,46 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
QDesktopServices::openUrl(QUrl(QStringLiteral("https://citra-emu.org/game/") + directory));
}
void GMainWindow::OnGameListDumpRomFS(QString game_path, u64 program_id) {
auto* dialog = new QProgressDialog(tr("Dumping..."), tr("Cancel"), 0, 0, this);
dialog->setWindowModality(Qt::WindowModal);
dialog->setWindowFlags(dialog->windowFlags() &
~(Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint));
dialog->setCancelButton(nullptr);
dialog->setMinimumDuration(0);
dialog->setValue(0);
const auto base_path = fmt::format(
"{}romfs/{:016X}", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
const auto update_path =
fmt::format("{}romfs/{:016X}", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
program_id | 0x0004000e00000000);
using FutureWatcher = QFutureWatcher<std::pair<Loader::ResultStatus, Loader::ResultStatus>>;
auto* future_watcher = new FutureWatcher(this);
connect(future_watcher, &FutureWatcher::finished,
[this, program_id, dialog, base_path, update_path, future_watcher] {
dialog->hide();
const auto& [base, update] = future_watcher->result();
if (base != Loader::ResultStatus::Success) {
QMessageBox::critical(
this, tr("Citra"),
tr("Could not dump base RomFS.\nRefer to the log for details."));
return;
}
QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(base_path)));
if (update == Loader::ResultStatus::Success) {
QDesktopServices::openUrl(
QUrl::fromLocalFile(QString::fromStdString(update_path)));
}
});
auto future = QtConcurrent::run([game_path, base_path, update_path] {
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(game_path.toStdString());
return std::make_pair(loader->DumpRomFS(base_path), loader->DumpUpdateRomFS(update_path));
});
future_watcher->setFuture(future);
}
void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
QString path;
if (directory == QStringLiteral("INSTALLED")) {

View file

@ -176,6 +176,7 @@ private slots:
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
void OnGameListDumpRomFS(QString game_path, u64 program_id);
void OnGameListOpenDirectory(const QString& directory);
void OnGameListAddDirectory();
void OnGameListShowList(bool show);

View file

@ -72,6 +72,8 @@ add_library(core STATIC
file_sys/delay_generator.h
file_sys/ivfc_archive.cpp
file_sys/ivfc_archive.h
file_sys/layered_fs.cpp
file_sys/layered_fs.h
file_sys/ncch_container.cpp
file_sys/ncch_container.h
file_sys/patch.cpp
@ -469,9 +471,17 @@ create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt open_source_archives Boost::serialization)
if (ENABLE_WEB_SERVICE)
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service)
get_directory_property(OPENSSL_LIBS
DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl
DEFINITION OPENSSL_LIBS)
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(core PRIVATE web_service ${OPENSSL_LIBS} httplib lurlparser)
if (ANDROID)
target_link_libraries(core PRIVATE ifaddrs)
endif()
endif()
if (ARCHITECTURE_x86_64)

View file

@ -10,6 +10,7 @@
#include "common/common_types.h"
#include "core/arm/skyeye_common/arm_regformat.h"
#include "core/arm/skyeye_common/vfp/asm_vfp.h"
#include "core/core_timing.h"
namespace Memory {
struct PageTable;
@ -18,6 +19,8 @@ struct PageTable;
/// Generic ARM11 CPU interface
class ARM_Interface : NonCopyable {
public:
explicit ARM_Interface(u32 id, std::shared_ptr<Core::Timing::Timer> timer)
: timer(timer), id(id){};
virtual ~ARM_Interface() {}
class ThreadContext {
@ -222,11 +225,26 @@ public:
virtual void PurgeState() = 0;
std::shared_ptr<Core::Timing::Timer> GetTimer() {
return timer;
}
u32 GetID() const {
return id;
}
protected:
std::shared_ptr<Core::Timing::Timer> timer;
private:
u32 id;
friend class boost::serialization::access;
template <class Archive>
void save(Archive& ar, const unsigned int file_version) const {
ar << timer;
ar << id;
auto page_table = GetPageTable();
ar << page_table;
for (auto i = 0; i < 15; i++) {
@ -254,6 +272,8 @@ private:
template <class Archive>
void load(Archive& ar, const unsigned int file_version) {
PurgeState();
ar >> timer;
ar >> id;
std::shared_ptr<Memory::PageTable> page_table = nullptr;
ar >> page_table;
SetPageTable(page_table);

View file

@ -72,8 +72,7 @@ private:
class DynarmicUserCallbacks final : public Dynarmic::A32::UserCallbacks {
public:
explicit DynarmicUserCallbacks(ARM_Dynarmic& parent)
: parent(parent), timing(parent.system.CoreTiming()), svc_context(parent.system),
memory(parent.memory) {}
: parent(parent), svc_context(parent.system), memory(parent.memory) {}
~DynarmicUserCallbacks() = default;
std::uint8_t MemoryRead8(VAddr vaddr) override {
@ -137,7 +136,7 @@ public:
parent.jit->HaltExecution();
parent.SetPC(pc);
Kernel::Thread* thread =
parent.system.Kernel().GetThreadManager().GetCurrentThread();
parent.system.Kernel().GetCurrentThreadManager().GetCurrentThread();
parent.SaveContext(thread->context);
GDBStub::Break();
GDBStub::SendTrap(thread, 5);
@ -150,22 +149,23 @@ public:
}
void AddTicks(std::uint64_t ticks) override {
timing.AddTicks(ticks);
parent.GetTimer()->AddTicks(ticks);
}
std::uint64_t GetTicksRemaining() override {
s64 ticks = timing.GetDowncount();
s64 ticks = parent.GetTimer()->GetDowncount();
return static_cast<u64>(ticks <= 0 ? 0 : ticks);
}
ARM_Dynarmic& parent;
Core::Timing& timing;
Kernel::SVCContext svc_context;
Memory::MemorySystem& memory;
};
ARM_Dynarmic::ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory,
PrivilegeMode initial_mode)
: system(*system), memory(memory), cb(std::make_unique<DynarmicUserCallbacks>(*this)) {
PrivilegeMode initial_mode, u32 id,
std::shared_ptr<Core::Timing::Timer> timer)
: ARM_Interface(id, timer), system(*system), memory(memory),
cb(std::make_unique<DynarmicUserCallbacks>(*this)) {
interpreter_state = std::make_shared<ARMul_State>(system, memory, initial_mode);
SetPageTable(memory.GetCurrentPageTable());
}

View file

@ -24,7 +24,8 @@ class DynarmicUserCallbacks;
class ARM_Dynarmic final : public ARM_Interface {
public:
ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, PrivilegeMode initial_mode);
ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, PrivilegeMode initial_mode,
u32 id, std::shared_ptr<Core::Timing::Timer> timer);
~ARM_Dynarmic() override;
void Run() override;

View file

@ -69,8 +69,9 @@ private:
};
ARM_DynCom::ARM_DynCom(Core::System* system, Memory::MemorySystem& memory,
PrivilegeMode initial_mode)
: system(system) {
PrivilegeMode initial_mode, u32 id,
std::shared_ptr<Core::Timing::Timer> timer)
: ARM_Interface(id, timer), system(system) {
state = std::make_unique<ARMul_State>(system, memory, initial_mode);
}
@ -78,7 +79,7 @@ ARM_DynCom::~ARM_DynCom() {}
void ARM_DynCom::Run() {
DEBUG_ASSERT(system != nullptr);
ExecuteInstructions(std::max<s64>(system->CoreTiming().GetDowncount(), 0));
ExecuteInstructions(std::max<s64>(timer->GetDowncount(), 0));
}
void ARM_DynCom::Step() {
@ -156,7 +157,7 @@ void ARM_DynCom::ExecuteInstructions(u64 num_instructions) {
state->NumInstrsToExecute = num_instructions;
unsigned ticks_executed = InterpreterMainLoop(state.get());
if (system != nullptr) {
system->CoreTiming().AddTicks(ticks_executed);
timer->AddTicks(ticks_executed);
}
state->ServeBreak();
}

View file

@ -21,7 +21,8 @@ class MemorySystem;
class ARM_DynCom final : public ARM_Interface {
public:
explicit ARM_DynCom(Core::System* system, Memory::MemorySystem& memory,
PrivilegeMode initial_mode);
PrivilegeMode initial_mode, u32 id,
std::shared_ptr<Core::Timing::Timer> timer);
~ARM_DynCom() override;
void Run() override;

View file

@ -3865,7 +3865,7 @@ SWI_INST : {
if (inst_base->cond == ConditionCode::AL || CondPassed(cpu, inst_base->cond)) {
DEBUG_ASSERT(cpu->system != nullptr);
swi_inst* const inst_cream = (swi_inst*)inst_base->component;
cpu->system->CoreTiming().AddTicks(num_instrs);
cpu->system->GetRunningCore().GetTimer()->AddTicks(num_instrs);
cpu->NumInstrsToExecute =
num_instrs >= cpu->NumInstrsToExecute ? 0 : cpu->NumInstrsToExecute - num_instrs;
num_instrs = 0;

View file

@ -607,8 +607,8 @@ void ARMul_State::ServeBreak() {
}
DEBUG_ASSERT(system != nullptr);
Kernel::Thread* thread = system->Kernel().GetThreadManager().GetCurrentThread();
system->CPU().SaveContext(thread->context);
Kernel::Thread* thread = system->Kernel().GetCurrentThreadManager().GetCurrentThread();
system->GetRunningCore().SaveContext(thread->context);
if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
last_bkpt_hit = false;

View file

@ -35,7 +35,7 @@ static inline std::enable_if_t<std::is_integral_v<T>> WriteOp(const GatewayCheat
Core::System& system) {
u32 addr = line.address + state.offset;
write_func(addr, static_cast<T>(line.value));
system.CPU().InvalidateCacheRange(addr, sizeof(T));
system.InvalidateCacheRange(addr, sizeof(T));
}
template <typename T, typename ReadFunction, typename CompareFunc>
@ -105,7 +105,7 @@ static inline std::enable_if_t<std::is_integral_v<T>> IncrementiveWriteOp(
Core::System& system) {
u32 addr = line.value + state.offset;
write_func(addr, static_cast<T>(state.reg));
system.CPU().InvalidateCacheRange(addr, sizeof(T));
system.InvalidateCacheRange(addr, sizeof(T));
state.offset += sizeof(T);
}
@ -143,7 +143,8 @@ static inline void PatchOp(const GatewayCheat::CheatLine& line, State& state, Co
}
u32 num_bytes = line.value;
u32 addr = line.address + state.offset;
system.CPU().InvalidateCacheRange(addr, num_bytes);
system.InvalidateCacheRange(addr, num_bytes);
bool first = true;
u32 bit_offset = 0;
if (num_bytes > 0)

View file

@ -5,6 +5,7 @@
#include <fstream>
#include <memory>
#include <stdexcept>
#include <utility>
#include <boost/serialization/array.hpp>
#include "audio_core/dsp_interface.h"
@ -65,7 +66,8 @@ System::~System() = default;
System::ResultStatus System::RunLoop(bool tight_loop) {
status = ResultStatus::Success;
if (!cpu_core) {
if (std::any_of(cpu_cores.begin(), cpu_cores.end(),
[](std::shared_ptr<ARM_Interface> ptr) { return ptr == nullptr; })) {
return ResultStatus::ErrorNotInitialized;
}
@ -83,22 +85,73 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
}
}
// If we don't have a currently active thread then don't execute instructions,
// instead advance to the next event and try to yield to the next thread
if (kernel->GetThreadManager().GetCurrentThread() == nullptr) {
LOG_TRACE(Core_ARM11, "Idling");
timing->Idle();
timing->Advance();
PrepareReschedule();
} else {
timing->Advance();
if (tight_loop) {
cpu_core->Run();
} else {
cpu_core->Step();
// All cores should have executed the same amount of ticks. If this is not the case an event was
// scheduled with a cycles_into_future smaller then the current downcount.
// So we have to get those cores to the same global time first
u64 global_ticks = timing->GetGlobalTicks();
s64 max_delay = 0;
std::shared_ptr<ARM_Interface> current_core_to_execute = nullptr;
for (auto& cpu_core : cpu_cores) {
if (cpu_core->GetTimer()->GetTicks() < global_ticks) {
s64 delay = global_ticks - cpu_core->GetTimer()->GetTicks();
cpu_core->GetTimer()->Advance(delay);
if (max_delay < delay) {
max_delay = delay;
current_core_to_execute = cpu_core;
}
}
}
if (max_delay > 0) {
LOG_TRACE(Core_ARM11, "Core {} running (delayed) for {} ticks",
current_core_to_execute->GetID(),
current_core_to_execute->GetTimer()->GetDowncount());
running_core = current_core_to_execute.get();
kernel->SetRunningCPU(current_core_to_execute);
if (kernel->GetCurrentThreadManager().GetCurrentThread() == nullptr) {
LOG_TRACE(Core_ARM11, "Core {} idling", current_core_to_execute->GetID());
current_core_to_execute->GetTimer()->Idle();
PrepareReschedule();
} else {
if (tight_loop) {
current_core_to_execute->Run();
} else {
current_core_to_execute->Step();
}
}
} else {
// Now all cores are at the same global time. So we will run them one after the other
// with a max slice that is the minimum of all max slices of all cores
// TODO: Make special check for idle since we can easily revert the time of idle cores
s64 max_slice = Timing::MAX_SLICE_LENGTH;
for (const auto& cpu_core : cpu_cores) {
max_slice = std::min(max_slice, cpu_core->GetTimer()->GetMaxSliceLength());
}
for (auto& cpu_core : cpu_cores) {
cpu_core->GetTimer()->Advance(max_slice);
}
for (auto& cpu_core : cpu_cores) {
LOG_TRACE(Core_ARM11, "Core {} running for {} ticks", cpu_core->GetID(),
cpu_core->GetTimer()->GetDowncount());
running_core = cpu_core.get();
kernel->SetRunningCPU(cpu_core);
// If we don't have a currently active thread then don't execute instructions,
// instead advance to the next event and try to yield to the next thread
if (kernel->GetCurrentThreadManager().GetCurrentThread() == nullptr) {
LOG_TRACE(Core_ARM11, "Core {} idling", cpu_core->GetID());
cpu_core->GetTimer()->Idle();
PrepareReschedule();
} else {
if (tight_loop) {
cpu_core->Run();
} else {
cpu_core->Step();
}
}
}
timing->AddToGlobalTicks(max_slice);
}
if (GDBStub::IsServerEnabled()) {
GDBStub::SetCpuStepFlag(false);
}
@ -183,7 +236,9 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
}
ASSERT(system_mode.first);
ResultStatus init_result{Init(emu_window, *system_mode.first)};
auto n3ds_mode = app_loader->LoadKernelN3dsMode();
ASSERT(n3ds_mode.first);
ResultStatus init_result{Init(emu_window, *system_mode.first, *n3ds_mode.first)};
if (init_result != ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
static_cast<u32>(init_result));
@ -235,7 +290,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
}
void System::PrepareReschedule() {
cpu_core->PrepareReschedule();
running_core->PrepareReschedule();
reschedule_pending = true;
}
@ -249,31 +304,50 @@ void System::Reschedule() {
}
reschedule_pending = false;
kernel->GetThreadManager().Reschedule();
for (const auto& core : cpu_cores) {
LOG_TRACE(Core_ARM11, "Reschedule core {}", core->GetID());
kernel->GetThreadManager(core->GetID()).Reschedule();
}
}
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mode) {
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode) {
LOG_DEBUG(HW_Memory, "initialized OK");
std::size_t num_cores = 2;
if (Settings::values.is_new_3ds) {
num_cores = 4;
}
memory = std::make_unique<Memory::MemorySystem>();
timing = std::make_unique<Timing>();
timing = std::make_unique<Timing>(num_cores);
kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing,
[this] { PrepareReschedule(); }, system_mode);
kernel = std::make_unique<Kernel::KernelSystem>(
*memory, *timing, [this] { PrepareReschedule(); }, system_mode, num_cores, n3ds_mode);
if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
cpu_core = std::make_shared<ARM_Dynarmic>(this, *memory, USER32MODE);
for (std::size_t i = 0; i < num_cores; ++i) {
cpu_cores.push_back(
std::make_shared<ARM_Dynarmic>(this, *memory, USER32MODE, i, timing->GetTimer(i)));
}
#else
cpu_core = std::make_shared<ARM_DynCom>(this, *memory, USER32MODE);
for (std::size_t i = 0; i < num_cores; ++i) {
cpu_cores.push_back(
std::make_shared<ARM_DynCom>(this, *memory, USER32MODE, i, timing->GetTimer(i)));
}
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
#endif
} else {
cpu_core = std::make_shared<ARM_DynCom>(this, *memory, USER32MODE);
for (std::size_t i = 0; i < num_cores; ++i) {
cpu_cores.push_back(
std::make_shared<ARM_DynCom>(this, *memory, USER32MODE, i, timing->GetTimer(i)));
}
}
running_core = cpu_cores[0].get();
kernel->SetCPU(cpu_core);
kernel->SetCPUs(cpu_cores);
kernel->SetRunningCPU(cpu_cores[0]);
if (Settings::values.enable_dsp_lle) {
dsp_core = std::make_unique<AudioCore::DspLle>(*memory,
@ -296,7 +370,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
HW::Init(*memory);
Service::Init(*this);
GDBStub::Init();
GDBStub::DeferStart();
VideoCore::ResultStatus result = VideoCore::Init(emu_window, *memory);
if (result != VideoCore::ResultStatus::Success) {
@ -318,6 +392,8 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
LOG_DEBUG(Core, "Initialized OK");
initalized = true;
return ResultStatus::Success;
}
@ -421,9 +497,10 @@ void System::Shutdown() {
perf_stats.reset();
rpc_server.reset();
cheat_engine.reset();
archive_manager.reset();
service_manager.reset();
dsp_core.reset();
cpu_core.reset();
cpu_cores.clear();
kernel.reset();
timing.reset();
app_loader.reset();
@ -452,11 +529,18 @@ void System::Reset() {
template <class Archive>
void System::serialize(Archive& ar, const unsigned int file_version) {
u32 num_cores;
ar& num_cores;
if (num_cores != this->GetNumCores()) {
throw std::runtime_error("Wrong N3DS mode");
}
// flush on save, don't flush on load
bool should_flush = !Archive::is_loading::value;
Memory::RasterizerClearAll(should_flush);
ar&* timing.get();
ar&* cpu_core.get();
for (int i = 0; i < num_cores; i++) {
ar&* cpu_cores[i].get();
}
ar&* service_manager.get();
ar& GPU::g_regs;
ar& LCD::g_regs;

View file

@ -148,7 +148,10 @@ public:
* @returns True if the emulated system is powered on, otherwise false.
*/
bool IsPoweredOn() const {
return cpu_core != nullptr;
return cpu_cores.size() > 0 &&
std::all_of(cpu_cores.begin(), cpu_cores.end(),
[](std::shared_ptr<ARM_Interface> ptr) { return ptr != nullptr; });
;
}
/**
@ -168,8 +171,29 @@ public:
* Gets a reference to the emulated CPU.
* @returns A reference to the emulated CPU.
*/
ARM_Interface& CPU() {
return *cpu_core;
ARM_Interface& GetRunningCore() {
return *running_core;
};
/**
* Gets a reference to the emulated CPU.
* @param core_id The id of the core requested.
* @returns A reference to the emulated CPU.
*/
ARM_Interface& GetCore(u32 core_id) {
return *cpu_cores[core_id];
};
u32 GetNumCores() const {
return static_cast<u32>(cpu_cores.size());
}
void InvalidateCacheRange(u32 start_address, std::size_t length) {
for (const auto& cpu : cpu_cores) {
cpu->InvalidateCacheRange(start_address, length);
}
}
/**
@ -291,7 +315,7 @@ private:
* @param system_mode The system mode.
* @return ResultStatus code, indicating if the operation succeeded.
*/
ResultStatus Init(Frontend::EmuWindow& emu_window, u32 system_mode);
ResultStatus Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode);
/// Reschedule the core emulation
void Reschedule();
@ -300,7 +324,8 @@ private:
std::unique_ptr<Loader::AppLoader> app_loader;
/// ARM11 CPU core
std::shared_ptr<ARM_Interface> cpu_core;
std::vector<std::shared_ptr<ARM_Interface>> cpu_cores;
ARM_Interface* running_core = nullptr;
/// DSP core
std::unique_ptr<AudioCore::DspInterface> dsp_core;
@ -342,6 +367,8 @@ private:
private:
static System s_instance;
bool initalized = false;
ResultStatus status = ResultStatus::Success;
std::string status_details = "";
/// Saved variables for reset
@ -358,8 +385,16 @@ private:
void serialize(Archive& ar, const unsigned int file_version);
};
inline ARM_Interface& CPU() {
return System::GetInstance().CPU();
inline ARM_Interface& GetRunningCore() {
return System::GetInstance().GetRunningCore();
}
inline ARM_Interface& GetCore(u32 core_id) {
return System::GetInstance().GetCore(core_id);
}
inline u32 GetNumCores() {
return System::GetInstance().GetNumCores();
}
inline AudioCore::DspInterface& DSP() {

View file

@ -14,14 +14,22 @@ namespace Core {
Timing* Timing::deserializing = nullptr;
// Sort by time, unless the times are the same, in which case sort by the order added to the queue
bool Timing::Event::operator>(const Event& right) const {
bool Timing::Event::operator>(const Timing::Event& right) const {
return std::tie(time, fifo_order) > std::tie(right.time, right.fifo_order);
}
bool Timing::Event::operator<(const Event& right) const {
bool Timing::Event::operator<(const Timing::Event& right) const {
return std::tie(time, fifo_order) < std::tie(right.time, right.fifo_order);
}
Timing::Timing(std::size_t num_cores) {
timers.resize(num_cores);
for (std::size_t i = 0; i < num_cores; ++i) {
timers[i] = std::make_shared<Timer>();
}
current_timer = timers[0];
}
TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback callback) {
// check for existing type with same name.
// we want event type names to remain unique so that we can use them for serialization.
@ -34,73 +42,102 @@ TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback ca
return event_type;
}
Timing::~Timing() {
void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, u64 userdata,
std::size_t core_id) {
ASSERT(event_type != nullptr);
std::shared_ptr<Timing::Timer> timer;
if (core_id == std::numeric_limits<std::size_t>::max()) {
timer = current_timer;
} else {
ASSERT(core_id < timers.size());
timer = timers.at(core_id);
}
s64 timeout = timer->GetTicks() + cycles_into_future;
if (current_timer == timer) {
// If this event needs to be scheduled before the next advance(), force one early
if (!timer->is_timer_sane)
timer->ForceExceptionCheck(cycles_into_future);
timer->event_queue.emplace_back(
Event{timeout, timer->event_fifo_id++, userdata, event_type});
std::push_heap(timer->event_queue.begin(), timer->event_queue.end(), std::greater<>());
} else {
timer->ts_queue.Push(Event{static_cast<s64>(timer->GetTicks() + cycles_into_future), 0,
userdata, event_type});
}
}
void Timing::UnscheduleEvent(const TimingEventType* event_type, u64 userdata) {
for (auto timer : timers) {
auto itr = std::remove_if(
timer->event_queue.begin(), timer->event_queue.end(),
[&](const Event& e) { return e.type == event_type && e.userdata == userdata; });
// Removing random items breaks the invariant so we have to re-establish it.
if (itr != timer->event_queue.end()) {
timer->event_queue.erase(itr, timer->event_queue.end());
std::make_heap(timer->event_queue.begin(), timer->event_queue.end(), std::greater<>());
}
}
// TODO:remove events from ts_queue
}
void Timing::RemoveEvent(const TimingEventType* event_type) {
for (auto timer : timers) {
auto itr = std::remove_if(timer->event_queue.begin(), timer->event_queue.end(),
[&](const Event& e) { return e.type == event_type; });
// Removing random items breaks the invariant so we have to re-establish it.
if (itr != timer->event_queue.end()) {
timer->event_queue.erase(itr, timer->event_queue.end());
std::make_heap(timer->event_queue.begin(), timer->event_queue.end(), std::greater<>());
}
}
// TODO:remove events from ts_queue
}
void Timing::SetCurrentTimer(std::size_t core_id) {
current_timer = timers[core_id];
}
s64 Timing::GetTicks() const {
return current_timer->GetTicks();
}
s64 Timing::GetGlobalTicks() const {
return global_timer;
}
std::chrono::microseconds Timing::GetGlobalTimeUs() const {
return std::chrono::microseconds{GetTicks() * 1000000 / BASE_CLOCK_RATE_ARM11};
}
std::shared_ptr<Timing::Timer> Timing::GetTimer(std::size_t cpu_id) {
return timers[cpu_id];
}
Timing::Timer::~Timer() {
MoveEvents();
}
u64 Timing::GetTicks() const {
u64 ticks = static_cast<u64>(global_timer);
if (!is_global_timer_sane) {
u64 Timing::Timer::GetTicks() const {
u64 ticks = static_cast<u64>(executed_ticks);
if (!is_timer_sane) {
ticks += slice_length - downcount;
}
return ticks;
}
void Timing::AddTicks(u64 ticks) {
void Timing::Timer::AddTicks(u64 ticks) {
downcount -= ticks;
}
u64 Timing::GetIdleTicks() const {
u64 Timing::Timer::GetIdleTicks() const {
return static_cast<u64>(idled_cycles);
}
void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type,
u64 userdata) {
ASSERT(event_type != nullptr);
s64 timeout = GetTicks() + cycles_into_future;
// If this event needs to be scheduled before the next advance(), force one early
if (!is_global_timer_sane)
ForceExceptionCheck(cycles_into_future);
event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
void Timing::ScheduleEventThreadsafe(s64 cycles_into_future, const TimingEventType* event_type,
u64 userdata) {
ts_queue.Push(Event{global_timer + cycles_into_future, 0, userdata, event_type});
}
void Timing::UnscheduleEvent(const TimingEventType* event_type, u64 userdata) {
auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
return e.type == event_type && e.userdata == userdata;
});
// Removing random items breaks the invariant so we have to re-establish it.
if (itr != event_queue.end()) {
event_queue.erase(itr, event_queue.end());
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
}
void Timing::RemoveEvent(const TimingEventType* event_type) {
auto itr = std::remove_if(event_queue.begin(), event_queue.end(),
[&](const Event& e) { return e.type == event_type; });
// Removing random items breaks the invariant so we have to re-establish it.
if (itr != event_queue.end()) {
event_queue.erase(itr, event_queue.end());
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
}
void Timing::RemoveNormalAndThreadsafeEvent(const TimingEventType* event_type) {
MoveEvents();
RemoveEvent(event_type);
}
void Timing::ForceExceptionCheck(s64 cycles) {
void Timing::Timer::ForceExceptionCheck(s64 cycles) {
cycles = std::max<s64>(0, cycles);
if (downcount > cycles) {
slice_length -= downcount - cycles;
@ -108,7 +145,7 @@ void Timing::ForceExceptionCheck(s64 cycles) {
}
}
void Timing::MoveEvents() {
void Timing::Timer::MoveEvents() {
for (Event ev; ts_queue.Pop(ev);) {
ev.fifo_order = event_fifo_id++;
event_queue.emplace_back(std::move(ev));
@ -116,50 +153,54 @@ void Timing::MoveEvents() {
}
}
void Timing::Advance() {
s64 Timing::Timer::GetMaxSliceLength() const {
auto next_event = std::find_if(event_queue.begin(), event_queue.end(),
[&](const Event& e) { return e.time - executed_ticks > 0; });
if (next_event != event_queue.end()) {
return next_event->time - executed_ticks;
}
return MAX_SLICE_LENGTH;
}
void Timing::Timer::Advance(s64 max_slice_length) {
MoveEvents();
s64 cycles_executed = slice_length - downcount;
global_timer += cycles_executed;
slice_length = MAX_SLICE_LENGTH;
idled_cycles = 0;
executed_ticks += cycles_executed;
slice_length = max_slice_length;
is_global_timer_sane = true;
is_timer_sane = true;
while (!event_queue.empty() && event_queue.front().time <= global_timer) {
while (!event_queue.empty() && event_queue.front().time <= executed_ticks) {
Event evt = std::move(event_queue.front());
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
event_queue.pop_back();
if (event_types.find(*evt.type->name) == event_types.end()) {
LOG_ERROR(Core, "Unknown queued event {}", *evt.type->name);
} else if (evt.type->callback == nullptr) {
if (evt.type->callback == nullptr) {
LOG_ERROR(Core, "Event '{}' has no callback", *evt.type->name);
}
if (evt.type->callback != nullptr) {
evt.type->callback(evt.userdata, global_timer - evt.time);
evt.type->callback(evt.userdata, executed_ticks - evt.time);
}
}
is_global_timer_sane = false;
is_timer_sane = false;
// Still events left (scheduled in the future)
if (!event_queue.empty()) {
slice_length = static_cast<int>(
std::min<s64>(event_queue.front().time - global_timer, MAX_SLICE_LENGTH));
std::min<s64>(event_queue.front().time - executed_ticks, max_slice_length));
}
downcount = slice_length;
}
void Timing::Idle() {
void Timing::Timer::Idle() {
idled_cycles += downcount;
downcount = 0;
}
std::chrono::microseconds Timing::GetGlobalTimeUs() const {
return std::chrono::microseconds{GetTicks() * 1000000 / BASE_CLOCK_RATE_ARM11};
}
s64 Timing::GetDowncount() const {
s64 Timing::Timer::GetDowncount() const {
return downcount;
}

View file

@ -135,65 +135,10 @@ struct TimingEventType {
};
class Timing {
public:
~Timing();
/**
* This should only be called from the emu thread, if you are calling it any other thread, you
* are doing something evil
*/
u64 GetTicks() const;
u64 GetIdleTicks() const;
void AddTicks(u64 ticks);
/**
* Returns the event_type identifier. if name is not unique, it will assert.
*/
TimingEventType* RegisterEvent(const std::string& name, TimedCallback callback);
/**
* After the first Advance, the slice lengths and the downcount will be reduced whenever an
* event is scheduled earlier than the current values. Scheduling from a callback will not
* update the downcount until the Advance() completes.
*/
void ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, u64 userdata = 0);
/**
* This is to be called when outside of hle threads, such as the graphics thread, wants to
* schedule things to be executed on the main thread.
* Not that this doesn't change slice_length and thus events scheduled by this might be called
* with a delay of up to MAX_SLICE_LENGTH
*/
void ScheduleEventThreadsafe(s64 cycles_into_future, const TimingEventType* event_type,
u64 userdata);
void UnscheduleEvent(const TimingEventType* event_type, u64 userdata);
/// We only permit one event of each type in the queue at a time.
void RemoveEvent(const TimingEventType* event_type);
void RemoveNormalAndThreadsafeEvent(const TimingEventType* event_type);
/** Advance must be called at the beginning of dispatcher loops, not the end. Advance() ends
* the previous timing slice and begins the next one, you must Advance from the previous
* slice to the current one before executing any cycles. CoreTiming starts in slice -1 so an
* Advance() is required to initialize the slice length before the first cycle of emulated
* instructions is executed.
*/
void Advance();
void MoveEvents();
/// Pretend that the main CPU has executed enough cycles to reach the next event.
void Idle();
void ForceExceptionCheck(s64 cycles);
std::chrono::microseconds GetGlobalTimeUs() const;
s64 GetDowncount() const;
private:
static Timing* deserializing;
public:
struct Event {
s64 time;
u64 fifo_order;
@ -229,48 +174,116 @@ private:
static constexpr int MAX_SLICE_LENGTH = 20000;
class Timer {
public:
~Timer();
s64 GetMaxSliceLength() const;
void Advance(s64 max_slice_length = MAX_SLICE_LENGTH);
void Idle();
u64 GetTicks() const;
u64 GetIdleTicks() const;
void AddTicks(u64 ticks);
s64 GetDowncount() const;
void ForceExceptionCheck(s64 cycles);
void MoveEvents();
private:
friend class Timing;
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
// erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't
// accomodated by the standard adaptor class.
std::vector<Event> event_queue;
u64 event_fifo_id = 0;
// the queue for storing the events from other threads threadsafe until they will be added
// to the event_queue by the emu thread
Common::MPSCQueue<Event> ts_queue;
// Are we in a function that has been called from Advance()
// If events are sheduled from a function that gets called from Advance(),
// don't change slice_length and downcount.
// The time between CoreTiming being intialized and the first call to Advance() is
// considered the slice boundary between slice -1 and slice 0. Dispatcher loops must call
// Advance() before executing the first cycle of each slice to prepare the slice length and
// downcount for that slice.
bool is_timer_sane = true;
s64 slice_length = MAX_SLICE_LENGTH;
s64 downcount = MAX_SLICE_LENGTH;
s64 executed_ticks = 0;
u64 idled_cycles;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
MoveEvents();
ar& slice_length;
ar& downcount;
ar& event_queue;
ar& event_fifo_id;
ar& idled_cycles;
}
friend class boost::serialization::access;
};
explicit Timing(std::size_t num_cores);
~Timing(){};
/**
* Returns the event_type identifier. if name is not unique, it will assert.
*/
TimingEventType* RegisterEvent(const std::string& name, TimedCallback callback);
void ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, u64 userdata = 0,
std::size_t core_id = std::numeric_limits<std::size_t>::max());
void UnscheduleEvent(const TimingEventType* event_type, u64 userdata);
/// We only permit one event of each type in the queue at a time.
void RemoveEvent(const TimingEventType* event_type);
void SetCurrentTimer(std::size_t core_id);
s64 GetTicks() const;
s64 GetGlobalTicks() const;
void AddToGlobalTicks(s64 ticks) {
global_timer += ticks;
}
std::chrono::microseconds GetGlobalTimeUs() const;
std::shared_ptr<Timer> GetTimer(std::size_t cpu_id);
private:
s64 global_timer = 0;
s64 slice_length = MAX_SLICE_LENGTH;
s64 downcount = MAX_SLICE_LENGTH;
// unordered_map stores each element separately as a linked list node so pointers to
// elements remain stable regardless of rehashes/resizing.
std::unordered_map<std::string, TimingEventType> event_types;
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
// erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't
// accomodated by the standard adaptor class.
std::vector<Event> event_queue;
u64 event_fifo_id = 0;
// the queue for storing the events from other threads threadsafe until they will be added
// to the event_queue by the emu thread
Common::MPSCQueue<Event> ts_queue;
s64 idled_cycles = 0;
// Are we in a function that has been called from Advance()
// If events are sheduled from a function that gets called from Advance(),
// don't change slice_length and downcount.
// The time between CoreTiming being intialized and the first call to Advance() is considered
// the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before
// executing the first cycle of each slice to prepare the slice length and downcount for
// that slice.
bool is_global_timer_sane = true;
std::vector<std::shared_ptr<Timer>> timers;
std::shared_ptr<Timer> current_timer;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
// event_types set during initialization of other things
deserializing = this;
MoveEvents();
ar& global_timer;
ar& slice_length;
ar& downcount;
ar& event_queue;
ar& event_fifo_id;
ar& idled_cycles;
ar& timers;
ar& current_timer;
deserializing = nullptr;
}
friend class boost::serialization::access;
};
} // namespace Core

View file

@ -0,0 +1,604 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstring>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/string_util.h"
#include "common/swap.h"
#include "core/file_sys/layered_fs.h"
#include "core/file_sys/patch.h"
namespace FileSys {
struct FileRelocationInfo {
int type; // 0 - none, 1 - replaced / created, 2 - patched, 3 - removed
u64 original_offset; // Type 0. Offset is absolute
std::string replace_file_path; // Type 1
std::vector<u8> patched_file; // Type 2
u64 size; // Relocated file size
};
struct LayeredFS::File {
std::string name;
std::string path;
FileRelocationInfo relocation{};
Directory* parent;
};
struct DirectoryMetadata {
u32_le parent_directory_offset;
u32_le next_sibling_offset;
u32_le first_child_directory_offset;
u32_le first_file_offset;
u32_le hash_bucket_next;
u32_le name_length;
// Followed by a name of name length (aligned up to 4)
};
static_assert(sizeof(DirectoryMetadata) == 0x18, "Size of DirectoryMetadata is not correct");
struct FileMetadata {
u32_le parent_directory_offset;
u32_le next_sibling_offset;
u64_le file_data_offset;
u64_le file_data_length;
u32_le hash_bucket_next;
u32_le name_length;
// Followed by a name of name length (aligned up to 4)
};
static_assert(sizeof(FileMetadata) == 0x20, "Size of FileMetadata is not correct");
LayeredFS::LayeredFS(std::shared_ptr<RomFSReader> romfs_, std::string patch_path_,
std::string patch_ext_path_, bool load_relocations)
: romfs(std::move(romfs_)), patch_path(std::move(patch_path_)),
patch_ext_path(std::move(patch_ext_path_)) {
romfs->ReadFile(0, sizeof(header), reinterpret_cast<u8*>(&header));
ASSERT_MSG(header.header_length == sizeof(header), "Header size is incorrect");
// TODO: is root always the first directory in table?
root.parent = &root;
LoadDirectory(root, 0);
if (load_relocations) {
LoadRelocations();
LoadExtRelocations();
}
RebuildMetadata();
}
LayeredFS::~LayeredFS() = default;
void LayeredFS::LoadDirectory(Directory& current, u32 offset) {
DirectoryMetadata metadata;
romfs->ReadFile(header.directory_metadata_table.offset + offset, sizeof(metadata),
reinterpret_cast<u8*>(&metadata));
current.name = ReadName(header.directory_metadata_table.offset + offset + sizeof(metadata),
metadata.name_length);
current.path = current.parent->path + current.name + DIR_SEP;
directory_path_map.emplace(current.path, &current);
if (metadata.first_file_offset != 0xFFFFFFFF) {
LoadFile(current, metadata.first_file_offset);
}
if (metadata.first_child_directory_offset != 0xFFFFFFFF) {
auto child = std::make_unique<Directory>();
auto& directory = *child;
directory.parent = &current;
current.directories.emplace_back(std::move(child));
LoadDirectory(directory, metadata.first_child_directory_offset);
}
if (metadata.next_sibling_offset != 0xFFFFFFFF) {
auto sibling = std::make_unique<Directory>();
auto& directory = *sibling;
directory.parent = current.parent;
current.parent->directories.emplace_back(std::move(sibling));
LoadDirectory(directory, metadata.next_sibling_offset);
}
}
void LayeredFS::LoadFile(Directory& parent, u32 offset) {
FileMetadata metadata;
romfs->ReadFile(header.file_metadata_table.offset + offset, sizeof(metadata),
reinterpret_cast<u8*>(&metadata));
auto file = std::make_unique<File>();
file->name = ReadName(header.file_metadata_table.offset + offset + sizeof(metadata),
metadata.name_length);
file->path = parent.path + file->name;
file->relocation.original_offset = header.file_data_offset + metadata.file_data_offset;
file->relocation.size = metadata.file_data_length;
file->parent = &parent;
file_path_map.emplace(file->path, file.get());
parent.files.emplace_back(std::move(file));
if (metadata.next_sibling_offset != 0xFFFFFFFF) {
LoadFile(parent, metadata.next_sibling_offset);
}
}
std::string LayeredFS::ReadName(u32 offset, u32 name_length) {
std::vector<u16_le> buffer(name_length / sizeof(u16_le));
romfs->ReadFile(offset, name_length, reinterpret_cast<u8*>(buffer.data()));
std::u16string name(buffer.size(), 0);
std::transform(buffer.begin(), buffer.end(), name.begin(), [](u16_le character) {
return static_cast<char16_t>(static_cast<u16>(character));
});
return Common::UTF16ToUTF8(name);
}
void LayeredFS::LoadRelocations() {
if (!FileUtil::Exists(patch_path)) {
return;
}
const FileUtil::DirectoryEntryCallable callback = [this,
&callback](u64* /*num_entries_out*/,
const std::string& directory,
const std::string& virtual_name) {
auto* parent = directory_path_map.at(directory.substr(patch_path.size() - 1));
if (FileUtil::IsDirectory(directory + virtual_name + DIR_SEP)) {
const auto path = (directory + virtual_name + DIR_SEP).substr(patch_path.size() - 1);
if (!directory_path_map.count(path)) { // Add this directory
auto directory = std::make_unique<Directory>();
directory->name = virtual_name;
directory->path = path;
directory->parent = parent;
directory_path_map.emplace(path, directory.get());
parent->directories.emplace_back(std::move(directory));
LOG_INFO(Service_FS, "LayeredFS created directory {}", path);
}
return FileUtil::ForeachDirectoryEntry(nullptr, directory + virtual_name + DIR_SEP,
callback);
}
const auto path = (directory + virtual_name).substr(patch_path.size() - 1);
if (!file_path_map.count(path)) { // Newly created file
auto file = std::make_unique<File>();
file->name = virtual_name;
file->path = path;
file->parent = parent;
file_path_map.emplace(path, file.get());
parent->files.emplace_back(std::move(file));
LOG_INFO(Service_FS, "LayeredFS created file {}", path);
}
auto* file = file_path_map.at(path);
file->relocation.type = 1;
file->relocation.replace_file_path = directory + virtual_name;
file->relocation.size = FileUtil::GetSize(directory + virtual_name);
LOG_INFO(Service_FS, "LayeredFS replacement file in use for {}", path);
return true;
};
FileUtil::ForeachDirectoryEntry(nullptr, patch_path, callback);
}
void LayeredFS::LoadExtRelocations() {
if (!FileUtil::Exists(patch_ext_path)) {
return;
}
if (patch_ext_path.back() == '/' || patch_ext_path.back() == '\\') {
// ScanDirectoryTree expects a path without trailing '/'
patch_ext_path.erase(patch_ext_path.size() - 1, 1);
}
FileUtil::FSTEntry result;
FileUtil::ScanDirectoryTree(patch_ext_path, result, 256);
for (const auto& entry : result.children) {
if (FileUtil::IsDirectory(entry.physicalName)) {
continue;
}
const auto path = entry.physicalName.substr(patch_ext_path.size());
if (path.size() >= 5 && path.substr(path.size() - 5) == ".stub") {
// Remove the corresponding file if exists
const auto file_path = path.substr(0, path.size() - 5);
if (file_path_map.count(file_path)) {
auto& file = *file_path_map[file_path];
file.relocation.type = 3;
file.relocation.size = 0;
file_path_map.erase(file_path);
LOG_INFO(Service_FS, "LayeredFS removed file {}", file_path);
} else {
LOG_WARNING(Service_FS, "LayeredFS file for stub {} not found", path);
}
} else if (path.size() >= 4) {
const auto extension = path.substr(path.size() - 4);
if (extension != ".ips" && extension != ".bps") {
LOG_WARNING(Service_FS, "LayeredFS unknown ext file {}", path);
}
const auto file_path = path.substr(0, path.size() - 4);
if (!file_path_map.count(file_path)) {
LOG_WARNING(Service_FS, "LayeredFS original file for patch {} not found", path);
continue;
}
FileUtil::IOFile patch_file(entry.physicalName, "rb");
if (!patch_file) {
LOG_ERROR(Service_FS, "LayeredFS Could not open file {}", entry.physicalName);
continue;
}
const auto size = patch_file.GetSize();
std::vector<u8> patch(size);
if (patch_file.ReadBytes(patch.data(), size) != size) {
LOG_ERROR(Service_FS, "LayeredFS Could not read file {}", entry.physicalName);
continue;
}
auto& file = *file_path_map[file_path];
std::vector<u8> buffer(file.relocation.size); // Original size
romfs->ReadFile(file.relocation.original_offset, buffer.size(), buffer.data());
bool ret = false;
if (extension == ".ips") {
ret = Patch::ApplyIpsPatch(patch, buffer);
} else {
ret = Patch::ApplyBpsPatch(patch, buffer);
}
if (ret) {
LOG_INFO(Service_FS, "LayeredFS patched file {}", file_path);
file.relocation.type = 2;
file.relocation.size = buffer.size();
file.relocation.patched_file = std::move(buffer);
} else {
LOG_ERROR(Service_FS, "LayeredFS failed to patch file {}", file_path);
}
} else {
LOG_WARNING(Service_FS, "LayeredFS unknown ext file {}", path);
}
}
}
std::size_t GetNameSize(const std::string& name) {
std::u16string u16name = Common::UTF8ToUTF16(name);
return Common::AlignUp(u16name.size() * 2, 4);
}
void LayeredFS::PrepareBuildDirectory(Directory& current) {
directory_metadata_offset_map.emplace(&current, current_directory_offset);
directory_list.emplace_back(&current);
current_directory_offset += sizeof(DirectoryMetadata) + GetNameSize(current.name);
}
void LayeredFS::PrepareBuildFile(File& current) {
if (current.relocation.type == 3) { // Deleted files are not counted
return;
}
file_metadata_offset_map.emplace(&current, current_file_offset);
file_list.emplace_back(&current);
current_file_offset += sizeof(FileMetadata) + GetNameSize(current.name);
}
void LayeredFS::PrepareBuild(Directory& current) {
for (const auto& child : current.files) {
PrepareBuildFile(*child);
}
for (const auto& child : current.directories) {
PrepareBuildDirectory(*child);
}
for (const auto& child : current.directories) {
PrepareBuild(*child);
}
}
// Implementation from 3dbrew
u32 CalcHash(const std::string& name, u32 parent_offset) {
u32 hash = parent_offset ^ 123456789;
std::u16string u16name = Common::UTF8ToUTF16(name);
for (char16_t c : u16name) {
hash = (hash >> 5) | (hash << 27);
hash ^= static_cast<u16>(c);
}
return hash;
}
std::size_t WriteName(u8* dest, std::u16string name) {
const auto buffer_size = Common::AlignUp(name.size() * 2, 4);
std::vector<u16_le> buffer(buffer_size / 2);
std::transform(name.begin(), name.end(), buffer.begin(), [](char16_t character) {
return static_cast<u16_le>(static_cast<u16>(character));
});
std::memcpy(dest, buffer.data(), buffer_size);
return buffer_size;
}
void LayeredFS::BuildDirectories() {
directory_metadata_table.resize(current_directory_offset, 0xFF);
std::size_t written = 0;
for (const auto& directory : directory_list) {
DirectoryMetadata metadata;
std::memset(&metadata, 0xFF, sizeof(metadata));
metadata.parent_directory_offset = directory_metadata_offset_map.at(directory->parent);
if (directory->parent != directory) {
bool flag = false;
for (const auto& sibling : directory->parent->directories) {
if (flag) {
metadata.next_sibling_offset = directory_metadata_offset_map.at(sibling.get());
break;
} else if (sibling.get() == directory) {
flag = true;
}
}
}
if (!directory->directories.empty()) {
metadata.first_child_directory_offset =
directory_metadata_offset_map.at(directory->directories.front().get());
}
if (!directory->files.empty()) {
metadata.first_file_offset =
file_metadata_offset_map.at(directory->files.front().get());
}
const auto bucket = CalcHash(directory->name, metadata.parent_directory_offset) %
directory_hash_table.size();
metadata.hash_bucket_next = directory_hash_table[bucket];
directory_hash_table[bucket] = directory_metadata_offset_map.at(directory);
// Write metadata and name
std::u16string u16name = Common::UTF8ToUTF16(directory->name);
metadata.name_length = u16name.size() * 2;
std::memcpy(directory_metadata_table.data() + written, &metadata, sizeof(metadata));
written += sizeof(metadata);
written += WriteName(directory_metadata_table.data() + written, u16name);
}
ASSERT_MSG(written == directory_metadata_table.size(),
"Calculated size for directory metadata table is wrong");
}
void LayeredFS::BuildFiles() {
file_metadata_table.resize(current_file_offset, 0xFF);
std::size_t written = 0;
for (const auto& file : file_list) {
FileMetadata metadata;
std::memset(&metadata, 0xFF, sizeof(metadata));
metadata.parent_directory_offset = directory_metadata_offset_map.at(file->parent);
bool flag = false;
for (const auto& sibling : file->parent->files) {
if (sibling->relocation.type == 3) { // removed file
continue;
}
if (flag) {
metadata.next_sibling_offset = file_metadata_offset_map.at(sibling.get());
break;
} else if (sibling.get() == file) {
flag = true;
}
}
metadata.file_data_offset = current_data_offset;
metadata.file_data_length = file->relocation.size;
current_data_offset += Common::AlignUp(metadata.file_data_length, 16);
if (metadata.file_data_length != 0) {
data_offset_map.emplace(metadata.file_data_offset, file);
}
const auto bucket =
CalcHash(file->name, metadata.parent_directory_offset) % file_hash_table.size();
metadata.hash_bucket_next = file_hash_table[bucket];
file_hash_table[bucket] = file_metadata_offset_map.at(file);
// Write metadata and name
std::u16string u16name = Common::UTF8ToUTF16(file->name);
metadata.name_length = u16name.size() * 2;
std::memcpy(file_metadata_table.data() + written, &metadata, sizeof(metadata));
written += sizeof(metadata);
written += WriteName(file_metadata_table.data() + written, u16name);
}
ASSERT_MSG(written == file_metadata_table.size(),
"Calculated size for file metadata table is wrong");
}
// Implementation from 3dbrew
std::size_t GetHashTableSize(std::size_t entry_count) {
if (entry_count < 3) {
return 3;
} else if (entry_count < 19) {
return entry_count | 1;
} else {
std::size_t count = entry_count;
while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 ||
count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
count++;
}
return count;
}
}
void LayeredFS::RebuildMetadata() {
PrepareBuildDirectory(root);
PrepareBuild(root);
directory_hash_table.resize(GetHashTableSize(directory_list.size()), 0xFFFFFFFF);
file_hash_table.resize(GetHashTableSize(file_list.size()), 0xFFFFFFFF);
BuildDirectories();
BuildFiles();
// Create header
RomFSHeader header;
header.header_length = sizeof(header);
header.directory_hash_table = {
/*offset*/ sizeof(header),
/*length*/ static_cast<u32_le>(directory_hash_table.size() * sizeof(u32_le))};
header.directory_metadata_table = {
/*offset*/
header.directory_hash_table.offset + header.directory_hash_table.length,
/*length*/ static_cast<u32_le>(directory_metadata_table.size())};
header.file_hash_table = {
/*offset*/
header.directory_metadata_table.offset + header.directory_metadata_table.length,
/*length*/ static_cast<u32_le>(file_hash_table.size() * sizeof(u32_le))};
header.file_metadata_table = {/*offset*/ header.file_hash_table.offset +
header.file_hash_table.length,
/*length*/ static_cast<u32_le>(file_metadata_table.size())};
header.file_data_offset =
Common::AlignUp(header.file_metadata_table.offset + header.file_metadata_table.length, 16);
// Write hash table and metadata table
metadata.resize(header.file_data_offset);
std::memcpy(metadata.data(), &header, header.header_length);
std::memcpy(metadata.data() + header.directory_hash_table.offset, directory_hash_table.data(),
header.directory_hash_table.length);
std::memcpy(metadata.data() + header.directory_metadata_table.offset,
directory_metadata_table.data(), header.directory_metadata_table.length);
std::memcpy(metadata.data() + header.file_hash_table.offset, file_hash_table.data(),
header.file_hash_table.length);
std::memcpy(metadata.data() + header.file_metadata_table.offset, file_metadata_table.data(),
header.file_metadata_table.length);
}
std::size_t LayeredFS::GetSize() const {
return metadata.size() + current_data_offset;
}
std::size_t LayeredFS::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
ASSERT_MSG(offset + length <= GetSize(), "Out of bound");
std::size_t read_size = 0;
if (offset < metadata.size()) {
// First read the metadata
const auto to_read = std::min(metadata.size() - offset, length);
std::memcpy(buffer, metadata.data() + offset, to_read);
read_size += to_read;
offset = 0;
} else {
offset -= metadata.size();
}
// Read files
auto current = (--data_offset_map.upper_bound(offset));
while (read_size < length) {
const auto relative_offset = offset - current->first;
std::size_t to_read{};
if (current->second->relocation.size > relative_offset) {
to_read = std::min<std::size_t>(current->second->relocation.size - relative_offset,
length - read_size);
}
const auto alignment =
std::min<std::size_t>(Common::AlignUp(current->second->relocation.size, 16) -
relative_offset,
length - read_size) -
to_read;
// Read the file in different ways depending on relocation type
auto& relocation = current->second->relocation;
if (relocation.type == 0) { // none
romfs->ReadFile(relocation.original_offset + relative_offset, to_read,
buffer + read_size);
} else if (relocation.type == 1) { // replace
FileUtil::IOFile replace_file(relocation.replace_file_path, "rb");
if (replace_file) {
replace_file.Seek(relative_offset, SEEK_SET);
replace_file.ReadBytes(buffer + read_size, to_read);
} else {
LOG_ERROR(Service_FS, "Could not open replacement file for {}",
current->second->path);
}
} else if (relocation.type == 2) { // patch
std::memcpy(buffer + read_size, relocation.patched_file.data() + relative_offset,
to_read);
} else {
UNREACHABLE();
}
std::memset(buffer + read_size + to_read, 0, alignment);
read_size += to_read + alignment;
offset += to_read + alignment;
current++;
}
return read_size;
}
bool LayeredFS::ExtractDirectory(Directory& current, const std::string& target_path) {
if (!FileUtil::CreateFullPath(target_path + current.path)) {
LOG_ERROR(Service_FS, "Could not create path {}", target_path + current.path);
return false;
}
constexpr std::size_t BufferSize = 0x10000;
std::array<u8, BufferSize> buffer;
for (const auto& file : current.files) {
// Extract file
const auto path = target_path + file->path;
LOG_INFO(Service_FS, "Extracting {} to {}", file->path, path);
FileUtil::IOFile target_file(path, "wb");
if (!target_file) {
LOG_ERROR(Service_FS, "Could not open file {}", path);
return false;
}
std::size_t written = 0;
while (written < file->relocation.size) {
const auto to_read =
std::min<std::size_t>(buffer.size(), file->relocation.size - written);
if (romfs->ReadFile(file->relocation.original_offset + written, to_read,
buffer.data()) != to_read) {
LOG_ERROR(Service_FS, "Could not read from RomFS");
return false;
}
if (target_file.WriteBytes(buffer.data(), to_read) != to_read) {
LOG_ERROR(Service_FS, "Could not write to file {}", path);
return false;
}
written += to_read;
}
}
for (const auto& directory : current.directories) {
if (!ExtractDirectory(*directory, target_path)) {
return false;
}
}
return true;
}
bool LayeredFS::DumpRomFS(const std::string& target_path) {
std::string path = target_path;
if (path.back() == '/' || path.back() == '\\') {
path.erase(path.size() - 1, 1);
}
return ExtractDirectory(root, path);
}
} // namespace FileSys

View file

@ -0,0 +1,123 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/romfs_reader.h"
namespace FileSys {
struct RomFSHeader {
struct Descriptor {
u32_le offset;
u32_le length;
};
u32_le header_length;
Descriptor directory_hash_table;
Descriptor directory_metadata_table;
Descriptor file_hash_table;
Descriptor file_metadata_table;
u32_le file_data_offset;
};
static_assert(sizeof(RomFSHeader) == 0x28, "Size of RomFSHeader is not correct");
/**
* LayeredFS implementation. This basically adds a layer to another RomFSReader.
*
* patch_path: Path for RomFS replacements. Files present in this path replace or create
* corresponding files in RomFS.
* patch_ext_path: Path for RomFS extensions. Files present in this path:
* - When with an extension of ".stub", remove the corresponding file in the RomFS.
* - When with an extension of ".ips" or ".bps", patch the file in the RomFS.
*/
class LayeredFS : public RomFSReader {
public:
explicit LayeredFS(std::shared_ptr<RomFSReader> romfs, std::string patch_path,
std::string patch_ext_path, bool load_relocations = true);
~LayeredFS() override;
std::size_t GetSize() const override;
std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override;
bool DumpRomFS(const std::string& target_path);
private:
struct File;
struct Directory {
std::string name;
std::string path; // with trailing '/'
std::vector<std::unique_ptr<File>> files;
std::vector<std::unique_ptr<Directory>> directories;
Directory* parent;
};
std::string ReadName(u32 offset, u32 name_length);
// Loads the current directory, then its siblings, and then its children.
void LoadDirectory(Directory& current, u32 offset);
// Load the file at offset, and then its siblings.
void LoadFile(Directory& parent, u32 offset);
// Load replace/create relocations
void LoadRelocations();
// Load patch/remove relocations
void LoadExtRelocations();
// Calculate the offset of a single directory add it to the map and list of directories
void PrepareBuildDirectory(Directory& current);
// Calculate the offset of a single file add it to the map and list of files
void PrepareBuildFile(File& current);
// Recursively generate a sequence of files and directories and their offsets for all
// children of current. (The current directory itself is not handled.)
void PrepareBuild(Directory& current);
void BuildDirectories();
void BuildFiles();
// Recursively extract a directory and all its contents to target_path
// target_path should be without trailing '/'.
bool ExtractDirectory(Directory& current, const std::string& target_path);
void RebuildMetadata();
std::shared_ptr<RomFSReader> romfs;
std::string patch_path;
std::string patch_ext_path;
RomFSHeader header;
Directory root;
std::unordered_map<std::string, File*> file_path_map;
std::unordered_map<std::string, Directory*> directory_path_map;
std::map<u64, File*> data_offset_map; // assigned data offset -> file
std::vector<u8> metadata; // Includes header, hash table and metadata
// Used for rebuilding header
std::vector<u32_le> directory_hash_table;
std::vector<u32_le> file_hash_table;
std::unordered_map<Directory*, u32>
directory_metadata_offset_map; // directory -> metadata offset
std::vector<Directory*> directory_list; // sequence of directories to be written to metadata
u64 current_directory_offset{}; // current directory metadata offset
std::vector<u8> directory_metadata_table; // rebuilt directory metadata table
std::unordered_map<File*, u32> file_metadata_offset_map; // file -> metadata offset
std::vector<File*> file_list; // sequence of files to be written to metadata
u64 current_file_offset{}; // current file metadata offset
std::vector<u8> file_metadata_table; // rebuilt file metadata table
u64 current_data_offset{}; // current assigned data offset
};
} // namespace FileSys

View file

@ -11,6 +11,7 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/layered_fs.h"
#include "core/file_sys/ncch_container.h"
#include "core/file_sys/patch.h"
#include "core/file_sys/seed_db.h"
@ -25,6 +26,14 @@ namespace FileSys {
static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs
static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes)
u64 GetModId(u64 program_id) {
constexpr u64 UPDATE_MASK = 0x0000000e'00000000;
if ((program_id & 0x000000ff'00000000) == UPDATE_MASK) { // Apply the mods to updates
return program_id & ~UPDATE_MASK;
}
return program_id;
}
/**
* Get the decompressed size of an LZSS compressed ExeFS file
* @param buffer Buffer of compressed file
@ -303,8 +312,22 @@ Loader::ResultStatus NCCHContainer::Load() {
}
}
FileUtil::IOFile exheader_override_file{filepath + ".exheader", "rb"};
const bool has_exheader_override = read_exheader(exheader_override_file);
const auto mods_path =
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
GetModId(ncch_header.program_id));
const std::array<std::string, 2> exheader_override_paths{{
mods_path + "exheader.bin",
filepath + ".exheader",
}};
bool has_exheader_override = false;
for (const auto& path : exheader_override_paths) {
FileUtil::IOFile exheader_override_file{path, "rb"};
if (read_exheader(exheader_override_file)) {
has_exheader_override = true;
break;
}
}
if (has_exheader_override) {
if (exheader_header.system_info.jump_id !=
exheader_header.arm11_system_local_caps.program_id) {
@ -512,7 +535,15 @@ Loader::ResultStatus NCCHContainer::ApplyCodePatch(std::vector<u8>& code) const
std::string path;
bool (*patch_fn)(const std::vector<u8>& patch, std::vector<u8>& code);
};
const std::array<PatchLocation, 2> patch_paths{{
const auto mods_path =
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
GetModId(ncch_header.program_id));
const std::array<PatchLocation, 6> patch_paths{{
{mods_path + "exefs/code.ips", Patch::ApplyIpsPatch},
{mods_path + "exefs/code.bps", Patch::ApplyBpsPatch},
{mods_path + "code.ips", Patch::ApplyIpsPatch},
{mods_path + "code.bps", Patch::ApplyBpsPatch},
{filepath + ".exefsdir/code.ips", Patch::ApplyIpsPatch},
{filepath + ".exefsdir/code.bps", Patch::ApplyBpsPatch},
}};
@ -551,23 +582,34 @@ Loader::ResultStatus NCCHContainer::LoadOverrideExeFSSection(const char* name,
else
return Loader::ResultStatus::Error;
std::string section_override = filepath + ".exefsdir/" + override_name;
FileUtil::IOFile section_file(section_override, "rb");
const auto mods_path =
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
GetModId(ncch_header.program_id));
const std::array<std::string, 3> override_paths{{
mods_path + "exefs/" + override_name,
mods_path + override_name,
filepath + ".exefsdir/" + override_name,
}};
if (section_file.IsOpen()) {
auto section_size = section_file.GetSize();
buffer.resize(section_size);
for (const auto& path : override_paths) {
FileUtil::IOFile section_file(path, "rb");
section_file.Seek(0, SEEK_SET);
if (section_file.ReadBytes(&buffer[0], section_size) == section_size) {
LOG_WARNING(Service_FS, "File {} overriding built-in ExeFS file", section_override);
return Loader::ResultStatus::Success;
if (section_file.IsOpen()) {
auto section_size = section_file.GetSize();
buffer.resize(section_size);
section_file.Seek(0, SEEK_SET);
if (section_file.ReadBytes(&buffer[0], section_size) == section_size) {
LOG_WARNING(Service_FS, "File {} overriding built-in ExeFS file", path);
return Loader::ResultStatus::Success;
}
}
}
return Loader::ResultStatus::ErrorNotUsed;
}
Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romfs_file) {
Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romfs_file,
bool use_layered_fs) {
Loader::ResultStatus result = Load();
if (result != Loader::ResultStatus::Success)
return result;
@ -597,14 +639,43 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
if (!romfs_file_inner.IsOpen())
return Loader::ResultStatus::Error;
std::shared_ptr<RomFSReader> direct_romfs;
if (is_encrypted) {
romfs_file = std::make_shared<RomFSReader>(std::move(romfs_file_inner), romfs_offset,
romfs_size, secondary_key, romfs_ctr, 0x1000);
direct_romfs =
std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), romfs_offset,
romfs_size, secondary_key, romfs_ctr, 0x1000);
} else {
romfs_file =
std::make_shared<RomFSReader>(std::move(romfs_file_inner), romfs_offset, romfs_size);
direct_romfs = std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner),
romfs_offset, romfs_size);
}
const auto path =
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
GetModId(ncch_header.program_id));
if (use_layered_fs &&
(FileUtil::Exists(path + "romfs/") || FileUtil::Exists(path + "romfs_ext/"))) {
romfs_file = std::make_shared<LayeredFS>(std::move(direct_romfs), path + "romfs/",
path + "romfs_ext/");
} else {
romfs_file = std::move(direct_romfs);
}
return Loader::ResultStatus::Success;
}
Loader::ResultStatus NCCHContainer::DumpRomFS(const std::string& target_path) {
std::shared_ptr<RomFSReader> direct_romfs;
Loader::ResultStatus result = ReadRomFS(direct_romfs, false);
if (result != Loader::ResultStatus::Success)
return result;
std::shared_ptr<LayeredFS> layered_fs =
std::make_shared<LayeredFS>(std::move(direct_romfs), "", "", false);
if (!layered_fs->DumpRomFS(target_path)) {
return Loader::ResultStatus::Error;
}
return Loader::ResultStatus::Success;
}
@ -614,9 +685,10 @@ Loader::ResultStatus NCCHContainer::ReadOverrideRomFS(std::shared_ptr<RomFSReade
if (FileUtil::Exists(split_filepath)) {
FileUtil::IOFile romfs_file_inner(split_filepath, "rb");
if (romfs_file_inner.IsOpen()) {
LOG_WARNING(Service_FS, "File {} overriding built-in RomFS", split_filepath);
romfs_file = std::make_shared<RomFSReader>(std::move(romfs_file_inner), 0,
romfs_file_inner.GetSize());
LOG_WARNING(Service_FS, "File {} overriding built-in RomFS; LayeredFS not enabled",
split_filepath);
romfs_file = std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), 0,
romfs_file_inner.GetSize());
return Loader::ResultStatus::Success;
}
}

View file

@ -149,7 +149,8 @@ struct ExHeader_StorageInfo {
struct ExHeader_ARM11_SystemLocalCaps {
u64_le program_id;
u32_le core_version;
u8 reserved_flags[2];
u8 reserved_flag;
u8 n3ds_mode;
union {
u8 flags0;
BitField<0, 2, u8> ideal_processor;
@ -247,7 +248,15 @@ public:
* @param size The size of the romfs
* @return ResultStatus result of function
*/
Loader::ResultStatus ReadRomFS(std::shared_ptr<RomFSReader>& romfs_file);
Loader::ResultStatus ReadRomFS(std::shared_ptr<RomFSReader>& romfs_file,
bool use_layered_fs = true);
/**
* Dump the RomFS of the NCCH container to the user folder.
* @param target_path target path to dump to
* @return ResultStatus result of function.
*/
Loader::ResultStatus DumpRomFS(const std::string& target_path);
/**
* Get the override RomFS of the NCCH container

View file

@ -5,7 +5,7 @@
namespace FileSys {
std::size_t RomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
if (length == 0)
return 0; // Crypto++ does not like zero size buffer
file.Seek(file_offset + offset, SEEK_SET);

View file

@ -7,23 +7,39 @@
namespace FileSys {
/**
* Interface for reading RomFS data.
*/
class RomFSReader {
public:
RomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size)
virtual ~RomFSReader() = default;
virtual std::size_t GetSize() const = 0;
virtual std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) = 0;
};
/**
* A RomFS reader that directly reads the RomFS file.
*/
class DirectRomFSReader : public RomFSReader {
public:
DirectRomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size)
: is_encrypted(false), file(std::move(file)), file_offset(file_offset),
data_size(data_size) {}
RomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size,
const std::array<u8, 16>& key, const std::array<u8, 16>& ctr,
std::size_t crypto_offset)
DirectRomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size,
const std::array<u8, 16>& key, const std::array<u8, 16>& ctr,
std::size_t crypto_offset)
: is_encrypted(true), file(std::move(file)), key(key), ctr(ctr), file_offset(file_offset),
crypto_offset(crypto_offset), data_size(data_size) {}
std::size_t GetSize() const {
~DirectRomFSReader() override = default;
std::size_t GetSize() const override {
return data_size;
}
std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer);
std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override;
private:
bool is_encrypted;
@ -34,7 +50,7 @@ private:
u64 crypto_offset;
u64 data_size;
RomFSReader() = default;
DirectRomFSReader() = default;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {

View file

@ -121,6 +121,7 @@ constexpr char target_xml[] =
)";
int gdbserver_socket = -1;
bool defer_start = false;
u8 command_buffer[GDB_BUFFER_SIZE];
u32 command_length;
@ -160,10 +161,14 @@ BreakpointMap breakpoints_write;
} // Anonymous namespace
static Kernel::Thread* FindThreadById(int id) {
const auto& threads = Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList();
for (auto& thread : threads) {
if (thread->GetThreadId() == static_cast<u32>(id)) {
return thread.get();
u32 num_cores = Core::GetNumCores();
for (u32 i = 0; i < num_cores; ++i) {
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList();
for (auto& thread : threads) {
if (thread->GetThreadId() == static_cast<u32>(id)) {
return thread.get();
}
}
}
return nullptr;
@ -414,7 +419,10 @@ static void RemoveBreakpoint(BreakpointType type, VAddr addr) {
Core::System::GetInstance().Memory().WriteBlock(
*Core::System::GetInstance().Kernel().GetCurrentProcess(), bp->second.addr,
bp->second.inst.data(), bp->second.inst.size());
Core::CPU().ClearInstructionCache();
u32 num_cores = Core::GetNumCores();
for (u32 i = 0; i < num_cores; ++i) {
Core::GetCore(i).ClearInstructionCache();
}
}
p.erase(addr);
}
@ -540,10 +548,13 @@ static void HandleQuery() {
SendReply(target_xml);
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
std::string val = "m";
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList();
for (const auto& thread : threads) {
val += fmt::format("{:x},", thread->GetThreadId());
u32 num_cores = Core::GetNumCores();
for (u32 i = 0; i < num_cores; ++i) {
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList();
for (const auto& thread : threads) {
val += fmt::format("{:x},", thread->GetThreadId());
}
}
val.pop_back();
SendReply(val.c_str());
@ -553,11 +564,14 @@ static void HandleQuery() {
std::string buffer;
buffer += "l<?xml version=\"1.0\"?>";
buffer += "<threads>";
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList();
for (const auto& thread : threads) {
buffer += fmt::format(R"*(<thread id="{:x}" name="Thread {:x}"></thread>)*",
thread->GetThreadId(), thread->GetThreadId());
u32 num_cores = Core::GetNumCores();
for (u32 i = 0; i < num_cores; ++i) {
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList();
for (const auto& thread : threads) {
buffer += fmt::format(R"*(<thread id="{:x}" name="Thread {:x}"></thread>)*",
thread->GetThreadId(), thread->GetThreadId());
}
}
buffer += "</threads>";
SendReply(buffer.c_str());
@ -619,9 +633,9 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
if (full) {
buffer = fmt::format("T{:02x}{:02x}:{:08x};{:02x}:{:08x};{:02x}:{:08x}", latest_signal,
PC_REGISTER, htonl(Core::CPU().GetPC()), SP_REGISTER,
htonl(Core::CPU().GetReg(SP_REGISTER)), LR_REGISTER,
htonl(Core::CPU().GetReg(LR_REGISTER)));
PC_REGISTER, htonl(Core::GetRunningCore().GetPC()), SP_REGISTER,
htonl(Core::GetRunningCore().GetReg(SP_REGISTER)), LR_REGISTER,
htonl(Core::GetRunningCore().GetReg(LR_REGISTER)));
} else {
buffer = fmt::format("T{:02x}", latest_signal);
}
@ -782,7 +796,7 @@ static void WriteRegister() {
return SendReply("E01");
}
Core::CPU().LoadContext(current_thread->context);
Core::GetRunningCore().LoadContext(current_thread->context);
SendReply("OK");
}
@ -812,7 +826,7 @@ static void WriteRegisters() {
}
}
Core::CPU().LoadContext(current_thread->context);
Core::GetRunningCore().LoadContext(current_thread->context);
SendReply("OK");
}
@ -869,7 +883,7 @@ static void WriteMemory() {
GdbHexToMem(data.data(), len_pos + 1, len);
Core::System::GetInstance().Memory().WriteBlock(
*Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, data.data(), len);
Core::CPU().ClearInstructionCache();
Core::GetRunningCore().ClearInstructionCache();
SendReply("OK");
}
@ -883,12 +897,12 @@ void Break(bool is_memory_break) {
static void Step() {
if (command_length > 1) {
RegWrite(PC_REGISTER, GdbHexToInt(command_buffer + 1), current_thread);
Core::CPU().LoadContext(current_thread->context);
Core::GetRunningCore().LoadContext(current_thread->context);
}
step_loop = true;
halt_loop = true;
send_trap = true;
Core::CPU().ClearInstructionCache();
Core::GetRunningCore().ClearInstructionCache();
}
bool IsMemoryBreak() {
@ -904,7 +918,7 @@ static void Continue() {
memory_break = false;
step_loop = false;
halt_loop = false;
Core::CPU().ClearInstructionCache();
Core::GetRunningCore().ClearInstructionCache();
}
/**
@ -930,7 +944,7 @@ static bool CommitBreakpoint(BreakpointType type, VAddr addr, u32 len) {
Core::System::GetInstance().Memory().WriteBlock(
*Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, btrap.data(),
btrap.size());
Core::CPU().ClearInstructionCache();
Core::GetRunningCore().ClearInstructionCache();
}
p.insert({addr, breakpoint});
@ -1030,6 +1044,9 @@ static void RemoveBreakpoint() {
void HandlePacket() {
if (!IsConnected()) {
if (defer_start) {
ToggleServer(true);
}
return;
}
@ -1120,6 +1137,10 @@ void ToggleServer(bool status) {
}
}
void DeferStart() {
defer_start = true;
}
static void Init(u16 port) {
if (!server_enabled) {
// Set the halt loop to false in case the user enabled the gdbstub mid-execution.
@ -1203,6 +1224,7 @@ void Shutdown() {
if (!server_enabled) {
return;
}
defer_start = false;
LOG_INFO(Debug_GDBStub, "Stopping GDB ...");
if (gdbserver_socket != -1) {

View file

@ -42,6 +42,13 @@ void ToggleServer(bool status);
/// Start the gdbstub server.
void Init();
/**
* Defer initialization of the gdbstub to the first packet processing functions.
* This avoids a case where the gdbstub thread is frozen after initialization
* and fails to respond in time to packets.
*/
void DeferStart();
/// Stop gdbstub server.
void Shutdown();

View file

@ -83,7 +83,7 @@ bool HandleTable::IsValid(Handle handle) const {
std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const {
if (handle == CurrentThread) {
return SharedFrom(kernel.GetThreadManager().GetCurrentThread());
return SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread());
} else if (handle == CurrentProcess) {
return kernel.GetCurrentProcess();
}

View file

@ -20,22 +20,30 @@ namespace Kernel {
/// Initialize the kernel
KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
std::function<void()> prepare_reschedule_callback, u32 system_mode)
std::function<void()> prepare_reschedule_callback, u32 system_mode,
u32 num_cores, u8 n3ds_mode)
: memory(memory), timing(timing),
prepare_reschedule_callback(std::move(prepare_reschedule_callback)) {
for (auto i = 0; i < memory_regions.size(); i++) {
memory_regions[i] = std::make_shared<MemoryRegionInfo>();
}
MemoryInit(system_mode);
MemoryInit(system_mode, n3ds_mode);
resource_limits = std::make_unique<ResourceLimitList>(*this);
thread_manager = std::make_unique<ThreadManager>(*this);
for (u32 core_id = 0; core_id < num_cores; ++core_id) {
thread_managers.push_back(std::make_unique<ThreadManager>(*this, core_id));
}
timer_manager = std::make_unique<TimerManager>(timing);
ipc_recorder = std::make_unique<IPCDebugger::Recorder>();
stored_processes.assign(num_cores, nullptr);
next_thread_id = 1;
}
/// Shutdown the kernel
KernelSystem::~KernelSystem() = default;
KernelSystem::~KernelSystem() {
ResetThreadIDs();
};
ResourceLimitList& KernelSystem::ResourceLimit() {
return *resource_limits;
@ -58,6 +66,15 @@ void KernelSystem::SetCurrentProcess(std::shared_ptr<Process> process) {
SetCurrentMemoryPageTable(process->vm_manager.page_table);
}
void KernelSystem::SetCurrentProcessForCPU(std::shared_ptr<Process> process, u32 core_id) {
if (current_cpu->GetID() == core_id) {
current_process = process;
SetCurrentMemoryPageTable(process->vm_manager.page_table);
} else {
stored_processes[core_id] = process;
}
}
void KernelSystem::SetCurrentMemoryPageTable(std::shared_ptr<Memory::PageTable> page_table) {
memory.SetCurrentPageTable(page_table);
if (current_cpu != nullptr) {
@ -65,17 +82,39 @@ void KernelSystem::SetCurrentMemoryPageTable(std::shared_ptr<Memory::PageTable>
}
}
void KernelSystem::SetCPU(std::shared_ptr<ARM_Interface> cpu) {
void KernelSystem::SetCPUs(std::vector<std::shared_ptr<ARM_Interface>> cpus) {
ASSERT(cpus.size() == thread_managers.size());
u32 i = 0;
for (const auto& cpu : cpus) {
thread_managers[i++]->SetCPU(*cpu);
}
}
void KernelSystem::SetRunningCPU(std::shared_ptr<ARM_Interface> cpu) {
if (current_process) {
stored_processes[current_cpu->GetID()] = current_process;
}
current_cpu = cpu;
thread_manager->SetCPU(*cpu);
timing.SetCurrentTimer(cpu->GetID());
if (stored_processes[current_cpu->GetID()]) {
SetCurrentProcess(stored_processes[current_cpu->GetID()]);
}
}
ThreadManager& KernelSystem::GetThreadManager() {
return *thread_manager;
ThreadManager& KernelSystem::GetThreadManager(u32 core_id) {
return *thread_managers[core_id];
}
const ThreadManager& KernelSystem::GetThreadManager() const {
return *thread_manager;
const ThreadManager& KernelSystem::GetThreadManager(u32 core_id) const {
return *thread_managers[core_id];
}
ThreadManager& KernelSystem::GetCurrentThreadManager() {
return *thread_managers[current_cpu->GetID()];
}
const ThreadManager& KernelSystem::GetCurrentThreadManager() const {
return *thread_managers[current_cpu->GetID()];
}
TimerManager& KernelSystem::GetTimerManager() {
@ -106,6 +145,14 @@ void KernelSystem::AddNamedPort(std::string name, std::shared_ptr<ClientPort> po
named_ports.emplace(std::move(name), std::move(port));
}
u32 KernelSystem::NewThreadId() {
return next_thread_id++;
}
void KernelSystem::ResetThreadIDs() {
next_thread_id = 0;
}
template <class Archive>
void KernelSystem::serialize(Archive& ar, const unsigned int file_version) {
ar& memory_regions;
@ -118,9 +165,14 @@ void KernelSystem::serialize(Archive& ar, const unsigned int file_version) {
ar& next_process_id;
ar& process_list;
ar& current_process;
ar&* thread_manager.get();
// NB: core count checked in 'core'
for (auto& thread_manager : thread_managers) {
ar&* thread_manager.get();
}
ar& config_mem_handler;
ar& shared_page_handler;
ar& stored_processes;
ar& next_thread_id;
// Deliberately don't include debugger info to allow debugging through loads
}

View file

@ -88,7 +88,8 @@ enum class MemoryRegion : u16 {
class KernelSystem {
public:
explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
std::function<void()> prepare_reschedule_callback, u32 system_mode);
std::function<void()> prepare_reschedule_callback, u32 system_mode,
u32 num_cores, u8 n3ds_mode);
~KernelSystem();
using PortPair = std::pair<std::shared_ptr<ServerPort>, std::shared_ptr<ClientPort>>;
@ -214,13 +215,19 @@ public:
std::shared_ptr<Process> GetCurrentProcess() const;
void SetCurrentProcess(std::shared_ptr<Process> process);
void SetCurrentProcessForCPU(std::shared_ptr<Process> process, u32 core_id);
void SetCurrentMemoryPageTable(std::shared_ptr<Memory::PageTable> page_table);
void SetCPU(std::shared_ptr<ARM_Interface> cpu);
void SetCPUs(std::vector<std::shared_ptr<ARM_Interface>> cpu);
ThreadManager& GetThreadManager();
const ThreadManager& GetThreadManager() const;
void SetRunningCPU(std::shared_ptr<ARM_Interface> cpu);
ThreadManager& GetThreadManager(u32 core_id);
const ThreadManager& GetThreadManager(u32 core_id) const;
ThreadManager& GetCurrentThreadManager();
const ThreadManager& GetCurrentThreadManager() const;
TimerManager& GetTimerManager();
const TimerManager& GetTimerManager() const;
@ -246,6 +253,10 @@ public:
prepare_reschedule_callback();
}
u32 NewThreadId();
void ResetThreadIDs();
/// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort
std::unordered_map<std::string, std::shared_ptr<ClientPort>> named_ports;
@ -256,7 +267,7 @@ public:
Core::Timing& timing;
private:
void MemoryInit(u32 mem_type);
void MemoryInit(u32 mem_type, u8 n3ds_mode);
std::function<void()> prepare_reschedule_callback;
@ -280,14 +291,17 @@ private:
std::vector<std::shared_ptr<Process>> process_list;
std::shared_ptr<Process> current_process;
std::vector<std::shared_ptr<Process>> stored_processes;
std::unique_ptr<ThreadManager> thread_manager;
std::vector<std::unique_ptr<ThreadManager>> thread_managers;
std::shared_ptr<ConfigMem::Handler> config_mem_handler;
std::shared_ptr<SharedPage::Handler> shared_page_handler;
std::unique_ptr<IPCDebugger::Recorder> ipc_recorder;
u32 next_thread_id;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version);

View file

@ -19,6 +19,7 @@
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/result.h"
#include "core/memory.h"
#include "core/settings.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -40,11 +41,32 @@ static const u32 memory_region_sizes[8][3] = {
{0x0B200000, 0x02E00000, 0x02000000}, // 7
};
void KernelSystem::MemoryInit(u32 mem_type) {
// TODO(yuriks): On the n3DS, all o3DS configurations (<=5) are forced to 6 instead.
ASSERT_MSG(mem_type <= 5, "New 3DS memory configuration aren't supported yet!");
namespace MemoryMode {
enum N3DSMode : u8 {
Mode6 = 1,
Mode7 = 2,
Mode6_2 = 3,
};
}
void KernelSystem::MemoryInit(u32 mem_type, u8 n3ds_mode) {
ASSERT(mem_type != 1);
const bool is_new_3ds = Settings::values.is_new_3ds;
u32 reported_mem_type = mem_type;
if (is_new_3ds) {
if (n3ds_mode == MemoryMode::Mode6 || n3ds_mode == MemoryMode::Mode6_2) {
mem_type = 6;
reported_mem_type = 6;
} else if (n3ds_mode == MemoryMode::Mode7) {
mem_type = 7;
reported_mem_type = 7;
} else {
// On the N3ds, all O3ds configurations (<=5) are forced to 6 instead.
mem_type = 6;
}
}
// The kernel allocation regions (APPLICATION, SYSTEM and BASE) are laid out in sequence, with
// the sizes specified in the memory_region_sizes table.
VAddr base = 0;
@ -55,14 +77,12 @@ void KernelSystem::MemoryInit(u32 mem_type) {
}
// We must've allocated the entire FCRAM by the end
ASSERT(base == Memory::FCRAM_SIZE);
ASSERT(base == (is_new_3ds ? Memory::FCRAM_N3DS_SIZE : Memory::FCRAM_SIZE));
config_mem_handler = std::make_shared<ConfigMem::Handler>();
auto& config_mem = config_mem_handler->GetConfigMem();
config_mem.app_mem_type = mem_type;
// app_mem_malloc does not always match the configured size for memory_region[0]: in case the
// n3DS type override is in effect it reports the size the game expects, not the real one.
config_mem.app_mem_alloc = memory_region_sizes[mem_type][0];
config_mem.app_mem_type = reported_mem_type;
config_mem.app_mem_alloc = memory_region_sizes[reported_mem_type][0];
config_mem.sys_mem_alloc = memory_regions[1]->size;
config_mem.base_mem_alloc = memory_regions[2]->size;

View file

@ -39,7 +39,7 @@ std::shared_ptr<Mutex> KernelSystem::CreateMutex(bool initial_locked, std::strin
// Acquire mutex with current thread if initialized as locked
if (initial_locked)
mutex->Acquire(thread_manager->GetCurrentThread());
mutex->Acquire(thread_managers[current_cpu->GetID()]->GetCurrentThread());
return mutex;
}

View file

@ -70,7 +70,7 @@ Handler::Handler(Core::Timing& timing) : timing(timing) {
using namespace std::placeholders;
update_time_event = timing.RegisterEvent("SharedPage::UpdateTimeCallback",
std::bind(&Handler::UpdateTimeCallback, this, _1, _2));
timing.ScheduleEvent(0, update_time_event);
timing.ScheduleEvent(0, update_time_event, 0, 0);
float slidestate = Settings::values.factor_3d / 100.0f;
shared_page.sliderstate_3d = static_cast<float_le>(slidestate);

View file

@ -280,12 +280,12 @@ void SVC::ExitProcess() {
current_process->status = ProcessStatus::Exited;
// Stop all the process threads that are currently waiting for objects.
auto& thread_list = kernel.GetThreadManager().GetThreadList();
auto& thread_list = kernel.GetCurrentThreadManager().GetThreadList();
for (auto& thread : thread_list) {
if (thread->owner_process != current_process)
continue;
if (thread.get() == kernel.GetThreadManager().GetCurrentThread())
if (thread.get() == kernel.GetCurrentThreadManager().GetCurrentThread())
continue;
// TODO(Subv): When are the other running/ready threads terminated?
@ -297,7 +297,7 @@ void SVC::ExitProcess() {
}
// Kill the current thread
kernel.GetThreadManager().GetCurrentThread()->Stop();
kernel.GetCurrentThreadManager().GetCurrentThread()->Stop();
system.PrepareReschedule();
}
@ -388,7 +388,7 @@ ResultCode SVC::SendSyncRequest(Handle handle) {
system.PrepareReschedule();
auto thread = SharedFrom(kernel.GetThreadManager().GetCurrentThread());
auto thread = SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread());
if (kernel.GetIPCRecorder().IsEnabled()) {
kernel.GetIPCRecorder().RegisterRequest(session, thread);
@ -476,7 +476,7 @@ private:
/// Wait for a handle to synchronize, timeout after the specified nanoseconds
ResultCode SVC::WaitSynchronization1(Handle handle, s64 nano_seconds) {
auto object = kernel.GetCurrentProcess()->handle_table.Get<WaitObject>(handle);
Thread* thread = kernel.GetThreadManager().GetCurrentThread();
Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread();
if (object == nullptr)
return ERR_INVALID_HANDLE;
@ -514,7 +514,7 @@ ResultCode SVC::WaitSynchronization1(Handle handle, s64 nano_seconds) {
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle_count,
bool wait_all, s64 nano_seconds) {
Thread* thread = kernel.GetThreadManager().GetCurrentThread();
Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread();
if (!Memory::IsValidVirtualAddress(*kernel.GetCurrentProcess(), handles_address))
return ERR_INVALID_POINTER;
@ -684,7 +684,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
// We are also sending a command reply.
// Do not send a reply if the command id in the command buffer is 0xFFFF.
Thread* thread = kernel.GetThreadManager().GetCurrentThread();
Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread();
u32 cmd_buff_header = memory.Read32(thread->GetCommandBufferAddress());
IPC::Header header{cmd_buff_header};
if (reply_target != 0 && header.command_id != 0xFFFF) {
@ -791,7 +791,7 @@ ResultCode SVC::ArbitrateAddress(Handle handle, u32 address, u32 type, u32 value
return ERR_INVALID_HANDLE;
auto res =
arbiter->ArbitrateAddress(SharedFrom(kernel.GetThreadManager().GetCurrentThread()),
arbiter->ArbitrateAddress(SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread()),
static_cast<ArbitrationType>(type), address, value, nanoseconds);
// TODO(Subv): Identify in which specific cases this call should cause a reschedule.
@ -912,14 +912,19 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr
break;
case ThreadProcessorIdAll:
LOG_INFO(Kernel_SVC,
"Newly created thread is allowed to be run in any Core, unimplemented.");
"Newly created thread is allowed to be run in any Core, for now run in core 0.");
processor_id = ThreadProcessorId0;
break;
case ThreadProcessorId1:
LOG_ERROR(Kernel_SVC,
"Newly created thread must run in the SysCore (Core1), unimplemented.");
case ThreadProcessorId2:
case ThreadProcessorId3:
// TODO: Check and log for: When processorid==0x2 and the process is not a BASE mem-region
// process, exheader kernel-flags bitmask 0x2000 must be set (otherwise error 0xD9001BEA is
// returned). When processorid==0x3 and the process is not a BASE mem-region process, error
// 0xD9001BEA is returned. These are the only restriction checks done by the kernel for
// processorid.
break;
default:
// TODO(bunnei): Implement support for other processor IDs
ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id);
break;
}
@ -945,9 +950,9 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr
/// Called when a thread exits
void SVC::ExitThread() {
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.CPU().GetPC());
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.GetRunningCore().GetPC());
kernel.GetThreadManager().ExitCurrentThread();
kernel.GetCurrentThreadManager().ExitCurrentThread();
system.PrepareReschedule();
}
@ -993,7 +998,7 @@ ResultCode SVC::SetThreadPriority(Handle handle, u32 priority) {
/// Create a mutex
ResultCode SVC::CreateMutex(Handle* out_handle, u32 initial_locked) {
std::shared_ptr<Mutex> mutex = kernel.CreateMutex(initial_locked != 0);
mutex->name = fmt::format("mutex-{:08x}", system.CPU().GetReg(14));
mutex->name = fmt::format("mutex-{:08x}", system.GetRunningCore().GetReg(14));
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(mutex)));
LOG_TRACE(Kernel_SVC, "called initial_locked={} : created handle=0x{:08X}",
@ -1010,7 +1015,7 @@ ResultCode SVC::ReleaseMutex(Handle handle) {
if (mutex == nullptr)
return ERR_INVALID_HANDLE;
return mutex->Release(kernel.GetThreadManager().GetCurrentThread());
return mutex->Release(kernel.GetCurrentThreadManager().GetCurrentThread());
}
/// Get the ID of the specified process
@ -1060,7 +1065,7 @@ ResultCode SVC::GetThreadId(u32* thread_id, Handle handle) {
ResultCode SVC::CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count) {
CASCADE_RESULT(std::shared_ptr<Semaphore> semaphore,
kernel.CreateSemaphore(initial_count, max_count));
semaphore->name = fmt::format("semaphore-{:08x}", system.CPU().GetReg(14));
semaphore->name = fmt::format("semaphore-{:08x}", system.GetRunningCore().GetReg(14));
CASCADE_RESULT(*out_handle,
kernel.GetCurrentProcess()->handle_table.Create(std::move(semaphore)));
@ -1130,8 +1135,9 @@ ResultCode SVC::QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, u32 ad
/// Create an event
ResultCode SVC::CreateEvent(Handle* out_handle, u32 reset_type) {
std::shared_ptr<Event> evt = kernel.CreateEvent(
static_cast<ResetType>(reset_type), fmt::format("event-{:08x}", system.CPU().GetReg(14)));
std::shared_ptr<Event> evt =
kernel.CreateEvent(static_cast<ResetType>(reset_type),
fmt::format("event-{:08x}", system.GetRunningCore().GetReg(14)));
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(evt)));
LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type,
@ -1173,8 +1179,9 @@ ResultCode SVC::ClearEvent(Handle handle) {
/// Creates a timer
ResultCode SVC::CreateTimer(Handle* out_handle, u32 reset_type) {
std::shared_ptr<Timer> timer = kernel.CreateTimer(
static_cast<ResetType>(reset_type), fmt ::format("timer-{:08x}", system.CPU().GetReg(14)));
std::shared_ptr<Timer> timer =
kernel.CreateTimer(static_cast<ResetType>(reset_type),
fmt ::format("timer-{:08x}", system.GetRunningCore().GetReg(14)));
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(timer)));
LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type,
@ -1228,7 +1235,7 @@ ResultCode SVC::CancelTimer(Handle handle) {
void SVC::SleepThread(s64 nanoseconds) {
LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
ThreadManager& thread_manager = kernel.GetThreadManager();
ThreadManager& thread_manager = kernel.GetCurrentThreadManager();
// Don't attempt to yield execution if there are no available threads to run,
// this way we avoid a useless reschedule to the idle thread.
@ -1246,10 +1253,11 @@ void SVC::SleepThread(s64 nanoseconds) {
/// This returns the total CPU ticks elapsed since the CPU was powered-on
s64 SVC::GetSystemTick() {
s64 result = system.CoreTiming().GetTicks();
// TODO: Use globalTicks here?
s64 result = system.GetRunningCore().GetTimer()->GetTicks();
// Advance time to defeat dumb games (like Cubic Ninja) that busy-wait for the frame to end.
// Measured time between two calls on a 9.2 o3DS with Ninjhax 1.1b
system.CoreTiming().AddTicks(150);
system.GetRunningCore().GetTimer()->AddTicks(150);
return result;
}
@ -1611,11 +1619,11 @@ void SVC::CallSVC(u32 immediate) {
SVC::SVC(Core::System& system) : system(system), kernel(system.Kernel()), memory(system.Memory()) {}
u32 SVC::GetReg(std::size_t n) {
return system.CPU().GetReg(static_cast<int>(n));
return system.GetRunningCore().GetReg(static_cast<int>(n));
}
void SVC::SetReg(std::size_t n, u32 value) {
system.CPU().SetReg(static_cast<int>(n), value);
system.GetRunningCore().SetReg(static_cast<int>(n), value);
}
SVCContext::SVCContext(Core::System& system) : impl(std::make_unique<SVC>(system)) {}

View file

@ -62,13 +62,10 @@ void Thread::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
}
u32 ThreadManager::NewThreadId() {
return next_thread_id++;
}
Thread::Thread(KernelSystem& kernel)
: WaitObject(kernel), context(kernel.GetThreadManager().NewContext()),
thread_manager(kernel.GetThreadManager()) {}
Thread::Thread(KernelSystem& kernel, u32 core_id)
: WaitObject(kernel), context(kernel.GetThreadManager(core_id).NewContext()),
core_id(core_id),
thread_manager(kernel.GetThreadManager(core_id)) {}
Thread::~Thread() {}
Thread* ThreadManager::GetCurrentThread() const {
@ -113,7 +110,7 @@ void ThreadManager::SwitchContext(Thread* new_thread) {
// Save context for previous thread
if (previous_thread) {
previous_thread->last_running_ticks = timing.GetTicks();
previous_thread->last_running_ticks = timing.GetGlobalTicks();
cpu->SaveContext(previous_thread->context);
if (previous_thread->status == ThreadStatus::Running) {
@ -140,7 +137,7 @@ void ThreadManager::SwitchContext(Thread* new_thread) {
new_thread->status = ThreadStatus::Running;
if (previous_process != current_thread->owner_process) {
kernel.SetCurrentProcess(current_thread->owner_process);
kernel.SetCurrentProcessForCPU(current_thread->owner_process, cpu->GetID());
}
cpu->LoadContext(new_thread->context);
@ -153,7 +150,7 @@ void ThreadManager::SwitchContext(Thread* new_thread) {
}
Thread* ThreadManager::PopNextReadyThread() {
Thread* next;
Thread* next = nullptr;
Thread* thread = GetCurrentThread();
if (thread && thread->status == ThreadStatus::Running) {
@ -337,22 +334,22 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(
ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
}
auto thread{std::make_shared<Thread>(*this)};
auto thread{std::make_shared<Thread>(*this, processor_id)};
thread_manager->thread_list.push_back(thread);
thread_manager->ready_queue.prepare(priority);
thread_managers[processor_id]->thread_list.push_back(thread);
thread_managers[processor_id]->ready_queue.prepare(priority);
thread->thread_id = thread_manager->NewThreadId();
thread->thread_id = NewThreadId();
thread->status = ThreadStatus::Dormant;
thread->entry_point = entry_point;
thread->stack_top = stack_top;
thread->nominal_priority = thread->current_priority = priority;
thread->last_running_ticks = timing.GetTicks();
thread->last_running_ticks = timing.GetGlobalTicks();
thread->processor_id = processor_id;
thread->wait_objects.clear();
thread->wait_address = 0;
thread->name = std::move(name);
thread_manager->wakeup_callback_table[thread->thread_id] = thread.get();
thread_managers[processor_id]->wakeup_callback_table[thread->thread_id] = thread.get();
thread->owner_process = owner_process;
// Find the next available TLS index, and mark it as used
@ -397,7 +394,7 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(
// to initialize the context
ResetThreadContext(thread->context, stack_top, entry_point, arg);
thread_manager->ready_queue.push_back(thread->current_priority, thread.get());
thread_managers[processor_id]->ready_queue.push_back(thread->current_priority, thread.get());
thread->status = ThreadStatus::Ready;
return MakeResult<std::shared_ptr<Thread>>(std::move(thread));
@ -463,6 +460,9 @@ void ThreadManager::Reschedule() {
LOG_TRACE(Kernel, "context switch {} -> idle", cur->GetObjectId());
} else if (next) {
LOG_TRACE(Kernel, "context switch idle -> {}", next->GetObjectId());
} else {
LOG_TRACE(Kernel, "context switch idle -> idle, do nothing");
return;
}
SwitchContext(next);
@ -489,11 +489,10 @@ VAddr Thread::GetCommandBufferAddress() const {
return GetTLSAddress() + command_header_offset;
}
ThreadManager::ThreadManager(Kernel::KernelSystem& kernel) : kernel(kernel) {
ThreadWakeupEventType =
kernel.timing.RegisterEvent("ThreadWakeupCallback", [this](u64 thread_id, s64 cycle_late) {
ThreadWakeupCallback(thread_id, cycle_late);
});
ThreadManager::ThreadManager(Kernel::KernelSystem& kernel, u32 core_id) : kernel(kernel) {
ThreadWakeupEventType = kernel.timing.RegisterEvent(
"ThreadWakeupCallback_" + std::to_string(core_id),
[this](u64 thread_id, s64 cycle_late) { ThreadWakeupCallback(thread_id, cycle_late); });
}
ThreadManager::~ThreadManager() {

View file

@ -38,7 +38,9 @@ enum ThreadProcessorId : s32 {
ThreadProcessorIdAll = -1, ///< Run thread on either core
ThreadProcessorId0 = 0, ///< Run thread on core 0 (AppCore)
ThreadProcessorId1 = 1, ///< Run thread on core 1 (SysCore)
ThreadProcessorIdMax = 2, ///< Processor ID must be less than this
ThreadProcessorId2 = 2, ///< Run thread on core 2 (additional n3ds core)
ThreadProcessorId3 = 3, ///< Run thread on core 3 (additional n3ds core)
ThreadProcessorIdMax = 4, ///< Processor ID must be less than this
};
enum class ThreadStatus {
@ -75,15 +77,9 @@ private:
class ThreadManager {
public:
explicit ThreadManager(Kernel::KernelSystem& kernel);
explicit ThreadManager(Kernel::KernelSystem& kernel, u32 core_id);
~ThreadManager();
/**
* Creates a new thread ID
* @return The new thread ID
*/
u32 NewThreadId();
/**
* Gets the current thread
*/
@ -150,7 +146,6 @@ private:
Kernel::KernelSystem& kernel;
ARM_Interface* cpu;
u32 next_thread_id = 1;
std::shared_ptr<Thread> current_thread;
Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue;
std::unordered_map<u64, Thread*> wakeup_callback_table;
@ -167,7 +162,6 @@ private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int file_version) {
ar& next_thread_id;
ar& current_thread;
ar& ready_queue;
ar& wakeup_callback_table;
@ -177,7 +171,7 @@ private:
class Thread final : public WaitObject {
public:
explicit Thread(KernelSystem&);
explicit Thread(KernelSystem&, u32 core_id);
~Thread() override;
std::string GetName() const override {
@ -329,6 +323,8 @@ public:
// available. In case of a timeout, the object will be nullptr.
std::shared_ptr<WakeupCallback> wakeup_callback;
const u32 core_id;
private:
ThreadManager& thread_manager;
@ -351,4 +347,20 @@ std::shared_ptr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::Thread)
CONSTRUCT_KERNEL_OBJECT(Kernel::Thread)
namespace boost::serialization {
template <class Archive>
inline void save_construct_data(Archive& ar, const Kernel::Thread* t,
const unsigned int file_version) {
ar << t->core_id;
}
template <class Archive>
inline void load_construct_data(Archive& ar, Kernel::Thread* t, const unsigned int file_version) {
u32 core_id;
ar >> core_id;
::new (t) Kernel::Thread(Core::Global<Kernel::KernelSystem>(), core_id);
}
} // namespace boost::serialization

View file

@ -6,9 +6,7 @@
#include <array>
#include <atomic>
#ifndef _MSC_VER
#include <cstddef>
#endif
#include <memory>
#include "common/bit_field.h"
#include "common/common_funcs.h"
@ -177,10 +175,6 @@ struct GyroscopeCalibrateParam {
} x, y, z;
};
// TODO: MSVC does not support using offsetof() on non-static data members even though this
// is technically allowed since C++11. This macro should be enabled once MSVC adds
// support for that.
#ifndef _MSC_VER
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(SharedMem, field_name) == position * 4, \
"Field " #field_name " has invalid position")
@ -189,7 +183,6 @@ ASSERT_REG_POSITION(pad.index_reset_ticks, 0x0);
ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A);
#undef ASSERT_REG_POSITION
#endif // !defined(_MSC_VER)
struct DirectionState {
bool up;

View file

@ -2,9 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <atomic>
#ifdef ENABLE_WEB_SERVICE
#include <LUrlParser.h>
#endif
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include "common/archives.h"
#include "common/assert.h"
#include "core/core.h"
#include "core/file_sys/archive_ncch.h"
#include "core/file_sys/file_backend.h"
@ -52,6 +57,82 @@ const ResultCode ERROR_WRONG_CERT_HANDLE = // 0xD8A0A0C9
const ResultCode ERROR_CERT_ALREADY_SET = // 0xD8A0A03D
ResultCode(61, ErrorModule::HTTP, ErrorSummary::InvalidState, ErrorLevel::Permanent);
void Context::MakeRequest() {
ASSERT(state == RequestState::NotStarted);
#ifdef ENABLE_WEB_SERVICE
LUrlParser::clParseURL parsedUrl = LUrlParser::clParseURL::ParseURL(url);
int port;
std::unique_ptr<httplib::Client> client;
if (parsedUrl.m_Scheme == "http") {
if (!parsedUrl.GetPort(&port)) {
port = 80;
}
// TODO(B3N30): Support for setting timeout
// Figure out what the default timeout on 3DS is
client = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port);
} else {
if (!parsedUrl.GetPort(&port)) {
port = 443;
}
// TODO(B3N30): Support for setting timeout
// Figure out what the default timeout on 3DS is
auto ssl_client = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host, port);
SSL_CTX* ctx = ssl_client->ssl_context();
client = std::move(ssl_client);
if (auto client_cert = ssl_config.client_cert_ctx.lock()) {
SSL_CTX_use_certificate_ASN1(ctx, client_cert->certificate.size(),
client_cert->certificate.data());
SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, ctx, client_cert->private_key.data(),
client_cert->private_key.size());
}
// TODO(B3N30): Check for SSLOptions-Bits and set the verify method accordingly
// https://www.3dbrew.org/wiki/SSL_Services#SSLOpt
// Hack: Since for now RootCerts are not implemented we set the VerifyMode to None.
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
}
state = RequestState::InProgress;
static const std::unordered_map<RequestMethod, std::string> request_method_strings{
{RequestMethod::Get, "GET"}, {RequestMethod::Post, "POST"},
{RequestMethod::Head, "HEAD"}, {RequestMethod::Put, "PUT"},
{RequestMethod::Delete, "DELETE"}, {RequestMethod::PostEmpty, "POST"},
{RequestMethod::PutEmpty, "PUT"},
};
httplib::Request request;
request.method = request_method_strings.at(method);
request.path = url;
// TODO(B3N30): Add post data body
request.progress = [this](u64 current, u64 total) -> bool {
// TODO(B3N30): Is there a state that shows response header are available
current_download_size_bytes = current;
total_download_size_bytes = total;
return true;
};
for (const auto& header : headers) {
request.headers.emplace(header.name, header.value);
}
if (!client->send(request, response)) {
LOG_ERROR(Service_HTTP, "Request failed");
state = RequestState::TimedOut;
} else {
LOG_DEBUG(Service_HTTP, "Request successful");
// TODO(B3N30): Verify this state on HW
state = RequestState::ReadyToDownloadContent;
}
#else
LOG_ERROR(Service_HTTP, "Tried to make request but WebServices is not enabled in this build");
state = RequestState::TimedOut;
#endif
}
void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x1, 1, 4);
const u32 shmem_size = rp.Pop<u32>();
@ -156,7 +237,15 @@ void HTTP_C::BeginRequest(Kernel::HLERequestContext& ctx) {
auto itr = contexts.find(context_handle);
ASSERT(itr != contexts.end());
// TODO(B3N30): Make the request
// On a 3DS BeginRequest and BeginRequestAsync will push the Request to a worker queue.
// You can only enqueue 8 requests at the same time.
// trying to enqueue any more will either fail (BeginRequestAsync), or block (BeginRequest)
// Note that you only can have 8 Contexts at a time. So this difference shouldn't matter
// Then there are 3? worker threads that pop the requests from the queue and send them
// For now make every request async in it's own thread.
itr->second.request_future =
std::async(std::launch::async, &Context::MakeRequest, std::ref(itr->second));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
@ -201,7 +290,15 @@ void HTTP_C::BeginRequestAsync(Kernel::HLERequestContext& ctx) {
auto itr = contexts.find(context_handle);
ASSERT(itr != contexts.end());
// TODO(B3N30): Make the request
// On a 3DS BeginRequest and BeginRequestAsync will push the Request to a worker queue.
// You can only enqueue 8 requests at the same time.
// trying to enqueue any more will either fail (BeginRequestAsync), or block (BeginRequest)
// Note that you only can have 8 Contexts at a time. So this difference shouldn't matter
// Then there are 3? worker threads that pop the requests from the queue and send them
// For now make every request async in it's own thread.
itr->second.request_future =
std::async(std::launch::async, &Context::MakeRequest, std::ref(itr->second));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
@ -264,7 +361,7 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) {
return;
}
contexts.emplace(++context_counter, Context());
contexts.try_emplace(++context_counter);
contexts[context_counter].url = std::move(url);
contexts[context_counter].method = method;
contexts[context_counter].state = RequestState::NotStarted;
@ -311,10 +408,9 @@ void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) {
}
// TODO(Subv): What happens if you try to close a context that's currently being used?
ASSERT(itr->second.state == RequestState::NotStarted);
// TODO(Subv): Make sure that only the session that created the context can close it.
// Note that this will block if a request is still in progress
contexts.erase(itr);
session_data->num_http_contexts--;

View file

@ -4,6 +4,7 @@
#pragma once
#include <future>
#include <memory>
#include <string>
#include <unordered_map>
@ -15,6 +16,12 @@
#include <boost/serialization/unordered_map.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/weak_ptr.hpp>
#ifdef ENABLE_WEB_SERVICE
#if defined(__ANDROID__)
#include <ifaddrs.h>
#endif
#include <httplib.h>
#endif
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/service.h"
@ -113,8 +120,7 @@ public:
Context(const Context&) = delete;
Context& operator=(const Context&) = delete;
Context(Context&& other) = default;
Context& operator=(Context&&) = default;
void MakeRequest();
struct Proxy {
std::string url;
@ -195,14 +201,21 @@ public:
u32 session_id;
std::string url;
RequestMethod method;
RequestState state = RequestState::NotStarted;
boost::optional<Proxy> proxy;
boost::optional<BasicAuth> basic_auth;
std::atomic<RequestState> state = RequestState::NotStarted;
std::optional<Proxy> proxy;
std::optional<BasicAuth> basic_auth;
SSLConfig ssl_config{};
u32 socket_buffer_size;
std::vector<RequestHeader> headers;
std::vector<PostData> post_data;
std::future<void> request_future;
std::atomic<u64> current_download_size_bytes;
std::atomic<u64> total_download_size_bytes;
#ifdef ENABLE_WEB_SERVICE
httplib::Response response;
#endif
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
@ -219,6 +232,7 @@ private:
ar& post_data;
}
friend class boost::serialization::access;
};
struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {

View file

@ -55,7 +55,7 @@ VAddr CROHelper::SegmentTagToAddress(SegmentTag segment_tag) const {
return 0;
SegmentEntry entry;
GetEntry(memory, segment_tag.segment_index, entry);
GetEntry(system.Memory(), segment_tag.segment_index, entry);
if (segment_tag.offset_into_segment >= entry.size)
return 0;
@ -71,12 +71,12 @@ ResultCode CROHelper::ApplyRelocation(VAddr target_address, RelocationType reloc
break;
case RelocationType::AbsoluteAddress:
case RelocationType::AbsoluteAddress2:
memory.Write32(target_address, symbol_address + addend);
cpu.InvalidateCacheRange(target_address, sizeof(u32));
system.Memory().Write32(target_address, symbol_address + addend);
system.InvalidateCacheRange(target_address, sizeof(u32));
break;
case RelocationType::RelativeAddress:
memory.Write32(target_address, symbol_address + addend - target_future_address);
cpu.InvalidateCacheRange(target_address, sizeof(u32));
system.Memory().Write32(target_address, symbol_address + addend - target_future_address);
system.InvalidateCacheRange(target_address, sizeof(u32));
break;
case RelocationType::ThumbBranch:
case RelocationType::ArmBranch:
@ -98,8 +98,8 @@ ResultCode CROHelper::ClearRelocation(VAddr target_address, RelocationType reloc
case RelocationType::AbsoluteAddress:
case RelocationType::AbsoluteAddress2:
case RelocationType::RelativeAddress:
memory.Write32(target_address, 0);
cpu.InvalidateCacheRange(target_address, sizeof(u32));
system.Memory().Write32(target_address, 0);
system.InvalidateCacheRange(target_address, sizeof(u32));
break;
case RelocationType::ThumbBranch:
case RelocationType::ArmBranch:
@ -121,7 +121,8 @@ ResultCode CROHelper::ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool
VAddr relocation_address = batch;
while (true) {
RelocationEntry relocation;
memory.ReadBlock(process, relocation_address, &relocation, sizeof(RelocationEntry));
system.Memory().ReadBlock(process, relocation_address, &relocation,
sizeof(RelocationEntry));
VAddr relocation_target = SegmentTagToAddress(relocation.target_position);
if (relocation_target == 0) {
@ -142,9 +143,9 @@ ResultCode CROHelper::ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool
}
RelocationEntry relocation;
memory.ReadBlock(process, batch, &relocation, sizeof(RelocationEntry));
system.Memory().ReadBlock(process, batch, &relocation, sizeof(RelocationEntry));
relocation.is_batch_resolved = reset ? 0 : 1;
memory.WriteBlock(process, batch, &relocation, sizeof(RelocationEntry));
system.Memory().WriteBlock(process, batch, &relocation, sizeof(RelocationEntry));
return RESULT_SUCCESS;
}
@ -154,13 +155,13 @@ VAddr CROHelper::FindExportNamedSymbol(const std::string& name) const {
std::size_t len = name.size();
ExportTreeEntry entry;
GetEntry(memory, 0, entry);
GetEntry(system.Memory(), 0, entry);
ExportTreeEntry::Child next;
next.raw = entry.left.raw;
u32 found_id;
while (true) {
GetEntry(memory, next.next_index, entry);
GetEntry(system.Memory(), next.next_index, entry);
if (next.is_end) {
found_id = entry.export_table_index;
@ -186,9 +187,9 @@ VAddr CROHelper::FindExportNamedSymbol(const std::string& name) const {
u32 export_strings_size = GetField(ExportStringsSize);
ExportNamedSymbolEntry symbol_entry;
GetEntry(memory, found_id, symbol_entry);
GetEntry(system.Memory(), found_id, symbol_entry);
if (memory.ReadCString(symbol_entry.name_offset, export_strings_size) != name)
if (system.Memory().ReadCString(symbol_entry.name_offset, export_strings_size) != name)
return 0;
return SegmentTagToAddress(symbol_entry.symbol_position);
@ -279,7 +280,7 @@ ResultVal<VAddr> CROHelper::RebaseSegmentTable(u32 cro_size, VAddr data_segment_
u32 segment_num = GetField(SegmentNum);
for (u32 i = 0; i < segment_num; ++i) {
SegmentEntry segment;
GetEntry(memory, i, segment);
GetEntry(system.Memory(), i, segment);
if (segment.type == SegmentType::Data) {
if (segment.size != 0) {
if (segment.size > data_segment_size)
@ -298,7 +299,7 @@ ResultVal<VAddr> CROHelper::RebaseSegmentTable(u32 cro_size, VAddr data_segment_
if (segment.offset > module_address + cro_size)
return CROFormatError(0x19);
}
SetEntry(memory, i, segment);
SetEntry(system.Memory(), i, segment);
}
return MakeResult<u32>(prev_data_segment + module_address);
}
@ -310,7 +311,7 @@ ResultCode CROHelper::RebaseExportNamedSymbolTable() {
u32 export_named_symbol_num = GetField(ExportNamedSymbolNum);
for (u32 i = 0; i < export_named_symbol_num; ++i) {
ExportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset += module_address;
@ -320,7 +321,7 @@ ResultCode CROHelper::RebaseExportNamedSymbolTable() {
}
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
return RESULT_SUCCESS;
}
@ -329,7 +330,7 @@ ResultCode CROHelper::VerifyExportTreeTable() const {
u32 tree_num = GetField(ExportTreeNum);
for (u32 i = 0; i < tree_num; ++i) {
ExportTreeEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.left.next_index >= tree_num || entry.right.next_index >= tree_num) {
return CROFormatError(0x11);
@ -353,7 +354,7 @@ ResultCode CROHelper::RebaseImportModuleTable() {
u32 module_num = GetField(ImportModuleNum);
for (u32 i = 0; i < module_num; ++i) {
ImportModuleEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset += module_address;
@ -379,7 +380,7 @@ ResultCode CROHelper::RebaseImportModuleTable() {
}
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
return RESULT_SUCCESS;
}
@ -395,7 +396,7 @@ ResultCode CROHelper::RebaseImportNamedSymbolTable() {
u32 num = GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset += module_address;
@ -413,7 +414,7 @@ ResultCode CROHelper::RebaseImportNamedSymbolTable() {
}
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
return RESULT_SUCCESS;
}
@ -427,7 +428,7 @@ ResultCode CROHelper::RebaseImportIndexedSymbolTable() {
u32 num = GetField(ImportIndexedSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportIndexedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.relocation_batch_offset != 0) {
entry.relocation_batch_offset += module_address;
@ -437,7 +438,7 @@ ResultCode CROHelper::RebaseImportIndexedSymbolTable() {
}
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
return RESULT_SUCCESS;
}
@ -451,7 +452,7 @@ ResultCode CROHelper::RebaseImportAnonymousSymbolTable() {
u32 num = GetField(ImportAnonymousSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportAnonymousSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.relocation_batch_offset != 0) {
entry.relocation_batch_offset += module_address;
@ -461,7 +462,7 @@ ResultCode CROHelper::RebaseImportAnonymousSymbolTable() {
}
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
return RESULT_SUCCESS;
}
@ -476,14 +477,14 @@ ResultCode CROHelper::ResetExternalRelocations() {
ExternalRelocationEntry relocation;
// Verifies that the last relocation is the end of a batch
GetEntry(memory, external_relocation_num - 1, relocation);
GetEntry(system.Memory(), external_relocation_num - 1, relocation);
if (!relocation.is_batch_end) {
return CROFormatError(0x12);
}
bool batch_begin = true;
for (u32 i = 0; i < external_relocation_num; ++i) {
GetEntry(memory, i, relocation);
GetEntry(system.Memory(), i, relocation);
VAddr relocation_target = SegmentTagToAddress(relocation.target_position);
if (relocation_target == 0) {
@ -500,7 +501,7 @@ ResultCode CROHelper::ResetExternalRelocations() {
if (batch_begin) {
// resets to unresolved state
relocation.is_batch_resolved = 0;
SetEntry(memory, i, relocation);
SetEntry(system.Memory(), i, relocation);
}
// if current is an end, then the next is a beginning
@ -516,7 +517,7 @@ ResultCode CROHelper::ClearExternalRelocations() {
bool batch_begin = true;
for (u32 i = 0; i < external_relocation_num; ++i) {
GetEntry(memory, i, relocation);
GetEntry(system.Memory(), i, relocation);
VAddr relocation_target = SegmentTagToAddress(relocation.target_position);
if (relocation_target == 0) {
@ -532,7 +533,7 @@ ResultCode CROHelper::ClearExternalRelocations() {
if (batch_begin) {
// resets to unresolved state
relocation.is_batch_resolved = 0;
SetEntry(memory, i, relocation);
SetEntry(system.Memory(), i, relocation);
}
// if current is an end, then the next is a beginning
@ -548,13 +549,13 @@ ResultCode CROHelper::ApplyStaticAnonymousSymbolToCRS(VAddr crs_address) {
static_relocation_table_offset +
GetField(StaticRelocationNum) * sizeof(StaticRelocationEntry);
CROHelper crs(crs_address, process, memory, cpu);
CROHelper crs(crs_address, process, system);
u32 offset_export_num = GetField(StaticAnonymousSymbolNum);
LOG_INFO(Service_LDR, "CRO \"{}\" exports {} static anonymous symbols", ModuleName(),
offset_export_num);
for (u32 i = 0; i < offset_export_num; ++i) {
StaticAnonymousSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
u32 batch_address = entry.relocation_batch_offset + module_address;
if (batch_address < static_relocation_table_offset ||
@ -579,7 +580,7 @@ ResultCode CROHelper::ApplyInternalRelocations(u32 old_data_segment_address) {
u32 internal_relocation_num = GetField(InternalRelocationNum);
for (u32 i = 0; i < internal_relocation_num; ++i) {
InternalRelocationEntry relocation;
GetEntry(memory, i, relocation);
GetEntry(system.Memory(), i, relocation);
VAddr target_addressB = SegmentTagToAddress(relocation.target_position);
if (target_addressB == 0) {
return CROFormatError(0x15);
@ -587,7 +588,7 @@ ResultCode CROHelper::ApplyInternalRelocations(u32 old_data_segment_address) {
VAddr target_address;
SegmentEntry target_segment;
GetEntry(memory, relocation.target_position.segment_index, target_segment);
GetEntry(system.Memory(), relocation.target_position.segment_index, target_segment);
if (target_segment.type == SegmentType::Data) {
// If the relocation is to the .data segment, we need to relocate it in the old buffer
@ -602,7 +603,7 @@ ResultCode CROHelper::ApplyInternalRelocations(u32 old_data_segment_address) {
}
SegmentEntry symbol_segment;
GetEntry(memory, relocation.symbol_segment, symbol_segment);
GetEntry(system.Memory(), relocation.symbol_segment, symbol_segment);
LOG_TRACE(Service_LDR, "Internally relocates 0x{:08X} with 0x{:08X}", target_address,
symbol_segment.offset);
ResultCode result = ApplyRelocation(target_address, relocation.type, relocation.addend,
@ -619,7 +620,7 @@ ResultCode CROHelper::ClearInternalRelocations() {
u32 internal_relocation_num = GetField(InternalRelocationNum);
for (u32 i = 0; i < internal_relocation_num; ++i) {
InternalRelocationEntry relocation;
GetEntry(memory, i, relocation);
GetEntry(system.Memory(), i, relocation);
VAddr target_address = SegmentTagToAddress(relocation.target_position);
if (target_address == 0) {
@ -639,13 +640,13 @@ void CROHelper::UnrebaseImportAnonymousSymbolTable() {
u32 num = GetField(ImportAnonymousSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportAnonymousSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.relocation_batch_offset != 0) {
entry.relocation_batch_offset -= module_address;
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
}
@ -653,13 +654,13 @@ void CROHelper::UnrebaseImportIndexedSymbolTable() {
u32 num = GetField(ImportIndexedSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportIndexedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.relocation_batch_offset != 0) {
entry.relocation_batch_offset -= module_address;
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
}
@ -667,7 +668,7 @@ void CROHelper::UnrebaseImportNamedSymbolTable() {
u32 num = GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < num; ++i) {
ImportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset -= module_address;
@ -677,7 +678,7 @@ void CROHelper::UnrebaseImportNamedSymbolTable() {
entry.relocation_batch_offset -= module_address;
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
}
@ -685,7 +686,7 @@ void CROHelper::UnrebaseImportModuleTable() {
u32 module_num = GetField(ImportModuleNum);
for (u32 i = 0; i < module_num; ++i) {
ImportModuleEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset -= module_address;
@ -699,7 +700,7 @@ void CROHelper::UnrebaseImportModuleTable() {
entry.import_anonymous_symbol_table_offset -= module_address;
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
}
@ -707,13 +708,13 @@ void CROHelper::UnrebaseExportNamedSymbolTable() {
u32 export_named_symbol_num = GetField(ExportNamedSymbolNum);
for (u32 i = 0; i < export_named_symbol_num; ++i) {
ExportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.name_offset != 0) {
entry.name_offset -= module_address;
}
SetEntry(memory, i, entry);
SetEntry(system.Memory(), i, entry);
}
}
@ -721,7 +722,7 @@ void CROHelper::UnrebaseSegmentTable() {
u32 segment_num = GetField(SegmentNum);
for (u32 i = 0; i < segment_num; ++i) {
SegmentEntry segment;
GetEntry(memory, i, segment);
GetEntry(system.Memory(), i, segment);
if (segment.type == SegmentType::BSS) {
segment.offset = 0;
@ -729,7 +730,7 @@ void CROHelper::UnrebaseSegmentTable() {
segment.offset -= module_address;
}
SetEntry(memory, i, segment);
SetEntry(system.Memory(), i, segment);
}
}
@ -751,17 +752,17 @@ ResultCode CROHelper::ApplyImportNamedSymbol(VAddr crs_address) {
u32 symbol_import_num = GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < symbol_import_num; ++i) {
ImportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
if (!relocation_entry.is_batch_resolved) {
ResultCode result = ForEachAutoLinkCRO(
process, memory, cpu, crs_address, [&](CROHelper source) -> ResultVal<bool> {
process, system, crs_address, [&](CROHelper source) -> ResultVal<bool> {
std::string symbol_name =
memory.ReadCString(entry.name_offset, import_strings_size);
system.Memory().ReadCString(entry.name_offset, import_strings_size);
u32 symbol_address = source.FindExportNamedSymbol(symbol_name);
if (symbol_address != 0) {
@ -794,11 +795,11 @@ ResultCode CROHelper::ResetImportNamedSymbol() {
u32 symbol_import_num = GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < symbol_import_num; ++i) {
ImportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true);
if (result.IsError()) {
@ -815,11 +816,11 @@ ResultCode CROHelper::ResetImportIndexedSymbol() {
u32 import_num = GetField(ImportIndexedSymbolNum);
for (u32 i = 0; i < import_num; ++i) {
ImportIndexedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true);
if (result.IsError()) {
@ -836,11 +837,11 @@ ResultCode CROHelper::ResetImportAnonymousSymbol() {
u32 import_num = GetField(ImportAnonymousSymbolNum);
for (u32 i = 0; i < import_num; ++i) {
ImportAnonymousSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true);
if (result.IsError()) {
@ -857,19 +858,20 @@ ResultCode CROHelper::ApplyModuleImport(VAddr crs_address) {
u32 import_module_num = GetField(ImportModuleNum);
for (u32 i = 0; i < import_module_num; ++i) {
ImportModuleEntry entry;
GetEntry(memory, i, entry);
std::string want_cro_name = memory.ReadCString(entry.name_offset, import_strings_size);
GetEntry(system.Memory(), i, entry);
std::string want_cro_name =
system.Memory().ReadCString(entry.name_offset, import_strings_size);
ResultCode result = ForEachAutoLinkCRO(
process, memory, cpu, crs_address, [&](CROHelper source) -> ResultVal<bool> {
process, system, crs_address, [&](CROHelper source) -> ResultVal<bool> {
if (want_cro_name == source.ModuleName()) {
LOG_INFO(Service_LDR, "CRO \"{}\" imports {} indexed symbols from \"{}\"",
ModuleName(), entry.import_indexed_symbol_num, source.ModuleName());
for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) {
ImportIndexedSymbolEntry im;
entry.GetImportIndexedSymbolEntry(process, memory, j, im);
entry.GetImportIndexedSymbolEntry(process, system.Memory(), j, im);
ExportIndexedSymbolEntry ex;
source.GetEntry(memory, im.index, ex);
source.GetEntry(system.Memory(), im.index, ex);
u32 symbol_address = source.SegmentTagToAddress(ex.symbol_position);
LOG_TRACE(Service_LDR, " Imports 0x{:08X}", symbol_address);
ResultCode result =
@ -884,7 +886,7 @@ ResultCode CROHelper::ApplyModuleImport(VAddr crs_address) {
ModuleName(), entry.import_anonymous_symbol_num, source.ModuleName());
for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) {
ImportAnonymousSymbolEntry im;
entry.GetImportAnonymousSymbolEntry(process, memory, j, im);
entry.GetImportAnonymousSymbolEntry(process, system.Memory(), j, im);
u32 symbol_address = source.SegmentTagToAddress(im.symbol_position);
LOG_TRACE(Service_LDR, " Imports 0x{:08X}", symbol_address);
ResultCode result =
@ -913,15 +915,15 @@ ResultCode CROHelper::ApplyExportNamedSymbol(CROHelper target) {
u32 target_symbol_import_num = target.GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < target_symbol_import_num; ++i) {
ImportNamedSymbolEntry entry;
target.GetEntry(memory, i, entry);
target.GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
if (!relocation_entry.is_batch_resolved) {
std::string symbol_name =
memory.ReadCString(entry.name_offset, target_import_strings_size);
system.Memory().ReadCString(entry.name_offset, target_import_strings_size);
u32 symbol_address = FindExportNamedSymbol(symbol_name);
if (symbol_address != 0) {
LOG_TRACE(Service_LDR, " exports symbol \"{}\"", symbol_name);
@ -944,15 +946,15 @@ ResultCode CROHelper::ResetExportNamedSymbol(CROHelper target) {
u32 target_symbol_import_num = target.GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < target_symbol_import_num; ++i) {
ImportNamedSymbolEntry entry;
target.GetEntry(memory, i, entry);
target.GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
if (relocation_entry.is_batch_resolved) {
std::string symbol_name =
memory.ReadCString(entry.name_offset, target_import_strings_size);
system.Memory().ReadCString(entry.name_offset, target_import_strings_size);
u32 symbol_address = FindExportNamedSymbol(symbol_name);
if (symbol_address != 0) {
LOG_TRACE(Service_LDR, " unexports symbol \"{}\"", symbol_name);
@ -974,18 +976,19 @@ ResultCode CROHelper::ApplyModuleExport(CROHelper target) {
u32 target_import_module_num = target.GetField(ImportModuleNum);
for (u32 i = 0; i < target_import_module_num; ++i) {
ImportModuleEntry entry;
target.GetEntry(memory, i, entry);
target.GetEntry(system.Memory(), i, entry);
if (memory.ReadCString(entry.name_offset, target_import_string_size) != module_name)
if (system.Memory().ReadCString(entry.name_offset, target_import_string_size) !=
module_name)
continue;
LOG_INFO(Service_LDR, "CRO \"{}\" exports {} indexed symbols to \"{}\"", module_name,
entry.import_indexed_symbol_num, target.ModuleName());
for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) {
ImportIndexedSymbolEntry im;
entry.GetImportIndexedSymbolEntry(process, memory, j, im);
entry.GetImportIndexedSymbolEntry(process, system.Memory(), j, im);
ExportIndexedSymbolEntry ex;
GetEntry(memory, im.index, ex);
GetEntry(system.Memory(), im.index, ex);
u32 symbol_address = SegmentTagToAddress(ex.symbol_position);
LOG_TRACE(Service_LDR, " exports symbol 0x{:08X}", symbol_address);
ResultCode result =
@ -1000,7 +1003,7 @@ ResultCode CROHelper::ApplyModuleExport(CROHelper target) {
entry.import_anonymous_symbol_num, target.ModuleName());
for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) {
ImportAnonymousSymbolEntry im;
entry.GetImportAnonymousSymbolEntry(process, memory, j, im);
entry.GetImportAnonymousSymbolEntry(process, system.Memory(), j, im);
u32 symbol_address = SegmentTagToAddress(im.symbol_position);
LOG_TRACE(Service_LDR, " exports symbol 0x{:08X}", symbol_address);
ResultCode result =
@ -1023,16 +1026,17 @@ ResultCode CROHelper::ResetModuleExport(CROHelper target) {
u32 target_import_module_num = target.GetField(ImportModuleNum);
for (u32 i = 0; i < target_import_module_num; ++i) {
ImportModuleEntry entry;
target.GetEntry(memory, i, entry);
target.GetEntry(system.Memory(), i, entry);
if (memory.ReadCString(entry.name_offset, target_import_string_size) != module_name)
if (system.Memory().ReadCString(entry.name_offset, target_import_string_size) !=
module_name)
continue;
LOG_DEBUG(Service_LDR, "CRO \"{}\" unexports indexed symbols to \"{}\"", module_name,
target.ModuleName());
for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) {
ImportIndexedSymbolEntry im;
entry.GetImportIndexedSymbolEntry(process, memory, j, im);
entry.GetImportIndexedSymbolEntry(process, system.Memory(), j, im);
ResultCode result =
target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true);
if (result.IsError()) {
@ -1045,7 +1049,7 @@ ResultCode CROHelper::ResetModuleExport(CROHelper target) {
target.ModuleName());
for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) {
ImportAnonymousSymbolEntry im;
entry.GetImportAnonymousSymbolEntry(process, memory, j, im);
entry.GetImportAnonymousSymbolEntry(process, system.Memory(), j, im);
ResultCode result =
target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true);
if (result.IsError()) {
@ -1063,15 +1067,16 @@ ResultCode CROHelper::ApplyExitRelocations(VAddr crs_address) {
u32 symbol_import_num = GetField(ImportNamedSymbolNum);
for (u32 i = 0; i < symbol_import_num; ++i) {
ImportNamedSymbolEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
VAddr relocation_addr = entry.relocation_batch_offset;
ExternalRelocationEntry relocation_entry;
memory.ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
system.Memory().ReadBlock(process, relocation_addr, &relocation_entry,
sizeof(ExternalRelocationEntry));
if (memory.ReadCString(entry.name_offset, import_strings_size) == "__aeabi_atexit") {
if (system.Memory().ReadCString(entry.name_offset, import_strings_size) ==
"__aeabi_atexit") {
ResultCode result = ForEachAutoLinkCRO(
process, memory, cpu, crs_address, [&](CROHelper source) -> ResultVal<bool> {
process, system, crs_address, [&](CROHelper source) -> ResultVal<bool> {
u32 symbol_address = source.FindExportNamedSymbol("nnroAeabiAtexit_");
if (symbol_address != 0) {
@ -1126,7 +1131,8 @@ ResultCode CROHelper::Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment
return result;
}
result = VerifyStringTableLength(memory, GetField(ModuleNameOffset), GetField(ModuleNameSize));
result = VerifyStringTableLength(system.Memory(), GetField(ModuleNameOffset),
GetField(ModuleNameSize));
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error verifying module name {:08X}", result.raw);
return result;
@ -1155,8 +1161,8 @@ ResultCode CROHelper::Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment
return result;
}
result =
VerifyStringTableLength(memory, GetField(ExportStringsOffset), GetField(ExportStringsSize));
result = VerifyStringTableLength(system.Memory(), GetField(ExportStringsOffset),
GetField(ExportStringsSize));
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error verifying export strings {:08X}", result.raw);
return result;
@ -1192,8 +1198,8 @@ ResultCode CROHelper::Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment
return result;
}
result =
VerifyStringTableLength(memory, GetField(ImportStringsOffset), GetField(ImportStringsSize));
result = VerifyStringTableLength(system.Memory(), GetField(ImportStringsOffset),
GetField(ImportStringsSize));
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error verifying import strings {:08X}", result.raw);
return result;
@ -1266,11 +1272,11 @@ ResultCode CROHelper::Link(VAddr crs_address, bool link_on_load_bug_fix) {
// so we do the same
if (GetField(SegmentNum) >= 2) { // means we have .data segment
SegmentEntry entry;
GetEntry(memory, 2, entry);
GetEntry(system.Memory(), 2, entry);
ASSERT(entry.type == SegmentType::Data);
data_segment_address = entry.offset;
entry.offset = GetField(DataOffset);
SetEntry(memory, 2, entry);
SetEntry(system.Memory(), 2, entry);
}
}
SCOPE_EXIT({
@ -1278,9 +1284,9 @@ ResultCode CROHelper::Link(VAddr crs_address, bool link_on_load_bug_fix) {
if (link_on_load_bug_fix) {
if (GetField(SegmentNum) >= 2) {
SegmentEntry entry;
GetEntry(memory, 2, entry);
GetEntry(system.Memory(), 2, entry);
entry.offset = data_segment_address;
SetEntry(memory, 2, entry);
SetEntry(system.Memory(), 2, entry);
}
}
});
@ -1301,7 +1307,7 @@ ResultCode CROHelper::Link(VAddr crs_address, bool link_on_load_bug_fix) {
}
// Exports symbols to other modules
result = ForEachAutoLinkCRO(process, memory, cpu, crs_address,
result = ForEachAutoLinkCRO(process, system, crs_address,
[this](CROHelper target) -> ResultVal<bool> {
ResultCode result = ApplyExportNamedSymbol(target);
if (result.IsError())
@ -1346,7 +1352,7 @@ ResultCode CROHelper::Unlink(VAddr crs_address) {
// Resets all symbols in other modules imported from this module
// Note: the RO service seems only searching in auto-link modules
result = ForEachAutoLinkCRO(process, memory, cpu, crs_address,
result = ForEachAutoLinkCRO(process, system, crs_address,
[this](CROHelper target) -> ResultVal<bool> {
ResultCode result = ResetExportNamedSymbol(target);
if (result.IsError())
@ -1387,13 +1393,13 @@ void CROHelper::InitCRS() {
}
void CROHelper::Register(VAddr crs_address, bool auto_link) {
CROHelper crs(crs_address, process, memory, cpu);
CROHelper head(auto_link ? crs.NextModule() : crs.PreviousModule(), process, memory, cpu);
CROHelper crs(crs_address, process, system);
CROHelper head(auto_link ? crs.NextModule() : crs.PreviousModule(), process, system);
if (head.module_address) {
// there are already CROs registered
// register as the new tail
CROHelper tail(head.PreviousModule(), process, memory, cpu);
CROHelper tail(head.PreviousModule(), process, system);
// link with the old tail
ASSERT(tail.NextModule() == 0);
@ -1419,11 +1425,11 @@ void CROHelper::Register(VAddr crs_address, bool auto_link) {
}
void CROHelper::Unregister(VAddr crs_address) {
CROHelper crs(crs_address, process, memory, cpu);
CROHelper next_head(crs.NextModule(), process, memory, cpu);
CROHelper previous_head(crs.PreviousModule(), process, memory, cpu);
CROHelper next(NextModule(), process, memory, cpu);
CROHelper previous(PreviousModule(), process, memory, cpu);
CROHelper crs(crs_address, process, system);
CROHelper next_head(crs.NextModule(), process, system);
CROHelper previous_head(crs.PreviousModule(), process, system);
CROHelper next(NextModule(), process, system);
CROHelper previous(PreviousModule(), process, system);
if (module_address == next_head.module_address ||
module_address == previous_head.module_address) {
@ -1517,7 +1523,7 @@ std::tuple<VAddr, u32> CROHelper::GetExecutablePages() const {
u32 segment_num = GetField(SegmentNum);
for (u32 i = 0; i < segment_num; ++i) {
SegmentEntry entry;
GetEntry(memory, i, entry);
GetEntry(system.Memory(), i, entry);
if (entry.type == SegmentType::Code && entry.size != 0) {
VAddr begin = Common::AlignDown(entry.offset, Memory::PAGE_SIZE);
VAddr end = Common::AlignUp(entry.offset + entry.size, Memory::PAGE_SIZE);

View file

@ -33,12 +33,11 @@ static constexpr u32 CRO_HASH_SIZE = 0x80;
class CROHelper final {
public:
// TODO (wwylele): pass in the process handle for memory access
explicit CROHelper(VAddr cro_address, Kernel::Process& process, Memory::MemorySystem& memory,
ARM_Interface& cpu)
: module_address(cro_address), process(process), memory(memory), cpu(cpu) {}
explicit CROHelper(VAddr cro_address, Kernel::Process& process, Core::System& system)
: module_address(cro_address), process(process), system(system) {}
std::string ModuleName() const {
return memory.ReadCString(GetField(ModuleNameOffset), GetField(ModuleNameSize));
return system.Memory().ReadCString(GetField(ModuleNameOffset), GetField(ModuleNameSize));
}
u32 GetFileSize() const {
@ -144,8 +143,7 @@ public:
private:
const VAddr module_address; ///< the virtual address of this module
Kernel::Process& process; ///< the owner process of this module
Memory::MemorySystem& memory;
ARM_Interface& cpu;
Core::System& system;
/**
* Each item in this enum represents a u32 field in the header begin from address+0x80,
@ -403,11 +401,11 @@ private:
}
u32 GetField(HeaderField field) const {
return memory.Read32(Field(field));
return system.Memory().Read32(Field(field));
}
void SetField(HeaderField field, u32 value) {
memory.Write32(Field(field), value);
system.Memory().Write32(Field(field), value);
}
/**
@ -474,12 +472,11 @@ private:
* otherwise error code of the last iteration.
*/
template <typename FunctionObject>
static ResultCode ForEachAutoLinkCRO(Kernel::Process& process, Memory::MemorySystem& memory,
ARM_Interface& cpu, VAddr crs_address,
FunctionObject func) {
static ResultCode ForEachAutoLinkCRO(Kernel::Process& process, Core::System& system,
VAddr crs_address, FunctionObject func) {
VAddr current = crs_address;
while (current != 0) {
CROHelper cro(current, process, memory, cpu);
CROHelper cro(current, process, system);
CASCADE_RESULT(bool next, func(cro));
if (!next)
break;

View file

@ -120,7 +120,7 @@ void RO::Initialize(Kernel::HLERequestContext& ctx) {
return;
}
CROHelper crs(crs_address, *process, system.Memory(), system.CPU());
CROHelper crs(crs_address, *process, system);
crs.InitCRS();
result = crs.Rebase(0, crs_size, 0, 0, 0, 0, true);
@ -254,7 +254,7 @@ void RO::LoadCRO(Kernel::HLERequestContext& ctx, bool link_on_load_bug_fix) {
return;
}
CROHelper cro(cro_address, *process, system.Memory(), system.CPU());
CROHelper cro(cro_address, *process, system);
result = cro.VerifyHash(cro_size, crr_address);
if (result.IsError()) {
@ -318,7 +318,7 @@ void RO::LoadCRO(Kernel::HLERequestContext& ctx, bool link_on_load_bug_fix) {
}
}
system.CPU().InvalidateCacheRange(cro_address, cro_size);
system.InvalidateCacheRange(cro_address, cro_size);
LOG_INFO(Service_LDR, "CRO \"{}\" loaded at 0x{:08X}, fixed_end=0x{:08X}", cro.ModuleName(),
cro_address, cro_address + fix_size);
@ -336,7 +336,7 @@ void RO::UnloadCRO(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_LDR, "called, cro_address=0x{:08X}, zero={}, cro_buffer_ptr=0x{:08X}",
cro_address, zero, cro_buffer_ptr);
CROHelper cro(cro_address, *process, system.Memory(), system.CPU());
CROHelper cro(cro_address, *process, system);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@ -391,7 +391,7 @@ void RO::UnloadCRO(Kernel::HLERequestContext& ctx) {
LOG_ERROR(Service_LDR, "Error unmapping CRO {:08X}", result.raw);
}
system.CPU().InvalidateCacheRange(cro_address, fixed_size);
system.InvalidateCacheRange(cro_address, fixed_size);
rb.Push(result);
}
@ -403,7 +403,7 @@ void RO::LinkCRO(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_LDR, "called, cro_address=0x{:08X}", cro_address);
CROHelper cro(cro_address, *process, system.Memory(), system.CPU());
CROHelper cro(cro_address, *process, system);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@ -443,7 +443,7 @@ void RO::UnlinkCRO(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_LDR, "called, cro_address=0x{:08X}", cro_address);
CROHelper cro(cro_address, *process, system.Memory(), system.CPU());
CROHelper cro(cro_address, *process, system);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@ -492,7 +492,7 @@ void RO::Shutdown(Kernel::HLERequestContext& ctx) {
return;
}
CROHelper crs(slot->loaded_crs, *process, system.Memory(), system.CPU());
CROHelper crs(slot->loaded_crs, *process, system);
crs.Unrebase(true);
ResultCode result = RESULT_SUCCESS;

View file

@ -402,8 +402,8 @@ inline void Write(u32 addr, const T data) {
switch (index) {
// Memory fills are triggered once the fill value is written.
case GPU_REG_INDEX_WORKAROUND(memory_fill_config[0].trigger, 0x00004 + 0x3):
case GPU_REG_INDEX_WORKAROUND(memory_fill_config[1].trigger, 0x00008 + 0x3): {
case GPU_REG_INDEX(memory_fill_config[0].trigger):
case GPU_REG_INDEX(memory_fill_config[1].trigger): {
const bool is_second_filler = (index != GPU_REG_INDEX(memory_fill_config[0].trigger));
auto& config = g_regs.memory_fill_config[is_second_filler];

View file

@ -22,41 +22,15 @@ namespace GPU {
constexpr float SCREEN_REFRESH_RATE = 60;
// Returns index corresponding to the Regs member labeled by field_name
// TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions
// when used with array elements (e.g. GPU_REG_INDEX(memory_fill_config[0])).
// For details cf.
// https://connect.microsoft.com/VisualStudio/feedback/details/209229/offsetof-does-not-produce-a-constant-expression-for-array-members
// Hopefully, this will be fixed sometime in the future.
// For lack of better alternatives, we currently hardcode the offsets when constant
// expressions are needed via GPU_REG_INDEX_WORKAROUND (on sane compilers, static_asserts
// will then make sure the offsets indeed match the automatically calculated ones).
#define GPU_REG_INDEX(field_name) (offsetof(GPU::Regs, field_name) / sizeof(u32))
#if defined(_MSC_VER)
#define GPU_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) (backup_workaround_index)
#else
// NOTE: Yeah, hacking in a static_assert here just to workaround the lacking MSVC compiler
// really is this annoying. This macro just forwards its first argument to GPU_REG_INDEX
// and then performs a (no-op) cast to std::size_t iff the second argument matches the
// expected field offset. Otherwise, the compiler will fail to compile this code.
#define GPU_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) \
((typename std::enable_if<backup_workaround_index == GPU_REG_INDEX(field_name), \
std::size_t>::type) GPU_REG_INDEX(field_name))
#endif
// MMIO region 0x1EFxxxxx
struct Regs {
// helper macro to make sure the defined structures are of the expected size.
#if defined(_MSC_VER)
// TODO: MSVC does not support using sizeof() on non-static data members even though this
// is technically allowed since C++11. This macro should be enabled once MSVC adds
// support for that.
#define ASSERT_MEMBER_SIZE(name, size_in_bytes)
#else
#define ASSERT_MEMBER_SIZE(name, size_in_bytes) \
static_assert(sizeof(name) == size_in_bytes, \
"Structure size and register block length don't match")
#endif
// Components are laid out in reverse byte order, most significant bits first.
enum class PixelFormat : u32 {
@ -307,10 +281,6 @@ private:
};
static_assert(std::is_standard_layout<Regs>::value, "Structure does not use standard layout");
// TODO: MSVC does not support using offsetof() on non-static data members even though this
// is technically allowed since C++11. This macro should be enabled once MSVC adds
// support for that.
#ifndef _MSC_VER
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(Regs, field_name) == position * 4, \
"Field " #field_name " has invalid position")
@ -323,7 +293,6 @@ ASSERT_REG_POSITION(display_transfer_config, 0x00300);
ASSERT_REG_POSITION(command_processor_config, 0x00638);
#undef ASSERT_REG_POSITION
#endif // !defined(_MSC_VER)
// The total number of registers is chosen arbitrarily, but let's make sure it's not some odd value
// anyway.

View file

@ -309,8 +309,8 @@ ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileSys::RomFSReader>
if (!romfs_file_inner.IsOpen())
return ResultStatus::Error;
romfs_file = std::make_shared<FileSys::RomFSReader>(std::move(romfs_file_inner),
romfs_offset, romfs_size);
romfs_file = std::make_shared<FileSys::DirectRomFSReader>(std::move(romfs_file_inner),
romfs_offset, romfs_size);
return ResultStatus::Success;
}

View file

@ -105,13 +105,22 @@ public:
* Loads the system mode that this application needs.
* This function defaults to 2 (96MB allocated to the application) if it can't read the
* information.
* @returns A pair with the optional system mode, and and the status.
* @returns A pair with the optional system mode, and the status.
*/
virtual std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() {
// 96MB allocated to the application.
return std::make_pair(2, ResultStatus::Success);
}
/**
* Loads the N3ds mode that this application uses.
* It defaults to 0 (O3DS default) if it can't read the information.
* @returns A pair with the optional N3ds mode, and the status.
*/
virtual std::pair<std::optional<u8>, ResultStatus> LoadKernelN3dsMode() {
return std::make_pair(0, ResultStatus::Success);
}
/**
* Get whether this application is executable.
* @param out_executable Reference to store the executable flag into.
@ -186,6 +195,15 @@ public:
return ResultStatus::ErrorNotImplemented;
}
/**
* Dump the RomFS of the applciation
* @param target_path The target path to dump to
* @return ResultStatus result of function
*/
virtual ResultStatus DumpRomFS(const std::string& target_path) {
return ResultStatus::ErrorNotImplemented;
}
/**
* Get the update RomFS of the application
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
@ -196,6 +214,15 @@ public:
return ResultStatus::ErrorNotImplemented;
}
/**
* Dump the update RomFS of the applciation
* @param target_path The target path to dump to
* @return ResultStatus result of function
*/
virtual ResultStatus DumpUpdateRomFS(const std::string& target_path) {
return ResultStatus::ErrorNotImplemented;
}
/**
* Get the title of the application
* @param title Reference to store the application title into

View file

@ -61,6 +61,19 @@ std::pair<std::optional<u32>, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode
ResultStatus::Success);
}
std::pair<std::optional<u8>, ResultStatus> AppLoader_NCCH::LoadKernelN3dsMode() {
if (!is_loaded) {
ResultStatus res = base_ncch.Load();
if (res != ResultStatus::Success) {
return std::make_pair(std::optional<u8>{}, res);
}
}
// Set the system mode as the one from the exheader.
return std::make_pair(overlay_ncch->exheader_header.arm11_system_local_caps.n3ds_mode,
ResultStatus::Success);
}
ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr<Kernel::Process>& process) {
using Kernel::CodeSet;
@ -254,6 +267,18 @@ ResultStatus AppLoader_NCCH::ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReade
return ResultStatus::Success;
}
ResultStatus AppLoader_NCCH::DumpRomFS(const std::string& target_path) {
return base_ncch.DumpRomFS(target_path);
}
ResultStatus AppLoader_NCCH::DumpUpdateRomFS(const std::string& target_path) {
u64 program_id;
ReadProgramId(program_id);
update_ncch.OpenFile(
Service::AM::GetTitleContentPath(Service::FS::MediaType::SDMC, program_id | UPDATE_MASK));
return update_ncch.DumpRomFS(target_path);
}
ResultStatus AppLoader_NCCH::ReadTitle(std::string& title) {
std::vector<u8> data;
Loader::SMDH smdh;

View file

@ -41,6 +41,8 @@ public:
*/
std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() override;
std::pair<std::optional<u8>, ResultStatus> LoadKernelN3dsMode() override;
ResultStatus IsExecutable(bool& out_executable) override;
ResultStatus ReadCode(std::vector<u8>& buffer) override;
@ -59,6 +61,10 @@ public:
ResultStatus ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override;
ResultStatus DumpRomFS(const std::string& target_path) override;
ResultStatus DumpUpdateRomFS(const std::string& target_path) override;
ResultStatus ReadTitle(std::string& title) override;
private:

View file

@ -46,7 +46,9 @@ void RPCServer::HandleWriteMemory(Packet& packet, u32 address, const u8* data, u
Core::System::GetInstance().Memory().WriteBlock(
*Core::System::GetInstance().Kernel().GetCurrentProcess(), address, data, data_size);
// If the memory happens to be executable code, make sure the changes become visible
Core::CPU().InvalidateCacheRange(address, data_size);
// Is current core correct here?
Core::System::GetInstance().InvalidateCacheRange(address, data_size);
}
packet.SetPacketDataSize(0);
packet.SendReply();

View file

@ -14,7 +14,6 @@
#include "input_common/udp/client.h"
#include "input_common/udp/protocol.h"
using boost::asio::ip::address_v4;
using boost::asio::ip::udp;
namespace InputCommon::CemuhookUDP {
@ -31,10 +30,10 @@ public:
explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id,
SocketCallback callback)
: client_id(client_id), timer(io_service),
send_endpoint(udp::endpoint(address_v4::from_string(host), port)),
socket(io_service, udp::endpoint(udp::v4(), 0)), pad_index(pad_index),
callback(std::move(callback)) {}
: callback(std::move(callback)), timer(io_service),
socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id),
pad_index(pad_index),
send_endpoint(udp::endpoint(boost::asio::ip::make_address_v4(host), port)) {}
void Stop() {
io_service.stop();
@ -126,7 +125,7 @@ static void SocketLoop(Socket* socket) {
Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
u8 pad_index, u32 client_id)
: status(status) {
: status(std::move(status)) {
StartCommunication(host, port, pad_index, client_id);
}
@ -208,7 +207,7 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie
Common::Event success_event;
SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
[&](Response::PadData data) { success_event.Set(); }};
Socket socket{host, port, pad_index, client_id, callback};
Socket socket{host, port, pad_index, client_id, std::move(callback)};
std::thread worker_thread{SocketLoop, &socket};
bool result = success_event.WaitFor(std::chrono::seconds(8));
socket.Stop();
@ -264,7 +263,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
complete_event.Set();
}
}};
Socket socket{host, port, pad_index, client_id, callback};
Socket socket{host, port, pad_index, client_id, std::move(callback)};
std::thread worker_thread{SocketLoop, &socket};
complete_event.Wait();
socket.Stop();

View file

@ -11,7 +11,6 @@
#include <string>
#include <thread>
#include <tuple>
#include <vector>
#include "common/common_types.h"
#include "common/thread.h"
#include "common/vector_math.h"

View file

@ -7,7 +7,6 @@
#include <array>
#include <optional>
#include <type_traits>
#include <vector>
#include <boost/crc.hpp>
#include "common/bit_field.h"
#include "common/swap.h"

View file

@ -2,7 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include <mutex>
#include <tuple>
#include "common/param_package.h"
#include "core/frontend/input.h"
#include "core/settings.h"
@ -14,7 +15,7 @@ namespace InputCommon::CemuhookUDP {
class UDPTouchDevice final : public Input::TouchDevice {
public:
explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
std::tuple<float, float, bool> GetStatus() const {
std::tuple<float, float, bool> GetStatus() const override {
std::lock_guard guard(status->update_mutex);
return status->touch_status;
}
@ -26,7 +27,7 @@ private:
class UDPMotionDevice final : public Input::MotionDevice {
public:
explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const {
std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
std::lock_guard guard(status->update_mutex);
return status->motion_status;
}

View file

@ -2,16 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <unordered_map>
#include "input_common/main.h"
#include "input_common/udp/client.h"
namespace InputCommon::CemuhookUDP {
class UDPTouchDevice;
class UDPMotionDevice;
class State {
public:
State();

View file

@ -15,9 +15,9 @@ static std::shared_ptr<Memory::PageTable> page_table = nullptr;
TestEnvironment::TestEnvironment(bool mutable_memory_)
: mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) {
timing = std::make_unique<Core::Timing>();
timing = std::make_unique<Core::Timing>(1);
memory = std::make_unique<Memory::MemorySystem>();
kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing, [] {}, 0);
kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing, [] {}, 0, 1, 0);
kernel->SetCurrentProcess(kernel->CreateProcess(kernel->CreateCodeSet("", 0)));
page_table = kernel->GetCurrentProcess()->vm_manager.page_table;

View file

@ -23,7 +23,7 @@ TEST_CASE("ARM_DynCom (vfp): vadd", "[arm_dyncom]") {
test_env.SetMemory32(0, 0xEE321A03); // vadd.f32 s2, s4, s6
test_env.SetMemory32(4, 0xEAFFFFFE); // b +#0
ARM_DynCom dyncom(nullptr, test_env.GetMemory(), USER32MODE);
ARM_DynCom dyncom(nullptr, test_env.GetMemory(), USER32MODE, 0, nullptr);
std::vector<VfpTestCase> test_cases{{
#include "vfp_vadd_f32.inc"

View file

@ -34,16 +34,16 @@ static void AdvanceAndCheck(Core::Timing& timing, u32 idx, int downcount, int ex
expected_callback = CB_IDS[idx];
lateness = expected_lateness;
timing.AddTicks(timing.GetDowncount() -
cpu_downcount); // Pretend we executed X cycles of instructions.
timing.Advance();
timing.GetTimer(0)->AddTicks(timing.GetTimer(0)->GetDowncount() -
cpu_downcount); // Pretend we executed X cycles of instructions.
timing.GetTimer(0)->Advance();
REQUIRE(decltype(callbacks_ran_flags)().set(idx) == callbacks_ran_flags);
REQUIRE(downcount == timing.GetDowncount());
REQUIRE(downcount == timing.GetTimer(0)->GetDowncount());
}
TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
Core::Timing timing;
Core::Timing timing(1);
Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>);
@ -52,60 +52,19 @@ TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
Core::TimingEventType* cb_e = timing.RegisterEvent("callbackE", CallbackTemplate<4>);
// Enter slice 0
timing.Advance();
timing.GetTimer(0)->Advance();
// D -> B -> C -> A -> E
timing.ScheduleEvent(1000, cb_a, CB_IDS[0]);
REQUIRE(1000 == timing.GetDowncount());
timing.ScheduleEvent(500, cb_b, CB_IDS[1]);
REQUIRE(500 == timing.GetDowncount());
timing.ScheduleEvent(800, cb_c, CB_IDS[2]);
REQUIRE(500 == timing.GetDowncount());
timing.ScheduleEvent(100, cb_d, CB_IDS[3]);
REQUIRE(100 == timing.GetDowncount());
timing.ScheduleEvent(1200, cb_e, CB_IDS[4]);
REQUIRE(100 == timing.GetDowncount());
AdvanceAndCheck(timing, 3, 400);
AdvanceAndCheck(timing, 1, 300);
AdvanceAndCheck(timing, 2, 200);
AdvanceAndCheck(timing, 0, 200);
AdvanceAndCheck(timing, 4, MAX_SLICE_LENGTH);
}
TEST_CASE("CoreTiming[Threadsave]", "[core]") {
Core::Timing timing;
Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>);
Core::TimingEventType* cb_c = timing.RegisterEvent("callbackC", CallbackTemplate<2>);
Core::TimingEventType* cb_d = timing.RegisterEvent("callbackD", CallbackTemplate<3>);
Core::TimingEventType* cb_e = timing.RegisterEvent("callbackE", CallbackTemplate<4>);
// Enter slice 0
timing.Advance();
// D -> B -> C -> A -> E
timing.ScheduleEventThreadsafe(1000, cb_a, CB_IDS[0]);
// Manually force since ScheduleEventThreadsafe doesn't call it
timing.ForceExceptionCheck(1000);
REQUIRE(1000 == timing.GetDowncount());
timing.ScheduleEventThreadsafe(500, cb_b, CB_IDS[1]);
// Manually force since ScheduleEventThreadsafe doesn't call it
timing.ForceExceptionCheck(500);
REQUIRE(500 == timing.GetDowncount());
timing.ScheduleEventThreadsafe(800, cb_c, CB_IDS[2]);
// Manually force since ScheduleEventThreadsafe doesn't call it
timing.ForceExceptionCheck(800);
REQUIRE(500 == timing.GetDowncount());
timing.ScheduleEventThreadsafe(100, cb_d, CB_IDS[3]);
// Manually force since ScheduleEventThreadsafe doesn't call it
timing.ForceExceptionCheck(100);
REQUIRE(100 == timing.GetDowncount());
timing.ScheduleEventThreadsafe(1200, cb_e, CB_IDS[4]);
// Manually force since ScheduleEventThreadsafe doesn't call it
timing.ForceExceptionCheck(1200);
REQUIRE(100 == timing.GetDowncount());
timing.ScheduleEvent(1000, cb_a, CB_IDS[0], 0);
REQUIRE(1000 == timing.GetTimer(0)->GetDowncount());
timing.ScheduleEvent(500, cb_b, CB_IDS[1], 0);
REQUIRE(500 == timing.GetTimer(0)->GetDowncount());
timing.ScheduleEvent(800, cb_c, CB_IDS[2], 0);
REQUIRE(500 == timing.GetTimer(0)->GetDowncount());
timing.ScheduleEvent(100, cb_d, CB_IDS[3], 0);
REQUIRE(100 == timing.GetTimer(0)->GetDowncount());
timing.ScheduleEvent(1200, cb_e, CB_IDS[4], 0);
REQUIRE(100 == timing.GetTimer(0)->GetDowncount());
AdvanceAndCheck(timing, 3, 400);
AdvanceAndCheck(timing, 1, 300);
@ -131,7 +90,7 @@ void FifoCallback(u64 userdata, s64 cycles_late) {
TEST_CASE("CoreTiming[SharedSlot]", "[core]") {
using namespace SharedSlotTest;
Core::Timing timing;
Core::Timing timing(1);
Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", FifoCallback<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", FifoCallback<1>);
@ -139,36 +98,36 @@ TEST_CASE("CoreTiming[SharedSlot]", "[core]") {
Core::TimingEventType* cb_d = timing.RegisterEvent("callbackD", FifoCallback<3>);
Core::TimingEventType* cb_e = timing.RegisterEvent("callbackE", FifoCallback<4>);
timing.ScheduleEvent(1000, cb_a, CB_IDS[0]);
timing.ScheduleEvent(1000, cb_b, CB_IDS[1]);
timing.ScheduleEvent(1000, cb_c, CB_IDS[2]);
timing.ScheduleEvent(1000, cb_d, CB_IDS[3]);
timing.ScheduleEvent(1000, cb_e, CB_IDS[4]);
timing.ScheduleEvent(1000, cb_a, CB_IDS[0], 0);
timing.ScheduleEvent(1000, cb_b, CB_IDS[1], 0);
timing.ScheduleEvent(1000, cb_c, CB_IDS[2], 0);
timing.ScheduleEvent(1000, cb_d, CB_IDS[3], 0);
timing.ScheduleEvent(1000, cb_e, CB_IDS[4], 0);
// Enter slice 0
timing.Advance();
REQUIRE(1000 == timing.GetDowncount());
timing.GetTimer(0)->Advance();
REQUIRE(1000 == timing.GetTimer(0)->GetDowncount());
callbacks_ran_flags = 0;
counter = 0;
lateness = 0;
timing.AddTicks(timing.GetDowncount());
timing.Advance();
REQUIRE(MAX_SLICE_LENGTH == timing.GetDowncount());
timing.GetTimer(0)->AddTicks(timing.GetTimer(0)->GetDowncount());
timing.GetTimer(0)->Advance();
REQUIRE(MAX_SLICE_LENGTH == timing.GetTimer(0)->GetDowncount());
REQUIRE(0x1FULL == callbacks_ran_flags.to_ullong());
}
TEST_CASE("CoreTiming[PredictableLateness]", "[core]") {
Core::Timing timing;
Core::Timing timing(1);
Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>);
// Enter slice 0
timing.Advance();
timing.GetTimer(0)->Advance();
timing.ScheduleEvent(100, cb_a, CB_IDS[0]);
timing.ScheduleEvent(200, cb_b, CB_IDS[1]);
timing.ScheduleEvent(100, cb_a, CB_IDS[0], 0);
timing.ScheduleEvent(200, cb_b, CB_IDS[1], 0);
AdvanceAndCheck(timing, 0, 90, 10, -10); // (100 - 10)
AdvanceAndCheck(timing, 1, MAX_SLICE_LENGTH, 50, -50);
@ -190,7 +149,7 @@ static void RescheduleCallback(Core::Timing& timing, u64 userdata, s64 cycles_la
TEST_CASE("CoreTiming[ChainScheduling]", "[core]") {
using namespace ChainSchedulingTest;
Core::Timing timing;
Core::Timing timing(1);
Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>);
@ -201,28 +160,30 @@ TEST_CASE("CoreTiming[ChainScheduling]", "[core]") {
});
// Enter slice 0
timing.Advance();
timing.GetTimer(0)->Advance();
timing.ScheduleEvent(800, cb_a, CB_IDS[0]);
timing.ScheduleEvent(1000, cb_b, CB_IDS[1]);
timing.ScheduleEvent(2200, cb_c, CB_IDS[2]);
timing.ScheduleEvent(1000, cb_rs, reinterpret_cast<u64>(cb_rs));
REQUIRE(800 == timing.GetDowncount());
timing.ScheduleEvent(800, cb_a, CB_IDS[0], 0);
timing.ScheduleEvent(1000, cb_b, CB_IDS[1], 0);
timing.ScheduleEvent(2200, cb_c, CB_IDS[2], 0);
timing.ScheduleEvent(1000, cb_rs, reinterpret_cast<u64>(cb_rs), 0);
REQUIRE(800 == timing.GetTimer(0)->GetDowncount());
reschedules = 3;
AdvanceAndCheck(timing, 0, 200); // cb_a
AdvanceAndCheck(timing, 1, 1000); // cb_b, cb_rs
REQUIRE(2 == reschedules);
timing.AddTicks(timing.GetDowncount());
timing.Advance(); // cb_rs
timing.GetTimer(0)->AddTicks(timing.GetTimer(0)->GetDowncount());
timing.GetTimer(0)->Advance(); // cb_rs
REQUIRE(1 == reschedules);
REQUIRE(200 == timing.GetDowncount());
REQUIRE(200 == timing.GetTimer(0)->GetDowncount());
AdvanceAndCheck(timing, 2, 800); // cb_c
timing.AddTicks(timing.GetDowncount());
timing.Advance(); // cb_rs
timing.GetTimer(0)->AddTicks(timing.GetTimer(0)->GetDowncount());
timing.GetTimer(0)->Advance(); // cb_rs
REQUIRE(0 == reschedules);
REQUIRE(MAX_SLICE_LENGTH == timing.GetDowncount());
REQUIRE(MAX_SLICE_LENGTH == timing.GetTimer(0)->GetDowncount());
}
// TODO: Add tests for multiple timers

View file

@ -24,9 +24,9 @@ static std::shared_ptr<Object> MakeObject(Kernel::KernelSystem& kernel) {
}
TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") {
Core::Timing timing;
Core::Timing timing(1);
Memory::MemorySystem memory;
Kernel::KernelSystem kernel(memory, timing, [] {}, 0);
Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1, 0);
auto [server, client] = kernel.CreateSessionPair();
HLERequestContext context(kernel, std::move(server), nullptr);
@ -239,9 +239,9 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
}
TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") {
Core::Timing timing;
Core::Timing timing(1);
Memory::MemorySystem memory;
Kernel::KernelSystem kernel(memory, timing, [] {}, 0);
Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1, 0);
auto [server, client] = kernel.CreateSessionPair();
HLERequestContext context(kernel, std::move(server), nullptr);

View file

@ -11,9 +11,9 @@
#include "core/memory.h"
TEST_CASE("Memory::IsValidVirtualAddress", "[core][memory]") {
Core::Timing timing;
Core::Timing timing(1);
Memory::MemorySystem memory;
Kernel::KernelSystem kernel(memory, timing, [] {}, 0);
Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1, 0);
SECTION("these regions should not be mapped on an empty process") {
auto process = kernel.CreateProcess(kernel.CreateCodeSet("", 0));
CHECK(Memory::IsValidVirtualAddress(*process, Memory::PROCESS_IMAGE_VADDR) == false);

View file

@ -1926,7 +1926,7 @@ void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr,
}
void RasterizerCacheOpenGL::ClearAll(bool flush) {
const SurfaceInterval flush_interval(0x0, 0xFFFFFFFF);
const auto flush_interval = PageMap::interval_type::right_open(0x0, 0xFFFFFFFF);
// Force flush all surfaces from the cache
if (flush) {
FlushRegion(0x0, 0xFFFFFFFF);
@ -1945,8 +1945,8 @@ void RasterizerCacheOpenGL::ClearAll(bool flush) {
// Remove the whole cache without really looking at it.
cached_pages -= flush_interval;
dirty_regions -= flush_interval;
surface_cache -= flush_interval;
dirty_regions -= SurfaceInterval(0x0, 0xFFFFFFFF);
surface_cache -= SurfaceInterval(0x0, 0xFFFFFFFF);
remove_surfaces.clear();
}

View file

@ -80,11 +80,15 @@ struct CachedSurface;
using Surface = std::shared_ptr<CachedSurface>;
using SurfaceSet = std::set<Surface>;
using SurfaceRegions = boost::icl::interval_set<PAddr>;
using SurfaceMap = boost::icl::interval_map<PAddr, Surface>;
using SurfaceCache = boost::icl::interval_map<PAddr, SurfaceSet>;
using SurfaceInterval = boost::icl::right_open_interval<PAddr>;
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
using SurfaceMap =
boost::icl::interval_map<PAddr, Surface, boost::icl::partial_absorber, std::less,
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
using SurfaceCache =
boost::icl::interval_map<PAddr, SurfaceSet, boost::icl::partial_absorber, std::less,
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
using SurfaceInterval = SurfaceCache::interval_type;
static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() &&
std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(),
"incorrect interval types");
@ -101,6 +105,29 @@ enum class ScaleMatch {
};
struct SurfaceParams {
private:
static constexpr std::array<unsigned int, 18> BPP_TABLE = {
32, // RGBA8
24, // RGB8
16, // RGB5A1
16, // RGB565
16, // RGBA4
16, // IA8
16, // RG8
8, // I8
8, // A8
8, // IA4
4, // I4
4, // A4
4, // ETC1
8, // ETC1A4
16, // D16
0,
24, // D24
32, // D24S8
};
public:
enum class PixelFormat {
// First 5 formats are shared between textures and color buffers
RGBA8 = 0,
@ -139,30 +166,11 @@ struct SurfaceParams {
};
static constexpr unsigned int GetFormatBpp(PixelFormat format) {
constexpr std::array<unsigned int, 18> bpp_table = {
32, // RGBA8
24, // RGB8
16, // RGB5A1
16, // RGB565
16, // RGBA4
16, // IA8
16, // RG8
8, // I8
8, // A8
8, // IA4
4, // I4
4, // A4
4, // ETC1
8, // ETC1A4
16, // D16
0,
24, // D24
32, // D24S8
};
assert(static_cast<std::size_t>(format) < bpp_table.size());
return bpp_table[static_cast<std::size_t>(format)];
const auto format_idx = static_cast<std::size_t>(format);
DEBUG_ASSERT_MSG(format_idx < BPP_TABLE.size(), "Invalid pixel format {}", format_idx);
return BPP_TABLE[format_idx];
}
unsigned int GetFormatBpp() const {
return GetFormatBpp(pixel_format);
}
@ -245,7 +253,7 @@ struct SurfaceParams {
}
SurfaceInterval GetInterval() const {
return SurfaceInterval::right_open(addr, end);
return SurfaceInterval(addr, end);
}
// Returns the outer rectangle containing "interval"

View file

@ -18,3 +18,6 @@ get_directory_property(OPENSSL_LIBS
DEFINITION OPENSSL_LIBS)
target_compile_definitions(web_service PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(web_service PRIVATE common network json-headers ${OPENSSL_LIBS} httplib lurlparser cpp-jwt)
if (ANDROID)
target_link_libraries(web_service PRIVATE ifaddrs)
endif()

View file

@ -8,6 +8,9 @@
#include <string>
#include <LUrlParser.h>
#include <fmt/format.h>
#if defined(__ANDROID__)
#include <ifaddrs.h>
#endif
#include <httplib.h>
#include "common/common_types.h"
#include "common/logging/log.h"
@ -73,14 +76,14 @@ struct Client::Impl {
if (!parsedUrl.GetPort(&port)) {
port = HTTP_PORT;
}
cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port,
TIMEOUT_SECONDS);
cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port);
cli->set_timeout_sec(TIMEOUT_SECONDS);
} else if (parsedUrl.m_Scheme == "https") {
if (!parsedUrl.GetPort(&port)) {
port = HTTPS_PORT;
}
cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
TIMEOUT_SECONDS);
cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port);
cli->set_timeout_sec(TIMEOUT_SECONDS);
} else {
LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};