mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Merge branch 'master' into feature/savestates-2
This commit is contained in:
		
						commit
						da3ab3d56e
					
				
					 80 changed files with 7297 additions and 2608 deletions
				
			
		|  | @ -1,7 +1,13 @@ | ||||||
| #!/bin/bash -e | #!/bin/bash -e | ||||||
| 
 | 
 | ||||||
| # Setup RC file for tx | # 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 | set -x | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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) | 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) | if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit) | ||||||
|     message(STATUS "Copying pre-commit hook") |     message(STATUS "Copying pre-commit hook") | ||||||
|     file(COPY hooks/pre-commit |     file(COPY hooks/pre-commit | ||||||
|  | @ -218,6 +220,12 @@ if (ENABLE_FFMPEG_VIDEO_DUMPER) | ||||||
|     add_definitions(-DENABLE_FFMPEG_VIDEO_DUMPER) |     add_definitions(-DENABLE_FFMPEG_VIDEO_DUMPER) | ||||||
| endif() | 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 | # Platform-specific library requirements | ||||||
| # ====================================== | # ====================================== | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| Citra | Citra | ||||||
| ============== | ============== | ||||||
| [](https://travis-ci.org/citra-emu/citra) | [](https://travis-ci.com/citra-emu/citra) | ||||||
| [](https://ci.appveyor.com/project/bunnei/citra) | [](https://ci.appveyor.com/project/bunnei/citra) | ||||||
| [](https://app.bitrise.io/app/4ccd8e5720f0d13b) | [](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. | 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 | ### Development | ||||||
| 
 | 
 | ||||||
| Most of the development happens on GitHub. It's also where [our central repository](https://github.com/citra-emu/citra) is hosted. | 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. | 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 | * 3DS games for testing | ||||||
| * Any equipment required for homebrew | * Any equipment required for homebrew | ||||||
| * Infrastructure setup | * 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. | ||||||
|  |  | ||||||
|  | @ -52,7 +52,7 @@ workflows: | ||||||
|                 sudo apt remove cmake -y |                 sudo apt remove cmake -y | ||||||
|                 sudo apt purge --auto-remove cmake -y |                 sudo apt purge --auto-remove cmake -y | ||||||
|                 sudo apt install ninja-build -y |                 sudo apt install ninja-build -y | ||||||
|                 version=3.8 |                 version=3.10 | ||||||
|                 build=2 |                 build=2 | ||||||
|                 mkdir ~/temp |                 mkdir ~/temp | ||||||
|                 cd ~/temp |                 cd ~/temp | ||||||
|  | @ -97,7 +97,7 @@ workflows: | ||||||
|                 sudo apt remove cmake -y |                 sudo apt remove cmake -y | ||||||
|                 sudo apt purge --auto-remove cmake -y |                 sudo apt purge --auto-remove cmake -y | ||||||
|                 sudo apt install ninja-build -y |                 sudo apt install ninja-build -y | ||||||
|                 version=3.8 |                 version=3.10 | ||||||
|                 build=2 |                 build=2 | ||||||
|                 mkdir ~/temp |                 mkdir ~/temp | ||||||
|                 cd ~/temp |                 cd ~/temp | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								externals/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								externals/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							|  | @ -115,9 +115,14 @@ if (ENABLE_WEB_SERVICE) | ||||||
|     # lurlparser |     # lurlparser | ||||||
|     add_subdirectory(lurlparser EXCLUDE_FROM_ALL) |     add_subdirectory(lurlparser EXCLUDE_FROM_ALL) | ||||||
| 
 | 
 | ||||||
|  |     if(ANDROID) | ||||||
|  |         add_subdirectory(android-ifaddrs) | ||||||
|  |     endif() | ||||||
|  | 
 | ||||||
|     # httplib |     # httplib | ||||||
|     add_library(httplib INTERFACE) |     add_library(httplib INTERFACE) | ||||||
|     target_include_directories(httplib INTERFACE ./httplib) |     target_include_directories(httplib INTERFACE ./httplib) | ||||||
|  |     target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT) | ||||||
| 
 | 
 | ||||||
|     # cpp-jwt |     # cpp-jwt | ||||||
|     add_library(cpp-jwt INTERFACE) |     add_library(cpp-jwt INTERFACE) | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								externals/android-ifaddrs/CMakeLists.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								externals/android-ifaddrs/CMakeLists.txt
									
										
									
									
										vendored
									
									
										Normal 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
									
								
							
							
						
						
									
										600
									
								
								externals/android-ifaddrs/ifaddrs.c
									
										
									
									
										vendored
									
									
										Normal 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
									
								
							
							
						
						
									
										54
									
								
								externals/android-ifaddrs/ifaddrs.h
									
										
									
									
										vendored
									
									
										Normal 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
									
									
								
							
							
						
						
									
										2
									
								
								externals/boost
									
										
									
									
										vendored
									
									
								
							|  | @ -1 +1 @@ | ||||||
| Subproject commit 6d7edc593be8e47c8de7bc5f7d6b32971fad0c24 | Subproject commit 727f616b6e5cafaba072131c077a3b8fea87b8be | ||||||
							
								
								
									
										3
									
								
								externals/httplib/README.md
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								externals/httplib/README.md
									
										
									
									
										vendored
									
									
								
							|  | @ -1,4 +1,4 @@ | ||||||
| From https://github.com/yhirose/cpp-httplib/commit/d9479bc0b12e8a1e8bce2d34da4feeef488581f3 | From https://github.com/yhirose/cpp-httplib/commit/b251668522dd459d2c6a75c10390a11b640be708 | ||||||
| 
 | 
 | ||||||
| MIT License | 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. | Inspired by Sinatra and express. | ||||||
| 
 | 
 | ||||||
| © 2017 Yuji Hirose | © 2017 Yuji Hirose | ||||||
|  | 
 | ||||||
|  |  | ||||||
							
								
								
									
										5282
									
								
								externals/httplib/httplib.h
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										5282
									
								
								externals/httplib/httplib.h
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -62,6 +62,13 @@ elseif(ENABLE_FFMPEG_AUDIO_DECODER) | ||||||
|         target_include_directories(audio_core PRIVATE ${FFMPEG_DIR}/include) |         target_include_directories(audio_core PRIVATE ${FFMPEG_DIR}/include) | ||||||
|     endif() |     endif() | ||||||
|     target_compile_definitions(audio_core PUBLIC HAVE_FFMPEG) |     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() | endif() | ||||||
| 
 | 
 | ||||||
| if(SDL2_FOUND) | if(SDL2_FOUND) | ||||||
|  |  | ||||||
							
								
								
									
										233
									
								
								src/audio_core/hle/fdk_decoder.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								src/audio_core/hle/fdk_decoder.cpp
									
										
									
									
									
										Normal 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
 | ||||||
							
								
								
									
										23
									
								
								src/audio_core/hle/fdk_decoder.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/audio_core/hle/fdk_decoder.h
									
										
									
									
									
										Normal 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
 | ||||||
|  | @ -13,6 +13,8 @@ | ||||||
| #include "audio_core/hle/wmf_decoder.h" | #include "audio_core/hle/wmf_decoder.h" | ||||||
| #elif HAVE_FFMPEG | #elif HAVE_FFMPEG | ||||||
| #include "audio_core/hle/ffmpeg_decoder.h" | #include "audio_core/hle/ffmpeg_decoder.h" | ||||||
|  | #elif HAVE_FDK | ||||||
|  | #include "audio_core/hle/fdk_decoder.h" | ||||||
| #endif | #endif | ||||||
| #include "audio_core/hle/common.h" | #include "audio_core/hle/common.h" | ||||||
| #include "audio_core/hle/decoder.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); |     decoder = std::make_unique<HLE::WMFDecoder>(memory); | ||||||
| #elif defined(HAVE_FFMPEG) | #elif defined(HAVE_FFMPEG) | ||||||
|     decoder = std::make_unique<HLE::FFMPEGDecoder>(memory); |     decoder = std::make_unique<HLE::FFMPEGDecoder>(memory); | ||||||
|  | #elif defined(HAVE_FDK) | ||||||
|  |     decoder = std::make_unique<HLE::FDKDecoder>(memory); | ||||||
| #else | #else | ||||||
|     LOG_WARNING(Audio_DSP, "No decoder found, this could lead to missing audio"); |     LOG_WARNING(Audio_DSP, "No decoder found, this could lead to missing audio"); | ||||||
|     decoder = std::make_unique<HLE::NullDecoder>(); |     decoder = std::make_unique<HLE::NullDecoder>(); | ||||||
|  |  | ||||||
|  | @ -25,10 +25,6 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) | ||||||
| ConfigureGeneral::~ConfigureGeneral() = default; | ConfigureGeneral::~ConfigureGeneral() = default; | ||||||
| 
 | 
 | ||||||
| void ConfigureGeneral::SetConfiguration() { | 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_check_exit->setChecked(UISettings::values.confirm_before_closing); | ||||||
|     ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background); |     ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background); | ||||||
| 
 | 
 | ||||||
|  | @ -57,9 +53,6 @@ void ConfigureGeneral::ResetDefaults() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ConfigureGeneral::ApplyConfiguration() { | 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.confirm_before_closing = ui->toggle_check_exit->isChecked(); | ||||||
|     UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); |     UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -114,7 +114,7 @@ void IPCRecorderWidget::SetEnabled(bool enabled) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void IPCRecorderWidget::Clear() { | void IPCRecorderWidget::Clear() { | ||||||
|     id_offset = records.size() + 1; |     id_offset += records.size(); | ||||||
| 
 | 
 | ||||||
|     records.clear(); |     records.clear(); | ||||||
|     ui->main->invisibleRootItem()->takeChildren(); |     ui->main->invisibleRootItem()->takeChildren(); | ||||||
|  |  | ||||||
|  | @ -61,13 +61,14 @@ void RegistersWidget::OnDebugModeEntered() { | ||||||
|     if (!Core::System::GetInstance().IsPoweredOn()) |     if (!Core::System::GetInstance().IsPoweredOn()) | ||||||
|         return; |         return; | ||||||
| 
 | 
 | ||||||
|  |     // Todo: Handle all cores
 | ||||||
|     for (int i = 0; i < core_registers->childCount(); ++i) |     for (int i = 0; i < core_registers->childCount(); ++i) | ||||||
|         core_registers->child(i)->setText( |         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) |     for (int i = 0; i < vfp_registers->childCount(); ++i) | ||||||
|         vfp_registers->child(i)->setText( |         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(); |     UpdateCPSRValues(); | ||||||
|     UpdateVFPSystemRegisterValues(); |     UpdateVFPSystemRegisterValues(); | ||||||
|  | @ -127,7 +128,8 @@ void RegistersWidget::CreateCPSRChildren() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RegistersWidget::UpdateCPSRValues() { | 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->setText(1, QStringLiteral("0x%1").arg(cpsr_val, 8, 16, QLatin1Char('0'))); | ||||||
|     cpsr->child(0)->setText( |     cpsr->child(0)->setText( | ||||||
|  | @ -191,10 +193,11 @@ void RegistersWidget::CreateVFPSystemRegisterChildren() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RegistersWidget::UpdateVFPSystemRegisterValues() { | void RegistersWidget::UpdateVFPSystemRegisterValues() { | ||||||
|     const u32 fpscr_val = Core::CPU().GetVFPSystemReg(VFP_FPSCR); |     // Todo: handle all cores
 | ||||||
|     const u32 fpexc_val = Core::CPU().GetVFPSystemReg(VFP_FPEXC); |     const u32 fpscr_val = Core::GetCore(0).GetVFPSystemReg(VFP_FPSCR); | ||||||
|     const u32 fpinst_val = Core::CPU().GetVFPSystemReg(VFP_FPINST); |     const u32 fpexc_val = Core::GetCore(0).GetVFPSystemReg(VFP_FPEXC); | ||||||
|     const u32 fpinst2_val = Core::CPU().GetVFPSystemReg(VFP_FPINST2); |     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); |     QTreeWidgetItem* const fpscr = vfp_system_registers->child(0); | ||||||
|     fpscr->setText(1, QStringLiteral("0x%1").arg(fpscr_val, 8, 16, QLatin1Char('0'))); |     fpscr->setText(1, QStringLiteral("0x%1").arg(fpscr_val, 8, 16, QLatin1Char('0'))); | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ | ||||||
| #include "core/hle/kernel/thread.h" | #include "core/hle/kernel/thread.h" | ||||||
| #include "core/hle/kernel/timer.h" | #include "core/hle/kernel/timer.h" | ||||||
| #include "core/hle/kernel/wait_object.h" | #include "core/hle/kernel/wait_object.h" | ||||||
|  | #include "core/settings.h" | ||||||
| 
 | 
 | ||||||
| WaitTreeItem::~WaitTreeItem() = default; | WaitTreeItem::~WaitTreeItem() = default; | ||||||
| 
 | 
 | ||||||
|  | @ -51,13 +52,17 @@ std::size_t WaitTreeItem::Row() const { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() { | 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; |     std::vector<std::unique_ptr<WaitTreeThread>> item_list; | ||||||
|     item_list.reserve(threads.size()); |     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) { |         for (std::size_t i = 0; i < threads.size(); ++i) { | ||||||
|             item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i])); |             item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i])); | ||||||
|             item_list.back()->row = i; |             item_list.back()->row = i; | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|     return item_list; |     return item_list; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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_dump_location = context_menu.addAction(tr("Open Texture Dump Location")); | ||||||
|     QAction* open_texture_load_location = |     QAction* open_texture_load_location = | ||||||
|         context_menu.addAction(tr("Open Custom Texture 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")); |     QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); | ||||||
| 
 | 
 | ||||||
|     const bool is_application = |     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_dump_location->setVisible(is_application); | ||||||
|     open_texture_load_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()); |     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); |             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]() { |     connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { | ||||||
|         emit NavigateToGamedbEntryRequested(program_id, compatibility_list); |         emit NavigateToGamedbEntryRequested(program_id, compatibility_list); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -35,7 +35,8 @@ enum class GameListOpenTarget { | ||||||
|     APPLICATION = 2, |     APPLICATION = 2, | ||||||
|     UPDATE_DATA = 3, |     UPDATE_DATA = 3, | ||||||
|     TEXTURE_DUMP = 4, |     TEXTURE_DUMP = 4, | ||||||
|     TEXTURE_LOAD = 5 |     TEXTURE_LOAD = 5, | ||||||
|  |     MODS = 6, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class GameList : public QWidget { | class GameList : public QWidget { | ||||||
|  | @ -81,6 +82,7 @@ signals: | ||||||
|     void OpenFolderRequested(u64 program_id, GameListOpenTarget target); |     void OpenFolderRequested(u64 program_id, GameListOpenTarget target); | ||||||
|     void NavigateToGamedbEntryRequested(u64 program_id, |     void NavigateToGamedbEntryRequested(u64 program_id, | ||||||
|                                         const CompatibilityList& compatibility_list); |                                         const CompatibilityList& compatibility_list); | ||||||
|  |     void DumpRomFSRequested(QString game_path, u64 program_id); | ||||||
|     void OpenDirectory(const QString& directory); |     void OpenDirectory(const QString& directory); | ||||||
|     void AddDirectory(); |     void AddDirectory(); | ||||||
|     void ShowList(bool show); |     void ShowList(bool show); | ||||||
|  |  | ||||||
|  | @ -597,6 +597,7 @@ void GMainWindow::ConnectWidgetEvents() { | ||||||
|     connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); |     connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); | ||||||
|     connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, |     connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, | ||||||
|             &GMainWindow::OnGameListNavigateToGamedbEntry); |             &GMainWindow::OnGameListNavigateToGamedbEntry); | ||||||
|  |     connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); | ||||||
|     connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory); |     connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory); | ||||||
|     connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this, |     connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this, | ||||||
|             &GMainWindow::OnGameListAddDirectory); |             &GMainWindow::OnGameListAddDirectory); | ||||||
|  | @ -1231,6 +1232,11 @@ void GMainWindow::OnGameListOpenFolder(u64 data_id, GameListOpenTarget target) { | ||||||
|         path = fmt::format("{}textures/{:016X}/", |         path = fmt::format("{}textures/{:016X}/", | ||||||
|                            FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), data_id); |                            FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), data_id); | ||||||
|         break; |         break; | ||||||
|  |     case GameListOpenTarget::MODS: | ||||||
|  |         open_target = "Mods"; | ||||||
|  |         path = fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), | ||||||
|  |                            data_id); | ||||||
|  |         break; | ||||||
|     default: |     default: | ||||||
|         LOG_ERROR(Frontend, "Unexpected target {}", static_cast<int>(target)); |         LOG_ERROR(Frontend, "Unexpected target {}", static_cast<int>(target)); | ||||||
|         return; |         return; | ||||||
|  | @ -1262,6 +1268,46 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, | ||||||
|     QDesktopServices::openUrl(QUrl(QStringLiteral("https://citra-emu.org/game/") + directory)); |     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) { | void GMainWindow::OnGameListOpenDirectory(const QString& directory) { | ||||||
|     QString path; |     QString path; | ||||||
|     if (directory == QStringLiteral("INSTALLED")) { |     if (directory == QStringLiteral("INSTALLED")) { | ||||||
|  |  | ||||||
|  | @ -176,6 +176,7 @@ private slots: | ||||||
|     void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); |     void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); | ||||||
|     void OnGameListNavigateToGamedbEntry(u64 program_id, |     void OnGameListNavigateToGamedbEntry(u64 program_id, | ||||||
|                                          const CompatibilityList& compatibility_list); |                                          const CompatibilityList& compatibility_list); | ||||||
|  |     void OnGameListDumpRomFS(QString game_path, u64 program_id); | ||||||
|     void OnGameListOpenDirectory(const QString& directory); |     void OnGameListOpenDirectory(const QString& directory); | ||||||
|     void OnGameListAddDirectory(); |     void OnGameListAddDirectory(); | ||||||
|     void OnGameListShowList(bool show); |     void OnGameListShowList(bool show); | ||||||
|  |  | ||||||
|  | @ -72,6 +72,8 @@ add_library(core STATIC | ||||||
|     file_sys/delay_generator.h |     file_sys/delay_generator.h | ||||||
|     file_sys/ivfc_archive.cpp |     file_sys/ivfc_archive.cpp | ||||||
|     file_sys/ivfc_archive.h |     file_sys/ivfc_archive.h | ||||||
|  |     file_sys/layered_fs.cpp | ||||||
|  |     file_sys/layered_fs.h | ||||||
|     file_sys/ncch_container.cpp |     file_sys/ncch_container.cpp | ||||||
|     file_sys/ncch_container.h |     file_sys/ncch_container.h | ||||||
|     file_sys/patch.cpp |     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 common PRIVATE audio_core network video_core) | ||||||
| target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt open_source_archives Boost::serialization) | target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt open_source_archives Boost::serialization) | ||||||
|  | 
 | ||||||
| if (ENABLE_WEB_SERVICE) | if (ENABLE_WEB_SERVICE) | ||||||
|     target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) |     get_directory_property(OPENSSL_LIBS | ||||||
|     target_link_libraries(core PRIVATE web_service) |         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() | endif() | ||||||
| 
 | 
 | ||||||
| if (ARCHITECTURE_x86_64) | if (ARCHITECTURE_x86_64) | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/arm/skyeye_common/arm_regformat.h" | #include "core/arm/skyeye_common/arm_regformat.h" | ||||||
| #include "core/arm/skyeye_common/vfp/asm_vfp.h" | #include "core/arm/skyeye_common/vfp/asm_vfp.h" | ||||||
|  | #include "core/core_timing.h" | ||||||
| 
 | 
 | ||||||
| namespace Memory { | namespace Memory { | ||||||
| struct PageTable; | struct PageTable; | ||||||
|  | @ -18,6 +19,8 @@ struct PageTable; | ||||||
| /// Generic ARM11 CPU interface
 | /// Generic ARM11 CPU interface
 | ||||||
| class ARM_Interface : NonCopyable { | class ARM_Interface : NonCopyable { | ||||||
| public: | public: | ||||||
|  |     explicit ARM_Interface(u32 id, std::shared_ptr<Core::Timing::Timer> timer) | ||||||
|  |         : timer(timer), id(id){}; | ||||||
|     virtual ~ARM_Interface() {} |     virtual ~ARM_Interface() {} | ||||||
| 
 | 
 | ||||||
|     class ThreadContext { |     class ThreadContext { | ||||||
|  | @ -222,11 +225,26 @@ public: | ||||||
| 
 | 
 | ||||||
|     virtual void PurgeState() = 0; |     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: | private: | ||||||
|  |     u32 id; | ||||||
|  | 
 | ||||||
|     friend class boost::serialization::access; |     friend class boost::serialization::access; | ||||||
| 
 | 
 | ||||||
|     template <class Archive> |     template <class Archive> | ||||||
|     void save(Archive& ar, const unsigned int file_version) const { |     void save(Archive& ar, const unsigned int file_version) const { | ||||||
|  |         ar << timer; | ||||||
|  |         ar << id; | ||||||
|         auto page_table = GetPageTable(); |         auto page_table = GetPageTable(); | ||||||
|         ar << page_table; |         ar << page_table; | ||||||
|         for (auto i = 0; i < 15; i++) { |         for (auto i = 0; i < 15; i++) { | ||||||
|  | @ -254,6 +272,8 @@ private: | ||||||
|     template <class Archive> |     template <class Archive> | ||||||
|     void load(Archive& ar, const unsigned int file_version) { |     void load(Archive& ar, const unsigned int file_version) { | ||||||
|         PurgeState(); |         PurgeState(); | ||||||
|  |         ar >> timer; | ||||||
|  |         ar >> id; | ||||||
|         std::shared_ptr<Memory::PageTable> page_table = nullptr; |         std::shared_ptr<Memory::PageTable> page_table = nullptr; | ||||||
|         ar >> page_table; |         ar >> page_table; | ||||||
|         SetPageTable(page_table); |         SetPageTable(page_table); | ||||||
|  |  | ||||||
|  | @ -72,8 +72,7 @@ private: | ||||||
| class DynarmicUserCallbacks final : public Dynarmic::A32::UserCallbacks { | class DynarmicUserCallbacks final : public Dynarmic::A32::UserCallbacks { | ||||||
| public: | public: | ||||||
|     explicit DynarmicUserCallbacks(ARM_Dynarmic& parent) |     explicit DynarmicUserCallbacks(ARM_Dynarmic& parent) | ||||||
|         : parent(parent), timing(parent.system.CoreTiming()), svc_context(parent.system), |         : parent(parent), svc_context(parent.system), memory(parent.memory) {} | ||||||
|           memory(parent.memory) {} |  | ||||||
|     ~DynarmicUserCallbacks() = default; |     ~DynarmicUserCallbacks() = default; | ||||||
| 
 | 
 | ||||||
|     std::uint8_t MemoryRead8(VAddr vaddr) override { |     std::uint8_t MemoryRead8(VAddr vaddr) override { | ||||||
|  | @ -137,7 +136,7 @@ public: | ||||||
|                 parent.jit->HaltExecution(); |                 parent.jit->HaltExecution(); | ||||||
|                 parent.SetPC(pc); |                 parent.SetPC(pc); | ||||||
|                 Kernel::Thread* thread = |                 Kernel::Thread* thread = | ||||||
|                     parent.system.Kernel().GetThreadManager().GetCurrentThread(); |                     parent.system.Kernel().GetCurrentThreadManager().GetCurrentThread(); | ||||||
|                 parent.SaveContext(thread->context); |                 parent.SaveContext(thread->context); | ||||||
|                 GDBStub::Break(); |                 GDBStub::Break(); | ||||||
|                 GDBStub::SendTrap(thread, 5); |                 GDBStub::SendTrap(thread, 5); | ||||||
|  | @ -150,22 +149,23 @@ public: | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void AddTicks(std::uint64_t ticks) override { |     void AddTicks(std::uint64_t ticks) override { | ||||||
|         timing.AddTicks(ticks); |         parent.GetTimer()->AddTicks(ticks); | ||||||
|     } |     } | ||||||
|     std::uint64_t GetTicksRemaining() override { |     std::uint64_t GetTicksRemaining() override { | ||||||
|         s64 ticks = timing.GetDowncount(); |         s64 ticks = parent.GetTimer()->GetDowncount(); | ||||||
|         return static_cast<u64>(ticks <= 0 ? 0 : ticks); |         return static_cast<u64>(ticks <= 0 ? 0 : ticks); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ARM_Dynarmic& parent; |     ARM_Dynarmic& parent; | ||||||
|     Core::Timing& timing; |  | ||||||
|     Kernel::SVCContext svc_context; |     Kernel::SVCContext svc_context; | ||||||
|     Memory::MemorySystem& memory; |     Memory::MemorySystem& memory; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| ARM_Dynarmic::ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, | ARM_Dynarmic::ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, | ||||||
|                            PrivilegeMode initial_mode) |                            PrivilegeMode initial_mode, u32 id, | ||||||
|     : system(*system), memory(memory), cb(std::make_unique<DynarmicUserCallbacks>(*this)) { |                            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); |     interpreter_state = std::make_shared<ARMul_State>(system, memory, initial_mode); | ||||||
|     SetPageTable(memory.GetCurrentPageTable()); |     SetPageTable(memory.GetCurrentPageTable()); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -24,7 +24,8 @@ class DynarmicUserCallbacks; | ||||||
| 
 | 
 | ||||||
| class ARM_Dynarmic final : public ARM_Interface { | class ARM_Dynarmic final : public ARM_Interface { | ||||||
| public: | 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; |     ~ARM_Dynarmic() override; | ||||||
| 
 | 
 | ||||||
|     void Run() override; |     void Run() override; | ||||||
|  |  | ||||||
|  | @ -69,8 +69,9 @@ private: | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| ARM_DynCom::ARM_DynCom(Core::System* system, Memory::MemorySystem& memory, | ARM_DynCom::ARM_DynCom(Core::System* system, Memory::MemorySystem& memory, | ||||||
|                        PrivilegeMode initial_mode) |                        PrivilegeMode initial_mode, u32 id, | ||||||
|     : system(system) { |                        std::shared_ptr<Core::Timing::Timer> timer) | ||||||
|  |     : ARM_Interface(id, timer), system(system) { | ||||||
|     state = std::make_unique<ARMul_State>(system, memory, initial_mode); |     state = std::make_unique<ARMul_State>(system, memory, initial_mode); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -78,7 +79,7 @@ ARM_DynCom::~ARM_DynCom() {} | ||||||
| 
 | 
 | ||||||
| void ARM_DynCom::Run() { | void ARM_DynCom::Run() { | ||||||
|     DEBUG_ASSERT(system != nullptr); |     DEBUG_ASSERT(system != nullptr); | ||||||
|     ExecuteInstructions(std::max<s64>(system->CoreTiming().GetDowncount(), 0)); |     ExecuteInstructions(std::max<s64>(timer->GetDowncount(), 0)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ARM_DynCom::Step() { | void ARM_DynCom::Step() { | ||||||
|  | @ -156,7 +157,7 @@ void ARM_DynCom::ExecuteInstructions(u64 num_instructions) { | ||||||
|     state->NumInstrsToExecute = num_instructions; |     state->NumInstrsToExecute = num_instructions; | ||||||
|     unsigned ticks_executed = InterpreterMainLoop(state.get()); |     unsigned ticks_executed = InterpreterMainLoop(state.get()); | ||||||
|     if (system != nullptr) { |     if (system != nullptr) { | ||||||
|         system->CoreTiming().AddTicks(ticks_executed); |         timer->AddTicks(ticks_executed); | ||||||
|     } |     } | ||||||
|     state->ServeBreak(); |     state->ServeBreak(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -21,7 +21,8 @@ class MemorySystem; | ||||||
| class ARM_DynCom final : public ARM_Interface { | class ARM_DynCom final : public ARM_Interface { | ||||||
| public: | public: | ||||||
|     explicit ARM_DynCom(Core::System* system, Memory::MemorySystem& memory, |     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; |     ~ARM_DynCom() override; | ||||||
| 
 | 
 | ||||||
|     void Run() override; |     void Run() override; | ||||||
|  |  | ||||||
|  | @ -3865,7 +3865,7 @@ SWI_INST : { | ||||||
|     if (inst_base->cond == ConditionCode::AL || CondPassed(cpu, inst_base->cond)) { |     if (inst_base->cond == ConditionCode::AL || CondPassed(cpu, inst_base->cond)) { | ||||||
|         DEBUG_ASSERT(cpu->system != nullptr); |         DEBUG_ASSERT(cpu->system != nullptr); | ||||||
|         swi_inst* const inst_cream = (swi_inst*)inst_base->component; |         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 = |         cpu->NumInstrsToExecute = | ||||||
|             num_instrs >= cpu->NumInstrsToExecute ? 0 : cpu->NumInstrsToExecute - num_instrs; |             num_instrs >= cpu->NumInstrsToExecute ? 0 : cpu->NumInstrsToExecute - num_instrs; | ||||||
|         num_instrs = 0; |         num_instrs = 0; | ||||||
|  |  | ||||||
|  | @ -607,8 +607,8 @@ void ARMul_State::ServeBreak() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     DEBUG_ASSERT(system != nullptr); |     DEBUG_ASSERT(system != nullptr); | ||||||
|     Kernel::Thread* thread = system->Kernel().GetThreadManager().GetCurrentThread(); |     Kernel::Thread* thread = system->Kernel().GetCurrentThreadManager().GetCurrentThread(); | ||||||
|     system->CPU().SaveContext(thread->context); |     system->GetRunningCore().SaveContext(thread->context); | ||||||
| 
 | 
 | ||||||
|     if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) { |     if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) { | ||||||
|         last_bkpt_hit = false; |         last_bkpt_hit = false; | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ static inline std::enable_if_t<std::is_integral_v<T>> WriteOp(const GatewayCheat | ||||||
|                                                               Core::System& system) { |                                                               Core::System& system) { | ||||||
|     u32 addr = line.address + state.offset; |     u32 addr = line.address + state.offset; | ||||||
|     write_func(addr, static_cast<T>(line.value)); |     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> | 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) { |     Core::System& system) { | ||||||
|     u32 addr = line.value + state.offset; |     u32 addr = line.value + state.offset; | ||||||
|     write_func(addr, static_cast<T>(state.reg)); |     write_func(addr, static_cast<T>(state.reg)); | ||||||
|     system.CPU().InvalidateCacheRange(addr, sizeof(T)); |     system.InvalidateCacheRange(addr, sizeof(T)); | ||||||
|     state.offset += 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 num_bytes = line.value; | ||||||
|     u32 addr = line.address + state.offset; |     u32 addr = line.address + state.offset; | ||||||
|     system.CPU().InvalidateCacheRange(addr, num_bytes); |     system.InvalidateCacheRange(addr, num_bytes); | ||||||
|  | 
 | ||||||
|     bool first = true; |     bool first = true; | ||||||
|     u32 bit_offset = 0; |     u32 bit_offset = 0; | ||||||
|     if (num_bytes > 0) |     if (num_bytes > 0) | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <fstream> | #include <fstream> | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include <stdexcept> | ||||||
| #include <utility> | #include <utility> | ||||||
| #include <boost/serialization/array.hpp> | #include <boost/serialization/array.hpp> | ||||||
| #include "audio_core/dsp_interface.h" | #include "audio_core/dsp_interface.h" | ||||||
|  | @ -65,7 +66,8 @@ System::~System() = default; | ||||||
| 
 | 
 | ||||||
| System::ResultStatus System::RunLoop(bool tight_loop) { | System::ResultStatus System::RunLoop(bool tight_loop) { | ||||||
|     status = ResultStatus::Success; |     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; |         return ResultStatus::ErrorNotInitialized; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -83,21 +85,72 @@ System::ResultStatus System::RunLoop(bool tight_loop) { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // If we don't have a currently active thread then don't execute instructions,
 |     // All cores should have executed the same amount of ticks. If this is not the case an event was
 | ||||||
|     // instead advance to the next event and try to yield to the next thread
 |     // scheduled with a cycles_into_future smaller then the current downcount.
 | ||||||
|     if (kernel->GetThreadManager().GetCurrentThread() == nullptr) { |     // So we have to get those cores to the same global time first
 | ||||||
|         LOG_TRACE(Core_ARM11, "Idling"); |     u64 global_ticks = timing->GetGlobalTicks(); | ||||||
|         timing->Idle(); |     s64 max_delay = 0; | ||||||
|         timing->Advance(); |     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(); |                 PrepareReschedule(); | ||||||
|             } else { |             } else { | ||||||
|         timing->Advance(); |  | ||||||
|                 if (tight_loop) { |                 if (tight_loop) { | ||||||
|                     cpu_core->Run(); |                     cpu_core->Run(); | ||||||
|                 } else { |                 } else { | ||||||
|                     cpu_core->Step(); |                     cpu_core->Step(); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |         timing->AddToGlobalTicks(max_slice); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if (GDBStub::IsServerEnabled()) { |     if (GDBStub::IsServerEnabled()) { | ||||||
|         GDBStub::SetCpuStepFlag(false); |         GDBStub::SetCpuStepFlag(false); | ||||||
|  | @ -183,7 +236,9 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ASSERT(system_mode.first); |     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) { |     if (init_result != ResultStatus::Success) { | ||||||
|         LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", |         LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", | ||||||
|                      static_cast<u32>(init_result)); |                      static_cast<u32>(init_result)); | ||||||
|  | @ -235,7 +290,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void System::PrepareReschedule() { | void System::PrepareReschedule() { | ||||||
|     cpu_core->PrepareReschedule(); |     running_core->PrepareReschedule(); | ||||||
|     reschedule_pending = true; |     reschedule_pending = true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -249,31 +304,50 @@ void System::Reschedule() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     reschedule_pending = false; |     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"); |     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>(); |     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, |     kernel = std::make_unique<Kernel::KernelSystem>( | ||||||
|                                                     [this] { PrepareReschedule(); }, system_mode); |         *memory, *timing, [this] { PrepareReschedule(); }, system_mode, num_cores, n3ds_mode); | ||||||
| 
 | 
 | ||||||
|     if (Settings::values.use_cpu_jit) { |     if (Settings::values.use_cpu_jit) { | ||||||
| #ifdef ARCHITECTURE_x86_64 | #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 | #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"); |         LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); | ||||||
| #endif | #endif | ||||||
|     } else { |     } 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) { |     if (Settings::values.enable_dsp_lle) { | ||||||
|         dsp_core = std::make_unique<AudioCore::DspLle>(*memory, |         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); |     HW::Init(*memory); | ||||||
|     Service::Init(*this); |     Service::Init(*this); | ||||||
|     GDBStub::Init(); |     GDBStub::DeferStart(); | ||||||
| 
 | 
 | ||||||
|     VideoCore::ResultStatus result = VideoCore::Init(emu_window, *memory); |     VideoCore::ResultStatus result = VideoCore::Init(emu_window, *memory); | ||||||
|     if (result != VideoCore::ResultStatus::Success) { |     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"); |     LOG_DEBUG(Core, "Initialized OK"); | ||||||
| 
 | 
 | ||||||
|  |     initalized = true; | ||||||
|  | 
 | ||||||
|     return ResultStatus::Success; |     return ResultStatus::Success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -421,9 +497,10 @@ void System::Shutdown() { | ||||||
|     perf_stats.reset(); |     perf_stats.reset(); | ||||||
|     rpc_server.reset(); |     rpc_server.reset(); | ||||||
|     cheat_engine.reset(); |     cheat_engine.reset(); | ||||||
|  |     archive_manager.reset(); | ||||||
|     service_manager.reset(); |     service_manager.reset(); | ||||||
|     dsp_core.reset(); |     dsp_core.reset(); | ||||||
|     cpu_core.reset(); |     cpu_cores.clear(); | ||||||
|     kernel.reset(); |     kernel.reset(); | ||||||
|     timing.reset(); |     timing.reset(); | ||||||
|     app_loader.reset(); |     app_loader.reset(); | ||||||
|  | @ -452,11 +529,18 @@ void System::Reset() { | ||||||
| 
 | 
 | ||||||
| template <class Archive> | template <class Archive> | ||||||
| void System::serialize(Archive& ar, const unsigned int file_version) { | 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
 |     // flush on save, don't flush on load
 | ||||||
|     bool should_flush = !Archive::is_loading::value; |     bool should_flush = !Archive::is_loading::value; | ||||||
|     Memory::RasterizerClearAll(should_flush); |     Memory::RasterizerClearAll(should_flush); | ||||||
|     ar&* timing.get(); |     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&* service_manager.get(); | ||||||
|     ar& GPU::g_regs; |     ar& GPU::g_regs; | ||||||
|     ar& LCD::g_regs; |     ar& LCD::g_regs; | ||||||
|  |  | ||||||
|  | @ -148,7 +148,10 @@ public: | ||||||
|      * @returns True if the emulated system is powered on, otherwise false. |      * @returns True if the emulated system is powered on, otherwise false. | ||||||
|      */ |      */ | ||||||
|     bool IsPoweredOn() const { |     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. |      * Gets a reference to the emulated CPU. | ||||||
|      * @returns 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. |      * @param system_mode The system mode. | ||||||
|      * @return ResultStatus code, indicating if the operation succeeded. |      * @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
 |     /// Reschedule the core emulation
 | ||||||
|     void Reschedule(); |     void Reschedule(); | ||||||
|  | @ -300,7 +324,8 @@ private: | ||||||
|     std::unique_ptr<Loader::AppLoader> app_loader; |     std::unique_ptr<Loader::AppLoader> app_loader; | ||||||
| 
 | 
 | ||||||
|     /// ARM11 CPU core
 |     /// 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
 |     /// DSP core
 | ||||||
|     std::unique_ptr<AudioCore::DspInterface> dsp_core; |     std::unique_ptr<AudioCore::DspInterface> dsp_core; | ||||||
|  | @ -342,6 +367,8 @@ private: | ||||||
| private: | private: | ||||||
|     static System s_instance; |     static System s_instance; | ||||||
| 
 | 
 | ||||||
|  |     bool initalized = false; | ||||||
|  | 
 | ||||||
|     ResultStatus status = ResultStatus::Success; |     ResultStatus status = ResultStatus::Success; | ||||||
|     std::string status_details = ""; |     std::string status_details = ""; | ||||||
|     /// Saved variables for reset
 |     /// Saved variables for reset
 | ||||||
|  | @ -358,8 +385,16 @@ private: | ||||||
|     void serialize(Archive& ar, const unsigned int file_version); |     void serialize(Archive& ar, const unsigned int file_version); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| inline ARM_Interface& CPU() { | inline ARM_Interface& GetRunningCore() { | ||||||
|     return System::GetInstance().CPU(); |     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() { | inline AudioCore::DspInterface& DSP() { | ||||||
|  |  | ||||||
|  | @ -14,14 +14,22 @@ namespace Core { | ||||||
| Timing* Timing::deserializing = nullptr; | Timing* Timing::deserializing = nullptr; | ||||||
| 
 | 
 | ||||||
| // Sort by time, unless the times are the same, in which case sort by the order added to the queue
 | // 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); |     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); |     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) { | TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback callback) { | ||||||
|     // check for existing type with same name.
 |     // check for existing type with same name.
 | ||||||
|     // we want event type names to remain unique so that we can use them for serialization.
 |     // 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; |     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(); |     MoveEvents(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| u64 Timing::GetTicks() const { | u64 Timing::Timer::GetTicks() const { | ||||||
|     u64 ticks = static_cast<u64>(global_timer); |     u64 ticks = static_cast<u64>(executed_ticks); | ||||||
|     if (!is_global_timer_sane) { |     if (!is_timer_sane) { | ||||||
|         ticks += slice_length - downcount; |         ticks += slice_length - downcount; | ||||||
|     } |     } | ||||||
|     return ticks; |     return ticks; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Timing::AddTicks(u64 ticks) { | void Timing::Timer::AddTicks(u64 ticks) { | ||||||
|     downcount -= ticks; |     downcount -= ticks; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| u64 Timing::GetIdleTicks() const { | u64 Timing::Timer::GetIdleTicks() const { | ||||||
|     return static_cast<u64>(idled_cycles); |     return static_cast<u64>(idled_cycles); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, | void Timing::Timer::ForceExceptionCheck(s64 cycles) { | ||||||
|                            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) { |  | ||||||
|     cycles = std::max<s64>(0, cycles); |     cycles = std::max<s64>(0, cycles); | ||||||
|     if (downcount > cycles) { |     if (downcount > cycles) { | ||||||
|         slice_length -= 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);) { |     for (Event ev; ts_queue.Pop(ev);) { | ||||||
|         ev.fifo_order = event_fifo_id++; |         ev.fifo_order = event_fifo_id++; | ||||||
|         event_queue.emplace_back(std::move(ev)); |         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(); |     MoveEvents(); | ||||||
| 
 | 
 | ||||||
|     s64 cycles_executed = slice_length - downcount; |     s64 cycles_executed = slice_length - downcount; | ||||||
|     global_timer += cycles_executed; |     idled_cycles = 0; | ||||||
|     slice_length = MAX_SLICE_LENGTH; |     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()); |         Event evt = std::move(event_queue.front()); | ||||||
|         std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>()); |         std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>()); | ||||||
|         event_queue.pop_back(); |         event_queue.pop_back(); | ||||||
|         if (event_types.find(*evt.type->name) == event_types.end()) { |         if (evt.type->callback == nullptr) { | ||||||
|             LOG_ERROR(Core, "Unknown queued event {}", *evt.type->name); |  | ||||||
|         } else if (evt.type->callback == nullptr) { |  | ||||||
|             LOG_ERROR(Core, "Event '{}' has no callback", *evt.type->name); |             LOG_ERROR(Core, "Event '{}' has no callback", *evt.type->name); | ||||||
|         } |         } | ||||||
|         if (evt.type->callback != nullptr) { |         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)
 |     // Still events left (scheduled in the future)
 | ||||||
|     if (!event_queue.empty()) { |     if (!event_queue.empty()) { | ||||||
|         slice_length = static_cast<int>( |         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; |     downcount = slice_length; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Timing::Idle() { | void Timing::Timer::Idle() { | ||||||
|     idled_cycles += downcount; |     idled_cycles += downcount; | ||||||
|     downcount = 0; |     downcount = 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::chrono::microseconds Timing::GetGlobalTimeUs() const { | s64 Timing::Timer::GetDowncount() const { | ||||||
|     return std::chrono::microseconds{GetTicks() * 1000000 / BASE_CLOCK_RATE_ARM11}; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| s64 Timing::GetDowncount() const { |  | ||||||
|     return downcount; |     return downcount; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -135,65 +135,10 @@ struct TimingEventType { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class Timing { | 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: | private: | ||||||
|     static Timing* deserializing; |     static Timing* deserializing; | ||||||
| 
 | 
 | ||||||
|  | public: | ||||||
|     struct Event { |     struct Event { | ||||||
|         s64 time; |         s64 time; | ||||||
|         u64 fifo_order; |         u64 fifo_order; | ||||||
|  | @ -229,14 +174,29 @@ private: | ||||||
| 
 | 
 | ||||||
|     static constexpr int MAX_SLICE_LENGTH = 20000; |     static constexpr int MAX_SLICE_LENGTH = 20000; | ||||||
| 
 | 
 | ||||||
|     s64 global_timer = 0; |     class Timer { | ||||||
|     s64 slice_length = MAX_SLICE_LENGTH; |     public: | ||||||
|     s64 downcount = MAX_SLICE_LENGTH; |         ~Timer(); | ||||||
| 
 | 
 | ||||||
|     // unordered_map stores each element separately as a linked list node so pointers to
 |         s64 GetMaxSliceLength() const; | ||||||
|     // elements remain stable regardless of rehashes/resizing.
 |  | ||||||
|     std::unordered_map<std::string, TimingEventType> event_types; |  | ||||||
| 
 | 
 | ||||||
|  |         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.
 |         // 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
 |         // 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
 |         // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't
 | ||||||
|  | @ -246,31 +206,84 @@ private: | ||||||
|         // the queue for storing the events from other threads threadsafe until they will be added
 |         // the queue for storing the events from other threads threadsafe until they will be added
 | ||||||
|         // to the event_queue by the emu thread
 |         // to the event_queue by the emu thread
 | ||||||
|         Common::MPSCQueue<Event> ts_queue; |         Common::MPSCQueue<Event> ts_queue; | ||||||
|     s64 idled_cycles = 0; |  | ||||||
| 
 |  | ||||||
|         // Are we in a function that has been called from Advance()
 |         // Are we in a function that has been called from Advance()
 | ||||||
|         // If events are sheduled from a function that gets called from Advance(),
 |         // If events are sheduled from a function that gets called from Advance(),
 | ||||||
|         // don't change slice_length and downcount.
 |         // don't change slice_length and downcount.
 | ||||||
|     // The time between CoreTiming being intialized and the first call to Advance() is considered
 |         // The time between CoreTiming being intialized and the first call to Advance() is
 | ||||||
|     // the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before
 |         // considered the slice boundary between slice -1 and slice 0. Dispatcher loops must call
 | ||||||
|     // executing the first cycle of each slice to prepare the slice length and downcount for
 |         // Advance() before executing the first cycle of each slice to prepare the slice length and
 | ||||||
|     // that slice.
 |         // downcount for that slice.
 | ||||||
|     bool is_global_timer_sane = true; |         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> |         template <class Archive> | ||||||
|         void serialize(Archive& ar, const unsigned int) { |         void serialize(Archive& ar, const unsigned int) { | ||||||
|         // event_types set during initialization of other things
 |  | ||||||
|         deserializing = this; |  | ||||||
|             MoveEvents(); |             MoveEvents(); | ||||||
|         ar& global_timer; |  | ||||||
|             ar& slice_length; |             ar& slice_length; | ||||||
|             ar& downcount; |             ar& downcount; | ||||||
|             ar& event_queue; |             ar& event_queue; | ||||||
|             ar& event_fifo_id; |             ar& event_fifo_id; | ||||||
|             ar& idled_cycles; |             ar& idled_cycles; | ||||||
|         deserializing = nullptr; |  | ||||||
|         } |         } | ||||||
|         friend class boost::serialization::access; |         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; | ||||||
|  | 
 | ||||||
|  |     // 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; | ||||||
|  | 
 | ||||||
|  |     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; | ||||||
|  |         ar& global_timer; | ||||||
|  |         ar& timers; | ||||||
|  |         ar& current_timer; | ||||||
|  |         deserializing = nullptr; | ||||||
|  |     } | ||||||
|  |     friend class boost::serialization::access; | ||||||
|  | 
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| } // namespace Core
 | } // namespace Core
 | ||||||
|  |  | ||||||
							
								
								
									
										604
									
								
								src/core/file_sys/layered_fs.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										604
									
								
								src/core/file_sys/layered_fs.cpp
									
										
									
									
									
										Normal 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, ¤t); | ||||||
|  | 
 | ||||||
|  |     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 = ¤t; | ||||||
|  |         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(¤t, current_directory_offset); | ||||||
|  |     directory_list.emplace_back(¤t); | ||||||
|  |     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(¤t, current_file_offset); | ||||||
|  |     file_list.emplace_back(¤t); | ||||||
|  |     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
 | ||||||
							
								
								
									
										123
									
								
								src/core/file_sys/layered_fs.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/core/file_sys/layered_fs.h
									
										
									
									
									
										Normal 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
 | ||||||
|  | @ -11,6 +11,7 @@ | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
|  | #include "core/file_sys/layered_fs.h" | ||||||
| #include "core/file_sys/ncch_container.h" | #include "core/file_sys/ncch_container.h" | ||||||
| #include "core/file_sys/patch.h" | #include "core/file_sys/patch.h" | ||||||
| #include "core/file_sys/seed_db.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 kMaxSections = 8;   ///< Maximum number of sections (files) in an ExeFs
 | ||||||
| static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes)
 | 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 |  * Get the decompressed size of an LZSS compressed ExeFS file | ||||||
|  * @param buffer Buffer of compressed file |  * @param buffer Buffer of compressed file | ||||||
|  | @ -303,8 +312,22 @@ Loader::ResultStatus NCCHContainer::Load() { | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             FileUtil::IOFile exheader_override_file{filepath + ".exheader", "rb"}; |             const auto mods_path = | ||||||
|             const bool has_exheader_override = read_exheader(exheader_override_file); |                 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 (has_exheader_override) { | ||||||
|                 if (exheader_header.system_info.jump_id != |                 if (exheader_header.system_info.jump_id != | ||||||
|                     exheader_header.arm11_system_local_caps.program_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; |         std::string path; | ||||||
|         bool (*patch_fn)(const std::vector<u8>& patch, std::vector<u8>& code); |         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.ips", Patch::ApplyIpsPatch}, | ||||||
|         {filepath + ".exefsdir/code.bps", Patch::ApplyBpsPatch}, |         {filepath + ".exefsdir/code.bps", Patch::ApplyBpsPatch}, | ||||||
|     }}; |     }}; | ||||||
|  | @ -551,8 +582,17 @@ Loader::ResultStatus NCCHContainer::LoadOverrideExeFSSection(const char* name, | ||||||
|     else |     else | ||||||
|         return Loader::ResultStatus::Error; |         return Loader::ResultStatus::Error; | ||||||
| 
 | 
 | ||||||
|     std::string section_override = filepath + ".exefsdir/" + override_name; |     const auto mods_path = | ||||||
|     FileUtil::IOFile section_file(section_override, "rb"); |         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, | ||||||
|  |     }}; | ||||||
|  | 
 | ||||||
|  |     for (const auto& path : override_paths) { | ||||||
|  |         FileUtil::IOFile section_file(path, "rb"); | ||||||
| 
 | 
 | ||||||
|         if (section_file.IsOpen()) { |         if (section_file.IsOpen()) { | ||||||
|             auto section_size = section_file.GetSize(); |             auto section_size = section_file.GetSize(); | ||||||
|  | @ -560,14 +600,16 @@ Loader::ResultStatus NCCHContainer::LoadOverrideExeFSSection(const char* name, | ||||||
| 
 | 
 | ||||||
|             section_file.Seek(0, SEEK_SET); |             section_file.Seek(0, SEEK_SET); | ||||||
|             if (section_file.ReadBytes(&buffer[0], section_size) == section_size) { |             if (section_file.ReadBytes(&buffer[0], section_size) == section_size) { | ||||||
|             LOG_WARNING(Service_FS, "File {} overriding built-in ExeFS file", section_override); |                 LOG_WARNING(Service_FS, "File {} overriding built-in ExeFS file", path); | ||||||
|                 return Loader::ResultStatus::Success; |                 return Loader::ResultStatus::Success; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|     return Loader::ResultStatus::ErrorNotUsed; |     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(); |     Loader::ResultStatus result = Load(); | ||||||
|     if (result != Loader::ResultStatus::Success) |     if (result != Loader::ResultStatus::Success) | ||||||
|         return result; |         return result; | ||||||
|  | @ -597,14 +639,43 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf | ||||||
|     if (!romfs_file_inner.IsOpen()) |     if (!romfs_file_inner.IsOpen()) | ||||||
|         return Loader::ResultStatus::Error; |         return Loader::ResultStatus::Error; | ||||||
| 
 | 
 | ||||||
|  |     std::shared_ptr<RomFSReader> direct_romfs; | ||||||
|     if (is_encrypted) { |     if (is_encrypted) { | ||||||
|         romfs_file = std::make_shared<RomFSReader>(std::move(romfs_file_inner), romfs_offset, |         direct_romfs = | ||||||
|  |             std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), romfs_offset, | ||||||
|                                                 romfs_size, secondary_key, romfs_ctr, 0x1000); |                                                 romfs_size, secondary_key, romfs_ctr, 0x1000); | ||||||
|     } else { |     } else { | ||||||
|         romfs_file = |         direct_romfs = std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), | ||||||
|             std::make_shared<RomFSReader>(std::move(romfs_file_inner), romfs_offset, romfs_size); |                                                            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; |     return Loader::ResultStatus::Success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -614,8 +685,9 @@ Loader::ResultStatus NCCHContainer::ReadOverrideRomFS(std::shared_ptr<RomFSReade | ||||||
|     if (FileUtil::Exists(split_filepath)) { |     if (FileUtil::Exists(split_filepath)) { | ||||||
|         FileUtil::IOFile romfs_file_inner(split_filepath, "rb"); |         FileUtil::IOFile romfs_file_inner(split_filepath, "rb"); | ||||||
|         if (romfs_file_inner.IsOpen()) { |         if (romfs_file_inner.IsOpen()) { | ||||||
|             LOG_WARNING(Service_FS, "File {} overriding built-in RomFS", split_filepath); |             LOG_WARNING(Service_FS, "File {} overriding built-in RomFS; LayeredFS not enabled", | ||||||
|             romfs_file = std::make_shared<RomFSReader>(std::move(romfs_file_inner), 0, |                         split_filepath); | ||||||
|  |             romfs_file = std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), 0, | ||||||
|                                                              romfs_file_inner.GetSize()); |                                                              romfs_file_inner.GetSize()); | ||||||
|             return Loader::ResultStatus::Success; |             return Loader::ResultStatus::Success; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -149,7 +149,8 @@ struct ExHeader_StorageInfo { | ||||||
| struct ExHeader_ARM11_SystemLocalCaps { | struct ExHeader_ARM11_SystemLocalCaps { | ||||||
|     u64_le program_id; |     u64_le program_id; | ||||||
|     u32_le core_version; |     u32_le core_version; | ||||||
|     u8 reserved_flags[2]; |     u8 reserved_flag; | ||||||
|  |     u8 n3ds_mode; | ||||||
|     union { |     union { | ||||||
|         u8 flags0; |         u8 flags0; | ||||||
|         BitField<0, 2, u8> ideal_processor; |         BitField<0, 2, u8> ideal_processor; | ||||||
|  | @ -247,7 +248,15 @@ public: | ||||||
|      * @param size The size of the romfs |      * @param size The size of the romfs | ||||||
|      * @return ResultStatus result of function |      * @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 |      * Get the override RomFS of the NCCH container | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | 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) |     if (length == 0) | ||||||
|         return 0; // Crypto++ does not like zero size buffer
 |         return 0; // Crypto++ does not like zero size buffer
 | ||||||
|     file.Seek(file_offset + offset, SEEK_SET); |     file.Seek(file_offset + offset, SEEK_SET); | ||||||
|  |  | ||||||
|  | @ -7,23 +7,39 @@ | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * Interface for reading RomFS data. | ||||||
|  |  */ | ||||||
| class RomFSReader { | class RomFSReader { | ||||||
| public: | 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), |         : is_encrypted(false), file(std::move(file)), file_offset(file_offset), | ||||||
|           data_size(data_size) {} |           data_size(data_size) {} | ||||||
| 
 | 
 | ||||||
|     RomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size, |     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, |                       const std::array<u8, 16>& key, const std::array<u8, 16>& ctr, | ||||||
|                       std::size_t crypto_offset) |                       std::size_t crypto_offset) | ||||||
|         : is_encrypted(true), file(std::move(file)), key(key), ctr(ctr), file_offset(file_offset), |         : is_encrypted(true), file(std::move(file)), key(key), ctr(ctr), file_offset(file_offset), | ||||||
|           crypto_offset(crypto_offset), data_size(data_size) {} |           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; |         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: | private: | ||||||
|     bool is_encrypted; |     bool is_encrypted; | ||||||
|  | @ -34,7 +50,7 @@ private: | ||||||
|     u64 crypto_offset; |     u64 crypto_offset; | ||||||
|     u64 data_size; |     u64 data_size; | ||||||
| 
 | 
 | ||||||
|     RomFSReader() = default; |     DirectRomFSReader() = default; | ||||||
| 
 | 
 | ||||||
|     template <class Archive> |     template <class Archive> | ||||||
|     void serialize(Archive& ar, const unsigned int) { |     void serialize(Archive& ar, const unsigned int) { | ||||||
|  |  | ||||||
|  | @ -121,6 +121,7 @@ constexpr char target_xml[] = | ||||||
| )"; | )"; | ||||||
| 
 | 
 | ||||||
| int gdbserver_socket = -1; | int gdbserver_socket = -1; | ||||||
|  | bool defer_start = false; | ||||||
| 
 | 
 | ||||||
| u8 command_buffer[GDB_BUFFER_SIZE]; | u8 command_buffer[GDB_BUFFER_SIZE]; | ||||||
| u32 command_length; | u32 command_length; | ||||||
|  | @ -160,12 +161,16 @@ BreakpointMap breakpoints_write; | ||||||
| } // Anonymous namespace
 | } // Anonymous namespace
 | ||||||
| 
 | 
 | ||||||
| static Kernel::Thread* FindThreadById(int id) { | static Kernel::Thread* FindThreadById(int id) { | ||||||
|     const auto& threads = Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList(); |     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) { |         for (auto& thread : threads) { | ||||||
|             if (thread->GetThreadId() == static_cast<u32>(id)) { |             if (thread->GetThreadId() == static_cast<u32>(id)) { | ||||||
|                 return thread.get(); |                 return thread.get(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|     return nullptr; |     return nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -414,7 +419,10 @@ static void RemoveBreakpoint(BreakpointType type, VAddr addr) { | ||||||
|         Core::System::GetInstance().Memory().WriteBlock( |         Core::System::GetInstance().Memory().WriteBlock( | ||||||
|             *Core::System::GetInstance().Kernel().GetCurrentProcess(), bp->second.addr, |             *Core::System::GetInstance().Kernel().GetCurrentProcess(), bp->second.addr, | ||||||
|             bp->second.inst.data(), bp->second.inst.size()); |             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); |     p.erase(addr); | ||||||
| } | } | ||||||
|  | @ -540,11 +548,14 @@ static void HandleQuery() { | ||||||
|         SendReply(target_xml); |         SendReply(target_xml); | ||||||
|     } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { |     } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { | ||||||
|         std::string val = "m"; |         std::string val = "m"; | ||||||
|  |         u32 num_cores = Core::GetNumCores(); | ||||||
|  |         for (u32 i = 0; i < num_cores; ++i) { | ||||||
|             const auto& threads = |             const auto& threads = | ||||||
|             Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList(); |                 Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList(); | ||||||
|             for (const auto& thread : threads) { |             for (const auto& thread : threads) { | ||||||
|                 val += fmt::format("{:x},", thread->GetThreadId()); |                 val += fmt::format("{:x},", thread->GetThreadId()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|         val.pop_back(); |         val.pop_back(); | ||||||
|         SendReply(val.c_str()); |         SendReply(val.c_str()); | ||||||
|     } else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) { |     } else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) { | ||||||
|  | @ -553,12 +564,15 @@ static void HandleQuery() { | ||||||
|         std::string buffer; |         std::string buffer; | ||||||
|         buffer += "l<?xml version=\"1.0\"?>"; |         buffer += "l<?xml version=\"1.0\"?>"; | ||||||
|         buffer += "<threads>"; |         buffer += "<threads>"; | ||||||
|  |         u32 num_cores = Core::GetNumCores(); | ||||||
|  |         for (u32 i = 0; i < num_cores; ++i) { | ||||||
|             const auto& threads = |             const auto& threads = | ||||||
|             Core::System::GetInstance().Kernel().GetThreadManager().GetThreadList(); |                 Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList(); | ||||||
|             for (const auto& thread : threads) { |             for (const auto& thread : threads) { | ||||||
|                 buffer += fmt::format(R"*(<thread id="{:x}" name="Thread {:x}"></thread>)*", |                 buffer += fmt::format(R"*(<thread id="{:x}" name="Thread {:x}"></thread>)*", | ||||||
|                                       thread->GetThreadId(), thread->GetThreadId()); |                                       thread->GetThreadId(), thread->GetThreadId()); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|         buffer += "</threads>"; |         buffer += "</threads>"; | ||||||
|         SendReply(buffer.c_str()); |         SendReply(buffer.c_str()); | ||||||
|     } else { |     } else { | ||||||
|  | @ -619,9 +633,9 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) { | ||||||
|     if (full) { |     if (full) { | ||||||
| 
 | 
 | ||||||
|         buffer = fmt::format("T{:02x}{:02x}:{:08x};{:02x}:{:08x};{:02x}:{:08x}", latest_signal, |         buffer = fmt::format("T{:02x}{:02x}:{:08x};{:02x}:{:08x};{:02x}:{:08x}", latest_signal, | ||||||
|                              PC_REGISTER, htonl(Core::CPU().GetPC()), SP_REGISTER, |                              PC_REGISTER, htonl(Core::GetRunningCore().GetPC()), SP_REGISTER, | ||||||
|                              htonl(Core::CPU().GetReg(SP_REGISTER)), LR_REGISTER, |                              htonl(Core::GetRunningCore().GetReg(SP_REGISTER)), LR_REGISTER, | ||||||
|                              htonl(Core::CPU().GetReg(LR_REGISTER))); |                              htonl(Core::GetRunningCore().GetReg(LR_REGISTER))); | ||||||
|     } else { |     } else { | ||||||
|         buffer = fmt::format("T{:02x}", latest_signal); |         buffer = fmt::format("T{:02x}", latest_signal); | ||||||
|     } |     } | ||||||
|  | @ -782,7 +796,7 @@ static void WriteRegister() { | ||||||
|         return SendReply("E01"); |         return SendReply("E01"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Core::CPU().LoadContext(current_thread->context); |     Core::GetRunningCore().LoadContext(current_thread->context); | ||||||
| 
 | 
 | ||||||
|     SendReply("OK"); |     SendReply("OK"); | ||||||
| } | } | ||||||
|  | @ -812,7 +826,7 @@ static void WriteRegisters() { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Core::CPU().LoadContext(current_thread->context); |     Core::GetRunningCore().LoadContext(current_thread->context); | ||||||
| 
 | 
 | ||||||
|     SendReply("OK"); |     SendReply("OK"); | ||||||
| } | } | ||||||
|  | @ -869,7 +883,7 @@ static void WriteMemory() { | ||||||
|     GdbHexToMem(data.data(), len_pos + 1, len); |     GdbHexToMem(data.data(), len_pos + 1, len); | ||||||
|     Core::System::GetInstance().Memory().WriteBlock( |     Core::System::GetInstance().Memory().WriteBlock( | ||||||
|         *Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, data.data(), len); |         *Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, data.data(), len); | ||||||
|     Core::CPU().ClearInstructionCache(); |     Core::GetRunningCore().ClearInstructionCache(); | ||||||
|     SendReply("OK"); |     SendReply("OK"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -883,12 +897,12 @@ void Break(bool is_memory_break) { | ||||||
| static void Step() { | static void Step() { | ||||||
|     if (command_length > 1) { |     if (command_length > 1) { | ||||||
|         RegWrite(PC_REGISTER, GdbHexToInt(command_buffer + 1), current_thread); |         RegWrite(PC_REGISTER, GdbHexToInt(command_buffer + 1), current_thread); | ||||||
|         Core::CPU().LoadContext(current_thread->context); |         Core::GetRunningCore().LoadContext(current_thread->context); | ||||||
|     } |     } | ||||||
|     step_loop = true; |     step_loop = true; | ||||||
|     halt_loop = true; |     halt_loop = true; | ||||||
|     send_trap = true; |     send_trap = true; | ||||||
|     Core::CPU().ClearInstructionCache(); |     Core::GetRunningCore().ClearInstructionCache(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IsMemoryBreak() { | bool IsMemoryBreak() { | ||||||
|  | @ -904,7 +918,7 @@ static void Continue() { | ||||||
|     memory_break = false; |     memory_break = false; | ||||||
|     step_loop = false; |     step_loop = false; | ||||||
|     halt_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().Memory().WriteBlock( | ||||||
|             *Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, btrap.data(), |             *Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, btrap.data(), | ||||||
|             btrap.size()); |             btrap.size()); | ||||||
|         Core::CPU().ClearInstructionCache(); |         Core::GetRunningCore().ClearInstructionCache(); | ||||||
|     } |     } | ||||||
|     p.insert({addr, breakpoint}); |     p.insert({addr, breakpoint}); | ||||||
| 
 | 
 | ||||||
|  | @ -1030,6 +1044,9 @@ static void RemoveBreakpoint() { | ||||||
| 
 | 
 | ||||||
| void HandlePacket() { | void HandlePacket() { | ||||||
|     if (!IsConnected()) { |     if (!IsConnected()) { | ||||||
|  |         if (defer_start) { | ||||||
|  |             ToggleServer(true); | ||||||
|  |         } | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -1120,6 +1137,10 @@ void ToggleServer(bool status) { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void DeferStart() { | ||||||
|  |     defer_start = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void Init(u16 port) { | static void Init(u16 port) { | ||||||
|     if (!server_enabled) { |     if (!server_enabled) { | ||||||
|         // Set the halt loop to false in case the user enabled the gdbstub mid-execution.
 |         // Set the halt loop to false in case the user enabled the gdbstub mid-execution.
 | ||||||
|  | @ -1203,6 +1224,7 @@ void Shutdown() { | ||||||
|     if (!server_enabled) { |     if (!server_enabled) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |     defer_start = false; | ||||||
| 
 | 
 | ||||||
|     LOG_INFO(Debug_GDBStub, "Stopping GDB ..."); |     LOG_INFO(Debug_GDBStub, "Stopping GDB ..."); | ||||||
|     if (gdbserver_socket != -1) { |     if (gdbserver_socket != -1) { | ||||||
|  |  | ||||||
|  | @ -42,6 +42,13 @@ void ToggleServer(bool status); | ||||||
| /// Start the gdbstub server.
 | /// Start the gdbstub server.
 | ||||||
| void Init(); | 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.
 | /// Stop gdbstub server.
 | ||||||
| void Shutdown(); | void Shutdown(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -83,7 +83,7 @@ bool HandleTable::IsValid(Handle handle) const { | ||||||
| 
 | 
 | ||||||
| std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const { | std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const { | ||||||
|     if (handle == CurrentThread) { |     if (handle == CurrentThread) { | ||||||
|         return SharedFrom(kernel.GetThreadManager().GetCurrentThread()); |         return SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread()); | ||||||
|     } else if (handle == CurrentProcess) { |     } else if (handle == CurrentProcess) { | ||||||
|         return kernel.GetCurrentProcess(); |         return kernel.GetCurrentProcess(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -20,22 +20,30 @@ namespace Kernel { | ||||||
| 
 | 
 | ||||||
| /// Initialize the kernel
 | /// Initialize the kernel
 | ||||||
| KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing, | 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), |     : memory(memory), timing(timing), | ||||||
|       prepare_reschedule_callback(std::move(prepare_reschedule_callback)) { |       prepare_reschedule_callback(std::move(prepare_reschedule_callback)) { | ||||||
|     for (auto i = 0; i < memory_regions.size(); i++) { |     for (auto i = 0; i < memory_regions.size(); i++) { | ||||||
|         memory_regions[i] = std::make_shared<MemoryRegionInfo>(); |         memory_regions[i] = std::make_shared<MemoryRegionInfo>(); | ||||||
|     } |     } | ||||||
|     MemoryInit(system_mode); |     MemoryInit(system_mode, n3ds_mode); | ||||||
| 
 | 
 | ||||||
|     resource_limits = std::make_unique<ResourceLimitList>(*this); |     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); |     timer_manager = std::make_unique<TimerManager>(timing); | ||||||
|     ipc_recorder = std::make_unique<IPCDebugger::Recorder>(); |     ipc_recorder = std::make_unique<IPCDebugger::Recorder>(); | ||||||
|  |     stored_processes.assign(num_cores, nullptr); | ||||||
|  | 
 | ||||||
|  |     next_thread_id = 1; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Shutdown the kernel
 | /// Shutdown the kernel
 | ||||||
| KernelSystem::~KernelSystem() = default; | KernelSystem::~KernelSystem() { | ||||||
|  |     ResetThreadIDs(); | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| ResourceLimitList& KernelSystem::ResourceLimit() { | ResourceLimitList& KernelSystem::ResourceLimit() { | ||||||
|     return *resource_limits; |     return *resource_limits; | ||||||
|  | @ -58,6 +66,15 @@ void KernelSystem::SetCurrentProcess(std::shared_ptr<Process> process) { | ||||||
|     SetCurrentMemoryPageTable(process->vm_manager.page_table); |     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) { | void KernelSystem::SetCurrentMemoryPageTable(std::shared_ptr<Memory::PageTable> page_table) { | ||||||
|     memory.SetCurrentPageTable(page_table); |     memory.SetCurrentPageTable(page_table); | ||||||
|     if (current_cpu != nullptr) { |     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; |     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() { | ThreadManager& KernelSystem::GetThreadManager(u32 core_id) { | ||||||
|     return *thread_manager; |     return *thread_managers[core_id]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const ThreadManager& KernelSystem::GetThreadManager() const { | const ThreadManager& KernelSystem::GetThreadManager(u32 core_id) const { | ||||||
|     return *thread_manager; |     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() { | 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)); |     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> | template <class Archive> | ||||||
| void KernelSystem::serialize(Archive& ar, const unsigned int file_version) { | void KernelSystem::serialize(Archive& ar, const unsigned int file_version) { | ||||||
|     ar& memory_regions; |     ar& memory_regions; | ||||||
|  | @ -118,9 +165,14 @@ void KernelSystem::serialize(Archive& ar, const unsigned int file_version) { | ||||||
|     ar& next_process_id; |     ar& next_process_id; | ||||||
|     ar& process_list; |     ar& process_list; | ||||||
|     ar& current_process; |     ar& current_process; | ||||||
|  |     // NB: core count checked in 'core'
 | ||||||
|  |     for (auto& thread_manager : thread_managers) { | ||||||
|         ar&* thread_manager.get(); |         ar&* thread_manager.get(); | ||||||
|  |     } | ||||||
|     ar& config_mem_handler; |     ar& config_mem_handler; | ||||||
|     ar& shared_page_handler; |     ar& shared_page_handler; | ||||||
|  |     ar& stored_processes; | ||||||
|  |     ar& next_thread_id; | ||||||
|     // Deliberately don't include debugger info to allow debugging through loads
 |     // Deliberately don't include debugger info to allow debugging through loads
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -88,7 +88,8 @@ enum class MemoryRegion : u16 { | ||||||
| class KernelSystem { | class KernelSystem { | ||||||
| public: | public: | ||||||
|     explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing, |     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(); |     ~KernelSystem(); | ||||||
| 
 | 
 | ||||||
|     using PortPair = std::pair<std::shared_ptr<ServerPort>, std::shared_ptr<ClientPort>>; |     using PortPair = std::pair<std::shared_ptr<ServerPort>, std::shared_ptr<ClientPort>>; | ||||||
|  | @ -214,13 +215,19 @@ public: | ||||||
| 
 | 
 | ||||||
|     std::shared_ptr<Process> GetCurrentProcess() const; |     std::shared_ptr<Process> GetCurrentProcess() const; | ||||||
|     void SetCurrentProcess(std::shared_ptr<Process> process); |     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 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(); |     void SetRunningCPU(std::shared_ptr<ARM_Interface> cpu); | ||||||
|     const ThreadManager& GetThreadManager() const; | 
 | ||||||
|  |     ThreadManager& GetThreadManager(u32 core_id); | ||||||
|  |     const ThreadManager& GetThreadManager(u32 core_id) const; | ||||||
|  | 
 | ||||||
|  |     ThreadManager& GetCurrentThreadManager(); | ||||||
|  |     const ThreadManager& GetCurrentThreadManager() const; | ||||||
| 
 | 
 | ||||||
|     TimerManager& GetTimerManager(); |     TimerManager& GetTimerManager(); | ||||||
|     const TimerManager& GetTimerManager() const; |     const TimerManager& GetTimerManager() const; | ||||||
|  | @ -246,6 +253,10 @@ public: | ||||||
|         prepare_reschedule_callback(); |         prepare_reschedule_callback(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     u32 NewThreadId(); | ||||||
|  | 
 | ||||||
|  |     void ResetThreadIDs(); | ||||||
|  | 
 | ||||||
|     /// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort
 |     /// 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; |     std::unordered_map<std::string, std::shared_ptr<ClientPort>> named_ports; | ||||||
| 
 | 
 | ||||||
|  | @ -256,7 +267,7 @@ public: | ||||||
|     Core::Timing& timing; |     Core::Timing& timing; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     void MemoryInit(u32 mem_type); |     void MemoryInit(u32 mem_type, u8 n3ds_mode); | ||||||
| 
 | 
 | ||||||
|     std::function<void()> prepare_reschedule_callback; |     std::function<void()> prepare_reschedule_callback; | ||||||
| 
 | 
 | ||||||
|  | @ -280,14 +291,17 @@ private: | ||||||
|     std::vector<std::shared_ptr<Process>> process_list; |     std::vector<std::shared_ptr<Process>> process_list; | ||||||
| 
 | 
 | ||||||
|     std::shared_ptr<Process> current_process; |     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<ConfigMem::Handler> config_mem_handler; | ||||||
|     std::shared_ptr<SharedPage::Handler> shared_page_handler; |     std::shared_ptr<SharedPage::Handler> shared_page_handler; | ||||||
| 
 | 
 | ||||||
|     std::unique_ptr<IPCDebugger::Recorder> ipc_recorder; |     std::unique_ptr<IPCDebugger::Recorder> ipc_recorder; | ||||||
| 
 | 
 | ||||||
|  |     u32 next_thread_id; | ||||||
|  | 
 | ||||||
|     friend class boost::serialization::access; |     friend class boost::serialization::access; | ||||||
|     template <class Archive> |     template <class Archive> | ||||||
|     void serialize(Archive& ar, const unsigned int file_version); |     void serialize(Archive& ar, const unsigned int file_version); | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ | ||||||
| #include "core/hle/kernel/vm_manager.h" | #include "core/hle/kernel/vm_manager.h" | ||||||
| #include "core/hle/result.h" | #include "core/hle/result.h" | ||||||
| #include "core/memory.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
 |     {0x0B200000, 0x02E00000, 0x02000000}, // 7
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| void KernelSystem::MemoryInit(u32 mem_type) { | namespace MemoryMode { | ||||||
|     // TODO(yuriks): On the n3DS, all o3DS configurations (<=5) are forced to 6 instead.
 | enum N3DSMode : u8 { | ||||||
|     ASSERT_MSG(mem_type <= 5, "New 3DS memory configuration aren't supported yet!"); |     Mode6 = 1, | ||||||
|  |     Mode7 = 2, | ||||||
|  |     Mode6_2 = 3, | ||||||
|  | }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void KernelSystem::MemoryInit(u32 mem_type, u8 n3ds_mode) { | ||||||
|     ASSERT(mem_type != 1); |     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 kernel allocation regions (APPLICATION, SYSTEM and BASE) are laid out in sequence, with
 | ||||||
|     // the sizes specified in the memory_region_sizes table.
 |     // the sizes specified in the memory_region_sizes table.
 | ||||||
|     VAddr base = 0; |     VAddr base = 0; | ||||||
|  | @ -55,14 +77,12 @@ void KernelSystem::MemoryInit(u32 mem_type) { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // We must've allocated the entire FCRAM by the end
 |     // 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>(); |     config_mem_handler = std::make_shared<ConfigMem::Handler>(); | ||||||
|     auto& config_mem = config_mem_handler->GetConfigMem(); |     auto& config_mem = config_mem_handler->GetConfigMem(); | ||||||
|     config_mem.app_mem_type = mem_type; |     config_mem.app_mem_type = reported_mem_type; | ||||||
|     // app_mem_malloc does not always match the configured size for memory_region[0]: in case the
 |     config_mem.app_mem_alloc = memory_region_sizes[reported_mem_type][0]; | ||||||
|     // 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.sys_mem_alloc = memory_regions[1]->size; |     config_mem.sys_mem_alloc = memory_regions[1]->size; | ||||||
|     config_mem.base_mem_alloc = memory_regions[2]->size; |     config_mem.base_mem_alloc = memory_regions[2]->size; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -39,7 +39,7 @@ std::shared_ptr<Mutex> KernelSystem::CreateMutex(bool initial_locked, std::strin | ||||||
| 
 | 
 | ||||||
|     // Acquire mutex with current thread if initialized as locked
 |     // Acquire mutex with current thread if initialized as locked
 | ||||||
|     if (initial_locked) |     if (initial_locked) | ||||||
|         mutex->Acquire(thread_manager->GetCurrentThread()); |         mutex->Acquire(thread_managers[current_cpu->GetID()]->GetCurrentThread()); | ||||||
| 
 | 
 | ||||||
|     return mutex; |     return mutex; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -70,7 +70,7 @@ Handler::Handler(Core::Timing& timing) : timing(timing) { | ||||||
|     using namespace std::placeholders; |     using namespace std::placeholders; | ||||||
|     update_time_event = timing.RegisterEvent("SharedPage::UpdateTimeCallback", |     update_time_event = timing.RegisterEvent("SharedPage::UpdateTimeCallback", | ||||||
|                                              std::bind(&Handler::UpdateTimeCallback, this, _1, _2)); |                                              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; |     float slidestate = Settings::values.factor_3d / 100.0f; | ||||||
|     shared_page.sliderstate_3d = static_cast<float_le>(slidestate); |     shared_page.sliderstate_3d = static_cast<float_le>(slidestate); | ||||||
|  |  | ||||||
|  | @ -280,12 +280,12 @@ void SVC::ExitProcess() { | ||||||
|     current_process->status = ProcessStatus::Exited; |     current_process->status = ProcessStatus::Exited; | ||||||
| 
 | 
 | ||||||
|     // Stop all the process threads that are currently waiting for objects.
 |     // 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) { |     for (auto& thread : thread_list) { | ||||||
|         if (thread->owner_process != current_process) |         if (thread->owner_process != current_process) | ||||||
|             continue; |             continue; | ||||||
| 
 | 
 | ||||||
|         if (thread.get() == kernel.GetThreadManager().GetCurrentThread()) |         if (thread.get() == kernel.GetCurrentThreadManager().GetCurrentThread()) | ||||||
|             continue; |             continue; | ||||||
| 
 | 
 | ||||||
|         // TODO(Subv): When are the other running/ready threads terminated?
 |         // TODO(Subv): When are the other running/ready threads terminated?
 | ||||||
|  | @ -297,7 +297,7 @@ void SVC::ExitProcess() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Kill the current thread
 |     // Kill the current thread
 | ||||||
|     kernel.GetThreadManager().GetCurrentThread()->Stop(); |     kernel.GetCurrentThreadManager().GetCurrentThread()->Stop(); | ||||||
| 
 | 
 | ||||||
|     system.PrepareReschedule(); |     system.PrepareReschedule(); | ||||||
| } | } | ||||||
|  | @ -388,7 +388,7 @@ ResultCode SVC::SendSyncRequest(Handle handle) { | ||||||
| 
 | 
 | ||||||
|     system.PrepareReschedule(); |     system.PrepareReschedule(); | ||||||
| 
 | 
 | ||||||
|     auto thread = SharedFrom(kernel.GetThreadManager().GetCurrentThread()); |     auto thread = SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread()); | ||||||
| 
 | 
 | ||||||
|     if (kernel.GetIPCRecorder().IsEnabled()) { |     if (kernel.GetIPCRecorder().IsEnabled()) { | ||||||
|         kernel.GetIPCRecorder().RegisterRequest(session, thread); |         kernel.GetIPCRecorder().RegisterRequest(session, thread); | ||||||
|  | @ -476,7 +476,7 @@ private: | ||||||
| /// Wait for a handle to synchronize, timeout after the specified nanoseconds
 | /// Wait for a handle to synchronize, timeout after the specified nanoseconds
 | ||||||
| ResultCode SVC::WaitSynchronization1(Handle handle, s64 nano_seconds) { | ResultCode SVC::WaitSynchronization1(Handle handle, s64 nano_seconds) { | ||||||
|     auto object = kernel.GetCurrentProcess()->handle_table.Get<WaitObject>(handle); |     auto object = kernel.GetCurrentProcess()->handle_table.Get<WaitObject>(handle); | ||||||
|     Thread* thread = kernel.GetThreadManager().GetCurrentThread(); |     Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread(); | ||||||
| 
 | 
 | ||||||
|     if (object == nullptr) |     if (object == nullptr) | ||||||
|         return ERR_INVALID_HANDLE; |         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
 | /// Wait for the given handles to synchronize, timeout after the specified nanoseconds
 | ||||||
| ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle_count, | ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle_count, | ||||||
|                                      bool wait_all, s64 nano_seconds) { |                                      bool wait_all, s64 nano_seconds) { | ||||||
|     Thread* thread = kernel.GetThreadManager().GetCurrentThread(); |     Thread* thread = kernel.GetCurrentThreadManager().GetCurrentThread(); | ||||||
| 
 | 
 | ||||||
|     if (!Memory::IsValidVirtualAddress(*kernel.GetCurrentProcess(), handles_address)) |     if (!Memory::IsValidVirtualAddress(*kernel.GetCurrentProcess(), handles_address)) | ||||||
|         return ERR_INVALID_POINTER; |         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.
 |     // We are also sending a command reply.
 | ||||||
|     // Do not send a reply if the command id in the command buffer is 0xFFFF.
 |     // 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()); |     u32 cmd_buff_header = memory.Read32(thread->GetCommandBufferAddress()); | ||||||
|     IPC::Header header{cmd_buff_header}; |     IPC::Header header{cmd_buff_header}; | ||||||
|     if (reply_target != 0 && header.command_id != 0xFFFF) { |     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; |         return ERR_INVALID_HANDLE; | ||||||
| 
 | 
 | ||||||
|     auto res = |     auto res = | ||||||
|         arbiter->ArbitrateAddress(SharedFrom(kernel.GetThreadManager().GetCurrentThread()), |         arbiter->ArbitrateAddress(SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread()), | ||||||
|                                   static_cast<ArbitrationType>(type), address, value, nanoseconds); |                                   static_cast<ArbitrationType>(type), address, value, nanoseconds); | ||||||
| 
 | 
 | ||||||
|     // TODO(Subv): Identify in which specific cases this call should cause a reschedule.
 |     // 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; |         break; | ||||||
|     case ThreadProcessorIdAll: |     case ThreadProcessorIdAll: | ||||||
|         LOG_INFO(Kernel_SVC, |         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; |         break; | ||||||
|     case ThreadProcessorId1: |     case ThreadProcessorId1: | ||||||
|         LOG_ERROR(Kernel_SVC, |     case ThreadProcessorId2: | ||||||
|                   "Newly created thread must run in the SysCore (Core1), unimplemented."); |     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; |         break; | ||||||
|     default: |     default: | ||||||
|         // TODO(bunnei): Implement support for other processor IDs
 |  | ||||||
|         ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id); |         ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id); | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  | @ -945,9 +950,9 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr | ||||||
| 
 | 
 | ||||||
| /// Called when a thread exits
 | /// Called when a thread exits
 | ||||||
| void SVC::ExitThread() { | 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(); |     system.PrepareReschedule(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -993,7 +998,7 @@ ResultCode SVC::SetThreadPriority(Handle handle, u32 priority) { | ||||||
| /// Create a mutex
 | /// Create a mutex
 | ||||||
| ResultCode SVC::CreateMutex(Handle* out_handle, u32 initial_locked) { | ResultCode SVC::CreateMutex(Handle* out_handle, u32 initial_locked) { | ||||||
|     std::shared_ptr<Mutex> mutex = kernel.CreateMutex(initial_locked != 0); |     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))); |     CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(mutex))); | ||||||
| 
 | 
 | ||||||
|     LOG_TRACE(Kernel_SVC, "called initial_locked={} : created handle=0x{:08X}", |     LOG_TRACE(Kernel_SVC, "called initial_locked={} : created handle=0x{:08X}", | ||||||
|  | @ -1010,7 +1015,7 @@ ResultCode SVC::ReleaseMutex(Handle handle) { | ||||||
|     if (mutex == nullptr) |     if (mutex == nullptr) | ||||||
|         return ERR_INVALID_HANDLE; |         return ERR_INVALID_HANDLE; | ||||||
| 
 | 
 | ||||||
|     return mutex->Release(kernel.GetThreadManager().GetCurrentThread()); |     return mutex->Release(kernel.GetCurrentThreadManager().GetCurrentThread()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Get the ID of the specified process
 | /// 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) { | ResultCode SVC::CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count) { | ||||||
|     CASCADE_RESULT(std::shared_ptr<Semaphore> semaphore, |     CASCADE_RESULT(std::shared_ptr<Semaphore> semaphore, | ||||||
|                    kernel.CreateSemaphore(initial_count, max_count)); |                    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, |     CASCADE_RESULT(*out_handle, | ||||||
|                    kernel.GetCurrentProcess()->handle_table.Create(std::move(semaphore))); |                    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
 | /// Create an event
 | ||||||
| ResultCode SVC::CreateEvent(Handle* out_handle, u32 reset_type) { | ResultCode SVC::CreateEvent(Handle* out_handle, u32 reset_type) { | ||||||
|     std::shared_ptr<Event> evt = kernel.CreateEvent( |     std::shared_ptr<Event> evt = | ||||||
|         static_cast<ResetType>(reset_type), fmt::format("event-{:08x}", system.CPU().GetReg(14))); |         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))); |     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, |     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
 | /// Creates a timer
 | ||||||
| ResultCode SVC::CreateTimer(Handle* out_handle, u32 reset_type) { | ResultCode SVC::CreateTimer(Handle* out_handle, u32 reset_type) { | ||||||
|     std::shared_ptr<Timer> timer = kernel.CreateTimer( |     std::shared_ptr<Timer> timer = | ||||||
|         static_cast<ResetType>(reset_type), fmt ::format("timer-{:08x}", system.CPU().GetReg(14))); |         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))); |     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, |     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) { | void SVC::SleepThread(s64 nanoseconds) { | ||||||
|     LOG_TRACE(Kernel_SVC, "called nanoseconds={}", 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,
 |     // 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.
 |     // 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
 | /// This returns the total CPU ticks elapsed since the CPU was powered-on
 | ||||||
| s64 SVC::GetSystemTick() { | 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.
 |     // 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
 |     // 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; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1611,11 +1619,11 @@ void SVC::CallSVC(u32 immediate) { | ||||||
| SVC::SVC(Core::System& system) : system(system), kernel(system.Kernel()), memory(system.Memory()) {} | SVC::SVC(Core::System& system) : system(system), kernel(system.Kernel()), memory(system.Memory()) {} | ||||||
| 
 | 
 | ||||||
| u32 SVC::GetReg(std::size_t n) { | 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) { | 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)) {} | SVCContext::SVCContext(Core::System& system) : impl(std::make_unique<SVC>(system)) {} | ||||||
|  |  | ||||||
|  | @ -62,13 +62,10 @@ void Thread::Acquire(Thread* thread) { | ||||||
|     ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); |     ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| u32 ThreadManager::NewThreadId() { | Thread::Thread(KernelSystem& kernel, u32 core_id) | ||||||
|     return next_thread_id++; |     : WaitObject(kernel), context(kernel.GetThreadManager(core_id).NewContext()), | ||||||
| } |       core_id(core_id), | ||||||
| 
 |       thread_manager(kernel.GetThreadManager(core_id)) {} | ||||||
| Thread::Thread(KernelSystem& kernel) |  | ||||||
|     : WaitObject(kernel), context(kernel.GetThreadManager().NewContext()), |  | ||||||
|       thread_manager(kernel.GetThreadManager()) {} |  | ||||||
| Thread::~Thread() {} | Thread::~Thread() {} | ||||||
| 
 | 
 | ||||||
| Thread* ThreadManager::GetCurrentThread() const { | Thread* ThreadManager::GetCurrentThread() const { | ||||||
|  | @ -113,7 +110,7 @@ void ThreadManager::SwitchContext(Thread* new_thread) { | ||||||
| 
 | 
 | ||||||
|     // Save context for previous thread
 |     // Save context for previous thread
 | ||||||
|     if (previous_thread) { |     if (previous_thread) { | ||||||
|         previous_thread->last_running_ticks = timing.GetTicks(); |         previous_thread->last_running_ticks = timing.GetGlobalTicks(); | ||||||
|         cpu->SaveContext(previous_thread->context); |         cpu->SaveContext(previous_thread->context); | ||||||
| 
 | 
 | ||||||
|         if (previous_thread->status == ThreadStatus::Running) { |         if (previous_thread->status == ThreadStatus::Running) { | ||||||
|  | @ -140,7 +137,7 @@ void ThreadManager::SwitchContext(Thread* new_thread) { | ||||||
|         new_thread->status = ThreadStatus::Running; |         new_thread->status = ThreadStatus::Running; | ||||||
| 
 | 
 | ||||||
|         if (previous_process != current_thread->owner_process) { |         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); |         cpu->LoadContext(new_thread->context); | ||||||
|  | @ -153,7 +150,7 @@ void ThreadManager::SwitchContext(Thread* new_thread) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Thread* ThreadManager::PopNextReadyThread() { | Thread* ThreadManager::PopNextReadyThread() { | ||||||
|     Thread* next; |     Thread* next = nullptr; | ||||||
|     Thread* thread = GetCurrentThread(); |     Thread* thread = GetCurrentThread(); | ||||||
| 
 | 
 | ||||||
|     if (thread && thread->status == ThreadStatus::Running) { |     if (thread && thread->status == ThreadStatus::Running) { | ||||||
|  | @ -337,22 +334,22 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread( | ||||||
|                           ErrorSummary::InvalidArgument, ErrorLevel::Permanent); |                           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_managers[processor_id]->thread_list.push_back(thread); | ||||||
|     thread_manager->ready_queue.prepare(priority); |     thread_managers[processor_id]->ready_queue.prepare(priority); | ||||||
| 
 | 
 | ||||||
|     thread->thread_id = thread_manager->NewThreadId(); |     thread->thread_id = NewThreadId(); | ||||||
|     thread->status = ThreadStatus::Dormant; |     thread->status = ThreadStatus::Dormant; | ||||||
|     thread->entry_point = entry_point; |     thread->entry_point = entry_point; | ||||||
|     thread->stack_top = stack_top; |     thread->stack_top = stack_top; | ||||||
|     thread->nominal_priority = thread->current_priority = priority; |     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->processor_id = processor_id; | ||||||
|     thread->wait_objects.clear(); |     thread->wait_objects.clear(); | ||||||
|     thread->wait_address = 0; |     thread->wait_address = 0; | ||||||
|     thread->name = std::move(name); |     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; |     thread->owner_process = owner_process; | ||||||
| 
 | 
 | ||||||
|     // Find the next available TLS index, and mark it as used
 |     // 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
 |     // to initialize the context
 | ||||||
|     ResetThreadContext(thread->context, stack_top, entry_point, arg); |     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; |     thread->status = ThreadStatus::Ready; | ||||||
| 
 | 
 | ||||||
|     return MakeResult<std::shared_ptr<Thread>>(std::move(thread)); |     return MakeResult<std::shared_ptr<Thread>>(std::move(thread)); | ||||||
|  | @ -463,6 +460,9 @@ void ThreadManager::Reschedule() { | ||||||
|         LOG_TRACE(Kernel, "context switch {} -> idle", cur->GetObjectId()); |         LOG_TRACE(Kernel, "context switch {} -> idle", cur->GetObjectId()); | ||||||
|     } else if (next) { |     } else if (next) { | ||||||
|         LOG_TRACE(Kernel, "context switch idle -> {}", next->GetObjectId()); |         LOG_TRACE(Kernel, "context switch idle -> {}", next->GetObjectId()); | ||||||
|  |     } else { | ||||||
|  |         LOG_TRACE(Kernel, "context switch idle -> idle, do nothing"); | ||||||
|  |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     SwitchContext(next); |     SwitchContext(next); | ||||||
|  | @ -489,11 +489,10 @@ VAddr Thread::GetCommandBufferAddress() const { | ||||||
|     return GetTLSAddress() + command_header_offset; |     return GetTLSAddress() + command_header_offset; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ThreadManager::ThreadManager(Kernel::KernelSystem& kernel) : kernel(kernel) { | ThreadManager::ThreadManager(Kernel::KernelSystem& kernel, u32 core_id) : kernel(kernel) { | ||||||
|     ThreadWakeupEventType = |     ThreadWakeupEventType = kernel.timing.RegisterEvent( | ||||||
|         kernel.timing.RegisterEvent("ThreadWakeupCallback", [this](u64 thread_id, s64 cycle_late) { |         "ThreadWakeupCallback_" + std::to_string(core_id), | ||||||
|             ThreadWakeupCallback(thread_id, cycle_late); |         [this](u64 thread_id, s64 cycle_late) { ThreadWakeupCallback(thread_id, cycle_late); }); | ||||||
|         }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ThreadManager::~ThreadManager() { | ThreadManager::~ThreadManager() { | ||||||
|  |  | ||||||
|  | @ -38,7 +38,9 @@ enum ThreadProcessorId : s32 { | ||||||
|     ThreadProcessorIdAll = -1,     ///< Run thread on either core
 |     ThreadProcessorIdAll = -1,     ///< Run thread on either core
 | ||||||
|     ThreadProcessorId0 = 0,        ///< Run thread on core 0 (AppCore)
 |     ThreadProcessorId0 = 0,        ///< Run thread on core 0 (AppCore)
 | ||||||
|     ThreadProcessorId1 = 1,        ///< Run thread on core 1 (SysCore)
 |     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 { | enum class ThreadStatus { | ||||||
|  | @ -75,15 +77,9 @@ private: | ||||||
| 
 | 
 | ||||||
| class ThreadManager { | class ThreadManager { | ||||||
| public: | public: | ||||||
|     explicit ThreadManager(Kernel::KernelSystem& kernel); |     explicit ThreadManager(Kernel::KernelSystem& kernel, u32 core_id); | ||||||
|     ~ThreadManager(); |     ~ThreadManager(); | ||||||
| 
 | 
 | ||||||
|     /**
 |  | ||||||
|      * Creates a new thread ID |  | ||||||
|      * @return The new thread ID |  | ||||||
|      */ |  | ||||||
|     u32 NewThreadId(); |  | ||||||
| 
 |  | ||||||
|     /**
 |     /**
 | ||||||
|      * Gets the current thread |      * Gets the current thread | ||||||
|      */ |      */ | ||||||
|  | @ -150,7 +146,6 @@ private: | ||||||
|     Kernel::KernelSystem& kernel; |     Kernel::KernelSystem& kernel; | ||||||
|     ARM_Interface* cpu; |     ARM_Interface* cpu; | ||||||
| 
 | 
 | ||||||
|     u32 next_thread_id = 1; |  | ||||||
|     std::shared_ptr<Thread> current_thread; |     std::shared_ptr<Thread> current_thread; | ||||||
|     Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue; |     Common::ThreadQueueList<Thread*, ThreadPrioLowest + 1> ready_queue; | ||||||
|     std::unordered_map<u64, Thread*> wakeup_callback_table; |     std::unordered_map<u64, Thread*> wakeup_callback_table; | ||||||
|  | @ -167,7 +162,6 @@ private: | ||||||
|     friend class boost::serialization::access; |     friend class boost::serialization::access; | ||||||
|     template <class Archive> |     template <class Archive> | ||||||
|     void serialize(Archive& ar, const unsigned int file_version) { |     void serialize(Archive& ar, const unsigned int file_version) { | ||||||
|         ar& next_thread_id; |  | ||||||
|         ar& current_thread; |         ar& current_thread; | ||||||
|         ar& ready_queue; |         ar& ready_queue; | ||||||
|         ar& wakeup_callback_table; |         ar& wakeup_callback_table; | ||||||
|  | @ -177,7 +171,7 @@ private: | ||||||
| 
 | 
 | ||||||
| class Thread final : public WaitObject { | class Thread final : public WaitObject { | ||||||
| public: | public: | ||||||
|     explicit Thread(KernelSystem&); |     explicit Thread(KernelSystem&, u32 core_id); | ||||||
|     ~Thread() override; |     ~Thread() override; | ||||||
| 
 | 
 | ||||||
|     std::string GetName() const override { |     std::string GetName() const override { | ||||||
|  | @ -329,6 +323,8 @@ public: | ||||||
|     // available. In case of a timeout, the object will be nullptr.
 |     // available. In case of a timeout, the object will be nullptr.
 | ||||||
|     std::shared_ptr<WakeupCallback> wakeup_callback; |     std::shared_ptr<WakeupCallback> wakeup_callback; | ||||||
| 
 | 
 | ||||||
|  |     const u32 core_id; | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     ThreadManager& thread_manager; |     ThreadManager& thread_manager; | ||||||
| 
 | 
 | ||||||
|  | @ -351,4 +347,20 @@ std::shared_ptr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u | ||||||
| } // namespace Kernel
 | } // namespace Kernel
 | ||||||
| 
 | 
 | ||||||
| BOOST_CLASS_EXPORT_KEY(Kernel::Thread) | 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
 | ||||||
|  |  | ||||||
|  | @ -6,9 +6,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <array> | #include <array> | ||||||
| #include <atomic> | #include <atomic> | ||||||
| #ifndef _MSC_VER |  | ||||||
| #include <cstddef> | #include <cstddef> | ||||||
| #endif |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include "common/bit_field.h" | #include "common/bit_field.h" | ||||||
| #include "common/common_funcs.h" | #include "common/common_funcs.h" | ||||||
|  | @ -177,10 +175,6 @@ struct GyroscopeCalibrateParam { | ||||||
|     } x, y, z; |     } 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)                                                  \ | #define ASSERT_REG_POSITION(field_name, position)                                                  \ | ||||||
|     static_assert(offsetof(SharedMem, field_name) == position * 4,                                 \ |     static_assert(offsetof(SharedMem, field_name) == position * 4,                                 \ | ||||||
|                   "Field " #field_name " has invalid position") |                   "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); | ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A); | ||||||
| 
 | 
 | ||||||
| #undef ASSERT_REG_POSITION | #undef ASSERT_REG_POSITION | ||||||
| #endif // !defined(_MSC_VER)
 |  | ||||||
| 
 | 
 | ||||||
| struct DirectionState { | struct DirectionState { | ||||||
|     bool up; |     bool up; | ||||||
|  |  | ||||||
|  | @ -2,9 +2,14 @@ | ||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
|  | #include <atomic> | ||||||
|  | #ifdef ENABLE_WEB_SERVICE | ||||||
|  | #include <LUrlParser.h> | ||||||
|  | #endif | ||||||
| #include <cryptopp/aes.h> | #include <cryptopp/aes.h> | ||||||
| #include <cryptopp/modes.h> | #include <cryptopp/modes.h> | ||||||
| #include "common/archives.h" | #include "common/archives.h" | ||||||
|  | #include "common/assert.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/file_sys/archive_ncch.h" | #include "core/file_sys/archive_ncch.h" | ||||||
| #include "core/file_sys/file_backend.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
 | const ResultCode ERROR_CERT_ALREADY_SET = // 0xD8A0A03D
 | ||||||
|     ResultCode(61, ErrorModule::HTTP, ErrorSummary::InvalidState, ErrorLevel::Permanent); |     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) { | void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { | ||||||
|     IPC::RequestParser rp(ctx, 0x1, 1, 4); |     IPC::RequestParser rp(ctx, 0x1, 1, 4); | ||||||
|     const u32 shmem_size = rp.Pop<u32>(); |     const u32 shmem_size = rp.Pop<u32>(); | ||||||
|  | @ -156,7 +237,15 @@ void HTTP_C::BeginRequest(Kernel::HLERequestContext& ctx) { | ||||||
|     auto itr = contexts.find(context_handle); |     auto itr = contexts.find(context_handle); | ||||||
|     ASSERT(itr != contexts.end()); |     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); |     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||||
|     rb.Push(RESULT_SUCCESS); |     rb.Push(RESULT_SUCCESS); | ||||||
|  | @ -201,7 +290,15 @@ void HTTP_C::BeginRequestAsync(Kernel::HLERequestContext& ctx) { | ||||||
|     auto itr = contexts.find(context_handle); |     auto itr = contexts.find(context_handle); | ||||||
|     ASSERT(itr != contexts.end()); |     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); |     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||||
|     rb.Push(RESULT_SUCCESS); |     rb.Push(RESULT_SUCCESS); | ||||||
|  | @ -264,7 +361,7 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     contexts.emplace(++context_counter, Context()); |     contexts.try_emplace(++context_counter); | ||||||
|     contexts[context_counter].url = std::move(url); |     contexts[context_counter].url = std::move(url); | ||||||
|     contexts[context_counter].method = method; |     contexts[context_counter].method = method; | ||||||
|     contexts[context_counter].state = RequestState::NotStarted; |     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?
 |     // 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.
 |     // 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); |     contexts.erase(itr); | ||||||
|     session_data->num_http_contexts--; |     session_data->num_http_contexts--; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <future> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <unordered_map> | #include <unordered_map> | ||||||
|  | @ -15,6 +16,12 @@ | ||||||
| #include <boost/serialization/unordered_map.hpp> | #include <boost/serialization/unordered_map.hpp> | ||||||
| #include <boost/serialization/vector.hpp> | #include <boost/serialization/vector.hpp> | ||||||
| #include <boost/serialization/weak_ptr.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/kernel/shared_memory.h" | ||||||
| #include "core/hle/service/service.h" | #include "core/hle/service/service.h" | ||||||
| 
 | 
 | ||||||
|  | @ -113,8 +120,7 @@ public: | ||||||
|     Context(const Context&) = delete; |     Context(const Context&) = delete; | ||||||
|     Context& operator=(const Context&) = delete; |     Context& operator=(const Context&) = delete; | ||||||
| 
 | 
 | ||||||
|     Context(Context&& other) = default; |     void MakeRequest(); | ||||||
|     Context& operator=(Context&&) = default; |  | ||||||
| 
 | 
 | ||||||
|     struct Proxy { |     struct Proxy { | ||||||
|         std::string url; |         std::string url; | ||||||
|  | @ -195,14 +201,21 @@ public: | ||||||
|     u32 session_id; |     u32 session_id; | ||||||
|     std::string url; |     std::string url; | ||||||
|     RequestMethod method; |     RequestMethod method; | ||||||
|     RequestState state = RequestState::NotStarted; |     std::atomic<RequestState> state = RequestState::NotStarted; | ||||||
|     boost::optional<Proxy> proxy; |     std::optional<Proxy> proxy; | ||||||
|     boost::optional<BasicAuth> basic_auth; |     std::optional<BasicAuth> basic_auth; | ||||||
|     SSLConfig ssl_config{}; |     SSLConfig ssl_config{}; | ||||||
|     u32 socket_buffer_size; |     u32 socket_buffer_size; | ||||||
|     std::vector<RequestHeader> headers; |     std::vector<RequestHeader> headers; | ||||||
|     std::vector<PostData> post_data; |     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: | private: | ||||||
|     template <class Archive> |     template <class Archive> | ||||||
|     void serialize(Archive& ar, const unsigned int) { |     void serialize(Archive& ar, const unsigned int) { | ||||||
|  | @ -219,6 +232,7 @@ private: | ||||||
|         ar& post_data; |         ar& post_data; | ||||||
|     } |     } | ||||||
|     friend class boost::serialization::access; |     friend class boost::serialization::access; | ||||||
|  | 
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase { | struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase { | ||||||
|  |  | ||||||
|  | @ -55,7 +55,7 @@ VAddr CROHelper::SegmentTagToAddress(SegmentTag segment_tag) const { | ||||||
|         return 0; |         return 0; | ||||||
| 
 | 
 | ||||||
|     SegmentEntry entry; |     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) |     if (segment_tag.offset_into_segment >= entry.size) | ||||||
|         return 0; |         return 0; | ||||||
|  | @ -71,12 +71,12 @@ ResultCode CROHelper::ApplyRelocation(VAddr target_address, RelocationType reloc | ||||||
|         break; |         break; | ||||||
|     case RelocationType::AbsoluteAddress: |     case RelocationType::AbsoluteAddress: | ||||||
|     case RelocationType::AbsoluteAddress2: |     case RelocationType::AbsoluteAddress2: | ||||||
|         memory.Write32(target_address, symbol_address + addend); |         system.Memory().Write32(target_address, symbol_address + addend); | ||||||
|         cpu.InvalidateCacheRange(target_address, sizeof(u32)); |         system.InvalidateCacheRange(target_address, sizeof(u32)); | ||||||
|         break; |         break; | ||||||
|     case RelocationType::RelativeAddress: |     case RelocationType::RelativeAddress: | ||||||
|         memory.Write32(target_address, symbol_address + addend - target_future_address); |         system.Memory().Write32(target_address, symbol_address + addend - target_future_address); | ||||||
|         cpu.InvalidateCacheRange(target_address, sizeof(u32)); |         system.InvalidateCacheRange(target_address, sizeof(u32)); | ||||||
|         break; |         break; | ||||||
|     case RelocationType::ThumbBranch: |     case RelocationType::ThumbBranch: | ||||||
|     case RelocationType::ArmBranch: |     case RelocationType::ArmBranch: | ||||||
|  | @ -98,8 +98,8 @@ ResultCode CROHelper::ClearRelocation(VAddr target_address, RelocationType reloc | ||||||
|     case RelocationType::AbsoluteAddress: |     case RelocationType::AbsoluteAddress: | ||||||
|     case RelocationType::AbsoluteAddress2: |     case RelocationType::AbsoluteAddress2: | ||||||
|     case RelocationType::RelativeAddress: |     case RelocationType::RelativeAddress: | ||||||
|         memory.Write32(target_address, 0); |         system.Memory().Write32(target_address, 0); | ||||||
|         cpu.InvalidateCacheRange(target_address, sizeof(u32)); |         system.InvalidateCacheRange(target_address, sizeof(u32)); | ||||||
|         break; |         break; | ||||||
|     case RelocationType::ThumbBranch: |     case RelocationType::ThumbBranch: | ||||||
|     case RelocationType::ArmBranch: |     case RelocationType::ArmBranch: | ||||||
|  | @ -121,7 +121,8 @@ ResultCode CROHelper::ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool | ||||||
|     VAddr relocation_address = batch; |     VAddr relocation_address = batch; | ||||||
|     while (true) { |     while (true) { | ||||||
|         RelocationEntry relocation; |         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); |         VAddr relocation_target = SegmentTagToAddress(relocation.target_position); | ||||||
|         if (relocation_target == 0) { |         if (relocation_target == 0) { | ||||||
|  | @ -142,9 +143,9 @@ ResultCode CROHelper::ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     RelocationEntry relocation; |     RelocationEntry relocation; | ||||||
|     memory.ReadBlock(process, batch, &relocation, sizeof(RelocationEntry)); |     system.Memory().ReadBlock(process, batch, &relocation, sizeof(RelocationEntry)); | ||||||
|     relocation.is_batch_resolved = reset ? 0 : 1; |     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; |     return RESULT_SUCCESS; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -154,13 +155,13 @@ VAddr CROHelper::FindExportNamedSymbol(const std::string& name) const { | ||||||
| 
 | 
 | ||||||
|     std::size_t len = name.size(); |     std::size_t len = name.size(); | ||||||
|     ExportTreeEntry entry; |     ExportTreeEntry entry; | ||||||
|     GetEntry(memory, 0, entry); |     GetEntry(system.Memory(), 0, entry); | ||||||
|     ExportTreeEntry::Child next; |     ExportTreeEntry::Child next; | ||||||
|     next.raw = entry.left.raw; |     next.raw = entry.left.raw; | ||||||
|     u32 found_id; |     u32 found_id; | ||||||
| 
 | 
 | ||||||
|     while (true) { |     while (true) { | ||||||
|         GetEntry(memory, next.next_index, entry); |         GetEntry(system.Memory(), next.next_index, entry); | ||||||
| 
 | 
 | ||||||
|         if (next.is_end) { |         if (next.is_end) { | ||||||
|             found_id = entry.export_table_index; |             found_id = entry.export_table_index; | ||||||
|  | @ -186,9 +187,9 @@ VAddr CROHelper::FindExportNamedSymbol(const std::string& name) const { | ||||||
| 
 | 
 | ||||||
|     u32 export_strings_size = GetField(ExportStringsSize); |     u32 export_strings_size = GetField(ExportStringsSize); | ||||||
|     ExportNamedSymbolEntry symbol_entry; |     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 0; | ||||||
| 
 | 
 | ||||||
|     return SegmentTagToAddress(symbol_entry.symbol_position); |     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); |     u32 segment_num = GetField(SegmentNum); | ||||||
|     for (u32 i = 0; i < segment_num; ++i) { |     for (u32 i = 0; i < segment_num; ++i) { | ||||||
|         SegmentEntry segment; |         SegmentEntry segment; | ||||||
|         GetEntry(memory, i, segment); |         GetEntry(system.Memory(), i, segment); | ||||||
|         if (segment.type == SegmentType::Data) { |         if (segment.type == SegmentType::Data) { | ||||||
|             if (segment.size != 0) { |             if (segment.size != 0) { | ||||||
|                 if (segment.size > data_segment_size) |                 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) |             if (segment.offset > module_address + cro_size) | ||||||
|                 return CROFormatError(0x19); |                 return CROFormatError(0x19); | ||||||
|         } |         } | ||||||
|         SetEntry(memory, i, segment); |         SetEntry(system.Memory(), i, segment); | ||||||
|     } |     } | ||||||
|     return MakeResult<u32>(prev_data_segment + module_address); |     return MakeResult<u32>(prev_data_segment + module_address); | ||||||
| } | } | ||||||
|  | @ -310,7 +311,7 @@ ResultCode CROHelper::RebaseExportNamedSymbolTable() { | ||||||
|     u32 export_named_symbol_num = GetField(ExportNamedSymbolNum); |     u32 export_named_symbol_num = GetField(ExportNamedSymbolNum); | ||||||
|     for (u32 i = 0; i < export_named_symbol_num; ++i) { |     for (u32 i = 0; i < export_named_symbol_num; ++i) { | ||||||
|         ExportNamedSymbolEntry entry; |         ExportNamedSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
| 
 | 
 | ||||||
|         if (entry.name_offset != 0) { |         if (entry.name_offset != 0) { | ||||||
|             entry.name_offset += module_address; |             entry.name_offset += module_address; | ||||||
|  | @ -320,7 +321,7 @@ ResultCode CROHelper::RebaseExportNamedSymbolTable() { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         SetEntry(memory, i, entry); |         SetEntry(system.Memory(), i, entry); | ||||||
|     } |     } | ||||||
|     return RESULT_SUCCESS; |     return RESULT_SUCCESS; | ||||||
| } | } | ||||||
|  | @ -329,7 +330,7 @@ ResultCode CROHelper::VerifyExportTreeTable() const { | ||||||
|     u32 tree_num = GetField(ExportTreeNum); |     u32 tree_num = GetField(ExportTreeNum); | ||||||
|     for (u32 i = 0; i < tree_num; ++i) { |     for (u32 i = 0; i < tree_num; ++i) { | ||||||
|         ExportTreeEntry entry; |         ExportTreeEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
| 
 | 
 | ||||||
|         if (entry.left.next_index >= tree_num || entry.right.next_index >= tree_num) { |         if (entry.left.next_index >= tree_num || entry.right.next_index >= tree_num) { | ||||||
|             return CROFormatError(0x11); |             return CROFormatError(0x11); | ||||||
|  | @ -353,7 +354,7 @@ ResultCode CROHelper::RebaseImportModuleTable() { | ||||||
|     u32 module_num = GetField(ImportModuleNum); |     u32 module_num = GetField(ImportModuleNum); | ||||||
|     for (u32 i = 0; i < module_num; ++i) { |     for (u32 i = 0; i < module_num; ++i) { | ||||||
|         ImportModuleEntry entry; |         ImportModuleEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
| 
 | 
 | ||||||
|         if (entry.name_offset != 0) { |         if (entry.name_offset != 0) { | ||||||
|             entry.name_offset += module_address; |             entry.name_offset += module_address; | ||||||
|  | @ -379,7 +380,7 @@ ResultCode CROHelper::RebaseImportModuleTable() { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         SetEntry(memory, i, entry); |         SetEntry(system.Memory(), i, entry); | ||||||
|     } |     } | ||||||
|     return RESULT_SUCCESS; |     return RESULT_SUCCESS; | ||||||
| } | } | ||||||
|  | @ -395,7 +396,7 @@ ResultCode CROHelper::RebaseImportNamedSymbolTable() { | ||||||
|     u32 num = GetField(ImportNamedSymbolNum); |     u32 num = GetField(ImportNamedSymbolNum); | ||||||
|     for (u32 i = 0; i < num; ++i) { |     for (u32 i = 0; i < num; ++i) { | ||||||
|         ImportNamedSymbolEntry entry; |         ImportNamedSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
| 
 | 
 | ||||||
|         if (entry.name_offset != 0) { |         if (entry.name_offset != 0) { | ||||||
|             entry.name_offset += module_address; |             entry.name_offset += module_address; | ||||||
|  | @ -413,7 +414,7 @@ ResultCode CROHelper::RebaseImportNamedSymbolTable() { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         SetEntry(memory, i, entry); |         SetEntry(system.Memory(), i, entry); | ||||||
|     } |     } | ||||||
|     return RESULT_SUCCESS; |     return RESULT_SUCCESS; | ||||||
| } | } | ||||||
|  | @ -427,7 +428,7 @@ ResultCode CROHelper::RebaseImportIndexedSymbolTable() { | ||||||
|     u32 num = GetField(ImportIndexedSymbolNum); |     u32 num = GetField(ImportIndexedSymbolNum); | ||||||
|     for (u32 i = 0; i < num; ++i) { |     for (u32 i = 0; i < num; ++i) { | ||||||
|         ImportIndexedSymbolEntry entry; |         ImportIndexedSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
| 
 | 
 | ||||||
|         if (entry.relocation_batch_offset != 0) { |         if (entry.relocation_batch_offset != 0) { | ||||||
|             entry.relocation_batch_offset += module_address; |             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; |     return RESULT_SUCCESS; | ||||||
| } | } | ||||||
|  | @ -451,7 +452,7 @@ ResultCode CROHelper::RebaseImportAnonymousSymbolTable() { | ||||||
|     u32 num = GetField(ImportAnonymousSymbolNum); |     u32 num = GetField(ImportAnonymousSymbolNum); | ||||||
|     for (u32 i = 0; i < num; ++i) { |     for (u32 i = 0; i < num; ++i) { | ||||||
|         ImportAnonymousSymbolEntry entry; |         ImportAnonymousSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
| 
 | 
 | ||||||
|         if (entry.relocation_batch_offset != 0) { |         if (entry.relocation_batch_offset != 0) { | ||||||
|             entry.relocation_batch_offset += module_address; |             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; |     return RESULT_SUCCESS; | ||||||
| } | } | ||||||
|  | @ -476,14 +477,14 @@ ResultCode CROHelper::ResetExternalRelocations() { | ||||||
|     ExternalRelocationEntry relocation; |     ExternalRelocationEntry relocation; | ||||||
| 
 | 
 | ||||||
|     // Verifies that the last relocation is the end of a batch
 |     // 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) { |     if (!relocation.is_batch_end) { | ||||||
|         return CROFormatError(0x12); |         return CROFormatError(0x12); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     bool batch_begin = true; |     bool batch_begin = true; | ||||||
|     for (u32 i = 0; i < external_relocation_num; ++i) { |     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); |         VAddr relocation_target = SegmentTagToAddress(relocation.target_position); | ||||||
| 
 | 
 | ||||||
|         if (relocation_target == 0) { |         if (relocation_target == 0) { | ||||||
|  | @ -500,7 +501,7 @@ ResultCode CROHelper::ResetExternalRelocations() { | ||||||
|         if (batch_begin) { |         if (batch_begin) { | ||||||
|             // resets to unresolved state
 |             // resets to unresolved state
 | ||||||
|             relocation.is_batch_resolved = 0; |             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
 |         // if current is an end, then the next is a beginning
 | ||||||
|  | @ -516,7 +517,7 @@ ResultCode CROHelper::ClearExternalRelocations() { | ||||||
| 
 | 
 | ||||||
|     bool batch_begin = true; |     bool batch_begin = true; | ||||||
|     for (u32 i = 0; i < external_relocation_num; ++i) { |     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); |         VAddr relocation_target = SegmentTagToAddress(relocation.target_position); | ||||||
| 
 | 
 | ||||||
|         if (relocation_target == 0) { |         if (relocation_target == 0) { | ||||||
|  | @ -532,7 +533,7 @@ ResultCode CROHelper::ClearExternalRelocations() { | ||||||
|         if (batch_begin) { |         if (batch_begin) { | ||||||
|             // resets to unresolved state
 |             // resets to unresolved state
 | ||||||
|             relocation.is_batch_resolved = 0; |             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
 |         // 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 + |         static_relocation_table_offset + | ||||||
|         GetField(StaticRelocationNum) * sizeof(StaticRelocationEntry); |         GetField(StaticRelocationNum) * sizeof(StaticRelocationEntry); | ||||||
| 
 | 
 | ||||||
|     CROHelper crs(crs_address, process, memory, cpu); |     CROHelper crs(crs_address, process, system); | ||||||
|     u32 offset_export_num = GetField(StaticAnonymousSymbolNum); |     u32 offset_export_num = GetField(StaticAnonymousSymbolNum); | ||||||
|     LOG_INFO(Service_LDR, "CRO \"{}\" exports {} static anonymous symbols", ModuleName(), |     LOG_INFO(Service_LDR, "CRO \"{}\" exports {} static anonymous symbols", ModuleName(), | ||||||
|              offset_export_num); |              offset_export_num); | ||||||
|     for (u32 i = 0; i < offset_export_num; ++i) { |     for (u32 i = 0; i < offset_export_num; ++i) { | ||||||
|         StaticAnonymousSymbolEntry entry; |         StaticAnonymousSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
|         u32 batch_address = entry.relocation_batch_offset + module_address; |         u32 batch_address = entry.relocation_batch_offset + module_address; | ||||||
| 
 | 
 | ||||||
|         if (batch_address < static_relocation_table_offset || |         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); |     u32 internal_relocation_num = GetField(InternalRelocationNum); | ||||||
|     for (u32 i = 0; i < internal_relocation_num; ++i) { |     for (u32 i = 0; i < internal_relocation_num; ++i) { | ||||||
|         InternalRelocationEntry relocation; |         InternalRelocationEntry relocation; | ||||||
|         GetEntry(memory, i, relocation); |         GetEntry(system.Memory(), i, relocation); | ||||||
|         VAddr target_addressB = SegmentTagToAddress(relocation.target_position); |         VAddr target_addressB = SegmentTagToAddress(relocation.target_position); | ||||||
|         if (target_addressB == 0) { |         if (target_addressB == 0) { | ||||||
|             return CROFormatError(0x15); |             return CROFormatError(0x15); | ||||||
|  | @ -587,7 +588,7 @@ ResultCode CROHelper::ApplyInternalRelocations(u32 old_data_segment_address) { | ||||||
| 
 | 
 | ||||||
|         VAddr target_address; |         VAddr target_address; | ||||||
|         SegmentEntry target_segment; |         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 (target_segment.type == SegmentType::Data) { | ||||||
|             // If the relocation is to the .data segment, we need to relocate it in the old buffer
 |             // 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; |         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, |         LOG_TRACE(Service_LDR, "Internally relocates 0x{:08X} with 0x{:08X}", target_address, | ||||||
|                   symbol_segment.offset); |                   symbol_segment.offset); | ||||||
|         ResultCode result = ApplyRelocation(target_address, relocation.type, relocation.addend, |         ResultCode result = ApplyRelocation(target_address, relocation.type, relocation.addend, | ||||||
|  | @ -619,7 +620,7 @@ ResultCode CROHelper::ClearInternalRelocations() { | ||||||
|     u32 internal_relocation_num = GetField(InternalRelocationNum); |     u32 internal_relocation_num = GetField(InternalRelocationNum); | ||||||
|     for (u32 i = 0; i < internal_relocation_num; ++i) { |     for (u32 i = 0; i < internal_relocation_num; ++i) { | ||||||
|         InternalRelocationEntry relocation; |         InternalRelocationEntry relocation; | ||||||
|         GetEntry(memory, i, relocation); |         GetEntry(system.Memory(), i, relocation); | ||||||
|         VAddr target_address = SegmentTagToAddress(relocation.target_position); |         VAddr target_address = SegmentTagToAddress(relocation.target_position); | ||||||
| 
 | 
 | ||||||
|         if (target_address == 0) { |         if (target_address == 0) { | ||||||
|  | @ -639,13 +640,13 @@ void CROHelper::UnrebaseImportAnonymousSymbolTable() { | ||||||
|     u32 num = GetField(ImportAnonymousSymbolNum); |     u32 num = GetField(ImportAnonymousSymbolNum); | ||||||
|     for (u32 i = 0; i < num; ++i) { |     for (u32 i = 0; i < num; ++i) { | ||||||
|         ImportAnonymousSymbolEntry entry; |         ImportAnonymousSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
| 
 | 
 | ||||||
|         if (entry.relocation_batch_offset != 0) { |         if (entry.relocation_batch_offset != 0) { | ||||||
|             entry.relocation_batch_offset -= module_address; |             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); |     u32 num = GetField(ImportIndexedSymbolNum); | ||||||
|     for (u32 i = 0; i < num; ++i) { |     for (u32 i = 0; i < num; ++i) { | ||||||
|         ImportIndexedSymbolEntry entry; |         ImportIndexedSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
| 
 | 
 | ||||||
|         if (entry.relocation_batch_offset != 0) { |         if (entry.relocation_batch_offset != 0) { | ||||||
|             entry.relocation_batch_offset -= module_address; |             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); |     u32 num = GetField(ImportNamedSymbolNum); | ||||||
|     for (u32 i = 0; i < num; ++i) { |     for (u32 i = 0; i < num; ++i) { | ||||||
|         ImportNamedSymbolEntry entry; |         ImportNamedSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
| 
 | 
 | ||||||
|         if (entry.name_offset != 0) { |         if (entry.name_offset != 0) { | ||||||
|             entry.name_offset -= module_address; |             entry.name_offset -= module_address; | ||||||
|  | @ -677,7 +678,7 @@ void CROHelper::UnrebaseImportNamedSymbolTable() { | ||||||
|             entry.relocation_batch_offset -= module_address; |             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); |     u32 module_num = GetField(ImportModuleNum); | ||||||
|     for (u32 i = 0; i < module_num; ++i) { |     for (u32 i = 0; i < module_num; ++i) { | ||||||
|         ImportModuleEntry entry; |         ImportModuleEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
| 
 | 
 | ||||||
|         if (entry.name_offset != 0) { |         if (entry.name_offset != 0) { | ||||||
|             entry.name_offset -= module_address; |             entry.name_offset -= module_address; | ||||||
|  | @ -699,7 +700,7 @@ void CROHelper::UnrebaseImportModuleTable() { | ||||||
|             entry.import_anonymous_symbol_table_offset -= module_address; |             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); |     u32 export_named_symbol_num = GetField(ExportNamedSymbolNum); | ||||||
|     for (u32 i = 0; i < export_named_symbol_num; ++i) { |     for (u32 i = 0; i < export_named_symbol_num; ++i) { | ||||||
|         ExportNamedSymbolEntry entry; |         ExportNamedSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
| 
 | 
 | ||||||
|         if (entry.name_offset != 0) { |         if (entry.name_offset != 0) { | ||||||
|             entry.name_offset -= module_address; |             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); |     u32 segment_num = GetField(SegmentNum); | ||||||
|     for (u32 i = 0; i < segment_num; ++i) { |     for (u32 i = 0; i < segment_num; ++i) { | ||||||
|         SegmentEntry segment; |         SegmentEntry segment; | ||||||
|         GetEntry(memory, i, segment); |         GetEntry(system.Memory(), i, segment); | ||||||
| 
 | 
 | ||||||
|         if (segment.type == SegmentType::BSS) { |         if (segment.type == SegmentType::BSS) { | ||||||
|             segment.offset = 0; |             segment.offset = 0; | ||||||
|  | @ -729,7 +730,7 @@ void CROHelper::UnrebaseSegmentTable() { | ||||||
|             segment.offset -= module_address; |             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); |     u32 symbol_import_num = GetField(ImportNamedSymbolNum); | ||||||
|     for (u32 i = 0; i < symbol_import_num; ++i) { |     for (u32 i = 0; i < symbol_import_num; ++i) { | ||||||
|         ImportNamedSymbolEntry entry; |         ImportNamedSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
|         VAddr relocation_addr = entry.relocation_batch_offset; |         VAddr relocation_addr = entry.relocation_batch_offset; | ||||||
|         ExternalRelocationEntry relocation_entry; |         ExternalRelocationEntry relocation_entry; | ||||||
|         memory.ReadBlock(process, relocation_addr, &relocation_entry, |         system.Memory().ReadBlock(process, relocation_addr, &relocation_entry, | ||||||
|                                   sizeof(ExternalRelocationEntry)); |                                   sizeof(ExternalRelocationEntry)); | ||||||
| 
 | 
 | ||||||
|         if (!relocation_entry.is_batch_resolved) { |         if (!relocation_entry.is_batch_resolved) { | ||||||
|             ResultCode result = ForEachAutoLinkCRO( |             ResultCode result = ForEachAutoLinkCRO( | ||||||
|                 process, memory, cpu, crs_address, [&](CROHelper source) -> ResultVal<bool> { |                 process, system, crs_address, [&](CROHelper source) -> ResultVal<bool> { | ||||||
|                     std::string symbol_name = |                     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); |                     u32 symbol_address = source.FindExportNamedSymbol(symbol_name); | ||||||
| 
 | 
 | ||||||
|                     if (symbol_address != 0) { |                     if (symbol_address != 0) { | ||||||
|  | @ -794,10 +795,10 @@ ResultCode CROHelper::ResetImportNamedSymbol() { | ||||||
|     u32 symbol_import_num = GetField(ImportNamedSymbolNum); |     u32 symbol_import_num = GetField(ImportNamedSymbolNum); | ||||||
|     for (u32 i = 0; i < symbol_import_num; ++i) { |     for (u32 i = 0; i < symbol_import_num; ++i) { | ||||||
|         ImportNamedSymbolEntry entry; |         ImportNamedSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
|         VAddr relocation_addr = entry.relocation_batch_offset; |         VAddr relocation_addr = entry.relocation_batch_offset; | ||||||
|         ExternalRelocationEntry relocation_entry; |         ExternalRelocationEntry relocation_entry; | ||||||
|         memory.ReadBlock(process, relocation_addr, &relocation_entry, |         system.Memory().ReadBlock(process, relocation_addr, &relocation_entry, | ||||||
|                                   sizeof(ExternalRelocationEntry)); |                                   sizeof(ExternalRelocationEntry)); | ||||||
| 
 | 
 | ||||||
|         ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true); |         ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true); | ||||||
|  | @ -815,10 +816,10 @@ ResultCode CROHelper::ResetImportIndexedSymbol() { | ||||||
|     u32 import_num = GetField(ImportIndexedSymbolNum); |     u32 import_num = GetField(ImportIndexedSymbolNum); | ||||||
|     for (u32 i = 0; i < import_num; ++i) { |     for (u32 i = 0; i < import_num; ++i) { | ||||||
|         ImportIndexedSymbolEntry entry; |         ImportIndexedSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
|         VAddr relocation_addr = entry.relocation_batch_offset; |         VAddr relocation_addr = entry.relocation_batch_offset; | ||||||
|         ExternalRelocationEntry relocation_entry; |         ExternalRelocationEntry relocation_entry; | ||||||
|         memory.ReadBlock(process, relocation_addr, &relocation_entry, |         system.Memory().ReadBlock(process, relocation_addr, &relocation_entry, | ||||||
|                                   sizeof(ExternalRelocationEntry)); |                                   sizeof(ExternalRelocationEntry)); | ||||||
| 
 | 
 | ||||||
|         ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true); |         ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true); | ||||||
|  | @ -836,10 +837,10 @@ ResultCode CROHelper::ResetImportAnonymousSymbol() { | ||||||
|     u32 import_num = GetField(ImportAnonymousSymbolNum); |     u32 import_num = GetField(ImportAnonymousSymbolNum); | ||||||
|     for (u32 i = 0; i < import_num; ++i) { |     for (u32 i = 0; i < import_num; ++i) { | ||||||
|         ImportAnonymousSymbolEntry entry; |         ImportAnonymousSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
|         VAddr relocation_addr = entry.relocation_batch_offset; |         VAddr relocation_addr = entry.relocation_batch_offset; | ||||||
|         ExternalRelocationEntry relocation_entry; |         ExternalRelocationEntry relocation_entry; | ||||||
|         memory.ReadBlock(process, relocation_addr, &relocation_entry, |         system.Memory().ReadBlock(process, relocation_addr, &relocation_entry, | ||||||
|                                   sizeof(ExternalRelocationEntry)); |                                   sizeof(ExternalRelocationEntry)); | ||||||
| 
 | 
 | ||||||
|         ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true); |         ResultCode result = ApplyRelocationBatch(relocation_addr, unresolved_symbol, true); | ||||||
|  | @ -857,19 +858,20 @@ ResultCode CROHelper::ApplyModuleImport(VAddr crs_address) { | ||||||
|     u32 import_module_num = GetField(ImportModuleNum); |     u32 import_module_num = GetField(ImportModuleNum); | ||||||
|     for (u32 i = 0; i < import_module_num; ++i) { |     for (u32 i = 0; i < import_module_num; ++i) { | ||||||
|         ImportModuleEntry entry; |         ImportModuleEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
|         std::string want_cro_name = memory.ReadCString(entry.name_offset, import_strings_size); |         std::string want_cro_name = | ||||||
|  |             system.Memory().ReadCString(entry.name_offset, import_strings_size); | ||||||
| 
 | 
 | ||||||
|         ResultCode result = ForEachAutoLinkCRO( |         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()) { |                 if (want_cro_name == source.ModuleName()) { | ||||||
|                     LOG_INFO(Service_LDR, "CRO \"{}\" imports {} indexed symbols from \"{}\"", |                     LOG_INFO(Service_LDR, "CRO \"{}\" imports {} indexed symbols from \"{}\"", | ||||||
|                              ModuleName(), entry.import_indexed_symbol_num, source.ModuleName()); |                              ModuleName(), entry.import_indexed_symbol_num, source.ModuleName()); | ||||||
|                     for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) { |                     for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) { | ||||||
|                         ImportIndexedSymbolEntry im; |                         ImportIndexedSymbolEntry im; | ||||||
|                         entry.GetImportIndexedSymbolEntry(process, memory, j, im); |                         entry.GetImportIndexedSymbolEntry(process, system.Memory(), j, im); | ||||||
|                         ExportIndexedSymbolEntry ex; |                         ExportIndexedSymbolEntry ex; | ||||||
|                         source.GetEntry(memory, im.index, ex); |                         source.GetEntry(system.Memory(), im.index, ex); | ||||||
|                         u32 symbol_address = source.SegmentTagToAddress(ex.symbol_position); |                         u32 symbol_address = source.SegmentTagToAddress(ex.symbol_position); | ||||||
|                         LOG_TRACE(Service_LDR, "    Imports 0x{:08X}", symbol_address); |                         LOG_TRACE(Service_LDR, "    Imports 0x{:08X}", symbol_address); | ||||||
|                         ResultCode result = |                         ResultCode result = | ||||||
|  | @ -884,7 +886,7 @@ ResultCode CROHelper::ApplyModuleImport(VAddr crs_address) { | ||||||
|                              ModuleName(), entry.import_anonymous_symbol_num, source.ModuleName()); |                              ModuleName(), entry.import_anonymous_symbol_num, source.ModuleName()); | ||||||
|                     for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) { |                     for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) { | ||||||
|                         ImportAnonymousSymbolEntry im; |                         ImportAnonymousSymbolEntry im; | ||||||
|                         entry.GetImportAnonymousSymbolEntry(process, memory, j, im); |                         entry.GetImportAnonymousSymbolEntry(process, system.Memory(), j, im); | ||||||
|                         u32 symbol_address = source.SegmentTagToAddress(im.symbol_position); |                         u32 symbol_address = source.SegmentTagToAddress(im.symbol_position); | ||||||
|                         LOG_TRACE(Service_LDR, "    Imports 0x{:08X}", symbol_address); |                         LOG_TRACE(Service_LDR, "    Imports 0x{:08X}", symbol_address); | ||||||
|                         ResultCode result = |                         ResultCode result = | ||||||
|  | @ -913,15 +915,15 @@ ResultCode CROHelper::ApplyExportNamedSymbol(CROHelper target) { | ||||||
|     u32 target_symbol_import_num = target.GetField(ImportNamedSymbolNum); |     u32 target_symbol_import_num = target.GetField(ImportNamedSymbolNum); | ||||||
|     for (u32 i = 0; i < target_symbol_import_num; ++i) { |     for (u32 i = 0; i < target_symbol_import_num; ++i) { | ||||||
|         ImportNamedSymbolEntry entry; |         ImportNamedSymbolEntry entry; | ||||||
|         target.GetEntry(memory, i, entry); |         target.GetEntry(system.Memory(), i, entry); | ||||||
|         VAddr relocation_addr = entry.relocation_batch_offset; |         VAddr relocation_addr = entry.relocation_batch_offset; | ||||||
|         ExternalRelocationEntry relocation_entry; |         ExternalRelocationEntry relocation_entry; | ||||||
|         memory.ReadBlock(process, relocation_addr, &relocation_entry, |         system.Memory().ReadBlock(process, relocation_addr, &relocation_entry, | ||||||
|                                   sizeof(ExternalRelocationEntry)); |                                   sizeof(ExternalRelocationEntry)); | ||||||
| 
 | 
 | ||||||
|         if (!relocation_entry.is_batch_resolved) { |         if (!relocation_entry.is_batch_resolved) { | ||||||
|             std::string symbol_name = |             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); |             u32 symbol_address = FindExportNamedSymbol(symbol_name); | ||||||
|             if (symbol_address != 0) { |             if (symbol_address != 0) { | ||||||
|                 LOG_TRACE(Service_LDR, "    exports symbol \"{}\"", symbol_name); |                 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); |     u32 target_symbol_import_num = target.GetField(ImportNamedSymbolNum); | ||||||
|     for (u32 i = 0; i < target_symbol_import_num; ++i) { |     for (u32 i = 0; i < target_symbol_import_num; ++i) { | ||||||
|         ImportNamedSymbolEntry entry; |         ImportNamedSymbolEntry entry; | ||||||
|         target.GetEntry(memory, i, entry); |         target.GetEntry(system.Memory(), i, entry); | ||||||
|         VAddr relocation_addr = entry.relocation_batch_offset; |         VAddr relocation_addr = entry.relocation_batch_offset; | ||||||
|         ExternalRelocationEntry relocation_entry; |         ExternalRelocationEntry relocation_entry; | ||||||
|         memory.ReadBlock(process, relocation_addr, &relocation_entry, |         system.Memory().ReadBlock(process, relocation_addr, &relocation_entry, | ||||||
|                                   sizeof(ExternalRelocationEntry)); |                                   sizeof(ExternalRelocationEntry)); | ||||||
| 
 | 
 | ||||||
|         if (relocation_entry.is_batch_resolved) { |         if (relocation_entry.is_batch_resolved) { | ||||||
|             std::string symbol_name = |             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); |             u32 symbol_address = FindExportNamedSymbol(symbol_name); | ||||||
|             if (symbol_address != 0) { |             if (symbol_address != 0) { | ||||||
|                 LOG_TRACE(Service_LDR, "    unexports symbol \"{}\"", symbol_name); |                 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); |     u32 target_import_module_num = target.GetField(ImportModuleNum); | ||||||
|     for (u32 i = 0; i < target_import_module_num; ++i) { |     for (u32 i = 0; i < target_import_module_num; ++i) { | ||||||
|         ImportModuleEntry entry; |         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; |             continue; | ||||||
| 
 | 
 | ||||||
|         LOG_INFO(Service_LDR, "CRO \"{}\" exports {} indexed symbols to \"{}\"", module_name, |         LOG_INFO(Service_LDR, "CRO \"{}\" exports {} indexed symbols to \"{}\"", module_name, | ||||||
|                  entry.import_indexed_symbol_num, target.ModuleName()); |                  entry.import_indexed_symbol_num, target.ModuleName()); | ||||||
|         for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) { |         for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) { | ||||||
|             ImportIndexedSymbolEntry im; |             ImportIndexedSymbolEntry im; | ||||||
|             entry.GetImportIndexedSymbolEntry(process, memory, j, im); |             entry.GetImportIndexedSymbolEntry(process, system.Memory(), j, im); | ||||||
|             ExportIndexedSymbolEntry ex; |             ExportIndexedSymbolEntry ex; | ||||||
|             GetEntry(memory, im.index, ex); |             GetEntry(system.Memory(), im.index, ex); | ||||||
|             u32 symbol_address = SegmentTagToAddress(ex.symbol_position); |             u32 symbol_address = SegmentTagToAddress(ex.symbol_position); | ||||||
|             LOG_TRACE(Service_LDR, "    exports symbol 0x{:08X}", symbol_address); |             LOG_TRACE(Service_LDR, "    exports symbol 0x{:08X}", symbol_address); | ||||||
|             ResultCode result = |             ResultCode result = | ||||||
|  | @ -1000,7 +1003,7 @@ ResultCode CROHelper::ApplyModuleExport(CROHelper target) { | ||||||
|                  entry.import_anonymous_symbol_num, target.ModuleName()); |                  entry.import_anonymous_symbol_num, target.ModuleName()); | ||||||
|         for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) { |         for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) { | ||||||
|             ImportAnonymousSymbolEntry im; |             ImportAnonymousSymbolEntry im; | ||||||
|             entry.GetImportAnonymousSymbolEntry(process, memory, j, im); |             entry.GetImportAnonymousSymbolEntry(process, system.Memory(), j, im); | ||||||
|             u32 symbol_address = SegmentTagToAddress(im.symbol_position); |             u32 symbol_address = SegmentTagToAddress(im.symbol_position); | ||||||
|             LOG_TRACE(Service_LDR, "    exports symbol 0x{:08X}", symbol_address); |             LOG_TRACE(Service_LDR, "    exports symbol 0x{:08X}", symbol_address); | ||||||
|             ResultCode result = |             ResultCode result = | ||||||
|  | @ -1023,16 +1026,17 @@ ResultCode CROHelper::ResetModuleExport(CROHelper target) { | ||||||
|     u32 target_import_module_num = target.GetField(ImportModuleNum); |     u32 target_import_module_num = target.GetField(ImportModuleNum); | ||||||
|     for (u32 i = 0; i < target_import_module_num; ++i) { |     for (u32 i = 0; i < target_import_module_num; ++i) { | ||||||
|         ImportModuleEntry entry; |         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; |             continue; | ||||||
| 
 | 
 | ||||||
|         LOG_DEBUG(Service_LDR, "CRO \"{}\" unexports indexed symbols to \"{}\"", module_name, |         LOG_DEBUG(Service_LDR, "CRO \"{}\" unexports indexed symbols to \"{}\"", module_name, | ||||||
|                   target.ModuleName()); |                   target.ModuleName()); | ||||||
|         for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) { |         for (u32 j = 0; j < entry.import_indexed_symbol_num; ++j) { | ||||||
|             ImportIndexedSymbolEntry im; |             ImportIndexedSymbolEntry im; | ||||||
|             entry.GetImportIndexedSymbolEntry(process, memory, j, im); |             entry.GetImportIndexedSymbolEntry(process, system.Memory(), j, im); | ||||||
|             ResultCode result = |             ResultCode result = | ||||||
|                 target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true); |                 target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true); | ||||||
|             if (result.IsError()) { |             if (result.IsError()) { | ||||||
|  | @ -1045,7 +1049,7 @@ ResultCode CROHelper::ResetModuleExport(CROHelper target) { | ||||||
|                   target.ModuleName()); |                   target.ModuleName()); | ||||||
|         for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) { |         for (u32 j = 0; j < entry.import_anonymous_symbol_num; ++j) { | ||||||
|             ImportAnonymousSymbolEntry im; |             ImportAnonymousSymbolEntry im; | ||||||
|             entry.GetImportAnonymousSymbolEntry(process, memory, j, im); |             entry.GetImportAnonymousSymbolEntry(process, system.Memory(), j, im); | ||||||
|             ResultCode result = |             ResultCode result = | ||||||
|                 target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true); |                 target.ApplyRelocationBatch(im.relocation_batch_offset, unresolved_symbol, true); | ||||||
|             if (result.IsError()) { |             if (result.IsError()) { | ||||||
|  | @ -1063,15 +1067,16 @@ ResultCode CROHelper::ApplyExitRelocations(VAddr crs_address) { | ||||||
|     u32 symbol_import_num = GetField(ImportNamedSymbolNum); |     u32 symbol_import_num = GetField(ImportNamedSymbolNum); | ||||||
|     for (u32 i = 0; i < symbol_import_num; ++i) { |     for (u32 i = 0; i < symbol_import_num; ++i) { | ||||||
|         ImportNamedSymbolEntry entry; |         ImportNamedSymbolEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
|         VAddr relocation_addr = entry.relocation_batch_offset; |         VAddr relocation_addr = entry.relocation_batch_offset; | ||||||
|         ExternalRelocationEntry relocation_entry; |         ExternalRelocationEntry relocation_entry; | ||||||
|         memory.ReadBlock(process, relocation_addr, &relocation_entry, |         system.Memory().ReadBlock(process, relocation_addr, &relocation_entry, | ||||||
|                                   sizeof(ExternalRelocationEntry)); |                                   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( |             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_"); |                     u32 symbol_address = source.FindExportNamedSymbol("nnroAeabiAtexit_"); | ||||||
| 
 | 
 | ||||||
|                     if (symbol_address != 0) { |                     if (symbol_address != 0) { | ||||||
|  | @ -1126,7 +1131,8 @@ ResultCode CROHelper::Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     result = VerifyStringTableLength(memory, GetField(ModuleNameOffset), GetField(ModuleNameSize)); |     result = VerifyStringTableLength(system.Memory(), GetField(ModuleNameOffset), | ||||||
|  |                                      GetField(ModuleNameSize)); | ||||||
|     if (result.IsError()) { |     if (result.IsError()) { | ||||||
|         LOG_ERROR(Service_LDR, "Error verifying module name {:08X}", result.raw); |         LOG_ERROR(Service_LDR, "Error verifying module name {:08X}", result.raw); | ||||||
|         return result; |         return result; | ||||||
|  | @ -1155,8 +1161,8 @@ ResultCode CROHelper::Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     result = |     result = VerifyStringTableLength(system.Memory(), GetField(ExportStringsOffset), | ||||||
|         VerifyStringTableLength(memory, GetField(ExportStringsOffset), GetField(ExportStringsSize)); |                                      GetField(ExportStringsSize)); | ||||||
|     if (result.IsError()) { |     if (result.IsError()) { | ||||||
|         LOG_ERROR(Service_LDR, "Error verifying export strings {:08X}", result.raw); |         LOG_ERROR(Service_LDR, "Error verifying export strings {:08X}", result.raw); | ||||||
|         return result; |         return result; | ||||||
|  | @ -1192,8 +1198,8 @@ ResultCode CROHelper::Rebase(VAddr crs_address, u32 cro_size, VAddr data_segment | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     result = |     result = VerifyStringTableLength(system.Memory(), GetField(ImportStringsOffset), | ||||||
|         VerifyStringTableLength(memory, GetField(ImportStringsOffset), GetField(ImportStringsSize)); |                                      GetField(ImportStringsSize)); | ||||||
|     if (result.IsError()) { |     if (result.IsError()) { | ||||||
|         LOG_ERROR(Service_LDR, "Error verifying import strings {:08X}", result.raw); |         LOG_ERROR(Service_LDR, "Error verifying import strings {:08X}", result.raw); | ||||||
|         return result; |         return result; | ||||||
|  | @ -1266,11 +1272,11 @@ ResultCode CROHelper::Link(VAddr crs_address, bool link_on_load_bug_fix) { | ||||||
|             // so we do the same
 |             // so we do the same
 | ||||||
|             if (GetField(SegmentNum) >= 2) { // means we have .data segment
 |             if (GetField(SegmentNum) >= 2) { // means we have .data segment
 | ||||||
|                 SegmentEntry entry; |                 SegmentEntry entry; | ||||||
|                 GetEntry(memory, 2, entry); |                 GetEntry(system.Memory(), 2, entry); | ||||||
|                 ASSERT(entry.type == SegmentType::Data); |                 ASSERT(entry.type == SegmentType::Data); | ||||||
|                 data_segment_address = entry.offset; |                 data_segment_address = entry.offset; | ||||||
|                 entry.offset = GetField(DataOffset); |                 entry.offset = GetField(DataOffset); | ||||||
|                 SetEntry(memory, 2, entry); |                 SetEntry(system.Memory(), 2, entry); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         SCOPE_EXIT({ |         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 (link_on_load_bug_fix) { | ||||||
|                 if (GetField(SegmentNum) >= 2) { |                 if (GetField(SegmentNum) >= 2) { | ||||||
|                     SegmentEntry entry; |                     SegmentEntry entry; | ||||||
|                     GetEntry(memory, 2, entry); |                     GetEntry(system.Memory(), 2, entry); | ||||||
|                     entry.offset = data_segment_address; |                     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
 |     // Exports symbols to other modules
 | ||||||
|     result = ForEachAutoLinkCRO(process, memory, cpu, crs_address, |     result = ForEachAutoLinkCRO(process, system, crs_address, | ||||||
|                                 [this](CROHelper target) -> ResultVal<bool> { |                                 [this](CROHelper target) -> ResultVal<bool> { | ||||||
|                                     ResultCode result = ApplyExportNamedSymbol(target); |                                     ResultCode result = ApplyExportNamedSymbol(target); | ||||||
|                                     if (result.IsError()) |                                     if (result.IsError()) | ||||||
|  | @ -1346,7 +1352,7 @@ ResultCode CROHelper::Unlink(VAddr crs_address) { | ||||||
| 
 | 
 | ||||||
|     // Resets all symbols in other modules imported from this module
 |     // Resets all symbols in other modules imported from this module
 | ||||||
|     // Note: the RO service seems only searching in auto-link modules
 |     // 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> { |                                 [this](CROHelper target) -> ResultVal<bool> { | ||||||
|                                     ResultCode result = ResetExportNamedSymbol(target); |                                     ResultCode result = ResetExportNamedSymbol(target); | ||||||
|                                     if (result.IsError()) |                                     if (result.IsError()) | ||||||
|  | @ -1387,13 +1393,13 @@ void CROHelper::InitCRS() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void CROHelper::Register(VAddr crs_address, bool auto_link) { | void CROHelper::Register(VAddr crs_address, bool auto_link) { | ||||||
|     CROHelper crs(crs_address, process, memory, cpu); |     CROHelper crs(crs_address, process, system); | ||||||
|     CROHelper head(auto_link ? crs.NextModule() : crs.PreviousModule(), process, memory, cpu); |     CROHelper head(auto_link ? crs.NextModule() : crs.PreviousModule(), process, system); | ||||||
| 
 | 
 | ||||||
|     if (head.module_address) { |     if (head.module_address) { | ||||||
|         // there are already CROs registered
 |         // there are already CROs registered
 | ||||||
|         // register as the new tail
 |         // register as the new tail
 | ||||||
|         CROHelper tail(head.PreviousModule(), process, memory, cpu); |         CROHelper tail(head.PreviousModule(), process, system); | ||||||
| 
 | 
 | ||||||
|         // link with the old tail
 |         // link with the old tail
 | ||||||
|         ASSERT(tail.NextModule() == 0); |         ASSERT(tail.NextModule() == 0); | ||||||
|  | @ -1419,11 +1425,11 @@ void CROHelper::Register(VAddr crs_address, bool auto_link) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void CROHelper::Unregister(VAddr crs_address) { | void CROHelper::Unregister(VAddr crs_address) { | ||||||
|     CROHelper crs(crs_address, process, memory, cpu); |     CROHelper crs(crs_address, process, system); | ||||||
|     CROHelper next_head(crs.NextModule(), process, memory, cpu); |     CROHelper next_head(crs.NextModule(), process, system); | ||||||
|     CROHelper previous_head(crs.PreviousModule(), process, memory, cpu); |     CROHelper previous_head(crs.PreviousModule(), process, system); | ||||||
|     CROHelper next(NextModule(), process, memory, cpu); |     CROHelper next(NextModule(), process, system); | ||||||
|     CROHelper previous(PreviousModule(), process, memory, cpu); |     CROHelper previous(PreviousModule(), process, system); | ||||||
| 
 | 
 | ||||||
|     if (module_address == next_head.module_address || |     if (module_address == next_head.module_address || | ||||||
|         module_address == previous_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); |     u32 segment_num = GetField(SegmentNum); | ||||||
|     for (u32 i = 0; i < segment_num; ++i) { |     for (u32 i = 0; i < segment_num; ++i) { | ||||||
|         SegmentEntry entry; |         SegmentEntry entry; | ||||||
|         GetEntry(memory, i, entry); |         GetEntry(system.Memory(), i, entry); | ||||||
|         if (entry.type == SegmentType::Code && entry.size != 0) { |         if (entry.type == SegmentType::Code && entry.size != 0) { | ||||||
|             VAddr begin = Common::AlignDown(entry.offset, Memory::PAGE_SIZE); |             VAddr begin = Common::AlignDown(entry.offset, Memory::PAGE_SIZE); | ||||||
|             VAddr end = Common::AlignUp(entry.offset + entry.size, Memory::PAGE_SIZE); |             VAddr end = Common::AlignUp(entry.offset + entry.size, Memory::PAGE_SIZE); | ||||||
|  |  | ||||||
|  | @ -33,12 +33,11 @@ static constexpr u32 CRO_HASH_SIZE = 0x80; | ||||||
| class CROHelper final { | class CROHelper final { | ||||||
| public: | public: | ||||||
|     // TODO (wwylele): pass in the process handle for memory access
 |     // TODO (wwylele): pass in the process handle for memory access
 | ||||||
|     explicit CROHelper(VAddr cro_address, Kernel::Process& process, Memory::MemorySystem& memory, |     explicit CROHelper(VAddr cro_address, Kernel::Process& process, Core::System& system) | ||||||
|                        ARM_Interface& cpu) |         : module_address(cro_address), process(process), system(system) {} | ||||||
|         : module_address(cro_address), process(process), memory(memory), cpu(cpu) {} |  | ||||||
| 
 | 
 | ||||||
|     std::string ModuleName() const { |     std::string ModuleName() const { | ||||||
|         return memory.ReadCString(GetField(ModuleNameOffset), GetField(ModuleNameSize)); |         return system.Memory().ReadCString(GetField(ModuleNameOffset), GetField(ModuleNameSize)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     u32 GetFileSize() const { |     u32 GetFileSize() const { | ||||||
|  | @ -144,8 +143,7 @@ public: | ||||||
| private: | private: | ||||||
|     const VAddr module_address; ///< the virtual address of this module
 |     const VAddr module_address; ///< the virtual address of this module
 | ||||||
|     Kernel::Process& process;   ///< the owner process of this module
 |     Kernel::Process& process;   ///< the owner process of this module
 | ||||||
|     Memory::MemorySystem& memory; |     Core::System& system; | ||||||
|     ARM_Interface& cpu; |  | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Each item in this enum represents a u32 field in the header begin from address+0x80, |      * 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 { |     u32 GetField(HeaderField field) const { | ||||||
|         return memory.Read32(Field(field)); |         return system.Memory().Read32(Field(field)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void SetField(HeaderField field, u32 value) { |     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. |      *         otherwise error code of the last iteration. | ||||||
|      */ |      */ | ||||||
|     template <typename FunctionObject> |     template <typename FunctionObject> | ||||||
|     static ResultCode ForEachAutoLinkCRO(Kernel::Process& process, Memory::MemorySystem& memory, |     static ResultCode ForEachAutoLinkCRO(Kernel::Process& process, Core::System& system, | ||||||
|                                          ARM_Interface& cpu, VAddr crs_address, |                                          VAddr crs_address, FunctionObject func) { | ||||||
|                                          FunctionObject func) { |  | ||||||
|         VAddr current = crs_address; |         VAddr current = crs_address; | ||||||
|         while (current != 0) { |         while (current != 0) { | ||||||
|             CROHelper cro(current, process, memory, cpu); |             CROHelper cro(current, process, system); | ||||||
|             CASCADE_RESULT(bool next, func(cro)); |             CASCADE_RESULT(bool next, func(cro)); | ||||||
|             if (!next) |             if (!next) | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|  | @ -120,7 +120,7 @@ void RO::Initialize(Kernel::HLERequestContext& ctx) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     CROHelper crs(crs_address, *process, system.Memory(), system.CPU()); |     CROHelper crs(crs_address, *process, system); | ||||||
|     crs.InitCRS(); |     crs.InitCRS(); | ||||||
| 
 | 
 | ||||||
|     result = crs.Rebase(0, crs_size, 0, 0, 0, 0, true); |     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; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     CROHelper cro(cro_address, *process, system.Memory(), system.CPU()); |     CROHelper cro(cro_address, *process, system); | ||||||
| 
 | 
 | ||||||
|     result = cro.VerifyHash(cro_size, crr_address); |     result = cro.VerifyHash(cro_size, crr_address); | ||||||
|     if (result.IsError()) { |     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(), |     LOG_INFO(Service_LDR, "CRO \"{}\" loaded at 0x{:08X}, fixed_end=0x{:08X}", cro.ModuleName(), | ||||||
|              cro_address, cro_address + fix_size); |              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}", |     LOG_DEBUG(Service_LDR, "called, cro_address=0x{:08X}, zero={}, cro_buffer_ptr=0x{:08X}", | ||||||
|               cro_address, zero, cro_buffer_ptr); |               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); |     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); |         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); |     rb.Push(result); | ||||||
| } | } | ||||||
|  | @ -403,7 +403,7 @@ void RO::LinkCRO(Kernel::HLERequestContext& ctx) { | ||||||
| 
 | 
 | ||||||
|     LOG_DEBUG(Service_LDR, "called, cro_address=0x{:08X}", cro_address); |     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); |     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); |     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); |     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||||
| 
 | 
 | ||||||
|  | @ -492,7 +492,7 @@ void RO::Shutdown(Kernel::HLERequestContext& ctx) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     CROHelper crs(slot->loaded_crs, *process, system.Memory(), system.CPU()); |     CROHelper crs(slot->loaded_crs, *process, system); | ||||||
|     crs.Unrebase(true); |     crs.Unrebase(true); | ||||||
| 
 | 
 | ||||||
|     ResultCode result = RESULT_SUCCESS; |     ResultCode result = RESULT_SUCCESS; | ||||||
|  |  | ||||||
|  | @ -402,8 +402,8 @@ inline void Write(u32 addr, const T data) { | ||||||
|     switch (index) { |     switch (index) { | ||||||
| 
 | 
 | ||||||
|     // Memory fills are triggered once the fill value is written.
 |     // 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(memory_fill_config[0].trigger): | ||||||
|     case GPU_REG_INDEX_WORKAROUND(memory_fill_config[1].trigger, 0x00008 + 0x3): { |     case GPU_REG_INDEX(memory_fill_config[1].trigger): { | ||||||
|         const bool is_second_filler = (index != GPU_REG_INDEX(memory_fill_config[0].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]; |         auto& config = g_regs.memory_fill_config[is_second_filler]; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -22,41 +22,15 @@ namespace GPU { | ||||||
| constexpr float SCREEN_REFRESH_RATE = 60; | constexpr float SCREEN_REFRESH_RATE = 60; | ||||||
| 
 | 
 | ||||||
| // Returns index corresponding to the Regs member labeled by field_name
 | // 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)) | #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
 | // MMIO region 0x1EFxxxxx
 | ||||||
| struct Regs { | struct Regs { | ||||||
| 
 | 
 | ||||||
| // helper macro to make sure the defined structures are of the expected size.
 | // 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)                                                    \ | #define ASSERT_MEMBER_SIZE(name, size_in_bytes)                                                    \ | ||||||
|     static_assert(sizeof(name) == size_in_bytes,                                                   \ |     static_assert(sizeof(name) == size_in_bytes,                                                   \ | ||||||
|                   "Structure size and register block length don't match") |                   "Structure size and register block length don't match") | ||||||
| #endif |  | ||||||
| 
 | 
 | ||||||
|     // Components are laid out in reverse byte order, most significant bits first.
 |     // Components are laid out in reverse byte order, most significant bits first.
 | ||||||
|     enum class PixelFormat : u32 { |     enum class PixelFormat : u32 { | ||||||
|  | @ -307,10 +281,6 @@ private: | ||||||
| }; | }; | ||||||
| static_assert(std::is_standard_layout<Regs>::value, "Structure does not use standard layout"); | 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)                                                  \ | #define ASSERT_REG_POSITION(field_name, position)                                                  \ | ||||||
|     static_assert(offsetof(Regs, field_name) == position * 4,                                      \ |     static_assert(offsetof(Regs, field_name) == position * 4,                                      \ | ||||||
|                   "Field " #field_name " has invalid position") |                   "Field " #field_name " has invalid position") | ||||||
|  | @ -323,7 +293,6 @@ ASSERT_REG_POSITION(display_transfer_config, 0x00300); | ||||||
| ASSERT_REG_POSITION(command_processor_config, 0x00638); | ASSERT_REG_POSITION(command_processor_config, 0x00638); | ||||||
| 
 | 
 | ||||||
| #undef ASSERT_REG_POSITION | #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
 | // The total number of registers is chosen arbitrarily, but let's make sure it's not some odd value
 | ||||||
| // anyway.
 | // anyway.
 | ||||||
|  |  | ||||||
|  | @ -309,7 +309,7 @@ ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileSys::RomFSReader> | ||||||
|         if (!romfs_file_inner.IsOpen()) |         if (!romfs_file_inner.IsOpen()) | ||||||
|             return ResultStatus::Error; |             return ResultStatus::Error; | ||||||
| 
 | 
 | ||||||
|         romfs_file = std::make_shared<FileSys::RomFSReader>(std::move(romfs_file_inner), |         romfs_file = std::make_shared<FileSys::DirectRomFSReader>(std::move(romfs_file_inner), | ||||||
|                                                                   romfs_offset, romfs_size); |                                                                   romfs_offset, romfs_size); | ||||||
| 
 | 
 | ||||||
|         return ResultStatus::Success; |         return ResultStatus::Success; | ||||||
|  |  | ||||||
|  | @ -105,13 +105,22 @@ public: | ||||||
|      * Loads the system mode that this application needs. |      * Loads the system mode that this application needs. | ||||||
|      * This function defaults to 2 (96MB allocated to the application) if it can't read the |      * This function defaults to 2 (96MB allocated to the application) if it can't read the | ||||||
|      * information. |      * 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() { |     virtual std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() { | ||||||
|         // 96MB allocated to the application.
 |         // 96MB allocated to the application.
 | ||||||
|         return std::make_pair(2, ResultStatus::Success); |         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. |      * Get whether this application is executable. | ||||||
|      * @param out_executable Reference to store the executable flag into. |      * @param out_executable Reference to store the executable flag into. | ||||||
|  | @ -186,6 +195,15 @@ public: | ||||||
|         return ResultStatus::ErrorNotImplemented; |         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 |      * Get the update RomFS of the application | ||||||
|      * Since the RomFS can be huge, we return a file reference instead of copying to a buffer |      * 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; |         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 |      * Get the title of the application | ||||||
|      * @param title Reference to store the application title into |      * @param title Reference to store the application title into | ||||||
|  |  | ||||||
|  | @ -61,6 +61,19 @@ std::pair<std::optional<u32>, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode | ||||||
|                           ResultStatus::Success); |                           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) { | ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr<Kernel::Process>& process) { | ||||||
|     using Kernel::CodeSet; |     using Kernel::CodeSet; | ||||||
| 
 | 
 | ||||||
|  | @ -254,6 +267,18 @@ ResultStatus AppLoader_NCCH::ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReade | ||||||
|     return ResultStatus::Success; |     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) { | ResultStatus AppLoader_NCCH::ReadTitle(std::string& title) { | ||||||
|     std::vector<u8> data; |     std::vector<u8> data; | ||||||
|     Loader::SMDH smdh; |     Loader::SMDH smdh; | ||||||
|  |  | ||||||
|  | @ -41,6 +41,8 @@ public: | ||||||
|      */ |      */ | ||||||
|     std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() override; |     std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() override; | ||||||
| 
 | 
 | ||||||
|  |     std::pair<std::optional<u8>, ResultStatus> LoadKernelN3dsMode() override; | ||||||
|  | 
 | ||||||
|     ResultStatus IsExecutable(bool& out_executable) override; |     ResultStatus IsExecutable(bool& out_executable) override; | ||||||
| 
 | 
 | ||||||
|     ResultStatus ReadCode(std::vector<u8>& buffer) override; |     ResultStatus ReadCode(std::vector<u8>& buffer) override; | ||||||
|  | @ -59,6 +61,10 @@ public: | ||||||
| 
 | 
 | ||||||
|     ResultStatus ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override; |     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; |     ResultStatus ReadTitle(std::string& title) override; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  |  | ||||||
|  | @ -46,7 +46,9 @@ void RPCServer::HandleWriteMemory(Packet& packet, u32 address, const u8* data, u | ||||||
|         Core::System::GetInstance().Memory().WriteBlock( |         Core::System::GetInstance().Memory().WriteBlock( | ||||||
|             *Core::System::GetInstance().Kernel().GetCurrentProcess(), address, data, data_size); |             *Core::System::GetInstance().Kernel().GetCurrentProcess(), address, data, data_size); | ||||||
|         // If the memory happens to be executable code, make sure the changes become visible
 |         // 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.SetPacketDataSize(0); | ||||||
|     packet.SendReply(); |     packet.SendReply(); | ||||||
|  |  | ||||||
|  | @ -14,7 +14,6 @@ | ||||||
| #include "input_common/udp/client.h" | #include "input_common/udp/client.h" | ||||||
| #include "input_common/udp/protocol.h" | #include "input_common/udp/protocol.h" | ||||||
| 
 | 
 | ||||||
| using boost::asio::ip::address_v4; |  | ||||||
| using boost::asio::ip::udp; | using boost::asio::ip::udp; | ||||||
| 
 | 
 | ||||||
| namespace InputCommon::CemuhookUDP { | namespace InputCommon::CemuhookUDP { | ||||||
|  | @ -31,10 +30,10 @@ public: | ||||||
| 
 | 
 | ||||||
|     explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id, |     explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id, | ||||||
|                     SocketCallback callback) |                     SocketCallback callback) | ||||||
|         : client_id(client_id), timer(io_service), |         : callback(std::move(callback)), timer(io_service), | ||||||
|           send_endpoint(udp::endpoint(address_v4::from_string(host), port)), |           socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id), | ||||||
|           socket(io_service, udp::endpoint(udp::v4(), 0)), pad_index(pad_index), |           pad_index(pad_index), | ||||||
|           callback(std::move(callback)) {} |           send_endpoint(udp::endpoint(boost::asio::ip::make_address_v4(host), port)) {} | ||||||
| 
 | 
 | ||||||
|     void Stop() { |     void Stop() { | ||||||
|         io_service.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, | Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, | ||||||
|                u8 pad_index, u32 client_id) |                u8 pad_index, u32 client_id) | ||||||
|     : status(status) { |     : status(std::move(status)) { | ||||||
|     StartCommunication(host, port, pad_index, client_id); |     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; |         Common::Event success_event; | ||||||
|         SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, |         SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, | ||||||
|                                 [&](Response::PadData data) { success_event.Set(); }}; |                                 [&](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}; |         std::thread worker_thread{SocketLoop, &socket}; | ||||||
|         bool result = success_event.WaitFor(std::chrono::seconds(8)); |         bool result = success_event.WaitFor(std::chrono::seconds(8)); | ||||||
|         socket.Stop(); |         socket.Stop(); | ||||||
|  | @ -264,7 +263,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob( | ||||||
|                                         complete_event.Set(); |                                         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}; |         std::thread worker_thread{SocketLoop, &socket}; | ||||||
|         complete_event.Wait(); |         complete_event.Wait(); | ||||||
|         socket.Stop(); |         socket.Stop(); | ||||||
|  |  | ||||||
|  | @ -11,7 +11,6 @@ | ||||||
| #include <string> | #include <string> | ||||||
| #include <thread> | #include <thread> | ||||||
| #include <tuple> | #include <tuple> | ||||||
| #include <vector> |  | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/thread.h" | #include "common/thread.h" | ||||||
| #include "common/vector_math.h" | #include "common/vector_math.h" | ||||||
|  |  | ||||||
|  | @ -7,7 +7,6 @@ | ||||||
| #include <array> | #include <array> | ||||||
| #include <optional> | #include <optional> | ||||||
| #include <type_traits> | #include <type_traits> | ||||||
| #include <vector> |  | ||||||
| #include <boost/crc.hpp> | #include <boost/crc.hpp> | ||||||
| #include "common/bit_field.h" | #include "common/bit_field.h" | ||||||
| #include "common/swap.h" | #include "common/swap.h" | ||||||
|  |  | ||||||
|  | @ -2,7 +2,8 @@ | ||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include "common/logging/log.h" | #include <mutex> | ||||||
|  | #include <tuple> | ||||||
| #include "common/param_package.h" | #include "common/param_package.h" | ||||||
| #include "core/frontend/input.h" | #include "core/frontend/input.h" | ||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
|  | @ -14,7 +15,7 @@ namespace InputCommon::CemuhookUDP { | ||||||
| class UDPTouchDevice final : public Input::TouchDevice { | class UDPTouchDevice final : public Input::TouchDevice { | ||||||
| public: | public: | ||||||
|     explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} |     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); |         std::lock_guard guard(status->update_mutex); | ||||||
|         return status->touch_status; |         return status->touch_status; | ||||||
|     } |     } | ||||||
|  | @ -26,7 +27,7 @@ private: | ||||||
| class UDPMotionDevice final : public Input::MotionDevice { | class UDPMotionDevice final : public Input::MotionDevice { | ||||||
| public: | public: | ||||||
|     explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} |     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); |         std::lock_guard guard(status->update_mutex); | ||||||
|         return status->motion_status; |         return status->motion_status; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -2,16 +2,13 @@ | ||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <unordered_map> |  | ||||||
| #include "input_common/main.h" |  | ||||||
| #include "input_common/udp/client.h" | #include "input_common/udp/client.h" | ||||||
| 
 | 
 | ||||||
| namespace InputCommon::CemuhookUDP { | namespace InputCommon::CemuhookUDP { | ||||||
| 
 | 
 | ||||||
| class UDPTouchDevice; |  | ||||||
| class UDPMotionDevice; |  | ||||||
| 
 |  | ||||||
| class State { | class State { | ||||||
| public: | public: | ||||||
|     State(); |     State(); | ||||||
|  |  | ||||||
|  | @ -15,9 +15,9 @@ static std::shared_ptr<Memory::PageTable> page_table = nullptr; | ||||||
| TestEnvironment::TestEnvironment(bool mutable_memory_) | TestEnvironment::TestEnvironment(bool mutable_memory_) | ||||||
|     : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) { |     : 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>(); |     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))); |     kernel->SetCurrentProcess(kernel->CreateProcess(kernel->CreateCodeSet("", 0))); | ||||||
|     page_table = kernel->GetCurrentProcess()->vm_manager.page_table; |     page_table = kernel->GetCurrentProcess()->vm_manager.page_table; | ||||||
|  |  | ||||||
|  | @ -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(0, 0xEE321A03); // vadd.f32 s2, s4, s6
 | ||||||
|     test_env.SetMemory32(4, 0xEAFFFFFE); // b +#0
 |     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{{ |     std::vector<VfpTestCase> test_cases{{ | ||||||
| #include "vfp_vadd_f32.inc" | #include "vfp_vadd_f32.inc" | ||||||
|  |  | ||||||
|  | @ -34,16 +34,16 @@ static void AdvanceAndCheck(Core::Timing& timing, u32 idx, int downcount, int ex | ||||||
|     expected_callback = CB_IDS[idx]; |     expected_callback = CB_IDS[idx]; | ||||||
|     lateness = expected_lateness; |     lateness = expected_lateness; | ||||||
| 
 | 
 | ||||||
|     timing.AddTicks(timing.GetDowncount() - |     timing.GetTimer(0)->AddTicks(timing.GetTimer(0)->GetDowncount() - | ||||||
|                                  cpu_downcount); // Pretend we executed X cycles of instructions.
 |                                  cpu_downcount); // Pretend we executed X cycles of instructions.
 | ||||||
|     timing.Advance(); |     timing.GetTimer(0)->Advance(); | ||||||
| 
 | 
 | ||||||
|     REQUIRE(decltype(callbacks_ran_flags)().set(idx) == callbacks_ran_flags); |     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]") { | TEST_CASE("CoreTiming[BasicOrder]", "[core]") { | ||||||
|     Core::Timing timing; |     Core::Timing timing(1); | ||||||
| 
 | 
 | ||||||
|     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>); |     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>); | ||||||
|     Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>); |     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>); |     Core::TimingEventType* cb_e = timing.RegisterEvent("callbackE", CallbackTemplate<4>); | ||||||
| 
 | 
 | ||||||
|     // Enter slice 0
 |     // Enter slice 0
 | ||||||
|     timing.Advance(); |     timing.GetTimer(0)->Advance(); | ||||||
| 
 | 
 | ||||||
|     // D -> B -> C -> A -> E
 |     // D -> B -> C -> A -> E
 | ||||||
|     timing.ScheduleEvent(1000, cb_a, CB_IDS[0]); |     timing.ScheduleEvent(1000, cb_a, CB_IDS[0], 0); | ||||||
|     REQUIRE(1000 == timing.GetDowncount()); |     REQUIRE(1000 == timing.GetTimer(0)->GetDowncount()); | ||||||
|     timing.ScheduleEvent(500, cb_b, CB_IDS[1]); |     timing.ScheduleEvent(500, cb_b, CB_IDS[1], 0); | ||||||
|     REQUIRE(500 == timing.GetDowncount()); |     REQUIRE(500 == timing.GetTimer(0)->GetDowncount()); | ||||||
|     timing.ScheduleEvent(800, cb_c, CB_IDS[2]); |     timing.ScheduleEvent(800, cb_c, CB_IDS[2], 0); | ||||||
|     REQUIRE(500 == timing.GetDowncount()); |     REQUIRE(500 == timing.GetTimer(0)->GetDowncount()); | ||||||
|     timing.ScheduleEvent(100, cb_d, CB_IDS[3]); |     timing.ScheduleEvent(100, cb_d, CB_IDS[3], 0); | ||||||
|     REQUIRE(100 == timing.GetDowncount()); |     REQUIRE(100 == timing.GetTimer(0)->GetDowncount()); | ||||||
|     timing.ScheduleEvent(1200, cb_e, CB_IDS[4]); |     timing.ScheduleEvent(1200, cb_e, CB_IDS[4], 0); | ||||||
|     REQUIRE(100 == timing.GetDowncount()); |     REQUIRE(100 == timing.GetTimer(0)->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()); |  | ||||||
| 
 | 
 | ||||||
|     AdvanceAndCheck(timing, 3, 400); |     AdvanceAndCheck(timing, 3, 400); | ||||||
|     AdvanceAndCheck(timing, 1, 300); |     AdvanceAndCheck(timing, 1, 300); | ||||||
|  | @ -131,7 +90,7 @@ void FifoCallback(u64 userdata, s64 cycles_late) { | ||||||
| TEST_CASE("CoreTiming[SharedSlot]", "[core]") { | TEST_CASE("CoreTiming[SharedSlot]", "[core]") { | ||||||
|     using namespace SharedSlotTest; |     using namespace SharedSlotTest; | ||||||
| 
 | 
 | ||||||
|     Core::Timing timing; |     Core::Timing timing(1); | ||||||
| 
 | 
 | ||||||
|     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", FifoCallback<0>); |     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", FifoCallback<0>); | ||||||
|     Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", FifoCallback<1>); |     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_d = timing.RegisterEvent("callbackD", FifoCallback<3>); | ||||||
|     Core::TimingEventType* cb_e = timing.RegisterEvent("callbackE", FifoCallback<4>); |     Core::TimingEventType* cb_e = timing.RegisterEvent("callbackE", FifoCallback<4>); | ||||||
| 
 | 
 | ||||||
|     timing.ScheduleEvent(1000, cb_a, CB_IDS[0]); |     timing.ScheduleEvent(1000, cb_a, CB_IDS[0], 0); | ||||||
|     timing.ScheduleEvent(1000, cb_b, CB_IDS[1]); |     timing.ScheduleEvent(1000, cb_b, CB_IDS[1], 0); | ||||||
|     timing.ScheduleEvent(1000, cb_c, CB_IDS[2]); |     timing.ScheduleEvent(1000, cb_c, CB_IDS[2], 0); | ||||||
|     timing.ScheduleEvent(1000, cb_d, CB_IDS[3]); |     timing.ScheduleEvent(1000, cb_d, CB_IDS[3], 0); | ||||||
|     timing.ScheduleEvent(1000, cb_e, CB_IDS[4]); |     timing.ScheduleEvent(1000, cb_e, CB_IDS[4], 0); | ||||||
| 
 | 
 | ||||||
|     // Enter slice 0
 |     // Enter slice 0
 | ||||||
|     timing.Advance(); |     timing.GetTimer(0)->Advance(); | ||||||
|     REQUIRE(1000 == timing.GetDowncount()); |     REQUIRE(1000 == timing.GetTimer(0)->GetDowncount()); | ||||||
| 
 | 
 | ||||||
|     callbacks_ran_flags = 0; |     callbacks_ran_flags = 0; | ||||||
|     counter = 0; |     counter = 0; | ||||||
|     lateness = 0; |     lateness = 0; | ||||||
|     timing.AddTicks(timing.GetDowncount()); |     timing.GetTimer(0)->AddTicks(timing.GetTimer(0)->GetDowncount()); | ||||||
|     timing.Advance(); |     timing.GetTimer(0)->Advance(); | ||||||
|     REQUIRE(MAX_SLICE_LENGTH == timing.GetDowncount()); |     REQUIRE(MAX_SLICE_LENGTH == timing.GetTimer(0)->GetDowncount()); | ||||||
|     REQUIRE(0x1FULL == callbacks_ran_flags.to_ullong()); |     REQUIRE(0x1FULL == callbacks_ran_flags.to_ullong()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| TEST_CASE("CoreTiming[PredictableLateness]", "[core]") { | TEST_CASE("CoreTiming[PredictableLateness]", "[core]") { | ||||||
|     Core::Timing timing; |     Core::Timing timing(1); | ||||||
| 
 | 
 | ||||||
|     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>); |     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>); | ||||||
|     Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>); |     Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>); | ||||||
| 
 | 
 | ||||||
|     // Enter slice 0
 |     // Enter slice 0
 | ||||||
|     timing.Advance(); |     timing.GetTimer(0)->Advance(); | ||||||
| 
 | 
 | ||||||
|     timing.ScheduleEvent(100, cb_a, CB_IDS[0]); |     timing.ScheduleEvent(100, cb_a, CB_IDS[0], 0); | ||||||
|     timing.ScheduleEvent(200, cb_b, CB_IDS[1]); |     timing.ScheduleEvent(200, cb_b, CB_IDS[1], 0); | ||||||
| 
 | 
 | ||||||
|     AdvanceAndCheck(timing, 0, 90, 10, -10); // (100 - 10)
 |     AdvanceAndCheck(timing, 0, 90, 10, -10); // (100 - 10)
 | ||||||
|     AdvanceAndCheck(timing, 1, MAX_SLICE_LENGTH, 50, -50); |     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]") { | TEST_CASE("CoreTiming[ChainScheduling]", "[core]") { | ||||||
|     using namespace ChainSchedulingTest; |     using namespace ChainSchedulingTest; | ||||||
| 
 | 
 | ||||||
|     Core::Timing timing; |     Core::Timing timing(1); | ||||||
| 
 | 
 | ||||||
|     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>); |     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>); | ||||||
|     Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>); |     Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>); | ||||||
|  | @ -201,28 +160,30 @@ TEST_CASE("CoreTiming[ChainScheduling]", "[core]") { | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|     // Enter slice 0
 |     // Enter slice 0
 | ||||||
|     timing.Advance(); |     timing.GetTimer(0)->Advance(); | ||||||
| 
 | 
 | ||||||
|     timing.ScheduleEvent(800, cb_a, CB_IDS[0]); |     timing.ScheduleEvent(800, cb_a, CB_IDS[0], 0); | ||||||
|     timing.ScheduleEvent(1000, cb_b, CB_IDS[1]); |     timing.ScheduleEvent(1000, cb_b, CB_IDS[1], 0); | ||||||
|     timing.ScheduleEvent(2200, cb_c, CB_IDS[2]); |     timing.ScheduleEvent(2200, cb_c, CB_IDS[2], 0); | ||||||
|     timing.ScheduleEvent(1000, cb_rs, reinterpret_cast<u64>(cb_rs)); |     timing.ScheduleEvent(1000, cb_rs, reinterpret_cast<u64>(cb_rs), 0); | ||||||
|     REQUIRE(800 == timing.GetDowncount()); |     REQUIRE(800 == timing.GetTimer(0)->GetDowncount()); | ||||||
| 
 | 
 | ||||||
|     reschedules = 3; |     reschedules = 3; | ||||||
|     AdvanceAndCheck(timing, 0, 200);  // cb_a
 |     AdvanceAndCheck(timing, 0, 200);  // cb_a
 | ||||||
|     AdvanceAndCheck(timing, 1, 1000); // cb_b, cb_rs
 |     AdvanceAndCheck(timing, 1, 1000); // cb_b, cb_rs
 | ||||||
|     REQUIRE(2 == reschedules); |     REQUIRE(2 == reschedules); | ||||||
| 
 | 
 | ||||||
|     timing.AddTicks(timing.GetDowncount()); |     timing.GetTimer(0)->AddTicks(timing.GetTimer(0)->GetDowncount()); | ||||||
|     timing.Advance(); // cb_rs
 |     timing.GetTimer(0)->Advance(); // cb_rs
 | ||||||
|     REQUIRE(1 == reschedules); |     REQUIRE(1 == reschedules); | ||||||
|     REQUIRE(200 == timing.GetDowncount()); |     REQUIRE(200 == timing.GetTimer(0)->GetDowncount()); | ||||||
| 
 | 
 | ||||||
|     AdvanceAndCheck(timing, 2, 800); // cb_c
 |     AdvanceAndCheck(timing, 2, 800); // cb_c
 | ||||||
| 
 | 
 | ||||||
|     timing.AddTicks(timing.GetDowncount()); |     timing.GetTimer(0)->AddTicks(timing.GetTimer(0)->GetDowncount()); | ||||||
|     timing.Advance(); // cb_rs
 |     timing.GetTimer(0)->Advance(); // cb_rs
 | ||||||
|     REQUIRE(0 == reschedules); |     REQUIRE(0 == reschedules); | ||||||
|     REQUIRE(MAX_SLICE_LENGTH == timing.GetDowncount()); |     REQUIRE(MAX_SLICE_LENGTH == timing.GetTimer(0)->GetDowncount()); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // TODO: Add tests for multiple timers
 | ||||||
|  |  | ||||||
|  | @ -24,9 +24,9 @@ static std::shared_ptr<Object> MakeObject(Kernel::KernelSystem& kernel) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") { | TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") { | ||||||
|     Core::Timing timing; |     Core::Timing timing(1); | ||||||
|     Memory::MemorySystem memory; |     Memory::MemorySystem memory; | ||||||
|     Kernel::KernelSystem kernel(memory, timing, [] {}, 0); |     Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1, 0); | ||||||
|     auto [server, client] = kernel.CreateSessionPair(); |     auto [server, client] = kernel.CreateSessionPair(); | ||||||
|     HLERequestContext context(kernel, std::move(server), nullptr); |     HLERequestContext context(kernel, std::move(server), nullptr); | ||||||
| 
 | 
 | ||||||
|  | @ -239,9 +239,9 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") { | TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") { | ||||||
|     Core::Timing timing; |     Core::Timing timing(1); | ||||||
|     Memory::MemorySystem memory; |     Memory::MemorySystem memory; | ||||||
|     Kernel::KernelSystem kernel(memory, timing, [] {}, 0); |     Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1, 0); | ||||||
|     auto [server, client] = kernel.CreateSessionPair(); |     auto [server, client] = kernel.CreateSessionPair(); | ||||||
|     HLERequestContext context(kernel, std::move(server), nullptr); |     HLERequestContext context(kernel, std::move(server), nullptr); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,9 +11,9 @@ | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
| 
 | 
 | ||||||
| TEST_CASE("Memory::IsValidVirtualAddress", "[core][memory]") { | TEST_CASE("Memory::IsValidVirtualAddress", "[core][memory]") { | ||||||
|     Core::Timing timing; |     Core::Timing timing(1); | ||||||
|     Memory::MemorySystem memory; |     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") { |     SECTION("these regions should not be mapped on an empty process") { | ||||||
|         auto process = kernel.CreateProcess(kernel.CreateCodeSet("", 0)); |         auto process = kernel.CreateProcess(kernel.CreateCodeSet("", 0)); | ||||||
|         CHECK(Memory::IsValidVirtualAddress(*process, Memory::PROCESS_IMAGE_VADDR) == false); |         CHECK(Memory::IsValidVirtualAddress(*process, Memory::PROCESS_IMAGE_VADDR) == false); | ||||||
|  |  | ||||||
|  | @ -1926,7 +1926,7 @@ void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RasterizerCacheOpenGL::ClearAll(bool flush) { | 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
 |     // Force flush all surfaces from the cache
 | ||||||
|     if (flush) { |     if (flush) { | ||||||
|         FlushRegion(0x0, 0xFFFFFFFF); |         FlushRegion(0x0, 0xFFFFFFFF); | ||||||
|  | @ -1945,8 +1945,8 @@ void RasterizerCacheOpenGL::ClearAll(bool flush) { | ||||||
| 
 | 
 | ||||||
|     // Remove the whole cache without really looking at it.
 |     // Remove the whole cache without really looking at it.
 | ||||||
|     cached_pages -= flush_interval; |     cached_pages -= flush_interval; | ||||||
|     dirty_regions -= flush_interval; |     dirty_regions -= SurfaceInterval(0x0, 0xFFFFFFFF); | ||||||
|     surface_cache -= flush_interval; |     surface_cache -= SurfaceInterval(0x0, 0xFFFFFFFF); | ||||||
|     remove_surfaces.clear(); |     remove_surfaces.clear(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -80,11 +80,15 @@ struct CachedSurface; | ||||||
| using Surface = std::shared_ptr<CachedSurface>; | using Surface = std::shared_ptr<CachedSurface>; | ||||||
| using SurfaceSet = std::set<Surface>; | using SurfaceSet = std::set<Surface>; | ||||||
| 
 | 
 | ||||||
| using SurfaceRegions = boost::icl::interval_set<PAddr>; | using SurfaceInterval = boost::icl::right_open_interval<PAddr>; | ||||||
| using SurfaceMap = boost::icl::interval_map<PAddr, Surface>; | using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>; | ||||||
| using SurfaceCache = boost::icl::interval_map<PAddr, SurfaceSet>; | 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>() && | static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() && | ||||||
|                   std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(), |                   std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(), | ||||||
|               "incorrect interval types"); |               "incorrect interval types"); | ||||||
|  | @ -101,6 +105,29 @@ enum class ScaleMatch { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| struct SurfaceParams { | 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 { |     enum class PixelFormat { | ||||||
|         // First 5 formats are shared between textures and color buffers
 |         // First 5 formats are shared between textures and color buffers
 | ||||||
|         RGBA8 = 0, |         RGBA8 = 0, | ||||||
|  | @ -139,30 +166,11 @@ struct SurfaceParams { | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     static constexpr unsigned int GetFormatBpp(PixelFormat format) { |     static constexpr unsigned int GetFormatBpp(PixelFormat format) { | ||||||
|         constexpr std::array<unsigned int, 18> bpp_table = { |         const auto format_idx = static_cast<std::size_t>(format); | ||||||
|             32, // RGBA8
 |         DEBUG_ASSERT_MSG(format_idx < BPP_TABLE.size(), "Invalid pixel format {}", format_idx); | ||||||
|             24, // RGB8
 |         return BPP_TABLE[format_idx]; | ||||||
|             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)]; |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     unsigned int GetFormatBpp() const { |     unsigned int GetFormatBpp() const { | ||||||
|         return GetFormatBpp(pixel_format); |         return GetFormatBpp(pixel_format); | ||||||
|     } |     } | ||||||
|  | @ -245,7 +253,7 @@ struct SurfaceParams { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     SurfaceInterval GetInterval() const { |     SurfaceInterval GetInterval() const { | ||||||
|         return SurfaceInterval::right_open(addr, end); |         return SurfaceInterval(addr, end); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Returns the outer rectangle containing "interval"
 |     // Returns the outer rectangle containing "interval"
 | ||||||
|  |  | ||||||
|  | @ -18,3 +18,6 @@ get_directory_property(OPENSSL_LIBS | ||||||
|         DEFINITION OPENSSL_LIBS) |         DEFINITION OPENSSL_LIBS) | ||||||
| target_compile_definitions(web_service PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT) | 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) | 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() | ||||||
|  |  | ||||||
|  | @ -8,6 +8,9 @@ | ||||||
| #include <string> | #include <string> | ||||||
| #include <LUrlParser.h> | #include <LUrlParser.h> | ||||||
| #include <fmt/format.h> | #include <fmt/format.h> | ||||||
|  | #if defined(__ANDROID__) | ||||||
|  | #include <ifaddrs.h> | ||||||
|  | #endif | ||||||
| #include <httplib.h> | #include <httplib.h> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
|  | @ -73,14 +76,14 @@ struct Client::Impl { | ||||||
|                 if (!parsedUrl.GetPort(&port)) { |                 if (!parsedUrl.GetPort(&port)) { | ||||||
|                     port = HTTP_PORT; |                     port = HTTP_PORT; | ||||||
|                 } |                 } | ||||||
|                 cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, |                 cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port); | ||||||
|                                                         TIMEOUT_SECONDS); |                 cli->set_timeout_sec(TIMEOUT_SECONDS); | ||||||
|             } else if (parsedUrl.m_Scheme == "https") { |             } else if (parsedUrl.m_Scheme == "https") { | ||||||
|                 if (!parsedUrl.GetPort(&port)) { |                 if (!parsedUrl.GetPort(&port)) { | ||||||
|                     port = HTTPS_PORT; |                     port = HTTPS_PORT; | ||||||
|                 } |                 } | ||||||
|                 cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port, |                 cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port); | ||||||
|                                                            TIMEOUT_SECONDS); |                 cli->set_timeout_sec(TIMEOUT_SECONDS); | ||||||
|             } else { |             } else { | ||||||
|                 LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); |                 LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); | ||||||
|                 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"}; |                 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"}; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue