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