mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-16 02:49:17 +01:00
Implement NSO loader
The NSO format is used by all retail games and some homebrew. It supports compressing sections with lz4 and dynamic linking through the use of rtld.
This commit is contained in:
parent
bf46293fc7
commit
3a23ec06a4
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -11,3 +11,6 @@
|
|||||||
[submodule "app/libraries/vkhpp"]
|
[submodule "app/libraries/vkhpp"]
|
||||||
path = app/libraries/vkhpp
|
path = app/libraries/vkhpp
|
||||||
url = https://github.com/skyline-emu/vkhpp
|
url = https://github.com/skyline-emu/vkhpp
|
||||||
|
[submodule "app/libraries/lz4"]
|
||||||
|
path = app/libraries/lz4
|
||||||
|
url = https://github.com/lz4/lz4.git
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/app/libraries/fmt" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$/app/libraries/fmt" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/app/libraries/lz4" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/app/libraries/oboe" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$/app/libraries/oboe" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/app/libraries/tinyxml2" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$/app/libraries/tinyxml2" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/app/libraries/vkhpp" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$/app/libraries/vkhpp" vcs="Git" />
|
||||||
|
@ -18,6 +18,8 @@ set(CMAKE_POLICY_DEFAULT_CMP0048 OLD)
|
|||||||
add_subdirectory("libraries/tinyxml2")
|
add_subdirectory("libraries/tinyxml2")
|
||||||
add_subdirectory("libraries/fmt")
|
add_subdirectory("libraries/fmt")
|
||||||
add_subdirectory("libraries/oboe")
|
add_subdirectory("libraries/oboe")
|
||||||
|
add_subdirectory("libraries/lz4/contrib/cmake_unofficial")
|
||||||
|
include_directories("libraries/lz4/lib")
|
||||||
include_directories("libraries/oboe/include")
|
include_directories("libraries/oboe/include")
|
||||||
include_directories("libraries/vkhpp/include")
|
include_directories("libraries/vkhpp/include")
|
||||||
set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
|
set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
|
||||||
@ -40,6 +42,7 @@ add_library(skyline SHARED
|
|||||||
${source_DIR}/skyline/os.cpp
|
${source_DIR}/skyline/os.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/kernel/memory.cpp
|
${source_DIR}/skyline/kernel/memory.cpp
|
||||||
${source_DIR}/skyline/kernel/ipc.cpp
|
${source_DIR}/skyline/kernel/ipc.cpp
|
||||||
${source_DIR}/skyline/kernel/svc.cpp
|
${source_DIR}/skyline/kernel/svc.cpp
|
||||||
@ -101,5 +104,5 @@ add_library(skyline SHARED
|
|||||||
${source_DIR}/skyline/vfs/nacp.cpp
|
${source_DIR}/skyline/vfs/nacp.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(skyline vulkan android fmt tinyxml2 oboe)
|
target_link_libraries(skyline vulkan android fmt tinyxml2 oboe lz4_static)
|
||||||
target_compile_options(skyline PRIVATE -Wno-c++17-extensions)
|
target_compile_options(skyline PRIVATE -Wno-c++17-extensions)
|
||||||
|
@ -212,6 +212,7 @@ namespace skyline {
|
|||||||
|
|
||||||
namespace loader {
|
namespace loader {
|
||||||
class NroLoader;
|
class NroLoader;
|
||||||
|
class NsoLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
@ -322,6 +323,7 @@ namespace skyline {
|
|||||||
friend class type::KTransferMemory;
|
friend class type::KTransferMemory;
|
||||||
friend class type::KProcess;
|
friend class type::KProcess;
|
||||||
friend class loader::NroLoader;
|
friend class loader::NroLoader;
|
||||||
|
friend class loader::NsoLoader;
|
||||||
|
|
||||||
friend void svc::SetMemoryAttribute(DeviceState &state);
|
friend void svc::SetMemoryAttribute(DeviceState &state);
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ namespace skyline::loader {
|
|||||||
*/
|
*/
|
||||||
enum class RomFormat {
|
enum class RomFormat {
|
||||||
NRO, //!< The NRO format: https://switchbrew.org/wiki/NRO
|
NRO, //!< The NRO format: https://switchbrew.org/wiki/NRO
|
||||||
|
NSO, //!< The NSO format: https://switchbrew.org/wiki/NSO
|
||||||
XCI, //!< The XCI format: https://switchbrew.org/wiki/XCI
|
XCI, //!< The XCI format: https://switchbrew.org/wiki/XCI
|
||||||
NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws
|
NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws
|
||||||
};
|
};
|
||||||
|
65
app/src/main/cpp/skyline/loader/nso.cpp
Normal file
65
app/src/main/cpp/skyline/loader/nso.cpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
|
#include <lz4.h>
|
||||||
|
#include <nce.h>
|
||||||
|
#include <os.h>
|
||||||
|
#include <kernel/memory.h>
|
||||||
|
#include "nso.h"
|
||||||
|
|
||||||
|
namespace skyline::loader {
|
||||||
|
NsoLoader::NsoLoader(const std::shared_ptr<vfs::Backing> &backing) : Loader(backing) {
|
||||||
|
u32 magic{};
|
||||||
|
backing->Read(&magic);
|
||||||
|
|
||||||
|
if (magic != util::MakeMagic<u32>("NSO0"))
|
||||||
|
throw exception("Invalid NSO magic! 0x{0:X}", magic);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> NsoLoader::GetSegment(const std::shared_ptr<vfs::Backing> &backing, const NsoSegmentHeader &segment, u32 compressedSize) {
|
||||||
|
std::vector<u8> outputBuffer(segment.decompressedSize);
|
||||||
|
|
||||||
|
if (compressedSize) {
|
||||||
|
std::vector<u8> compressedBuffer(compressedSize);
|
||||||
|
backing->Read(compressedBuffer.data(), segment.fileOffset, compressedSize);
|
||||||
|
|
||||||
|
LZ4_decompress_safe(reinterpret_cast<char *>(compressedBuffer.data()), reinterpret_cast<char *>(outputBuffer.data()), compressedSize, segment.decompressedSize);
|
||||||
|
} else {
|
||||||
|
backing->Read(outputBuffer.data(), segment.fileOffset, segment.decompressedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader::ExecutableLoadInfo NsoLoader::LoadNso(const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, size_t offset) {
|
||||||
|
NsoHeader header{};
|
||||||
|
backing->Read(&header);
|
||||||
|
|
||||||
|
if (header.magic != util::MakeMagic<u32>("NSO0"))
|
||||||
|
throw exception("Invalid NSO magic! 0x{0:X}", header.magic);
|
||||||
|
|
||||||
|
Executable nsoExecutable{};
|
||||||
|
|
||||||
|
nsoExecutable.text.contents = GetSegment(backing, header.text, header.flags.textCompressed ? header.textCompressedSize : 0);
|
||||||
|
nsoExecutable.text.contents.resize(util::AlignUp(nsoExecutable.text.contents.size(), PAGE_SIZE));
|
||||||
|
nsoExecutable.text.offset = header.text.memoryOffset;
|
||||||
|
|
||||||
|
nsoExecutable.ro.contents = GetSegment(backing, header.ro, header.flags.textCompressed ? header.textCompressedSize : 0);
|
||||||
|
nsoExecutable.ro.contents.resize(util::AlignUp(nsoExecutable.ro.contents.size(), PAGE_SIZE));
|
||||||
|
nsoExecutable.ro.offset = header.ro.memoryOffset;
|
||||||
|
|
||||||
|
nsoExecutable.data.contents = GetSegment(backing, header.data, header.flags.textCompressed ? header.textCompressedSize : 0);
|
||||||
|
nsoExecutable.data.contents.resize(util::AlignUp(nsoExecutable.data.contents.size(), PAGE_SIZE));
|
||||||
|
nsoExecutable.data.offset = header.data.memoryOffset;
|
||||||
|
|
||||||
|
nsoExecutable.bssSize = util::AlignUp(header.bssSize, PAGE_SIZE);
|
||||||
|
|
||||||
|
return LoadExecutable(process, state, nsoExecutable, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NsoLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) {
|
||||||
|
auto loadInfo = LoadNso(backing, process, state);
|
||||||
|
|
||||||
|
state.os->memory.InitializeRegions(loadInfo.base, loadInfo.size, memory::AddressSpaceType::AddressSpace39Bit);
|
||||||
|
}
|
||||||
|
}
|
92
app/src/main/cpp/skyline/loader/nso.h
Normal file
92
app/src/main/cpp/skyline/loader/nso.h
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <common.h>
|
||||||
|
#include "loader.h"
|
||||||
|
|
||||||
|
namespace skyline::loader {
|
||||||
|
/**
|
||||||
|
* @brief The NsoLoader class abstracts access to an NSO file through the Loader interface (https://switchbrew.org/wiki/NSO)
|
||||||
|
*/
|
||||||
|
class NsoLoader : public Loader {
|
||||||
|
private:
|
||||||
|
union NsoFlags {
|
||||||
|
struct {
|
||||||
|
bool textCompressed : 1; //!< .text is compressed
|
||||||
|
bool roCompressed : 1; //!< .rodata is compressed
|
||||||
|
bool dataCompressed : 1; //!< .data is compressed
|
||||||
|
bool textHash : 1; //!< .text hash should be checked before loading
|
||||||
|
bool roHash : 1; //!< .rodata hash should be checked before loading
|
||||||
|
bool dataHash : 1; //!< .data hash should be checked before loading
|
||||||
|
};
|
||||||
|
u32 raw; //!< The raw value of the flags
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NsoFlags) == 0x4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This holds a single data segment's offset, loading offset and size
|
||||||
|
*/
|
||||||
|
struct NsoSegmentHeader {
|
||||||
|
u32 fileOffset; //!< The offset of the segment in the NSO
|
||||||
|
u32 memoryOffset; //!< The memory offset where the region should be loaded
|
||||||
|
u32 decompressedSize; //!< Size of the region after decompression
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NsoSegmentHeader) == 0xc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This holds the header of an NSO file
|
||||||
|
*/
|
||||||
|
struct NsoHeader {
|
||||||
|
u32 magic; //!< The NSO magic "NSO0"
|
||||||
|
u32 version; //!< The version of the application
|
||||||
|
u32 _pad0_;
|
||||||
|
NsoFlags flags; //!< The flags used with the NSO
|
||||||
|
|
||||||
|
NsoSegmentHeader text; //!< The .text segment header
|
||||||
|
u32 modOffset; //!< The offset of the MOD metadata
|
||||||
|
NsoSegmentHeader ro; //!< The .rodata segment header
|
||||||
|
u32 modSize; //!< The size of the MOD metadata
|
||||||
|
NsoSegmentHeader data; //!< The .data segment header
|
||||||
|
u32 bssSize; //!< The size of the .bss segment
|
||||||
|
|
||||||
|
u64 buildId[4]; //!< The build ID of the NSO
|
||||||
|
|
||||||
|
u32 textCompressedSize; //!< The size of the compressed .text segment
|
||||||
|
u32 roCompressedSize; //!< The size of the compressed .rodata segment
|
||||||
|
u32 dataCompressedSize; //!< The size of the compressed .data segment
|
||||||
|
|
||||||
|
u32 _pad1_[7];
|
||||||
|
|
||||||
|
u64 apiInfo; //!< The .rodata-relative offset of .apiInfo
|
||||||
|
u64 dynstr; //!< The .rodata-relative offset of .dynstr
|
||||||
|
u64 dynsym; //!< The .rodata-relative offset of .dynsym
|
||||||
|
|
||||||
|
u64 segmentHashes[3][4]; //!< The SHA256 checksums of the .text, .rodata and .data segments
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NsoHeader) == 0x100);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This reads the specified segment from the backing and decompresses it if needed
|
||||||
|
* @param segment The header of the segment to read
|
||||||
|
* @param compressedSize The compressed size of the segment, 0 if the segment is not compressed
|
||||||
|
* @return A buffer containing the data of the requested segment
|
||||||
|
*/
|
||||||
|
static std::vector<u8> GetSegment(const std::shared_ptr<vfs::Backing> &backing, const NsoSegmentHeader &segment, u32 compressedSize);
|
||||||
|
|
||||||
|
public:
|
||||||
|
NsoLoader(const std::shared_ptr<vfs::Backing> &backing);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This loads an NSO into memory, offset by the given amount
|
||||||
|
* @param backing The backing of the NSO
|
||||||
|
* @param process The process to load the NSO into
|
||||||
|
* @param offset The offset from the base address to place the NSO
|
||||||
|
* @return An ExecutableLoadInfo struct containing the load base and size
|
||||||
|
*/
|
||||||
|
static ExecutableLoadInfo LoadNso(const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, size_t offset = 0);
|
||||||
|
|
||||||
|
void LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state);
|
||||||
|
};
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "vfs/os_backing.h"
|
#include "vfs/os_backing.h"
|
||||||
#include "loader/nro.h"
|
#include "loader/nro.h"
|
||||||
|
#include "loader/nso.h"
|
||||||
#include "nce/guest.h"
|
#include "nce/guest.h"
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
|
|
||||||
@ -14,8 +15,11 @@ namespace skyline::kernel {
|
|||||||
|
|
||||||
if (romType == loader::RomFormat::NRO) {
|
if (romType == loader::RomFormat::NRO) {
|
||||||
state.loader = std::make_shared<loader::NroLoader>(romFile);
|
state.loader = std::make_shared<loader::NroLoader>(romFile);
|
||||||
} else
|
} else if (romType == loader::RomFormat::NSO) {
|
||||||
|
state.loader = std::make_shared<loader::NsoLoader>(romFile);
|
||||||
|
} else {
|
||||||
throw exception("Unsupported ROM extension.");
|
throw exception("Unsupported ROM extension.");
|
||||||
|
}
|
||||||
|
|
||||||
auto process = CreateProcess(constant::BaseAddress, 0, constant::DefStackSize);
|
auto process = CreateProcess(constant::BaseAddress, 0, constant::DefStackSize);
|
||||||
state.loader->LoadProcessData(process, state);
|
state.loader->LoadProcessData(process, state);
|
||||||
|
@ -78,7 +78,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
romFd.close();
|
romFd.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,10 +111,11 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick
|
|||||||
try {
|
try {
|
||||||
runOnUiThread { adapter.clear() }
|
runOnUiThread { adapter.clear() }
|
||||||
|
|
||||||
val foundNros = addEntries("nro", RomFormat.NRO, DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!)
|
var foundRoms = addEntries("nro", RomFormat.NRO, DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!)
|
||||||
|
foundRoms = foundRoms or addEntries("nso", RomFormat.NSO, DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!)
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (!foundNros)
|
if (!foundRoms)
|
||||||
adapter.addHeader(getString(R.string.no_rom))
|
adapter.addHeader(getString(R.string.no_rom))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -24,8 +24,9 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
enum class RomFormat(val format: Int){
|
enum class RomFormat(val format: Int){
|
||||||
NRO(0),
|
NRO(0),
|
||||||
XCI(1),
|
NSO(1),
|
||||||
NSP(2),
|
XCI(2),
|
||||||
|
NSP(3),
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -172,7 +173,7 @@ internal class RomFile(val context : Context, val format : RomFormat, val file :
|
|||||||
private external fun destroy(instance : Long)
|
private external fun destroy(instance : Long)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is used to get the [AppEntry] for the specified NRO
|
* This is used to get the [AppEntry] for the specified ROM
|
||||||
*/
|
*/
|
||||||
fun getAppEntry(uri : Uri) : AppEntry {
|
fun getAppEntry(uri : Uri) : AppEntry {
|
||||||
return if (hasAssets(instance)) {
|
return if (hasAssets(instance)) {
|
||||||
|
Loading…
Reference in New Issue
Block a user