mirror of https://github.com/PCSX2/pcsx2.git
recording: Use conventional savestate functions, save save-state in a separate file alongside recording file
Regressions were discovered after merging with master due to way the save state data was saved within the movie file. This change uses the same functions used in the GUI to create savestates to create a compressed save-state file. Eventually this could be re-incorporated back into the recording file and could be backwards compatible.
This commit is contained in:
parent
270f7fd905
commit
eb7030cf12
|
@ -1,6 +1,7 @@
|
|||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "AppSaveStates.h"
|
||||
#include "AppGameDatabase.h"
|
||||
#include "Common.h"
|
||||
#include "Counters.h"
|
||||
#include "MemoryTypes.h"
|
||||
|
@ -117,54 +118,73 @@ void InputRecording::Stop() {
|
|||
//----------------------------------
|
||||
// start
|
||||
//----------------------------------
|
||||
void InputRecording::Start(wxString FileName,bool fReadOnly, VmStateBuffer* ss)
|
||||
void InputRecording::Create(wxString FileName, bool fromSaveState, wxString authorName)
|
||||
{
|
||||
g_RecordingControls.Pause();
|
||||
Stop();
|
||||
|
||||
if (fReadOnly)
|
||||
// create
|
||||
if (!InputRecordingData.Open(FileName, true, fromSaveState)) {
|
||||
return;
|
||||
}
|
||||
// Set author name
|
||||
if (!authorName.IsEmpty())
|
||||
{
|
||||
if (!InputRecordingData.Open(FileName, false)) {
|
||||
return;
|
||||
}
|
||||
if (!InputRecordingData.readHeaderAndCheck()) {
|
||||
recordingConLog(L"[REC]: This file is not a correct InputRecording file.\n");
|
||||
InputRecordingData.Close();
|
||||
return;
|
||||
}
|
||||
// cdrom
|
||||
if (!g_Conf->CurrentIso.IsEmpty())
|
||||
InputRecordingData.getHeader().setAuthor(authorName);
|
||||
}
|
||||
// Set Game Name
|
||||
// Code loosely taken from AppCoreThread.cpp to resolve the Game Name
|
||||
// Fallback is ISO name
|
||||
wxString gameName;
|
||||
const wxString gameKey(SysGetDiscID());
|
||||
if (!gameKey.IsEmpty())
|
||||
{
|
||||
if (IGameDatabase* GameDB = AppHost_GetGameDatabase())
|
||||
{
|
||||
if (Path::GetFilename(g_Conf->CurrentIso) != InputRecordingData.getHeader().cdrom) {
|
||||
recordingConLog(L"[REC]: Information on CD in Movie file is Different.\n");
|
||||
Game_Data game;
|
||||
if (GameDB->findGame(game, gameKey))
|
||||
{
|
||||
gameName = game.getString("Name");
|
||||
gameName += L" (" + game.getString("Region") + L")";
|
||||
}
|
||||
}
|
||||
state = REPLAY;
|
||||
recordingConLog(wxString::Format(L"[REC]: Replaying movie - [%s]\n",FileName));
|
||||
recordingConLog(wxString::Format(L"MaxFrame: %d\n", InputRecordingData.getMaxFrame()));
|
||||
recordingConLog(wxString::Format(L"UndoCount: %d\n", InputRecordingData.getUndoCount()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// create
|
||||
if (!InputRecordingData.Open(FileName, true, ss)) {
|
||||
return;
|
||||
}
|
||||
// cdrom
|
||||
if (!g_Conf->CurrentIso.IsEmpty())
|
||||
{
|
||||
InputRecordingData.getHeader().setCdrom(Path::GetFilename(g_Conf->CurrentIso));
|
||||
}
|
||||
InputRecordingData.writeHeader();
|
||||
InputRecordingData.writeSavestate();
|
||||
InputRecordingData.getHeader().setGameName(!gameName.IsEmpty() ? gameName : Path::GetFilename(g_Conf->CurrentIso));
|
||||
InputRecordingData.writeHeader();
|
||||
state = RECORD;
|
||||
recordingConLog(wxString::Format(L"[REC]: Started new recording - [%s]\n", FileName));
|
||||
|
||||
state = RECORD;
|
||||
recordingConLog(wxString::Format(L"[REC]: Started new recording - [%s]\n", FileName));
|
||||
}
|
||||
// In every case, we reset the g_FrameCount
|
||||
g_FrameCount = 0;
|
||||
}
|
||||
|
||||
void InputRecording::Play(wxString FileName, bool fromSaveState)
|
||||
{
|
||||
g_RecordingControls.Pause();
|
||||
Stop();
|
||||
|
||||
if (!InputRecordingData.Open(FileName, false, false)) {
|
||||
return;
|
||||
}
|
||||
if (!InputRecordingData.readHeaderAndCheck()) {
|
||||
recordingConLog(L"[REC]: This file is not a correct InputRecording file.\n");
|
||||
InputRecordingData.Close();
|
||||
return;
|
||||
}
|
||||
// Check author name
|
||||
if (!g_Conf->CurrentIso.IsEmpty())
|
||||
{
|
||||
if (Path::GetFilename(g_Conf->CurrentIso) != InputRecordingData.getHeader().gameName) {
|
||||
recordingConLog(L"[REC]: Information on CD in Movie file is Different.\n");
|
||||
}
|
||||
}
|
||||
// TODO - probably output more informatiion on it
|
||||
state = REPLAY;
|
||||
recordingConLog(wxString::Format(L"[REC]: Replaying movie - [%s]\n", FileName));
|
||||
recordingConLog(wxString::Format(L"MaxFrame: %d\n", InputRecordingData.getMaxFrame()));
|
||||
recordingConLog(wxString::Format(L"UndoCount: %d\n", InputRecordingData.getUndoCount()));
|
||||
}
|
||||
|
||||
//----------------------------------
|
||||
// shortcut key
|
||||
//----------------------------------
|
||||
|
|
|
@ -16,7 +16,8 @@ public:
|
|||
|
||||
// menu bar
|
||||
void Stop();
|
||||
void Start(wxString filename, bool fReadOnly, VmStateBuffer* ss = nullptr);
|
||||
void Create(wxString filename, bool fromSaveState, wxString authorName);
|
||||
void Play(wxString filename, bool fromSaveState);
|
||||
|
||||
// shortcut key
|
||||
void RecordModeToggle();
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "MemoryTypes.h"
|
||||
#include "App.h"
|
||||
#include "Common.h"
|
||||
#include "Counters.h"
|
||||
#include "MainFrame.h"
|
||||
#include "MemoryTypes.h"
|
||||
|
||||
#include "InputRecordingFile.h"
|
||||
|
||||
#define HEADER_SIZE (sizeof(InputRecordingHeader)+4+4)
|
||||
#define SAVESTATE_HEADER_SIZE (sizeof(bool) + sizeof(savestate.savestatesize) + sizeof(savestate.savestate[0]) * savestate.savestatesize)
|
||||
#define SAVESTATE_HEADER_SIZE (sizeof(bool))
|
||||
#define BLOCK_HEADER_SIZE (0)
|
||||
#define BLOCK_DATA_SIZE (18*2)
|
||||
#define BLOCK_SIZE (BLOCK_HEADER_SIZE+BLOCK_DATA_SIZE)
|
||||
|
@ -29,10 +30,8 @@ long InputRecordingFile::_getBlockSeekPoint(const long & frame)
|
|||
}
|
||||
}
|
||||
|
||||
//----------------------------------
|
||||
// file
|
||||
//----------------------------------
|
||||
bool InputRecordingFile::Open(const wxString path, bool fNewOpen, VmStateBuffer *ss)
|
||||
// Inits the new (or existing) input recording file
|
||||
bool InputRecordingFile::Open(const wxString path, bool fNewOpen, bool fromSaveState)
|
||||
{
|
||||
Close();
|
||||
wxString mode = L"rb+";
|
||||
|
@ -50,14 +49,13 @@ bool InputRecordingFile::Open(const wxString path, bool fNewOpen, VmStateBuffer
|
|||
}
|
||||
filename = path;
|
||||
|
||||
// TODO - from power on its fine
|
||||
// problems seem to be be based in how we are saving the savestate
|
||||
if (fNewOpen) {
|
||||
if (ss) {
|
||||
if (fromSaveState) {
|
||||
savestate.fromSavestate = true;
|
||||
savestate.savestatesize = ss->GetLength();
|
||||
savestate.savestate.MakeRoomFor(ss->GetLength());
|
||||
for (size_t i = 0; i < ss->GetLength(); i++) {
|
||||
savestate.savestate[i] = (*ss)[i];
|
||||
}
|
||||
// TODO - Check if existing, if so rename
|
||||
StateCopy_SaveToFile(path + "_SaveState.p2s");
|
||||
}
|
||||
else {
|
||||
sApp.SysExecute();
|
||||
|
@ -65,17 +63,28 @@ bool InputRecordingFile::Open(const wxString path, bool fNewOpen, VmStateBuffer
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputRecordingFile::Close()
|
||||
{
|
||||
if (recordingFile == NULL)return false;
|
||||
writeHeader();
|
||||
writeSavestate();
|
||||
writeSaveState();
|
||||
fclose(recordingFile);
|
||||
recordingFile = NULL;
|
||||
filename = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputRecordingFile::writeSaveState() {
|
||||
if (recordingFile == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
fseek(recordingFile, SEEKPOINT_SAVESTATE, SEEK_SET);
|
||||
if (fwrite(&savestate.fromSavestate, sizeof(bool), 1, recordingFile) != 1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------
|
||||
// write frame
|
||||
//----------------------------------
|
||||
|
@ -111,9 +120,6 @@ bool InputRecordingFile::readKeyBuf(u8 & result,const uint & frame, const uint p
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//===================================
|
||||
// pad
|
||||
//===================================
|
||||
|
@ -126,6 +132,7 @@ void InputRecordingFile::getPadData(PadData & result, unsigned long frame)
|
|||
if (fread(result.buf, 1, BLOCK_DATA_SIZE, recordingFile) == 0)return;
|
||||
result.fExistKey = true;
|
||||
}
|
||||
|
||||
bool InputRecordingFile::DeletePadData(unsigned long frame)
|
||||
{
|
||||
if (recordingFile == NULL)return false;
|
||||
|
@ -147,6 +154,7 @@ bool InputRecordingFile::DeletePadData(unsigned long frame)
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputRecordingFile::InsertPadData(unsigned long frame, const PadData& key)
|
||||
{
|
||||
if (recordingFile == NULL)return false;
|
||||
|
@ -174,88 +182,53 @@ bool InputRecordingFile::InsertPadData(unsigned long frame, const PadData& key)
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputRecordingFile::UpdatePadData(unsigned long frame, const PadData& key)
|
||||
{
|
||||
if (recordingFile == NULL)return false;
|
||||
if (!key.fExistKey)return false;
|
||||
if (recordingFile == NULL) return false;
|
||||
if (!key.fExistKey) return false;
|
||||
|
||||
long seek = _getBlockSeekPoint(frame) + BLOCK_HEADER_SIZE;
|
||||
fseek(recordingFile, seek, SEEK_SET);
|
||||
if (fwrite(key.buf, 1, BLOCK_DATA_SIZE, recordingFile) == 0)return false;
|
||||
if (fwrite(key.buf, 1, BLOCK_DATA_SIZE, recordingFile) == 0) return false;
|
||||
|
||||
fflush(recordingFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//===================================
|
||||
// header
|
||||
//===================================
|
||||
// TODO - see if we can get the actual game name, not just the ISO name
|
||||
// Verify header of recording file
|
||||
bool InputRecordingFile::readHeaderAndCheck()
|
||||
{
|
||||
if (recordingFile == NULL)return false;
|
||||
if (recordingFile == NULL) return false;
|
||||
rewind(recordingFile);
|
||||
if (fread(&header, sizeof(InputRecordingHeader), 1, recordingFile) != 1)return false;
|
||||
if (fread(&MaxFrame, 4, 1, recordingFile) != 1)return false;
|
||||
if (fread(&UndoCount, 4, 1, recordingFile) != 1)return false;
|
||||
if (fread(&header, sizeof(InputRecordingHeader), 1, recordingFile) != 1) return false;
|
||||
if (fread(&MaxFrame, 4, 1, recordingFile) != 1) return false;
|
||||
if (fread(&UndoCount, 4, 1, recordingFile) != 1) return false;
|
||||
if (fread(&savestate.fromSavestate, sizeof(bool), 1, recordingFile) != 1) return false;
|
||||
if (savestate.fromSavestate) {
|
||||
// We read the size (and the savestate) only if we must
|
||||
if (fread(&savestate.savestatesize, sizeof(savestate.savestatesize), 1, recordingFile) != 1) return false;
|
||||
if (savestate.savestatesize == 0) {
|
||||
recordingConLog(L"[REC]: Invalid size of the savestate.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
savestate.savestate.MakeRoomFor(savestate.savestatesize);
|
||||
// We read "savestatesize" * the size of a cell
|
||||
if (fread(savestate.savestate.GetPtr(), sizeof(savestate.savestate[0]), savestate.savestatesize, recordingFile)
|
||||
!= savestate.savestatesize) return false;
|
||||
|
||||
// We load the savestate
|
||||
memLoadingState load(savestate.savestate);
|
||||
UI_DisableSysActions();
|
||||
GetCoreThread().Pause();
|
||||
SysClearExecutionCache();
|
||||
load.FreezeAll();
|
||||
GetCoreThread().Resume();
|
||||
// TODO - check to see if the file is there, if it AINT, return false, throw an error, ETC (SAY WHAT FILE WE ARE LOOKING FOR)
|
||||
StateCopy_LoadFromFile(filename + "_SaveState.p2s");
|
||||
}
|
||||
else {
|
||||
sApp.SysExecute();
|
||||
}
|
||||
|
||||
// ID
|
||||
if (header.ID != 0xCC) {
|
||||
return false;
|
||||
}
|
||||
// ver
|
||||
if (header.version != 3) {
|
||||
// Check for current verison
|
||||
// TODO - more specific log if fails for this reason
|
||||
if (header.version != 1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool InputRecordingFile::writeHeader()
|
||||
{
|
||||
if (recordingFile == NULL)return false;
|
||||
if (recordingFile == NULL) return false;
|
||||
rewind(recordingFile);
|
||||
if (fwrite(&header, sizeof(InputRecordingHeader), 1, recordingFile) != 1) return false;
|
||||
return true;
|
||||
}
|
||||
bool InputRecordingFile::writeSavestate()
|
||||
{
|
||||
if (recordingFile == NULL) return false;
|
||||
fseek(recordingFile, SEEKPOINT_SAVESTATE, SEEK_SET);
|
||||
if (fwrite(&savestate.fromSavestate, sizeof(bool), 1, recordingFile) != 1) return false;
|
||||
|
||||
if (savestate.fromSavestate) {
|
||||
if (fwrite(&savestate.savestatesize, sizeof(savestate.savestatesize), 1, recordingFile) != 1) return false;
|
||||
if (fwrite(savestate.savestate.GetPtr(), sizeof(savestate.savestate[0]), savestate.savestatesize, recordingFile)
|
||||
!= savestate.savestatesize) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool InputRecordingFile::writeMaxFrame()
|
||||
{
|
||||
if (recordingFile == NULL)return false;
|
||||
|
@ -263,6 +236,7 @@ bool InputRecordingFile::writeMaxFrame()
|
|||
if (fwrite(&MaxFrame, 4, 1, recordingFile) != 1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void InputRecordingFile::updateFrameMax(unsigned long frame)
|
||||
{
|
||||
if (MaxFrame >= frame) {
|
||||
|
@ -273,13 +247,13 @@ void InputRecordingFile::updateFrameMax(unsigned long frame)
|
|||
fseek(recordingFile, SEEKPOINT_FRAMEMAX, SEEK_SET);
|
||||
fwrite(&MaxFrame, 4, 1, recordingFile);
|
||||
}
|
||||
|
||||
void InputRecordingFile::addUndoCount()
|
||||
{
|
||||
UndoCount++;
|
||||
if (recordingFile == NULL)return;
|
||||
fseek(recordingFile, SEEKPOINT_UNDOCOUNT, SEEK_SET);
|
||||
fwrite(&UndoCount, 4, 1, recordingFile);
|
||||
|
||||
}
|
||||
|
||||
void InputRecordingHeader::setAuthor(wxString _author)
|
||||
|
@ -288,14 +262,16 @@ void InputRecordingHeader::setAuthor(wxString _author)
|
|||
strncpy(author, _author.c_str(), max);
|
||||
author[max] = 0;
|
||||
}
|
||||
void InputRecordingHeader::setCdrom(wxString _cdrom)
|
||||
|
||||
void InputRecordingHeader::setGameName(wxString _gameName)
|
||||
{
|
||||
int max = ArraySize(cdrom) - 1;
|
||||
strncpy(cdrom, _cdrom.c_str(), max);
|
||||
cdrom[max] = 0;
|
||||
int max = ArraySize(gameName) - 1;
|
||||
strncpy(gameName, _gameName.c_str(), max);
|
||||
gameName[max] = 0;
|
||||
}
|
||||
|
||||
void InputRecordingHeader::init()
|
||||
{
|
||||
memset(author, 0, ArraySize(author));
|
||||
memset(cdrom, 0, ArraySize(cdrom));
|
||||
memset(gameName, 0, ArraySize(gameName));
|
||||
}
|
||||
|
|
|
@ -5,87 +5,65 @@
|
|||
|
||||
struct InputRecordingHeader
|
||||
{
|
||||
u8 version = 3;
|
||||
u8 ID = 0xCC;
|
||||
|
||||
char emu[50] = "pcsx2-1.5.X";
|
||||
char author[50] = "";
|
||||
char cdrom[50] = "";
|
||||
u8 version = 1;
|
||||
char emu[50] = "PCSX2-1.5.X";
|
||||
char author[255] = "";
|
||||
char gameName[255] = "";
|
||||
|
||||
public:
|
||||
void setAuthor(wxString author);
|
||||
void setCdrom(wxString cdrom);
|
||||
void setGameName(wxString cdrom);
|
||||
void init();
|
||||
};
|
||||
|
||||
//----------------------------
|
||||
// InputRecordingSavestate
|
||||
// Contains info about the starting point of the movie
|
||||
//----------------------------
|
||||
struct InputRecordingSavestate
|
||||
{
|
||||
bool fromSavestate = false; // Whether we start from the savestate or from power-on
|
||||
unsigned int savestatesize; // The size of the savestate
|
||||
VmStateBuffer savestate; // The savestate
|
||||
// Whether we start from the savestate or from power-on
|
||||
bool fromSavestate = false;
|
||||
};
|
||||
|
||||
//----------------------------
|
||||
// InputRecordingFile
|
||||
//----------------------------
|
||||
|
||||
class InputRecordingFile {
|
||||
public:
|
||||
InputRecordingFile() {}
|
||||
~InputRecordingFile() { Close(); }
|
||||
public:
|
||||
|
||||
// file
|
||||
bool Open(const wxString fn, bool fNewOpen, VmStateBuffer *ss = nullptr);
|
||||
// Movie File Manipulation
|
||||
bool Open(const wxString fn, bool fNewOpen, bool fromSaveState);
|
||||
bool Close();
|
||||
|
||||
// movie
|
||||
bool writeKeyBuf(const uint & frame, const uint port, const uint bufIndex, const u8 & buf);
|
||||
bool readKeyBuf(u8 & result, const uint & frame, const uint port, const uint bufIndex);
|
||||
|
||||
// pad data
|
||||
// Controller Data
|
||||
void getPadData(PadData & result_pad, unsigned long frame);
|
||||
bool DeletePadData(unsigned long frame);
|
||||
bool InsertPadData(unsigned long frame, const PadData& key);
|
||||
bool UpdatePadData(unsigned long frame, const PadData& key);
|
||||
|
||||
private:
|
||||
FILE * recordingFile = NULL;
|
||||
wxString filename = "";
|
||||
|
||||
private:
|
||||
|
||||
//--------------------
|
||||
// block
|
||||
//--------------------
|
||||
long _getBlockSeekPoint(const long & frame);
|
||||
|
||||
|
||||
public:
|
||||
//--------------------
|
||||
// header
|
||||
//--------------------
|
||||
// Header
|
||||
InputRecordingHeader& getHeader() { return header; }
|
||||
unsigned long& getMaxFrame() { return MaxFrame; }
|
||||
unsigned long& getUndoCount() { return UndoCount; }
|
||||
const wxString & getFilename() { return filename; }
|
||||
|
||||
bool writeHeader();
|
||||
bool writeSavestate();
|
||||
bool writeMaxFrame();
|
||||
bool writeSaveState();
|
||||
|
||||
bool readHeaderAndCheck();
|
||||
void updateFrameMax(unsigned long frame);
|
||||
void addUndoCount();
|
||||
|
||||
private:
|
||||
// Movie File
|
||||
FILE * recordingFile = NULL;
|
||||
wxString filename = "";
|
||||
long _getBlockSeekPoint(const long & frame);
|
||||
|
||||
// Header
|
||||
InputRecordingHeader header;
|
||||
InputRecordingSavestate savestate;
|
||||
unsigned long MaxFrame = 0;
|
||||
unsigned long UndoCount = 0;
|
||||
|
||||
|
||||
};
|
||||
|
|
|
@ -788,7 +788,6 @@ void MainEmuFrame::Menu_Recording_New_Click(wxCommandEvent &event)
|
|||
{
|
||||
return;
|
||||
}
|
||||
g_InputRecordingHeader.setAuthor(NewRecordingFrame->getAuthor());
|
||||
// From Current Frame
|
||||
if (NewRecordingFrame->getFrom() == 0)
|
||||
{
|
||||
|
@ -796,16 +795,13 @@ void MainEmuFrame::Menu_Recording_New_Click(wxCommandEvent &event)
|
|||
recordingConLog(L"[REC]: Game is not open, aborting new input recording.\n");
|
||||
return;
|
||||
}
|
||||
VmStateBuffer savestate;
|
||||
memSavingState memSS(savestate);
|
||||
memSS.FreezeAll();
|
||||
g_InputRecording.Start(NewRecordingFrame->getFile(), false, &savestate);
|
||||
g_InputRecording.Create(NewRecordingFrame->getFile(), true, NewRecordingFrame->getAuthor());
|
||||
}
|
||||
// From Power-On
|
||||
else if (NewRecordingFrame->getFrom() == 1)
|
||||
{
|
||||
// TODO extensively test this
|
||||
g_InputRecording.Start(NewRecordingFrame->getFile(), false);
|
||||
g_InputRecording.Create(NewRecordingFrame->getFile(), false, NewRecordingFrame->getAuthor());
|
||||
}
|
||||
}
|
||||
m_menuRecording.FindChildItem(MenuId_Recording_New)->Enable(false);
|
||||
|
@ -815,12 +811,12 @@ void MainEmuFrame::Menu_Recording_New_Click(wxCommandEvent &event)
|
|||
void MainEmuFrame::Menu_Recording_Play_Click(wxCommandEvent &event)
|
||||
{
|
||||
g_InputRecording.Stop();
|
||||
|
||||
wxFileDialog openFileDialog(this, _("Select P2M2 record file."), L"", L"",
|
||||
L"p2m2 file(*.p2m2)|*.p2m2", wxFD_OPEN);
|
||||
if (openFileDialog.ShowModal() == wxID_CANCEL)return; // cancel
|
||||
if (openFileDialog.ShowModal() == wxID_CANCEL) return;
|
||||
|
||||
wxString path = openFileDialog.GetPath();
|
||||
g_InputRecording.Start(path, true);
|
||||
g_InputRecording.Play(path, true);
|
||||
m_menuRecording.FindChildItem(MenuId_Recording_New)->Enable(false);
|
||||
m_menuRecording.FindChildItem(MenuId_Recording_Stop)->Enable(true);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue