Merge branch 'master' into feature/savestates-2
commit
da3ab3d56e
@ -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})
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
@ -1 +1 @@
|
||||
Subproject commit 6d7edc593be8e47c8de7bc5f7d6b32971fad0c24
|
||||
Subproject commit 727f616b6e5cafaba072131c077a3b8fea87b8be
|
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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
|
@ -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
|
@ -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
|
Loading…
Reference in New Issue