1 // Copyright 2008 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4 
5 #include "Core/Boot/Boot.h"
6 
7 #ifdef _MSC_VER
8 #include <filesystem>
9 namespace fs = std::filesystem;
10 #define HAS_STD_FILESYSTEM
11 #endif
12 
13 #include <algorithm>
14 #include <array>
15 #include <cstring>
16 #include <memory>
17 #include <numeric>
18 #include <optional>
19 #include <string>
20 #include <unordered_set>
21 #include <utility>
22 #include <vector>
23 
24 #include <zlib.h>
25 
26 #include "Common/Align.h"
27 #include "Common/CDUtils.h"
28 #include "Common/CommonPaths.h"
29 #include "Common/CommonTypes.h"
30 #include "Common/Config/Config.h"
31 #include "Common/File.h"
32 #include "Common/FileUtil.h"
33 #include "Common/Logging/Log.h"
34 #include "Common/MsgHandler.h"
35 #include "Common/StringUtil.h"
36 
37 #include "Core/Boot/DolReader.h"
38 #include "Core/Boot/ElfReader.h"
39 #include "Core/CommonTitles.h"
40 #include "Core/Config/MainSettings.h"
41 #include "Core/Config/SYSCONFSettings.h"
42 #include "Core/ConfigManager.h"
43 #include "Core/FifoPlayer/FifoPlayer.h"
44 #include "Core/HLE/HLE.h"
45 #include "Core/HW/DVD/DVDInterface.h"
46 #include "Core/HW/EXI/EXI_DeviceIPL.h"
47 #include "Core/HW/Memmap.h"
48 #include "Core/HW/VideoInterface.h"
49 #include "Core/Host.h"
50 #include "Core/IOS/ES/ES.h"
51 #include "Core/IOS/FS/FileSystem.h"
52 #include "Core/IOS/IOS.h"
53 #include "Core/IOS/IOSC.h"
54 #include "Core/IOS/Uids.h"
55 #include "Core/PatchEngine.h"
56 #include "Core/PowerPC/PPCAnalyst.h"
57 #include "Core/PowerPC/PPCSymbolDB.h"
58 #include "Core/PowerPC/PowerPC.h"
59 
60 #include "DiscIO/Enums.h"
61 #include "DiscIO/VolumeDisc.h"
62 #include "DiscIO/VolumeWad.h"
63 
ReadM3UFile(const std::string & m3u_path,const std::string & folder_path)64 static std::vector<std::string> ReadM3UFile(const std::string& m3u_path,
65                                             const std::string& folder_path)
66 {
67 #ifndef HAS_STD_FILESYSTEM
68   ASSERT(folder_path.back() == '/');
69 #endif
70 
71   std::vector<std::string> result;
72   std::vector<std::string> nonexistent;
73 
74   std::ifstream s;
75   File::OpenFStream(s, m3u_path, std::ios_base::in);
76 
77   std::string line;
78   while (std::getline(s, line))
79   {
80     // This is the UTF-8 representation of U+FEFF.
81     const std::string utf8_bom = "\xEF\xBB\xBF";
82 
83     if (StringBeginsWith(line, utf8_bom))
84     {
85       WARN_LOG(BOOT, "UTF-8 BOM in file: %s", m3u_path.c_str());
86       line.erase(0, utf8_bom.length());
87     }
88 
89     if (!line.empty() && line.front() != '#')  // Comments start with #
90     {
91 #ifdef HAS_STD_FILESYSTEM
92       const std::string path_to_add = PathToString(StringToPath(folder_path) / StringToPath(line));
93 #else
94       const std::string path_to_add = line.front() != '/' ? folder_path + line : line;
95 #endif
96 
97       (File::Exists(path_to_add) ? result : nonexistent).push_back(path_to_add);
98     }
99   }
100 
101   if (!nonexistent.empty())
102   {
103     PanicAlertT("Files specified in the M3U file \"%s\" were not found:\n%s", m3u_path.c_str(),
104                 JoinStrings(nonexistent, "\n").c_str());
105     return {};
106   }
107 
108   if (result.empty())
109     PanicAlertT("No paths found in the M3U file \"%s\"", m3u_path.c_str());
110 
111   return result;
112 }
113 
BootParameters(Parameters && parameters_,const std::optional<std::string> & savestate_path_)114 BootParameters::BootParameters(Parameters&& parameters_,
115                                const std::optional<std::string>& savestate_path_)
116     : parameters(std::move(parameters_)), savestate_path(savestate_path_)
117 {
118 }
119 
120 std::unique_ptr<BootParameters>
GenerateFromFile(std::string boot_path,const std::optional<std::string> & savestate_path)121 BootParameters::GenerateFromFile(std::string boot_path,
122                                  const std::optional<std::string>& savestate_path)
123 {
124   return GenerateFromFile(std::vector<std::string>{std::move(boot_path)}, savestate_path);
125 }
126 
127 std::unique_ptr<BootParameters>
GenerateFromFile(std::vector<std::string> paths,const std::optional<std::string> & savestate_path)128 BootParameters::GenerateFromFile(std::vector<std::string> paths,
129                                  const std::optional<std::string>& savestate_path)
130 {
131   ASSERT(!paths.empty());
132 
133   const bool is_drive = Common::IsCDROMDevice(paths.front());
134   // Check if the file exist, we may have gotten it from a --elf command line
135   // that gave an incorrect file name
136   if (!is_drive && !File::Exists(paths.front()))
137   {
138     PanicAlertT("The specified file \"%s\" does not exist", paths.front().c_str());
139     return {};
140   }
141 
142   std::string folder_path;
143   std::string extension;
144   SplitPath(paths.front(), &folder_path, nullptr, &extension);
145   std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
146 
147   if (extension == ".m3u" || extension == ".m3u8")
148   {
149     paths = ReadM3UFile(paths.front(), folder_path);
150     if (paths.empty())
151       return {};
152 
153     SplitPath(paths.front(), nullptr, nullptr, &extension);
154     std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
155   }
156 
157   std::string path = paths.front();
158   if (paths.size() == 1)
159     paths.clear();
160 
161   static const std::unordered_set<std::string> disc_image_extensions = {
162       {".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".wia", ".rvz", ".dol", ".elf"}};
163   if (disc_image_extensions.find(extension) != disc_image_extensions.end() || is_drive)
164   {
165     std::unique_ptr<DiscIO::VolumeDisc> disc = DiscIO::CreateDisc(path);
166     if (disc)
167     {
168       return std::make_unique<BootParameters>(Disc{std::move(path), std::move(disc), paths},
169                                               savestate_path);
170     }
171 
172     if (extension == ".elf")
173     {
174       auto elf_reader = std::make_unique<ElfReader>(path);
175       return std::make_unique<BootParameters>(Executable{std::move(path), std::move(elf_reader)},
176                                               savestate_path);
177     }
178 
179     if (extension == ".dol")
180     {
181       auto dol_reader = std::make_unique<DolReader>(path);
182       return std::make_unique<BootParameters>(Executable{std::move(path), std::move(dol_reader)},
183                                               savestate_path);
184     }
185 
186     if (is_drive)
187     {
188       PanicAlertT("Could not read \"%s\". "
189                   "There is no disc in the drive or it is not a GameCube/Wii backup. "
190                   "Please note that Dolphin cannot play games directly from the original "
191                   "GameCube and Wii discs.",
192                   path.c_str());
193     }
194     else
195     {
196       PanicAlertT("\"%s\" is an invalid GCM/ISO file, or is not a GC/Wii ISO.", path.c_str());
197     }
198     return {};
199   }
200 
201   if (extension == ".dff")
202     return std::make_unique<BootParameters>(DFF{std::move(path)}, savestate_path);
203 
204   if (extension == ".wad")
205   {
206     std::unique_ptr<DiscIO::VolumeWAD> wad = DiscIO::CreateWAD(std::move(path));
207     if (wad)
208       return std::make_unique<BootParameters>(std::move(*wad), savestate_path);
209   }
210 
211   PanicAlertT("Could not recognize file %s", path.c_str());
212   return {};
213 }
214 
IPL(DiscIO::Region region_)215 BootParameters::IPL::IPL(DiscIO::Region region_) : region(region_)
216 {
217   const std::string directory = SConfig::GetInstance().GetDirectoryForRegion(region);
218   path = SConfig::GetInstance().GetBootROMPath(directory);
219 }
220 
IPL(DiscIO::Region region_,Disc && disc_)221 BootParameters::IPL::IPL(DiscIO::Region region_, Disc&& disc_) : IPL(region_)
222 {
223   disc = std::move(disc_);
224 }
225 
226 // Inserts a disc into the emulated disc drive and returns a pointer to it.
227 // The returned pointer must only be used while we are still booting,
228 // because DVDThread can do whatever it wants to the disc after that.
SetDisc(std::unique_ptr<DiscIO::VolumeDisc> disc,std::vector<std::string> auto_disc_change_paths={})229 static const DiscIO::VolumeDisc* SetDisc(std::unique_ptr<DiscIO::VolumeDisc> disc,
230                                          std::vector<std::string> auto_disc_change_paths = {})
231 {
232   const DiscIO::VolumeDisc* pointer = disc.get();
233   DVDInterface::SetDisc(std::move(disc), auto_disc_change_paths);
234   return pointer;
235 }
236 
DVDRead(const DiscIO::VolumeDisc & disc,u64 dvd_offset,u32 output_address,u32 length,const DiscIO::Partition & partition)237 bool CBoot::DVDRead(const DiscIO::VolumeDisc& disc, u64 dvd_offset, u32 output_address, u32 length,
238                     const DiscIO::Partition& partition)
239 {
240   std::vector<u8> buffer(length);
241   if (!disc.Read(dvd_offset, length, buffer.data(), partition))
242     return false;
243   Memory::CopyToEmu(output_address, buffer.data(), length);
244   return true;
245 }
246 
DVDReadDiscID(const DiscIO::VolumeDisc & disc,u32 output_address)247 bool CBoot::DVDReadDiscID(const DiscIO::VolumeDisc& disc, u32 output_address)
248 {
249   std::array<u8, 0x20> buffer;
250   if (!disc.Read(0, buffer.size(), buffer.data(), DiscIO::PARTITION_NONE))
251     return false;
252   Memory::CopyToEmu(output_address, buffer.data(), buffer.size());
253   // Transition out of the DiscIdNotRead state (which the drive should be in at this point,
254   // on the assumption that this is only used for the first read)
255   DVDInterface::SetDriveState(DVDInterface::DriveState::ReadyNoReadsMade);
256   return true;
257 }
258 
UpdateDebugger_MapLoaded()259 void CBoot::UpdateDebugger_MapLoaded()
260 {
261   Host_NotifyMapLoaded();
262 }
263 
264 // Get map file paths for the active title.
FindMapFile(std::string * existing_map_file,std::string * writable_map_file)265 bool CBoot::FindMapFile(std::string* existing_map_file, std::string* writable_map_file)
266 {
267   const std::string& game_id = SConfig::GetInstance().m_debugger_game_id;
268 
269   if (writable_map_file)
270     *writable_map_file = File::GetUserPath(D_MAPS_IDX) + game_id + ".map";
271 
272   static const std::array<std::string, 2> maps_directories{
273       File::GetUserPath(D_MAPS_IDX),
274       File::GetSysDirectory() + MAPS_DIR DIR_SEP,
275   };
276   for (const auto& directory : maps_directories)
277   {
278     std::string path = directory + game_id + ".map";
279     if (File::Exists(path))
280     {
281       if (existing_map_file)
282         *existing_map_file = std::move(path);
283 
284       return true;
285     }
286   }
287 
288   return false;
289 }
290 
LoadMapFromFilename()291 bool CBoot::LoadMapFromFilename()
292 {
293   std::string strMapFilename;
294   bool found = FindMapFile(&strMapFilename, nullptr);
295   if (found && g_symbolDB.LoadMap(strMapFilename))
296   {
297     UpdateDebugger_MapLoaded();
298     return true;
299   }
300 
301   return false;
302 }
303 
304 // If ipl.bin is not found, this function does *some* of what BS1 does:
305 // loading IPL(BS2) and jumping to it.
306 // It does not initialize the hardware or anything else like BS1 does.
Load_BS2(const std::string & boot_rom_filename)307 bool CBoot::Load_BS2(const std::string& boot_rom_filename)
308 {
309   // CRC32 hashes of the IPL file, obtained from Redump
310   constexpr u32 NTSC_v1_0 = 0x6DAC1F2A;
311   constexpr u32 NTSC_v1_1 = 0xD5E6FEEA;
312   constexpr u32 NTSC_v1_2 = 0x86573808;
313   constexpr u32 MPAL_v1_1 = 0x667D0B64;  // Brazil
314   constexpr u32 PAL_v1_0 = 0x4F319F43;
315   constexpr u32 PAL_v1_2 = 0xAD1B7F16;
316 
317   // Load the whole ROM dump
318   std::string data;
319   if (!File::ReadFileToString(boot_rom_filename, data))
320     return false;
321 
322   // Use zlibs crc32 implementation to compute the hash
323   u32 ipl_hash = crc32(0L, Z_NULL, 0);
324   ipl_hash = crc32(ipl_hash, (const Bytef*)data.data(), (u32)data.size());
325   bool known_ipl = false;
326   bool pal_ipl = false;
327   switch (ipl_hash)
328   {
329   case NTSC_v1_0:
330   case NTSC_v1_1:
331   case NTSC_v1_2:
332   case MPAL_v1_1:
333     known_ipl = true;
334     break;
335   case PAL_v1_0:
336   case PAL_v1_2:
337     pal_ipl = true;
338     known_ipl = true;
339     break;
340   default:
341     PanicAlertT("The IPL file is not a known good dump. (CRC32: %x)", ipl_hash);
342     break;
343   }
344 
345   const DiscIO::Region boot_region = SConfig::GetInstance().m_region;
346   if (known_ipl && pal_ipl != (boot_region == DiscIO::Region::PAL))
347   {
348     PanicAlertT("%s IPL found in %s directory. The disc might not be recognized",
349                 pal_ipl ? "PAL" : "NTSC", SConfig::GetDirectoryForRegion(boot_region));
350   }
351 
352   // Run the descrambler over the encrypted section containing BS1/BS2
353   ExpansionInterface::CEXIIPL::Descrambler((u8*)data.data() + 0x100, 0x1AFE00);
354 
355   // TODO: Execution is supposed to start at 0xFFF00000, not 0x81200000;
356   // copying the initial boot code to 0x81200000 is a hack.
357   // For now, HLE the first few instructions and start at 0x81200150
358   // to work around this.
359   Memory::CopyToEmu(0x01200000, data.data() + 0x100, 0x700);
360   Memory::CopyToEmu(0x01300000, data.data() + 0x820, 0x1AFE00);
361 
362   PowerPC::ppcState.gpr[3] = 0xfff0001f;
363   PowerPC::ppcState.gpr[4] = 0x00002030;
364   PowerPC::ppcState.gpr[5] = 0x0000009c;
365 
366   MSR.FP = 1;
367   MSR.DR = 1;
368   MSR.IR = 1;
369 
370   PowerPC::ppcState.spr[SPR_HID0] = 0x0011c464;
371   PowerPC::ppcState.spr[SPR_IBAT3U] = 0xfff0001f;
372   PowerPC::ppcState.spr[SPR_IBAT3L] = 0xfff00001;
373   PowerPC::ppcState.spr[SPR_DBAT3U] = 0xfff0001f;
374   PowerPC::ppcState.spr[SPR_DBAT3L] = 0xfff00001;
375   SetupBAT(/*is_wii*/ false);
376 
377   PC = 0x81200150;
378   return true;
379 }
380 
SetDefaultDisc()381 static void SetDefaultDisc()
382 {
383   const std::string default_iso = Config::Get(Config::MAIN_DEFAULT_ISO);
384   if (!default_iso.empty())
385     SetDisc(DiscIO::CreateDisc(default_iso));
386 }
387 
CopyDefaultExceptionHandlers()388 static void CopyDefaultExceptionHandlers()
389 {
390   constexpr u32 EXCEPTION_HANDLER_ADDRESSES[] = {0x00000100, 0x00000200, 0x00000300, 0x00000400,
391                                                  0x00000500, 0x00000600, 0x00000700, 0x00000800,
392                                                  0x00000900, 0x00000C00, 0x00000D00, 0x00000F00,
393                                                  0x00001300, 0x00001400, 0x00001700};
394 
395   constexpr u32 RFI_INSTRUCTION = 0x4C000064;
396   for (const u32 address : EXCEPTION_HANDLER_ADDRESSES)
397     Memory::Write_U32(RFI_INSTRUCTION, address);
398 }
399 
400 // Third boot step after BootManager and Core. See Call schedule in BootManager.cpp
BootUp(std::unique_ptr<BootParameters> boot)401 bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
402 {
403   SConfig& config = SConfig::GetInstance();
404 
405   if (!g_symbolDB.IsEmpty())
406   {
407     g_symbolDB.Clear();
408     UpdateDebugger_MapLoaded();
409   }
410 
411   // PAL Wii uses NTSC framerate and linecount in 60Hz modes
412   VideoInterface::Preset(DiscIO::IsNTSC(config.m_region) ||
413                          (config.bWii && Config::Get(Config::SYSCONF_PAL60)));
414 
415   struct BootTitle
416   {
417     BootTitle() : config(SConfig::GetInstance()) {}
418     bool operator()(BootParameters::Disc& disc) const
419     {
420       NOTICE_LOG(BOOT, "Booting from disc: %s", disc.path.c_str());
421       const DiscIO::VolumeDisc* volume =
422           SetDisc(std::move(disc.volume), disc.auto_disc_change_paths);
423 
424       if (!volume)
425         return false;
426 
427       if (!EmulatedBS2(config.bWii, *volume))
428         return false;
429 
430       // Try to load the symbol map if there is one, and then scan it for
431       // and eventually replace code
432       if (LoadMapFromFilename())
433         HLE::PatchFunctions();
434 
435       return true;
436     }
437 
438     bool operator()(const BootParameters::Executable& executable) const
439     {
440       NOTICE_LOG(BOOT, "Booting from executable: %s", executable.path.c_str());
441 
442       if (!executable.reader->IsValid())
443         return false;
444 
445       if (!executable.reader->LoadIntoMemory())
446       {
447         PanicAlertT("Failed to load the executable to memory.");
448         return false;
449       }
450 
451       SetDefaultDisc();
452 
453       SetupMSR();
454       SetupBAT(config.bWii);
455       CopyDefaultExceptionHandlers();
456 
457       if (config.bWii)
458       {
459         PowerPC::ppcState.spr[SPR_HID0] = 0x0011c464;
460         PowerPC::ppcState.spr[SPR_HID4] = 0x82000000;
461 
462         // Set a value for the SP. It doesn't matter where this points to,
463         // as long as it is a valid location. This value is taken from a homebrew binary.
464         PowerPC::ppcState.gpr[1] = 0x8004d4bc;
465 
466         // Because there is no TMD to get the requested system (IOS) version from,
467         // we default to IOS58, which is the version used by the Homebrew Channel.
468         SetupWiiMemory(IOS::HLE::IOSC::ConsoleType::Retail);
469         IOS::HLE::GetIOS()->BootIOS(Titles::IOS(58));
470       }
471       else
472       {
473         SetupGCMemory();
474       }
475 
476       PC = executable.reader->GetEntryPoint();
477 
478       if (executable.reader->LoadSymbols() || LoadMapFromFilename())
479       {
480         UpdateDebugger_MapLoaded();
481         HLE::PatchFunctions();
482       }
483       return true;
484     }
485 
486     bool operator()(const DiscIO::VolumeWAD& wad) const
487     {
488       SetDefaultDisc();
489       return Boot_WiiWAD(wad);
490     }
491 
492     bool operator()(const BootParameters::NANDTitle& nand_title) const
493     {
494       SetDefaultDisc();
495       return BootNANDTitle(nand_title.id);
496     }
497 
498     bool operator()(const BootParameters::IPL& ipl) const
499     {
500       NOTICE_LOG(BOOT, "Booting GC IPL: %s", ipl.path.c_str());
501       if (!File::Exists(ipl.path))
502       {
503         if (ipl.disc)
504           PanicAlertT("Cannot start the game, because the GC IPL could not be found.");
505         else
506           PanicAlertT("Cannot find the GC IPL.");
507         return false;
508       }
509 
510       if (!Load_BS2(ipl.path))
511         return false;
512 
513       if (ipl.disc)
514       {
515         NOTICE_LOG(BOOT, "Inserting disc: %s", ipl.disc->path.c_str());
516         SetDisc(DiscIO::CreateDisc(ipl.disc->path), ipl.disc->auto_disc_change_paths);
517       }
518 
519       if (LoadMapFromFilename())
520         HLE::PatchFunctions();
521 
522       return true;
523     }
524 
525     bool operator()(const BootParameters::DFF& dff) const
526     {
527       NOTICE_LOG(BOOT, "Booting DFF: %s", dff.dff_path.c_str());
528       return FifoPlayer::GetInstance().Open(dff.dff_path);
529     }
530 
531   private:
532     const SConfig& config;
533   };
534 
535   if (!std::visit(BootTitle(), boot->parameters))
536     return false;
537 
538   PatchEngine::LoadPatches();
539   HLE::PatchFixedFunctions();
540   return true;
541 }
542 
BootExecutableReader(const std::string & file_name)543 BootExecutableReader::BootExecutableReader(const std::string& file_name)
544     : BootExecutableReader(File::IOFile{file_name, "rb"})
545 {
546 }
547 
BootExecutableReader(File::IOFile file)548 BootExecutableReader::BootExecutableReader(File::IOFile file)
549 {
550   file.Seek(0, SEEK_SET);
551   m_bytes.resize(file.GetSize());
552   file.ReadBytes(m_bytes.data(), m_bytes.size());
553 }
554 
BootExecutableReader(std::vector<u8> bytes)555 BootExecutableReader::BootExecutableReader(std::vector<u8> bytes) : m_bytes(std::move(bytes))
556 {
557 }
558 
559 BootExecutableReader::~BootExecutableReader() = default;
560 
UpdateChecksum()561 void StateFlags::UpdateChecksum()
562 {
563   constexpr size_t length_in_bytes = sizeof(StateFlags) - 4;
564   constexpr size_t num_elements = length_in_bytes / sizeof(u32);
565   std::array<u32, num_elements> flag_data;
566   std::memcpy(flag_data.data(), &flags, length_in_bytes);
567   checksum = std::accumulate(flag_data.cbegin(), flag_data.cend(), 0U);
568 }
569 
UpdateStateFlags(std::function<void (StateFlags *)> update_function)570 void UpdateStateFlags(std::function<void(StateFlags*)> update_function)
571 {
572   CreateSystemMenuTitleDirs();
573   const std::string file_path = Common::GetTitleDataPath(Titles::SYSTEM_MENU) + "/" WII_STATE;
574   const auto fs = IOS::HLE::GetIOS()->GetFS();
575   constexpr IOS::HLE::FS::Mode rw_mode = IOS::HLE::FS::Mode::ReadWrite;
576   const auto file = fs->CreateAndOpenFile(IOS::SYSMENU_UID, IOS::SYSMENU_GID, file_path,
577                                           {rw_mode, rw_mode, rw_mode});
578   if (!file)
579     return;
580 
581   StateFlags state{};
582   if (file->GetStatus()->size == sizeof(StateFlags))
583     file->Read(&state, 1);
584 
585   update_function(&state);
586   state.UpdateChecksum();
587 
588   file->Seek(0, IOS::HLE::FS::SeekMode::Set);
589   file->Write(&state, 1);
590 }
591 
CreateSystemMenuTitleDirs()592 void CreateSystemMenuTitleDirs()
593 {
594   const auto es = IOS::HLE::GetIOS()->GetES();
595   es->CreateTitleDirectories(Titles::SYSTEM_MENU, IOS::SYSMENU_GID);
596 }
597