diff --git a/Source/Core/Core/Src/HW/EXI_DeviceIPL.cpp b/Source/Core/Core/Src/HW/EXI_DeviceIPL.cpp index 29e7f71998..44f79f0043 100644 --- a/Source/Core/Core/Src/HW/EXI_DeviceIPL.cpp +++ b/Source/Core/Core/Src/HW/EXI_DeviceIPL.cpp @@ -24,6 +24,7 @@ #include "../ConfigManager.h" #include "MemoryUtil.h" #include "FileUtil.h" +#include "../OnFrame.h" // english SRAM sram_dump = {{ @@ -415,7 +416,9 @@ u32 CEXIIPL::GetGCTime() // hack in some netplay stuff ltime = NetPlay_GetGCTime(); #endif - if (0 == ltime) + if (Frame::IsRecordingInput() || Frame::IsPlayingInput()) + ltime = 1234567890; // TODO: Should you be able to set a custom time in movies? + else if (0 == ltime) ltime = Common::Timer::GetLocalTimeSinceJan1970(); return ((u32)ltime - cJanuary2000); diff --git a/Source/Core/Core/Src/OnFrame.cpp b/Source/Core/Core/Src/OnFrame.cpp index 17a61446a6..61d8db9a99 100644 --- a/Source/Core/Core/Src/OnFrame.cpp +++ b/Source/Core/Core/Src/OnFrame.cpp @@ -31,20 +31,21 @@ bool g_bFrameStep = false; bool g_bFrameStop = false; bool g_bAutoFire = false; u32 g_autoFirstKey = 0, g_autoSecondKey = 0; +u32 g_rerecords = 0; bool g_bFirstKey = true; PlayMode g_playMode = MODE_NONE; unsigned int g_framesToSkip = 0, g_frameSkipCounter = 0; int g_numPads = 0; -ControllerState *g_padStates; +ControllerState g_padState; FILE *g_recordfd = NULL; u64 g_frameCounter = 0, g_lagCounter = 0; bool g_bPolled = false; int g_numRerecords = 0; -std::string g_recordFile; +std::string g_recordFile = "0.dtm"; void FrameUpdate() { @@ -67,17 +68,6 @@ void FrameUpdate() if (g_bAutoFire) g_bFirstKey = !g_bFirstKey; - // Dump/Read all controllers' states for this frame - if(IsRecordingInput()) - fwrite(g_padStates, sizeof(ControllerState), g_numPads, g_recordfd); - else if(IsPlayingInput()) { - fread(g_padStates, sizeof(ControllerState), g_numPads, g_recordfd); - - // End of recording - if(feof(g_recordfd)) - EndPlayInput(); - } - g_bPolled = false; } @@ -196,11 +186,13 @@ bool IsPlayingInput() } // TODO: Add BeginRecordingFromSavestate -bool BeginRecordingInput(const char *filename, int controllers) +bool BeginRecordingInput(int controllers) { - if(!filename || g_playMode != MODE_NONE || g_recordfd) + if(g_playMode != MODE_NONE || g_recordfd) return false; + const char *filename = g_recordFile.c_str(); + if(File::Exists(filename)) File::Delete(filename); @@ -215,79 +207,44 @@ bool BeginRecordingInput(const char *filename, int controllers) fwrite(&dummy, sizeof(DTMHeader), 1, g_recordfd); g_numPads = controllers; - g_padStates = new ControllerState[controllers]; g_frameCounter = 0; g_lagCounter = 0; g_playMode = MODE_RECORDING; - g_recordFile = filename; + Core::DisplayMessage("Starting movie recording", 2000); return true; } -void EndRecordingInput() -{ - rewind(g_recordfd); - - // Create the real header now and write it - DTMHeader header; - memset(&header, 0, sizeof(DTMHeader)); - - header.filetype[0] = 'D'; header.filetype[1] = 'T'; header.filetype[2] = 'M'; header.filetype[3] = 0x1A; - strncpy((char *)header.gameID, Core::g_CoreStartupParameter.GetUniqueID().c_str(), 6); - header.bWii = Core::g_CoreStartupParameter.bWii; - header.numControllers = g_numPads; - - header.bFromSaveState = false; // TODO: add the case where it's true - header.frameCount = g_frameCounter; - header.lagCount = g_lagCounter; - - // TODO - header.uniqueID = 0; - header.numRerecords = 0; - // header.author; - // header.videoPlugin; - // header.audioPlugin; - - fwrite(&header, sizeof(DTMHeader), 1, g_recordfd); - - fclose(g_recordfd); - g_recordfd = NULL; - - delete[] g_padStates; - - g_playMode = MODE_NONE; -} - void RecordInput(SPADStatus *PadStatus, int controllerID) { if(!IsRecordingInput() || controllerID >= g_numPads || controllerID < 0) return; + + g_padState.A = ((PadStatus->button & PAD_BUTTON_A) != 0); + g_padState.B = ((PadStatus->button & PAD_BUTTON_B) != 0); + g_padState.X = ((PadStatus->button & PAD_BUTTON_X) != 0); + g_padState.Y = ((PadStatus->button & PAD_BUTTON_Y) != 0); + g_padState.Z = ((PadStatus->button & PAD_TRIGGER_Z) != 0); + g_padState.Start = ((PadStatus->button & PAD_BUTTON_START) != 0); - g_padStates[controllerID].A = ((PadStatus->button & PAD_BUTTON_A) != 0); - g_padStates[controllerID].B = ((PadStatus->button & PAD_BUTTON_B) != 0); - g_padStates[controllerID].X = ((PadStatus->button & PAD_BUTTON_X) != 0); - g_padStates[controllerID].Y = ((PadStatus->button & PAD_BUTTON_Y) != 0); - g_padStates[controllerID].Z = ((PadStatus->button & PAD_TRIGGER_Z) != 0); - g_padStates[controllerID].Start = ((PadStatus->button & PAD_BUTTON_START) != 0); + g_padState.DPadUp = ((PadStatus->button & PAD_BUTTON_UP) != 0); + g_padState.DPadDown = ((PadStatus->button & PAD_BUTTON_DOWN) != 0); + g_padState.DPadLeft = ((PadStatus->button & PAD_BUTTON_LEFT) != 0); + g_padState.DPadRight = ((PadStatus->button & PAD_BUTTON_RIGHT) != 0); - g_padStates[controllerID].DPadUp = ((PadStatus->button & PAD_BUTTON_UP) != 0); - g_padStates[controllerID].DPadDown = ((PadStatus->button & PAD_BUTTON_DOWN) != 0); - g_padStates[controllerID].DPadLeft = ((PadStatus->button & PAD_BUTTON_LEFT) != 0); - g_padStates[controllerID].DPadRight = ((PadStatus->button & PAD_BUTTON_RIGHT) != 0); + g_padState.L = PadStatus->triggerLeft; + g_padState.R = PadStatus->triggerRight; - g_padStates[controllerID].L = PadStatus->triggerLeft; - g_padStates[controllerID].R = PadStatus->triggerRight; + g_padState.AnalogStickX = PadStatus->stickX; + g_padState.AnalogStickY = PadStatus->stickY; - g_padStates[controllerID].AnalogStickX = PadStatus->stickX; - g_padStates[controllerID].AnalogStickY = PadStatus->stickY; + g_padState.CStickX = PadStatus->substickX; + g_padState.CStickY = PadStatus->substickY; - g_padStates[controllerID].CStickX = PadStatus->substickX; - g_padStates[controllerID].CStickY = PadStatus->substickY; - - PlayController(PadStatus, controllerID); + fwrite(&g_padState, sizeof(ControllerState), 1, g_recordfd); } bool PlayInput(const char *filename) @@ -330,9 +287,7 @@ bool PlayInput(const char *filename) */ g_numPads = header.numControllers; - g_padStates = new ControllerState[g_numPads]; g_numRerecords = header.numRerecords; - g_recordFile = filename; g_playMode = MODE_PLAYING; @@ -344,61 +299,146 @@ cleanup: return false; } +void LoadInput(const char *filename) +{ + FILE *t_record = fopen(filename, "rb"); + + DTMHeader header; + + fread(&header, sizeof(DTMHeader), 1, t_record); + + if(header.filetype[0] != 'D' || header.filetype[1] != 'T' || header.filetype[2] != 'M' || header.filetype[3] != 0x1A) { + PanicAlert("Savestate movie %s is corrupted, movie recording stopping...", filename); + fclose(t_record); + EndPlayInput(); + return; + } + + if (g_rerecords == 0) + g_rerecords = header.numRerecords; + + g_numPads = header.numControllers; + + fclose(t_record); + + if (g_recordfd) + fclose(g_recordfd); + + File::Delete(g_recordFile.c_str()); + File::Copy(filename, g_recordFile.c_str()); + + g_recordfd = fopen(g_recordFile.c_str(), "r+b"); + fseek(g_recordfd, 0, SEEK_END); + + g_rerecords++; + + Core::DisplayMessage("Resuming movie recording", 2000); + + g_playMode = MODE_RECORDING; +} + void PlayController(SPADStatus *PadStatus, int controllerID) { + // Correct playback is entirely dependent on the emulator polling the controllers + // in the same order done during recording if(!IsPlayingInput() || controllerID >= g_numPads || controllerID < 0) return; memset(PadStatus, 0, sizeof(SPADStatus)); + fread(&g_padState, sizeof(ControllerState), 1, g_recordfd); PadStatus->button |= PAD_USE_ORIGIN; - if(g_padStates[controllerID].A) { + if(g_padState.A) { PadStatus->button |= PAD_BUTTON_A; PadStatus->analogA = 0xFF; } - if(g_padStates[controllerID].B) { + if(g_padState.B) { PadStatus->button |= PAD_BUTTON_B; PadStatus->analogB = 0xFF; } - if(g_padStates[controllerID].X) + if(g_padState.X) PadStatus->button |= PAD_BUTTON_X; - if(g_padStates[controllerID].Y) + if(g_padState.Y) PadStatus->button |= PAD_BUTTON_Y; - if(g_padStates[controllerID].Z) + if(g_padState.Z) PadStatus->button |= PAD_TRIGGER_Z; - if(g_padStates[controllerID].Start) + if(g_padState.Start) PadStatus->button |= PAD_BUTTON_START; - if(g_padStates[controllerID].DPadUp) + if(g_padState.DPadUp) PadStatus->button |= PAD_BUTTON_UP; - if(g_padStates[controllerID].DPadDown) + if(g_padState.DPadDown) PadStatus->button |= PAD_BUTTON_DOWN; - if(g_padStates[controllerID].DPadLeft) + if(g_padState.DPadLeft) PadStatus->button |= PAD_BUTTON_LEFT; - if(g_padStates[controllerID].DPadRight) + if(g_padState.DPadRight) PadStatus->button |= PAD_BUTTON_RIGHT; - PadStatus->triggerLeft = g_padStates[controllerID].L; + PadStatus->triggerLeft = g_padState.L; if(PadStatus->triggerLeft > 230) PadStatus->button |= PAD_TRIGGER_L; - PadStatus->triggerRight = g_padStates[controllerID].R; + PadStatus->triggerRight = g_padState.R; if(PadStatus->triggerRight > 230) PadStatus->button |= PAD_TRIGGER_R; - PadStatus->stickX = g_padStates[controllerID].AnalogStickX; - PadStatus->stickY = g_padStates[controllerID].AnalogStickY; + PadStatus->stickX = g_padState.AnalogStickX; + PadStatus->stickY = g_padState.AnalogStickY; - PadStatus->substickX = g_padStates[controllerID].CStickX; - PadStatus->substickY = g_padStates[controllerID].CStickY; + PadStatus->substickX = g_padState.CStickX; + PadStatus->substickY = g_padState.CStickY; + + if(feof(g_recordfd)) + { + Core::DisplayMessage("Movie End", 2000); + // TODO: read-only mode + //EndPlayInput(); + g_playMode = MODE_RECORDING; + } } void EndPlayInput() { - fclose(g_recordfd); + if (g_recordfd) + fclose(g_recordfd); g_recordfd = NULL; - g_numPads = 0; - delete[] g_padStates; + g_numPads = g_rerecords = 0; + g_frameCounter = g_lagCounter = 0; g_playMode = MODE_NONE; } +void SaveRecording(const char *filename) +{ + rewind(g_recordfd); + + // Create the real header now and write it + DTMHeader header; + memset(&header, 0, sizeof(DTMHeader)); + + header.filetype[0] = 'D'; header.filetype[1] = 'T'; header.filetype[2] = 'M'; header.filetype[3] = 0x1A; + strncpy((char *)header.gameID, Core::g_CoreStartupParameter.GetUniqueID().c_str(), 6); + header.bWii = Core::g_CoreStartupParameter.bWii; + header.numControllers = g_numPads; + + header.bFromSaveState = false; // TODO: add the case where it's true + header.frameCount = g_frameCounter; + header.lagCount = g_lagCounter; + header.numRerecords = g_rerecords; + + // TODO + header.uniqueID = 0; + // header.author; + // header.videoPlugin; + // header.audioPlugin; + + fwrite(&header, sizeof(DTMHeader), 1, g_recordfd); + fclose(g_recordfd); + + if (File::Copy(g_recordFile.c_str(), filename)) + Core::DisplayMessage(StringFromFormat("DTM %s saved", filename).c_str(), 2000); + else + Core::DisplayMessage(StringFromFormat("Failed to save %s", filename).c_str(), 2000); + + g_recordfd = fopen(g_recordFile.c_str(), "r+b"); + fseek(g_recordfd, 0, SEEK_END); +} }; diff --git a/Source/Core/Core/Src/OnFrame.h b/Source/Core/Core/Src/OnFrame.h index d0d1c9bb2d..c46dd6af26 100644 --- a/Source/Core/Core/Src/OnFrame.h +++ b/Source/Core/Core/Src/OnFrame.h @@ -40,11 +40,12 @@ struct ControllerState { bool Start:1, A:1, B:1, X:1, Y:1, Z:1; // Binary buttons, 6 bits bool DPadUp:1, DPadDown:1, // Binary D-Pad buttons, 4 bits DPadLeft:1, DPadRight:1; + bool reserved:6; // Reserved bits used for padding, 6 bits + u8 L, R; // Triggers, 16 bits u8 AnalogStickX, AnalogStickY; // Main Stick, 16 bits u8 CStickX, CStickY; // Sub-Stick, 16 bits - bool reserved:6; // Reserved bits, 6 bits }; // Total: 58 + 6 = 64 bits per frame #pragma pack(pop) @@ -110,13 +111,14 @@ void FrameSkipping(); void ModifyController(SPADStatus *PadStatus, int controllerID); -bool BeginRecordingInput(const char *filename, int controllers); +bool BeginRecordingInput(int controllers); void RecordInput(SPADStatus *PadStatus, int controllerID); -void EndRecordingInput(); bool PlayInput(const char *filename); +void LoadInput(const char *filename); void PlayController(SPADStatus *PadStatus, int controllerID); void EndPlayInput(); +void SaveRecording(const char *filename); }; diff --git a/Source/Core/Core/Src/State.cpp b/Source/Core/Core/Src/State.cpp index 9f7a739c84..938228e783 100644 --- a/Source/Core/Core/Src/State.cpp +++ b/Source/Core/Core/Src/State.cpp @@ -23,6 +23,7 @@ #include "StringUtil.h" #include "Thread.h" #include "CoreTiming.h" +#include "OnFrame.h" #include "HW/HW.h" #include "PowerPC/PowerPC.h" #include "PowerPC/JitCommon/JitBase.h" @@ -255,6 +256,9 @@ void SaveStateCallback(u64 userdata, int cyclesLate) saveStruct *saveData = new saveStruct; saveData->buffer = buffer; saveData->size = sz; + + if (Frame::IsRecordingInput()) + Frame::SaveRecording(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str()); Core::DisplayMessage("Saving State...", 1000); @@ -369,6 +373,11 @@ void LoadStateCallback(u64 userdata, int cyclesLate) Core::DisplayMessage("Unable to Load : Can't load state from other revisions !", 4000); delete[] buffer; + + if (File::Exists(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str())) + Frame::LoadInput(StringFromFormat("%s.dtm", cur_filename.c_str()).c_str()); + else + Frame::EndPlayInput(); state_op_in_progress = false; diff --git a/Source/Core/DolphinWX/Src/Frame.cpp b/Source/Core/DolphinWX/Src/Frame.cpp index 56de51cfba..301a43fde7 100644 --- a/Source/Core/DolphinWX/Src/Frame.cpp +++ b/Source/Core/DolphinWX/Src/Frame.cpp @@ -246,6 +246,7 @@ EVT_MENU(IDM_STOP, CFrame::OnStop) EVT_MENU(IDM_RESET, CFrame::OnReset) EVT_MENU(IDM_RECORD, CFrame::OnRecord) EVT_MENU(IDM_PLAYRECORD, CFrame::OnPlayRecording) +EVT_MENU(IDM_RECORDEXPORT, CFrame::OnRecordExport) EVT_MENU(IDM_FRAMESTEP, CFrame::OnFrameStep) EVT_MENU(IDM_LUA, CFrame::OnOpenLuaWindow) EVT_MENU(IDM_SCREENSHOT, CFrame::OnScreenshot) diff --git a/Source/Core/DolphinWX/Src/Frame.h b/Source/Core/DolphinWX/Src/Frame.h index 8a41936fc9..076420c631 100644 --- a/Source/Core/DolphinWX/Src/Frame.h +++ b/Source/Core/DolphinWX/Src/Frame.h @@ -117,6 +117,7 @@ class CFrame : public CRenderFrame void InitBitmaps(); void DoPause(); void DoStop(); + void DoRecordingSave(); bool bRenderToMain; bool bNoWiimoteMsg; void UpdateGUI(); @@ -272,6 +273,7 @@ class CFrame : public CRenderFrame void OnReset(wxCommandEvent& event); void OnRecord(wxCommandEvent& event); void OnPlayRecording(wxCommandEvent& event); + void OnRecordExport(wxCommandEvent& event); void OnChangeDisc(wxCommandEvent& event); void OnScreenshot(wxCommandEvent& event); void OnActive(wxActivateEvent& event); diff --git a/Source/Core/DolphinWX/Src/FrameTools.cpp b/Source/Core/DolphinWX/Src/FrameTools.cpp index 97fa7ec42f..a80ea00e92 100644 --- a/Source/Core/DolphinWX/Src/FrameTools.cpp +++ b/Source/Core/DolphinWX/Src/FrameTools.cpp @@ -131,8 +131,9 @@ void CFrame::CreateMenu() emulationMenu->AppendSeparator(); emulationMenu->Append(IDM_TOGGLE_FULLSCREEN, GetMenuLabel(HK_FULLSCREEN)); emulationMenu->AppendSeparator(); - emulationMenu->Append(IDM_RECORD, _T("Start Re&cording...")); + emulationMenu->Append(IDM_RECORD, _T("Start Re&cording")); emulationMenu->Append(IDM_PLAYRECORD, _T("P&lay Recording...")); + emulationMenu->Append(IDM_RECORDEXPORT, _T("Export Recording...")); emulationMenu->AppendSeparator(); emulationMenu->Append(IDM_CHANGEDISC, _T("Change &Disc")); @@ -627,23 +628,8 @@ void CFrame::OnChangeDisc(wxCommandEvent& WXUNUSED (event)) void CFrame::OnRecord(wxCommandEvent& WXUNUSED (event)) { - wxString path = wxFileSelector( - _T("Select The Recording File"), - wxEmptyString, wxEmptyString, wxEmptyString, - wxString::Format - ( - _T("Dolphin TAS Movies (*.dtm)|*.dtm|All files (%s)|%s"), - wxFileSelectorDefaultWildcardStr, - wxFileSelectorDefaultWildcardStr - ), - wxFD_SAVE | wxFD_PREVIEW, - this); - - if(path.IsEmpty()) - return; - // TODO: Take controller settings from Gamecube Configuration menu - if(Frame::BeginRecordingInput(path.mb_str(), 1)) + if(Frame::BeginRecordingInput(1)) BootGame(std::string("")); } @@ -668,6 +654,11 @@ void CFrame::OnPlayRecording(wxCommandEvent& WXUNUSED (event)) BootGame(std::string("")); } +void CFrame::OnRecordExport(wxCommandEvent& WXUNUSED (event)) +{ + DoRecordingSave(); +} + void CFrame::OnPlay(wxCommandEvent& WXUNUSED (event)) { if (Core::GetState() != Core::CORE_UNINITIALIZED) @@ -901,8 +892,8 @@ void CFrame::DoStop() // TODO: Show the author/description dialog here if(Frame::IsRecordingInput()) - Frame::EndRecordingInput(); - if(Frame::IsPlayingInput()) + DoRecordingSave(); + if(Frame::IsPlayingInput() || Frame::IsRecordingInput()) Frame::EndPlayInput(); // These windows cause segmentation faults if they are open when the emulator @@ -946,6 +937,34 @@ void CFrame::DoStop() } } +void CFrame::DoRecordingSave() +{ + bool paused = (Core::GetState() == Core::CORE_PAUSE); + + if (!paused) + DoPause(); + + wxString path = wxFileSelector( + _T("Select The Recording File"), + wxEmptyString, wxEmptyString, wxEmptyString, + wxString::Format + ( + _T("Dolphin TAS Movies (*.dtm)|*.dtm|All files (%s)|%s"), + wxFileSelectorDefaultWildcardStr, + wxFileSelectorDefaultWildcardStr + ), + wxFD_SAVE | wxFD_PREVIEW, + this); + + if(path.IsEmpty()) + return; + + Frame::SaveRecording(path.mb_str()); + + if (!paused) + DoPause(); +} + void CFrame::OnStop(wxCommandEvent& WXUNUSED (event)) { m_bGameLoading = false; @@ -1281,6 +1300,7 @@ void CFrame::UpdateGUI() GetMenuBar()->FindItem(IDM_RESET)->Enable(Running || Paused); GetMenuBar()->FindItem(IDM_RECORD)->Enable(!Initialized); GetMenuBar()->FindItem(IDM_PLAYRECORD)->Enable(!Initialized); + GetMenuBar()->FindItem(IDM_RECORDEXPORT)->Enable(Frame::IsRecordingInput()); GetMenuBar()->FindItem(IDM_FRAMESTEP)->Enable(Running || Paused); GetMenuBar()->FindItem(IDM_SCREENSHOT)->Enable(Running || Paused); GetMenuBar()->FindItem(IDM_TOGGLE_FULLSCREEN)->Enable(Running || Paused); diff --git a/Source/Core/DolphinWX/Src/Globals.h b/Source/Core/DolphinWX/Src/Globals.h index 5db01cf710..a4a7a90367 100644 --- a/Source/Core/DolphinWX/Src/Globals.h +++ b/Source/Core/DolphinWX/Src/Globals.h @@ -77,6 +77,7 @@ enum IDM_TOGGLE_FULLSCREEN, IDM_RECORD, IDM_PLAYRECORD, + IDM_RECORDEXPORT, IDM_FRAMESTEP, IDM_SCREENSHOT, IDM_BROWSE,