2020-12-05 13:08:24 +01:00
|
|
|
|
#include "stdafx.h"
|
2017-02-16 03:15:00 +01:00
|
|
|
|
|
2021-03-05 17:07:36 +01:00
|
|
|
|
#include "Emu/VFS.h"
|
|
|
|
|
|
#include "Emu/System.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include "Crypto/unself.h"
|
|
|
|
|
|
|
2017-02-16 03:15:00 +01:00
|
|
|
|
#include "TAR.h"
|
|
|
|
|
|
|
2021-03-11 00:26:39 +01:00
|
|
|
|
#include "util/asm.hpp"
|
2023-11-15 20:07:42 +01:00
|
|
|
|
|
2023-11-27 21:13:49 +01:00
|
|
|
|
#include "util/serialization_ext.hpp"
|
2021-03-11 00:26:39 +01:00
|
|
|
|
|
|
|
|
|
|
#include <charconv>
|
2023-11-15 20:07:42 +01:00
|
|
|
|
#include <span>
|
2017-02-16 03:15:00 +01:00
|
|
|
|
|
2020-02-01 08:43:43 +01:00
|
|
|
|
LOG_CHANNEL(tar_log, "TAR");
|
2020-02-01 05:15:50 +01:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
fs::file make_file_view(const fs::file& file, u64 offset, u64 size);
|
|
|
|
|
|
|
|
|
|
|
|
// File constructor
|
2021-03-11 00:26:39 +01:00
|
|
|
|
tar_object::tar_object(const fs::file& file)
|
2023-11-15 20:07:42 +01:00
|
|
|
|
: m_file(std::addressof(file))
|
|
|
|
|
|
, m_ar(nullptr)
|
|
|
|
|
|
, m_ar_tar_start(umax)
|
|
|
|
|
|
{
|
|
|
|
|
|
ensure(*m_file);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Stream (pipe-like) constructor
|
|
|
|
|
|
tar_object::tar_object(utils::serial& ar)
|
|
|
|
|
|
: m_file(nullptr)
|
|
|
|
|
|
, m_ar(std::addressof(ar))
|
|
|
|
|
|
, m_ar_tar_start(ar.pos)
|
2017-02-16 03:15:00 +01:00
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-11 00:26:39 +01:00
|
|
|
|
TARHeader tar_object::read_header(u64 offset) const
|
2017-02-16 03:15:00 +01:00
|
|
|
|
{
|
2021-03-11 00:26:39 +01:00
|
|
|
|
TARHeader header{};
|
|
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (m_ar)
|
2021-03-11 00:26:39 +01:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
ensure(m_ar->pos == m_ar_tar_start + offset);
|
|
|
|
|
|
m_ar->serialize(header);
|
2021-03-11 00:26:39 +01:00
|
|
|
|
return header;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (m_file->read_at(offset, &header, sizeof(header)) != sizeof(header))
|
2021-03-11 00:26:39 +01:00
|
|
|
|
{
|
|
|
|
|
|
std::memset(&header, 0, sizeof(header));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-02-16 03:15:00 +01:00
|
|
|
|
return header;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-11 00:26:39 +01:00
|
|
|
|
u64 octal_text_to_u64(std::string_view sv)
|
2017-02-16 03:15:00 +01:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
u64 i = umax;
|
2021-03-11 00:26:39 +01:00
|
|
|
|
const auto ptr = std::from_chars(sv.data(), sv.data() + sv.size(), i, 8).ptr;
|
|
|
|
|
|
|
|
|
|
|
|
// Range must be terminated with either NUL or space
|
|
|
|
|
|
if (ptr == sv.data() + sv.size() || (*ptr && *ptr != ' '))
|
2017-02-16 03:15:00 +01:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
i = umax;
|
2017-02-16 03:15:00 +01:00
|
|
|
|
}
|
2021-03-11 00:26:39 +01:00
|
|
|
|
|
|
|
|
|
|
return i;
|
2017-02-16 03:15:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> tar_object::get_filenames()
|
|
|
|
|
|
{
|
|
|
|
|
|
std::vector<std::string> vec;
|
2023-11-15 20:07:42 +01:00
|
|
|
|
|
2017-02-16 03:15:00 +01:00
|
|
|
|
get_file("");
|
2023-11-15 20:07:42 +01:00
|
|
|
|
|
2017-02-16 03:15:00 +01:00
|
|
|
|
for (auto it = m_map.cbegin(); it != m_map.cend(); ++it)
|
|
|
|
|
|
{
|
|
|
|
|
|
vec.push_back(it->first);
|
|
|
|
|
|
}
|
2023-11-15 20:07:42 +01:00
|
|
|
|
|
2017-02-16 03:15:00 +01:00
|
|
|
|
return vec;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
std::unique_ptr<utils::serial> tar_object::get_file(const std::string& path, std::string* new_file_path)
|
2017-02-16 03:15:00 +01:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
std::unique_ptr<utils::serial> m_out;
|
2017-02-16 03:15:00 +01:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
auto emplace_single_entry = [&](usz& offset, const usz max_size) -> std::pair<usz, std::string>
|
2017-02-16 03:15:00 +01:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (offset >= max_size)
|
2017-02-22 14:08:53 +01:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
return {};
|
|
|
|
|
|
}
|
2017-02-16 03:15:00 +01:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
TARHeader header = read_header(offset);
|
|
|
|
|
|
offset += 512;
|
2017-02-16 03:15:00 +01:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
u64 size = umax;
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
std::string filename;
|
2021-03-11 00:26:39 +01:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (std::memcmp(header.magic, "ustar", 5) == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
const std::string_view size_sv{header.size, std::size(header.size)};
|
2021-03-11 00:26:39 +01:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
size = octal_text_to_u64(size_sv);
|
2021-03-11 00:26:39 +01:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
// Check for overflows and if surpasses file size
|
|
|
|
|
|
if ((header.name[0] || header.prefix[0]) && ~size >= 512 && max_size >= size && max_size - size >= offset)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Cache size in native u64 format
|
|
|
|
|
|
static_assert(sizeof(size) < sizeof(header.size));
|
|
|
|
|
|
std::memcpy(header.size, &size, 8);
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
std::string_view prefix_name{header.prefix, std::size(header.prefix)};
|
|
|
|
|
|
std::string_view name{header.name, std::size(header.name)};
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
prefix_name = prefix_name.substr(0, prefix_name.find_first_of('\0'));
|
|
|
|
|
|
name = name.substr(0, name.find_first_of('\0'));
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
filename += prefix_name;
|
|
|
|
|
|
filename += name;
|
|
|
|
|
|
|
|
|
|
|
|
// Save header and offset
|
|
|
|
|
|
m_map.insert_or_assign(filename, std::make_pair(offset, header));
|
|
|
|
|
|
|
|
|
|
|
|
if (new_file_path)
|
2021-03-11 00:26:39 +01:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
*new_file_path = filename;
|
2021-03-11 00:26:39 +01:00
|
|
|
|
}
|
2023-11-15 20:07:42 +01:00
|
|
|
|
|
|
|
|
|
|
return { size, std::move(filename) };
|
2021-03-11 00:26:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
tar_log.error("tar_object::get_file() failed to convert header.size=%s, filesize=0x%x", size_sv, max_size);
|
2021-03-11 00:26:39 +01:00
|
|
|
|
}
|
2023-11-15 20:07:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2023-11-28 14:17:01 +01:00
|
|
|
|
tar_log.notice("tar_object::get_file() failed to parse header: offset=0x%x, filesize=0x%x, header_first16=0x%016x", offset, max_size, read_from_ptr<be_t<u128>>(reinterpret_cast<const u8*>(&header)));
|
2023-11-15 20:07:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { size, {} };
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (auto it = m_map.find(path); it != m_map.end())
|
|
|
|
|
|
{
|
|
|
|
|
|
u64 size = 0;
|
|
|
|
|
|
std::memcpy(&size, it->second.second.size, sizeof(size));
|
|
|
|
|
|
|
|
|
|
|
|
if (m_file)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_out = std::make_unique<utils::serial>();
|
|
|
|
|
|
m_out->set_reading_state();
|
|
|
|
|
|
m_out->m_file_handler = make_uncompressed_serialization_file_handler(make_file_view(*m_file, it->second.first, size));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
m_out = std::make_unique<utils::serial>();
|
|
|
|
|
|
*m_out = std::move(*m_ar);
|
|
|
|
|
|
m_out->m_max_data = m_ar_tar_start + it->second.first + size;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return m_out;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (m_ar && path.empty())
|
|
|
|
|
|
{
|
|
|
|
|
|
const u64 size = emplace_single_entry(largest_offset, m_ar->get_size(umax) - m_ar_tar_start).first;
|
|
|
|
|
|
|
|
|
|
|
|
// Advance offset to next block
|
|
|
|
|
|
largest_offset += utils::align(size, 512);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Continue scanning from last file entered
|
|
|
|
|
|
else if (m_file)
|
|
|
|
|
|
{
|
|
|
|
|
|
const u64 max_size = m_file->size();
|
|
|
|
|
|
|
|
|
|
|
|
while (largest_offset < max_size)
|
|
|
|
|
|
{
|
|
|
|
|
|
const auto [size, filename] = emplace_single_entry(largest_offset, max_size);
|
2017-02-16 03:15:00 +01:00
|
|
|
|
|
2021-03-11 00:26:39 +01:00
|
|
|
|
if (size == umax)
|
|
|
|
|
|
{
|
2021-07-09 19:28:38 +02:00
|
|
|
|
continue;
|
2017-02-16 03:15:00 +01:00
|
|
|
|
}
|
2021-03-11 00:26:39 +01:00
|
|
|
|
|
2021-07-09 19:28:38 +02:00
|
|
|
|
// Advance offset to next block
|
2023-11-15 20:07:42 +01:00
|
|
|
|
largest_offset += utils::align(size, 512);
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
|
|
|
|
|
if (!path.empty() && path == filename)
|
2021-03-11 00:26:39 +01:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
// Path is equal, return handle to the file data
|
|
|
|
|
|
return get_file(path);
|
2017-02-16 03:15:00 +01:00
|
|
|
|
}
|
2017-02-22 14:08:53 +01:00
|
|
|
|
}
|
2017-02-16 03:15:00 +01:00
|
|
|
|
}
|
2023-11-15 20:07:42 +01:00
|
|
|
|
|
|
|
|
|
|
return m_out;
|
2017-02-16 03:15:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-04 15:02:17 +02:00
|
|
|
|
bool tar_object::extract(std::string prefix_path, bool is_vfs)
|
2017-02-16 03:15:00 +01:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
std::vector<u8> filedata_buffer(0x80'0000);
|
|
|
|
|
|
std::span<u8> filedata_span{filedata_buffer.data(), filedata_buffer.size()};
|
2017-02-16 03:15:00 +01:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
auto iter = m_map.begin();
|
2021-03-11 00:26:39 +01:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
auto get_next = [&](bool is_first)
|
2017-02-16 03:15:00 +01:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (m_ar)
|
|
|
|
|
|
{
|
|
|
|
|
|
ensure(!is_first || m_map.empty()); // Must be empty on first call
|
|
|
|
|
|
std::string name_iter;
|
|
|
|
|
|
get_file("", &name_iter); // Get next entry
|
|
|
|
|
|
return m_map.find(name_iter);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (is_first)
|
|
|
|
|
|
{
|
|
|
|
|
|
get_file(""); // Scan entries
|
|
|
|
|
|
return m_map.begin();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return std::next(iter);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
for (iter = get_next(true); iter != m_map.end(); iter = get_next(false))
|
|
|
|
|
|
{
|
|
|
|
|
|
const TARHeader& header = iter->second.second;
|
|
|
|
|
|
const std::string& name = iter->first;
|
2018-06-23 08:26:11 +02:00
|
|
|
|
|
2024-01-15 09:54:11 +01:00
|
|
|
|
// Backwards compatibility measure
|
|
|
|
|
|
const bool should_ignore = name.find(reinterpret_cast<const char*>(u8"$")) != umax;
|
|
|
|
|
|
|
2021-03-05 17:07:36 +01:00
|
|
|
|
std::string result = name;
|
|
|
|
|
|
|
2022-07-04 15:02:17 +02:00
|
|
|
|
if (!prefix_path.empty())
|
2021-03-05 17:07:36 +01:00
|
|
|
|
{
|
2022-07-04 15:02:17 +02:00
|
|
|
|
result = prefix_path + '/' + result;
|
2021-03-05 17:07:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2022-07-04 15:02:17 +02:00
|
|
|
|
// Must be VFS here
|
|
|
|
|
|
is_vfs = true;
|
2021-03-05 17:07:36 +01:00
|
|
|
|
result.insert(result.begin(), '/');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-04 15:02:17 +02:00
|
|
|
|
if (is_vfs)
|
2018-06-23 08:26:11 +02:00
|
|
|
|
{
|
2022-07-04 15:02:17 +02:00
|
|
|
|
result = vfs::get(result);
|
|
|
|
|
|
|
|
|
|
|
|
if (result.empty())
|
|
|
|
|
|
{
|
|
|
|
|
|
tar_log.error("Path of entry is not mounted: '%s' (prefix_path='%s')", name, prefix_path);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2018-06-23 08:26:11 +02:00
|
|
|
|
}
|
2017-02-16 03:15:00 +01:00
|
|
|
|
|
2021-07-09 19:28:38 +02:00
|
|
|
|
u64 mtime = octal_text_to_u64({header.mtime, std::size(header.mtime)});
|
|
|
|
|
|
|
2021-07-30 08:31:36 +02:00
|
|
|
|
// Let's use it for optional atime
|
2021-07-09 19:28:38 +02:00
|
|
|
|
u64 atime = octal_text_to_u64({header.padding, 12});
|
|
|
|
|
|
|
|
|
|
|
|
// This is a fake timestamp, it can be invalid
|
|
|
|
|
|
if (atime == umax)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Set to mtime if not provided
|
|
|
|
|
|
atime = mtime;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-02-22 14:08:53 +01:00
|
|
|
|
switch (header.filetype)
|
|
|
|
|
|
{
|
2021-03-11 00:26:39 +01:00
|
|
|
|
case '\0':
|
2017-02-16 03:15:00 +01:00
|
|
|
|
case '0':
|
|
|
|
|
|
{
|
2022-07-04 15:02:17 +02:00
|
|
|
|
// Create the directories which should have been mount points if prefix_path is not empty
|
|
|
|
|
|
if (!prefix_path.empty() && !fs::create_path(fs::get_parent_dir(result)))
|
2021-03-05 17:07:36 +01:00
|
|
|
|
{
|
|
|
|
|
|
tar_log.error("TAR Loader: failed to create directory for file %s (%s)", name, fs::g_tls_error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
// For restoring m_ar->m_max_data
|
|
|
|
|
|
usz restore_limit = umax;
|
|
|
|
|
|
|
|
|
|
|
|
if (!m_file)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Restore m_ar (remove limit)
|
|
|
|
|
|
restore_limit = m_ar->m_max_data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<utils::serial> file_data = get_file(name);
|
2020-04-11 23:47:58 +02:00
|
|
|
|
|
2024-01-15 09:54:11 +01:00
|
|
|
|
fs::file file;
|
|
|
|
|
|
|
|
|
|
|
|
if (should_ignore)
|
|
|
|
|
|
{
|
|
|
|
|
|
file = fs::make_stream<std::vector<u8>>();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
file.open(result, fs::rewrite);
|
|
|
|
|
|
}
|
2020-04-11 23:47:58 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (file && file_data)
|
2020-04-11 23:47:58 +02:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
while (true)
|
|
|
|
|
|
{
|
|
|
|
|
|
const usz unread_size = file_data->try_read(filedata_span);
|
|
|
|
|
|
|
|
|
|
|
|
if (unread_size == 0)
|
|
|
|
|
|
{
|
2024-01-15 09:54:11 +01:00
|
|
|
|
file.write(filedata_span.data(), should_ignore ? 0 : filedata_span.size());
|
2023-11-15 20:07:42 +01:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Tail data
|
|
|
|
|
|
|
|
|
|
|
|
if (usz read_size = filedata_span.size() - unread_size)
|
|
|
|
|
|
{
|
|
|
|
|
|
ensure(file_data->try_read(filedata_span.first(read_size)) == 0);
|
2024-01-15 09:54:11 +01:00
|
|
|
|
file.write(filedata_span.data(), should_ignore ? 0 : read_size);
|
2023-11-15 20:07:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-09 19:28:38 +02:00
|
|
|
|
file.close();
|
|
|
|
|
|
|
2024-01-15 09:54:11 +01:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
file_data->seek_pos(m_ar_tar_start + largest_offset, true);
|
|
|
|
|
|
|
|
|
|
|
|
if (!m_file)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Restore m_ar
|
|
|
|
|
|
*m_ar = std::move(*file_data);
|
|
|
|
|
|
m_ar->m_max_data = restore_limit;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-15 09:54:11 +01:00
|
|
|
|
if (should_ignore)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-09 19:28:38 +02:00
|
|
|
|
if (mtime != umax && !fs::utime(result, atime, mtime))
|
|
|
|
|
|
{
|
|
|
|
|
|
tar_log.error("TAR Loader: fs::utime failed on %s (%s)", result, fs::g_tls_error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-11 00:26:39 +01:00
|
|
|
|
tar_log.notice("TAR Loader: written file %s", name);
|
2020-04-11 23:47:58 +02:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (!m_file)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Restore m_ar
|
|
|
|
|
|
*m_ar = std::move(*file_data);
|
|
|
|
|
|
m_ar->m_max_data = restore_limit;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-11 00:26:39 +01:00
|
|
|
|
const auto old_error = fs::g_tls_error;
|
|
|
|
|
|
tar_log.error("TAR Loader: failed to write file %s (%s) (fs::exists=%s)", name, old_error, fs::exists(result));
|
2020-04-11 23:47:58 +02:00
|
|
|
|
return false;
|
2017-02-16 03:15:00 +01:00
|
|
|
|
}
|
2018-06-23 08:26:11 +02:00
|
|
|
|
|
2017-02-16 03:15:00 +01:00
|
|
|
|
case '5':
|
|
|
|
|
|
{
|
2024-01-15 09:54:11 +01:00
|
|
|
|
if (should_ignore)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-28 20:59:27 +01:00
|
|
|
|
if (!fs::create_path(result))
|
|
|
|
|
|
{
|
2021-03-11 00:26:39 +01:00
|
|
|
|
tar_log.error("TAR Loader: failed to create directory %s (%s)", name, fs::g_tls_error);
|
2021-02-28 20:59:27 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-09 19:28:38 +02:00
|
|
|
|
if (mtime != umax && !fs::utime(result, atime, mtime))
|
|
|
|
|
|
{
|
|
|
|
|
|
tar_log.error("TAR Loader: fs::utime failed on %s (%s)", result, fs::g_tls_error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-02-16 03:15:00 +01:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
2020-02-01 05:15:50 +01:00
|
|
|
|
tar_log.error("TAR Loader: unknown file type: 0x%x", header.filetype);
|
2017-02-16 03:15:00 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
2017-02-22 14:08:53 +01:00
|
|
|
|
}
|
2021-03-05 17:07:36 +01:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
void tar_object::save_directory(const std::string& target_path, utils::serial& ar, const process_func& func, std::vector<fs::dir_entry>&& entries, bool has_evaluated_results, usz src_dir_pos)
|
2021-07-09 19:28:38 +02:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
const bool is_null = ar.m_file_handler && ar.m_file_handler->is_null();
|
|
|
|
|
|
const bool reuse_entries = !is_null || has_evaluated_results;
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (reuse_entries)
|
2021-07-09 19:28:38 +02:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
ensure(!entries.empty());
|
2021-07-09 19:28:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
auto write_octal = [](char* ptr, u64 i)
|
2021-07-09 19:28:38 +02:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (!i)
|
2021-07-09 19:28:38 +02:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
*ptr = '0';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ptr += utils::aligned_div(static_cast<u32>(std::bit_width(i)), 3) - 1;
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
for (; i; ptr--, i /= 8)
|
|
|
|
|
|
{
|
|
|
|
|
|
*ptr = static_cast<char>('0' + (i % 8));
|
2021-07-09 19:28:38 +02:00
|
|
|
|
}
|
2023-11-15 20:07:42 +01:00
|
|
|
|
};
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
auto save_file = [&](const fs::stat_t& file_stat, const std::string& file_name)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!file_stat.size)
|
2021-07-09 19:28:38 +02:00
|
|
|
|
{
|
2023-10-29 01:46:52 +02:00
|
|
|
|
return;
|
2021-07-09 19:28:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (is_null && !func)
|
2021-07-09 19:28:38 +02:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
ar.pos += utils::align(file_stat.size, 512);
|
2021-07-09 19:28:38 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (fs::file fd{file_name})
|
|
|
|
|
|
{
|
|
|
|
|
|
const u64 old_pos = ar.pos;
|
|
|
|
|
|
const usz old_size = ar.data.size();
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (func)
|
|
|
|
|
|
{
|
|
|
|
|
|
std::string saved_path{&::at32(file_name, src_dir_pos), file_name.size() - src_dir_pos};
|
|
|
|
|
|
|
|
|
|
|
|
// Use custom function for file saving if provided
|
|
|
|
|
|
// Allows for example to compress PNG files as JPEG in the TAR itself
|
|
|
|
|
|
if (!func(fd, saved_path, ar))
|
|
|
|
|
|
{
|
|
|
|
|
|
// Revert (this entry should not be included if func returns false)
|
|
|
|
|
|
|
|
|
|
|
|
if (is_null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ar.pos = old_pos;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ar.data.resize(old_size);
|
|
|
|
|
|
ar.seek_end();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (is_null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Align
|
|
|
|
|
|
ar.pos += utils::align(ar.pos - old_pos, 512);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
constexpr usz transfer_block_size = 0x100'0000;
|
|
|
|
|
|
|
|
|
|
|
|
for (usz read_index = 0; read_index < file_stat.size; read_index += transfer_block_size)
|
|
|
|
|
|
{
|
|
|
|
|
|
const usz read_size = std::min<usz>(transfer_block_size, file_stat.size - read_index);
|
|
|
|
|
|
|
|
|
|
|
|
// Read file data
|
2023-12-29 15:10:13 +01:00
|
|
|
|
const usz buffer_tail = ar.data.size();
|
|
|
|
|
|
ar.data.resize(buffer_tail + read_size);
|
|
|
|
|
|
ensure(fd.read_at(read_index, ar.data.data() + buffer_tail, read_size) == read_size);
|
2023-11-15 20:07:42 +01:00
|
|
|
|
|
|
|
|
|
|
// Set position to the end of data, so breathe() would work correctly
|
|
|
|
|
|
ar.seek_end();
|
|
|
|
|
|
|
|
|
|
|
|
// Allow flushing to file if needed
|
|
|
|
|
|
ar.breathe();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Align
|
|
|
|
|
|
const usz diff = ar.pos - old_pos;
|
|
|
|
|
|
ar.data.resize(ar.data.size() + utils::align(diff, 512) - diff);
|
|
|
|
|
|
ar.seek_end();
|
|
|
|
|
|
|
|
|
|
|
|
fd.close();
|
|
|
|
|
|
ensure(fs::utime(file_name, file_stat.atime, file_stat.mtime));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2021-07-09 19:28:38 +02:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
ensure(false);
|
2021-07-09 19:28:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
auto save_header = [&](const fs::stat_t& stat, const std::string& name)
|
|
|
|
|
|
{
|
|
|
|
|
|
static_assert(sizeof(TARHeader) == 512);
|
|
|
|
|
|
|
|
|
|
|
|
std::string_view saved_path{name.size() == src_dir_pos ? name.c_str() : &::at32(name, src_dir_pos), name.size() - src_dir_pos};
|
|
|
|
|
|
|
|
|
|
|
|
if (is_null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ar.pos += sizeof(TARHeader);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (usz pos = saved_path.find_first_not_of(fs::delim); pos != umax)
|
|
|
|
|
|
{
|
|
|
|
|
|
saved_path = saved_path.substr(pos, saved_path.size());
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Target the destination directory, I do not know if this is compliant with TAR format
|
|
|
|
|
|
saved_path = "/"sv;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TARHeader header{};
|
|
|
|
|
|
std::memcpy(header.magic, "ustar ", 6);
|
|
|
|
|
|
|
|
|
|
|
|
// Prefer saving to name field as much as we can
|
|
|
|
|
|
// If it doesn't fit, save 100 characters at name and 155 characters preceding to it at max
|
|
|
|
|
|
const u64 prefix_size = std::clamp<usz>(saved_path.size(), 100, 255) - 100;
|
|
|
|
|
|
std::memcpy(header.prefix, saved_path.data(), prefix_size);
|
|
|
|
|
|
const u64 name_size = std::min<usz>(saved_path.size(), 255) - prefix_size;
|
|
|
|
|
|
std::memcpy(header.name, saved_path.data() + prefix_size, name_size);
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
write_octal(header.size, stat.is_directory ? 0 : stat.size);
|
|
|
|
|
|
write_octal(header.mtime, stat.mtime);
|
|
|
|
|
|
write_octal(header.padding, stat.atime);
|
|
|
|
|
|
header.filetype = stat.is_directory ? '5' : '0';
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
ar(header);
|
|
|
|
|
|
ar.breathe();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
fs::stat_t stat{};
|
|
|
|
|
|
|
|
|
|
|
|
if (src_dir_pos == umax)
|
2021-07-09 19:28:38 +02:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
// First call, get source directory string size so it can be cut from entry paths
|
|
|
|
|
|
src_dir_pos = target_path.size();
|
|
|
|
|
|
}
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (has_evaluated_results)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Save from cached data by previous call
|
|
|
|
|
|
for (auto&& entry : entries)
|
|
|
|
|
|
{
|
|
|
|
|
|
ensure(entry.name.starts_with(target_path));
|
|
|
|
|
|
save_header(entry, entry.name);
|
2021-09-06 14:45:13 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (!entry.is_directory)
|
|
|
|
|
|
{
|
|
|
|
|
|
save_file(entry, entry.name);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (entries.empty())
|
2021-07-09 19:28:38 +02:00
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (!fs::get_stat(target_path, stat))
|
2021-07-09 19:28:38 +02:00
|
|
|
|
{
|
2023-10-29 01:46:52 +02:00
|
|
|
|
return;
|
2021-07-09 19:28:38 +02:00
|
|
|
|
}
|
2023-11-15 20:07:42 +01:00
|
|
|
|
|
|
|
|
|
|
save_header(stat, target_path);
|
|
|
|
|
|
|
|
|
|
|
|
// Optimization: avoid saving to list if this is not an evaluation call
|
|
|
|
|
|
if (is_null)
|
|
|
|
|
|
{
|
|
|
|
|
|
static_cast<fs::stat_t&>(entries.emplace_back()) = stat;
|
|
|
|
|
|
entries.back().name = target_path;
|
|
|
|
|
|
}
|
2021-07-09 19:28:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2023-11-15 20:07:42 +01:00
|
|
|
|
stat = entries.back();
|
|
|
|
|
|
save_header(stat, entries.back().name);
|
2021-07-09 19:28:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
if (stat.is_directory)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool exists = false;
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
for (auto&& entry : fs::dir(target_path))
|
|
|
|
|
|
{
|
|
|
|
|
|
exists = true;
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2024-01-15 09:54:11 +01:00
|
|
|
|
if (entry.name.find_first_not_of('.') == umax || entry.name.starts_with(reinterpret_cast<const char*>(u8"$")))
|
2023-11-15 20:07:42 +01:00
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
entry.name = target_path.ends_with('/') ? target_path + entry.name : target_path + '/' + entry.name;
|
|
|
|
|
|
|
|
|
|
|
|
if (!entry.is_directory)
|
|
|
|
|
|
{
|
|
|
|
|
|
save_header(entry, entry.name);
|
|
|
|
|
|
save_file(entry, entry.name);
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
// TAR is an old format which does not depend on previous data so memory ventilation is trivial here
|
|
|
|
|
|
ar.breathe();
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
entries.emplace_back(std::move(entry));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!is_null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Optimization: avoid saving to list if this is not an evaluation call
|
|
|
|
|
|
entries.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
entries.emplace_back(std::move(entry));
|
|
|
|
|
|
save_directory(::as_rvalue(entries.back().name), ar, func, std::move(entries), false, src_dir_pos);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-07-09 19:28:38 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
ensure(exists);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
fs::dir_entry entry{};
|
|
|
|
|
|
entry.name = target_path;
|
|
|
|
|
|
static_cast<fs::stat_t&>(entry) = stat;
|
2023-10-29 01:46:52 +02:00
|
|
|
|
|
2023-11-15 20:07:42 +01:00
|
|
|
|
save_file(entry, entry.name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ar.breathe();
|
|
|
|
|
|
}
|
2021-07-09 19:28:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool extract_tar(const std::string& file_path, const std::string& dir_path, fs::file file)
|
2021-03-05 17:07:36 +01:00
|
|
|
|
{
|
|
|
|
|
|
tar_log.notice("Extracting '%s' to directory '%s'...", file_path, dir_path);
|
|
|
|
|
|
|
2021-07-09 19:28:38 +02:00
|
|
|
|
if (!file)
|
|
|
|
|
|
{
|
|
|
|
|
|
file.open(file_path);
|
|
|
|
|
|
}
|
2021-03-05 17:07:36 +01:00
|
|
|
|
|
|
|
|
|
|
if (!file)
|
|
|
|
|
|
{
|
|
|
|
|
|
tar_log.error("Error opening file '%s' (%s)", file_path, fs::g_tls_error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<fs::file> vec;
|
|
|
|
|
|
|
|
|
|
|
|
if (SCEDecrypter self_dec(file); self_dec.LoadHeaders())
|
|
|
|
|
|
{
|
|
|
|
|
|
// Encrypted file, decrypt
|
|
|
|
|
|
self_dec.LoadMetadata(SCEPKG_ERK, SCEPKG_RIV);
|
|
|
|
|
|
|
|
|
|
|
|
if (!self_dec.DecryptData())
|
|
|
|
|
|
{
|
|
|
|
|
|
tar_log.error("Failed to decrypt TAR.");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
vec = self_dec.MakeFile();
|
|
|
|
|
|
if (vec.size() < 3)
|
|
|
|
|
|
{
|
|
|
|
|
|
tar_log.error("Failed to decrypt TAR.");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Not an encrypted file
|
|
|
|
|
|
tar_log.warning("TAR is not encrypted, it may not be valid for this tool. Encrypted TAR are known to be found in PS3 Firmware files only.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!vfs::mount("/tar_extract", dir_path))
|
|
|
|
|
|
{
|
|
|
|
|
|
tar_log.error("Failed to mount '%s'", dir_path);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tar_object tar(vec.empty() ? file : vec[2]);
|
|
|
|
|
|
|
2022-07-04 15:02:17 +02:00
|
|
|
|
const bool ok = tar.extract("/tar_extract", true);
|
2021-03-05 17:07:36 +01:00
|
|
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
tar_log.success("Extraction complete!");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
tar_log.error("TAR contents are invalid.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unmount
|
|
|
|
|
|
Emu.Init();
|
|
|
|
|
|
return ok;
|
|
|
|
|
|
}
|