diff --git a/.gitmodules b/.gitmodules
index 4b7ec23e..d07594aa 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -11,3 +11,6 @@
[submodule "app/libraries/vkhpp"]
path = app/libraries/vkhpp
url = https://github.com/skyline-emu/vkhpp
+[submodule "app/libraries/lz4"]
+ path = app/libraries/lz4
+ url = https://github.com/lz4/lz4.git
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index c458539a..8bfaf805 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -3,8 +3,9 @@
+
-
\ No newline at end of file
+
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 2a8c4bc6..0d2a00ab 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -18,6 +18,8 @@ set(CMAKE_POLICY_DEFAULT_CMP0048 OLD)
add_subdirectory("libraries/tinyxml2")
add_subdirectory("libraries/fmt")
add_subdirectory("libraries/oboe")
+add_subdirectory("libraries/lz4/contrib/cmake_unofficial")
+include_directories("libraries/lz4/lib")
include_directories("libraries/oboe/include")
include_directories("libraries/vkhpp/include")
set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
@@ -40,6 +42,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/os.cpp
${source_DIR}/skyline/loader/loader.cpp
${source_DIR}/skyline/loader/nro.cpp
+ ${source_DIR}/skyline/loader/nso.cpp
${source_DIR}/skyline/kernel/memory.cpp
${source_DIR}/skyline/kernel/ipc.cpp
${source_DIR}/skyline/kernel/svc.cpp
@@ -101,5 +104,5 @@ add_library(skyline SHARED
${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)
diff --git a/app/src/main/cpp/skyline/kernel/memory.h b/app/src/main/cpp/skyline/kernel/memory.h
index 229fd087..3fb091cd 100644
--- a/app/src/main/cpp/skyline/kernel/memory.h
+++ b/app/src/main/cpp/skyline/kernel/memory.h
@@ -212,6 +212,7 @@ namespace skyline {
namespace loader {
class NroLoader;
+ class NsoLoader;
}
namespace kernel {
@@ -322,6 +323,7 @@ namespace skyline {
friend class type::KTransferMemory;
friend class type::KProcess;
friend class loader::NroLoader;
+ friend class loader::NsoLoader;
friend void svc::SetMemoryAttribute(DeviceState &state);
diff --git a/app/src/main/cpp/skyline/loader/loader.h b/app/src/main/cpp/skyline/loader/loader.h
index 27b5bb81..7c3cb08c 100644
--- a/app/src/main/cpp/skyline/loader/loader.h
+++ b/app/src/main/cpp/skyline/loader/loader.h
@@ -14,6 +14,7 @@ namespace skyline::loader {
*/
enum class RomFormat {
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
NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws
};
diff --git a/app/src/main/cpp/skyline/loader/nso.cpp b/app/src/main/cpp/skyline/loader/nso.cpp
new file mode 100644
index 00000000..3f74c1f1
--- /dev/null
+++ b/app/src/main/cpp/skyline/loader/nso.cpp
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+
+#include
+#include
+#include
+#include
+#include "nso.h"
+
+namespace skyline::loader {
+ NsoLoader::NsoLoader(const std::shared_ptr &backing) : Loader(backing) {
+ u32 magic{};
+ backing->Read(&magic);
+
+ if (magic != util::MakeMagic("NSO0"))
+ throw exception("Invalid NSO magic! 0x{0:X}", magic);
+ }
+
+ std::vector NsoLoader::GetSegment(const std::shared_ptr &backing, const NsoSegmentHeader &segment, u32 compressedSize) {
+ std::vector outputBuffer(segment.decompressedSize);
+
+ if (compressedSize) {
+ std::vector compressedBuffer(compressedSize);
+ backing->Read(compressedBuffer.data(), segment.fileOffset, compressedSize);
+
+ LZ4_decompress_safe(reinterpret_cast(compressedBuffer.data()), reinterpret_cast(outputBuffer.data()), compressedSize, segment.decompressedSize);
+ } else {
+ backing->Read(outputBuffer.data(), segment.fileOffset, segment.decompressedSize);
+ }
+
+ return outputBuffer;
+ }
+
+ Loader::ExecutableLoadInfo NsoLoader::LoadNso(const std::shared_ptr &backing, const std::shared_ptr process, const DeviceState &state, size_t offset) {
+ NsoHeader header{};
+ backing->Read(&header);
+
+ if (header.magic != util::MakeMagic("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 process, const DeviceState &state) {
+ auto loadInfo = LoadNso(backing, process, state);
+
+ state.os->memory.InitializeRegions(loadInfo.base, loadInfo.size, memory::AddressSpaceType::AddressSpace39Bit);
+ }
+}
diff --git a/app/src/main/cpp/skyline/loader/nso.h b/app/src/main/cpp/skyline/loader/nso.h
new file mode 100644
index 00000000..66f283b0
--- /dev/null
+++ b/app/src/main/cpp/skyline/loader/nso.h
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
+
+#pragma once
+
+#include
+#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 GetSegment(const std::shared_ptr &backing, const NsoSegmentHeader &segment, u32 compressedSize);
+
+ public:
+ NsoLoader(const std::shared_ptr &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 &backing, const std::shared_ptr process, const DeviceState &state, size_t offset = 0);
+
+ void LoadProcessData(const std::shared_ptr process, const DeviceState &state);
+ };
+}
diff --git a/app/src/main/cpp/skyline/os.cpp b/app/src/main/cpp/skyline/os.cpp
index 045a768b..d6916a5c 100644
--- a/app/src/main/cpp/skyline/os.cpp
+++ b/app/src/main/cpp/skyline/os.cpp
@@ -3,6 +3,7 @@
#include "vfs/os_backing.h"
#include "loader/nro.h"
+#include "loader/nso.h"
#include "nce/guest.h"
#include "os.h"
@@ -14,8 +15,11 @@ namespace skyline::kernel {
if (romType == loader::RomFormat::NRO) {
state.loader = std::make_shared(romFile);
- } else
+ } else if (romType == loader::RomFormat::NSO) {
+ state.loader = std::make_shared(romFile);
+ } else {
throw exception("Unsupported ROM extension.");
+ }
auto process = CreateProcess(constant::BaseAddress, 0, constant::DefStackSize);
state.loader->LoadProcessData(process, state);
diff --git a/app/src/main/java/emu/skyline/MainActivity.kt b/app/src/main/java/emu/skyline/MainActivity.kt
index d19d19e5..0784da0c 100644
--- a/app/src/main/java/emu/skyline/MainActivity.kt
+++ b/app/src/main/java/emu/skyline/MainActivity.kt
@@ -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 {
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 {
- if (!foundNros)
+ if (!foundRoms)
adapter.addHeader(getString(R.string.no_rom))
try {
diff --git a/app/src/main/java/emu/skyline/loader/RomFile.kt b/app/src/main/java/emu/skyline/loader/RomFile.kt
index d27189b5..7ad9d920 100644
--- a/app/src/main/java/emu/skyline/loader/RomFile.kt
+++ b/app/src/main/java/emu/skyline/loader/RomFile.kt
@@ -24,8 +24,9 @@ import java.util.*
*/
enum class RomFormat(val format: Int){
NRO(0),
- XCI(1),
- NSP(2),
+ NSO(1),
+ 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)
/**
- * 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 {
return if (hasAssets(instance)) {