2010-06-09 01:37:08 +00:00
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
2011-06-24 06:50:50 +00:00
# include "Movie.h"
2010-06-09 01:37:08 +00:00
# include "Core.h"
2011-07-11 20:15:05 +00:00
# include "ConfigManager.h"
2010-06-09 01:37:08 +00:00
# include "Thread.h"
# include "FileUtil.h"
# include "PowerPC/PowerPC.h"
2010-09-06 21:41:01 +00:00
# include "HW/SI.h"
2011-02-11 19:09:46 +00:00
# include "HW/Wiimote.h"
2011-12-14 04:03:05 -08:00
# include "HW/WiimoteEmu/WiimoteEmu.h"
# include "HW/WiimoteEmu/WiimoteHid.h"
2011-02-11 19:09:46 +00:00
# include "IPC_HLE/WII_IPC_HLE_Device_usb.h"
2011-01-31 01:28:32 +00:00
# include "VideoBackendBase.h"
2011-02-15 09:07:55 +00:00
# include "State.h"
2011-12-26 05:09:30 -06:00
# include "Timer.h"
2011-02-12 02:14:20 +00:00
2011-05-03 00:06:44 +00:00
// large enough for just over 24 hours of single-player recording
# define MAX_DTM_LENGTH (40 * 1024 * 1024)
2011-03-05 06:11:26 +00:00
std : : mutex cs_frameSkip ;
2010-06-09 01:37:08 +00:00
2011-06-24 06:50:50 +00:00
namespace Movie {
2010-06-09 01:37:08 +00:00
bool g_bFrameStep = false ;
bool g_bFrameStop = false ;
2011-02-12 02:14:20 +00:00
bool g_bReadOnly = true ;
2010-08-30 07:05:47 +00:00
u32 g_rerecords = 0 ;
2010-06-09 01:37:08 +00:00
PlayMode g_playMode = MODE_NONE ;
2011-05-03 00:06:44 +00:00
u32 g_framesToSkip = 0 , g_frameSkipCounter = 0 ;
2010-06-09 01:37:08 +00:00
2011-05-03 00:06:44 +00:00
u8 g_numPads = 0 ;
2010-08-30 07:05:47 +00:00
ControllerState g_padState ;
2011-05-03 00:06:44 +00:00
DTMHeader tmpHeader ;
2011-12-14 04:03:05 -08:00
u8 * tmpInput = NULL ;
u64 g_currentByte = 0 , g_totalBytes = 0 ;
u64 g_currentFrame = 0 , g_totalFrames = 0 ; // VI
u64 g_currentLagCount = 0 , g_totalLagCount = 0 ; // just stats
u64 g_currentInputCount = 0 , g_totalInputCount = 0 ; // just stats
2011-12-26 05:09:30 -06:00
u64 g_recordingStartTime ; // seconds since 1970 that recording started
2010-06-09 01:37:08 +00:00
2011-02-15 09:07:55 +00:00
bool g_bRecordingFromSaveState = false ;
2010-06-09 01:37:08 +00:00
bool g_bPolled = false ;
2011-12-14 04:03:05 -08:00
int g_currentSaveVersion = 0 ;
2010-06-09 01:37:08 +00:00
2011-05-03 00:06:44 +00:00
std : : string tmpStateFilename = " dtm.sav " ;
2010-06-09 01:37:08 +00:00
2011-12-14 04:03:05 -08:00
std : : string g_InputDisplay [ 8 ] ;
2011-02-17 09:12:36 +00:00
2011-06-24 06:50:50 +00:00
ManipFunction mfunc = NULL ;
2011-12-26 05:09:30 -06:00
2011-02-17 09:12:36 +00:00
std : : string GetInputDisplay ( )
{
std : : string inputDisplay = " " ;
2011-12-14 04:03:05 -08:00
for ( int i = 0 ; i < 8 ; + + i )
if ( ( g_numPads & ( 1 < < i ) ) ! = 0 )
inputDisplay . append ( g_InputDisplay [ i ] ) ;
2011-02-17 09:12:36 +00:00
return inputDisplay ;
}
2010-06-09 01:37:08 +00:00
void FrameUpdate ( )
{
2011-12-14 04:03:05 -08:00
g_currentFrame + + ;
if ( ! g_bPolled )
g_currentLagCount + + ;
2011-02-15 17:03:20 +00:00
if ( IsRecordingInput ( ) )
2011-12-14 04:03:05 -08:00
{
g_totalFrames = g_currentFrame ;
g_totalLagCount = g_currentLagCount ;
}
2010-06-09 01:37:08 +00:00
if ( g_bFrameStep )
2011-12-11 21:08:26 -08:00
{
2010-06-09 01:37:08 +00:00
Core : : SetState ( Core : : CORE_PAUSE ) ;
2011-12-11 21:08:26 -08:00
g_bFrameStep = false ;
}
2010-06-09 01:37:08 +00:00
// ("framestop") the only purpose of this is to cause interpreter/jit Run() to return temporarily.
// after that we set it back to CPU_RUNNING and continue as normal.
if ( g_bFrameStop )
* PowerPC : : GetStatePtr ( ) = PowerPC : : CPU_STEPPING ;
if ( g_framesToSkip )
FrameSkipping ( ) ;
g_bPolled = false ;
}
2011-03-17 10:41:56 +00:00
void InputUpdate ( )
{
2011-12-14 04:03:05 -08:00
g_currentInputCount + + ;
if ( IsRecordingInput ( ) )
g_totalInputCount = g_currentInputCount ;
2011-03-17 10:41:56 +00:00
}
2010-06-09 01:37:08 +00:00
void SetFrameSkipping ( unsigned int framesToSkip )
{
2011-03-05 06:11:26 +00:00
std : : lock_guard < std : : mutex > lk ( cs_frameSkip ) ;
2010-06-09 01:37:08 +00:00
g_framesToSkip = framesToSkip ;
g_frameSkipCounter = 0 ;
// Don't forget to re-enable rendering in case it wasn't...
// as this won't be changed anymore when frameskip is turned off
if ( framesToSkip = = 0 )
2011-01-31 01:28:32 +00:00
g_video_backend - > Video_SetRendering ( true ) ;
2010-06-09 01:37:08 +00:00
}
void SetPolledDevice ( )
{
g_bPolled = true ;
}
2011-12-11 21:08:26 -08:00
void DoFrameStep ( )
2010-06-09 01:37:08 +00:00
{
2011-12-11 21:08:26 -08:00
if ( Core : : GetState ( ) = = Core : : CORE_PAUSE )
{
// if already paused, frame advance for 1 frame
Core : : SetState ( Core : : CORE_RUN ) ;
2011-12-14 04:03:05 -08:00
Core : : RequestRefreshInfo ( ) ;
2011-12-11 21:08:26 -08:00
g_bFrameStep = true ;
}
2011-12-14 04:03:05 -08:00
else if ( ! g_bFrameStep )
2011-12-11 21:08:26 -08:00
{
// if not paused yet, pause immediately instead
Core : : SetState ( Core : : CORE_PAUSE ) ;
}
2010-06-09 01:37:08 +00:00
}
2011-01-17 01:47:27 +00:00
void SetFrameStopping ( bool bEnabled )
2010-06-09 01:37:08 +00:00
{
2011-01-17 01:47:27 +00:00
g_bFrameStop = bEnabled ;
2010-06-09 01:37:08 +00:00
}
2011-02-12 02:14:20 +00:00
void SetReadOnly ( bool bEnabled )
{
2011-12-11 21:08:26 -08:00
if ( g_bReadOnly ! = bEnabled )
Core : : DisplayMessage ( bEnabled ? " Read-only mode. " : " Read+Write mode. " , 1000 ) ;
2011-02-12 02:14:20 +00:00
g_bReadOnly = bEnabled ;
}
2010-06-09 01:37:08 +00:00
void FrameSkipping ( )
{
2011-02-11 12:26:15 +00:00
// Frameskipping will desync movie playback
if ( ! IsPlayingInput ( ) & & ! IsRecordingInput ( ) )
{
2011-03-05 06:11:26 +00:00
std : : lock_guard < std : : mutex > lk ( cs_frameSkip ) ;
2011-02-11 12:26:15 +00:00
g_frameSkipCounter + + ;
2011-03-15 23:09:12 +00:00
if ( g_frameSkipCounter > g_framesToSkip | | Core : : ShouldSkipFrame ( g_frameSkipCounter ) = = false )
2011-02-11 12:26:15 +00:00
g_frameSkipCounter = 0 ;
g_video_backend - > Video_SetRendering ( ! g_frameSkipCounter ) ;
}
2010-06-09 01:37:08 +00:00
}
bool IsRecordingInput ( )
{
return ( g_playMode = = MODE_RECORDING ) ;
}
2011-02-15 09:07:55 +00:00
bool IsRecordingInputFromSaveState ( )
{
return g_bRecordingFromSaveState ;
}
2011-12-14 04:03:05 -08:00
bool IsJustStartingRecordingInputFromSaveState ( )
{
return IsRecordingInputFromSaveState ( ) & & g_currentFrame = = 0 ;
}
2010-06-09 01:37:08 +00:00
bool IsPlayingInput ( )
{
return ( g_playMode = = MODE_PLAYING ) ;
}
2011-07-11 20:15:05 +00:00
bool IsReadOnly ( )
{
return g_bReadOnly ;
}
2011-12-26 05:09:30 -06:00
u64 GetRecordingStartTime ( )
{
return g_recordingStartTime ;
}
2010-09-06 21:41:01 +00:00
bool IsUsingPad ( int controller )
{
2011-02-12 00:06:58 +00:00
return ( ( g_numPads & ( 1 < < controller ) ) ! = 0 ) ;
2011-02-11 19:09:46 +00:00
}
bool IsUsingWiimote ( int wiimote )
{
2011-02-12 00:06:58 +00:00
return ( ( g_numPads & ( 1 < < ( wiimote + 4 ) ) ) ! = 0 ) ;
2010-09-06 21:41:01 +00:00
}
2011-02-12 00:06:58 +00:00
void ChangePads ( bool instantly )
2010-09-06 21:41:01 +00:00
{
2011-07-11 20:15:05 +00:00
if ( Core : : GetState ( ) = = Core : : CORE_UNINITIALIZED )
return ;
int controllers = 0 ;
for ( int i = 0 ; i < 4 ; i + + )
2011-10-23 07:01:17 -07:00
if ( SConfig : : GetInstance ( ) . m_SIDevice [ i ] = = SIDEVICE_GC_CONTROLLER )
2011-07-11 20:15:05 +00:00
controllers | = ( 1 < < i ) ;
if ( instantly & & ( g_numPads & 0x0F ) = = controllers )
return ;
for ( int i = 0 ; i < 4 ; i + + )
if ( instantly ) // Changes from savestates need to be instantaneous
2011-10-23 07:01:17 -07:00
SerialInterface : : AddDevice ( IsUsingPad ( i ) ? SIDEVICE_GC_CONTROLLER : SIDEVICE_NONE , i ) ;
2011-07-11 20:15:05 +00:00
else
2011-10-23 07:01:17 -07:00
SerialInterface : : ChangeDevice ( IsUsingPad ( i ) ? SIDEVICE_GC_CONTROLLER : SIDEVICE_NONE , i ) ;
2011-02-11 19:09:46 +00:00
}
2011-07-11 20:15:05 +00:00
void ChangeWiiPads ( bool instantly )
2011-02-11 19:09:46 +00:00
{
2011-07-11 20:15:05 +00:00
int controllers = 0 ;
for ( int i = 0 ; i < 4 ; i + + )
if ( g_wiimote_sources [ i ] ! = WIIMOTE_SRC_NONE )
controllers | = ( 1 < < i ) ;
// This is important for Wiimotes, because they can desync easily if they get re-activated
if ( instantly & & ( g_numPads > > 4 ) = = controllers )
return ;
2011-02-11 19:09:46 +00:00
for ( int i = 0 ; i < 4 ; i + + )
{
g_wiimote_sources [ i ] = IsUsingWiimote ( i ) ? WIIMOTE_SRC_EMU : WIIMOTE_SRC_NONE ;
GetUsbPointer ( ) - > AccessWiiMote ( i | 0x100 ) - > Activate ( IsUsingWiimote ( i ) ) ;
2010-09-06 21:41:01 +00:00
}
}
2010-08-30 07:05:47 +00:00
bool BeginRecordingInput ( int controllers )
2010-06-09 01:37:08 +00:00
{
2011-05-03 00:06:44 +00:00
if ( g_playMode ! = MODE_NONE | | controllers = = 0 )
2010-06-09 01:37:08 +00:00
return false ;
2011-02-15 09:07:55 +00:00
2011-03-15 23:09:12 +00:00
if ( Core : : IsRunning ( ) )
2011-02-15 09:07:55 +00:00
{
2011-05-03 00:06:44 +00:00
if ( File : : Exists ( tmpStateFilename ) )
File : : Delete ( tmpStateFilename ) ;
2011-02-15 09:07:55 +00:00
2011-05-03 00:06:44 +00:00
State : : SaveAs ( tmpStateFilename . c_str ( ) ) ;
g_bRecordingFromSaveState = true ;
2010-06-09 01:37:08 +00:00
}
g_numPads = controllers ;
2011-12-14 04:03:05 -08:00
g_currentFrame = g_totalFrames = 0 ;
g_currentLagCount = g_totalLagCount = 0 ;
g_currentInputCount = g_totalInputCount = 0 ;
2011-12-26 05:09:30 -06:00
g_recordingStartTime = Common : : Timer : : GetLocalTimeSinceJan1970 ( ) ;
2011-05-03 00:06:44 +00:00
g_rerecords = 0 ;
2010-06-09 01:37:08 +00:00
g_playMode = MODE_RECORDING ;
2011-12-14 04:03:05 -08:00
2011-05-03 00:06:44 +00:00
delete tmpInput ;
tmpInput = new u8 [ MAX_DTM_LENGTH ] ;
2011-12-14 04:03:05 -08:00
g_currentByte = g_totalBytes = 0 ;
2010-08-30 07:05:47 +00:00
Core : : DisplayMessage ( " Starting movie recording " , 2000 ) ;
2010-06-09 01:37:08 +00:00
return true ;
}
2011-12-14 04:03:05 -08:00
static void Analog2DToString ( u8 x , u8 y , const char * prefix , char * str )
{
if ( ( x < = 1 | | x = = 128 | | x > = 255 )
& & ( y < = 1 | | y = = 128 | | y > = 255 ) )
{
if ( x ! = 128 | | y ! = 128 )
{
if ( x ! = 128 & & y ! = 128 )
{
sprintf ( str , " %s:%s,%s " , prefix , x < 128 ? " LEFT " : " RIGHT " , y < 128 ? " DOWN " : " UP " ) ;
}
else if ( x ! = 128 )
{
sprintf ( str , " %s:%s " , prefix , x < 128 ? " LEFT " : " RIGHT " ) ;
}
else
{
sprintf ( str , " %s:%s " , prefix , y < 128 ? " DOWN " : " UP " ) ;
}
}
else
{
str [ 0 ] = ' \0 ' ;
}
}
else
{
sprintf ( str , " %s:%d,%d " , prefix , x , y ) ;
}
}
static void Analog1DToString ( u8 v , const char * prefix , char * str )
{
if ( v > 0 )
{
if ( v = = 255 )
{
strcpy ( str , prefix ) ;
}
else
{
sprintf ( str , " %s:%d " , prefix , v ) ;
}
}
else
{
str [ 0 ] = ' \0 ' ;
}
}
2011-02-17 09:12:36 +00:00
void SetInputDisplayString ( ControllerState padState , int controllerID )
{
char inp [ 70 ] ;
2011-12-14 04:03:05 -08:00
sprintf ( inp , " P%d: " , controllerID + 1 ) ;
2011-02-17 09:12:36 +00:00
g_InputDisplay [ controllerID ] = inp ;
if ( g_padState . A )
g_InputDisplay [ controllerID ] . append ( " A " ) ;
if ( g_padState . B )
g_InputDisplay [ controllerID ] . append ( " B " ) ;
if ( g_padState . X )
g_InputDisplay [ controllerID ] . append ( " X " ) ;
if ( g_padState . Y )
g_InputDisplay [ controllerID ] . append ( " Y " ) ;
if ( g_padState . Z )
g_InputDisplay [ controllerID ] . append ( " Z " ) ;
if ( g_padState . Start )
g_InputDisplay [ controllerID ] . append ( " START " ) ;
if ( g_padState . DPadUp )
g_InputDisplay [ controllerID ] . append ( " UP " ) ;
if ( g_padState . DPadDown )
g_InputDisplay [ controllerID ] . append ( " DOWN " ) ;
if ( g_padState . DPadLeft )
g_InputDisplay [ controllerID ] . append ( " LEFT " ) ;
if ( g_padState . DPadRight )
g_InputDisplay [ controllerID ] . append ( " RIGHT " ) ;
2011-12-14 04:03:05 -08:00
//if(g_padState.L)
//{
// g_InputDisplay[controllerID].append(" L");
//}
//if(g_padState.R)
//{
// g_InputDisplay[controllerID].append(" R");
//}
Analog1DToString ( g_padState . TriggerL , " L " , inp ) ;
g_InputDisplay [ controllerID ] . append ( inp ) ;
Analog1DToString ( g_padState . TriggerR , " R " , inp ) ;
g_InputDisplay [ controllerID ] . append ( inp ) ;
Analog2DToString ( g_padState . AnalogStickX , g_padState . AnalogStickY , " ANA " , inp ) ;
g_InputDisplay [ controllerID ] . append ( inp ) ;
Analog2DToString ( g_padState . CStickX , g_padState . CStickY , " C " , inp ) ;
g_InputDisplay [ controllerID ] . append ( inp ) ;
g_InputDisplay [ controllerID ] . append ( " \n " ) ;
}
void SetWiiInputDisplayString ( int remoteID , u8 * const coreData , u8 * const accelData , u8 * const irData )
{
int controllerID = remoteID + 4 ;
char inp [ 70 ] ;
sprintf ( inp , " R%d: " , remoteID + 1 ) ;
g_InputDisplay [ controllerID ] = inp ;
if ( coreData )
{
wm_core buttons = * ( wm_core * ) coreData ;
if ( buttons & WiimoteEmu : : Wiimote : : PAD_LEFT )
g_InputDisplay [ controllerID ] . append ( " LEFT " ) ;
if ( buttons & WiimoteEmu : : Wiimote : : PAD_RIGHT )
g_InputDisplay [ controllerID ] . append ( " RIGHT " ) ;
if ( buttons & WiimoteEmu : : Wiimote : : PAD_DOWN )
g_InputDisplay [ controllerID ] . append ( " DOWN " ) ;
if ( buttons & WiimoteEmu : : Wiimote : : PAD_UP )
g_InputDisplay [ controllerID ] . append ( " UP " ) ;
if ( buttons & WiimoteEmu : : Wiimote : : BUTTON_A )
g_InputDisplay [ controllerID ] . append ( " A " ) ;
if ( buttons & WiimoteEmu : : Wiimote : : BUTTON_B )
g_InputDisplay [ controllerID ] . append ( " B " ) ;
if ( buttons & WiimoteEmu : : Wiimote : : BUTTON_PLUS )
g_InputDisplay [ controllerID ] . append ( " + " ) ;
if ( buttons & WiimoteEmu : : Wiimote : : BUTTON_MINUS )
g_InputDisplay [ controllerID ] . append ( " - " ) ;
if ( buttons & WiimoteEmu : : Wiimote : : BUTTON_ONE )
g_InputDisplay [ controllerID ] . append ( " 1 " ) ;
if ( buttons & WiimoteEmu : : Wiimote : : BUTTON_TWO )
g_InputDisplay [ controllerID ] . append ( " 2 " ) ;
if ( buttons & WiimoteEmu : : Wiimote : : BUTTON_HOME )
g_InputDisplay [ controllerID ] . append ( " HOME " ) ;
2011-02-17 09:12:36 +00:00
}
2011-12-14 04:03:05 -08:00
if ( accelData )
2011-02-17 09:12:36 +00:00
{
2011-12-14 04:03:05 -08:00
wm_accel * dt = ( wm_accel * ) accelData ;
sprintf ( inp , " ACC:%d,%d,%d " , dt - > x , dt - > y , dt - > z ) ;
g_InputDisplay [ controllerID ] . append ( inp ) ;
2011-02-17 09:12:36 +00:00
}
2011-12-14 04:03:05 -08:00
if ( irData ) // incomplete
2011-02-17 09:12:36 +00:00
{
2011-12-14 04:03:05 -08:00
sprintf ( inp , " IR:%d,%d " , ( ( u8 * ) irData ) [ 0 ] , ( ( u8 * ) irData ) [ 1 ] ) ;
g_InputDisplay [ controllerID ] . append ( inp ) ;
2011-02-17 09:12:36 +00:00
}
g_InputDisplay [ controllerID ] . append ( " \n " ) ;
}
2011-12-14 04:03:05 -08:00
2010-06-09 01:37:08 +00:00
void RecordInput ( SPADStatus * PadStatus , int controllerID )
{
2010-09-06 21:41:01 +00:00
if ( ! IsRecordingInput ( ) | | ! IsUsingPad ( controllerID ) )
2010-06-09 01:37:08 +00:00
return ;
2010-08-30 07:05:47 +00:00
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 ) ;
2010-06-09 01:37:08 +00:00
2010-08-30 07:05:47 +00:00
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 ) ;
2010-06-09 01:37:08 +00:00
2011-02-15 23:38:44 +00:00
g_padState . L = ( ( PadStatus - > button & PAD_TRIGGER_L ) ! = 0 ) ;
g_padState . R = ( ( PadStatus - > button & PAD_TRIGGER_R ) ! = 0 ) ;
g_padState . TriggerL = PadStatus - > triggerLeft ;
g_padState . TriggerR = PadStatus - > triggerRight ;
2010-06-09 01:37:08 +00:00
2010-08-30 07:05:47 +00:00
g_padState . AnalogStickX = PadStatus - > stickX ;
g_padState . AnalogStickY = PadStatus - > stickY ;
2010-06-09 01:37:08 +00:00
2010-08-30 07:05:47 +00:00
g_padState . CStickX = PadStatus - > substickX ;
g_padState . CStickY = PadStatus - > substickY ;
2011-12-15 09:22:16 -08:00
2011-12-14 04:03:05 -08:00
memcpy ( & ( tmpInput [ g_currentByte ] ) , & g_padState , 8 ) ;
g_currentByte + = 8 ;
g_totalBytes = g_currentByte ;
2011-02-17 09:12:36 +00:00
SetInputDisplayString ( g_padState , controllerID ) ;
2010-06-09 01:37:08 +00:00
}
2011-12-15 09:22:16 -08:00
void RecordWiimote ( int wiimote , u8 * data , const WiimoteEmu : : ReportFeatures & rptf , int irMode )
2011-02-11 18:53:51 +00:00
{
2011-02-12 08:25:09 +00:00
if ( ! IsRecordingInput ( ) | | ! IsUsingWiimote ( wiimote ) )
2011-02-11 18:53:51 +00:00
return ;
2011-12-15 09:22:16 -08:00
u8 * const coreData = rptf . core ? ( data + rptf . core ) : NULL ;
u8 * const accelData = rptf . accel ? ( data + rptf . accel ) : NULL ;
u8 * const irData = rptf . ir ? ( data + rptf . ir ) : NULL ;
u8 size = rptf . size ;
2011-12-14 04:03:05 -08:00
InputUpdate ( ) ;
2011-12-15 09:22:16 -08:00
tmpInput [ g_currentByte + + ] = size ;
2011-12-14 04:03:05 -08:00
memcpy ( & ( tmpInput [ g_currentByte ] ) , data , size ) ;
g_currentByte + = size ;
g_totalBytes = g_currentByte ;
SetWiiInputDisplayString ( wiimote , coreData , accelData , irData ) ;
2011-02-11 18:53:51 +00:00
}
2010-06-09 01:37:08 +00:00
bool PlayInput ( const char * filename )
{
2011-05-03 00:06:44 +00:00
if ( ! filename | | g_playMode ! = MODE_NONE )
2010-06-09 01:37:08 +00:00
return false ;
2011-05-03 00:06:44 +00:00
2010-06-09 01:37:08 +00:00
if ( ! File : : Exists ( filename ) )
return false ;
2011-05-03 00:06:44 +00:00
File : : IOFile g_recordfd ;
2011-02-12 02:14:20 +00:00
2011-05-03 00:06:44 +00:00
if ( ! g_recordfd . Open ( filename , " rb " ) )
2010-06-09 01:37:08 +00:00
return false ;
2011-05-03 00:06:44 +00:00
g_recordfd . ReadArray ( & tmpHeader , 1 ) ;
2010-06-09 01:37:08 +00:00
2011-05-03 00:06:44 +00:00
if ( tmpHeader . filetype [ 0 ] ! = ' D ' | | tmpHeader . filetype [ 1 ] ! = ' T ' | | tmpHeader . filetype [ 2 ] ! = ' M ' | | tmpHeader . filetype [ 3 ] ! = 0x1A ) {
2011-01-13 02:05:58 +00:00
PanicAlertT ( " Invalid recording file " ) ;
2010-06-09 01:37:08 +00:00
goto cleanup ;
}
// Load savestate (and skip to frame data)
2011-05-03 00:06:44 +00:00
if ( tmpHeader . bFromSaveState )
2011-02-15 09:07:55 +00:00
{
2011-03-01 03:06:14 +00:00
const std : : string stateFilename = std : : string ( filename ) + " .sav " ;
if ( File : : Exists ( stateFilename ) )
2011-02-15 09:07:55 +00:00
Core : : SetStateFileName ( stateFilename ) ;
g_bRecordingFromSaveState = true ;
2010-06-09 01:37:08 +00:00
}
/* TODO: Put this verification somewhere we have the gameID of the played game
// TODO: Replace with Unique ID
2011-05-03 00:06:44 +00:00
if ( tmpHeader . uniqueID ! = 0 ) {
2010-06-09 01:37:08 +00:00
PanicAlert ( " Recording Unique ID Verification Failed " ) ;
goto cleanup ;
}
2011-05-03 00:06:44 +00:00
if ( strncmp ( ( char * ) tmpHeader . gameID , Core : : g_CoreStartupParameter . GetUniqueID ( ) . c_str ( ) , 6 ) ) {
2010-06-09 01:37:08 +00:00
PanicAlert ( " The recorded game (%s) is not the same as the selected game (%s) " , header . gameID , Core : : g_CoreStartupParameter . GetUniqueID ( ) . c_str ( ) ) ;
goto cleanup ;
}
*/
2011-05-03 00:06:44 +00:00
g_numPads = tmpHeader . numControllers ;
g_rerecords = tmpHeader . numRerecords ;
2011-12-14 04:03:05 -08:00
g_totalFrames = tmpHeader . frameCount ;
g_totalLagCount = tmpHeader . lagCount ;
g_totalInputCount = tmpHeader . inputCount ;
2011-12-26 05:09:30 -06:00
g_recordingStartTime = tmpHeader . recordingStartTime ;
2011-12-14 04:03:05 -08:00
2011-12-15 09:22:16 -08:00
g_currentFrame = 0 ;
g_currentLagCount = 0 ;
g_currentInputCount = 0 ;
2010-06-09 01:37:08 +00:00
g_playMode = MODE_PLAYING ;
2011-12-14 04:03:05 -08:00
g_totalBytes = g_recordfd . GetSize ( ) - 256 ;
2011-05-03 00:06:44 +00:00
delete tmpInput ;
tmpInput = new u8 [ MAX_DTM_LENGTH ] ;
2011-12-14 04:03:05 -08:00
g_recordfd . ReadArray ( tmpInput , ( size_t ) g_totalBytes ) ;
g_currentByte = 0 ;
2011-05-03 00:06:44 +00:00
g_recordfd . Close ( ) ;
2010-06-09 01:37:08 +00:00
return true ;
cleanup :
2011-03-11 10:21:46 +00:00
g_recordfd . Close ( ) ;
2010-06-09 01:37:08 +00:00
return false ;
}
2011-12-14 04:03:05 -08:00
void DoState ( PointerWrap & p , bool doNot )
{
if ( doNot )
{
if ( p . GetMode ( ) = = PointerWrap : : MODE_READ )
g_currentSaveVersion = 0 ;
return ;
}
static const int MOVIE_STATE_VERSION = 1 ;
g_currentSaveVersion = MOVIE_STATE_VERSION ;
p . Do ( g_currentSaveVersion ) ;
// many of these could be useful to save even when no movie is active,
// and the data is tiny, so let's just save it regardless of movie state.
p . Do ( g_currentFrame ) ;
p . Do ( g_currentByte ) ;
p . Do ( g_currentLagCount ) ;
p . Do ( g_currentInputCount ) ;
p . Do ( g_bPolled ) ;
// other variables (such as g_totalBytes and g_totalFrames) are set in LoadInput
}
2010-08-30 07:05:47 +00:00
void LoadInput ( const char * filename )
{
2011-05-03 00:06:44 +00:00
File : : IOFile t_record ( filename , " r+b " ) ;
2010-08-30 07:05:47 +00:00
2011-05-03 00:06:44 +00:00
t_record . ReadArray ( & tmpHeader , 1 ) ;
2010-08-30 07:05:47 +00:00
2011-05-03 00:06:44 +00:00
if ( tmpHeader . filetype [ 0 ] ! = ' D ' | | tmpHeader . filetype [ 1 ] ! = ' T ' | | tmpHeader . filetype [ 2 ] ! = ' M ' | | tmpHeader . filetype [ 3 ] ! = 0x1A )
2011-02-15 09:07:55 +00:00
{
2011-01-13 02:05:58 +00:00
PanicAlertT ( " Savestate movie %s is corrupted, movie recording stopping... " , filename ) ;
2011-02-15 17:03:20 +00:00
EndPlayInput ( false ) ;
2010-08-30 07:05:47 +00:00
return ;
}
2011-07-11 20:15:05 +00:00
if ( ! g_bReadOnly )
2011-12-17 23:27:11 -08:00
{
2011-07-11 20:15:05 +00:00
tmpHeader . numRerecords + + ;
2011-12-17 23:27:11 -08:00
t_record . Seek ( 0 , SEEK_SET ) ;
t_record . WriteArray ( & tmpHeader , 1 ) ;
}
2010-08-30 07:05:47 +00:00
2011-05-03 00:06:44 +00:00
g_numPads = tmpHeader . numControllers ;
2011-02-12 00:06:58 +00:00
ChangePads ( true ) ;
if ( Core : : g_CoreStartupParameter . bWii )
2011-07-11 20:15:05 +00:00
ChangeWiiPads ( true ) ;
2011-05-03 00:06:44 +00:00
2011-12-17 23:27:11 -08:00
u64 totalSavedBytes = t_record . GetSize ( ) - 256 ;
bool afterEnd = false ;
if ( g_currentByte > totalSavedBytes )
{
//PanicAlertT("Warning: You loaded a save whose movie ends before the current frame in the save (byte %u < %u) (frame %u < %u). You should load another save before continuing.", (u32)totalSavedBytes+256, (u32)g_currentByte+256, (u32)tmpHeader.frameCount, (u32)g_currentFrame);
afterEnd = true ;
}
if ( ! g_bReadOnly | | tmpInput = = NULL )
2011-12-14 04:03:05 -08:00
{
g_totalFrames = tmpHeader . frameCount ;
g_totalLagCount = tmpHeader . lagCount ;
g_totalInputCount = tmpHeader . inputCount ;
2011-12-17 23:27:11 -08:00
g_totalBytes = totalSavedBytes ;
2011-12-14 04:03:05 -08:00
delete tmpInput ;
tmpInput = new u8 [ MAX_DTM_LENGTH ] ;
t_record . ReadArray ( tmpInput , ( size_t ) g_totalBytes ) ;
}
else if ( g_currentByte > 0 )
{
2011-12-17 23:27:11 -08:00
if ( g_currentByte > totalSavedBytes )
2011-12-14 04:03:05 -08:00
{
}
2011-12-17 23:27:11 -08:00
else if ( g_currentByte > g_totalBytes )
2011-12-14 04:03:05 -08:00
{
2011-12-17 23:27:11 -08:00
PanicAlertT ( " Warning: You loaded a save that's after the end of the current movie. (byte %u > %u) (frame %u > %u). You should load another save before continuing, or load this state with read-only mode off. " , ( u32 ) g_currentByte + 256 , ( u32 ) g_totalBytes + 256 , ( u32 ) g_currentFrame , ( u32 ) g_totalFrames ) ;
}
else if ( g_currentByte > 0 & & g_totalBytes > 0 )
{
2011-12-26 05:09:30 -06:00
// verify identical from movie start to the save's current frame
2011-12-14 04:03:05 -08:00
u32 len = ( u32 ) g_currentByte ;
2011-12-17 23:27:11 -08:00
u8 * movInput = new u8 [ len ] ;
t_record . ReadArray ( movInput , ( size_t ) len ) ;
2011-12-14 04:03:05 -08:00
for ( u32 i = 0 ; i < len ; + + i )
{
2011-12-17 23:27:11 -08:00
if ( movInput [ i ] ! = tmpInput [ i ] )
2011-12-14 04:03:05 -08:00
{
2011-12-17 23:27:11 -08:00
// this is a "you did something wrong" alert for the user's benefit.
// we'll try to say what's going on in excruciating detail, otherwise the user might not believe us.
2011-12-14 04:03:05 -08:00
if ( Core : : g_CoreStartupParameter . bWii )
2011-12-17 23:27:11 -08:00
{
// TODO: more detail
PanicAlertT ( " Warning: You loaded a save whose movie mismatches on byte %d (0x%X). You should load another save before continuing, or load this state with read-only mode off. Otherwise you'll probably get a desync. " , i + 256 , i + 256 ) ;
}
2011-12-14 04:03:05 -08:00
else
2011-12-17 23:27:11 -08:00
{
int frame = i / 8 ;
ControllerState curPadState ;
memcpy ( & curPadState , & ( tmpInput [ frame * 8 ] ) , 8 ) ;
ControllerState movPadState ;
memcpy ( & movPadState , & ( movInput [ frame * 8 ] ) , 8 ) ;
PanicAlertT ( " Warning: You loaded a save whose movie mismatches on frame %d. You should load another save before continuing, or load this state with read-only mode off. Otherwise you'll probably get a desync. \n \n "
" More information: The current movie is %d frames long and the savestate's movie is %d frames long. \n \n "
" On frame %d, the current movie presses: \n "
" Start=%d, A=%d, B=%d, X=%d, Y=%d, Z=%d, DUp=%d, DDown=%d, DLeft=%d, DRight=%d, L=%d, R=%d, LT=%d, RT=%d, AnalogX=%d, AnalogY=%d, CX=%d, CY=%d "
" \n \n "
" On frame %d, the savestate's movie presses: \n "
" Start=%d, A=%d, B=%d, X=%d, Y=%d, Z=%d, DUp=%d, DDown=%d, DLeft=%d, DRight=%d, L=%d, R=%d, LT=%d, RT=%d, AnalogX=%d, AnalogY=%d, CX=%d, CY=%d " ,
( int ) frame ,
( int ) g_totalFrames , ( int ) tmpHeader . frameCount ,
( int ) frame ,
( int ) curPadState . Start , ( int ) curPadState . A , ( int ) curPadState . B , ( int ) curPadState . X , ( int ) curPadState . Y , ( int ) curPadState . Z , ( int ) curPadState . DPadUp , ( int ) curPadState . DPadDown , ( int ) curPadState . DPadLeft , ( int ) curPadState . DPadRight , ( int ) curPadState . L , ( int ) curPadState . R , ( int ) curPadState . TriggerL , ( int ) curPadState . TriggerR , ( int ) curPadState . AnalogStickX , ( int ) curPadState . AnalogStickY , ( int ) curPadState . CStickX , ( int ) curPadState . CStickY ,
( int ) frame ,
( int ) movPadState . Start , ( int ) movPadState . A , ( int ) movPadState . B , ( int ) movPadState . X , ( int ) movPadState . Y , ( int ) movPadState . Z , ( int ) movPadState . DPadUp , ( int ) movPadState . DPadDown , ( int ) movPadState . DPadLeft , ( int ) movPadState . DPadRight , ( int ) movPadState . L , ( int ) movPadState . R , ( int ) movPadState . TriggerL , ( int ) movPadState . TriggerR , ( int ) movPadState . AnalogStickX , ( int ) movPadState . AnalogStickY , ( int ) movPadState . CStickX , ( int ) movPadState . CStickY ) ;
}
2011-12-14 04:03:05 -08:00
break ;
}
}
2011-12-17 23:27:11 -08:00
delete movInput ;
2011-12-14 04:03:05 -08:00
}
}
2011-05-03 00:06:44 +00:00
t_record . Close ( ) ;
2011-12-14 04:03:05 -08:00
2011-05-03 00:06:44 +00:00
g_rerecords = tmpHeader . numRerecords ;
2011-07-11 20:15:05 +00:00
2011-12-17 23:27:11 -08:00
if ( ! afterEnd )
2011-07-11 20:15:05 +00:00
{
2011-12-17 23:27:11 -08:00
if ( g_bReadOnly )
2011-12-14 04:03:05 -08:00
{
2011-12-17 23:27:11 -08:00
if ( g_playMode ! = MODE_PLAYING )
{
g_playMode = MODE_PLAYING ;
Core : : DisplayMessage ( " Switched to playback " , 2000 ) ;
}
}
else
{
if ( g_playMode ! = MODE_RECORDING )
{
g_playMode = MODE_RECORDING ;
Core : : DisplayMessage ( " Switched to recording " , 2000 ) ;
}
2011-12-14 04:03:05 -08:00
}
2011-07-11 20:15:05 +00:00
}
else
{
2011-12-17 23:27:11 -08:00
EndPlayInput ( false ) ;
2011-12-14 04:03:05 -08:00
}
}
static void CheckInputEnd ( )
{
if ( g_currentFrame > g_totalFrames | | g_currentByte > = g_totalBytes )
{
EndPlayInput ( ! g_bReadOnly ) ;
2011-07-11 20:15:05 +00:00
}
2010-08-30 07:05:47 +00:00
}
2010-06-09 01:37:08 +00:00
void PlayController ( SPADStatus * PadStatus , int controllerID )
{
2010-08-30 07:05:47 +00:00
// Correct playback is entirely dependent on the emulator polling the controllers
// in the same order done during recording
2011-05-03 17:15:38 +00:00
if ( ! IsPlayingInput ( ) | | ! IsUsingPad ( controllerID ) | | tmpInput = = NULL )
2010-06-09 01:37:08 +00:00
return ;
2011-02-17 09:12:36 +00:00
2011-12-14 04:03:05 -08:00
if ( g_currentByte + 8 > g_totalBytes )
2011-03-11 10:21:46 +00:00
{
2011-12-14 04:03:05 -08:00
PanicAlertT ( " Premature movie end in PlayController. %u + 8 > %u " , ( u32 ) g_currentByte , ( u32 ) g_totalBytes ) ;
2011-03-11 10:21:46 +00:00
EndPlayInput ( ! g_bReadOnly ) ;
2011-05-03 00:06:44 +00:00
return ;
2011-03-11 10:21:46 +00:00
}
2011-06-24 06:50:50 +00:00
// dtm files don't save the mic button or error bit. not sure if they're actually
// used, but better safe than sorry
signed char e = PadStatus - > err ;
memset ( PadStatus , 0 , sizeof ( SPADStatus ) ) ;
PadStatus - > err = e ;
2011-12-15 09:22:16 -08:00
2011-12-14 04:03:05 -08:00
memcpy ( & g_padState , & ( tmpInput [ g_currentByte ] ) , 8 ) ;
g_currentByte + = 8 ;
2011-05-03 00:06:44 +00:00
2011-02-17 09:12:36 +00:00
PadStatus - > triggerLeft = g_padState . TriggerL ;
PadStatus - > triggerRight = g_padState . TriggerR ;
PadStatus - > stickX = g_padState . AnalogStickX ;
PadStatus - > stickY = g_padState . AnalogStickY ;
PadStatus - > substickX = g_padState . CStickX ;
PadStatus - > substickY = g_padState . CStickY ;
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_USE_ORIGIN ;
2011-02-17 09:12:36 +00:00
if ( g_padState . A )
{
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_BUTTON_A ;
PadStatus - > analogA = 0xFF ;
}
2011-02-17 09:12:36 +00:00
if ( g_padState . B )
{
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_BUTTON_B ;
PadStatus - > analogB = 0xFF ;
}
2010-08-30 07:05:47 +00:00
if ( g_padState . X )
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_BUTTON_X ;
2010-08-30 07:05:47 +00:00
if ( g_padState . Y )
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_BUTTON_Y ;
2010-08-30 07:05:47 +00:00
if ( g_padState . Z )
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_TRIGGER_Z ;
2010-08-30 07:05:47 +00:00
if ( g_padState . Start )
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_BUTTON_START ;
2010-08-30 07:05:47 +00:00
if ( g_padState . DPadUp )
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_BUTTON_UP ;
2010-08-30 07:05:47 +00:00
if ( g_padState . DPadDown )
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_BUTTON_DOWN ;
2010-08-30 07:05:47 +00:00
if ( g_padState . DPadLeft )
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_BUTTON_LEFT ;
2010-08-30 07:05:47 +00:00
if ( g_padState . DPadRight )
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_BUTTON_RIGHT ;
2011-02-15 23:38:44 +00:00
if ( g_padState . L )
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_TRIGGER_L ;
2011-02-15 23:38:44 +00:00
if ( g_padState . R )
2010-06-09 01:37:08 +00:00
PadStatus - > button | = PAD_TRIGGER_R ;
2011-02-17 09:12:36 +00:00
SetInputDisplayString ( g_padState , controllerID ) ;
2011-12-14 04:03:05 -08:00
CheckInputEnd ( ) ;
2010-06-09 01:37:08 +00:00
}
2011-12-15 09:22:16 -08:00
bool PlayWiimote ( int wiimote , u8 * data , const WiimoteEmu : : ReportFeatures & rptf , int irMode )
2011-02-11 18:53:51 +00:00
{
2011-05-03 17:15:38 +00:00
if ( ! IsPlayingInput ( ) | | ! IsUsingWiimote ( wiimote ) | | tmpInput = = NULL )
2011-02-12 08:25:09 +00:00
return false ;
2011-12-15 09:22:16 -08:00
2011-12-14 04:03:05 -08:00
if ( g_currentByte > g_totalBytes )
2011-05-03 00:06:44 +00:00
{
2011-12-14 04:03:05 -08:00
PanicAlertT ( " Premature movie end in PlayWiimote. %u > %u " , ( u32 ) g_currentByte , ( u32 ) g_totalBytes ) ;
2011-05-03 00:06:44 +00:00
EndPlayInput ( ! g_bReadOnly ) ;
return false ;
}
2011-12-15 09:22:16 -08:00
u8 * const coreData = rptf . core ? ( data + rptf . core ) : NULL ;
u8 * const accelData = rptf . accel ? ( data + rptf . accel ) : NULL ;
u8 * const irData = rptf . ir ? ( data + rptf . ir ) : NULL ;
u8 size = rptf . size ;
u8 sizeInMovie = tmpInput [ g_currentByte ] ;
if ( size ! = sizeInMovie )
2011-05-03 00:06:44 +00:00
{
2011-12-26 05:09:30 -06:00
PanicAlertT ( " Fatal desync. Aborting playback. (Error in PlayWiimote: %u != %u, byte %d.)%s " , sizeInMovie , size , g_currentByte , ( g_numPads & 0xF ) ? " Try re-creating the recording with all GameCube controllers disabled. " : " " ) ;
2011-12-15 09:22:16 -08:00
EndPlayInput ( ! g_bReadOnly ) ;
return false ;
}
g_currentByte + + ;
if ( g_currentByte + size > g_totalBytes )
{
PanicAlertT ( " Premature movie end in PlayWiimote. %u + %d > %u " , ( u32 ) g_currentByte , size , ( u32 ) g_totalBytes ) ;
2011-05-03 00:06:44 +00:00
EndPlayInput ( ! g_bReadOnly ) ;
return false ;
}
2011-12-15 09:22:16 -08:00
memcpy ( data , & ( tmpInput [ g_currentByte ] ) , size ) ;
g_currentByte + = size ;
2011-02-11 18:53:51 +00:00
2011-12-14 04:03:05 -08:00
SetWiiInputDisplayString ( wiimote , coreData , accelData , irData ) ;
g_currentInputCount + + ;
2011-05-03 00:06:44 +00:00
2011-12-14 04:03:05 -08:00
CheckInputEnd ( ) ;
2011-02-12 08:25:09 +00:00
return true ;
2011-02-11 18:53:51 +00:00
}
2011-02-15 17:03:20 +00:00
void EndPlayInput ( bool cont )
{
2011-05-03 00:06:44 +00:00
if ( cont )
{
2011-02-12 02:14:20 +00:00
g_playMode = MODE_RECORDING ;
2011-12-17 23:27:11 -08:00
Core : : DisplayMessage ( " Reached movie end. Resuming recording. " , 2000 ) ;
2011-02-12 02:14:20 +00:00
}
2011-12-14 04:03:05 -08:00
else if ( g_playMode ! = MODE_NONE )
2011-02-12 02:14:20 +00:00
{
g_numPads = g_rerecords = 0 ;
2011-12-17 23:27:11 -08:00
g_currentByte = 0 ;
2011-02-12 02:14:20 +00:00
g_playMode = MODE_NONE ;
2011-12-17 23:27:11 -08:00
Core : : DisplayMessage ( " Movie End. " , 2000 ) ;
// we don't clear these things because otherwise we can't resume playback if we load a movie state later
//g_totalFrames = g_totalBytes = 0;
//delete tmpInput;
//tmpInput = NULL;
2011-02-12 02:14:20 +00:00
}
2010-06-09 01:37:08 +00:00
}
2010-08-30 07:05:47 +00:00
void SaveRecording ( const char * filename )
{
2011-05-03 00:06:44 +00:00
File : : IOFile save_record ( filename , " wb " ) ;
2011-07-11 20:15:05 +00:00
// 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 & ( Core : : g_CoreStartupParameter . bWii ? 0xFF : 0x0F ) ;
header . bFromSaveState = g_bRecordingFromSaveState ;
2011-12-14 04:03:05 -08:00
header . frameCount = g_totalFrames ;
header . lagCount = g_totalLagCount ;
header . inputCount = g_totalInputCount ;
2011-07-11 20:15:05 +00:00
header . numRerecords = g_rerecords ;
2011-12-26 05:09:30 -06:00
header . recordingStartTime = g_recordingStartTime ;
2011-07-11 20:15:05 +00:00
// TODO
header . uniqueID = 0 ;
// header.author;
// header.videoBackend;
// header.audioEmulator;
save_record . WriteArray ( & header , 1 ) ;
2011-02-12 02:14:20 +00:00
2011-12-14 04:03:05 -08:00
bool success = save_record . WriteArray ( tmpInput , ( size_t ) g_totalBytes ) ;
2011-02-12 02:14:20 +00:00
2011-02-15 09:07:55 +00:00
if ( success & & g_bRecordingFromSaveState )
{
std : : string stateFilename = filename ;
stateFilename . append ( " .sav " ) ;
2011-03-01 03:06:14 +00:00
success = File : : Copy ( tmpStateFilename , stateFilename ) ;
2011-02-15 09:07:55 +00:00
}
2010-08-30 07:05:47 +00:00
2011-02-12 02:14:20 +00:00
if ( success )
2010-08-30 07:05:47 +00:00
Core : : DisplayMessage ( StringFromFormat ( " DTM %s saved " , filename ) . c_str ( ) , 2000 ) ;
else
Core : : DisplayMessage ( StringFromFormat ( " Failed to save %s " , filename ) . c_str ( ) , 2000 ) ;
}
2011-06-24 06:50:50 +00:00
void SetInputManip ( ManipFunction func )
{
mfunc = func ;
}
void CallInputManip ( SPADStatus * PadStatus , int controllerID )
{
if ( mfunc )
( * mfunc ) ( PadStatus , controllerID ) ;
}
2010-06-09 01:37:08 +00:00
} ;