VitaShell/archive.c
cuevavirus 3d7ebe61e6
Only use Ehdr and Phdr from SELF header (#581)
segment_info in the SELF header contains offsets to segments. It is a
coincidence of Vitasdk that the first segment also contains the ELF
header and program headers. The second ELF header and program headers
may precede the first segment or may not be present at all.
2020-09-25 19:50:22 +02:00

965 lines
24 KiB
C

/*
VitaShell
Copyright (C) 2015-2018, TheFloW
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "main.h"
#include "browser.h"
#include "archive.h"
#include "psarc.h"
#include "file.h"
#include "utils.h"
#include "elf.h"
static int is_psarc = 0;
static char archive_file[MAX_PATH_LENGTH];
static int archive_path_start = 0;
struct archive *archive_fd = NULL;
static int need_password = 0;
static char password[128];
int checkForUnsafeImports(const Elf32_Ehdr *ehdr, const Elf32_Phdr *phdr, void *buffer);
char *uncompressBuffer(const Elf32_Ehdr *ehdr, const Elf32_Phdr *phdr, const segment_info *segment,
const char *buffer);
void waitpid() {}
void __archive_create_child() {}
void __archive_check_child() {}
struct archive_data {
char *filename;
SceUID fd;
void *buffer;
int block_size;
};
static const char *file_passphrase(struct archive *a, void *client_data) {
return password;
}
static int file_open(struct archive *a, void *client_data) {
struct archive_data *archive_data = client_data;
archive_data->fd = sceIoOpen(archive_data->filename, SCE_O_RDONLY, 0);
if (archive_data->fd < 0)
return ARCHIVE_FATAL;
archive_data->buffer = memalign(4096, TRANSFER_SIZE);
archive_data->block_size = TRANSFER_SIZE;
return ARCHIVE_OK;
}
static ssize_t file_read(struct archive *a, void *client_data, const void **buff) {
struct archive_data *archive_data = client_data;
*buff = archive_data->buffer;
return sceIoRead(archive_data->fd, archive_data->buffer, archive_data->block_size);
}
static int64_t file_skip(struct archive *a, void *client_data, int64_t request) {
struct archive_data *archive_data = client_data;
int64_t old_offset, new_offset;
if ((old_offset = sceIoLseek(archive_data->fd, 0, SCE_SEEK_CUR)) >= 0 &&
(new_offset = sceIoLseek(archive_data->fd, request, SCE_SEEK_CUR)) >= 0)
return new_offset - old_offset;
return -1;
}
static int64_t file_seek(struct archive *a, void *client_data, int64_t request, int whence) {
struct archive_data *archive_data = client_data;
int64_t r;
r = sceIoLseek(archive_data->fd, request, whence);
if (r >= 0)
return r;
return ARCHIVE_FATAL;
}
static int file_close2(struct archive *a, void *client_data) {
struct archive_data *archive_data = client_data;
if (archive_data->fd >= 0) {
sceIoClose(archive_data->fd);
archive_data->fd = -1;
}
free(archive_data->buffer);
archive_data->buffer = NULL;
return ARCHIVE_OK;
}
static int file_close(struct archive *a, void *client_data) {
struct archive_data *archive_data = client_data;
file_close2(a, client_data);
free(archive_data->filename);
free(archive_data);
return ARCHIVE_OK;
}
static int file_switch(struct archive *a, void *client_data1, void *client_data2) {
file_close2(a, client_data1);
return file_open(a, client_data2);
}
int append_archive(struct archive *a, const char *filename) {
struct archive_data *archive_data = malloc(sizeof(struct archive_data));
if (archive_data) {
archive_data->filename = malloc(strlen(filename) + 1);
strcpy(archive_data->filename, filename);
if (archive_read_append_callback_data(a, archive_data) != ARCHIVE_OK) {
free(archive_data->filename);
free(archive_data);
return ARCHIVE_FATAL;
}
}
return ARCHIVE_OK;
}
struct archive *open_archive(const char *filename) {
struct archive *a = archive_read_new();
if (!a)
return NULL;
archive_read_support_filter_all(a);
archive_read_support_format_all(a);
archive_read_set_passphrase_callback(a, NULL, file_passphrase);
archive_read_set_open_callback(a, file_open);
archive_read_set_read_callback(a, file_read);
archive_read_set_skip_callback(a, file_skip);
archive_read_set_close_callback(a, file_close);
archive_read_set_switch_callback(a, file_switch);
archive_read_set_seek_callback(a, file_seek);
// Get path and name of filename
int type = 0;
char *p = strrchr(filename, '/');
if (!p)
p = strrchr(filename, ':');
if (p) {
char *q = strrchr(p + 1, '.');
if (q) {
char path[MAX_PATH_LENGTH];
char name[MAX_NAME_LENGTH];
char new_path[MAX_PATH_LENGTH];
char format[24];
int part_format = 0;
strncpy(path, filename, p - filename + 1);
path[p - filename + 1] = '\0';
strncpy(name, p + 1, q - (p + 1));
name[q - (p + 1)] = '\0';
// Check for multi volume rar
if (strcasecmp(q, ".rar") == 0) {
// Check for .partXXXX.rar archives
q = strrchr(name, '.');
if (q) {
if (strncasecmp(q + 1, "part", 4) == 0) {
name[q - name] = '\0';
for (part_format = 0; part_format < 4; part_format++) {
strcpy(format, "%s%s.part%0Xd.rar");
format[11] = '1' + part_format;
snprintf(new_path, MAX_PATH_LENGTH, format, path, name, 1);
if (checkFileExist(new_path)) {
type = 1;
break;
}
}
}
}
// Check for .rXX archives
if (type == 0) {
snprintf(new_path, MAX_PATH_LENGTH, "%s%s.r00", path, name);
if (checkFileExist(new_path)) {
strcpy(format, "%s%s.r%02d");
type = 2;
}
}
if (type != 0) {
int num = 1;
if (type == 2) {
num = 0; // this type begins with .r00
// Append .rar as first archive
if (append_archive(a, filename) != ARCHIVE_OK) {
archive_read_free(a);
return NULL;
}
}
// Append other parts
while (1) {
snprintf(new_path, MAX_PATH_LENGTH, format, path, name, num);
if (!checkFileExist(new_path))
break;
if (append_archive(a, new_path) != ARCHIVE_OK) {
archive_read_free(a);
return NULL;
}
num++;
}
}
}
}
}
// Single volume
if (type == 0) {
if (append_archive(a, filename) != ARCHIVE_OK) {
archive_read_free(a);
return NULL;
}
}
// Open archive
if (archive_read_open1(a)) {
archive_read_free(a);
return NULL;
}
return a;
}
typedef struct ArchiveFileNode {
struct ArchiveFileNode *child;
struct ArchiveFileNode *next;
char *name;
SceIoStat stat;
} ArchiveFileNode;
static ArchiveFileNode *archive_root = NULL;
char *serializePathName(char *name, char **p) {
if (!p)
return name;
if (*p) {
**p = '/'; // restore
name = *p + 1;
}
*p = strchr(name, '/');
if (*p)
**p = '\0';
return name;
}
ArchiveFileNode *createArchiveNode(const char *name, SceIoStat *stat) {
ArchiveFileNode *node = malloc(sizeof(ArchiveFileNode));
if (!node)
return NULL;
memset(node, 0, sizeof(ArchiveFileNode));
node->name = malloc(strlen(name) + 1);
if (!node->name) {
free(node);
return NULL;
}
strcpy(node->name, name);
node->child = NULL;
node->next = NULL;
memcpy(&node->stat, stat, sizeof(SceIoStat));
return node;
}
ArchiveFileNode *_findArchiveNode(ArchiveFileNode *parent, char *name, ArchiveFileNode **parent_out,
ArchiveFileNode **prev_out, char **name_out, char **p_out) {
char *p = NULL;
ArchiveFileNode *prev = NULL;
// Remove trailing slash
removeEndSlash(name);
// Serialize path name
name = serializePathName(name, &p);
// Root
if (name[0] == '\0')
return parent;
// Traverse
ArchiveFileNode *curr = parent->child;
while (curr) {
// Found name
if (strcasecmp(curr->name, name) == 0) {
// Found node
if (!p)
return curr;
// Is folder
if (SCE_S_ISDIR(curr->stat.st_mode)) {
// Serialize path name
name = serializePathName(name, &p);
// Get child entry of this directory
parent = curr;
curr = curr->child;
continue;
}
}
// Get next entry in this directory
prev = curr;
curr = curr->next;
}
// Out
if (parent_out)
*parent_out = parent;
if (prev_out)
*prev_out = prev;
if (name_out)
*name_out = name;
if (p_out)
*p_out = p;
return NULL;
}
ArchiveFileNode *findArchiveNode(const char *path) {
char name[MAX_PATH_LENGTH];
strcpy(name, path);
return _findArchiveNode(archive_root, name, NULL, NULL, NULL, NULL);
}
int addArchiveNodeRecursive(ArchiveFileNode *parent, char *name, SceIoStat *stat) {
char *p = NULL;
ArchiveFileNode *prev = NULL;
if (!parent)
return VITASHELL_ERROR_ILLEGAL_ADDR;
ArchiveFileNode *res = _findArchiveNode(parent, name, &parent, &prev, &name, &p);
// Already exist
if (res) {
memcpy(&res->stat, stat, sizeof(SceIoStat));
return 0;
}
// Create new node
SceIoStat node_stat;
memcpy(&node_stat, stat, sizeof(SceIoStat));
node_stat.st_mode = SCE_S_IFDIR;
node_stat.st_size = 0;
ArchiveFileNode *node = createArchiveNode(name, p ? &node_stat : stat);
if (!parent->child) { // First child
parent->child = node;
} else { // Neighbour
prev->next = node;
}
// Recursion
if (p)
addArchiveNodeRecursive(node, p + 1, stat);
return 0;
}
void addArchiveNode(const char *path, SceIoStat *stat) {
char name[MAX_PATH_LENGTH];
strcpy(name, path);
addArchiveNodeRecursive(archive_root, name, stat);
}
void freeArchiveNodes(ArchiveFileNode *curr) {
// Traverse
while (curr) {
// Enter directory
if (SCE_S_ISDIR(curr->stat.st_mode)) {
// Traverse child entry of this directory
freeArchiveNodes(curr->child);
}
// Get next entry in this directory
ArchiveFileNode *next = curr->next;
free(curr->name);
free(curr);
curr = next;
}
}
int archiveCheckFilesForUnsafeFself() {
// Open archive file
struct archive *archive = open_archive(archive_file);
if (!archive)
return 0;
// Traverse
while (1) {
struct archive_entry *archive_entry;
int res = archive_read_next_header(archive, &archive_entry);
if (res == ARCHIVE_EOF)
break;
if (res != ARCHIVE_OK) {
archive_read_free(archive);
return 0;
}
// Get entry information
const char *name = archive_entry_pathname(archive_entry);
const struct stat *stat = archive_entry_stat(archive_entry);
// Read magic
uint32_t magic = 0;
archive_read_data(archive, &magic, sizeof(uint32_t));
// SCE magic
if (magic == 0x00454353) {
char sce_header[0x84];
archive_read_data(archive, sce_header, sizeof(sce_header));
uint64_t elf1_offset = *(uint64_t *)(sce_header + 0x3C);
uint64_t phdr_offset = *(uint64_t *)(sce_header + 0x44);
uint64_t section_info_offset = *(uint64_t *)(sce_header + 0x54);
// jump to ehdr
// Until here we have read 0x88 bytes
int i;
for (i = 0; i < elf1_offset - 0x88; i += sizeof(uint32_t)) {
uint32_t dummy = 0;
archive_read_data(archive, &dummy, sizeof(uint32_t));
}
// Check imports
char *buffer = malloc(stat->st_size);
if (buffer) {
archive_read_data(archive, buffer, stat->st_size);
Elf32_Ehdr *ehdr = (Elf32_Ehdr*)buffer;
Elf32_Phdr *phdr = (Elf32_Phdr*)(buffer + phdr_offset - elf1_offset);
segment_info *info = (segment_info*)(buffer + section_info_offset - elf1_offset);
// segment is a pointer to the first segment
char *segment = buffer + info->offset - elf1_offset;
// zlib compress magic
char *uncompressed_buffer = NULL;
if (segment[0] == 0x78) {
// uncompressedBuffer will return uncompressed segments
uncompressed_buffer = uncompressBuffer(ehdr, phdr, info, segment);
if (uncompressed_buffer) {
segment = uncompressed_buffer;
}
}
int unsafe = checkForUnsafeImports(ehdr, phdr, segment);
if (uncompressed_buffer)
free(uncompressed_buffer);
free(buffer);
if (unsafe) {
archive_read_free(archive);
return unsafe;
}
}
// Check authid flag
uint64_t authid = *(uint64_t *)(sce_header + 0x7C);
if (authid != 0x2F00000000000002) {
archive_read_free(archive);
return 1; // Unsafe
}
}
}
archive_read_free(archive);
return 0;
}
int fileListGetArchiveEntries(FileList *list, const char *path, int sort) {
if (is_psarc)
return fileListGetPsarcEntries(list, path, sort);
int res;
if (!list)
return VITASHELL_ERROR_ILLEGAL_ADDR;
FileListEntry *entry = malloc(sizeof(FileListEntry));
if (entry) {
entry->name_length = strlen(DIR_UP);
entry->name = malloc(entry->name_length + 1);
strcpy(entry->name, DIR_UP);
entry->is_folder = 1;
entry->is_symlink = 0;
entry->type = FILE_TYPE_UNKNOWN;
fileListAddEntry(list, entry, sort);
}
// Traverse
ArchiveFileNode *curr = findArchiveNode(path + archive_path_start);
if (curr)
curr = curr->child;
while (curr) {
FileListEntry *entry = malloc(sizeof(FileListEntry));
if (entry) {
entry->is_symlink = 0;
entry->is_folder = SCE_S_ISDIR(curr->stat.st_mode);
if (entry->is_folder) {
entry->name_length = strlen(curr->name) + 1;
entry->name = malloc(entry->name_length + 1);
strcpy(entry->name, curr->name);
addEndSlash(entry->name);
entry->type = FILE_TYPE_UNKNOWN;
list->folders++;
} else {
entry->name_length = strlen(curr->name);
entry->name = malloc(entry->name_length + 1);
strcpy(entry->name, curr->name);
entry->type = getFileType(entry->name);
list->files++;
}
entry->size = curr->stat.st_size;
memcpy(&entry->ctime, (SceDateTime *)&curr->stat.st_ctime, sizeof(SceDateTime));
memcpy(&entry->mtime, (SceDateTime *)&curr->stat.st_mtime, sizeof(SceDateTime));
memcpy(&entry->atime, (SceDateTime *)&curr->stat.st_atime, sizeof(SceDateTime));
fileListAddEntry(list, entry, sort);
}
// Get next entry in this directory
curr = curr->next;
}
return 0;
}
int getArchivePathInfo(const char *path, uint64_t *size, uint32_t *folders,
uint32_t *files, int (* handler)(const char *path)) {
if (is_psarc)
return getPsarcPathInfo(path, size, folders, files, handler);
SceIoStat stat;
memset(&stat, 0, sizeof(SceIoStat));
int res = archiveFileGetstat(path, &stat);
if (res < 0)
return res;
if (SCE_S_ISDIR(stat.st_mode)) {
FileList list;
memset(&list, 0, sizeof(FileList));
fileListGetArchiveEntries(&list, path, SORT_NONE);
FileListEntry *entry = list.head->next; // Ignore ..
int i;
for (i = 0; i < list.length - 1; i++, entry = entry->next) {
char *new_path = malloc(strlen(path) + strlen(entry->name) + 2);
snprintf(new_path, MAX_PATH_LENGTH, "%s%s", path, entry->name);
if (handler && handler(new_path)) {
free(new_path);
continue;
}
if (entry->is_folder) {
int ret = getArchivePathInfo(new_path, size, folders, files, handler);
if (ret <= 0) {
free(new_path);
fileListEmpty(&list);
return ret;
}
} else {
if (size)
(*size) += entry->size;
if (files)
(*files)++;
}
free(new_path);
}
if (folders)
(*folders)++;
fileListEmpty(&list);
} else {
if (handler && handler(path))
return 1;
if (size)
(*size) += stat.st_size;
if (files)
(*files)++;
}
return 1;
}
int extractArchiveFile(const char *src_path, const char *dst_path, FileProcessParam *param) {
SceUID fdsrc = archiveFileOpen(src_path, SCE_O_RDONLY, 0);
if (fdsrc < 0)
return fdsrc;
SceUID fddst = sceIoOpen(dst_path, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777);
if (fddst < 0) {
psarcFileClose(fdsrc);
return fddst;
}
void *buf = memalign(4096, TRANSFER_SIZE);
while (1) {
int read = archiveFileRead(fdsrc, buf, TRANSFER_SIZE);
if (read < 0) {
free(buf);
sceIoClose(fddst);
archiveFileClose(fdsrc);
sceIoRemove(dst_path);
return read;
}
if (read == 0)
break;
int written = sceIoWrite(fddst, buf, read);
if (written < 0) {
free(buf);
sceIoClose(fddst);
archiveFileClose(fdsrc);
sceIoRemove(dst_path);
return written;
}
if (param) {
if (param->value)
(*param->value) += read;
if (param->SetProgress)
param->SetProgress(param->value ? *param->value : 0, param->max);
if (param->cancelHandler && param->cancelHandler()) {
free(buf);
sceIoClose(fddst);
archiveFileClose(fdsrc);
sceIoRemove(dst_path);
return 0;
}
}
}
free(buf);
sceIoClose(fddst);
archiveFileClose(fdsrc);
return 1;
}
int extractArchivePath(const char *src_path, const char *dst_path, FileProcessParam *param) {
if (is_psarc)
return extractPsarcPath(src_path, dst_path, param);
SceIoStat stat;
memset(&stat, 0, sizeof(SceIoStat));
int res = archiveFileGetstat(src_path, &stat);
if (res < 0)
return res;
if (SCE_S_ISDIR(stat.st_mode)) {
FileList list;
memset(&list, 0, sizeof(FileList));
fileListGetArchiveEntries(&list, src_path, SORT_NONE);
int ret = sceIoMkdir(dst_path, 0777);
if (ret < 0 && ret != SCE_ERROR_ERRNO_EEXIST) {
fileListEmpty(&list);
return ret;
}
if (param) {
if (param->value)
(*param->value) += DIRECTORY_SIZE;
if (param->SetProgress)
param->SetProgress(param->value ? *param->value : 0, param->max);
if (param->cancelHandler && param->cancelHandler()) {
fileListEmpty(&list);
return 0;
}
}
FileListEntry *entry = list.head->next; // Ignore ..
int i;
for (i = 0; i < list.length - 1; i++, entry = entry->next) {
char *new_src_path = malloc(strlen(src_path) + strlen(entry->name) + 2);
snprintf(new_src_path, MAX_PATH_LENGTH, "%s%s", src_path, entry->name);
char *new_dst_path = malloc(strlen(dst_path) + strlen(entry->name) + 2);
snprintf(new_dst_path, MAX_PATH_LENGTH, "%s%s", dst_path, entry->name);
int ret = 0;
if (entry->is_folder) {
ret = extractArchivePath(new_src_path, new_dst_path, param);
} else {
ret = extractArchiveFile(new_src_path, new_dst_path, param);
}
free(new_dst_path);
free(new_src_path);
if (ret <= 0) {
fileListEmpty(&list);
return ret;
}
}
fileListEmpty(&list);
} else {
return extractArchiveFile(src_path, dst_path, param);
}
return 1;
}
int archiveFileGetstat(const char *file, SceIoStat *stat) {
if (is_psarc)
return psarcFileGetstat(file, stat);
ArchiveFileNode *node = findArchiveNode(file + archive_path_start);
if (!node)
return VITASHELL_ERROR_ILLEGAL_ADDR;
if (stat)
memcpy(stat, &node->stat, sizeof(SceIoStat));
return 0;
}
int archiveFileOpen(const char *file, int flags, SceMode mode) {
if (is_psarc)
return psarcFileOpen(file, flags, mode);
// A file is already open
if (archive_fd)
return -1;
// Open archive file
archive_fd = open_archive(archive_file);
if (!archive_fd)
return -1;
// Traverse
while (1) {
struct archive_entry *archive_entry;
int res = archive_read_next_header(archive_fd, &archive_entry);
if (res == ARCHIVE_EOF)
break;
if (res != ARCHIVE_OK) {
archive_read_free(archive_fd);
return -1;
}
// Compare pathname
const char *name = archive_entry_pathname(archive_entry);
if (strcasecmp(name, file + archive_path_start) == 0) {
return ARCHIVE_FD;
}
}
archive_read_free(archive_fd);
archive_fd = NULL;
return -1;
}
int archiveFileRead(SceUID fd, void *data, SceSize size) {
if (is_psarc)
return psarcFileRead(fd, data, size);
if (!archive_fd || fd != ARCHIVE_FD)
return -1;
return archive_read_data(archive_fd, data, size);
}
int archiveFileClose(SceUID fd) {
if (is_psarc)
return psarcFileClose(fd);
if (!archive_fd || fd != ARCHIVE_FD)
return -1;
archive_read_free(archive_fd);
archive_fd = NULL;
return 0;
}
int ReadArchiveFile(const char *file, void *buf, int size) {
SceUID fd = archiveFileOpen(file, SCE_O_RDONLY, 0);
if (fd < 0)
return fd;
int read = archiveFileRead(fd, buf, size);
archiveFileClose(fd);
return read;
}
int archiveNeedPassword() {
return need_password;
}
void archiveClearPassword() {
memset(password, 0, sizeof(password));
}
void archiveSetPassword(char *string) {
strncpy(password, string, sizeof(password) - 1);
password[sizeof(password) - 1] = '\0';
}
int archiveClose() {
if (is_psarc)
return psarcClose();
freeArchiveNodes(archive_root);
return 0;
}
SceMode convert_stat_mode(mode_t mode) {
SceMode sce_mode = 0;
if (mode & S_IFDIR)
sce_mode |= SCE_S_IFDIR;
if (mode & S_IFREG)
sce_mode |= SCE_S_IFREG;
return sce_mode;
}
int archiveOpen(const char *file) {
// Read magic
uint32_t magic;
int read = ReadFile(file, &magic, sizeof(uint32_t));
if (read < 0)
return read;
// PSARC file
is_psarc = 0;
if (magic == 0x52415350) {
is_psarc = 1;
return psarcOpen(file);
}
// Start position of the archive path
archive_path_start = strlen(file) + 1;
strcpy(archive_file, file);
// Open archive file
struct archive *archive = open_archive(file);
if (!archive)
return -1;
// Need password?
need_password = 0;
if (archive_read_has_encrypted_entries(archive) == 1)
need_password = 1;
// Create archive root
SceIoStat root_stat;
memset(&root_stat, 0, sizeof(SceIoStat));
root_stat.st_mode = SCE_S_IFDIR;
archive_root = createArchiveNode("/", &root_stat);
// Traverse
while (1) {
struct archive_entry *archive_entry;
int res = archive_read_next_header(archive, &archive_entry);
if (res == ARCHIVE_EOF)
break;
if (res != ARCHIVE_OK) {
archive_read_free(archive);
return -1;
}
// // Need password?
if (archive_entry_is_encrypted(archive_entry))
need_password = 1;
// Get entry information
const char *name = archive_entry_pathname(archive_entry);
// File stat
SceIoStat stat;
memset(&stat, 0, sizeof(SceIoStat));
stat.st_mode = convert_stat_mode(archive_entry_mode(archive_entry));
stat.st_size = archive_entry_size(archive_entry);
SceDateTime time;
sceRtcSetTime_t(&time, archive_entry_ctime(archive_entry));
convertLocalTimeToUtc(&stat.st_ctime, &time);
sceRtcSetTime_t(&time, archive_entry_mtime(archive_entry));
convertLocalTimeToUtc(&stat.st_mtime, &time);
sceRtcSetTime_t(&time, archive_entry_atime(archive_entry));
convertLocalTimeToUtc(&stat.st_atime, &time);
// Add node
addArchiveNode(name, &stat);
}
archive_read_free(archive);
return 0;
}