Compare commits
80 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9791f0d590 | ||
|
|
2a3d7128d1 | ||
|
|
aac807fd3a | ||
|
|
c594ec3417 | ||
|
|
c00b374e78 | ||
|
|
69236e5aff | ||
|
|
1dd27aff47 | ||
|
|
cee6a7ab55 | ||
|
|
a2fa37b499 | ||
|
|
f96de510ee | ||
|
|
91140f6c0a | ||
|
|
38592a3b5e | ||
|
|
40ecdda19e | ||
|
|
5ef447cc0e | ||
|
|
11c221cc62 | ||
|
|
40f83fee6a | ||
|
|
0bca5743ab | ||
|
|
aac5792a2b | ||
|
|
5752454629 | ||
|
|
87d8a9c986 | ||
|
|
b74df62959 | ||
|
|
e0b0f4eece | ||
|
|
b1148d269d | ||
|
|
8599e1e4fc | ||
|
|
3aad82b1a3 | ||
|
|
2a42dea568 | ||
|
|
c8cd1785e6 | ||
|
|
991eb4824c | ||
|
|
301baaa942 | ||
|
|
3db1b8e0cd | ||
|
|
8f9c49f7ee | ||
|
|
f009ed63f3 | ||
|
|
18dfa99030 | ||
|
|
6eda9ebbdb | ||
|
|
ad7815a28d | ||
|
|
409d2e07c2 | ||
|
|
8dc4407586 | ||
|
|
96c0b81a51 | ||
|
|
25d71454d1 | ||
|
|
290439a6a5 | ||
|
|
dc876fd63a | ||
|
|
d8fd3ef4fe | ||
|
|
1c31cbad72 | ||
|
|
2e715ef70d | ||
|
|
319dbc5843 | ||
|
|
6d549abb4e | ||
|
|
0ce0905380 | ||
|
|
11895d54af | ||
|
|
b6c47b578f | ||
|
|
9d09d92c56 | ||
|
|
59d18ef55b | ||
|
|
eab35c8235 | ||
|
|
35e4a47be0 | ||
|
|
6b76b77400 | ||
|
|
fdf27bf390 | ||
|
|
8f06a0f898 | ||
|
|
dda8ef11c7 | ||
|
|
149bda980a | ||
|
|
893447b6b0 | ||
|
|
22bdddd6f0 | ||
|
|
62e859c6c7 | ||
|
|
f78a6e752f | ||
|
|
3b3c919e20 | ||
|
|
10812f8407 | ||
|
|
e5504a060d | ||
|
|
167bfddafa | ||
|
|
bfb945c243 | ||
|
|
b67e751ccb | ||
|
|
a91983b11c | ||
|
|
9aab787122 | ||
|
|
ab8acce645 | ||
|
|
9b0e3556ed | ||
|
|
c0257cf52f | ||
|
|
70a510bd8f | ||
|
|
95bb1067c1 | ||
|
|
5b4119fa7f | ||
|
|
42114e1df4 | ||
|
|
a27ec24c0f | ||
|
|
b70a831608 | ||
|
|
10aac376d1 |
@@ -66,10 +66,12 @@ if (NOT ENABLE_GENERIC)
|
||||
detect_architecture("_M_AMD64" x86_64)
|
||||
detect_architecture("_M_IX86" x86)
|
||||
detect_architecture("_M_ARM" ARM)
|
||||
detect_architecture("_M_ARM64" ARM64)
|
||||
else()
|
||||
detect_architecture("__x86_64__" x86_64)
|
||||
detect_architecture("__i386__" x86)
|
||||
detect_architecture("__arm__" ARM)
|
||||
detect_architecture("__aarch64__" ARM64)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -29,8 +29,6 @@ add_library(common STATIC
|
||||
assert.h
|
||||
bit_field.h
|
||||
bit_set.h
|
||||
break_points.cpp
|
||||
break_points.h
|
||||
cityhash.cpp
|
||||
cityhash.h
|
||||
color.h
|
||||
@@ -40,6 +38,8 @@ add_library(common STATIC
|
||||
file_util.cpp
|
||||
file_util.h
|
||||
hash.h
|
||||
hex_util.cpp
|
||||
hex_util.h
|
||||
logging/backend.cpp
|
||||
logging/backend.h
|
||||
logging/filter.cpp
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include "common/break_points.h"
|
||||
|
||||
bool BreakPoints::IsAddressBreakPoint(u32 iAddress) const {
|
||||
auto cond = [&iAddress](const TBreakPoint& bp) { return bp.iAddress == iAddress; };
|
||||
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
||||
return it != m_BreakPoints.end();
|
||||
}
|
||||
|
||||
bool BreakPoints::IsTempBreakPoint(u32 iAddress) const {
|
||||
auto cond = [&iAddress](const TBreakPoint& bp) {
|
||||
return bp.iAddress == iAddress && bp.bTemporary;
|
||||
};
|
||||
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
||||
return it != m_BreakPoints.end();
|
||||
}
|
||||
|
||||
BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const {
|
||||
TBreakPointsStr bps;
|
||||
for (auto breakpoint : m_BreakPoints) {
|
||||
if (!breakpoint.bTemporary) {
|
||||
std::stringstream bp;
|
||||
bp << std::hex << breakpoint.iAddress << " " << (breakpoint.bOn ? "n" : "");
|
||||
bps.push_back(bp.str());
|
||||
}
|
||||
}
|
||||
|
||||
return bps;
|
||||
}
|
||||
|
||||
void BreakPoints::AddFromStrings(const TBreakPointsStr& bps) {
|
||||
for (auto bps_item : bps) {
|
||||
TBreakPoint bp;
|
||||
std::stringstream bpstr;
|
||||
bpstr << std::hex << bps_item;
|
||||
bpstr >> bp.iAddress;
|
||||
bp.bOn = bps_item.find("n") != bps_item.npos;
|
||||
bp.bTemporary = false;
|
||||
Add(bp);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakPoints::Add(const TBreakPoint& bp) {
|
||||
if (!IsAddressBreakPoint(bp.iAddress)) {
|
||||
m_BreakPoints.push_back(bp);
|
||||
// if (jit)
|
||||
// jit->GetBlockCache()->InvalidateICache(bp.iAddress, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakPoints::Add(u32 em_address, bool temp) {
|
||||
if (!IsAddressBreakPoint(em_address)) // only add new addresses
|
||||
{
|
||||
TBreakPoint pt; // breakpoint settings
|
||||
pt.bOn = true;
|
||||
pt.bTemporary = temp;
|
||||
pt.iAddress = em_address;
|
||||
|
||||
m_BreakPoints.push_back(pt);
|
||||
|
||||
// if (jit)
|
||||
// jit->GetBlockCache()->InvalidateICache(em_address, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakPoints::Remove(u32 em_address) {
|
||||
auto cond = [&em_address](const TBreakPoint& bp) { return bp.iAddress == em_address; };
|
||||
auto it = std::find_if(m_BreakPoints.begin(), m_BreakPoints.end(), cond);
|
||||
if (it != m_BreakPoints.end())
|
||||
m_BreakPoints.erase(it);
|
||||
}
|
||||
|
||||
void BreakPoints::Clear() {
|
||||
// if (jit)
|
||||
//{
|
||||
// std::for_each(m_BreakPoints.begin(), m_BreakPoints.end(),
|
||||
// [](const TBreakPoint& bp)
|
||||
// {
|
||||
// jit->GetBlockCache()->InvalidateICache(bp.iAddress, 4);
|
||||
// }
|
||||
// );
|
||||
//}
|
||||
|
||||
m_BreakPoints.clear();
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
class DebugInterface;
|
||||
|
||||
struct TBreakPoint {
|
||||
u32 iAddress;
|
||||
bool bOn;
|
||||
bool bTemporary;
|
||||
};
|
||||
|
||||
// Code breakpoints.
|
||||
class BreakPoints {
|
||||
public:
|
||||
typedef std::vector<TBreakPoint> TBreakPoints;
|
||||
typedef std::vector<std::string> TBreakPointsStr;
|
||||
|
||||
const TBreakPoints& GetBreakPoints() {
|
||||
return m_BreakPoints;
|
||||
}
|
||||
|
||||
TBreakPointsStr GetStrings() const;
|
||||
void AddFromStrings(const TBreakPointsStr& bps);
|
||||
|
||||
// is address breakpoint
|
||||
bool IsAddressBreakPoint(u32 iAddress) const;
|
||||
bool IsTempBreakPoint(u32 iAddress) const;
|
||||
|
||||
// Add BreakPoint
|
||||
void Add(u32 em_address, bool temp = false);
|
||||
void Add(const TBreakPoint& bp);
|
||||
|
||||
// Remove Breakpoint
|
||||
void Remove(u32 iAddress);
|
||||
void Clear();
|
||||
|
||||
void DeleteByAddress(u32 Address);
|
||||
|
||||
private:
|
||||
TBreakPoints m_BreakPoints;
|
||||
u32 m_iBreakOnCount;
|
||||
};
|
||||
@@ -750,6 +750,12 @@ std::string GetHactoolConfigurationPath() {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string GetNANDRegistrationDir(bool system) {
|
||||
if (system)
|
||||
return GetUserPath(UserPath::NANDDir) + "system/Contents/registered/";
|
||||
return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/";
|
||||
}
|
||||
|
||||
size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
|
||||
return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
|
||||
}
|
||||
|
||||
@@ -129,6 +129,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
|
||||
|
||||
std::string GetHactoolConfigurationPath();
|
||||
|
||||
std::string GetNANDRegistrationDir(bool system = false);
|
||||
|
||||
// Returns the path to where the sys file are
|
||||
std::string GetSysDirectory();
|
||||
|
||||
|
||||
27
src/common/hex_util.cpp
Normal file
27
src/common/hex_util.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/hex_util.h"
|
||||
|
||||
u8 ToHexNibble(char c1) {
|
||||
if (c1 >= 65 && c1 <= 70)
|
||||
return c1 - 55;
|
||||
if (c1 >= 97 && c1 <= 102)
|
||||
return c1 - 87;
|
||||
if (c1 >= 48 && c1 <= 57)
|
||||
return c1 - 48;
|
||||
throw std::logic_error("Invalid hex digit");
|
||||
}
|
||||
|
||||
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
|
||||
if (len != 32)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
return HexStringToArray<16>(str);
|
||||
}
|
||||
|
||||
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
|
||||
if (len != 64)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
return HexStringToArray<32>(str);
|
||||
}
|
||||
37
src/common/hex_util.h
Normal file
37
src/common/hex_util.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
u8 ToHexNibble(char c1);
|
||||
|
||||
template <size_t Size, bool le = false>
|
||||
std::array<u8, Size> HexStringToArray(std::string_view str) {
|
||||
std::array<u8, Size> out{};
|
||||
if constexpr (le) {
|
||||
for (size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2)
|
||||
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
|
||||
} else {
|
||||
for (size_t i = 0; i < 2 * Size; i += 2)
|
||||
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t Size>
|
||||
std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
|
||||
std::string out;
|
||||
for (u8 c : array)
|
||||
out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
|
||||
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
@@ -9,10 +9,9 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_set.h"
|
||||
|
||||
namespace Common {
|
||||
namespace X64 {
|
||||
namespace Common::X64 {
|
||||
|
||||
int RegToIndex(const Xbyak::Reg& reg) {
|
||||
inline int RegToIndex(const Xbyak::Reg& reg) {
|
||||
using Kind = Xbyak::Reg::Kind;
|
||||
ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0,
|
||||
"RegSet only support GPRs and XMM registers.");
|
||||
@@ -152,8 +151,8 @@ constexpr size_t ABI_SHADOW_SPACE = 0;
|
||||
|
||||
#endif
|
||||
|
||||
void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_frame_size,
|
||||
s32* out_subtraction, s32* out_xmm_offset) {
|
||||
inline void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_frame_size,
|
||||
s32* out_subtraction, s32* out_xmm_offset) {
|
||||
int count = (regs & ABI_ALL_GPRS).Count();
|
||||
rsp_alignment -= count * 8;
|
||||
size_t subtraction = 0;
|
||||
@@ -174,8 +173,8 @@ void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_f
|
||||
*out_xmm_offset = (s32)(subtraction - xmm_base_subtraction);
|
||||
}
|
||||
|
||||
size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs,
|
||||
size_t rsp_alignment, size_t needed_frame_size = 0) {
|
||||
inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs,
|
||||
size_t rsp_alignment, size_t needed_frame_size = 0) {
|
||||
s32 subtraction, xmm_offset;
|
||||
ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset);
|
||||
|
||||
@@ -195,8 +194,8 @@ size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs
|
||||
return ABI_SHADOW_SPACE;
|
||||
}
|
||||
|
||||
void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, size_t rsp_alignment,
|
||||
size_t needed_frame_size = 0) {
|
||||
inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs,
|
||||
size_t rsp_alignment, size_t needed_frame_size = 0) {
|
||||
s32 subtraction, xmm_offset;
|
||||
ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset);
|
||||
|
||||
@@ -217,5 +216,4 @@ void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, s
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace X64
|
||||
} // namespace Common
|
||||
} // namespace Common::X64
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#include <xbyak.h>
|
||||
#include "common/x64/xbyak_abi.h"
|
||||
|
||||
namespace Common {
|
||||
namespace X64 {
|
||||
namespace Common::X64 {
|
||||
|
||||
// Constants for use with cmpps/cmpss
|
||||
enum {
|
||||
@@ -45,5 +44,4 @@ inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace X64
|
||||
} // namespace Common
|
||||
} // namespace Common::X64
|
||||
|
||||
@@ -20,6 +20,8 @@ add_library(core STATIC
|
||||
crypto/key_manager.h
|
||||
crypto/ctr_encryption_layer.cpp
|
||||
crypto/ctr_encryption_layer.h
|
||||
file_sys/bis_factory.cpp
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
file_sys/card_image.h
|
||||
file_sys/content_archive.cpp
|
||||
@@ -29,10 +31,14 @@ add_library(core STATIC
|
||||
file_sys/directory.h
|
||||
file_sys/errors.h
|
||||
file_sys/mode.h
|
||||
file_sys/nca_metadata.cpp
|
||||
file_sys/nca_metadata.h
|
||||
file_sys/partition_filesystem.cpp
|
||||
file_sys/partition_filesystem.h
|
||||
file_sys/program_metadata.cpp
|
||||
file_sys/program_metadata.h
|
||||
file_sys/registered_cache.cpp
|
||||
file_sys/registered_cache.h
|
||||
file_sys/romfs.cpp
|
||||
file_sys/romfs.h
|
||||
file_sys/romfs_factory.cpp
|
||||
@@ -43,6 +49,8 @@ add_library(core STATIC
|
||||
file_sys/sdmc_factory.h
|
||||
file_sys/vfs.cpp
|
||||
file_sys/vfs.h
|
||||
file_sys/vfs_concat.cpp
|
||||
file_sys/vfs_concat.h
|
||||
file_sys/vfs_offset.cpp
|
||||
file_sys/vfs_offset.h
|
||||
file_sys/vfs_real.cpp
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
@@ -17,6 +18,7 @@
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
#include "file_sys/vfs_concat.h"
|
||||
#include "file_sys/vfs_real.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
@@ -88,8 +90,39 @@ System::ResultStatus System::SingleStep() {
|
||||
return RunLoop(false);
|
||||
}
|
||||
|
||||
static FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
const std::string& path) {
|
||||
// To account for split 00+01+etc files.
|
||||
std::string dir_name;
|
||||
std::string filename;
|
||||
Common::SplitPath(path, &dir_name, &filename, nullptr);
|
||||
if (filename == "00") {
|
||||
const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read);
|
||||
std::vector<FileSys::VirtualFile> concat;
|
||||
for (u8 i = 0; i < 0x10; ++i) {
|
||||
auto next = dir->GetFile(fmt::format("{:02X}", i));
|
||||
if (next != nullptr)
|
||||
concat.push_back(std::move(next));
|
||||
else {
|
||||
next = dir->GetFile(fmt::format("{:02x}", i));
|
||||
if (next != nullptr)
|
||||
concat.push_back(std::move(next));
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (concat.empty())
|
||||
return nullptr;
|
||||
|
||||
return FileSys::ConcatenateFiles(concat, dir->GetName());
|
||||
}
|
||||
|
||||
return vfs->OpenFile(path, FileSys::Mode::Read);
|
||||
}
|
||||
|
||||
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
|
||||
app_loader = Loader::GetLoader(virtual_filesystem->OpenFile(filepath, FileSys::Mode::Read));
|
||||
app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
|
||||
|
||||
if (!app_loader) {
|
||||
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
||||
|
||||
@@ -56,6 +56,9 @@ static u64 event_fifo_id;
|
||||
// to the event_queue by the emu thread
|
||||
static Common::MPSCQueue<Event, false> ts_queue;
|
||||
|
||||
// the queue for unscheduling the events from other threads threadsafe
|
||||
static Common::MPSCQueue<std::pair<const EventType*, u64>, false> unschedule_queue;
|
||||
|
||||
constexpr int MAX_SLICE_LENGTH = 20000;
|
||||
|
||||
static s64 idled_cycles;
|
||||
@@ -158,6 +161,10 @@ void UnscheduleEvent(const EventType* event_type, u64 userdata) {
|
||||
}
|
||||
}
|
||||
|
||||
void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata) {
|
||||
unschedule_queue.Push(std::make_pair(event_type, userdata));
|
||||
}
|
||||
|
||||
void RemoveEvent(const EventType* event_type) {
|
||||
auto itr = std::remove_if(event_queue.begin(), event_queue.end(),
|
||||
[&](const Event& e) { return e.type == event_type; });
|
||||
@@ -194,6 +201,9 @@ void MoveEvents() {
|
||||
|
||||
void Advance() {
|
||||
MoveEvents();
|
||||
for (std::pair<const EventType*, u64> ev; unschedule_queue.Pop(ev);) {
|
||||
UnscheduleEvent(ev.first, ev.second);
|
||||
}
|
||||
|
||||
int cycles_executed = slice_length - downcount;
|
||||
global_timer += cycles_executed;
|
||||
|
||||
@@ -65,6 +65,7 @@ void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 user
|
||||
void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata);
|
||||
|
||||
void UnscheduleEvent(const EventType* event_type, u64 userdata);
|
||||
void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata);
|
||||
|
||||
/// We only permit one event of each type in the queue at a time.
|
||||
void RemoveEvent(const EventType* event_type);
|
||||
|
||||
@@ -10,44 +10,13 @@
|
||||
#include <string_view>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
static u8 ToHexNibble(char c1) {
|
||||
if (c1 >= 65 && c1 <= 70)
|
||||
return c1 - 55;
|
||||
if (c1 >= 97 && c1 <= 102)
|
||||
return c1 - 87;
|
||||
if (c1 >= 48 && c1 <= 57)
|
||||
return c1 - 48;
|
||||
throw std::logic_error("Invalid hex digit");
|
||||
}
|
||||
|
||||
template <size_t Size>
|
||||
static std::array<u8, Size> HexStringToArray(std::string_view str) {
|
||||
std::array<u8, Size> out{};
|
||||
for (size_t i = 0; i < 2 * Size; i += 2) {
|
||||
auto d1 = str[i];
|
||||
auto d2 = str[i + 1];
|
||||
out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
|
||||
if (len != 32)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
return HexStringToArray<16>(str);
|
||||
}
|
||||
|
||||
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
|
||||
if (len != 64)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
return HexStringToArray<32>(str);
|
||||
}
|
||||
|
||||
KeyManager::KeyManager() {
|
||||
// Initialize keys
|
||||
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
|
||||
|
||||
@@ -87,9 +87,6 @@ struct hash<Core::Crypto::KeyIndex<KeyType>> {
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
|
||||
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
|
||||
|
||||
class KeyManager {
|
||||
public:
|
||||
KeyManager();
|
||||
|
||||
31
src/core/file_sys/bis_factory.cpp
Normal file
31
src/core/file_sys/bis_factory.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) {
|
||||
const auto res = dir->GetDirectoryRelative(path);
|
||||
if (res == nullptr)
|
||||
return dir->CreateDirectoryRelative(path);
|
||||
return res;
|
||||
}
|
||||
|
||||
BISFactory::BISFactory(VirtualDir nand_root_)
|
||||
: nand_root(std::move(nand_root_)),
|
||||
sysnand_cache(std::make_shared<RegisteredCache>(
|
||||
GetOrCreateDirectory(nand_root, "/system/Contents/registered"))),
|
||||
usrnand_cache(std::make_shared<RegisteredCache>(
|
||||
GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {}
|
||||
|
||||
std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
|
||||
return sysnand_cache;
|
||||
}
|
||||
|
||||
std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
|
||||
return usrnand_cache;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
30
src/core/file_sys/bis_factory.h
Normal file
30
src/core/file_sys/bis_factory.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "core/loader/loader.h"
|
||||
#include "registered_cache.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the Built-In Storage
|
||||
/// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND
|
||||
/// registered caches.
|
||||
class BISFactory {
|
||||
public:
|
||||
explicit BISFactory(VirtualDir nand_root);
|
||||
|
||||
std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
|
||||
std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
|
||||
|
||||
private:
|
||||
VirtualDir nand_root;
|
||||
|
||||
std::shared_ptr<RegisteredCache> sysnand_cache;
|
||||
std::shared_ptr<RegisteredCache> usrnand_cache;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -4,11 +4,14 @@
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <core/loader/loader.h>
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -93,6 +96,10 @@ VirtualDir XCI::GetLogoPartition() const {
|
||||
return GetPartition(XCIPartition::Logo);
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
|
||||
return ncas;
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
|
||||
const auto iter =
|
||||
std::find_if(ncas.begin(), ncas.end(),
|
||||
@@ -142,7 +149,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
|
||||
const u16 error_id = static_cast<u16>(nca->GetStatus());
|
||||
LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})",
|
||||
partition_names[static_cast<size_t>(part)], nca->GetName(), error_id,
|
||||
Loader::GetMessageForResultStatus(nca->GetStatus()));
|
||||
nca->GetStatus());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ public:
|
||||
VirtualDir GetUpdatePartition() const;
|
||||
VirtualDir GetLogoPartition() const;
|
||||
|
||||
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
|
||||
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
|
||||
VirtualFile GetNCAFileByType(NCAContentType type) const;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ std::string LanguageEntry::GetDeveloperName() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
|
||||
}
|
||||
|
||||
NACP::NACP(VirtualFile file_) : file(std::move(file_)), raw(std::make_unique<RawNACP>()) {
|
||||
NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
|
||||
file->ReadObject(raw.get());
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ public:
|
||||
std::string GetVersionString() const;
|
||||
|
||||
private:
|
||||
VirtualFile file;
|
||||
std::unique_ptr<RawNACP> raw;
|
||||
};
|
||||
|
||||
|
||||
131
src/core/file_sys/nca_metadata.cpp
Normal file
131
src/core/file_sys/nca_metadata.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
bool operator>=(TitleType lhs, TitleType rhs) {
|
||||
return static_cast<size_t>(lhs) >= static_cast<size_t>(rhs);
|
||||
}
|
||||
|
||||
bool operator<=(TitleType lhs, TitleType rhs) {
|
||||
return static_cast<size_t>(lhs) <= static_cast<size_t>(rhs);
|
||||
}
|
||||
|
||||
CNMT::CNMT(VirtualFile file) {
|
||||
if (file->ReadObject(&header) != sizeof(CNMTHeader))
|
||||
return;
|
||||
|
||||
// If type is {Application, Update, AOC} has opt-header.
|
||||
if (header.type >= TitleType::Application && header.type <= TitleType::AOC) {
|
||||
if (file->ReadObject(&opt_header, sizeof(CNMTHeader)) != sizeof(OptionalHeader)) {
|
||||
LOG_WARNING(Loader, "Failed to read optional header.");
|
||||
}
|
||||
}
|
||||
|
||||
for (u16 i = 0; i < header.number_content_entries; ++i) {
|
||||
auto& next = content_records.emplace_back(ContentRecord{});
|
||||
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) +
|
||||
header.table_offset) != sizeof(ContentRecord)) {
|
||||
content_records.erase(content_records.end() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (u16 i = 0; i < header.number_meta_entries; ++i) {
|
||||
auto& next = meta_records.emplace_back(MetaRecord{});
|
||||
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) +
|
||||
header.table_offset) != sizeof(MetaRecord)) {
|
||||
meta_records.erase(meta_records.end() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
|
||||
std::vector<MetaRecord> meta_records)
|
||||
: header(std::move(header)), opt_header(std::move(opt_header)),
|
||||
content_records(std::move(content_records)), meta_records(std::move(meta_records)) {}
|
||||
|
||||
u64 CNMT::GetTitleID() const {
|
||||
return header.title_id;
|
||||
}
|
||||
|
||||
u32 CNMT::GetTitleVersion() const {
|
||||
return header.title_version;
|
||||
}
|
||||
|
||||
TitleType CNMT::GetType() const {
|
||||
return header.type;
|
||||
}
|
||||
|
||||
const std::vector<ContentRecord>& CNMT::GetContentRecords() const {
|
||||
return content_records;
|
||||
}
|
||||
|
||||
const std::vector<MetaRecord>& CNMT::GetMetaRecords() const {
|
||||
return meta_records;
|
||||
}
|
||||
|
||||
bool CNMT::UnionRecords(const CNMT& other) {
|
||||
bool change = false;
|
||||
for (const auto& rec : other.content_records) {
|
||||
const auto iter = std::find_if(content_records.begin(), content_records.end(),
|
||||
[&rec](const ContentRecord& r) {
|
||||
return r.nca_id == rec.nca_id && r.type == rec.type;
|
||||
});
|
||||
if (iter == content_records.end()) {
|
||||
content_records.emplace_back(rec);
|
||||
++header.number_content_entries;
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
for (const auto& rec : other.meta_records) {
|
||||
const auto iter =
|
||||
std::find_if(meta_records.begin(), meta_records.end(), [&rec](const MetaRecord& r) {
|
||||
return r.title_id == rec.title_id && r.title_version == rec.title_version &&
|
||||
r.type == rec.type;
|
||||
});
|
||||
if (iter == meta_records.end()) {
|
||||
meta_records.emplace_back(rec);
|
||||
++header.number_meta_entries;
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
std::vector<u8> CNMT::Serialize() const {
|
||||
const bool has_opt_header =
|
||||
header.type >= TitleType::Application && header.type <= TitleType::AOC;
|
||||
const auto dead_zone = header.table_offset + sizeof(CNMTHeader);
|
||||
std::vector<u8> out(
|
||||
std::max(sizeof(CNMTHeader) + (has_opt_header ? sizeof(OptionalHeader) : 0), dead_zone) +
|
||||
content_records.size() * sizeof(ContentRecord) + meta_records.size() * sizeof(MetaRecord));
|
||||
memcpy(out.data(), &header, sizeof(CNMTHeader));
|
||||
|
||||
// Optional Header
|
||||
if (has_opt_header) {
|
||||
memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader));
|
||||
}
|
||||
|
||||
auto offset = header.table_offset;
|
||||
|
||||
for (const auto& rec : content_records) {
|
||||
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord));
|
||||
offset += sizeof(ContentRecord);
|
||||
}
|
||||
|
||||
for (const auto& rec : meta_records) {
|
||||
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord));
|
||||
offset += sizeof(MetaRecord);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
} // namespace FileSys
|
||||
111
src/core/file_sys/nca_metadata.h
Normal file
111
src/core/file_sys/nca_metadata.h
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
class CNMT;
|
||||
|
||||
struct CNMTHeader;
|
||||
struct OptionalHeader;
|
||||
|
||||
enum class TitleType : u8 {
|
||||
SystemProgram = 0x01,
|
||||
SystemDataArchive = 0x02,
|
||||
SystemUpdate = 0x03,
|
||||
FirmwarePackageA = 0x04,
|
||||
FirmwarePackageB = 0x05,
|
||||
Application = 0x80,
|
||||
Update = 0x81,
|
||||
AOC = 0x82,
|
||||
DeltaTitle = 0x83,
|
||||
};
|
||||
|
||||
bool operator>=(TitleType lhs, TitleType rhs);
|
||||
bool operator<=(TitleType lhs, TitleType rhs);
|
||||
|
||||
enum class ContentRecordType : u8 {
|
||||
Meta = 0,
|
||||
Program = 1,
|
||||
Data = 2,
|
||||
Control = 3,
|
||||
Manual = 4,
|
||||
Legal = 5,
|
||||
Patch = 6,
|
||||
};
|
||||
|
||||
struct ContentRecord {
|
||||
std::array<u8, 0x20> hash;
|
||||
std::array<u8, 0x10> nca_id;
|
||||
std::array<u8, 0x6> size;
|
||||
ContentRecordType type;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
};
|
||||
static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size.");
|
||||
|
||||
constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}};
|
||||
|
||||
struct MetaRecord {
|
||||
u64_le title_id;
|
||||
u32_le title_version;
|
||||
TitleType type;
|
||||
u8 install_byte;
|
||||
INSERT_PADDING_BYTES(2);
|
||||
};
|
||||
static_assert(sizeof(MetaRecord) == 0x10, "MetaRecord has incorrect size.");
|
||||
|
||||
struct OptionalHeader {
|
||||
u64_le title_id;
|
||||
u64_le minimum_version;
|
||||
};
|
||||
static_assert(sizeof(OptionalHeader) == 0x10, "OptionalHeader has incorrect size.");
|
||||
|
||||
struct CNMTHeader {
|
||||
u64_le title_id;
|
||||
u32_le title_version;
|
||||
TitleType type;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u16_le table_offset;
|
||||
u16_le number_content_entries;
|
||||
u16_le number_meta_entries;
|
||||
INSERT_PADDING_BYTES(12);
|
||||
};
|
||||
static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size.");
|
||||
|
||||
// A class representing the format used by NCA metadata files, typically named {}.cnmt.nca or
|
||||
// meta0.ncd. These describe which NCA's belong with which titles in the registered cache.
|
||||
class CNMT {
|
||||
public:
|
||||
explicit CNMT(VirtualFile file);
|
||||
CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
|
||||
std::vector<MetaRecord> meta_records);
|
||||
|
||||
u64 GetTitleID() const;
|
||||
u32 GetTitleVersion() const;
|
||||
TitleType GetType() const;
|
||||
|
||||
const std::vector<ContentRecord>& GetContentRecords() const;
|
||||
const std::vector<MetaRecord>& GetMetaRecords() const;
|
||||
|
||||
bool UnionRecords(const CNMT& other);
|
||||
std::vector<u8> Serialize() const;
|
||||
|
||||
private:
|
||||
CNMTHeader header;
|
||||
OptionalHeader opt_header;
|
||||
std::vector<ContentRecord> content_records;
|
||||
std::vector<MetaRecord> meta_records;
|
||||
|
||||
// TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data
|
||||
// after the table. This is not documented, unfortunately.
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
476
src/core/file_sys/registered_cache.cpp
Normal file
476
src/core/file_sys/registered_cache.cpp
Normal file
@@ -0,0 +1,476 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <regex>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/encryption_layer.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
|
||||
namespace FileSys {
|
||||
std::string RegisteredCacheEntry::DebugInfo() const {
|
||||
return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
|
||||
}
|
||||
|
||||
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
|
||||
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
|
||||
}
|
||||
|
||||
static bool FollowsTwoDigitDirFormat(std::string_view name) {
|
||||
static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript |
|
||||
std::regex_constants::icase);
|
||||
return std::regex_match(name.begin(), name.end(), two_digit_regex);
|
||||
}
|
||||
|
||||
static bool FollowsNcaIdFormat(std::string_view name) {
|
||||
static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript |
|
||||
std::regex_constants::icase);
|
||||
return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex);
|
||||
}
|
||||
|
||||
static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
|
||||
bool within_two_digit) {
|
||||
if (!within_two_digit)
|
||||
return fmt::format("/{}.nca", HexArrayToString(nca_id, second_hex_upper));
|
||||
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
|
||||
return fmt::format("/000000{:02X}/{}.nca", hash[0], HexArrayToString(nca_id, second_hex_upper));
|
||||
}
|
||||
|
||||
static std::string GetCNMTName(TitleType type, u64 title_id) {
|
||||
constexpr std::array<const char*, 9> TITLE_TYPE_NAMES{
|
||||
"SystemProgram",
|
||||
"SystemData",
|
||||
"SystemUpdate",
|
||||
"BootImagePackage",
|
||||
"BootImagePackageSafe",
|
||||
"Application",
|
||||
"Patch",
|
||||
"AddOnContent",
|
||||
"" ///< Currently unknown 'DeltaTitle'
|
||||
};
|
||||
|
||||
auto index = static_cast<size_t>(type);
|
||||
// If the index is after the jump in TitleType, subtract it out.
|
||||
if (index >= static_cast<size_t>(TitleType::Application)) {
|
||||
index -= static_cast<size_t>(TitleType::Application) -
|
||||
static_cast<size_t>(TitleType::FirmwarePackageB);
|
||||
}
|
||||
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
|
||||
}
|
||||
|
||||
static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
switch (type) {
|
||||
case NCAContentType::Program:
|
||||
// TODO(DarkLordZach): Differentiate between Program and Patch
|
||||
return ContentRecordType::Program;
|
||||
case NCAContentType::Meta:
|
||||
return ContentRecordType::Meta;
|
||||
case NCAContentType::Control:
|
||||
return ContentRecordType::Control;
|
||||
case NCAContentType::Data:
|
||||
return ContentRecordType::Data;
|
||||
case NCAContentType::Manual:
|
||||
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
|
||||
return ContentRecordType::Manual;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
|
||||
std::string_view path) const {
|
||||
if (dir->GetFileRelative(path) != nullptr)
|
||||
return dir->GetFileRelative(path);
|
||||
if (dir->GetDirectoryRelative(path) != nullptr) {
|
||||
const auto nca_dir = dir->GetDirectoryRelative(path);
|
||||
VirtualFile file = nullptr;
|
||||
|
||||
const auto files = nca_dir->GetFiles();
|
||||
if (files.size() == 1 && files[0]->GetName() == "00") {
|
||||
file = files[0];
|
||||
} else {
|
||||
std::vector<VirtualFile> concat;
|
||||
// Since the files are a two-digit hex number, max is FF.
|
||||
for (size_t i = 0; i < 0x100; ++i) {
|
||||
auto next = nca_dir->GetFile(fmt::format("{:02X}", i));
|
||||
if (next != nullptr) {
|
||||
concat.push_back(std::move(next));
|
||||
} else {
|
||||
next = nca_dir->GetFile(fmt::format("{:02x}", i));
|
||||
if (next != nullptr)
|
||||
concat.push_back(std::move(next));
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (concat.empty())
|
||||
return nullptr;
|
||||
|
||||
file = FileSys::ConcatenateFiles(concat);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
|
||||
VirtualFile file;
|
||||
// Try all four modes of file storage:
|
||||
// (bit 1 = uppercase/lower, bit 0 = within a two-digit dir)
|
||||
// 00: /000000**/{:032X}.nca
|
||||
// 01: /{:032X}.nca
|
||||
// 10: /000000**/{:032x}.nca
|
||||
// 11: /{:032x}.nca
|
||||
for (u8 i = 0; i < 4; ++i) {
|
||||
const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0);
|
||||
file = OpenFileOrDirectoryConcat(dir, path);
|
||||
if (file != nullptr)
|
||||
return file;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
static boost::optional<NcaID> CheckMapForContentRecord(
|
||||
const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
|
||||
if (map.find(title_id) == map.end())
|
||||
return boost::none;
|
||||
|
||||
const auto& cnmt = map.at(title_id);
|
||||
|
||||
const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
|
||||
[type](const ContentRecord& rec) { return rec.type == type; });
|
||||
if (iter == cnmt.GetContentRecords().end())
|
||||
return boost::none;
|
||||
|
||||
return boost::make_optional(iter->nca_id);
|
||||
}
|
||||
|
||||
boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
|
||||
ContentRecordType type) const {
|
||||
if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end())
|
||||
return meta_id.at(title_id);
|
||||
|
||||
const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type);
|
||||
if (res1 != boost::none)
|
||||
return res1;
|
||||
return CheckMapForContentRecord(meta, title_id, type);
|
||||
}
|
||||
|
||||
std::vector<NcaID> RegisteredCache::AccumulateFiles() const {
|
||||
std::vector<NcaID> ids;
|
||||
for (const auto& d2_dir : dir->GetSubdirectories()) {
|
||||
if (FollowsNcaIdFormat(d2_dir->GetName())) {
|
||||
ids.push_back(HexStringToArray<0x10, true>(d2_dir->GetName().substr(0, 0x20)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!FollowsTwoDigitDirFormat(d2_dir->GetName()))
|
||||
continue;
|
||||
|
||||
for (const auto& nca_dir : d2_dir->GetSubdirectories()) {
|
||||
if (!FollowsNcaIdFormat(nca_dir->GetName()))
|
||||
continue;
|
||||
|
||||
ids.push_back(HexStringToArray<0x10, true>(nca_dir->GetName().substr(0, 0x20)));
|
||||
}
|
||||
|
||||
for (const auto& nca_file : d2_dir->GetFiles()) {
|
||||
if (!FollowsNcaIdFormat(nca_file->GetName()))
|
||||
continue;
|
||||
|
||||
ids.push_back(HexStringToArray<0x10, true>(nca_file->GetName().substr(0, 0x20)));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& d2_file : dir->GetFiles()) {
|
||||
if (FollowsNcaIdFormat(d2_file->GetName()))
|
||||
ids.push_back(HexStringToArray<0x10, true>(d2_file->GetName().substr(0, 0x20)));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
|
||||
for (const auto& id : ids) {
|
||||
const auto file = GetFileAtID(id);
|
||||
|
||||
if (file == nullptr)
|
||||
continue;
|
||||
const auto nca = std::make_shared<NCA>(parser(file, id));
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success ||
|
||||
nca->GetType() != NCAContentType::Meta) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto section0 = nca->GetSubdirectories()[0];
|
||||
|
||||
for (const auto& file : section0->GetFiles()) {
|
||||
if (file->GetExtension() != "cnmt")
|
||||
continue;
|
||||
|
||||
meta.insert_or_assign(nca->GetTitleId(), CNMT(file));
|
||||
meta_id.insert_or_assign(nca->GetTitleId(), id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisteredCache::AccumulateYuzuMeta() {
|
||||
const auto dir = this->dir->GetSubdirectory("yuzu_meta");
|
||||
if (dir == nullptr)
|
||||
return;
|
||||
|
||||
for (const auto& file : dir->GetFiles()) {
|
||||
if (file->GetExtension() != "cnmt")
|
||||
continue;
|
||||
|
||||
CNMT cnmt(file);
|
||||
yuzu_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt));
|
||||
}
|
||||
}
|
||||
|
||||
void RegisteredCache::Refresh() {
|
||||
if (dir == nullptr)
|
||||
return;
|
||||
const auto ids = AccumulateFiles();
|
||||
ProcessFiles(ids);
|
||||
AccumulateYuzuMeta();
|
||||
}
|
||||
|
||||
RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function)
|
||||
: dir(std::move(dir_)), parser(std::move(parsing_function)) {
|
||||
Refresh();
|
||||
}
|
||||
|
||||
bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
return GetEntryRaw(title_id, type) != nullptr;
|
||||
}
|
||||
|
||||
bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry) != nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
const auto id = GetNcaIDFromMetadata(title_id, type);
|
||||
if (id == boost::none)
|
||||
return nullptr;
|
||||
|
||||
return parser(GetFileAtID(id.get()), id.get());
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
return nullptr;
|
||||
return std::make_shared<NCA>(raw);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void RegisteredCache::IterateAllMetadata(
|
||||
std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc,
|
||||
std::function<bool(const CNMT&, const ContentRecord&)> filter) const {
|
||||
for (const auto& kv : meta) {
|
||||
const auto& cnmt = kv.second;
|
||||
if (filter(cnmt, EMPTY_META_CONTENT_RECORD))
|
||||
out.push_back(proc(cnmt, EMPTY_META_CONTENT_RECORD));
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
|
||||
out.push_back(proc(cnmt, rec));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& kv : yuzu_meta) {
|
||||
const auto& cnmt = kv.second;
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
|
||||
out.push_back(proc(cnmt, rec));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[](const CNMT& c, const ContentRecord& r) { return true; });
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
|
||||
boost::optional<u64> title_id) const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
|
||||
if (title_type != boost::none && title_type.get() != c.GetType())
|
||||
return false;
|
||||
if (record_type != boost::none && record_type.get() != r.type)
|
||||
return false;
|
||||
if (title_id != boost::none && title_id.get() != c.GetTitleID())
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) {
|
||||
const auto filename = fmt::format("{}.nca", HexArrayToString(id, false));
|
||||
const auto iter =
|
||||
std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(),
|
||||
[&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; });
|
||||
return iter == xci->GetNCAs().end() ? nullptr : *iter;
|
||||
}
|
||||
|
||||
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists,
|
||||
const VfsCopyFunction& copy) {
|
||||
const auto& ncas = xci->GetNCAs();
|
||||
const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) {
|
||||
return nca->GetType() == NCAContentType::Meta;
|
||||
});
|
||||
|
||||
if (meta_iter == ncas.end()) {
|
||||
LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and "
|
||||
"is therefore malformed. Double check your encryption keys.");
|
||||
return InstallResult::ErrorMetaFailed;
|
||||
}
|
||||
|
||||
// Install Metadata File
|
||||
const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32);
|
||||
const auto meta_id = HexStringToArray<16>(meta_id_raw);
|
||||
|
||||
const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id);
|
||||
if (res != InstallResult::Success)
|
||||
return res;
|
||||
|
||||
// Install all the other NCAs
|
||||
const auto section0 = (*meta_iter)->GetSubdirectories()[0];
|
||||
const auto cnmt_file = section0->GetFiles()[0];
|
||||
const CNMT cnmt(cnmt_file);
|
||||
for (const auto& record : cnmt.GetContentRecords()) {
|
||||
const auto nca = GetNCAFromXCIForID(xci, record.nca_id);
|
||||
if (nca == nullptr)
|
||||
return InstallResult::ErrorCopyFailed;
|
||||
const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id);
|
||||
if (res2 != InstallResult::Success)
|
||||
return res2;
|
||||
}
|
||||
|
||||
Refresh();
|
||||
return InstallResult::Success;
|
||||
}
|
||||
|
||||
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
|
||||
bool overwrite_if_exists, const VfsCopyFunction& copy) {
|
||||
CNMTHeader header{
|
||||
nca->GetTitleId(), ///< Title ID
|
||||
0, ///< Ignore/Default title version
|
||||
type, ///< Type
|
||||
{}, ///< Padding
|
||||
0x10, ///< Default table offset
|
||||
1, ///< 1 Content Entry
|
||||
0, ///< No Meta Entries
|
||||
{}, ///< Padding
|
||||
};
|
||||
OptionalHeader opt_header{0, 0};
|
||||
ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca->GetType()), {}};
|
||||
const auto& data = nca->GetBaseFile()->ReadBytes(0x100000);
|
||||
mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0);
|
||||
memcpy(&c_rec.nca_id, &c_rec.hash, 16);
|
||||
const CNMT new_cnmt(header, opt_header, {c_rec}, {});
|
||||
if (!RawInstallYuzuMeta(new_cnmt))
|
||||
return InstallResult::ErrorMetaFailed;
|
||||
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
|
||||
}
|
||||
|
||||
InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
|
||||
bool overwrite_if_exists,
|
||||
boost::optional<NcaID> override_id) {
|
||||
const auto in = nca->GetBaseFile();
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
|
||||
// Calculate NcaID
|
||||
// NOTE: Because computing the SHA256 of an entire NCA is quite expensive (especially if the
|
||||
// game is massive), we're going to cheat and only hash the first MB of the NCA.
|
||||
// Also, for XCIs the NcaID matters, so if the override id isn't none, use that.
|
||||
NcaID id{};
|
||||
if (override_id == boost::none) {
|
||||
const auto& data = in->ReadBytes(0x100000);
|
||||
mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
|
||||
memcpy(id.data(), hash.data(), 16);
|
||||
} else {
|
||||
id = override_id.get();
|
||||
}
|
||||
|
||||
std::string path = GetRelativePathFromNcaID(id, false, true);
|
||||
|
||||
if (GetFileAtID(id) != nullptr && !overwrite_if_exists) {
|
||||
LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping...");
|
||||
return InstallResult::ErrorAlreadyExists;
|
||||
}
|
||||
|
||||
if (GetFileAtID(id) != nullptr) {
|
||||
LOG_WARNING(Loader, "Overwriting existing NCA...");
|
||||
VirtualDir c_dir;
|
||||
{ c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); }
|
||||
c_dir->DeleteFile(FileUtil::GetFilename(path));
|
||||
}
|
||||
|
||||
auto out = dir->CreateFileRelative(path);
|
||||
if (out == nullptr)
|
||||
return InstallResult::ErrorCopyFailed;
|
||||
return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed;
|
||||
}
|
||||
|
||||
bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
|
||||
// Reasoning behind this method can be found in the comment for InstallEntry, NCA overload.
|
||||
const auto dir = this->dir->CreateDirectoryRelative("yuzu_meta");
|
||||
const auto filename = GetCNMTName(cnmt.GetType(), cnmt.GetTitleID());
|
||||
if (dir->GetFile(filename) == nullptr) {
|
||||
auto out = dir->CreateFile(filename);
|
||||
const auto buffer = cnmt.Serialize();
|
||||
out->Resize(buffer.size());
|
||||
out->WriteBytes(buffer);
|
||||
} else {
|
||||
auto out = dir->GetFile(filename);
|
||||
CNMT old_cnmt(out);
|
||||
// Returns true on change
|
||||
if (old_cnmt.UnionRecords(cnmt)) {
|
||||
out->Resize(0);
|
||||
const auto buffer = old_cnmt.Serialize();
|
||||
out->Resize(buffer.size());
|
||||
out->WriteBytes(buffer);
|
||||
}
|
||||
}
|
||||
Refresh();
|
||||
return std::find_if(yuzu_meta.begin(), yuzu_meta.end(),
|
||||
[&cnmt](const std::pair<u64, CNMT>& kv) {
|
||||
return kv.second.GetType() == cnmt.GetType() &&
|
||||
kv.second.GetTitleID() == cnmt.GetTitleID();
|
||||
}) != yuzu_meta.end();
|
||||
}
|
||||
} // namespace FileSys
|
||||
124
src/core/file_sys/registered_cache.h
Normal file
124
src/core/file_sys/registered_cache.h
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
class XCI;
|
||||
class CNMT;
|
||||
|
||||
using NcaID = std::array<u8, 0x10>;
|
||||
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
|
||||
using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>;
|
||||
|
||||
enum class InstallResult {
|
||||
Success,
|
||||
ErrorAlreadyExists,
|
||||
ErrorCopyFailed,
|
||||
ErrorMetaFailed,
|
||||
};
|
||||
|
||||
struct RegisteredCacheEntry {
|
||||
u64 title_id;
|
||||
ContentRecordType type;
|
||||
|
||||
std::string DebugInfo() const;
|
||||
};
|
||||
|
||||
// boost flat_map requires operator< for O(log(n)) lookups.
|
||||
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||
|
||||
/*
|
||||
* A class that catalogues NCAs in the registered directory structure.
|
||||
* Nintendo's registered format follows this structure:
|
||||
*
|
||||
* Root
|
||||
* | 000000XX <- XX is the ____ two digits of the NcaID
|
||||
* | <hash>.nca <- hash is the NcaID (first half of SHA256 over entire file) (folder)
|
||||
* | 00
|
||||
* | 01 <- Actual content split along 4GB boundaries. (optional)
|
||||
*
|
||||
* (This impl also supports substituting the nca dir for an nca file, as that's more convenient when
|
||||
* 4GB splitting can be ignored.)
|
||||
*/
|
||||
class RegisteredCache {
|
||||
public:
|
||||
// Parsing function defines the conversion from raw file to NCA. If there are other steps
|
||||
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
|
||||
// parsing function.
|
||||
explicit RegisteredCache(VirtualDir dir,
|
||||
RegisteredCacheParsingFunction parsing_function =
|
||||
[](const VirtualFile& file, const NcaID& id) { return file; });
|
||||
|
||||
void Refresh();
|
||||
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not boost::none, it will be filtered for from all entries.
|
||||
std::vector<RegisteredCacheEntry> ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type = boost::none,
|
||||
boost::optional<ContentRecordType> record_type = boost::none,
|
||||
boost::optional<u64> title_id = boost::none) const;
|
||||
|
||||
// Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there
|
||||
// is a meta NCA and all of them are accessible.
|
||||
InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
|
||||
// Due to the fact that we must use Meta-type NCAs to determine the existance of files, this
|
||||
// poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a
|
||||
// dir inside the NAND called 'yuzu_meta' and store the raw CNMT there.
|
||||
// TODO(DarkLordZach): Author real meta-type NCAs and install those.
|
||||
InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
|
||||
bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void IterateAllMetadata(std::vector<T>& out,
|
||||
std::function<T(const CNMT&, const ContentRecord&)> proc,
|
||||
std::function<bool(const CNMT&, const ContentRecord&)> filter) const;
|
||||
std::vector<NcaID> AccumulateFiles() const;
|
||||
void ProcessFiles(const std::vector<NcaID>& ids);
|
||||
void AccumulateYuzuMeta();
|
||||
boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetFileAtID(NcaID id) const;
|
||||
VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
|
||||
InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
|
||||
bool overwrite_if_exists,
|
||||
boost::optional<NcaID> override_id = boost::none);
|
||||
bool RawInstallYuzuMeta(const CNMT& cnmt);
|
||||
|
||||
VirtualDir dir;
|
||||
RegisteredCacheParsingFunction parser;
|
||||
// maps tid -> NcaID of meta
|
||||
boost::container::flat_map<u64, NcaID> meta_id;
|
||||
// maps tid -> meta
|
||||
boost::container::flat_map<u64, CNMT> meta;
|
||||
// maps tid -> meta for CNMT in yuzu_meta
|
||||
boost::container::flat_map<u64, CNMT> yuzu_meta;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -65,7 +65,7 @@ void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 t
|
||||
auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset);
|
||||
|
||||
parent->AddFile(std::make_shared<OffsetVfsFile>(
|
||||
file, entry.first.size, entry.first.offset + data_offset, entry.second, parent));
|
||||
file, entry.first.size, entry.first.offset + data_offset, entry.second));
|
||||
|
||||
if (entry.first.sibling == ROMFS_ENTRY_EMPTY)
|
||||
break;
|
||||
@@ -79,7 +79,7 @@ void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, s
|
||||
while (true) {
|
||||
auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset);
|
||||
auto current = std::make_shared<VectorVfsDirectory>(
|
||||
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parent, entry.second);
|
||||
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, entry.second);
|
||||
|
||||
if (entry.first.child_file != ROMFS_ENTRY_EMPTY) {
|
||||
ProcessFile(file, file_offset, data_offset, entry.first.child_file, current);
|
||||
@@ -108,9 +108,9 @@ VirtualDir ExtractRomFS(VirtualFile file) {
|
||||
const u64 file_offset = header.file_meta.offset;
|
||||
const u64 dir_offset = header.directory_meta.offset + 4;
|
||||
|
||||
const auto root =
|
||||
auto root =
|
||||
std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{},
|
||||
file->GetContainingDirectory(), file->GetName());
|
||||
file->GetName(), file->GetContainingDirectory());
|
||||
|
||||
ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root);
|
||||
|
||||
|
||||
94
src/core/file_sys/vfs_concat.cpp
Normal file
94
src/core/file_sys/vfs_concat.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) {
|
||||
if (files.empty())
|
||||
return nullptr;
|
||||
if (files.size() == 1)
|
||||
return files[0];
|
||||
|
||||
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
|
||||
}
|
||||
|
||||
ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name)
|
||||
: name(std::move(name)) {
|
||||
size_t next_offset = 0;
|
||||
for (const auto& file : files_) {
|
||||
files[next_offset] = file;
|
||||
next_offset += file->GetSize();
|
||||
}
|
||||
}
|
||||
|
||||
std::string ConcatenatedVfsFile::GetName() const {
|
||||
if (files.empty())
|
||||
return "";
|
||||
if (!name.empty())
|
||||
return name;
|
||||
return files.begin()->second->GetName();
|
||||
}
|
||||
|
||||
size_t ConcatenatedVfsFile::GetSize() const {
|
||||
if (files.empty())
|
||||
return 0;
|
||||
return files.rbegin()->first + files.rbegin()->second->GetSize();
|
||||
}
|
||||
|
||||
bool ConcatenatedVfsFile::Resize(size_t new_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const {
|
||||
if (files.empty())
|
||||
return nullptr;
|
||||
return files.begin()->second->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool ConcatenatedVfsFile::IsWritable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ConcatenatedVfsFile::IsReadable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const {
|
||||
auto entry = files.end();
|
||||
for (auto iter = files.begin(); iter != files.end(); ++iter) {
|
||||
if (iter->first > offset) {
|
||||
entry = --iter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the entry should be the last one. The loop above will make it end().
|
||||
if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize())
|
||||
--entry;
|
||||
|
||||
if (entry == files.end())
|
||||
return 0;
|
||||
|
||||
const auto remaining = entry->second->GetSize() + offset - entry->first;
|
||||
if (length > remaining) {
|
||||
return entry->second->Read(data, remaining, offset - entry->first) +
|
||||
Read(data + remaining, length - remaining, offset + remaining);
|
||||
}
|
||||
|
||||
return entry->second->Read(data, length, offset - entry->first);
|
||||
}
|
||||
|
||||
size_t ConcatenatedVfsFile::Write(const u8* data, size_t length, size_t offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ConcatenatedVfsFile::Rename(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
41
src/core/file_sys/vfs_concat.h
Normal file
41
src/core/file_sys/vfs_concat.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
|
||||
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = "");
|
||||
|
||||
// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
|
||||
// read-only.
|
||||
class ConcatenatedVfsFile : public VfsFile {
|
||||
friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
|
||||
|
||||
ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
|
||||
|
||||
public:
|
||||
std::string GetName() const override;
|
||||
size_t GetSize() const override;
|
||||
bool Resize(size_t new_size) override;
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
size_t Read(u8* data, size_t length, size_t offset) const override;
|
||||
size_t Write(const u8* data, size_t length, size_t offset) override;
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
private:
|
||||
// Maps starting offset to file -- more efficient.
|
||||
boost::container::flat_map<u64, VirtualFile> files;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -83,8 +83,12 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
|
||||
|
||||
VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
|
||||
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
if (!FileUtil::Exists(path) && !FileUtil::CreateEmptyFile(path))
|
||||
return nullptr;
|
||||
const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
|
||||
if (!FileUtil::Exists(path)) {
|
||||
FileUtil::CreateFullPath(path_fwd);
|
||||
if (!FileUtil::CreateEmptyFile(path))
|
||||
return nullptr;
|
||||
}
|
||||
return OpenFile(path, perms);
|
||||
}
|
||||
|
||||
@@ -140,8 +144,12 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms)
|
||||
|
||||
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
|
||||
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
if (!FileUtil::Exists(path) && !FileUtil::CreateDir(path))
|
||||
return nullptr;
|
||||
const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
|
||||
if (!FileUtil::Exists(path)) {
|
||||
FileUtil::CreateFullPath(path_fwd);
|
||||
if (!FileUtil::CreateDir(path))
|
||||
return nullptr;
|
||||
}
|
||||
// Cannot use make_shared as RealVfsDirectory constructor is private
|
||||
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
|
||||
}
|
||||
@@ -306,14 +314,14 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
|
||||
|
||||
std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
if (!FileUtil::Exists(full_path))
|
||||
if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path))
|
||||
return nullptr;
|
||||
return base.OpenFile(full_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
|
||||
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
if (!FileUtil::Exists(full_path))
|
||||
if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path))
|
||||
return nullptr;
|
||||
return base.OpenDirectory(full_path, perms);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
namespace FileSys {
|
||||
VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
|
||||
std::vector<VirtualDir> dirs_, VirtualDir parent_,
|
||||
std::string name_)
|
||||
std::vector<VirtualDir> dirs_, std::string name_,
|
||||
VirtualDir parent_)
|
||||
: files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)),
|
||||
name(std::move(name_)) {}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace FileSys {
|
||||
class VectorVfsDirectory : public VfsDirectory {
|
||||
public:
|
||||
explicit VectorVfsDirectory(std::vector<VirtualFile> files = {},
|
||||
std::vector<VirtualDir> dirs = {}, VirtualDir parent = nullptr,
|
||||
std::string name = "");
|
||||
std::vector<VirtualDir> dirs = {}, std::string name = "",
|
||||
VirtualDir parent = nullptr);
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
|
||||
@@ -34,9 +34,9 @@ class EmuWindow {
|
||||
public:
|
||||
/// Data structure to store emuwindow configuration
|
||||
struct WindowConfig {
|
||||
bool fullscreen;
|
||||
int res_width;
|
||||
int res_height;
|
||||
bool fullscreen = false;
|
||||
int res_width = 0;
|
||||
int res_height = 0;
|
||||
std::pair<unsigned, unsigned> min_client_area_size;
|
||||
};
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
|
||||
// Handle scenario when ConvertToDomain command was issued, as we must do the conversion at the
|
||||
// end of the command such that only commands following this one are handled as domains
|
||||
if (convert_to_domain) {
|
||||
ASSERT_MSG(domain_request_handlers.empty(), "already a domain");
|
||||
ASSERT_MSG(IsSession(), "ServerSession is already a domain instance.");
|
||||
domain_request_handlers = {hle_handler};
|
||||
convert_to_domain = false;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,12 @@ public:
|
||||
|
||||
/// Returns true if the session has been converted to a domain, otherwise False
|
||||
bool IsDomain() const {
|
||||
return !domain_request_handlers.empty();
|
||||
return !IsSession();
|
||||
}
|
||||
|
||||
/// Returns true if this session has not been converted to a domain, otherwise false.
|
||||
bool IsSession() const {
|
||||
return domain_request_handlers.empty();
|
||||
}
|
||||
|
||||
/// Converts the session to a domain at the end of the current command
|
||||
|
||||
@@ -250,8 +250,11 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
||||
}
|
||||
|
||||
/// Break program execution
|
||||
static void Break(u64 unk_0, u64 unk_1, u64 unk_2) {
|
||||
LOG_CRITICAL(Debug_Emulated, "Emulated program broke execution!");
|
||||
static void Break(u64 reason, u64 info1, u64 info2) {
|
||||
LOG_CRITICAL(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
reason, info1, info2);
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
|
||||
}
|
||||
|
||||
void Thread::CancelWakeupTimer() {
|
||||
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
|
||||
CoreTiming::UnscheduleEventThreadsafe(ThreadWakeupEventType, callback_handle);
|
||||
}
|
||||
|
||||
static boost::optional<s32> GetNextProcessorId(u64 mask) {
|
||||
|
||||
@@ -226,6 +226,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
|
||||
static std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
|
||||
static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
|
||||
static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
|
||||
static std::unique_ptr<FileSys::BISFactory> bis_factory;
|
||||
|
||||
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
|
||||
ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS");
|
||||
@@ -248,6 +249,13 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
|
||||
ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS");
|
||||
bis_factory = std::move(factory);
|
||||
LOG_DEBUG(Service_FS, "Registred BIS");
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) {
|
||||
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id);
|
||||
|
||||
@@ -281,6 +289,14 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
|
||||
return sdmc_factory->Open();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
|
||||
return bis_factory->GetSystemNANDContents();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
|
||||
return bis_factory->GetUserNANDContents();
|
||||
}
|
||||
|
||||
void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
|
||||
romfs_factory = nullptr;
|
||||
save_data_factory = nullptr;
|
||||
@@ -291,6 +307,9 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
|
||||
auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
|
||||
FileSys::Mode::ReadWrite);
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory);
|
||||
|
||||
auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
|
||||
save_data_factory = std::move(savedata);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
#include "core/file_sys/directory.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
@@ -24,16 +25,15 @@ namespace FileSystem {
|
||||
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
|
||||
ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
|
||||
ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
|
||||
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
|
||||
|
||||
// TODO(DarkLordZach): BIS Filesystem
|
||||
// ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id);
|
||||
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
|
||||
FileSys::SaveDataDescriptor save_struct);
|
||||
ResultVal<FileSys::VirtualDir> OpenSDMC();
|
||||
|
||||
// TODO(DarkLordZach): BIS Filesystem
|
||||
// ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS();
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
|
||||
|
||||
/// Registers all Filesystem services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);
|
||||
|
||||
@@ -92,7 +92,11 @@ private:
|
||||
|
||||
// Parse out log metadata
|
||||
u32 line{};
|
||||
std::string message, filename, function;
|
||||
std::string module;
|
||||
std::string message;
|
||||
std::string filename;
|
||||
std::string function;
|
||||
std::string thread;
|
||||
while (addr < end_addr) {
|
||||
const Field field{static_cast<Field>(Memory::Read8(addr++))};
|
||||
const size_t length{Memory::Read8(addr++)};
|
||||
@@ -102,6 +106,8 @@ private:
|
||||
}
|
||||
|
||||
switch (field) {
|
||||
case Field::Skip:
|
||||
break;
|
||||
case Field::Message:
|
||||
message = Memory::ReadCString(addr, length);
|
||||
break;
|
||||
@@ -114,6 +120,12 @@ private:
|
||||
case Field::Function:
|
||||
function = Memory::ReadCString(addr, length);
|
||||
break;
|
||||
case Field::Module:
|
||||
module = Memory::ReadCString(addr, length);
|
||||
break;
|
||||
case Field::Thread:
|
||||
thread = Memory::ReadCString(addr, length);
|
||||
break;
|
||||
}
|
||||
|
||||
addr += length;
|
||||
@@ -128,12 +140,18 @@ private:
|
||||
if (!filename.empty()) {
|
||||
log_stream << filename << ':';
|
||||
}
|
||||
if (!module.empty()) {
|
||||
log_stream << module << ':';
|
||||
}
|
||||
if (!function.empty()) {
|
||||
log_stream << function << ':';
|
||||
}
|
||||
if (line) {
|
||||
log_stream << std::to_string(line) << ':';
|
||||
}
|
||||
if (!thread.empty()) {
|
||||
log_stream << thread << ':';
|
||||
}
|
||||
if (log_stream.str().length() > 0 && log_stream.str().back() == ':') {
|
||||
log_stream << ' ';
|
||||
}
|
||||
@@ -142,7 +160,7 @@ private:
|
||||
if (header.IsTailLog()) {
|
||||
switch (header.severity) {
|
||||
case MessageHeader::Severity::Trace:
|
||||
LOG_TRACE(Debug_Emulated, "{}", log_stream.str());
|
||||
LOG_DEBUG(Debug_Emulated, "{}", log_stream.str());
|
||||
break;
|
||||
case MessageHeader::Severity::Info:
|
||||
LOG_INFO(Debug_Emulated, "{}", log_stream.str());
|
||||
|
||||
@@ -9,42 +9,63 @@
|
||||
|
||||
namespace Service::MM {
|
||||
|
||||
class MM_U final : public ServiceFramework<MM_U> {
|
||||
public:
|
||||
explicit MM_U() : ServiceFramework{"mm:u"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &MM_U::Initialize, "InitializeOld"},
|
||||
{1, &MM_U::Finalize, "FinalizeOld"},
|
||||
{2, &MM_U::SetAndWait, "SetAndWaitOld"},
|
||||
{3, &MM_U::Get, "GetOld"},
|
||||
{4, &MM_U::Initialize, "Initialize"},
|
||||
{5, &MM_U::Finalize, "Finalize"},
|
||||
{6, &MM_U::SetAndWait, "SetAndWait"},
|
||||
{7, &MM_U::Get, "Get"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void Initialize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Finalize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void SetAndWait(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
min = rp.Pop<u32>();
|
||||
max = rp.Pop<u32>();
|
||||
current = min;
|
||||
|
||||
LOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Get(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(current);
|
||||
}
|
||||
|
||||
u32 min{0};
|
||||
u32 max{0};
|
||||
u32 current{0};
|
||||
};
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
std::make_shared<MM_U>()->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
void MM_U::Initialize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void MM_U::SetAndWait(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
min = rp.Pop<u32>();
|
||||
max = rp.Pop<u32>();
|
||||
current = min;
|
||||
|
||||
LOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void MM_U::Get(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(current);
|
||||
}
|
||||
|
||||
MM_U::MM_U() : ServiceFramework("mm:u") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "InitializeOld"}, {1, nullptr, "FinalizeOld"},
|
||||
{2, nullptr, "SetAndWaitOld"}, {3, nullptr, "GetOld"},
|
||||
{4, &MM_U::Initialize, "Initialize"}, {5, nullptr, "Finalize"},
|
||||
{6, &MM_U::SetAndWait, "SetAndWait"}, {7, &MM_U::Get, "Get"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace Service::MM
|
||||
|
||||
@@ -8,21 +8,6 @@
|
||||
|
||||
namespace Service::MM {
|
||||
|
||||
class MM_U final : public ServiceFramework<MM_U> {
|
||||
public:
|
||||
MM_U();
|
||||
~MM_U() = default;
|
||||
|
||||
private:
|
||||
void Initialize(Kernel::HLERequestContext& ctx);
|
||||
void SetAndWait(Kernel::HLERequestContext& ctx);
|
||||
void Get(Kernel::HLERequestContext& ctx);
|
||||
|
||||
u32 min{0};
|
||||
u32 max{0};
|
||||
u32 current{0};
|
||||
};
|
||||
|
||||
/// Registers all MM services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
namespace Service::SM {
|
||||
|
||||
void Controller::ConvertSessionToDomain(Kernel::HLERequestContext& ctx) {
|
||||
ASSERT_MSG(!ctx.Session()->IsDomain(), "session is alread a domain");
|
||||
ASSERT_MSG(ctx.Session()->IsSession(), "Session is already a domain");
|
||||
ctx.Session()->ConvertToDomain();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
@@ -41,7 +41,7 @@ void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) {
|
||||
void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0x500);
|
||||
rb.Push<u16>(0x500);
|
||||
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
}
|
||||
|
||||
@@ -118,7 +118,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
|
||||
|
||||
process->program_id = metadata.GetTitleID();
|
||||
process->svc_access_mask.set();
|
||||
process->address_mappings = default_address_mappings;
|
||||
process->resource_limit =
|
||||
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
|
||||
process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(),
|
||||
|
||||
@@ -398,7 +398,6 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
|
||||
process->LoadModule(codeset, codeset->entrypoint);
|
||||
process->svc_access_mask.set();
|
||||
process->address_mappings = default_address_mappings;
|
||||
|
||||
// Attach the default resource limit (APPLICATION) to the process
|
||||
process->resource_limit =
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
@@ -17,12 +18,6 @@
|
||||
|
||||
namespace Loader {
|
||||
|
||||
const std::initializer_list<Kernel::AddressMapping> default_address_mappings = {
|
||||
{0x1FF50000, 0x8000, true}, // part of DSP RAM
|
||||
{0x1FF70000, 0x8000, true}, // part of DSP RAM
|
||||
{0x1F000000, 0x600000, false}, // entire VRAM
|
||||
};
|
||||
|
||||
FileType IdentifyFile(FileSys::VirtualFile file) {
|
||||
FileType type;
|
||||
|
||||
@@ -46,6 +41,8 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
|
||||
FileType GuessFromFilename(const std::string& name) {
|
||||
if (name == "main")
|
||||
return FileType::DeconstructedRomDirectory;
|
||||
if (name == "00")
|
||||
return FileType::NCA;
|
||||
|
||||
const std::string extension =
|
||||
Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name)));
|
||||
@@ -125,14 +122,9 @@ constexpr std::array<const char*, 36> RESULT_MESSAGES{
|
||||
"There is no control data available.",
|
||||
};
|
||||
|
||||
std::string GetMessageForResultStatus(ResultStatus status) {
|
||||
return GetMessageForResultStatus(static_cast<u16>(status));
|
||||
}
|
||||
|
||||
std::string GetMessageForResultStatus(u16 status) {
|
||||
if (status >= 36)
|
||||
return "";
|
||||
return RESULT_MESSAGES[status];
|
||||
std::ostream& operator<<(std::ostream& os, ResultStatus status) {
|
||||
os << RESULT_MESSAGES.at(static_cast<size_t>(status));
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -95,8 +95,7 @@ enum class ResultStatus : u16 {
|
||||
ErrorNoControl,
|
||||
};
|
||||
|
||||
std::string GetMessageForResultStatus(ResultStatus status);
|
||||
std::string GetMessageForResultStatus(u16 status);
|
||||
std::ostream& operator<<(std::ostream& os, ResultStatus status);
|
||||
|
||||
/// Interface for loading an application
|
||||
class AppLoader : NonCopyable {
|
||||
@@ -207,12 +206,6 @@ protected:
|
||||
bool is_loaded = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Common address mappings found in most games, used for binary formats that don't have this
|
||||
* information.
|
||||
*/
|
||||
extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings;
|
||||
|
||||
/**
|
||||
* Identifies a bootable file and return a suitable loader
|
||||
* @param file The bootable file
|
||||
|
||||
@@ -186,7 +186,6 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
}
|
||||
|
||||
process->svc_access_mask.set();
|
||||
process->address_mappings = default_address_mappings;
|
||||
process->resource_limit =
|
||||
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
|
||||
process->Run(base_addr, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
|
||||
|
||||
@@ -152,7 +152,6 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), Memory::PROCESS_IMAGE_VADDR);
|
||||
|
||||
process->svc_access_mask.set();
|
||||
process->address_mappings = default_address_mappings;
|
||||
process->resource_limit =
|
||||
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
|
||||
process->Run(Memory::PROCESS_IMAGE_VADDR, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
|
||||
|
||||
@@ -648,11 +648,11 @@ std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(
|
||||
|
||||
if (used_buffer.IsIndirect()) {
|
||||
// Buffer is accessed indirectly, so upload the entire thing
|
||||
size = buffer.size * sizeof(float);
|
||||
size = buffer.size;
|
||||
|
||||
if (size > MaxConstbufferSize) {
|
||||
LOG_ERROR(HW_GPU, "indirect constbuffer size {} exceeds maximum {}", size,
|
||||
MaxConstbufferSize);
|
||||
LOG_CRITICAL(HW_GPU, "indirect constbuffer size {} exceeds maximum {}", size,
|
||||
MaxConstbufferSize);
|
||||
size = MaxConstbufferSize;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -94,11 +94,11 @@ struct FormatTuple {
|
||||
static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U
|
||||
{GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S
|
||||
{GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5
|
||||
{GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U
|
||||
{GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm,
|
||||
false}, // A2B10G10R10
|
||||
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5
|
||||
{GL_R8, GL_RED, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // R8
|
||||
false}, // A2B10G10R10U
|
||||
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5U
|
||||
{GL_R8, GL_RED, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // R8U
|
||||
{GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // R8UI
|
||||
{GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, ComponentType::Float, false}, // RGBA16F
|
||||
{GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RGBA16U
|
||||
@@ -119,13 +119,14 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
|
||||
{GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
|
||||
true}, // BC7U
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4
|
||||
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // G8R8
|
||||
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // G8R8U
|
||||
{GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // G8R8S
|
||||
{GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8
|
||||
{GL_RGBA32F, GL_RGBA, GL_FLOAT, ComponentType::Float, false}, // RGBA32F
|
||||
{GL_RG32F, GL_RG, GL_FLOAT, ComponentType::Float, false}, // RG32F
|
||||
{GL_R32F, GL_RED, GL_FLOAT, ComponentType::Float, false}, // R32F
|
||||
{GL_R16F, GL_RED, GL_HALF_FLOAT, ComponentType::Float, false}, // R16F
|
||||
{GL_R16, GL_RED, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // R16UNORM
|
||||
{GL_R16, GL_RED, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // R16U
|
||||
{GL_R16_SNORM, GL_RED, GL_SHORT, ComponentType::SNorm, false}, // R16S
|
||||
{GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // R16UI
|
||||
{GL_R16I, GL_RED_INTEGER, GL_SHORT, ComponentType::SInt, false}, // R16I
|
||||
@@ -242,10 +243,10 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
|
||||
// clang-format off
|
||||
MortonCopy<true, PixelFormat::ABGR8U>,
|
||||
MortonCopy<true, PixelFormat::ABGR8S>,
|
||||
MortonCopy<true, PixelFormat::B5G6R5>,
|
||||
MortonCopy<true, PixelFormat::A2B10G10R10>,
|
||||
MortonCopy<true, PixelFormat::A1B5G5R5>,
|
||||
MortonCopy<true, PixelFormat::R8>,
|
||||
MortonCopy<true, PixelFormat::B5G6R5U>,
|
||||
MortonCopy<true, PixelFormat::A2B10G10R10U>,
|
||||
MortonCopy<true, PixelFormat::A1B5G5R5U>,
|
||||
MortonCopy<true, PixelFormat::R8U>,
|
||||
MortonCopy<true, PixelFormat::R8UI>,
|
||||
MortonCopy<true, PixelFormat::RGBA16F>,
|
||||
MortonCopy<true, PixelFormat::RGBA16U>,
|
||||
@@ -260,13 +261,14 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
|
||||
MortonCopy<true, PixelFormat::DXN2SNORM>,
|
||||
MortonCopy<true, PixelFormat::BC7U>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
|
||||
MortonCopy<true, PixelFormat::G8R8>,
|
||||
MortonCopy<true, PixelFormat::G8R8U>,
|
||||
MortonCopy<true, PixelFormat::G8R8S>,
|
||||
MortonCopy<true, PixelFormat::BGRA8>,
|
||||
MortonCopy<true, PixelFormat::RGBA32F>,
|
||||
MortonCopy<true, PixelFormat::RG32F>,
|
||||
MortonCopy<true, PixelFormat::R32F>,
|
||||
MortonCopy<true, PixelFormat::R16F>,
|
||||
MortonCopy<true, PixelFormat::R16UNORM>,
|
||||
MortonCopy<true, PixelFormat::R16U>,
|
||||
MortonCopy<true, PixelFormat::R16S>,
|
||||
MortonCopy<true, PixelFormat::R16UI>,
|
||||
MortonCopy<true, PixelFormat::R16I>,
|
||||
@@ -295,10 +297,10 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
|
||||
// clang-format off
|
||||
MortonCopy<false, PixelFormat::ABGR8U>,
|
||||
MortonCopy<false, PixelFormat::ABGR8S>,
|
||||
MortonCopy<false, PixelFormat::B5G6R5>,
|
||||
MortonCopy<false, PixelFormat::A2B10G10R10>,
|
||||
MortonCopy<false, PixelFormat::A1B5G5R5>,
|
||||
MortonCopy<false, PixelFormat::R8>,
|
||||
MortonCopy<false, PixelFormat::B5G6R5U>,
|
||||
MortonCopy<false, PixelFormat::A2B10G10R10U>,
|
||||
MortonCopy<false, PixelFormat::A1B5G5R5U>,
|
||||
MortonCopy<false, PixelFormat::R8U>,
|
||||
MortonCopy<false, PixelFormat::R8UI>,
|
||||
MortonCopy<false, PixelFormat::RGBA16F>,
|
||||
MortonCopy<false, PixelFormat::RGBA16U>,
|
||||
@@ -315,13 +317,14 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::G8R8>,
|
||||
MortonCopy<false, PixelFormat::G8R8U>,
|
||||
MortonCopy<false, PixelFormat::G8R8S>,
|
||||
MortonCopy<false, PixelFormat::BGRA8>,
|
||||
MortonCopy<false, PixelFormat::RGBA32F>,
|
||||
MortonCopy<false, PixelFormat::RG32F>,
|
||||
MortonCopy<false, PixelFormat::R32F>,
|
||||
MortonCopy<false, PixelFormat::R16F>,
|
||||
MortonCopy<false, PixelFormat::R16UNORM>,
|
||||
MortonCopy<false, PixelFormat::R16U>,
|
||||
MortonCopy<false, PixelFormat::R16S>,
|
||||
MortonCopy<false, PixelFormat::R16UI>,
|
||||
MortonCopy<false, PixelFormat::R16I>,
|
||||
@@ -461,7 +464,7 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
}
|
||||
|
||||
static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
const auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8)};
|
||||
const auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)};
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
const size_t offset{bpp * (y * width + x)};
|
||||
@@ -493,7 +496,8 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
|
||||
ConvertS8Z24ToZ24S8(data, width, height);
|
||||
break;
|
||||
|
||||
case PixelFormat::G8R8:
|
||||
case PixelFormat::G8R8U:
|
||||
case PixelFormat::G8R8S:
|
||||
// Convert the G8R8 color format to R8G8, as OpenGL does not support G8R8.
|
||||
ConvertG8R8ToR8G8(data, width, height);
|
||||
break;
|
||||
|
||||
@@ -25,10 +25,10 @@ struct SurfaceParams {
|
||||
enum class PixelFormat {
|
||||
ABGR8U = 0,
|
||||
ABGR8S = 1,
|
||||
B5G6R5 = 2,
|
||||
A2B10G10R10 = 3,
|
||||
A1B5G5R5 = 4,
|
||||
R8 = 5,
|
||||
B5G6R5U = 2,
|
||||
A2B10G10R10U = 3,
|
||||
A1B5G5R5U = 4,
|
||||
R8U = 5,
|
||||
R8UI = 6,
|
||||
RGBA16F = 7,
|
||||
RGBA16U = 8,
|
||||
@@ -43,36 +43,37 @@ struct SurfaceParams {
|
||||
DXN2SNORM = 17,
|
||||
BC7U = 18,
|
||||
ASTC_2D_4X4 = 19,
|
||||
G8R8 = 20,
|
||||
BGRA8 = 21,
|
||||
RGBA32F = 22,
|
||||
RG32F = 23,
|
||||
R32F = 24,
|
||||
R16F = 25,
|
||||
R16UNORM = 26,
|
||||
R16S = 27,
|
||||
R16UI = 28,
|
||||
R16I = 29,
|
||||
RG16 = 30,
|
||||
RG16F = 31,
|
||||
RG16UI = 32,
|
||||
RG16I = 33,
|
||||
RG16S = 34,
|
||||
RGB32F = 35,
|
||||
SRGBA8 = 36,
|
||||
RG8U = 37,
|
||||
RG8S = 38,
|
||||
RG32UI = 39,
|
||||
R32UI = 40,
|
||||
G8R8U = 20,
|
||||
G8R8S = 21,
|
||||
BGRA8 = 22,
|
||||
RGBA32F = 23,
|
||||
RG32F = 24,
|
||||
R32F = 25,
|
||||
R16F = 26,
|
||||
R16U = 27,
|
||||
R16S = 28,
|
||||
R16UI = 29,
|
||||
R16I = 30,
|
||||
RG16 = 31,
|
||||
RG16F = 32,
|
||||
RG16UI = 33,
|
||||
RG16I = 34,
|
||||
RG16S = 35,
|
||||
RGB32F = 36,
|
||||
SRGBA8 = 37,
|
||||
RG8U = 38,
|
||||
RG8S = 39,
|
||||
RG32UI = 40,
|
||||
R32UI = 41,
|
||||
|
||||
MaxColorFormat,
|
||||
|
||||
// DepthStencil formats
|
||||
Z24S8 = 41,
|
||||
S8Z24 = 42,
|
||||
Z32F = 43,
|
||||
Z16 = 44,
|
||||
Z32FS8 = 45,
|
||||
Z24S8 = 42,
|
||||
S8Z24 = 43,
|
||||
Z32F = 44,
|
||||
Z16 = 45,
|
||||
Z32FS8 = 46,
|
||||
|
||||
MaxDepthStencilFormat,
|
||||
|
||||
@@ -112,10 +113,10 @@ struct SurfaceParams {
|
||||
constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{
|
||||
1, // ABGR8U
|
||||
1, // ABGR8S
|
||||
1, // B5G6R5
|
||||
1, // A2B10G10R10
|
||||
1, // A1B5G5R5
|
||||
1, // R8
|
||||
1, // B5G6R5U
|
||||
1, // A2B10G10R10U
|
||||
1, // A1B5G5R5U
|
||||
1, // R8U
|
||||
1, // R8UI
|
||||
1, // RGBA16F
|
||||
1, // RGBA16U
|
||||
@@ -130,13 +131,14 @@ struct SurfaceParams {
|
||||
4, // DXN2SNORM
|
||||
4, // BC7U
|
||||
4, // ASTC_2D_4X4
|
||||
1, // G8R8
|
||||
1, // G8R8U
|
||||
1, // G8R8S
|
||||
1, // BGRA8
|
||||
1, // RGBA32F
|
||||
1, // RG32F
|
||||
1, // R32F
|
||||
1, // R16F
|
||||
1, // R16UNORM
|
||||
1, // R16U
|
||||
1, // R16S
|
||||
1, // R16UI
|
||||
1, // R16I
|
||||
@@ -169,10 +171,10 @@ struct SurfaceParams {
|
||||
constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
|
||||
32, // ABGR8U
|
||||
32, // ABGR8S
|
||||
16, // B5G6R5
|
||||
32, // A2B10G10R10
|
||||
16, // A1B5G5R5
|
||||
8, // R8
|
||||
16, // B5G6R5U
|
||||
32, // A2B10G10R10U
|
||||
16, // A1B5G5R5U
|
||||
8, // R8U
|
||||
8, // R8UI
|
||||
64, // RGBA16F
|
||||
64, // RGBA16U
|
||||
@@ -187,13 +189,14 @@ struct SurfaceParams {
|
||||
128, // DXN2SNORM
|
||||
128, // BC7U
|
||||
32, // ASTC_2D_4X4
|
||||
16, // G8R8
|
||||
16, // G8R8U
|
||||
16, // G8R8S
|
||||
32, // BGRA8
|
||||
128, // RGBA32F
|
||||
64, // RG32F
|
||||
32, // R32F
|
||||
16, // R16F
|
||||
16, // R16UNORM
|
||||
16, // R16U
|
||||
16, // R16S
|
||||
16, // R16UI
|
||||
16, // R16I
|
||||
@@ -253,7 +256,7 @@ struct SurfaceParams {
|
||||
case Tegra::RenderTargetFormat::BGRA8_UNORM:
|
||||
return PixelFormat::BGRA8;
|
||||
case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
|
||||
return PixelFormat::A2B10G10R10;
|
||||
return PixelFormat::A2B10G10R10U;
|
||||
case Tegra::RenderTargetFormat::RGBA16_FLOAT:
|
||||
return PixelFormat::RGBA16F;
|
||||
case Tegra::RenderTargetFormat::RGBA16_UNORM:
|
||||
@@ -267,11 +270,11 @@ struct SurfaceParams {
|
||||
case Tegra::RenderTargetFormat::R11G11B10_FLOAT:
|
||||
return PixelFormat::R11FG11FB10F;
|
||||
case Tegra::RenderTargetFormat::B5G6R5_UNORM:
|
||||
return PixelFormat::B5G6R5;
|
||||
return PixelFormat::B5G6R5U;
|
||||
case Tegra::RenderTargetFormat::RGBA32_UINT:
|
||||
return PixelFormat::RGBA32UI;
|
||||
case Tegra::RenderTargetFormat::R8_UNORM:
|
||||
return PixelFormat::R8;
|
||||
return PixelFormat::R8U;
|
||||
case Tegra::RenderTargetFormat::R8_UINT:
|
||||
return PixelFormat::R8UI;
|
||||
case Tegra::RenderTargetFormat::RG16_FLOAT:
|
||||
@@ -291,7 +294,7 @@ struct SurfaceParams {
|
||||
case Tegra::RenderTargetFormat::R16_FLOAT:
|
||||
return PixelFormat::R16F;
|
||||
case Tegra::RenderTargetFormat::R16_UNORM:
|
||||
return PixelFormat::R16UNORM;
|
||||
return PixelFormat::R16U;
|
||||
case Tegra::RenderTargetFormat::R16_SNORM:
|
||||
return PixelFormat::R16S;
|
||||
case Tegra::RenderTargetFormat::R16_UINT:
|
||||
@@ -325,15 +328,33 @@ struct SurfaceParams {
|
||||
static_cast<u32>(component_type));
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::B5G6R5:
|
||||
return PixelFormat::B5G6R5;
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::UNORM:
|
||||
return PixelFormat::B5G6R5U;
|
||||
}
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
|
||||
static_cast<u32>(component_type));
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::A2B10G10R10:
|
||||
return PixelFormat::A2B10G10R10;
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::UNORM:
|
||||
return PixelFormat::A2B10G10R10U;
|
||||
}
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
|
||||
static_cast<u32>(component_type));
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::A1B5G5R5:
|
||||
return PixelFormat::A1B5G5R5;
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::UNORM:
|
||||
return PixelFormat::A1B5G5R5U;
|
||||
}
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
|
||||
static_cast<u32>(component_type));
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::R8:
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::UNORM:
|
||||
return PixelFormat::R8;
|
||||
return PixelFormat::R8U;
|
||||
case Tegra::Texture::ComponentType::UINT:
|
||||
return PixelFormat::R8UI;
|
||||
}
|
||||
@@ -341,11 +362,33 @@ struct SurfaceParams {
|
||||
static_cast<u32>(component_type));
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::G8R8:
|
||||
return PixelFormat::G8R8;
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::UNORM:
|
||||
return PixelFormat::G8R8U;
|
||||
case Tegra::Texture::ComponentType::SNORM:
|
||||
return PixelFormat::G8R8S;
|
||||
}
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
|
||||
static_cast<u32>(component_type));
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::R16_G16_B16_A16:
|
||||
return PixelFormat::RGBA16F;
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::UNORM:
|
||||
return PixelFormat::RGBA16U;
|
||||
case Tegra::Texture::ComponentType::FLOAT:
|
||||
return PixelFormat::RGBA16F;
|
||||
}
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
|
||||
static_cast<u32>(component_type));
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::BF10GF11RF11:
|
||||
return PixelFormat::R11FG11FB10F;
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::FLOAT:
|
||||
return PixelFormat::R11FG11FB10F;
|
||||
}
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
|
||||
static_cast<u32>(component_type));
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::R32_G32_B32_A32:
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::FLOAT:
|
||||
@@ -367,13 +410,19 @@ struct SurfaceParams {
|
||||
static_cast<u32>(component_type));
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::R32_G32_B32:
|
||||
return PixelFormat::RGB32F;
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::FLOAT:
|
||||
return PixelFormat::RGB32F;
|
||||
}
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
|
||||
static_cast<u32>(component_type));
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::R16:
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::FLOAT:
|
||||
return PixelFormat::R16F;
|
||||
case Tegra::Texture::ComponentType::UNORM:
|
||||
return PixelFormat::R16UNORM;
|
||||
return PixelFormat::R16U;
|
||||
case Tegra::Texture::ComponentType::SNORM:
|
||||
return PixelFormat::R16S;
|
||||
case Tegra::Texture::ComponentType::UINT:
|
||||
@@ -396,6 +445,8 @@ struct SurfaceParams {
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::ZF32:
|
||||
return PixelFormat::Z32F;
|
||||
case Tegra::Texture::TextureFormat::Z16:
|
||||
return PixelFormat::Z16;
|
||||
case Tegra::Texture::TextureFormat::Z24S8:
|
||||
return PixelFormat::Z24S8;
|
||||
case Tegra::Texture::TextureFormat::DXT1:
|
||||
|
||||
@@ -367,20 +367,23 @@ public:
|
||||
}
|
||||
|
||||
/// Generates code representing a uniform (C buffer) register, interpreted as the input type.
|
||||
std::string GetUniform(u64 index, u64 offset, GLSLRegister::Type type) {
|
||||
std::string GetUniform(u64 index, u64 offset, GLSLRegister::Type type,
|
||||
Register::Size size = Register::Size::Word) {
|
||||
declr_const_buffers[index].MarkAsUsed(index, offset, stage);
|
||||
std::string value = 'c' + std::to_string(index) + '[' + std::to_string(offset / 4) + "][" +
|
||||
std::to_string(offset % 4) + ']';
|
||||
|
||||
if (type == GLSLRegister::Type::Float) {
|
||||
return value;
|
||||
// Do nothing, default
|
||||
} else if (type == GLSLRegister::Type::Integer) {
|
||||
return "floatBitsToInt(" + value + ')';
|
||||
value = "floatBitsToInt(" + value + ')';
|
||||
} else if (type == GLSLRegister::Type::UnsignedInteger) {
|
||||
return "floatBitsToUint(" + value + ')';
|
||||
value = "floatBitsToUint(" + value + ')';
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return ConvertIntegerSize(value, size);
|
||||
}
|
||||
|
||||
std::string GetUniformIndirect(u64 cbuf_index, s64 offset, const std::string& index_str,
|
||||
@@ -1247,20 +1250,41 @@ private:
|
||||
op_a = "abs(" + op_a + ')';
|
||||
}
|
||||
|
||||
if (instr.conversion.negate_a) {
|
||||
op_a = "-(" + op_a + ')';
|
||||
}
|
||||
|
||||
regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1,
|
||||
1, instr.alu.saturate_d, 0, instr.conversion.dest_size);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::I2F_R: {
|
||||
case OpCode::Id::I2F_R:
|
||||
case OpCode::Id::I2F_C: {
|
||||
ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented");
|
||||
ASSERT_MSG(!instr.conversion.selector, "Unimplemented");
|
||||
std::string op_a = regs.GetRegisterAsInteger(
|
||||
instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size);
|
||||
|
||||
std::string op_a{};
|
||||
|
||||
if (instr.is_b_gpr) {
|
||||
op_a =
|
||||
regs.GetRegisterAsInteger(instr.gpr20, 0, instr.conversion.is_input_signed,
|
||||
instr.conversion.src_size);
|
||||
} else {
|
||||
op_a = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
|
||||
instr.conversion.is_input_signed
|
||||
? GLSLRegister::Type::Integer
|
||||
: GLSLRegister::Type::UnsignedInteger,
|
||||
instr.conversion.src_size);
|
||||
}
|
||||
|
||||
if (instr.conversion.abs_a) {
|
||||
op_a = "abs(" + op_a + ')';
|
||||
}
|
||||
|
||||
if (instr.conversion.negate_a) {
|
||||
op_a = "-(" + op_a + ')';
|
||||
}
|
||||
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
|
||||
break;
|
||||
}
|
||||
@@ -1269,6 +1293,14 @@ private:
|
||||
ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented");
|
||||
std::string op_a = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
|
||||
if (instr.conversion.abs_a) {
|
||||
op_a = "abs(" + op_a + ')';
|
||||
}
|
||||
|
||||
if (instr.conversion.negate_a) {
|
||||
op_a = "-(" + op_a + ')';
|
||||
}
|
||||
|
||||
switch (instr.conversion.f2f.rounding) {
|
||||
case Tegra::Shader::F2fRoundingOp::None:
|
||||
break;
|
||||
@@ -1291,19 +1323,27 @@ private:
|
||||
break;
|
||||
}
|
||||
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1, instr.alu.saturate_d);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::F2I_R:
|
||||
case OpCode::Id::F2I_C: {
|
||||
ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented");
|
||||
std::string op_a{};
|
||||
|
||||
if (instr.is_b_gpr) {
|
||||
op_a = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
} else {
|
||||
op_a = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
|
||||
GLSLRegister::Type::Float);
|
||||
}
|
||||
|
||||
if (instr.conversion.abs_a) {
|
||||
op_a = "abs(" + op_a + ')';
|
||||
}
|
||||
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1, instr.alu.saturate_d);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::F2I_R: {
|
||||
ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented");
|
||||
std::string op_a = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
|
||||
if (instr.conversion.abs_a) {
|
||||
op_a = "abs(" + op_a + ')';
|
||||
if (instr.conversion.negate_a) {
|
||||
op_a = "-(" + op_a + ')';
|
||||
}
|
||||
|
||||
switch (instr.conversion.f2i.rounding) {
|
||||
|
||||
@@ -24,16 +24,25 @@ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
|
||||
switch (attrib.type) {
|
||||
case Maxwell::VertexAttribute::Type::UnsignedInt:
|
||||
case Maxwell::VertexAttribute::Type::UnsignedNorm: {
|
||||
|
||||
switch (attrib.size) {
|
||||
case Maxwell::VertexAttribute::Size::Size_8:
|
||||
case Maxwell::VertexAttribute::Size::Size_8_8:
|
||||
case Maxwell::VertexAttribute::Size::Size_8_8_8:
|
||||
case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
|
||||
return GL_UNSIGNED_BYTE;
|
||||
case Maxwell::VertexAttribute::Size::Size_16:
|
||||
case Maxwell::VertexAttribute::Size::Size_16_16:
|
||||
case Maxwell::VertexAttribute::Size::Size_16_16_16:
|
||||
case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
|
||||
return GL_UNSIGNED_SHORT;
|
||||
case Maxwell::VertexAttribute::Size::Size_32:
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32:
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32_32:
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
|
||||
return GL_UNSIGNED_INT;
|
||||
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
|
||||
return GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||
}
|
||||
@@ -43,16 +52,25 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
|
||||
return {};
|
||||
}
|
||||
|
||||
case Maxwell::VertexAttribute::Type::SignedInt:
|
||||
case Maxwell::VertexAttribute::Type::SignedNorm: {
|
||||
|
||||
switch (attrib.size) {
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32_32:
|
||||
return GL_INT;
|
||||
case Maxwell::VertexAttribute::Size::Size_8:
|
||||
case Maxwell::VertexAttribute::Size::Size_8_8:
|
||||
case Maxwell::VertexAttribute::Size::Size_8_8_8:
|
||||
case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
|
||||
return GL_BYTE;
|
||||
case Maxwell::VertexAttribute::Size::Size_16:
|
||||
case Maxwell::VertexAttribute::Size::Size_16_16:
|
||||
case Maxwell::VertexAttribute::Size::Size_16_16_16:
|
||||
case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
|
||||
return GL_SHORT;
|
||||
case Maxwell::VertexAttribute::Size::Size_32:
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32:
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32_32:
|
||||
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
|
||||
return GL_INT;
|
||||
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
|
||||
return GL_INT_2_10_10_10_REV;
|
||||
}
|
||||
@@ -62,9 +80,6 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
|
||||
return {};
|
||||
}
|
||||
|
||||
case Maxwell::VertexAttribute::Type::UnsignedInt:
|
||||
return GL_UNSIGNED_INT;
|
||||
|
||||
case Maxwell::VertexAttribute::Type::Float:
|
||||
return GL_FLOAT;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <regex>
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
@@ -402,12 +403,72 @@ void GameList::RefreshGameDirectory() {
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
|
||||
boost::container::flat_map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
|
||||
static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
|
||||
std::vector<u8>& icon, std::string& name) {
|
||||
const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS());
|
||||
if (control_dir == nullptr)
|
||||
return;
|
||||
|
||||
const auto nca_control_callback =
|
||||
[this, &nca_control_map](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
const auto nacp_file = control_dir->GetFile("control.nacp");
|
||||
if (nacp_file == nullptr)
|
||||
return;
|
||||
FileSys::NACP nacp(nacp_file);
|
||||
name = nacp.GetApplicationName();
|
||||
|
||||
FileSys::VirtualFile icon_file = nullptr;
|
||||
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
||||
icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat");
|
||||
if (icon_file != nullptr) {
|
||||
icon = icon_file->ReadAllBytes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
const auto usernand = Service::FileSystem::GetUserNANDContents();
|
||||
const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Program);
|
||||
|
||||
for (const auto& game : installed_games) {
|
||||
const auto& file = usernand->GetEntryRaw(game);
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
|
||||
if (!loader)
|
||||
continue;
|
||||
|
||||
std::vector<u8> icon;
|
||||
std::string name;
|
||||
u64 program_id;
|
||||
loader->ReadProgramId(program_id);
|
||||
|
||||
const auto& control =
|
||||
usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
|
||||
if (control != nullptr)
|
||||
GetMetadataFromControlNCA(control, icon, name);
|
||||
emit EntryReady({
|
||||
new GameListItemPath(
|
||||
FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(file->GetSize()),
|
||||
});
|
||||
}
|
||||
|
||||
const auto control_data = usernand->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Control);
|
||||
|
||||
for (const auto& entry : control_data) {
|
||||
const auto nca = usernand->GetEntry(entry);
|
||||
if (nca != nullptr)
|
||||
nca_control_map.insert_or_assign(entry.title_id, nca);
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWorker::FillControlMap(const std::string& dir_path) {
|
||||
const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (stop_processing)
|
||||
@@ -425,10 +486,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
|
||||
}
|
||||
|
||||
const auto callback = [this, recursion,
|
||||
&nca_control_map](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
|
||||
const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (stop_processing)
|
||||
@@ -458,20 +520,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
// Use from metadata pool.
|
||||
if (nca_control_map.find(program_id) != nca_control_map.end()) {
|
||||
const auto nca = nca_control_map[program_id];
|
||||
const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS());
|
||||
|
||||
const auto nacp_file = control_dir->GetFile("control.nacp");
|
||||
FileSys::NACP nacp(nacp_file);
|
||||
name = nacp.GetApplicationName();
|
||||
|
||||
FileSys::VirtualFile icon_file = nullptr;
|
||||
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
||||
icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat");
|
||||
if (icon_file != nullptr) {
|
||||
icon = icon_file->ReadAllBytes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
GetMetadataFromControlNCA(nca, icon, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,7 +547,10 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
void GameListWorker::run() {
|
||||
stop_processing = false;
|
||||
watch_list.append(dir_path);
|
||||
FillControlMap(dir_path.toStdString());
|
||||
AddInstalledTitlesToGameList();
|
||||
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||
nca_control_map.clear();
|
||||
emit Finished(watch_list);
|
||||
}
|
||||
|
||||
|
||||
@@ -163,10 +163,13 @@ signals:
|
||||
|
||||
private:
|
||||
FileSys::VirtualFilesystem vfs;
|
||||
std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
|
||||
QStringList watch_list;
|
||||
QString dir_path;
|
||||
bool deep_scan;
|
||||
std::atomic_bool stop_processing;
|
||||
|
||||
void AddInstalledTitlesToGameList();
|
||||
void FillControlMap(const std::string& dir_path);
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
#include <clocale>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
#define QT_NO_OPENGL
|
||||
#include <QDesktopWidget>
|
||||
#include <QFileDialog>
|
||||
@@ -24,6 +27,7 @@
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/loader/loader.h"
|
||||
@@ -114,6 +118,9 @@ GMainWindow::GMainWindow()
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
||||
show();
|
||||
|
||||
// Necessary to load titles from nand in gamelist.
|
||||
Service::FileSystem::RegisterBIS(std::make_unique<FileSys::BISFactory>(vfs->OpenDirectory(
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite)));
|
||||
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
|
||||
|
||||
// Show one-time "callout" messages to the user
|
||||
@@ -309,6 +316,8 @@ void GMainWindow::ConnectMenuEvents() {
|
||||
// File
|
||||
connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile);
|
||||
connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder);
|
||||
connect(ui.action_Install_File_NAND, &QAction::triggered, this,
|
||||
&GMainWindow::OnMenuInstallToNAND);
|
||||
connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
|
||||
&GMainWindow::OnMenuSelectGameListRoot);
|
||||
connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
|
||||
@@ -454,7 +463,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
"While attempting to load the ROM requested, an error occured. Please "
|
||||
"refer to the yuzu wiki for more information or the yuzu discord for "
|
||||
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
|
||||
loader_id, error_id, Loader::GetMessageForResultStatus(error_id))));
|
||||
loader_id, error_id, static_cast<Loader::ResultStatus>(error_id))));
|
||||
} else {
|
||||
QMessageBox::critical(
|
||||
this, tr("Error while loading ROM!"),
|
||||
@@ -612,6 +621,148 @@ void GMainWindow::OnMenuLoadFolder() {
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnMenuInstallToNAND() {
|
||||
const QString file_filter =
|
||||
tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge "
|
||||
"Image (*.xci)");
|
||||
QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
|
||||
UISettings::values.roms_path, file_filter);
|
||||
|
||||
if (filename.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) {
|
||||
if (src == nullptr || dest == nullptr)
|
||||
return false;
|
||||
if (!dest->Resize(src->GetSize()))
|
||||
return false;
|
||||
|
||||
std::array<u8, 0x1000> buffer{};
|
||||
const int progress_maximum = static_cast<int>(src->GetSize() / buffer.size());
|
||||
|
||||
QProgressDialog progress(
|
||||
tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName())),
|
||||
tr("Cancel"), 0, progress_maximum, this);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
|
||||
for (size_t i = 0; i < src->GetSize(); i += buffer.size()) {
|
||||
if (progress.wasCanceled()) {
|
||||
dest->Resize(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
const int progress_value = static_cast<int>(i / buffer.size());
|
||||
progress.setValue(progress_value);
|
||||
|
||||
const auto read = src->Read(buffer.data(), buffer.size(), i);
|
||||
dest->Write(buffer.data(), read, i);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const auto success = [this]() {
|
||||
QMessageBox::information(this, tr("Successfully Installed"),
|
||||
tr("The file was successfully installed."));
|
||||
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
|
||||
};
|
||||
|
||||
const auto failed = [this]() {
|
||||
QMessageBox::warning(
|
||||
this, tr("Failed to Install"),
|
||||
tr("There was an error while attempting to install the provided file. It "
|
||||
"could have an incorrect format or be missing metadata. Please "
|
||||
"double-check your file and try again."));
|
||||
};
|
||||
|
||||
const auto overwrite = [this]() {
|
||||
return QMessageBox::question(this, tr("Failed to Install"),
|
||||
tr("The file you are attempting to install already exists "
|
||||
"in the cache. Would you like to overwrite it?")) ==
|
||||
QMessageBox::Yes;
|
||||
};
|
||||
|
||||
if (filename.endsWith("xci", Qt::CaseInsensitive)) {
|
||||
const auto xci = std::make_shared<FileSys::XCI>(
|
||||
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
|
||||
if (xci->GetStatus() != Loader::ResultStatus::Success) {
|
||||
failed();
|
||||
return;
|
||||
}
|
||||
const auto res =
|
||||
Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy);
|
||||
if (res == FileSys::InstallResult::Success) {
|
||||
success();
|
||||
} else {
|
||||
if (res == FileSys::InstallResult::ErrorAlreadyExists) {
|
||||
if (overwrite()) {
|
||||
const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
|
||||
xci, true, qt_raw_copy);
|
||||
if (res2 == FileSys::InstallResult::Success) {
|
||||
success();
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto nca = std::make_shared<FileSys::NCA>(
|
||||
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success) {
|
||||
failed();
|
||||
return;
|
||||
}
|
||||
|
||||
const QStringList tt_options{tr("System Application"),
|
||||
tr("System Archive"),
|
||||
tr("System Application Update"),
|
||||
tr("Firmware Package (Type A)"),
|
||||
tr("Firmware Package (Type B)"),
|
||||
tr("Game"),
|
||||
tr("Game Update"),
|
||||
tr("Game DLC"),
|
||||
tr("Delta Title")};
|
||||
bool ok;
|
||||
const auto item = QInputDialog::getItem(
|
||||
this, tr("Select NCA Install Type..."),
|
||||
tr("Please select the type of title you would like to install this NCA as:\n(In "
|
||||
"most instances, the default 'Game' is fine.)"),
|
||||
tt_options, 5, false, &ok);
|
||||
|
||||
auto index = tt_options.indexOf(item);
|
||||
if (!ok || index == -1) {
|
||||
QMessageBox::warning(this, tr("Failed to Install"),
|
||||
tr("The title type you selected for the NCA is invalid."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (index >= 5)
|
||||
index += 0x7B;
|
||||
|
||||
const auto res = Service::FileSystem::GetUserNANDContents()->InstallEntry(
|
||||
nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy);
|
||||
if (res == FileSys::InstallResult::Success) {
|
||||
success();
|
||||
} else if (res == FileSys::InstallResult::ErrorAlreadyExists) {
|
||||
if (overwrite()) {
|
||||
const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
|
||||
nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
|
||||
if (res2 == FileSys::InstallResult::Success) {
|
||||
success();
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnMenuSelectGameListRoot() {
|
||||
QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
|
||||
if (!dir_path.isEmpty()) {
|
||||
|
||||
@@ -125,6 +125,7 @@ private slots:
|
||||
void OnGameListOpenSaveFolder(u64 program_id);
|
||||
void OnMenuLoadFile();
|
||||
void OnMenuLoadFolder();
|
||||
void OnMenuInstallToNAND();
|
||||
/// Called whenever a user selects the "File->Select Game List Root" menu item
|
||||
void OnMenuSelectGameListRoot();
|
||||
void OnMenuRecentFile();
|
||||
|
||||
@@ -57,6 +57,8 @@
|
||||
<string>Recent Files</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="action_Install_File_NAND" />
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Load_File"/>
|
||||
<addaction name="action_Load_Folder"/>
|
||||
<addaction name="separator"/>
|
||||
@@ -102,6 +104,11 @@
|
||||
<addaction name="menu_View"/>
|
||||
<addaction name="menu_Help"/>
|
||||
</widget>
|
||||
<action name="action_Install_File_NAND">
|
||||
<property name="text">
|
||||
<string>Install File to NAND...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Load_File">
|
||||
<property name="text">
|
||||
<string>Load File...</string>
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "common/common_paths.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/filter.h"
|
||||
@@ -194,7 +196,7 @@ int main(int argc, char** argv) {
|
||||
"While attempting to load the ROM requested, an error occured. Please "
|
||||
"refer to the yuzu wiki for more information or the yuzu discord for "
|
||||
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
|
||||
loader_id, error_id, Loader::GetMessageForResultStatus(error_id));
|
||||
loader_id, error_id, static_cast<Loader::ResultStatus>(error_id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user