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(&current_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