1 // Copyright 2014 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4
5 #include <algorithm>
6 #include <vector>
7 #include "common/logging/log.h"
8 #include "core/core.h"
9 #include "core/hle/kernel/process.h"
10 #include "core/hle/kernel/resource_limit.h"
11 #include "core/hle/service/fs/archive.h"
12 #include "core/hle/service/fs/fs_user.h"
13 #include "core/loader/3dsx.h"
14 #include "core/memory.h"
15
16 namespace Loader {
17
18 /*
19 * File layout:
20 * - File header
21 * - Code, rodata and data relocation table headers
22 * - Code segment
23 * - Rodata segment
24 * - Loadable (non-BSS) part of the data segment
25 * - Code relocation table
26 * - Rodata relocation table
27 * - Data relocation table
28 *
29 * Memory layout before relocations are applied:
30 * [0..codeSegSize) -> code segment
31 * [codeSegSize..rodataSegSize) -> rodata segment
32 * [rodataSegSize..dataSegSize) -> data segment
33 *
34 * Memory layout after relocations are applied: well, however the loader sets it up :)
35 * The entrypoint is always the start of the code segment.
36 * The BSS section must be cleared manually by the application.
37 */
38
39 enum THREEDSX_Error { ERROR_NONE = 0, ERROR_READ = 1, ERROR_FILE = 2, ERROR_ALLOC = 3 };
40
41 static const u32 RELOCBUFSIZE = 512;
42 static const unsigned int NUM_SEGMENTS = 3;
43
44 // File header
45 #pragma pack(1)
46 struct THREEDSX_Header {
47 u32 magic;
48 u16 header_size, reloc_hdr_size;
49 u32 format_ver;
50 u32 flags;
51
52 // Sizes of the code, rodata and data segments +
53 // size of the BSS section (uninitialized latter half of the data segment)
54 u32 code_seg_size, rodata_seg_size, data_seg_size, bss_size;
55 // offset and size of smdh
56 u32 smdh_offset, smdh_size;
57 // offset to filesystem
58 u32 fs_offset;
59 };
60
61 // Relocation header: all fields (even extra unknown fields) are guaranteed to be relocation counts.
62 struct THREEDSX_RelocHdr {
63 // # of absolute relocations (that is, fix address to post-relocation memory layout)
64 u32 cross_segment_absolute;
65 // # of cross-segment relative relocations (that is, 32bit signed offsets that need to be
66 // patched)
67 u32 cross_segment_relative;
68 // more?
69
70 // Relocations are written in this order:
71 // - Absolute relocations
72 // - Relative relocations
73 };
74
75 // Relocation entry: from the current pointer, skip X words and patch Y words
76 struct THREEDSX_Reloc {
77 u16 skip, patch;
78 };
79 #pragma pack()
80
81 struct THREEloadinfo {
82 u8* seg_ptrs[3]; // code, rodata & data
83 u32 seg_addrs[3];
84 u32 seg_sizes[3];
85 };
86
TranslateAddr(u32 addr,const THREEloadinfo * loadinfo,u32 * offsets)87 static u32 TranslateAddr(u32 addr, const THREEloadinfo* loadinfo, u32* offsets) {
88 if (addr < offsets[0])
89 return loadinfo->seg_addrs[0] + addr;
90 if (addr < offsets[1])
91 return loadinfo->seg_addrs[1] + addr - offsets[0];
92 return loadinfo->seg_addrs[2] + addr - offsets[1];
93 }
94
95 using Kernel::CodeSet;
96
Load3DSXFile(FileUtil::IOFile & file,u32 base_addr,std::shared_ptr<CodeSet> * out_codeset)97 static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr,
98 std::shared_ptr<CodeSet>* out_codeset) {
99 if (!file.IsOpen())
100 return ERROR_FILE;
101
102 // Reset read pointer in case this file has been read before.
103 file.Seek(0, SEEK_SET);
104
105 THREEDSX_Header hdr;
106 if (file.ReadBytes(&hdr, sizeof(hdr)) != sizeof(hdr))
107 return ERROR_READ;
108
109 THREEloadinfo loadinfo;
110 // loadinfo segments must be a multiple of 0x1000
111 loadinfo.seg_sizes[0] = (hdr.code_seg_size + 0xFFF) & ~0xFFF;
112 loadinfo.seg_sizes[1] = (hdr.rodata_seg_size + 0xFFF) & ~0xFFF;
113 loadinfo.seg_sizes[2] = (hdr.data_seg_size + 0xFFF) & ~0xFFF;
114 u32 offsets[2] = {loadinfo.seg_sizes[0], loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1]};
115 u32 n_reloc_tables = hdr.reloc_hdr_size / sizeof(u32);
116 std::vector<u8> program_image(loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1] +
117 loadinfo.seg_sizes[2]);
118
119 loadinfo.seg_addrs[0] = base_addr;
120 loadinfo.seg_addrs[1] = loadinfo.seg_addrs[0] + loadinfo.seg_sizes[0];
121 loadinfo.seg_addrs[2] = loadinfo.seg_addrs[1] + loadinfo.seg_sizes[1];
122 loadinfo.seg_ptrs[0] = program_image.data();
123 loadinfo.seg_ptrs[1] = loadinfo.seg_ptrs[0] + loadinfo.seg_sizes[0];
124 loadinfo.seg_ptrs[2] = loadinfo.seg_ptrs[1] + loadinfo.seg_sizes[1];
125
126 // Skip header for future compatibility
127 file.Seek(hdr.header_size, SEEK_SET);
128
129 // Read the relocation headers
130 std::vector<u32> relocs(n_reloc_tables * NUM_SEGMENTS);
131 for (unsigned int current_segment = 0; current_segment < NUM_SEGMENTS; ++current_segment) {
132 std::size_t size = n_reloc_tables * sizeof(u32);
133 if (file.ReadBytes(&relocs[current_segment * n_reloc_tables], size) != size)
134 return ERROR_READ;
135 }
136
137 // Read the segments
138 if (file.ReadBytes(loadinfo.seg_ptrs[0], hdr.code_seg_size) != hdr.code_seg_size)
139 return ERROR_READ;
140 if (file.ReadBytes(loadinfo.seg_ptrs[1], hdr.rodata_seg_size) != hdr.rodata_seg_size)
141 return ERROR_READ;
142 if (file.ReadBytes(loadinfo.seg_ptrs[2], hdr.data_seg_size - hdr.bss_size) !=
143 hdr.data_seg_size - hdr.bss_size)
144 return ERROR_READ;
145
146 // BSS clear
147 memset((char*)loadinfo.seg_ptrs[2] + hdr.data_seg_size - hdr.bss_size, 0, hdr.bss_size);
148
149 // Relocate the segments
150 for (unsigned int current_segment = 0; current_segment < NUM_SEGMENTS; ++current_segment) {
151 for (unsigned current_segment_reloc_table = 0; current_segment_reloc_table < n_reloc_tables;
152 current_segment_reloc_table++) {
153 u32 n_relocs = relocs[current_segment * n_reloc_tables + current_segment_reloc_table];
154 if (current_segment_reloc_table >= 2) {
155 // We are not using this table - ignore it because we don't know what it dose
156 file.Seek(n_relocs * sizeof(THREEDSX_Reloc), SEEK_CUR);
157 continue;
158 }
159 THREEDSX_Reloc reloc_table[RELOCBUFSIZE];
160
161 u32* pos = (u32*)loadinfo.seg_ptrs[current_segment];
162 const u32* end_pos = pos + (loadinfo.seg_sizes[current_segment] / 4);
163
164 while (n_relocs) {
165 u32 remaining = std::min(RELOCBUFSIZE, n_relocs);
166 n_relocs -= remaining;
167
168 if (file.ReadBytes(reloc_table, remaining * sizeof(THREEDSX_Reloc)) !=
169 remaining * sizeof(THREEDSX_Reloc))
170 return ERROR_READ;
171
172 for (unsigned current_inprogress = 0;
173 current_inprogress < remaining && pos < end_pos; current_inprogress++) {
174 const auto& table = reloc_table[current_inprogress];
175 LOG_TRACE(Loader, "(t={},skip={},patch={})", current_segment_reloc_table,
176 static_cast<u32>(table.skip), static_cast<u32>(table.patch));
177 pos += table.skip;
178 s32 num_patches = table.patch;
179 while (0 < num_patches && pos < end_pos) {
180 u32 in_addr = base_addr + static_cast<u32>(reinterpret_cast<u8*>(pos) -
181 program_image.data());
182 u32 orig_data = *pos;
183 u32 sub_type = orig_data >> (32 - 4);
184 u32 addr = TranslateAddr(orig_data & ~0xF0000000, &loadinfo, offsets);
185 LOG_TRACE(Loader, "Patching {:08X} <-- rel({:08X},{}) ({:08X})", in_addr,
186 addr, current_segment_reloc_table, *pos);
187 switch (current_segment_reloc_table) {
188 case 0: {
189 if (sub_type != 0)
190 return ERROR_READ;
191 *pos = addr;
192 break;
193 }
194 case 1: {
195 u32 data = addr - in_addr;
196 switch (sub_type) {
197 case 0: // 32-bit signed offset
198 *pos = data;
199 break;
200 case 1: // 31-bit signed offset
201 *pos = data & ~(1U << 31);
202 break;
203 default:
204 return ERROR_READ;
205 }
206 break;
207 }
208 default:
209 break; // this should never happen
210 }
211 pos++;
212 num_patches--;
213 }
214 }
215 }
216 }
217 }
218
219 // Create the CodeSet
220 std::shared_ptr<CodeSet> code_set = Core::System::GetInstance().Kernel().CreateCodeSet("", 0);
221
222 code_set->CodeSegment().offset = loadinfo.seg_ptrs[0] - program_image.data();
223 code_set->CodeSegment().addr = loadinfo.seg_addrs[0];
224 code_set->CodeSegment().size = loadinfo.seg_sizes[0];
225
226 code_set->RODataSegment().offset = loadinfo.seg_ptrs[1] - program_image.data();
227 code_set->RODataSegment().addr = loadinfo.seg_addrs[1];
228 code_set->RODataSegment().size = loadinfo.seg_sizes[1];
229
230 code_set->DataSegment().offset = loadinfo.seg_ptrs[2] - program_image.data();
231 code_set->DataSegment().addr = loadinfo.seg_addrs[2];
232 code_set->DataSegment().size = loadinfo.seg_sizes[2];
233
234 code_set->entrypoint = code_set->CodeSegment().addr;
235 code_set->memory = std::move(program_image);
236
237 LOG_DEBUG(Loader, "code size: {:#X}", loadinfo.seg_sizes[0]);
238 LOG_DEBUG(Loader, "rodata size: {:#X}", loadinfo.seg_sizes[1]);
239 LOG_DEBUG(Loader, "data size: {:#X} (including {:#X} of bss)", loadinfo.seg_sizes[2],
240 hdr.bss_size);
241
242 *out_codeset = code_set;
243 return ERROR_NONE;
244 }
245
IdentifyType(FileUtil::IOFile & file)246 FileType AppLoader_THREEDSX::IdentifyType(FileUtil::IOFile& file) {
247 u32 magic;
248 file.Seek(0, SEEK_SET);
249 if (1 != file.ReadArray<u32>(&magic, 1))
250 return FileType::Error;
251
252 if (MakeMagic('3', 'D', 'S', 'X') == magic)
253 return FileType::THREEDSX;
254
255 return FileType::Error;
256 }
257
Load(std::shared_ptr<Kernel::Process> & process)258 ResultStatus AppLoader_THREEDSX::Load(std::shared_ptr<Kernel::Process>& process) {
259 if (is_loaded)
260 return ResultStatus::ErrorAlreadyLoaded;
261
262 if (!file.IsOpen())
263 return ResultStatus::Error;
264
265 std::shared_ptr<CodeSet> codeset;
266 if (Load3DSXFile(file, Memory::PROCESS_IMAGE_VADDR, &codeset) != ERROR_NONE)
267 return ResultStatus::Error;
268 codeset->name = filename;
269
270 process = Core::System::GetInstance().Kernel().CreateProcess(std::move(codeset));
271 process->Set3dsxKernelCaps();
272
273 // Attach the default resource limit (APPLICATION) to the process
274 process->resource_limit = Core::System::GetInstance().Kernel().ResourceLimit().GetForCategory(
275 Kernel::ResourceLimitCategory::APPLICATION);
276
277 // On real HW this is done with FS:Reg, but we can be lazy
278 auto fs_user =
279 Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
280 fs_user->Register(process->GetObjectId(), process->codeset->program_id, filepath);
281
282 process->Run(48, Kernel::DEFAULT_STACK_SIZE);
283
284 Core::System::GetInstance().ArchiveManager().RegisterSelfNCCH(*this);
285
286 is_loaded = true;
287 return ResultStatus::Success;
288 }
289
ReadRomFS(std::shared_ptr<FileSys::RomFSReader> & romfs_file)290 ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) {
291 if (!file.IsOpen())
292 return ResultStatus::Error;
293
294 // Reset read pointer in case this file has been read before.
295 file.Seek(0, SEEK_SET);
296
297 THREEDSX_Header hdr;
298 if (file.ReadBytes(&hdr, sizeof(THREEDSX_Header)) != sizeof(THREEDSX_Header))
299 return ResultStatus::Error;
300
301 if (hdr.header_size != sizeof(THREEDSX_Header))
302 return ResultStatus::Error;
303
304 // Check if the 3DSX has a RomFS...
305 if (hdr.fs_offset != 0) {
306 u32 romfs_offset = hdr.fs_offset;
307 u32 romfs_size = static_cast<u32>(file.GetSize()) - hdr.fs_offset;
308
309 LOG_DEBUG(Loader, "RomFS offset: {:#010X}", romfs_offset);
310 LOG_DEBUG(Loader, "RomFS size: {:#010X}", romfs_size);
311
312 // We reopen the file, to allow its position to be independent from file's
313 FileUtil::IOFile romfs_file_inner(filepath, "rb");
314 if (!romfs_file_inner.IsOpen())
315 return ResultStatus::Error;
316
317 romfs_file = std::make_shared<FileSys::DirectRomFSReader>(std::move(romfs_file_inner),
318 romfs_offset, romfs_size);
319
320 return ResultStatus::Success;
321 }
322 LOG_DEBUG(Loader, "3DSX has no RomFS");
323 return ResultStatus::ErrorNotUsed;
324 }
325
ReadIcon(std::vector<u8> & buffer)326 ResultStatus AppLoader_THREEDSX::ReadIcon(std::vector<u8>& buffer) {
327 if (!file.IsOpen())
328 return ResultStatus::Error;
329
330 // Reset read pointer in case this file has been read before.
331 file.Seek(0, SEEK_SET);
332
333 THREEDSX_Header hdr;
334 if (file.ReadBytes(&hdr, sizeof(THREEDSX_Header)) != sizeof(THREEDSX_Header))
335 return ResultStatus::Error;
336
337 if (hdr.header_size != sizeof(THREEDSX_Header))
338 return ResultStatus::Error;
339
340 // Check if the 3DSX has a SMDH...
341 if (hdr.smdh_offset != 0) {
342 file.Seek(hdr.smdh_offset, SEEK_SET);
343 buffer.resize(hdr.smdh_size);
344
345 if (file.ReadBytes(buffer.data(), hdr.smdh_size) != hdr.smdh_size)
346 return ResultStatus::Error;
347
348 return ResultStatus::Success;
349 }
350 return ResultStatus::ErrorNotUsed;
351 }
352
353 } // namespace Loader
354