Implement NPDM, Core Mask SVCs + Fix VMM bug + Introduce Verbose Log Level

This commit is contained in:
◱ PixelyIon 2020-10-21 22:39:35 +05:30 committed by ◱ PixelyIon
parent 324381908b
commit c65c91e1bc
20 changed files with 291 additions and 102 deletions

View File

@ -36,12 +36,18 @@ add_library(skyline SHARED
${source_DIR}/skyline/nce/guest.S ${source_DIR}/skyline/nce/guest.S
${source_DIR}/skyline/nce.cpp ${source_DIR}/skyline/nce.cpp
${source_DIR}/skyline/jvm.cpp ${source_DIR}/skyline/jvm.cpp
${source_DIR}/skyline/os.cpp
${source_DIR}/skyline/kernel/memory.cpp
${source_DIR}/skyline/kernel/ipc.cpp
${source_DIR}/skyline/kernel/svc.cpp
${source_DIR}/skyline/kernel/types/KProcess.cpp
${source_DIR}/skyline/kernel/types/KThread.cpp
${source_DIR}/skyline/kernel/types/KSharedMemory.cpp
${source_DIR}/skyline/kernel/types/KPrivateMemory.cpp
${source_DIR}/skyline/audio.cpp ${source_DIR}/skyline/audio.cpp
${source_DIR}/skyline/audio/track.cpp ${source_DIR}/skyline/audio/track.cpp
${source_DIR}/skyline/audio/resampler.cpp ${source_DIR}/skyline/audio/resampler.cpp
${source_DIR}/skyline/audio/adpcm_decoder.cpp ${source_DIR}/skyline/audio/adpcm_decoder.cpp
${source_DIR}/skyline/crypto/aes_cipher.cpp
${source_DIR}/skyline/crypto/key_store.cpp
${source_DIR}/skyline/gpu.cpp ${source_DIR}/skyline/gpu.cpp
${source_DIR}/skyline/gpu/macro_interpreter.cpp ${source_DIR}/skyline/gpu/macro_interpreter.cpp
${source_DIR}/skyline/gpu/memory_manager.cpp ${source_DIR}/skyline/gpu/memory_manager.cpp
@ -52,19 +58,21 @@ add_library(skyline SHARED
${source_DIR}/skyline/input/npad.cpp ${source_DIR}/skyline/input/npad.cpp
${source_DIR}/skyline/input/npad_device.cpp ${source_DIR}/skyline/input/npad_device.cpp
${source_DIR}/skyline/input/touch.cpp ${source_DIR}/skyline/input/touch.cpp
${source_DIR}/skyline/os.cpp ${source_DIR}/skyline/crypto/aes_cipher.cpp
${source_DIR}/skyline/crypto/key_store.cpp
${source_DIR}/skyline/loader/loader.cpp ${source_DIR}/skyline/loader/loader.cpp
${source_DIR}/skyline/loader/nro.cpp ${source_DIR}/skyline/loader/nro.cpp
${source_DIR}/skyline/loader/nso.cpp ${source_DIR}/skyline/loader/nso.cpp
${source_DIR}/skyline/loader/nca.cpp ${source_DIR}/skyline/loader/nca.cpp
${source_DIR}/skyline/loader/nsp.cpp ${source_DIR}/skyline/loader/nsp.cpp
${source_DIR}/skyline/kernel/memory.cpp ${source_DIR}/skyline/vfs/os_filesystem.cpp
${source_DIR}/skyline/kernel/ipc.cpp ${source_DIR}/skyline/vfs/partition_filesystem.cpp
${source_DIR}/skyline/kernel/svc.cpp ${source_DIR}/skyline/vfs/ctr_encrypted_backing.cpp
${source_DIR}/skyline/kernel/types/KProcess.cpp ${source_DIR}/skyline/vfs/rom_filesystem.cpp
${source_DIR}/skyline/kernel/types/KThread.cpp ${source_DIR}/skyline/vfs/os_backing.cpp
${source_DIR}/skyline/kernel/types/KSharedMemory.cpp ${source_DIR}/skyline/vfs/nacp.cpp
${source_DIR}/skyline/kernel/types/KPrivateMemory.cpp ${source_DIR}/skyline/vfs/npdm.cpp
${source_DIR}/skyline/vfs/nca.cpp
${source_DIR}/skyline/services/serviceman.cpp ${source_DIR}/skyline/services/serviceman.cpp
${source_DIR}/skyline/services/base_service.cpp ${source_DIR}/skyline/services/base_service.cpp
${source_DIR}/skyline/services/common/parcel.cpp ${source_DIR}/skyline/services/common/parcel.cpp
@ -149,13 +157,6 @@ add_library(skyline SHARED
${source_DIR}/skyline/services/ssl/ISslService.cpp ${source_DIR}/skyline/services/ssl/ISslService.cpp
${source_DIR}/skyline/services/ssl/ISslContext.cpp ${source_DIR}/skyline/services/ssl/ISslContext.cpp
${source_DIR}/skyline/services/prepo/IPrepoService.cpp ${source_DIR}/skyline/services/prepo/IPrepoService.cpp
${source_DIR}/skyline/vfs/os_filesystem.cpp
${source_DIR}/skyline/vfs/partition_filesystem.cpp
${source_DIR}/skyline/vfs/ctr_encrypted_backing.cpp
${source_DIR}/skyline/vfs/rom_filesystem.cpp
${source_DIR}/skyline/vfs/os_backing.cpp
${source_DIR}/skyline/vfs/nacp.cpp
${source_DIR}/skyline/vfs/nca.cpp
) )
target_link_libraries(skyline vulkan android fmt tinyxml2 oboe lz4_static mbedtls::mbedcrypto) target_link_libraries(skyline vulkan android fmt tinyxml2 oboe lz4_static mbedtls::mbedcrypto)

View File

@ -141,8 +141,8 @@ namespace skyline {
} }
void Logger::Write(LogLevel level, std::string str) { void Logger::Write(LogLevel level, std::string str) {
constexpr std::array<char, 4> levelCharacter{'0', '1', '2', '3'}; // The LogLevel as written out to a file constexpr std::array<char, 5> levelCharacter{'0', '1', '2', '3', '4'}; // The LogLevel as written out to a file
constexpr std::array<int, 4> levelAlog{ANDROID_LOG_ERROR, ANDROID_LOG_WARN, ANDROID_LOG_INFO, ANDROID_LOG_DEBUG}; // This corresponds to LogLevel and provides it's equivalent for NDK Logging constexpr std::array<int, 5> levelAlog{ANDROID_LOG_ERROR, ANDROID_LOG_WARN, ANDROID_LOG_INFO, ANDROID_LOG_DEBUG, ANDROID_LOG_VERBOSE}; // This corresponds to LogLevel and provides it's equivalent for NDK Logging
__android_log_write(levelAlog[static_cast<u8>(level)], "emu-cpp", str.c_str()); __android_log_write(levelAlog[static_cast<u8>(level)], "emu-cpp", str.c_str());

View File

@ -436,6 +436,7 @@ namespace skyline {
Warn, Warn,
Info, Info,
Debug, Debug,
Verbose,
}; };
LogLevel configLevel; //!< The minimum level of logs to write LogLevel configLevel; //!< The minimum level of logs to write
@ -453,22 +454,11 @@ namespace skyline {
/** /**
* @brief Writes a header, should only be used for emulation starting and ending * @brief Writes a header, should only be used for emulation starting and ending
* @param str The value to be written
*/ */
void WriteHeader(const std::string &str); void WriteHeader(const std::string &str);
/**
* @brief Write a log to the log file
* @param level The level of the log
* @param str The value to be written
*/
void Write(LogLevel level, std::string str); void Write(LogLevel level, std::string str);
/**
* @brief Write an error log with libfmt formatting
* @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args> template<typename S, typename... Args>
inline void Error(const S &formatStr, Args &&... args) { inline void Error(const S &formatStr, Args &&... args) {
if (LogLevel::Error <= configLevel) { if (LogLevel::Error <= configLevel) {
@ -476,11 +466,6 @@ namespace skyline {
} }
} }
/**
* @brief Write a debug log with libfmt formatting
* @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args> template<typename S, typename... Args>
inline void Warn(const S &formatStr, Args &&... args) { inline void Warn(const S &formatStr, Args &&... args) {
if (LogLevel::Warn <= configLevel) { if (LogLevel::Warn <= configLevel) {
@ -488,11 +473,6 @@ namespace skyline {
} }
} }
/**
* @brief Write a debug log with libfmt formatting
* @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args> template<typename S, typename... Args>
inline void Info(const S &formatStr, Args &&... args) { inline void Info(const S &formatStr, Args &&... args) {
if (LogLevel::Info <= configLevel) { if (LogLevel::Info <= configLevel) {
@ -500,17 +480,19 @@ namespace skyline {
} }
} }
/**
* @brief Write a debug log with libfmt formatting
* @param formatStr The value to be written, with libfmt formatting
* @param args The arguments based on format_str
*/
template<typename S, typename... Args> template<typename S, typename... Args>
inline void Debug(const S &formatStr, Args &&... args) { inline void Debug(const S &formatStr, Args &&... args) {
if (LogLevel::Debug <= configLevel) { if (LogLevel::Debug <= configLevel) {
Write(LogLevel::Debug, fmt::format(formatStr, util::FmtCast(args)...)); Write(LogLevel::Debug, fmt::format(formatStr, util::FmtCast(args)...));
} }
} }
template<typename S, typename... Args>
inline void Verbose(const S &formatStr, Args &&... args) {
if (LogLevel::Verbose <= configLevel) {
Write(LogLevel::Verbose, fmt::format(formatStr, util::FmtCast(args)...));
}
}
}; };
/** /**

View File

@ -31,7 +31,7 @@ namespace skyline::kernel::ipc {
auto bufX{reinterpret_cast<BufferDescriptorX *>(pointer)}; auto bufX{reinterpret_cast<BufferDescriptorX *>(pointer)};
if (bufX->Pointer()) { if (bufX->Pointer()) {
inputBuf.emplace_back(bufX->Pointer(), static_cast<u16>(bufX->size)); inputBuf.emplace_back(bufX->Pointer(), static_cast<u16>(bufX->size));
state.logger->Debug("Buf X #{}: 0x{:X}, 0x{:X}, #{}", index, bufX->Pointer(), static_cast<u16>(bufX->size), static_cast<u16>(bufX->Counter())); state.logger->Verbose("Buf X #{}: 0x{:X}, 0x{:X}, #{}", index, bufX->Pointer(), static_cast<u16>(bufX->size), static_cast<u16>(bufX->Counter()));
} }
pointer += sizeof(BufferDescriptorX); pointer += sizeof(BufferDescriptorX);
} }
@ -40,7 +40,7 @@ namespace skyline::kernel::ipc {
auto bufA{reinterpret_cast<BufferDescriptorABW *>(pointer)}; auto bufA{reinterpret_cast<BufferDescriptorABW *>(pointer)};
if (bufA->Pointer()) { if (bufA->Pointer()) {
inputBuf.emplace_back(bufA->Pointer(), bufA->Size()); inputBuf.emplace_back(bufA->Pointer(), bufA->Size());
state.logger->Debug("Buf A #{}: 0x{:X}, 0x{:X}", index, bufA->Pointer(), static_cast<u64>(bufA->Size())); state.logger->Verbose("Buf A #{}: 0x{:X}, 0x{:X}", index, bufA->Pointer(), static_cast<u64>(bufA->Size()));
} }
pointer += sizeof(BufferDescriptorABW); pointer += sizeof(BufferDescriptorABW);
} }
@ -49,7 +49,7 @@ namespace skyline::kernel::ipc {
auto bufB{reinterpret_cast<BufferDescriptorABW *>(pointer)}; auto bufB{reinterpret_cast<BufferDescriptorABW *>(pointer)};
if (bufB->Pointer()) { if (bufB->Pointer()) {
outputBuf.emplace_back(bufB->Pointer(), bufB->Size()); outputBuf.emplace_back(bufB->Pointer(), bufB->Size());
state.logger->Debug("Buf B #{}: 0x{:X}, 0x{:X}", index, bufB->Pointer(), static_cast<u64>(bufB->Size())); state.logger->Verbose("Buf B #{}: 0x{:X}, 0x{:X}", index, bufB->Pointer(), static_cast<u64>(bufB->Size()));
} }
pointer += sizeof(BufferDescriptorABW); pointer += sizeof(BufferDescriptorABW);
} }
@ -59,7 +59,7 @@ namespace skyline::kernel::ipc {
if (bufW->Pointer()) { if (bufW->Pointer()) {
outputBuf.emplace_back(bufW->Pointer(), bufW->Size()); outputBuf.emplace_back(bufW->Pointer(), bufW->Size());
outputBuf.emplace_back(bufW->Pointer(), bufW->Size()); outputBuf.emplace_back(bufW->Pointer(), bufW->Size());
state.logger->Debug("Buf W #{}: 0x{:X}, 0x{:X}", index, bufW->Pointer(), static_cast<u16>(bufW->Size())); state.logger->Verbose("Buf W #{}: 0x{:X}, 0x{:X}", index, bufW->Pointer(), static_cast<u16>(bufW->Size()));
} }
pointer += sizeof(BufferDescriptorABW); pointer += sizeof(BufferDescriptorABW);
} }
@ -103,26 +103,26 @@ namespace skyline::kernel::ipc {
auto bufC{reinterpret_cast<BufferDescriptorC *>(pointer)}; auto bufC{reinterpret_cast<BufferDescriptorC *>(pointer)};
if (bufC->address) { if (bufC->address) {
outputBuf.emplace_back(bufC->Pointer(), static_cast<u16>(bufC->size)); outputBuf.emplace_back(bufC->Pointer(), static_cast<u16>(bufC->size));
state.logger->Debug("Buf C: 0x{:X}, 0x{:X}", bufC->Pointer(), static_cast<u16>(bufC->size)); state.logger->Verbose("Buf C: 0x{:X}, 0x{:X}", bufC->Pointer(), static_cast<u16>(bufC->size));
} }
} else if (header->cFlag > BufferCFlag::SingleDescriptor) { } else if (header->cFlag > BufferCFlag::SingleDescriptor) {
for (u8 index{}; (static_cast<u8>(header->cFlag) - 2) > index; index++) { // (cFlag - 2) C descriptors are present for (u8 index{}; (static_cast<u8>(header->cFlag) - 2) > index; index++) { // (cFlag - 2) C descriptors are present
auto bufC{reinterpret_cast<BufferDescriptorC *>(pointer)}; auto bufC{reinterpret_cast<BufferDescriptorC *>(pointer)};
if (bufC->address) { if (bufC->address) {
outputBuf.emplace_back(bufC->Pointer(), static_cast<u16>(bufC->size)); outputBuf.emplace_back(bufC->Pointer(), static_cast<u16>(bufC->size));
state.logger->Debug("Buf C #{}: 0x{:X}, 0x{:X}", index, bufC->Pointer(), static_cast<u16>(bufC->size)); state.logger->Verbose("Buf C #{}: 0x{:X}, 0x{:X}", index, bufC->Pointer(), static_cast<u16>(bufC->size));
} }
pointer += sizeof(BufferDescriptorC); pointer += sizeof(BufferDescriptorC);
} }
} }
if (header->type == CommandType::Request || header->type == CommandType::RequestWithContext) { if (header->type == CommandType::Request || header->type == CommandType::RequestWithContext) {
state.logger->Debug("Header: Input No: {}, Output No: {}, Raw Size: {}", inputBuf.size(), outputBuf.size(), static_cast<u64>(cmdArgSz)); state.logger->Verbose("Header: Input No: {}, Output No: {}, Raw Size: {}", inputBuf.size(), outputBuf.size(), static_cast<u64>(cmdArgSz));
if (header->handleDesc) if (header->handleDesc)
state.logger->Debug("Handle Descriptor: Send PID: {}, Copy Count: {}, Move Count: {}", static_cast<bool>(handleDesc->sendPid), static_cast<u32>(handleDesc->copyCount), static_cast<u32>(handleDesc->moveCount)); state.logger->Verbose("Handle Descriptor: Send PID: {}, Copy Count: {}, Move Count: {}", static_cast<bool>(handleDesc->sendPid), static_cast<u32>(handleDesc->copyCount), static_cast<u32>(handleDesc->moveCount));
if (isDomain) if (isDomain)
state.logger->Debug("Domain Header: Command: {}, Input Object Count: {}, Object ID: 0x{:X}", domain->command, domain->inputCount, domain->objectId); state.logger->Verbose("Domain Header: Command: {}, Input Object Count: {}, Object ID: 0x{:X}", domain->command, domain->inputCount, domain->objectId);
state.logger->Debug("Command ID: 0x{:X}", static_cast<u32>(payload->value)); state.logger->Verbose("Command ID: 0x{:X}", static_cast<u32>(payload->value));
} }
} }
@ -183,6 +183,6 @@ namespace skyline::kernel::ipc {
} }
} }
state.logger->Debug("Output: Raw Size: {}, Command ID: 0x{:X}, Copy Handles: {}, Move Handles: {}", static_cast<u32>(header->rawSize), static_cast<u32>(payloadHeader->value), copyHandles.size(), moveHandles.size()); state.logger->Verbose("Output: Raw Size: {}, Command ID: 0x{:X}, Copy Handles: {}, Move Handles: {}", static_cast<u32>(header->rawSize), static_cast<u32>(payloadHeader->value), copyHandles.size(), moveHandles.size());
} }
} }

View File

@ -12,6 +12,7 @@ namespace skyline::kernel {
void MemoryManager::InitializeVmm(memory::AddressSpaceType type) { void MemoryManager::InitializeVmm(memory::AddressSpaceType type) {
switch (type) { switch (type) {
case memory::AddressSpaceType::AddressSpace32Bit: case memory::AddressSpaceType::AddressSpace32Bit:
case memory::AddressSpaceType::AddressSpace32BitNoReserved:
throw exception("32-bit address spaces are not supported"); throw exception("32-bit address spaces are not supported");
case memory::AddressSpaceType::AddressSpace36Bit: { case memory::AddressSpaceType::AddressSpace36Bit: {
@ -119,7 +120,7 @@ namespace skyline::kernel {
if (upper == chunks.begin()) if (upper == chunks.begin())
throw exception("InsertChunk: Chunk inserted outside address space: 0x{:X} - 0x{:X} and 0x{:X} - 0x{:X}", upper->ptr, upper->ptr + upper->size, chunk.ptr, chunk.ptr + chunk.size); throw exception("InsertChunk: Chunk inserted outside address space: 0x{:X} - 0x{:X} and 0x{:X} - 0x{:X}", upper->ptr, upper->ptr + upper->size, chunk.ptr, chunk.ptr + chunk.size);
upper = chunks.erase(upper, std::upper_bound(upper, chunks.end(), chunk.ptr + chunk.size, [](const u8 *ptr, const ChunkDescriptor &chunk) -> bool { return ptr < chunk.ptr; })); upper = chunks.erase(upper, std::upper_bound(upper, chunks.end(), chunk.ptr + chunk.size, [](const u8 *ptr, const ChunkDescriptor &chunk) -> bool { return ptr < chunk.ptr + chunk.size; }));
if (upper != chunks.end() && upper->ptr < chunk.ptr + chunk.size) { if (upper != chunks.end() && upper->ptr < chunk.ptr + chunk.size) {
auto end{upper->ptr + upper->size}; auto end{upper->ptr + upper->size};
upper->ptr = chunk.ptr + chunk.size; upper->ptr = chunk.ptr + chunk.size;

View File

@ -179,8 +179,9 @@ namespace skyline {
}; };
enum class AddressSpaceType : u8 { enum class AddressSpaceType : u8 {
AddressSpace32Bit, //!< 32-bit address space used by 32-bit applications AddressSpace32Bit = 0, //!< 32-bit address space used by 32-bit applications
AddressSpace36Bit, //!< 36-bit address space used by 64-bit applications before 2.0.0 AddressSpace36Bit = 1, //!< 36-bit address space used by 64-bit applications before 2.0.0
AddressSpace32BitNoReserved = 2, //!< 32-bit address space without the map region
AddressSpace39Bit, //!< 39-bit address space used by 64-bit applications after 2.0.0 AddressSpace39Bit, //!< 39-bit address space used by 64-bit applications after 2.0.0
}; };
} }

View File

@ -219,14 +219,26 @@ namespace skyline::kernel::svc {
exit(0); exit(0);
} }
constexpr i32 IdealCoreDontCare{-1};
constexpr i32 IdealCoreUseProcessValue{-2};
constexpr i32 IdealCoreNoUpdate{-3};
void CreateThread(const DeviceState &state) { void CreateThread(const DeviceState &state) {
auto entry{reinterpret_cast<void *>(state.ctx->gpr.x1)}; auto entry{reinterpret_cast<void *>(state.ctx->gpr.x1)};
auto entryArgument{state.ctx->gpr.x2}; auto entryArgument{state.ctx->gpr.x2};
auto stackTop{reinterpret_cast<u8 *>(state.ctx->gpr.x3)}; auto stackTop{reinterpret_cast<u8 *>(state.ctx->gpr.x3)};
auto priority{static_cast<i8>(static_cast<u32>(state.ctx->gpr.w4))}; auto priority{static_cast<i8>(static_cast<u32>(state.ctx->gpr.w4))};
auto idealCore{static_cast<i8>(static_cast<u32>(state.ctx->gpr.w5))};
idealCore = (idealCore == IdealCoreUseProcessValue) ? state.process->npdm.meta.idealCore : idealCore;
if (idealCore < 0 || idealCore >= constant::CoreCount) {
state.ctx->gpr.w0 = result::InvalidCoreId;
state.logger->Warn("svcCreateThread: 'idealCore' invalid: {}", idealCore);
return;
}
if (!constant::HosPriority.Valid(priority)) { if (!constant::HosPriority.Valid(priority)) {
state.ctx->gpr.w0 = result::InvalidAddress; state.ctx->gpr.w0 = result::InvalidPriority;
state.logger->Warn("svcCreateThread: 'priority' invalid: {}", priority); state.logger->Warn("svcCreateThread: 'priority' invalid: {}", priority);
return; return;
} }
@ -235,7 +247,7 @@ namespace skyline::kernel::svc {
if (!stack) if (!stack)
throw exception("svcCreateThread: Cannot find memory object in handle table for thread stack: 0x{:X}", stackTop); throw exception("svcCreateThread: Cannot find memory object in handle table for thread stack: 0x{:X}", stackTop);
auto thread{state.process->CreateThread(entry, entryArgument, stackTop, priority)}; auto thread{state.process->CreateThread(entry, entryArgument, stackTop, priority, idealCore)};
state.logger->Debug("svcCreateThread: Created thread with handle 0x{:X} (Entry Point: 0x{:X}, Argument: 0x{:X}, Stack Pointer: 0x{:X}, Priority: {}, ID: {})", thread->handle, entry, entryArgument, stackTop, priority, thread->id); state.logger->Debug("svcCreateThread: Created thread with handle 0x{:X} (Entry Point: 0x{:X}, Argument: 0x{:X}, Stack Pointer: 0x{:X}, Priority: {}, ID: {})", thread->handle, entry, entryArgument, stackTop, priority, thread->id);
state.ctx->gpr.w1 = thread->handle; state.ctx->gpr.w1 = thread->handle;
@ -283,8 +295,9 @@ namespace skyline::kernel::svc {
void GetThreadPriority(const DeviceState &state) { void GetThreadPriority(const DeviceState &state) {
KHandle handle{state.ctx->gpr.w1}; KHandle handle{state.ctx->gpr.w1};
try { try {
auto priority{state.process->GetHandle<type::KThread>(handle)->priority}; auto thread{state.process->GetHandle<type::KThread>(handle)};
state.logger->Debug("svcGetThreadPriority: Writing thread priority {}", priority); auto priority{thread->priority};
state.logger->Debug("svcGetThreadPriority: Retrieving thread #{}'s priority: {}", thread->id, priority);
state.ctx->gpr.w1 = priority; state.ctx->gpr.w1 = priority;
state.ctx->gpr.w0 = Result{}; state.ctx->gpr.w0 = Result{};
@ -297,10 +310,15 @@ namespace skyline::kernel::svc {
void SetThreadPriority(const DeviceState &state) { void SetThreadPriority(const DeviceState &state) {
KHandle handle{state.ctx->gpr.w0}; KHandle handle{state.ctx->gpr.w0};
u32 priority{state.ctx->gpr.w1}; u32 priority{state.ctx->gpr.w1};
if (!constant::HosPriority.Valid(priority)) {
state.logger->Warn("svcSetThreadPriority: 'priority' invalid: 0x{:X}", priority);
state.ctx->gpr.w0 = result::InvalidPriority;
return;
}
try { try {
state.logger->Debug("svcSetThreadPriority: Setting thread priority to {}", priority); auto thread{state.process->GetHandle<type::KThread>(handle)};
state.process->GetHandle<type::KThread>(handle)->UpdatePriority(static_cast<u8>(priority)); state.logger->Debug("svcSetThreadPriority: Setting thread priority to {}", thread->id, priority);
thread->UpdatePriority(static_cast<u8>(priority));
state.ctx->gpr.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) { } catch (const std::exception &) {
state.logger->Warn("svcSetThreadPriority: 'handle' invalid: 0x{:X}", handle); state.logger->Warn("svcSetThreadPriority: 'handle' invalid: 0x{:X}", handle);
@ -308,6 +326,62 @@ namespace skyline::kernel::svc {
} }
} }
void GetThreadCoreMask(const DeviceState &state) {
KHandle handle{state.ctx->gpr.w2};
try {
auto thread{state.process->GetHandle<type::KThread>(handle)};
auto idealCore{thread->idealCore};
auto affinityMask{thread->affinityMask};
state.logger->Debug("svcGetThreadCoreMask: Writing thread #{}'s Ideal Core ({}) + Affinity Mask ({})", thread->id, idealCore, affinityMask.to_string());
state.ctx->gpr.x2 = affinityMask.to_ullong();
state.ctx->gpr.w1 = idealCore;
state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) {
state.logger->Warn("svcGetThreadCoreMask: 'handle' invalid: 0x{:X}", handle);
state.ctx->gpr.w0 = result::InvalidHandle;
}
}
void SetThreadCoreMask(const DeviceState &state) {
KHandle handle{state.ctx->gpr.w0};
i32 idealCore{static_cast<i32>(state.ctx->gpr.w1)};
std::bitset<constant::CoreCount> affinityMask{state.ctx->gpr.x2};
try {
auto thread{state.process->GetHandle<type::KThread>(handle)};
if (idealCore == IdealCoreUseProcessValue) {
idealCore = state.process->npdm.meta.idealCore;
affinityMask.reset().set(idealCore);
} else if (idealCore == IdealCoreNoUpdate) {
idealCore = thread->idealCore;
} else if (idealCore == IdealCoreDontCare) {
idealCore = __builtin_ctzll(affinityMask.to_ullong()); // The first enabled core in the affinity mask
}
if (affinityMask.none() || !affinityMask.test(idealCore)) {
state.logger->Warn("svcSetThreadCoreMask: 'affinityMask' invalid: {} (Ideal Core: {})", affinityMask.to_string(), idealCore);
state.ctx->gpr.w0 = result::InvalidCombination;
return;
}
state.logger->Debug("svcSetThreadCoreMask: Setting thread #{}'s Ideal Core ({}) + Affinity Mask ({})", thread->id, idealCore, affinityMask.to_string());
thread->idealCore = idealCore;
thread->affinityMask = affinityMask;
state.ctx->gpr.w0 = Result{};
} catch (const std::exception &) {
state.logger->Warn("svcSetThreadCoreMask: 'handle' invalid: 0x{:X}", handle);
state.ctx->gpr.w0 = result::InvalidHandle;
}
}
void GetCurrentProcessorNumber(const DeviceState &state) {
state.logger->Debug("svcGetCurrentProcessorNumber: Writing current core for thread #{}: {}", state.thread->id, state.thread->coreId);
state.ctx->gpr.w0 = state.thread->coreId;
}
void ClearEvent(const DeviceState &state) { void ClearEvent(const DeviceState &state) {
auto object{state.process->GetHandle<type::KEvent>(state.ctx->gpr.w0)}; auto object{state.process->GetHandle<type::KEvent>(state.ctx->gpr.w0)};
object->signalled = false; object->signalled = false;
@ -612,18 +686,12 @@ namespace skyline::kernel::svc {
} }
void GetThreadId(const DeviceState &state) { void GetThreadId(const DeviceState &state) {
constexpr KHandle threadSelf{0xFFFF8000}; // The handle used by threads to refer to themselves
KHandle handle{state.ctx->gpr.w1}; KHandle handle{state.ctx->gpr.w1};
pid_t pid{}; size_t pid{state.process->GetHandle<type::KThread>(handle)->id};
if (handle != threadSelf)
pid = state.process->GetHandle<type::KThread>(handle)->id;
else
pid = state.thread->id;
state.logger->Debug("svcGetThreadId: Handle: 0x{:X}, PID: {}", handle, pid); state.logger->Debug("svcGetThreadId: Handle: 0x{:X}, PID: {}", handle, pid);
state.ctx->gpr.x1 = static_cast<u64>(pid); state.ctx->gpr.x1 = pid;
state.ctx->gpr.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} }
@ -706,7 +774,11 @@ namespace skyline::kernel::svc {
break; break;
case InfoState::TotalMemoryUsage: case InfoState::TotalMemoryUsage:
out = state.process->memory.GetMemoryUsage(); out = state.process->memory.GetMemoryUsage() + state.process->memory.GetKMemoryBlockSize();
break;
case InfoState::RandomEntropy:
out = util::GetTimeTicks();
break; break;
case InfoState::AddressSpaceBaseAddr: case InfoState::AddressSpaceBaseAddr:
@ -726,20 +798,20 @@ namespace skyline::kernel::svc {
break; break;
case InfoState::TotalSystemResourceAvailable: case InfoState::TotalSystemResourceAvailable:
out = totalPhysicalMemory; // TODO: NPDM specifies this in it's PersonalMmHeapSize field out = state.process->npdm.meta.systemResourceSize;
break; break;
case InfoState::TotalSystemResourceUsage: case InfoState::TotalSystemResourceUsage:
// A very rough approximation of what this should be on the Switch, the amount of memory allocated for storing the memory blocks (https://switchbrew.org/wiki/Kernel_objects#KMemoryBlockManager) // A very rough approximation of what this should be on the Switch, the amount of memory allocated for storing the memory blocks (https://switchbrew.org/wiki/Kernel_objects#KMemoryBlockManager)
out = state.process->memory.GetKMemoryBlockSize(); out = std::min(static_cast<size_t>(state.process->npdm.meta.systemResourceSize), state.process->memory.GetKMemoryBlockSize());
break; break;
case InfoState::TotalMemoryAvailableWithoutSystemResource: case InfoState::TotalMemoryAvailableWithoutSystemResource:
out = totalPhysicalMemory; // TODO: Subtract TotalSystemResourceAvailable from this out = totalPhysicalMemory - state.process->npdm.meta.systemResourceSize;
break; break;
case InfoState::TotalMemoryUsageWithoutSystemResource: case InfoState::TotalMemoryUsageWithoutSystemResource:
out = state.process->memory.GetMemoryUsage(); out = state.process->memory.GetMemoryUsage(); // Our regular estimates don't contain the system resources
break; break;
case InfoState::UserExceptionContextAddr: case InfoState::UserExceptionContextAddr:

View File

@ -78,6 +78,24 @@ namespace skyline::kernel::svc {
*/ */
void SetThreadPriority(const DeviceState &state); void SetThreadPriority(const DeviceState &state);
/**
* @brief Get core mask of provided thread handle
* @url https://switchbrew.org/wiki/SVC#GetThreadCoreMask
*/
void GetThreadCoreMask(const DeviceState &state);
/**
* @brief Set core mask of provided thread handle
* @url https://switchbrew.org/wiki/SVC#SetThreadCoreMask
*/
void SetThreadCoreMask(const DeviceState &state);
/**
* @brief Returns the core on which the current thread is running
* @url https://switchbrew.org/wiki/SVC#GetCurrentProcessorNumber
*/
void GetCurrentProcessorNumber(const DeviceState &state);
/** /**
* @brief Clears a KEvent of it's signal * @brief Clears a KEvent of it's signal
* @url https://switchbrew.org/wiki/SVC#ClearEvent * @url https://switchbrew.org/wiki/SVC#ClearEvent
@ -210,9 +228,9 @@ namespace skyline::kernel::svc {
SleepThread, // 0x0B SleepThread, // 0x0B
GetThreadPriority, // 0x0C GetThreadPriority, // 0x0C
SetThreadPriority, // 0x0D SetThreadPriority, // 0x0D
nullptr, // 0x0E GetThreadCoreMask, // 0x0E
nullptr, // 0x0F SetThreadCoreMask, // 0x0F
nullptr, // 0x10 GetCurrentProcessorNumber, // 0x10
nullptr, // 0x11 nullptr, // 0x11
ClearEvent, // 0x12 ClearEvent, // 0x12
MapSharedMemory, // 0x13 MapSharedMemory, // 0x13

View File

@ -49,15 +49,14 @@ namespace skyline::kernel::type {
return tlsPage->ReserveSlot(); return tlsPage->ReserveSlot();
} }
std::shared_ptr<KThread> KProcess::CreateThread(void *entry, u64 argument, void *stackTop, i8 priority) { std::shared_ptr<KThread> KProcess::CreateThread(void *entry, u64 argument, void *stackTop, i8 priority, i8 idealCore) {
if (!stackTop && threads.empty()) { //!< Main thread stack is created by the kernel and owned by the process if (!stackTop && threads.empty()) { //!< Main thread stack is created by the kernel and owned by the process
constexpr u64 DefaultStackSize{0x200000}; //!< The default amount of stack: 2 MB mainThreadStack = mainThreadStack.make_shared(state, reinterpret_cast<u8 *>(state.process->memory.stack.address), state.process->npdm.meta.mainThreadStackSize, memory::Permission{true, true, false}, memory::states::Stack);
mainThreadStack = mainThreadStack.make_shared(state, reinterpret_cast<u8 *>(state.process->memory.stack.address), DefaultStackSize, memory::Permission{true, true, false}, memory::states::Stack);
if (mprotect(mainThreadStack->ptr, PAGE_SIZE, PROT_NONE)) if (mprotect(mainThreadStack->ptr, PAGE_SIZE, PROT_NONE))
throw exception("Failed to create guard page for thread stack at 0x{:X}", mainThreadStack->ptr); throw exception("Failed to create guard page for thread stack at 0x{:X}", mainThreadStack->ptr);
stackTop = mainThreadStack->ptr + mainThreadStack->size; stackTop = mainThreadStack->ptr + mainThreadStack->size;
} }
auto thread{NewHandle<KThread>(this, threads.size(), entry, argument, stackTop, priority).item}; auto thread{NewHandle<KThread>(this, threads.size(), entry, argument, stackTop, (priority == -1) ? state.process->npdm.meta.mainThreadPriority : priority, (idealCore == -1) ? state.process->npdm.meta.idealCore : idealCore).item};
threads.push_back(thread); threads.push_back(thread);
return thread; return thread;
} }

View File

@ -5,6 +5,7 @@
#include <list> #include <list>
#include <kernel/memory.h> #include <kernel/memory.h>
#include <vfs/npdm.h>
#include "KThread.h" #include "KThread.h"
#include "KTransferMemory.h" #include "KTransferMemory.h"
#include "KSession.h" #include "KSession.h"
@ -70,6 +71,7 @@ namespace skyline {
std::shared_ptr<KPrivateMemory> heap; std::shared_ptr<KPrivateMemory> heap;
std::vector<std::shared_ptr<KThread>> threads; std::vector<std::shared_ptr<KThread>> threads;
std::vector<std::shared_ptr<TlsPage>> tlsPages; std::vector<std::shared_ptr<TlsPage>> tlsPages;
vfs::NPDM npdm;
KProcess(const DeviceState &state); KProcess(const DeviceState &state);
@ -83,7 +85,10 @@ namespace skyline {
*/ */
u8* AllocateTlsSlot(); u8* AllocateTlsSlot();
std::shared_ptr<KThread> CreateThread(void *entry, u64 argument = 0, void *stackTop = nullptr, i8 priority = constant::DefaultPriority); /**
* @note The default values are for the main thread and will use values from the NPDM
*/
std::shared_ptr<KThread> CreateThread(void *entry, u64 argument = 0, void *stackTop = nullptr, i8 priority = -1, i8 idealCore = -1);
/** /**
* @brief The output for functions that return created kernel objects * @brief The output for functions that return created kernel objects
@ -130,9 +135,12 @@ namespace skyline {
std::shared_lock lock(handleMutex); std::shared_lock lock(handleMutex);
KType objectType; KType objectType;
if constexpr(std::is_same<objectClass, KThread>()) if constexpr(std::is_same<objectClass, KThread>()) {
constexpr KHandle threadSelf{0xFFFF8000}; // The handle used by threads to refer to themselves
if (handle == threadSelf)
return state.thread;
objectType = KType::KThread; objectType = KType::KThread;
else if constexpr(std::is_same<objectClass, KProcess>()) } else if constexpr(std::is_same<objectClass, KProcess>())
objectType = KType::KProcess; objectType = KType::KProcess;
else if constexpr(std::is_same<objectClass, KSharedMemory>()) else if constexpr(std::is_same<objectClass, KSharedMemory>())
objectType = KType::KSharedMemory; objectType = KType::KSharedMemory;

View File

@ -12,7 +12,8 @@
#include "KProcess.h" #include "KProcess.h"
namespace skyline::kernel::type { namespace skyline::kernel::type {
KThread::KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, i8 priority) : handle(handle), parent(parent), id(id), entry(entry), entryArgument(argument), stackTop(stackTop), KSyncObject(state, KType::KThread) { KThread::KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, i8 priority, i8 idealCore) : handle(handle), parent(parent), id(id), entry(entry), entryArgument(argument), stackTop(stackTop), idealCore(idealCore), coreId(idealCore), KSyncObject(state, KType::KThread) {
affinityMask.set(coreId);
UpdatePriority(priority); UpdatePriority(priority);
} }

View File

@ -34,7 +34,7 @@ namespace skyline {
} }
namespace constant { namespace constant {
constexpr i8 DefaultPriority{44}; // The default priority of an HOS process constexpr u8 CoreCount{4}; // The amount of cores an HOS process can be scheduled onto (User applications can only be on the first 3 cores, the last one is reserved for the system)
constexpr kernel::type::Priority AndroidPriority{19, -8}; //!< The range of priorities for Android constexpr kernel::type::Priority AndroidPriority{19, -8}; //!< The range of priorities for Android
constexpr kernel::type::Priority HosPriority{0, 63}; //!< The range of priorities for Horizon OS constexpr kernel::type::Priority HosPriority{0, 63}; //!< The range of priorities for Horizon OS
} }
@ -62,9 +62,13 @@ namespace skyline {
void* entry; void* entry;
u64 entryArgument; u64 entryArgument;
void* stackTop; void* stackTop;
i8 priority;
KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, i8 priority = constant::DefaultPriority); i8 priority;
i8 idealCore;
i8 coreId; //!< The CPU core on which this thread is running
std::bitset<constant::CoreCount> affinityMask{}; //!< The CPU core on which this thread is running
KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, i8 priority, i8 idealCore);
~KThread(); ~KThread();

View File

@ -66,8 +66,8 @@ namespace skyline::loader {
static ExecutableLoadInfo LoadExecutable(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, Executable &executable, size_t offset = 0); static ExecutableLoadInfo LoadExecutable(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, Executable &executable, size_t offset = 0);
public: public:
std::shared_ptr<vfs::NACP> nacp; //!< The NACP of the current application std::optional<vfs::NACP> nacp;
std::shared_ptr<vfs::Backing> romFs; //!< The RomFS of the current application std::shared_ptr<vfs::Backing> romFs;
virtual ~Loader() = default; virtual ~Loader() = default;

View File

@ -26,7 +26,7 @@ namespace skyline::loader {
u8* base{loadInfo.base}; u8* base{loadInfo.base};
void* entry{loadInfo.entry}; void* entry{loadInfo.entry};
state.logger->Info("Loaded nso 'rtld' at 0x{:X}", base); state.logger->Info("Loaded nso 'rtld' at 0x{:X} (.text @ 0x{:X})", base, entry);
for (const auto &nso : {"main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { for (const auto &nso : {"main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) {
nsoFile = exeFs->OpenFile(nso); nsoFile = exeFs->OpenFile(nso);
@ -35,7 +35,7 @@ namespace skyline::loader {
continue; continue;
loadInfo = NsoLoader::LoadNso(nsoFile, process, state, offset); loadInfo = NsoLoader::LoadNso(nsoFile, process, state, offset);
state.logger->Info("Loaded nso '{}' at 0x{:X}", nso, base + offset); state.logger->Info("Loaded '{}.nso' at 0x{:X} (.text @ 0x{:X})", nso, base + offset, loadInfo.entry);
offset += loadInfo.size; offset += loadInfo.size;
} }
@ -45,6 +45,7 @@ namespace skyline::loader {
} }
void* NcaLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) { void* NcaLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
process->npdm = vfs::NPDM(nca.exeFs->OpenFile("main.npdm"));
return LoadExeFs(nca.exeFs, process, state); return LoadExeFs(nca.exeFs, process, state);
} }
} }

View File

@ -22,7 +22,7 @@ namespace skyline::loader {
throw exception("Invalid ASET magic! 0x{0:X}", assetHeader.magic); throw exception("Invalid ASET magic! 0x{0:X}", assetHeader.magic);
NroAssetSection &nacpHeader{assetHeader.nacp}; NroAssetSection &nacpHeader{assetHeader.nacp};
nacp = std::make_shared<vfs::NACP>(std::make_shared<vfs::RegionBacking>(backing, header.size + nacpHeader.offset, nacpHeader.size)); nacp.emplace(std::make_shared<vfs::RegionBacking>(backing, header.size + nacpHeader.offset, nacpHeader.size));
NroAssetSection &romFsHeader{assetHeader.romFs}; NroAssetSection &romFsHeader{assetHeader.romFs};
romFs = std::make_shared<vfs::RegionBacking>(backing, header.size + romFsHeader.offset, romFsHeader.size); romFs = std::make_shared<vfs::RegionBacking>(backing, header.size + romFsHeader.offset, romFsHeader.size);

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <kernel/types/KProcess.h>
#include "nca.h" #include "nca.h"
#include "nsp.h" #include "nsp.h"
@ -31,10 +32,11 @@ namespace skyline::loader {
romFs = programNca->romFs; romFs = programNca->romFs;
controlRomFs = std::make_shared<vfs::RomFileSystem>(controlNca->romFs); controlRomFs = std::make_shared<vfs::RomFileSystem>(controlNca->romFs);
nacp = std::make_shared<vfs::NACP>(controlRomFs->OpenFile("control.nacp")); nacp.emplace(controlRomFs->OpenFile("control.nacp"));
} }
void* NspLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) { void* NspLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
process->npdm = vfs::NPDM(programNca->exeFs->OpenFile("main.npdm"));
return NcaLoader::LoadExeFs(programNca->exeFs, process, state); return NcaLoader::LoadExeFs(programNca->exeFs, process, state);
} }

View File

@ -136,8 +136,8 @@ namespace skyline::service {
void ServiceManager::SyncRequestHandler(KHandle handle) { void ServiceManager::SyncRequestHandler(KHandle handle) {
auto session{state.process->GetHandle<type::KSession>(handle)}; auto session{state.process->GetHandle<type::KSession>(handle)};
state.logger->Debug("----Start----"); state.logger->Verbose("----IPC Start----");
state.logger->Debug("Handle is 0x{:X}", handle); state.logger->Verbose("Handle is 0x{:X}", handle);
if (session->isOpen) { if (session->isOpen) {
ipc::IpcRequest request(session->isDomain, state); ipc::IpcRequest request(session->isDomain, state);
@ -205,6 +205,6 @@ namespace skyline::service {
} else { } else {
state.logger->Warn("svcSendSyncRequest called on closed handle: 0x{:X}", handle); state.logger->Warn("svcSendSyncRequest called on closed handle: 0x{:X}", handle);
} }
state.logger->Debug("====End===="); state.logger->Verbose("====IPC End====");
} }
} }

View File

@ -0,0 +1,43 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <kernel/types/KThread.h>
#include "npdm.h"
namespace skyline::vfs {
constexpr u32 MetaMagic = util::MakeMagic<u32>("META");
NPDM::NPDM() {
constexpr i8 DefaultPriority{44}; // The default priority of an HOS process
constexpr i8 DefaultCore{0}; // The default core for an HOS process
constexpr u64 DefaultStackSize{0x200000}; //!< The default amount of stack: 2 MiB
constexpr u64 DefaultSystemResourceSize{0x1FE00000}; //!< The amount of memory reserved for system resources, it's the maximum at 510 MiB
meta = {
.magic = MetaMagic,
.flags = {
{
.is64Bit = true,
.type = memory::AddressSpaceType::AddressSpace39Bit,
.optimizeMemoryAllocation = false,
}
},
.mainThreadPriority = DefaultPriority,
.idealCore = DefaultCore,
.mainThreadStackSize = DefaultStackSize,
.systemResourceSize = DefaultSystemResourceSize,
.name = "Application",
};
}
NPDM::NPDM(const std::shared_ptr<vfs::Backing> &backing) {
meta = backing->Read<NpdmMeta>();
if(meta.magic != MetaMagic)
throw exception("NPDM Meta Magic isn't correct: 0x{:X} (\"META\" = 0x{:X})", meta.magic, MetaMagic);
if (!constant::HosPriority.Valid(meta.mainThreadPriority))
throw exception("NPDM Main Thread Priority isn't valid: {}", meta.mainThreadStackSize);
if (meta.idealCore >= constant::CoreCount)
throw exception("NPDM Ideal Core isn't valid: {}", meta.idealCore);
if (!util::PageAligned(meta.mainThreadStackSize))
throw exception("NPDM Main Thread Stack isn't page aligned: 0x{:X}", meta.mainThreadStackSize);
}
}

View File

@ -0,0 +1,54 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include "backing.h"
#include <kernel/memory.h>
namespace skyline::vfs {
/**
* @url https://switchbrew.org/wiki/NPDM
*/
class NPDM {
public:
/**
* @url https://switchbrew.org/wiki/NPDM#META
*/
struct __attribute__((packed)) NpdmMeta {
u32 magic; //!< "META"
u32 acidSignatureKeyGeneration;
u32 _unk0_;
union {
struct {
bool is64Bit : 1;
memory::AddressSpaceType type : 2;
bool optimizeMemoryAllocation : 1;
};
u8 raw{};
} flags;
u8 _unk1_;
u8 mainThreadPriority;
u8 idealCore;
u32 _unk2_;
u32 systemResourceSize; //!< 3.0.0+
u32 version;
u32 mainThreadStackSize;
std::array<char, 0x10> name; //!< "Application"
std::array<u8, 0x10> productCode;
u8 _unk3_[0x30];
u32 aciOffset;
u32 aciSize;
u32 acidOffset;
u32 acidSize;
} meta;
static_assert(sizeof(NpdmMeta::flags) == sizeof(u8));
static_assert(sizeof(NpdmMeta) == 0x80);
public:
NPDM();
NPDM(const std::shared_ptr<vfs::Backing> &backing);
};
}

View File

@ -5,12 +5,14 @@
<item>Warn</item> <item>Warn</item>
<item>Info</item> <item>Info</item>
<item>Debug</item> <item>Debug</item>
<item>Verbose</item>
</string-array> </string-array>
<string-array name="log_level_val"> <string-array name="log_level_val">
<item>0</item> <item>0</item>
<item>1</item> <item>1</item>
<item>2</item> <item>2</item>
<item>3</item> <item>3</item>
<item>4</item>
</string-array> </string-array>
<string-array name="layout_type"> <string-array name="layout_type">
<item>List</item> <item>List</item>