1 // Copyright 2009 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4
5 #include "Core/Movie.h"
6
7 #include <algorithm>
8 #include <array>
9 #include <cctype>
10 #include <cstring>
11 #include <iomanip>
12 #include <iterator>
13 #include <mbedtls/config.h>
14 #include <mbedtls/md.h>
15 #include <mutex>
16 #include <sstream>
17 #include <thread>
18 #include <utility>
19 #include <variant>
20 #include <vector>
21
22 #include <fmt/format.h>
23
24 #include "Common/Assert.h"
25 #include "Common/ChunkFile.h"
26 #include "Common/CommonPaths.h"
27 #include "Common/Config/Config.h"
28 #include "Common/File.h"
29 #include "Common/FileUtil.h"
30 #include "Common/Hash.h"
31 #include "Common/NandPaths.h"
32 #include "Common/StringUtil.h"
33 #include "Common/Timer.h"
34 #include "Common/Version.h"
35
36 #include "Core/Boot/Boot.h"
37 #include "Core/Config/MainSettings.h"
38 #include "Core/Config/SYSCONFSettings.h"
39 #include "Core/ConfigLoaders/MovieConfigLoader.h"
40 #include "Core/ConfigManager.h"
41 #include "Core/Core.h"
42 #include "Core/CoreTiming.h"
43 #include "Core/DSP/DSPCore.h"
44 #include "Core/HW/CPU.h"
45 #include "Core/HW/DVD/DVDInterface.h"
46 #include "Core/HW/EXI/EXI_DeviceIPL.h"
47 #include "Core/HW/EXI/EXI_DeviceMemoryCard.h"
48 #include "Core/HW/ProcessorInterface.h"
49 #include "Core/HW/SI/SI.h"
50 #include "Core/HW/SI/SI_Device.h"
51 #include "Core/HW/Wiimote.h"
52 #include "Core/HW/WiimoteCommon/DataReport.h"
53 #include "Core/HW/WiimoteCommon/WiimoteReport.h"
54
55 #include "Core/HW/WiimoteEmu/Encryption.h"
56 #include "Core/HW/WiimoteEmu/Extension/Classic.h"
57 #include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
58 #include "Core/HW/WiimoteEmu/ExtensionPort.h"
59
60 #include "Core/IOS/USB/Bluetooth/BTEmu.h"
61 #include "Core/IOS/USB/Bluetooth/WiimoteDevice.h"
62 #include "Core/NetPlayProto.h"
63 #include "Core/State.h"
64
65 #include "DiscIO/Enums.h"
66
67 #include "InputCommon/GCPadStatus.h"
68
69 #include "VideoCommon/VideoBackendBase.h"
70 #include "VideoCommon/VideoConfig.h"
71
72 // The chunk to allocate movie data in multiples of.
73 #define DTM_BASE_LENGTH (1024)
74
75 namespace Movie
76 {
77 using namespace WiimoteCommon;
78 using namespace WiimoteEmu;
79
80 static bool s_bReadOnly = true;
81 static u32 s_rerecords = 0;
82 static PlayMode s_playMode = MODE_NONE;
83
84 static u8 s_controllers = 0;
85 static ControllerState s_padState;
86 static DTMHeader tmpHeader;
87 static std::vector<u8> s_temp_input;
88 static u64 s_currentByte = 0;
89 static u64 s_currentFrame = 0, s_totalFrames = 0; // VI
90 static u64 s_currentLagCount = 0;
91 static u64 s_totalLagCount = 0; // just stats
92 static u64 s_currentInputCount = 0, s_totalInputCount = 0; // just stats
93 static u64 s_totalTickCount = 0, s_tickCountAtLastInput = 0; // just stats
94 static u64 s_recordingStartTime; // seconds since 1970 that recording started
95 static bool s_bSaveConfig = false, s_bNetPlay = false;
96 static bool s_bClearSave = false;
97 static bool s_bDiscChange = false;
98 static bool s_bReset = false;
99 static std::string s_author;
100 static std::string s_discChange;
101 static std::array<u8, 16> s_MD5;
102 static u8 s_bongos, s_memcards;
103 static std::array<u8, 20> s_revision;
104 static u32 s_DSPiromHash = 0;
105 static u32 s_DSPcoefHash = 0;
106
107 static bool s_bRecordingFromSaveState = false;
108 static bool s_bPolled = false;
109
110 // s_InputDisplay is used by both CPU and GPU (is mutable).
111 static std::mutex s_input_display_lock;
112 static std::string s_InputDisplay[8];
113
114 static GCManipFunction s_gc_manip_func;
115 static WiiManipFunction s_wii_manip_func;
116
117 static std::string s_current_file_name;
118
119 static void GetSettings();
IsMovieHeader(const std::array<u8,4> & magic)120 static bool IsMovieHeader(const std::array<u8, 4>& magic)
121 {
122 return magic[0] == 'D' && magic[1] == 'T' && magic[2] == 'M' && magic[3] == 0x1A;
123 }
124
ConvertGitRevisionToBytes(const std::string & revision)125 static std::array<u8, 20> ConvertGitRevisionToBytes(const std::string& revision)
126 {
127 std::array<u8, 20> revision_bytes{};
128
129 if (revision.size() % 2 == 0 && std::all_of(revision.begin(), revision.end(), ::isxdigit))
130 {
131 // The revision string normally contains a git commit hash,
132 // which is 40 hexadecimal digits long. In DTM files, each pair of
133 // hexadecimal digits is stored as one byte, for a total of 20 bytes.
134 size_t bytes_to_write = std::min(revision.size() / 2, revision_bytes.size());
135 unsigned int temp;
136 for (size_t i = 0; i < bytes_to_write; ++i)
137 {
138 sscanf(&revision[2 * i], "%02x", &temp);
139 revision_bytes[i] = temp;
140 }
141 }
142 else
143 {
144 // If the revision string for some reason doesn't only contain hexadecimal digit
145 // pairs, we instead copy the string with no conversion. This probably doesn't match
146 // the intended design of the DTM format, but it's the most sensible fallback.
147 size_t bytes_to_write = std::min(revision.size(), revision_bytes.size());
148 std::copy_n(std::begin(revision), bytes_to_write, std::begin(revision_bytes));
149 }
150
151 return revision_bytes;
152 }
153
154 // NOTE: GPU Thread
GetInputDisplay()155 std::string GetInputDisplay()
156 {
157 if (!IsMovieActive())
158 {
159 s_controllers = 0;
160 for (int i = 0; i < 4; ++i)
161 {
162 if (SerialInterface::GetDeviceType(i) != SerialInterface::SIDEVICE_NONE)
163 s_controllers |= (1 << i);
164 if (WiimoteCommon::GetSource(i) != WiimoteSource::None)
165 s_controllers |= (1 << (i + 4));
166 }
167 }
168
169 std::string input_display;
170 {
171 std::lock_guard<std::mutex> guard(s_input_display_lock);
172 for (int i = 0; i < 8; ++i)
173 {
174 if ((s_controllers & (1 << i)) != 0)
175 input_display += s_InputDisplay[i] + '\n';
176 }
177 }
178 return input_display;
179 }
180
181 // NOTE: GPU Thread
GetRTCDisplay()182 std::string GetRTCDisplay()
183 {
184 using ExpansionInterface::CEXIIPL;
185
186 const time_t current_time = CEXIIPL::GetEmulatedTime(CEXIIPL::UNIX_EPOCH);
187 const tm* const gm_time = gmtime(¤t_time);
188
189 std::ostringstream format_time;
190 format_time << std::put_time(gm_time, "Date/Time: %c\n");
191 return format_time.str();
192 }
193
FrameUpdate()194 void FrameUpdate()
195 {
196 s_currentFrame++;
197 if (!s_bPolled)
198 s_currentLagCount++;
199
200 if (IsRecordingInput())
201 {
202 s_totalFrames = s_currentFrame;
203 s_totalLagCount = s_currentLagCount;
204 }
205
206 s_bPolled = false;
207 }
208
209 static void CheckMD5();
210 static void GetMD5();
211
212 // called when game is booting up, even if no movie is active,
213 // but potentially after BeginRecordingInput or PlayInput has been called.
214 // NOTE: EmuThread
Init(const BootParameters & boot)215 void Init(const BootParameters& boot)
216 {
217 if (std::holds_alternative<BootParameters::Disc>(boot.parameters))
218 s_current_file_name = std::get<BootParameters::Disc>(boot.parameters).path;
219 else
220 s_current_file_name.clear();
221
222 s_bPolled = false;
223 s_bSaveConfig = false;
224 if (IsPlayingInput())
225 {
226 ReadHeader();
227 std::thread md5thread(CheckMD5);
228 md5thread.detach();
229 if (strncmp(tmpHeader.gameID.data(), SConfig::GetInstance().GetGameID().c_str(), 6))
230 {
231 PanicAlertT("The recorded game (%s) is not the same as the selected game (%s)",
232 tmpHeader.gameID.data(), SConfig::GetInstance().GetGameID().c_str());
233 EndPlayInput(false);
234 }
235 }
236
237 if (IsRecordingInput())
238 {
239 GetSettings();
240 std::thread md5thread(GetMD5);
241 md5thread.detach();
242 s_tickCountAtLastInput = 0;
243 }
244
245 memset(&s_padState, 0, sizeof(s_padState));
246
247 for (auto& disp : s_InputDisplay)
248 disp.clear();
249
250 if (!IsMovieActive())
251 {
252 s_bRecordingFromSaveState = false;
253 s_rerecords = 0;
254 s_currentByte = 0;
255 s_currentFrame = 0;
256 s_currentLagCount = 0;
257 s_currentInputCount = 0;
258 }
259 }
260
261 // NOTE: CPU Thread
InputUpdate()262 void InputUpdate()
263 {
264 s_currentInputCount++;
265 if (IsRecordingInput())
266 {
267 s_totalInputCount = s_currentInputCount;
268 s_totalTickCount += CoreTiming::GetTicks() - s_tickCountAtLastInput;
269 s_tickCountAtLastInput = CoreTiming::GetTicks();
270 }
271 }
272
273 // NOTE: CPU Thread
SetPolledDevice()274 void SetPolledDevice()
275 {
276 s_bPolled = true;
277 }
278
279 // NOTE: Host Thread
SetReadOnly(bool bEnabled)280 void SetReadOnly(bool bEnabled)
281 {
282 if (s_bReadOnly != bEnabled)
283 Core::DisplayMessage(bEnabled ? "Read-only mode." : "Read+Write mode.", 1000);
284
285 s_bReadOnly = bEnabled;
286 }
287
IsRecordingInput()288 bool IsRecordingInput()
289 {
290 return (s_playMode == MODE_RECORDING);
291 }
292
IsRecordingInputFromSaveState()293 bool IsRecordingInputFromSaveState()
294 {
295 return s_bRecordingFromSaveState;
296 }
297
IsJustStartingRecordingInputFromSaveState()298 bool IsJustStartingRecordingInputFromSaveState()
299 {
300 return IsRecordingInputFromSaveState() && s_currentFrame == 0;
301 }
302
IsJustStartingPlayingInputFromSaveState()303 bool IsJustStartingPlayingInputFromSaveState()
304 {
305 return IsRecordingInputFromSaveState() && s_currentFrame == 1 && IsPlayingInput();
306 }
307
IsPlayingInput()308 bool IsPlayingInput()
309 {
310 return (s_playMode == MODE_PLAYING);
311 }
312
IsMovieActive()313 bool IsMovieActive()
314 {
315 return s_playMode != MODE_NONE;
316 }
317
IsReadOnly()318 bool IsReadOnly()
319 {
320 return s_bReadOnly;
321 }
322
GetRecordingStartTime()323 u64 GetRecordingStartTime()
324 {
325 return s_recordingStartTime;
326 }
327
GetCurrentFrame()328 u64 GetCurrentFrame()
329 {
330 return s_currentFrame;
331 }
332
GetTotalFrames()333 u64 GetTotalFrames()
334 {
335 return s_totalFrames;
336 }
337
GetCurrentInputCount()338 u64 GetCurrentInputCount()
339 {
340 return s_currentInputCount;
341 }
342
GetTotalInputCount()343 u64 GetTotalInputCount()
344 {
345 return s_totalInputCount;
346 }
347
GetCurrentLagCount()348 u64 GetCurrentLagCount()
349 {
350 return s_currentLagCount;
351 }
352
GetTotalLagCount()353 u64 GetTotalLagCount()
354 {
355 return s_totalLagCount;
356 }
357
SetClearSave(bool enabled)358 void SetClearSave(bool enabled)
359 {
360 s_bClearSave = enabled;
361 }
362
SignalDiscChange(const std::string & new_path)363 void SignalDiscChange(const std::string& new_path)
364 {
365 if (Movie::IsRecordingInput())
366 {
367 size_t size_of_path_without_filename = new_path.find_last_of("/\\") + 1;
368 std::string filename = new_path.substr(size_of_path_without_filename);
369 constexpr size_t maximum_length = sizeof(DTMHeader::discChange);
370 if (filename.length() > maximum_length)
371 {
372 PanicAlertT("The disc change to \"%s\" could not be saved in the .dtm file.\n"
373 "The filename of the disc image must not be longer than 40 characters.",
374 filename.c_str());
375 }
376 s_discChange = filename;
377 s_bDiscChange = true;
378 }
379 }
380
SetReset(bool reset)381 void SetReset(bool reset)
382 {
383 s_bReset = reset;
384 }
385
IsUsingPad(int controller)386 bool IsUsingPad(int controller)
387 {
388 return ((s_controllers & (1 << controller)) != 0);
389 }
390
IsUsingBongo(int controller)391 bool IsUsingBongo(int controller)
392 {
393 return ((s_bongos & (1 << controller)) != 0);
394 }
395
IsUsingWiimote(int wiimote)396 bool IsUsingWiimote(int wiimote)
397 {
398 return ((s_controllers & (1 << (wiimote + 4))) != 0);
399 }
400
IsConfigSaved()401 bool IsConfigSaved()
402 {
403 return s_bSaveConfig;
404 }
405
IsStartingFromClearSave()406 bool IsStartingFromClearSave()
407 {
408 return s_bClearSave;
409 }
410
IsUsingMemcard(int memcard)411 bool IsUsingMemcard(int memcard)
412 {
413 return (s_memcards & (1 << memcard)) != 0;
414 }
415
IsNetPlayRecording()416 bool IsNetPlayRecording()
417 {
418 return s_bNetPlay;
419 }
420
421 // NOTE: Host Thread
ChangePads()422 void ChangePads()
423 {
424 if (!Core::IsRunning())
425 return;
426
427 int controllers = 0;
428
429 for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
430 {
431 if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i]))
432 controllers |= (1 << i);
433 }
434
435 if ((s_controllers & 0x0F) == controllers)
436 return;
437
438 for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
439 {
440 SerialInterface::SIDevices device = SerialInterface::SIDEVICE_NONE;
441 if (IsUsingPad(i))
442 {
443 if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i]))
444 {
445 device = SConfig::GetInstance().m_SIDevice[i];
446 }
447 else
448 {
449 device = IsUsingBongo(i) ? SerialInterface::SIDEVICE_GC_TARUKONGA :
450 SerialInterface::SIDEVICE_GC_CONTROLLER;
451 }
452 }
453
454 SerialInterface::ChangeDevice(device, i);
455 }
456 }
457
458 // NOTE: Host / Emu Threads
ChangeWiiPads(bool instantly)459 void ChangeWiiPads(bool instantly)
460 {
461 int controllers = 0;
462
463 for (int i = 0; i < MAX_WIIMOTES; ++i)
464 if (WiimoteCommon::GetSource(i) != WiimoteSource::None)
465 controllers |= (1 << i);
466
467 // This is important for Wiimotes, because they can desync easily if they get re-activated
468 if (instantly && (s_controllers >> 4) == controllers)
469 return;
470
471 const auto ios = IOS::HLE::GetIOS();
472 const auto bt = ios ? std::static_pointer_cast<IOS::HLE::Device::BluetoothEmu>(
473 ios->GetDeviceByName("/dev/usb/oh1/57e/305")) :
474 nullptr;
475 for (int i = 0; i < MAX_WIIMOTES; ++i)
476 {
477 const bool is_using_wiimote = IsUsingWiimote(i);
478
479 WiimoteCommon::SetSource(i, is_using_wiimote ? WiimoteSource::Emulated : WiimoteSource::None);
480 if (!SConfig::GetInstance().m_bt_passthrough_enabled && bt)
481 bt->AccessWiimoteByIndex(i)->Activate(is_using_wiimote);
482 }
483 }
484
485 // NOTE: Host Thread
BeginRecordingInput(int controllers)486 bool BeginRecordingInput(int controllers)
487 {
488 if (s_playMode != MODE_NONE || controllers == 0)
489 return false;
490
491 Core::RunAsCPUThread([controllers] {
492 s_controllers = controllers;
493 s_currentFrame = s_totalFrames = 0;
494 s_currentLagCount = s_totalLagCount = 0;
495 s_currentInputCount = s_totalInputCount = 0;
496 s_totalTickCount = s_tickCountAtLastInput = 0;
497 s_bongos = 0;
498 s_memcards = 0;
499 if (NetPlay::IsNetPlayRunning())
500 {
501 s_bNetPlay = true;
502 s_recordingStartTime = ExpansionInterface::CEXIIPL::NetPlay_GetEmulatedTime();
503 }
504 else if (SConfig::GetInstance().bEnableCustomRTC)
505 {
506 s_recordingStartTime = SConfig::GetInstance().m_customRTCValue;
507 }
508 else
509 {
510 s_recordingStartTime = Common::Timer::GetLocalTimeSinceJan1970();
511 }
512
513 s_rerecords = 0;
514
515 for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
516 {
517 if (SConfig::GetInstance().m_SIDevice[i] == SerialInterface::SIDEVICE_GC_TARUKONGA)
518 s_bongos |= (1 << i);
519 }
520
521 if (Core::IsRunningAndStarted())
522 {
523 const std::string save_path = File::GetUserPath(D_STATESAVES_IDX) + "dtm.sav";
524 if (File::Exists(save_path))
525 File::Delete(save_path);
526
527 State::SaveAs(save_path);
528 s_bRecordingFromSaveState = true;
529
530 std::thread md5thread(GetMD5);
531 md5thread.detach();
532 GetSettings();
533 }
534
535 // Wiimotes cause desync issues if they're not reset before launching the game
536 if (!Core::IsRunningAndStarted())
537 {
538 // This will also reset the wiimotes for gamecube games, but that shouldn't do anything
539 Wiimote::ResetAllWiimotes();
540 }
541
542 s_playMode = MODE_RECORDING;
543 s_author = SConfig::GetInstance().m_strMovieAuthor;
544 s_temp_input.clear();
545
546 s_currentByte = 0;
547
548 if (Core::IsRunning())
549 Core::UpdateWantDeterminism();
550 });
551
552 Core::DisplayMessage("Starting movie recording", 2000);
553 return true;
554 }
555
Analog2DToString(u32 x,u32 y,const std::string & prefix,u32 range=255)556 static std::string Analog2DToString(u32 x, u32 y, const std::string& prefix, u32 range = 255)
557 {
558 const u32 center = range / 2 + 1;
559
560 if ((x <= 1 || x == center || x >= range) && (y <= 1 || y == center || y >= range))
561 {
562 if (x != center || y != center)
563 {
564 if (x != center && y != center)
565 {
566 return fmt::format("{}:{},{}", prefix, x < center ? "LEFT" : "RIGHT",
567 y < center ? "DOWN" : "UP");
568 }
569
570 if (x != center)
571 {
572 return fmt::format("{}:{}", prefix, x < center ? "LEFT" : "RIGHT");
573 }
574
575 return fmt::format("{}:{}", prefix, y < center ? "DOWN" : "UP");
576 }
577
578 return "";
579 }
580
581 return fmt::format("{}:{},{}", prefix, x, y);
582 }
583
Analog1DToString(u32 v,const std::string & prefix,u32 range=255)584 static std::string Analog1DToString(u32 v, const std::string& prefix, u32 range = 255)
585 {
586 if (v == 0)
587 return "";
588
589 if (v == range)
590 return prefix;
591
592 return fmt::format("{}:{}", prefix, v);
593 }
594
595 // NOTE: CPU Thread
SetInputDisplayString(ControllerState padState,int controllerID)596 static void SetInputDisplayString(ControllerState padState, int controllerID)
597 {
598 std::string display_str = fmt::format("P{}:", controllerID + 1);
599
600 if (padState.is_connected)
601 {
602 if (padState.A)
603 display_str += " A";
604 if (padState.B)
605 display_str += " B";
606 if (padState.X)
607 display_str += " X";
608 if (padState.Y)
609 display_str += " Y";
610 if (padState.Z)
611 display_str += " Z";
612 if (padState.Start)
613 display_str += " START";
614
615 if (padState.DPadUp)
616 display_str += " UP";
617 if (padState.DPadDown)
618 display_str += " DOWN";
619 if (padState.DPadLeft)
620 display_str += " LEFT";
621 if (padState.DPadRight)
622 display_str += " RIGHT";
623 if (padState.reset)
624 display_str += " RESET";
625
626 display_str += Analog1DToString(padState.TriggerL, " L");
627 display_str += Analog1DToString(padState.TriggerR, " R");
628 display_str += Analog2DToString(padState.AnalogStickX, padState.AnalogStickY, " ANA");
629 display_str += Analog2DToString(padState.CStickX, padState.CStickY, " C");
630 }
631 else
632 {
633 display_str += " DISCONNECTED";
634 }
635
636 std::lock_guard<std::mutex> guard(s_input_display_lock);
637 s_InputDisplay[controllerID] = std::move(display_str);
638 }
639
640 // NOTE: CPU Thread
SetWiiInputDisplayString(int remoteID,const DataReportBuilder & rpt,int ext,const EncryptionKey & key)641 static void SetWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt, int ext,
642 const EncryptionKey& key)
643 {
644 int controllerID = remoteID + 4;
645
646 std::string display_str = fmt::format("R{}:", remoteID + 1);
647
648 if (rpt.HasCore())
649 {
650 ButtonData buttons;
651 rpt.GetCoreData(&buttons);
652
653 if (buttons.left)
654 display_str += " LEFT";
655 if (buttons.right)
656 display_str += " RIGHT";
657 if (buttons.down)
658 display_str += " DOWN";
659 if (buttons.up)
660 display_str += " UP";
661 if (buttons.a)
662 display_str += " A";
663 if (buttons.b)
664 display_str += " B";
665 if (buttons.plus)
666 display_str += " +";
667 if (buttons.minus)
668 display_str += " -";
669 if (buttons.one)
670 display_str += " 1";
671 if (buttons.two)
672 display_str += " 2";
673 if (buttons.home)
674 display_str += " HOME";
675 }
676
677 if (rpt.HasAccel())
678 {
679 AccelData accel_data;
680 rpt.GetAccelData(&accel_data);
681
682 // FYI: This will only print partial data for interleaved reports.
683
684 display_str +=
685 fmt::format(" ACC:{},{},{}", accel_data.value.x, accel_data.value.y, accel_data.value.z);
686 }
687
688 if (rpt.HasIR())
689 {
690 const u8* const ir_data = rpt.GetIRDataPtr();
691
692 // TODO: This does not handle the different IR formats.
693
694 const u16 x = ir_data[0] | ((ir_data[2] >> 4 & 0x3) << 8);
695 const u16 y = ir_data[1] | ((ir_data[2] >> 6 & 0x3) << 8);
696 display_str += fmt::format(" IR:{},{}", x, y);
697 }
698
699 // Nunchuk
700 if (rpt.HasExt() && ext == ExtensionNumber::NUNCHUK)
701 {
702 const u8* const extData = rpt.GetExtDataPtr();
703
704 Nunchuk::DataFormat nunchuk;
705 memcpy(&nunchuk, extData, sizeof(nunchuk));
706 key.Decrypt((u8*)&nunchuk, 0, sizeof(nunchuk));
707 nunchuk.bt.hex = nunchuk.bt.hex ^ 0x3;
708
709 const std::string accel = fmt::format(" N-ACC:{},{},{}", nunchuk.GetAccelX(),
710 nunchuk.GetAccelY(), nunchuk.GetAccelZ());
711
712 if (nunchuk.bt.c)
713 display_str += " C";
714 if (nunchuk.bt.z)
715 display_str += " Z";
716 display_str += accel;
717 display_str += Analog2DToString(nunchuk.jx, nunchuk.jy, " ANA");
718 }
719
720 // Classic controller
721 if (rpt.HasExt() && ext == ExtensionNumber::CLASSIC)
722 {
723 const u8* const extData = rpt.GetExtDataPtr();
724
725 Classic::DataFormat cc;
726 memcpy(&cc, extData, sizeof(cc));
727 key.Decrypt((u8*)&cc, 0, sizeof(cc));
728 cc.bt.hex = cc.bt.hex ^ 0xFFFF;
729
730 if (cc.bt.dpad_left)
731 display_str += " LEFT";
732 if (cc.bt.dpad_right)
733 display_str += " RIGHT";
734 if (cc.bt.dpad_down)
735 display_str += " DOWN";
736 if (cc.bt.dpad_up)
737 display_str += " UP";
738 if (cc.bt.a)
739 display_str += " A";
740 if (cc.bt.b)
741 display_str += " B";
742 if (cc.bt.x)
743 display_str += " X";
744 if (cc.bt.y)
745 display_str += " Y";
746 if (cc.bt.zl)
747 display_str += " ZL";
748 if (cc.bt.zr)
749 display_str += " ZR";
750 if (cc.bt.plus)
751 display_str += " +";
752 if (cc.bt.minus)
753 display_str += " -";
754 if (cc.bt.home)
755 display_str += " HOME";
756
757 display_str += Analog1DToString(cc.GetLeftTrigger().value, " L", 31);
758 display_str += Analog1DToString(cc.GetRightTrigger().value, " R", 31);
759
760 const auto left_stick = cc.GetLeftStick().value;
761 display_str += Analog2DToString(left_stick.x, left_stick.y, " ANA", 63);
762
763 const auto right_stick = cc.GetRightStick().value;
764 display_str += Analog2DToString(right_stick.x, right_stick.y, " R-ANA", 31);
765 }
766
767 std::lock_guard<std::mutex> guard(s_input_display_lock);
768 s_InputDisplay[controllerID] = std::move(display_str);
769 }
770
771 // NOTE: CPU Thread
CheckPadStatus(const GCPadStatus * PadStatus,int controllerID)772 void CheckPadStatus(const GCPadStatus* PadStatus, int controllerID)
773 {
774 s_padState.A = ((PadStatus->button & PAD_BUTTON_A) != 0);
775 s_padState.B = ((PadStatus->button & PAD_BUTTON_B) != 0);
776 s_padState.X = ((PadStatus->button & PAD_BUTTON_X) != 0);
777 s_padState.Y = ((PadStatus->button & PAD_BUTTON_Y) != 0);
778 s_padState.Z = ((PadStatus->button & PAD_TRIGGER_Z) != 0);
779 s_padState.Start = ((PadStatus->button & PAD_BUTTON_START) != 0);
780
781 s_padState.DPadUp = ((PadStatus->button & PAD_BUTTON_UP) != 0);
782 s_padState.DPadDown = ((PadStatus->button & PAD_BUTTON_DOWN) != 0);
783 s_padState.DPadLeft = ((PadStatus->button & PAD_BUTTON_LEFT) != 0);
784 s_padState.DPadRight = ((PadStatus->button & PAD_BUTTON_RIGHT) != 0);
785
786 s_padState.L = ((PadStatus->button & PAD_TRIGGER_L) != 0);
787 s_padState.R = ((PadStatus->button & PAD_TRIGGER_R) != 0);
788 s_padState.TriggerL = PadStatus->triggerLeft;
789 s_padState.TriggerR = PadStatus->triggerRight;
790
791 s_padState.AnalogStickX = PadStatus->stickX;
792 s_padState.AnalogStickY = PadStatus->stickY;
793
794 s_padState.CStickX = PadStatus->substickX;
795 s_padState.CStickY = PadStatus->substickY;
796
797 s_padState.is_connected = PadStatus->isConnected;
798
799 s_padState.get_origin = (PadStatus->button & PAD_GET_ORIGIN) != 0;
800
801 s_padState.disc = s_bDiscChange;
802 s_bDiscChange = false;
803 s_padState.reset = s_bReset;
804 s_bReset = false;
805
806 SetInputDisplayString(s_padState, controllerID);
807 }
808
809 // NOTE: CPU Thread
RecordInput(const GCPadStatus * PadStatus,int controllerID)810 void RecordInput(const GCPadStatus* PadStatus, int controllerID)
811 {
812 if (!IsRecordingInput() || !IsUsingPad(controllerID))
813 return;
814
815 CheckPadStatus(PadStatus, controllerID);
816
817 s_temp_input.resize(s_currentByte + sizeof(ControllerState));
818 memcpy(&s_temp_input[s_currentByte], &s_padState, sizeof(ControllerState));
819 s_currentByte += sizeof(ControllerState);
820 }
821
822 // NOTE: CPU Thread
CheckWiimoteStatus(int wiimote,const DataReportBuilder & rpt,int ext,const EncryptionKey & key)823 void CheckWiimoteStatus(int wiimote, const DataReportBuilder& rpt, int ext,
824 const EncryptionKey& key)
825 {
826 SetWiiInputDisplayString(wiimote, rpt, ext, key);
827
828 if (IsRecordingInput())
829 RecordWiimote(wiimote, rpt.GetDataPtr(), rpt.GetDataSize());
830 }
831
RecordWiimote(int wiimote,const u8 * data,u8 size)832 void RecordWiimote(int wiimote, const u8* data, u8 size)
833 {
834 if (!IsRecordingInput() || !IsUsingWiimote(wiimote))
835 return;
836
837 InputUpdate();
838 s_temp_input.resize(s_currentByte + size + 1);
839 s_temp_input[s_currentByte++] = size;
840 memcpy(&s_temp_input[s_currentByte], data, size);
841 s_currentByte += size;
842 }
843
844 // NOTE: EmuThread / Host Thread
ReadHeader()845 void ReadHeader()
846 {
847 s_controllers = tmpHeader.controllers;
848 s_recordingStartTime = tmpHeader.recordingStartTime;
849 if (s_rerecords < tmpHeader.numRerecords)
850 s_rerecords = tmpHeader.numRerecords;
851
852 if (tmpHeader.bSaveConfig)
853 {
854 s_bSaveConfig = true;
855 Config::AddLayer(ConfigLoaders::GenerateMovieConfigLoader(&tmpHeader));
856 SConfig::GetInstance().bJITFollowBranch = tmpHeader.bFollowBranch;
857 s_bClearSave = tmpHeader.bClearSave;
858 s_memcards = tmpHeader.memcards;
859 s_bongos = tmpHeader.bongos;
860 s_bNetPlay = tmpHeader.bNetPlay;
861 s_revision = tmpHeader.revision;
862 }
863 else
864 {
865 GetSettings();
866 }
867
868 s_discChange = {tmpHeader.discChange.begin(), tmpHeader.discChange.end()};
869 s_author = {tmpHeader.author.begin(), tmpHeader.author.end()};
870 s_MD5 = tmpHeader.md5;
871 s_DSPiromHash = tmpHeader.DSPiromHash;
872 s_DSPcoefHash = tmpHeader.DSPcoefHash;
873 }
874
875 // NOTE: Host Thread
PlayInput(const std::string & movie_path,std::optional<std::string> * savestate_path)876 bool PlayInput(const std::string& movie_path, std::optional<std::string>* savestate_path)
877 {
878 if (s_playMode != MODE_NONE)
879 return false;
880
881 File::IOFile recording_file(movie_path, "rb");
882 if (!recording_file.ReadArray(&tmpHeader, 1))
883 return false;
884
885 if (!IsMovieHeader(tmpHeader.filetype))
886 {
887 PanicAlertT("Invalid recording file");
888 return false;
889 }
890
891 ReadHeader();
892 s_totalFrames = tmpHeader.frameCount;
893 s_totalLagCount = tmpHeader.lagCount;
894 s_totalInputCount = tmpHeader.inputCount;
895 s_totalTickCount = tmpHeader.tickCount;
896 s_currentFrame = 0;
897 s_currentLagCount = 0;
898 s_currentInputCount = 0;
899
900 s_playMode = MODE_PLAYING;
901
902 // Wiimotes cause desync issues if they're not reset before launching the game
903 Wiimote::ResetAllWiimotes();
904
905 Core::UpdateWantDeterminism();
906
907 s_temp_input.resize(recording_file.GetSize() - 256);
908 recording_file.ReadBytes(s_temp_input.data(), s_temp_input.size());
909 s_currentByte = 0;
910 recording_file.Close();
911
912 // Load savestate (and skip to frame data)
913 if (tmpHeader.bFromSaveState && savestate_path)
914 {
915 const std::string savestate_path_temp = movie_path + ".sav";
916 if (File::Exists(savestate_path_temp))
917 *savestate_path = savestate_path_temp;
918 s_bRecordingFromSaveState = true;
919 Movie::LoadInput(movie_path);
920 }
921
922 return true;
923 }
924
DoState(PointerWrap & p)925 void DoState(PointerWrap& p)
926 {
927 // many of these could be useful to save even when no movie is active,
928 // and the data is tiny, so let's just save it regardless of movie state.
929 p.Do(s_currentFrame);
930 p.Do(s_currentByte);
931 p.Do(s_currentLagCount);
932 p.Do(s_currentInputCount);
933 p.Do(s_bPolled);
934 p.Do(s_tickCountAtLastInput);
935 // other variables (such as s_totalBytes and s_totalFrames) are set in LoadInput
936 }
937
938 // NOTE: Host Thread
LoadInput(const std::string & movie_path)939 void LoadInput(const std::string& movie_path)
940 {
941 File::IOFile t_record;
942 if (!t_record.Open(movie_path, "r+b"))
943 {
944 PanicAlertT("Failed to read %s", movie_path.c_str());
945 EndPlayInput(false);
946 return;
947 }
948
949 t_record.ReadArray(&tmpHeader, 1);
950
951 if (!IsMovieHeader(tmpHeader.filetype))
952 {
953 PanicAlertT("Savestate movie %s is corrupted, movie recording stopping...", movie_path.c_str());
954 EndPlayInput(false);
955 return;
956 }
957 ReadHeader();
958 if (!s_bReadOnly)
959 {
960 s_rerecords++;
961 tmpHeader.numRerecords = s_rerecords;
962 t_record.Seek(0, SEEK_SET);
963 t_record.WriteArray(&tmpHeader, 1);
964 }
965
966 ChangePads();
967 if (SConfig::GetInstance().bWii)
968 ChangeWiiPads(true);
969
970 u64 totalSavedBytes = t_record.GetSize() - 256;
971
972 bool afterEnd = false;
973 // This can only happen if the user manually deletes data from the dtm.
974 if (s_currentByte > totalSavedBytes)
975 {
976 PanicAlertT("Warning: You loaded a save whose movie ends before the current frame in the save "
977 "(byte %u < %u) (frame %u < %u). You should load another save before continuing.",
978 (u32)totalSavedBytes + 256, (u32)s_currentByte + 256, (u32)tmpHeader.frameCount,
979 (u32)s_currentFrame);
980 afterEnd = true;
981 }
982
983 if (!s_bReadOnly || s_temp_input.empty())
984 {
985 s_totalFrames = tmpHeader.frameCount;
986 s_totalLagCount = tmpHeader.lagCount;
987 s_totalInputCount = tmpHeader.inputCount;
988 s_totalTickCount = s_tickCountAtLastInput = tmpHeader.tickCount;
989
990 s_temp_input.resize(static_cast<size_t>(totalSavedBytes));
991 t_record.ReadBytes(s_temp_input.data(), s_temp_input.size());
992 }
993 else if (s_currentByte > 0)
994 {
995 if (s_currentByte > totalSavedBytes)
996 {
997 }
998 else if (s_currentByte > s_temp_input.size())
999 {
1000 afterEnd = true;
1001 PanicAlertT("Warning: You loaded a save that's after the end of the current movie. (byte %u "
1002 "> %zu) (input %u > %u). You should load another save before continuing, or load "
1003 "this state with read-only mode off.",
1004 (u32)s_currentByte + 256, s_temp_input.size() + 256, (u32)s_currentInputCount,
1005 (u32)s_totalInputCount);
1006 }
1007 else if (s_currentByte > 0 && !s_temp_input.empty())
1008 {
1009 // verify identical from movie start to the save's current frame
1010 std::vector<u8> movInput(s_currentByte);
1011 t_record.ReadArray(movInput.data(), movInput.size());
1012
1013 const auto result = std::mismatch(movInput.begin(), movInput.end(), s_temp_input.begin());
1014
1015 if (result.first != movInput.end())
1016 {
1017 const ptrdiff_t mismatch_index = std::distance(movInput.begin(), result.first);
1018
1019 // this is a "you did something wrong" alert for the user's benefit.
1020 // we'll try to say what's going on in excruciating detail, otherwise the user might not
1021 // believe us.
1022 if (IsUsingWiimote(0))
1023 {
1024 const size_t byte_offset = static_cast<size_t>(mismatch_index) + sizeof(DTMHeader);
1025
1026 // TODO: more detail
1027 PanicAlertT("Warning: You loaded a save whose movie mismatches on byte %zu (0x%zX). "
1028 "You should load another save before continuing, or load this state with "
1029 "read-only mode off. Otherwise you'll probably get a desync.",
1030 byte_offset, byte_offset);
1031
1032 std::copy(movInput.begin(), movInput.end(), s_temp_input.begin());
1033 }
1034 else
1035 {
1036 const ptrdiff_t frame = mismatch_index / sizeof(ControllerState);
1037 ControllerState curPadState;
1038 memcpy(&curPadState, &s_temp_input[frame * sizeof(ControllerState)],
1039 sizeof(ControllerState));
1040 ControllerState movPadState;
1041 memcpy(&movPadState, &s_temp_input[frame * sizeof(ControllerState)],
1042 sizeof(ControllerState));
1043 PanicAlertT(
1044 "Warning: You loaded a save whose movie mismatches on frame %td. You should load "
1045 "another save before continuing, or load this state with read-only mode off. "
1046 "Otherwise you'll probably get a desync.\n\n"
1047 "More information: The current movie is %d frames long and the savestate's movie "
1048 "is %d frames long.\n\n"
1049 "On frame %td, the current movie presses:\n"
1050 "Start=%d, A=%d, B=%d, X=%d, Y=%d, Z=%d, DUp=%d, DDown=%d, DLeft=%d, DRight=%d, "
1051 "L=%d, R=%d, LT=%d, RT=%d, AnalogX=%d, AnalogY=%d, CX=%d, CY=%d, Connected=%d"
1052 "\n\n"
1053 "On frame %td, the savestate's movie presses:\n"
1054 "Start=%d, A=%d, B=%d, X=%d, Y=%d, Z=%d, DUp=%d, DDown=%d, DLeft=%d, DRight=%d, "
1055 "L=%d, R=%d, LT=%d, RT=%d, AnalogX=%d, AnalogY=%d, CX=%d, CY=%d, Connected=%d",
1056 frame, (int)s_totalFrames, (int)tmpHeader.frameCount, frame, (int)curPadState.Start,
1057 (int)curPadState.A, (int)curPadState.B, (int)curPadState.X, (int)curPadState.Y,
1058 (int)curPadState.Z, (int)curPadState.DPadUp, (int)curPadState.DPadDown,
1059 (int)curPadState.DPadLeft, (int)curPadState.DPadRight, (int)curPadState.L,
1060 (int)curPadState.R, (int)curPadState.TriggerL, (int)curPadState.TriggerR,
1061 (int)curPadState.AnalogStickX, (int)curPadState.AnalogStickY,
1062 (int)curPadState.CStickX, (int)curPadState.CStickY, (int)curPadState.is_connected,
1063 frame, (int)movPadState.Start, (int)movPadState.A, (int)movPadState.B,
1064 (int)movPadState.X, (int)movPadState.Y, (int)movPadState.Z, (int)movPadState.DPadUp,
1065 (int)movPadState.DPadDown, (int)movPadState.DPadLeft, (int)movPadState.DPadRight,
1066 (int)movPadState.L, (int)movPadState.R, (int)movPadState.TriggerL,
1067 (int)movPadState.TriggerR, (int)movPadState.AnalogStickX,
1068 (int)movPadState.AnalogStickY, (int)movPadState.CStickX, (int)movPadState.CStickY,
1069 (int)curPadState.is_connected);
1070 }
1071 }
1072 }
1073 }
1074 t_record.Close();
1075
1076 s_bSaveConfig = tmpHeader.bSaveConfig;
1077
1078 if (!afterEnd)
1079 {
1080 if (s_bReadOnly)
1081 {
1082 if (s_playMode != MODE_PLAYING)
1083 {
1084 s_playMode = MODE_PLAYING;
1085 Core::UpdateWantDeterminism();
1086 Core::DisplayMessage("Switched to playback", 2000);
1087 }
1088 }
1089 else
1090 {
1091 if (s_playMode != MODE_RECORDING)
1092 {
1093 s_playMode = MODE_RECORDING;
1094 Core::UpdateWantDeterminism();
1095 Core::DisplayMessage("Switched to recording", 2000);
1096 }
1097 }
1098 }
1099 else
1100 {
1101 EndPlayInput(false);
1102 }
1103 }
1104
1105 // NOTE: CPU Thread
CheckInputEnd()1106 static void CheckInputEnd()
1107 {
1108 if (s_currentByte >= s_temp_input.size() ||
1109 (CoreTiming::GetTicks() > s_totalTickCount && !IsRecordingInputFromSaveState()))
1110 {
1111 EndPlayInput(!s_bReadOnly);
1112 }
1113 }
1114
1115 // NOTE: CPU Thread
PlayController(GCPadStatus * PadStatus,int controllerID)1116 void PlayController(GCPadStatus* PadStatus, int controllerID)
1117 {
1118 // Correct playback is entirely dependent on the emulator polling the controllers
1119 // in the same order done during recording
1120 if (!IsPlayingInput() || !IsUsingPad(controllerID) || s_temp_input.empty())
1121 return;
1122
1123 if (s_currentByte + sizeof(ControllerState) > s_temp_input.size())
1124 {
1125 PanicAlertT("Premature movie end in PlayController. %u + %zu > %zu", (u32)s_currentByte,
1126 sizeof(ControllerState), s_temp_input.size());
1127 EndPlayInput(!s_bReadOnly);
1128 return;
1129 }
1130
1131 memcpy(&s_padState, &s_temp_input[s_currentByte], sizeof(ControllerState));
1132 s_currentByte += sizeof(ControllerState);
1133
1134 PadStatus->isConnected = s_padState.is_connected;
1135
1136 PadStatus->triggerLeft = s_padState.TriggerL;
1137 PadStatus->triggerRight = s_padState.TriggerR;
1138
1139 PadStatus->stickX = s_padState.AnalogStickX;
1140 PadStatus->stickY = s_padState.AnalogStickY;
1141
1142 PadStatus->substickX = s_padState.CStickX;
1143 PadStatus->substickY = s_padState.CStickY;
1144
1145 PadStatus->button = 0;
1146 PadStatus->button |= PAD_USE_ORIGIN;
1147
1148 if (s_padState.A)
1149 {
1150 PadStatus->button |= PAD_BUTTON_A;
1151 PadStatus->analogA = 0xFF;
1152 }
1153 if (s_padState.B)
1154 {
1155 PadStatus->button |= PAD_BUTTON_B;
1156 PadStatus->analogB = 0xFF;
1157 }
1158 if (s_padState.X)
1159 PadStatus->button |= PAD_BUTTON_X;
1160 if (s_padState.Y)
1161 PadStatus->button |= PAD_BUTTON_Y;
1162 if (s_padState.Z)
1163 PadStatus->button |= PAD_TRIGGER_Z;
1164 if (s_padState.Start)
1165 PadStatus->button |= PAD_BUTTON_START;
1166
1167 if (s_padState.DPadUp)
1168 PadStatus->button |= PAD_BUTTON_UP;
1169 if (s_padState.DPadDown)
1170 PadStatus->button |= PAD_BUTTON_DOWN;
1171 if (s_padState.DPadLeft)
1172 PadStatus->button |= PAD_BUTTON_LEFT;
1173 if (s_padState.DPadRight)
1174 PadStatus->button |= PAD_BUTTON_RIGHT;
1175
1176 if (s_padState.L)
1177 PadStatus->button |= PAD_TRIGGER_L;
1178 if (s_padState.R)
1179 PadStatus->button |= PAD_TRIGGER_R;
1180
1181 if (s_padState.get_origin)
1182 PadStatus->button |= PAD_GET_ORIGIN;
1183
1184 if (s_padState.disc)
1185 {
1186 Core::RunAsCPUThread([] {
1187 if (!DVDInterface::AutoChangeDisc())
1188 {
1189 CPU::Break();
1190 PanicAlertT("Change the disc to %s", s_discChange.c_str());
1191 }
1192 });
1193 }
1194
1195 if (s_padState.reset)
1196 ProcessorInterface::ResetButton_Tap();
1197
1198 SetInputDisplayString(s_padState, controllerID);
1199 CheckInputEnd();
1200 }
1201
1202 // NOTE: CPU Thread
PlayWiimote(int wiimote,WiimoteCommon::DataReportBuilder & rpt,int ext,const EncryptionKey & key)1203 bool PlayWiimote(int wiimote, WiimoteCommon::DataReportBuilder& rpt, int ext,
1204 const EncryptionKey& key)
1205 {
1206 if (!IsPlayingInput() || !IsUsingWiimote(wiimote) || s_temp_input.empty())
1207 return false;
1208
1209 if (s_currentByte > s_temp_input.size())
1210 {
1211 PanicAlertT("Premature movie end in PlayWiimote. %u > %zu", (u32)s_currentByte,
1212 s_temp_input.size());
1213 EndPlayInput(!s_bReadOnly);
1214 return false;
1215 }
1216
1217 const u8 size = rpt.GetDataSize();
1218 const u8 sizeInMovie = s_temp_input[s_currentByte];
1219
1220 if (size != sizeInMovie)
1221 {
1222 PanicAlertT("Fatal desync. Aborting playback. (Error in PlayWiimote: %u != %u, byte %u.)%s",
1223 (u32)sizeInMovie, (u32)size, (u32)s_currentByte,
1224 (s_controllers & 0xF) ?
1225 " Try re-creating the recording with all GameCube controllers "
1226 "disabled (in Configure > GameCube > Device Settings)." :
1227 "");
1228 EndPlayInput(!s_bReadOnly);
1229 return false;
1230 }
1231
1232 s_currentByte++;
1233
1234 if (s_currentByte + size > s_temp_input.size())
1235 {
1236 PanicAlertT("Premature movie end in PlayWiimote. %u + %d > %zu", (u32)s_currentByte, size,
1237 s_temp_input.size());
1238 EndPlayInput(!s_bReadOnly);
1239 return false;
1240 }
1241
1242 memcpy(rpt.GetDataPtr(), &s_temp_input[s_currentByte], size);
1243 s_currentByte += size;
1244
1245 s_currentInputCount++;
1246
1247 CheckInputEnd();
1248 return true;
1249 }
1250
1251 // NOTE: Host / EmuThread / CPU Thread
EndPlayInput(bool cont)1252 void EndPlayInput(bool cont)
1253 {
1254 if (cont)
1255 {
1256 // If !IsMovieActive(), changing s_playMode requires calling UpdateWantDeterminism
1257 ASSERT(IsMovieActive());
1258
1259 s_playMode = MODE_RECORDING;
1260 Core::DisplayMessage("Reached movie end. Resuming recording.", 2000);
1261 }
1262 else if (s_playMode != MODE_NONE)
1263 {
1264 // We can be called by EmuThread during boot (CPU::State::PowerDown)
1265 bool was_running = Core::IsRunningAndStarted() && !CPU::IsStepping();
1266 if (was_running)
1267 CPU::Break();
1268 s_rerecords = 0;
1269 s_currentByte = 0;
1270 s_playMode = MODE_NONE;
1271 Core::DisplayMessage("Movie End.", 2000);
1272 s_bRecordingFromSaveState = false;
1273 // we don't clear these things because otherwise we can't resume playback if we load a movie
1274 // state later
1275 // s_totalFrames = s_totalBytes = 0;
1276 // delete tmpInput;
1277 // tmpInput = nullptr;
1278
1279 Core::QueueHostJob([=] {
1280 Core::UpdateWantDeterminism();
1281 if (was_running && !SConfig::GetInstance().m_PauseMovie)
1282 CPU::EnableStepping(false);
1283 });
1284 }
1285 }
1286
1287 // NOTE: Save State + Host Thread
SaveRecording(const std::string & filename)1288 void SaveRecording(const std::string& filename)
1289 {
1290 File::IOFile save_record(filename, "wb");
1291 // Create the real header now and write it
1292 DTMHeader header;
1293 memset(&header, 0, sizeof(DTMHeader));
1294
1295 header.filetype[0] = 'D';
1296 header.filetype[1] = 'T';
1297 header.filetype[2] = 'M';
1298 header.filetype[3] = 0x1A;
1299 strncpy(header.gameID.data(), SConfig::GetInstance().GetGameID().c_str(), 6);
1300 header.bWii = SConfig::GetInstance().bWii;
1301 header.bFollowBranch = SConfig::GetInstance().bJITFollowBranch;
1302 header.controllers = s_controllers & (SConfig::GetInstance().bWii ? 0xFF : 0x0F);
1303
1304 header.bFromSaveState = s_bRecordingFromSaveState;
1305 header.frameCount = s_totalFrames;
1306 header.lagCount = s_totalLagCount;
1307 header.inputCount = s_totalInputCount;
1308 header.numRerecords = s_rerecords;
1309 header.recordingStartTime = s_recordingStartTime;
1310
1311 header.bSaveConfig = true;
1312 ConfigLoaders::SaveToDTM(&header);
1313 header.memcards = s_memcards;
1314 header.bClearSave = s_bClearSave;
1315 header.bNetPlay = s_bNetPlay;
1316 strncpy(header.discChange.data(), s_discChange.c_str(), header.discChange.size());
1317 strncpy(header.author.data(), s_author.c_str(), header.author.size());
1318 header.md5 = s_MD5;
1319 header.bongos = s_bongos;
1320 header.revision = s_revision;
1321 header.DSPiromHash = s_DSPiromHash;
1322 header.DSPcoefHash = s_DSPcoefHash;
1323 header.tickCount = s_totalTickCount;
1324
1325 // TODO
1326 header.uniqueID = 0;
1327 // header.audioEmulator;
1328
1329 save_record.WriteArray(&header, 1);
1330
1331 bool success = save_record.WriteBytes(s_temp_input.data(), s_temp_input.size());
1332
1333 if (success && s_bRecordingFromSaveState)
1334 {
1335 std::string stateFilename = filename + ".sav";
1336 success = File::Copy(File::GetUserPath(D_STATESAVES_IDX) + "dtm.sav", stateFilename);
1337 }
1338
1339 if (success)
1340 Core::DisplayMessage(fmt::format("DTM {} saved", filename), 2000);
1341 else
1342 Core::DisplayMessage(fmt::format("Failed to save {}", filename), 2000);
1343 }
1344
SetGCInputManip(GCManipFunction func)1345 void SetGCInputManip(GCManipFunction func)
1346 {
1347 s_gc_manip_func = std::move(func);
1348 }
SetWiiInputManip(WiiManipFunction func)1349 void SetWiiInputManip(WiiManipFunction func)
1350 {
1351 s_wii_manip_func = std::move(func);
1352 }
1353
1354 // NOTE: CPU Thread
CallGCInputManip(GCPadStatus * PadStatus,int controllerID)1355 void CallGCInputManip(GCPadStatus* PadStatus, int controllerID)
1356 {
1357 if (s_gc_manip_func)
1358 s_gc_manip_func(PadStatus, controllerID);
1359 }
1360 // NOTE: CPU Thread
CallWiiInputManip(DataReportBuilder & rpt,int controllerID,int ext,const EncryptionKey & key)1361 void CallWiiInputManip(DataReportBuilder& rpt, int controllerID, int ext, const EncryptionKey& key)
1362 {
1363 if (s_wii_manip_func)
1364 s_wii_manip_func(rpt, controllerID, ext, key);
1365 }
1366
1367 // NOTE: GPU Thread
SetGraphicsConfig()1368 void SetGraphicsConfig()
1369 {
1370 g_Config.bEFBAccessEnable = tmpHeader.bEFBAccessEnable;
1371 g_Config.bSkipEFBCopyToRam = tmpHeader.bSkipEFBCopyToRam;
1372 g_Config.bEFBEmulateFormatChanges = tmpHeader.bEFBEmulateFormatChanges;
1373 g_Config.bImmediateXFB = tmpHeader.bImmediateXFB;
1374 g_Config.bSkipXFBCopyToRam = tmpHeader.bSkipXFBCopyToRam;
1375 }
1376
1377 // NOTE: EmuThread / Host Thread
GetSettings()1378 void GetSettings()
1379 {
1380 const bool slot_a_has_raw_memcard =
1381 SConfig::GetInstance().m_EXIDevice[0] == ExpansionInterface::EXIDEVICE_MEMORYCARD;
1382 const bool slot_a_has_gci_folder =
1383 SConfig::GetInstance().m_EXIDevice[0] == ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER;
1384 const bool slot_b_has_raw_memcard =
1385 SConfig::GetInstance().m_EXIDevice[1] == ExpansionInterface::EXIDEVICE_MEMORYCARD;
1386 const bool slot_b_has_gci_folder =
1387 SConfig::GetInstance().m_EXIDevice[1] == ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER;
1388
1389 s_bSaveConfig = true;
1390 s_bNetPlay = NetPlay::IsNetPlayRunning();
1391 if (SConfig::GetInstance().bWii)
1392 {
1393 u64 title_id = SConfig::GetInstance().GetTitleID();
1394 s_bClearSave = !File::Exists(Common::GetTitleDataPath(title_id, Common::FROM_SESSION_ROOT) +
1395 "/banner.bin");
1396 }
1397 else
1398 {
1399 const auto gci_folder_has_saves = [](int card_index) {
1400 const auto [path, migrate] = ExpansionInterface::CEXIMemoryCard::GetGCIFolderPath(
1401 card_index, ExpansionInterface::AllowMovieFolder::No);
1402 const u64 number_of_saves = File::ScanDirectoryTree(path, false).size;
1403 return number_of_saves > 0;
1404 };
1405
1406 s_bClearSave =
1407 !(slot_a_has_raw_memcard && File::Exists(Config::Get(Config::MAIN_MEMCARD_A_PATH))) &&
1408 !(slot_b_has_raw_memcard && File::Exists(Config::Get(Config::MAIN_MEMCARD_B_PATH))) &&
1409 !(slot_a_has_gci_folder && gci_folder_has_saves(0)) &&
1410 !(slot_b_has_gci_folder && gci_folder_has_saves(1));
1411 }
1412 s_memcards |= (slot_a_has_raw_memcard || slot_a_has_gci_folder) << 0;
1413 s_memcards |= (slot_b_has_raw_memcard || slot_b_has_gci_folder) << 1;
1414
1415 s_revision = ConvertGitRevisionToBytes(Common::scm_rev_git_str);
1416
1417 if (!Config::Get(Config::MAIN_DSP_HLE))
1418 {
1419 std::string irom_file = File::GetUserPath(D_GCUSER_IDX) + DSP_IROM;
1420 std::string coef_file = File::GetUserPath(D_GCUSER_IDX) + DSP_COEF;
1421
1422 if (!File::Exists(irom_file))
1423 irom_file = File::GetSysDirectory() + GC_SYS_DIR DIR_SEP DSP_IROM;
1424 if (!File::Exists(coef_file))
1425 coef_file = File::GetSysDirectory() + GC_SYS_DIR DIR_SEP DSP_COEF;
1426 std::vector<u16> irom(DSP::DSP_IROM_SIZE);
1427 File::IOFile file_irom(irom_file, "rb");
1428
1429 file_irom.ReadArray(irom.data(), irom.size());
1430 file_irom.Close();
1431 for (u16& entry : irom)
1432 entry = Common::swap16(entry);
1433
1434 std::vector<u16> coef(DSP::DSP_COEF_SIZE);
1435 File::IOFile file_coef(coef_file, "rb");
1436
1437 file_coef.ReadArray(coef.data(), coef.size());
1438 file_coef.Close();
1439 for (u16& entry : coef)
1440 entry = Common::swap16(entry);
1441 s_DSPiromHash =
1442 Common::HashAdler32(reinterpret_cast<u8*>(irom.data()), DSP::DSP_IROM_BYTE_SIZE);
1443 s_DSPcoefHash =
1444 Common::HashAdler32(reinterpret_cast<u8*>(coef.data()), DSP::DSP_COEF_BYTE_SIZE);
1445 }
1446 else
1447 {
1448 s_DSPiromHash = 0;
1449 s_DSPcoefHash = 0;
1450 }
1451 }
1452
1453 static const mbedtls_md_info_t* s_md5_info = mbedtls_md_info_from_type(MBEDTLS_MD_MD5);
1454
1455 // NOTE: Entrypoint for own thread
CheckMD5()1456 static void CheckMD5()
1457 {
1458 if (s_current_file_name.empty())
1459 return;
1460
1461 for (int i = 0, n = 0; i < 16; ++i)
1462 {
1463 if (tmpHeader.md5[i] != 0)
1464 continue;
1465 n++;
1466 if (n == 16)
1467 return;
1468 }
1469 Core::DisplayMessage("Verifying checksum...", 2000);
1470
1471 std::array<u8, 16> game_md5;
1472 mbedtls_md_file(s_md5_info, s_current_file_name.c_str(), game_md5.data());
1473
1474 if (game_md5 == s_MD5)
1475 Core::DisplayMessage("Checksum of current game matches the recorded game.", 2000);
1476 else
1477 Core::DisplayMessage("Checksum of current game does not match the recorded game!", 3000);
1478 }
1479
1480 // NOTE: Entrypoint for own thread
GetMD5()1481 static void GetMD5()
1482 {
1483 if (s_current_file_name.empty())
1484 return;
1485
1486 Core::DisplayMessage("Calculating checksum of game file...", 2000);
1487 mbedtls_md_file(s_md5_info, s_current_file_name.c_str(), s_MD5.data());
1488 Core::DisplayMessage("Finished calculating checksum.", 2000);
1489 }
1490
1491 // NOTE: EmuThread
Shutdown()1492 void Shutdown()
1493 {
1494 s_currentInputCount = s_totalInputCount = s_totalFrames = s_tickCountAtLastInput = 0;
1495 s_temp_input.clear();
1496 }
1497 } // namespace Movie
1498