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