1 // Copyright 2018 yuzu emulator team
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4
5 #include <utility>
6 #include <vector>
7
8 #include "common/common_funcs.h"
9 #include "common/common_types.h"
10 #include "common/file_util.h"
11 #include "common/logging/log.h"
12 #include "common/swap.h"
13 #include "core/core.h"
14 #include "core/file_sys/control_metadata.h"
15 #include "core/file_sys/romfs_factory.h"
16 #include "core/file_sys/vfs_offset.h"
17 #include "core/hle/kernel/code_set.h"
18 #include "core/hle/kernel/memory/page_table.h"
19 #include "core/hle/kernel/process.h"
20 #include "core/hle/kernel/thread.h"
21 #include "core/hle/service/filesystem/filesystem.h"
22 #include "core/loader/nro.h"
23 #include "core/loader/nso.h"
24 #include "core/memory.h"
25 #include "core/settings.h"
26
27 namespace Loader {
28
29 struct NroSegmentHeader {
30 u32_le offset;
31 u32_le size;
32 };
33 static_assert(sizeof(NroSegmentHeader) == 0x8, "NroSegmentHeader has incorrect size.");
34
35 struct NroHeader {
36 INSERT_PADDING_BYTES(0x4);
37 u32_le module_header_offset;
38 INSERT_PADDING_BYTES(0x8);
39 u32_le magic;
40 INSERT_PADDING_BYTES(0x4);
41 u32_le file_size;
42 INSERT_PADDING_BYTES(0x4);
43 std::array<NroSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
44 u32_le bss_size;
45 INSERT_PADDING_BYTES(0x44);
46 };
47 static_assert(sizeof(NroHeader) == 0x80, "NroHeader has incorrect size.");
48
49 struct ModHeader {
50 u32_le magic;
51 u32_le dynamic_offset;
52 u32_le bss_start_offset;
53 u32_le bss_end_offset;
54 u32_le unwind_start_offset;
55 u32_le unwind_end_offset;
56 u32_le module_offset; // Offset to runtime-generated module object. typically equal to .bss base
57 };
58 static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size.");
59
60 struct AssetSection {
61 u64_le offset;
62 u64_le size;
63 };
64 static_assert(sizeof(AssetSection) == 0x10, "AssetSection has incorrect size.");
65
66 struct AssetHeader {
67 u32_le magic;
68 u32_le format_version;
69 AssetSection icon;
70 AssetSection nacp;
71 AssetSection romfs;
72 };
73 static_assert(sizeof(AssetHeader) == 0x38, "AssetHeader has incorrect size.");
74
AppLoader_NRO(FileSys::VirtualFile file)75 AppLoader_NRO::AppLoader_NRO(FileSys::VirtualFile file) : AppLoader(file) {
76 NroHeader nro_header{};
77 if (file->ReadObject(&nro_header) != sizeof(NroHeader)) {
78 return;
79 }
80
81 if (file->GetSize() >= nro_header.file_size + sizeof(AssetHeader)) {
82 const u64 offset = nro_header.file_size;
83 AssetHeader asset_header{};
84 if (file->ReadObject(&asset_header, offset) != sizeof(AssetHeader)) {
85 return;
86 }
87
88 if (asset_header.format_version != 0) {
89 LOG_WARNING(Loader,
90 "NRO Asset Header has format {}, currently supported format is 0. If "
91 "strange glitches occur with metadata, check NRO assets.",
92 asset_header.format_version);
93 }
94
95 if (asset_header.magic != Common::MakeMagic('A', 'S', 'E', 'T')) {
96 return;
97 }
98
99 if (asset_header.nacp.size > 0) {
100 nacp = std::make_unique<FileSys::NACP>(std::make_shared<FileSys::OffsetVfsFile>(
101 file, asset_header.nacp.size, offset + asset_header.nacp.offset, "Control.nacp"));
102 }
103
104 if (asset_header.romfs.size > 0) {
105 romfs = std::make_shared<FileSys::OffsetVfsFile>(
106 file, asset_header.romfs.size, offset + asset_header.romfs.offset, "game.romfs");
107 }
108
109 if (asset_header.icon.size > 0) {
110 icon_data = file->ReadBytes(asset_header.icon.size, offset + asset_header.icon.offset);
111 }
112 }
113 }
114
115 AppLoader_NRO::~AppLoader_NRO() = default;
116
IdentifyType(const FileSys::VirtualFile & file)117 FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& file) {
118 // Read NSO header
119 NroHeader nro_header{};
120 if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
121 return FileType::Error;
122 }
123 if (nro_header.magic == Common::MakeMagic('N', 'R', 'O', '0')) {
124 return FileType::NRO;
125 }
126 return FileType::Error;
127 }
128
PageAlignSize(u32 size)129 static constexpr u32 PageAlignSize(u32 size) {
130 return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK);
131 }
132
LoadNroImpl(Kernel::Process & process,const std::vector<u8> & data,const std::string & name)133 static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data,
134 const std::string& name) {
135 if (data.size() < sizeof(NroHeader)) {
136 return {};
137 }
138
139 // Read NSO header
140 NroHeader nro_header{};
141 std::memcpy(&nro_header, data.data(), sizeof(NroHeader));
142 if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) {
143 return {};
144 }
145
146 // Build program image
147 Kernel::PhysicalMemory program_image(PageAlignSize(nro_header.file_size));
148 std::memcpy(program_image.data(), data.data(), program_image.size());
149 if (program_image.size() != PageAlignSize(nro_header.file_size)) {
150 return {};
151 }
152
153 Kernel::CodeSet codeset;
154 for (std::size_t i = 0; i < nro_header.segments.size(); ++i) {
155 codeset.segments[i].addr = nro_header.segments[i].offset;
156 codeset.segments[i].offset = nro_header.segments[i].offset;
157 codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size);
158 }
159
160 if (!Settings::values.program_args.empty()) {
161 const auto arg_data = Settings::values.program_args;
162 codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
163 NSOArgumentHeader args_header{
164 NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
165 const auto end_offset = program_image.size();
166 program_image.resize(static_cast<u32>(program_image.size()) +
167 NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
168 std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
169 std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
170 arg_data.size());
171 }
172
173 // Default .bss to NRO header bss size if MOD0 section doesn't exist
174 u32 bss_size{PageAlignSize(nro_header.bss_size)};
175
176 // Read MOD header
177 ModHeader mod_header{};
178 std::memcpy(&mod_header, program_image.data() + nro_header.module_header_offset,
179 sizeof(ModHeader));
180
181 const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')};
182 if (has_mod_header) {
183 // Resize program image to include .bss section and page align each section
184 bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
185 }
186
187 codeset.DataSegment().size += bss_size;
188 program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
189
190 // Setup the process code layout
191 if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size())
192 .IsError()) {
193 return false;
194 }
195
196 // Load codeset for current process
197 codeset.memory = std::move(program_image);
198 process.LoadModule(std::move(codeset), process.PageTable().GetCodeRegionStart());
199
200 return true;
201 }
202
LoadNro(Kernel::Process & process,const FileSys::VfsFile & file)203 bool AppLoader_NRO::LoadNro(Kernel::Process& process, const FileSys::VfsFile& file) {
204 return LoadNroImpl(process, file.ReadAllBytes(), file.GetName());
205 }
206
Load(Kernel::Process & process,Core::System & system)207 AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process, Core::System& system) {
208 if (is_loaded) {
209 return {ResultStatus::ErrorAlreadyLoaded, {}};
210 }
211
212 if (!LoadNro(process, *file)) {
213 return {ResultStatus::ErrorLoadingNRO, {}};
214 }
215
216 if (romfs != nullptr) {
217 system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
218 *this, system.GetContentProvider(), system.GetFileSystemController()));
219 }
220
221 is_loaded = true;
222 return {ResultStatus::Success,
223 LoadParameters{Kernel::THREADPRIO_DEFAULT, Core::Memory::DEFAULT_STACK_SIZE}};
224 }
225
ReadIcon(std::vector<u8> & buffer)226 ResultStatus AppLoader_NRO::ReadIcon(std::vector<u8>& buffer) {
227 if (icon_data.empty()) {
228 return ResultStatus::ErrorNoIcon;
229 }
230
231 buffer = icon_data;
232 return ResultStatus::Success;
233 }
234
ReadProgramId(u64 & out_program_id)235 ResultStatus AppLoader_NRO::ReadProgramId(u64& out_program_id) {
236 if (nacp == nullptr) {
237 return ResultStatus::ErrorNoControl;
238 }
239
240 out_program_id = nacp->GetTitleId();
241 return ResultStatus::Success;
242 }
243
ReadRomFS(FileSys::VirtualFile & dir)244 ResultStatus AppLoader_NRO::ReadRomFS(FileSys::VirtualFile& dir) {
245 if (romfs == nullptr) {
246 return ResultStatus::ErrorNoRomFS;
247 }
248
249 dir = romfs;
250 return ResultStatus::Success;
251 }
252
ReadTitle(std::string & title)253 ResultStatus AppLoader_NRO::ReadTitle(std::string& title) {
254 if (nacp == nullptr) {
255 return ResultStatus::ErrorNoControl;
256 }
257
258 title = nacp->GetApplicationName();
259 return ResultStatus::Success;
260 }
261
ReadControlData(FileSys::NACP & control)262 ResultStatus AppLoader_NRO::ReadControlData(FileSys::NACP& control) {
263 if (nacp == nullptr) {
264 return ResultStatus::ErrorNoControl;
265 }
266
267 control = *nacp;
268 return ResultStatus::Success;
269 }
270
IsRomFSUpdatable() const271 bool AppLoader_NRO::IsRomFSUpdatable() const {
272 return false;
273 }
274
275 } // namespace Loader
276