Compare commits

..

80 Commits

Author SHA1 Message Date
Lioncash
9791f0d590 qt/main: Unindent code in OnMenuInstallToNAND()
We can change this into an early-return if the filename is empty.
There's no need to include all of the code within the if statement.
2018-08-16 10:37:58 -04:00
Lioncash
2a3d7128d1 qt/main: Make installation dialog text within OnMenuInstallToNAND() translatable
This is user-facing text, so it should be marked as translatable by Qt.
2018-08-16 10:36:42 -04:00
Lioncash
aac807fd3a qt/main: Get rid of compilation warnings
Gets rid of truncation warnings about conversion to int. While we're at
it, we can also de-hardcode the buffer size being used.
2018-08-16 10:28:06 -04:00
bunnei
c594ec3417 Merge pull request #1005 from DarkLordZach/registered-fmt
file_sys: Add support for registration format
2018-08-15 23:11:58 -04:00
bunnei
c00b374e78 Merge pull request #1078 from lioncash/message
lm: Handle threads and modules within the logger
2018-08-15 18:53:47 -04:00
bunnei
69236e5aff Merge pull request #1079 from lioncash/fmt
loader: Make ResultStatus directly compatible with fmt
2018-08-15 18:25:57 -04:00
bunnei
1dd27aff47 Merge pull request #1051 from B3n30/UnscheduleEventThreadsafe
Core::CoreTiming: add UnscheduleEventThreadsafe
2018-08-15 18:25:30 -04:00
bunnei
cee6a7ab55 Merge pull request #1080 from lioncash/ret
sm/controller: Correct return value of QueryPointerBufferSize
2018-08-15 18:25:05 -04:00
bunnei
a2fa37b499 Merge pull request #1083 from Subv/conv_neg
Shaders: Implemented I2F_C and F2I_C, along with the negation bits of the conversion instructions.
2018-08-15 18:24:47 -04:00
bunnei
f96de510ee Merge pull request #1081 from lioncash/convert
kernel/server_session: Add IsSession() member function
2018-08-15 13:02:25 -04:00
Subv
91140f6c0a Shader/Conversion: Implemented the negate bit in F2F and I2I instructions. 2018-08-15 09:27:43 -05:00
Subv
38592a3b5e Shader/I2F: Implemented the negate I2F_C instruction variant. 2018-08-15 09:25:02 -05:00
Subv
40ecdda19e Shader/F2I: Implemented the negate bit in the I2F instruction 2018-08-15 09:18:55 -05:00
Subv
5ef447cc0e Shader/F2I: Implemented the F2I_C instruction variant. 2018-08-15 09:16:35 -05:00
Subv
11c221cc62 Shader/F2I: Implemented the negate bit in the F2I instruction. 2018-08-15 09:15:55 -05:00
bunnei
40f83fee6a Merge pull request #1077 from bunnei/rgba16u
gl_rasterizer_cache: Add RGBA16U to PixelFormatFromTextureFormat.
2018-08-15 09:25:15 -04:00
bunnei
0bca5743ab Merge pull request #1076 from bunnei/format-cleanup
gl_rasterizer_cache: Cleanup some PixelFormat names and logging.
2018-08-15 09:24:54 -04:00
Lioncash
aac5792a2b kernel/server_session: Add IsSession() member function
Allows querying the inverse of IsDomain() to make things more readable.
This will likely also be usable in the event of implementing
ConvertDomainToSession().
2018-08-15 06:50:50 -04:00
Lioncash
5752454629 sm/controller: Correct return value of QueryPointerBufferSize
This should be returning a u16 according to Switch Brew.
2018-08-15 06:16:10 -04:00
Lioncash
87d8a9c986 loader: Make ResultStatus directly compatible with fmt
We can make the enum class type compatible with fmt by providing an
overload of operator<<.

While we're at it, perform proper bounds checking. If something exceeds
the array, it should be a hard fail, because it's, without a doubt, a
programmer error in this case.
2018-08-15 05:52:37 -04:00
Lioncash
b74df62959 lm: Use LOG_DEBUG for printing out trace logs
Using LOG_TRACE here isn't a good idea because LOG_TRACE is only enabled
when yuzu is compiled in debug mode. Debug mode is also quite slow, and
so we're potentially throwing away logging messages that can provide
value when trying to boot games.
2018-08-15 01:07:41 -04:00
Lioncash
e0b0f4eece lm: Handle threads and modules within the logger
The thread field serves to indicate which thread a log is related to and
provides the length of the thread's name, so we can print that out,
ditto for modules.

Now we can know what threads are potentially spawning off logging
messages (for example Lydie & Suelle bounces between MainThread and
LoadingThread when initializing the game).
2018-08-15 01:05:50 -04:00
bunnei
b1148d269d gl_rasterizer_cache: Cleanup some PixelFormat names and logging. 2018-08-14 23:31:45 -04:00
bunnei
8599e1e4fc gl_rasterizer_cache: Add RGBA16U to PixelFormatFromTextureFormat.
- Used by Breath of the Wild.
2018-08-14 23:18:34 -04:00
bunnei
3aad82b1a3 Merge pull request #1069 from bunnei/vtx-sz
maxwell_to_gl: Properly handle UnsignedInt/SignedInt sizes.
2018-08-14 23:14:44 -04:00
bunnei
2a42dea568 Merge pull request #1070 from bunnei/cbuf-sz
gl_rasterizer: Fix upload size for constant buffers.
2018-08-14 23:14:24 -04:00
bunnei
c8cd1785e6 Merge pull request #1071 from bunnei/fix-ldc
gl_shader_decompiler: Several fixes for indirect constant buffer loads.
2018-08-14 23:14:09 -04:00
bunnei
991eb4824c Merge pull request #1068 from bunnei/g8r8s
gl_rasterizer_cache: Implement G8R8S format.
2018-08-14 23:13:43 -04:00
bunnei
301baaa942 Merge pull request #1067 from lioncash/init
emu_window: Ensure WindowConfig members are always initialized
2018-08-14 22:43:32 -04:00
bunnei
3db1b8e0cd Merge pull request #1073 from lioncash/3ds
loader: Remove address mapping remnants from citra
2018-08-14 22:43:04 -04:00
bunnei
8f9c49f7ee Merge pull request #1072 from lioncash/svc
kernel/svc: Log svcBreak parameters
2018-08-14 22:42:44 -04:00
bunnei
f009ed63f3 Merge pull request #1063 from lioncash/inline
common/xbyak_abi: Mark defined functions in header as inline
2018-08-14 22:40:23 -04:00
bunnei
18dfa99030 Merge pull request #1074 from greggameplayer/Z16_UNORM
Implement Z16 in PixelFormatFromTextureFormat function
2018-08-14 22:39:09 -04:00
greggameplayer
6eda9ebbdb Implement Z16_UNORM in PixelFormatFromTextureFormat function
Require by Zelda Breath Of The Wild
2018-08-15 04:14:15 +02:00
bunnei
ad7815a28d Merge pull request #1054 from zhaowenlan1779/misc-fixup
common/misc: use windows.h
2018-08-14 21:47:28 -04:00
bunnei
409d2e07c2 Merge pull request #1056 from lioncash/mm
mm_u: Move interface class into the cpp file
2018-08-14 21:47:07 -04:00
bunnei
8dc4407586 Merge pull request #1066 from lioncash/aarch64
CMakeLists: Add architecture detection for AArch64
2018-08-14 21:46:53 -04:00
Lioncash
96c0b81a51 loader: Remove address mapping remnants from citra
These mappings are leftovers from citra and don't apply to the Switch.
2018-08-14 21:37:03 -04:00
Lioncash
25d71454d1 kernel/svc: Log svcBreak parameters
Given if we hit here all is lost, we should probably be logging the
break reason code and associated information to distinguish between the
causes.
2018-08-14 20:54:05 -04:00
bunnei
290439a6a5 gl_rasterizer: Fix upload size for constant buffers. 2018-08-14 20:44:19 -04:00
bunnei
dc876fd63a maxwell_to_gl: Properly handle UnsignedInt/SignedInt sizes. 2018-08-14 20:43:02 -04:00
bunnei
d8fd3ef4fe gl_rasterizer_cache: Implement G8R8S format.
- Used by Super Mario Odyssey.
2018-08-14 20:41:49 -04:00
bunnei
1c31cbad72 Merge pull request #1062 from lioncash/unused
common: Remove unused old breakpoint source files
2018-08-14 20:26:56 -04:00
Lioncash
2e715ef70d emu_window: Ensure WindowConfig members are always initialized
Previously we weren't always initializing all members of the struct.
Prevents potentially wonky behavior from occurring.
2018-08-14 19:36:43 -04:00
Lioncash
319dbc5843 CMakeLists: Add architecture detection for AArch64
We already have an equivalent in place for the 32-bit ARM architecture, so we
should also have one for the newer 64-bit ARM architecture as well.
2018-08-14 19:06:55 -04:00
Lioncash
6d549abb4e common/xbyak_abi: Mark defined functions in header as inline
Avoids potential One Definition Rule violations when these are used in
the future.
2018-08-14 18:29:56 -04:00
Lioncash
0ce0905380 common/xbyak: Use nested namespace specifiers where applicable 2018-08-14 18:27:27 -04:00
Lioncash
11895d54af common: Remove unused old breakpoint source files
These currently aren't used and contain commented out source code that
corresponds to Dolphin's JIT. Given our CPU code is organized quite
differently, we shouldn't be keeping this around (at the moment it just
adds to compile times marginally).
2018-08-14 18:14:01 -04:00
Lioncash
b6c47b578f mm_u: Forward all old variants of functions to the new ones
Ensures both variants go through the same interface, and while we're at
it, add Finalize to provide the inverse of Initialize for consistency.
2018-08-13 18:59:10 -04:00
Lioncash
9d09d92c56 mm_u: Move implementation class into the cpp file
Now if changes are ever made to the behavior of the class, it doesn't
involve rebuilding everything that includes the mm_u header.
2018-08-13 18:59:07 -04:00
Zhu PengFei
59d18ef55b common/misc: use windows.h
linux-mingw does not really like this.
2018-08-14 04:28:24 +08:00
B3n30
eab35c8235 Core::CoreTiming: add UnscheduleEventThreadsafe 2018-08-13 13:56:41 +02:00
Zach Hilman
35e4a47be0 registration: Various style and documentation improvements
Fix logic in RealVfsFilesystem Create methods
Remove magic numbers
Fix regex errors
2018-08-12 15:55:44 -04:00
Zach Hilman
6b76b77400 registration: Add support for force overwrite of installed 2018-08-11 23:01:42 -04:00
Zach Hilman
fdf27bf390 game_list: Split game list scans to multiple functions
Avoids unnecessary rebuilds of control data on every layer of recursion in AddFstEntriesToGameList
2018-08-11 22:50:48 -04:00
Zach Hilman
8f06a0f898 vfs_real: Add CreateFullPath to Create* operations 2018-08-11 22:50:48 -04:00
Zach Hilman
dda8ef11c7 control_metadata: Remove unnecessary reference to base file 2018-08-11 22:50:48 -04:00
Zach Hilman
149bda980a romfs: Remove cyclic shared_ptr leak in romfs code 2018-08-11 22:50:48 -04:00
Zach Hilman
893447b6b0 registration: Update documentation and style 2018-08-11 22:50:48 -04:00
Zach Hilman
22bdddd6f0 nca_metadata: Remove unnecessary reference to base file 2018-08-11 22:50:48 -04:00
Zach Hilman
62e859c6c7 bis_factory: Create NAND dirs if they don't exist 2018-08-11 22:50:48 -04:00
Zach Hilman
f78a6e752f qt: Use custom RawCopy with progress bar for installs 2018-08-11 22:50:48 -04:00
Zach Hilman
3b3c919e20 registration: Take RawCopy function as parameter
Instead of defaulting to VfsRawCopy
2018-08-11 22:50:48 -04:00
Zach Hilman
10812f8407 game_list: Populate control data from installed NAND 2018-08-11 22:50:48 -04:00
Zach Hilman
e5504a060d registered_cache: Fix missing reading from yuzu_meta 2018-08-11 22:50:48 -04:00
Zach Hilman
167bfddafa file_sys: Comply to style guidelines 2018-08-11 22:50:48 -04:00
Zach Hilman
bfb945c243 qt: Add 'Install to NAND' option to menu
Prompts for title type on NCA files.
2018-08-11 22:50:48 -04:00
Zach Hilman
b67e751ccb game_list: Modify game list to scan installed titles 2018-08-11 22:50:48 -04:00
Zach Hilman
a91983b11c file_sys: Add RegisteredCache
Manages NAND NCA get and install.
2018-08-11 22:50:48 -04:00
Zach Hilman
9aab787122 file_sys: Add support for parsing NCA metadata (CNMT) 2018-08-11 22:50:48 -04:00
Zach Hilman
ab8acce645 card_image: Add accessor for all NCAs in XCI 2018-08-11 22:50:48 -04:00
Zach Hilman
9b0e3556ed vfs_real: Add CreateFullPath to CreateFile
Fixes bugs with calling CreateFile when the immediate directory does not exist.
2018-08-11 22:50:48 -04:00
Zach Hilman
c0257cf52f filesystem: Add Open and Register functions for BISFactory 2018-08-11 22:50:48 -04:00
Zach Hilman
70a510bd8f bis_factory: Add partial implementation of BISFactory
Creates and stores RegisteredCaches for user and system NAND, as creation of a RegisteredCache is expensive.
2018-08-11 22:50:48 -04:00
Zach Hilman
95bb1067c1 loader: Join 0* files in directory if filename is 00
i.e. Load the concatenated 00+01 if 01 exists as well. Needed for split NAND NCAs.
2018-08-11 22:50:48 -04:00
Zach Hilman
5b4119fa7f loader: Recognize filename '00' as NCA
Needed to avoid mismatch filetype warnings on split NAND NCAs
2018-08-11 22:50:08 -04:00
Zach Hilman
42114e1df4 vfs: Add ConcatenatedVfsFile 2018-08-11 22:50:08 -04:00
Zach Hilman
a27ec24c0f crypto: Remove hex utilities from key_manager
Move to hex_util.h in common
2018-08-11 22:50:08 -04:00
Zach Hilman
b70a831608 file_util: Add getter for NAND registration directory 2018-08-11 22:50:08 -04:00
Zach Hilman
10aac376d1 common: Move hex string processing to separate file 2018-08-11 22:50:08 -04:00
62 changed files with 1787 additions and 428 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;
};

View File

@@ -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());
}

View File

@@ -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
View 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
View 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);

View File

@@ -4,7 +4,7 @@
#include <cstddef>
#ifdef _WIN32
#include <Windows.h>
#include <windows.h>
#else
#include <cerrno>
#include <cstring>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();

View File

@@ -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();

View 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

View 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

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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());
}

View File

@@ -81,7 +81,6 @@ public:
std::string GetVersionString() const;
private:
VirtualFile file;
std::unique_ptr<RawNACP> raw;
};

View 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

View 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

View 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

View 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

View File

@@ -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);

View 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

View 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

View File

@@ -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);
}

View File

@@ -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"

View File

@@ -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_)) {}

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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());

View File

@@ -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

View File

@@ -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);

View File

@@ -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");
}

View File

@@ -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(),

View File

@@ -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 =

View File

@@ -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;
}
/**

View File

@@ -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

View 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);

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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:

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
};

View File

@@ -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()) {

View File

@@ -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();

View File

@@ -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>

View File

@@ -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));
}
}