mupen64plus-oldsvn/main/inputrecording.c

533 lines
16 KiB
C

/**
* Mupen64Plus - inputrecording.c
* Copyright (C) 2008 okaygo
*
* Mupen64Plus homepage: http://code.google.com/p/mupen64plus/
*
*
* This program is free software; you can redistribute it and/
* or modify it under the terms of the GNU General Public Li-
* cence as published by the Free Software Foundation; either
* version 2 of the Licence, or any later version.
*
* This program is distributed in the hope that it will be use-
* ful, but WITHOUT ANY WARRANTY; without even the implied war-
* ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public Licence for more details.
*
* You should have received a copy of the GNU General Public
* Licence along with this program; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
* USA.
*
**/
// Rename to input_handler.c
#include "plugin.h"
#include "rom.h"
#include "savestates.h"
#include "main.h"
#include "inputrecording.h"
#include "../memory/memory.h"
#include "config.h"
#include "../r4300/interupt.h"
#include "translate.h"
#include "../opengl/osd.h"
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
#define MUP_MAGIC (0x1a34364d) // M64\0x1a
#define MUP_VERSION (3)
#define MUP_HEADER_SIZE (sizeof(m64_header))
#define CONTROLLER_PRESENT (0x001)
#define CONTROLLER_MEMPACK (0x010)
#define CONTROLLER_RUMBLEPACK (0x100)
int l_CurrentSample = 0;
int l_CurrentVI = 0;
char l_InputDisplay[64];
int l_LastInput = 0;
int l_TotalSamples = 0;
int g_UseSaveData = 1; // TAS will always use its own version of savedata. This is a global variable
int l_ForceManual = 0; // use manual settings - ie: do not let emulator configure for you.
char progress_file[255];
char playback_file[255];
FILE *PlaybackFile;
FILE *RecordingFile;
m64_header Header;
char* get_m64_filename()
{
// create the .m64 file in the .mupen64plus/save directory
size_t length = strlen(get_savespath()) + strlen((char*)ROM_HEADER->nom) + 4 + 1;
char *file = (char *) malloc(length);
snprintf(file, length, "%s%s.m64", get_savespath(), ROM_HEADER->nom);
file[length] = '\0';
return file;
}
int BeginPlayback(const char *sz_filename)
{
int result = 0;
if (g_EmulatorRunning) {
strcpy(playback_file, sz_filename);
strcpy(progress_file, sz_filename);
strcat(progress_file, ".progress"); // savestate at end of recording
PlaybackFile = fopen(sz_filename,"rb");
if (!PlaybackFile) {
EndPlaybackAndRecording();
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Could not open file %s for playback."), sz_filename);
return 0;
}
if (!fread(&Header,sizeof(Header),1,PlaybackFile) == 1) {
EndPlaybackAndRecording();
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Failed to read file header: %s."), sz_filename);
return 0;
}
if (SetupEmulationState()) {
g_EmulatorPlayback = 1;
if (Header.start_type == MOVIE_START_FROM_RESET) {
add_interupt_event(HW2_INT, 0); /* Hardware 2 Interrupt immediately */
add_interupt_event(NMI_INT, 50000000); /* Non maskable Interrupt after 1/2 second */
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Playback started from reset: %s"), sz_filename);
} else {
savestates_select_slot((unsigned int) Header.movie_uid);
savestates_job |= LOADSTATE;
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Playback from savestate started"));
}
} else {
EndPlaybackAndRecording();
return 0;
}
result = 1;
}
return result;
}
int ResumeEof(const char *sz_filename)
{
int result = 0;
if (g_EmulatorRunning) {
strcpy(playback_file, sz_filename);
strcpy(progress_file, sz_filename);
strcat(progress_file, ".progress");
RecordingFile = fopen(sz_filename,"rb+");
fseek(RecordingFile, 0L, SEEK_END);
if (!RecordingFile) {
EndPlaybackAndRecording();
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Could not open file %s for recording."), sz_filename);
return 0;
}
// Resume the .progress file
// Saved on "End Recording"
savestates_select_filename(progress_file);
savestates_job |= LOADSTATE;
if (g_ReadOnlyPlayback == 0) {
g_EmulatorRecording = 1;
fseek(RecordingFile, 0L, SEEK_SET);
if (!fread(&Header,sizeof(Header),1,RecordingFile) == 1) {
EndPlaybackAndRecording();
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Failed to read file header: %s."), sz_filename);
return 0;
}
fseek(RecordingFile, 0L, SEEK_END);
SetupEmulationState();
}
}
return result;
}
int BeginRecording(char *sz_filename, int fromSnapshot, const char *authorUTF8, const char *descriptionUTF8 )
{
int result = 0;
if (g_EmulatorRunning) {
strcpy(playback_file, sz_filename);
strcpy(progress_file, sz_filename);
strcat(progress_file, ".progress");
RecordingFile = fopen(sz_filename,"wb");
if (!RecordingFile) {
EndPlaybackAndRecording();
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Could not create file %s for recording."), sz_filename);
return 0;
}
WriteEmulationState(fromSnapshot, authorUTF8, descriptionUTF8);
if (!fwrite(&Header,sizeof(Header),1,RecordingFile) == 1) {
EndPlaybackAndRecording();
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Failed to write to file: %s"), sz_filename);
return 0;
}
if (SetupEmulationState()) {
g_EmulatorRecording = 1;
if (Header.start_type == MOVIE_START_FROM_RESET) {
add_interupt_event(HW2_INT, 0); /* Hardware 2 Interrupt immediately */
add_interupt_event(NMI_INT, 50000000); /* Non maskable Interrupt after 1/2 second */
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Starting from reset"));
} else {
savestates_job |= SAVESTATE;
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Starting from savestate"));
}
}
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Recording started"));
result = 1;
}
return result;
}
int WriteEmulationState(int fromSnapshot, const char *authorUTF8, const char *descriptionUTF8)
{
int i;
memset(&Header, 0, MUP_HEADER_SIZE);
Header.signature = MUP_MAGIC;
Header.version_number = MUP_VERSION;
Header.movie_uid = (int) savestates_get_slot();
Header.rerecord_count = 0;
for (i = 0; i < 4; i++) {
if (Controls[i].Present) {
Header.controllers ++;
Header.controller_flags |= CONTROLLER_PRESENT << i;
if (Controls[i].Plugin == PLUGIN_MEMPAK) {
Header.controller_flags |= CONTROLLER_MEMPACK << i;
}
if (Controls[i].Plugin == PLUGIN_RUMBLE_PAK) {
Header.controller_flags |= CONTROLLER_RUMBLEPACK << i;
}
}
}
Header.core_type = (short) dynacore; // was: reserved1
if (fromSnapshot == 0) {
Header.start_type = MOVIE_START_FROM_RESET;
} else {
Header.start_type = MOVIE_START_FROM_SNAPSHOT;
}
sprintf(Header.utf_authorname, "%s", authorUTF8);
sprintf(Header.utf_moviedesc, "%s", descriptionUTF8);
sprintf(Header.rom_name, "%s", ROM_HEADER->nom);
Header.rom_crc = ROM_HEADER->CRC1;
Header.rom_cc = (short) ROM_HEADER->Country_code;
snprintf(Header.video_plugin, 64, "%s", getGfxName());
snprintf(Header.input_plugin, 64, "%s", getInputName());
snprintf(Header.sound_plugin, 64, "%s", getAudioName());
snprintf(Header.rsp_plugin, 64, "%s", getRspName());
}
int SetupEmulationState()
{
if (Header.signature != MUP_MAGIC) {
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Invalid signature in header file."));
return 0;
}
if (Header.version_number != 3) {
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Invalid version number: %i"), Header.version_number);
return 0;
}
printf("Movie UID: %i\n",Header.movie_uid);
printf("Total VI's: %i\n",Header.total_vi);
printf("Rerecord Count: %i\n",Header.rerecord_count);
printf("FPS: %i\n",Header.fps);
printf("Controllers: %i\n",Header.controllers);
// todo: enable this many controllers, check force_manual
printf("Input Samples: %i\n",Header.input_samples);
l_TotalSamples = Header.input_samples;
printf("Start Type: %s\n", (Header.start_type == 1 ? "Savestate" : "Start"));
if (Header.start_type == 1) {
// TODO: look for .st of the same file name
}
//TODO: check 020 4-byte unsigned int: controller flags
printf("ROM Name: %s\n",Header.rom_name);
printf("ROM CRC: %i\n",Header.rom_crc);
printf("ROM CC: %i\n",Header.rom_cc);
//TODO: plugin checking
return 1;
}
// taken from mupen64rerecording v8
void InputToString ()
{
// input display
l_InputDisplay[0] = '\0';
{
BOOL a, b, z, l, r, s, cl, cu, cr, cd, dl, du, dr, dd;
signed char x, y;
dr = (l_LastInput & (0x0001)) != 0;
dl = (l_LastInput & (0x0002)) != 0;
dd = (l_LastInput & (0x0004)) != 0;
du = (l_LastInput & (0x0008)) != 0;
s = (l_LastInput & (0x0010)) != 0; // start button
z = (l_LastInput & (0x0020)) != 0;
b = (l_LastInput & (0x0040)) != 0;
a = (l_LastInput & (0x0080)) != 0;
cr = (l_LastInput & (0x0100)) != 0;
cl = (l_LastInput & (0x0200)) != 0;
cd = (l_LastInput & (0x0400)) != 0;
cu = (l_LastInput & (0x0800)) != 0;
r = (l_LastInput & (0x1000)) != 0;
l = (l_LastInput & (0x2000)) != 0;
x = ((l_LastInput & (0x00FF0000)) >> 16);
y = ((l_LastInput & (0xFF000000)) >> 24);
if(!x && !y) {
strcpy(l_InputDisplay, "");
} else {
int xamt = (x<0?-x:x) * 99/127; if(!xamt && x) xamt = 1;
int yamt = (y<0?-y:y) * 99/127; if(!yamt && y) yamt = 1;
if(x && y) {
sprintf(l_InputDisplay, "%c%d %c%d ", x<0?'<':'>', xamt, y<0?'v':'^', yamt);
} else if(x) {
sprintf(l_InputDisplay, "%c%d ", x<0?'<':'>', xamt);
} else { //if(y)
sprintf(l_InputDisplay, "%c%d ", y<0?'v':'^', yamt);
}
}
if(s) strcat(l_InputDisplay, "S");
if(z) strcat(l_InputDisplay, "Z");
if(a) strcat(l_InputDisplay, "A");
if(b) strcat(l_InputDisplay, "B");
if(l) strcat(l_InputDisplay, "L");
if(r) strcat(l_InputDisplay, "R");
if(cu||cd||cl||cr) {
strcat(l_InputDisplay, " C");
if(cu) strcat(l_InputDisplay, "^");
if(cd) strcat(l_InputDisplay, "v");
if(cl) strcat(l_InputDisplay, "<");
if(cr) strcat(l_InputDisplay, ">");
}
if(du||dd||dl||dr) {
strcat(l_InputDisplay, " D");
if(du) strcat(l_InputDisplay, "^");
if(dd) strcat(l_InputDisplay, "v");
if(dl) strcat(l_InputDisplay, "<");
if(dr) strcat(l_InputDisplay, ">");
}
}
}
void _StartROM()
{
l_CurrentSample = 0;
l_CurrentVI = 0;
g_UseSaveData = 1;
}
void EndPlaybackAndRecording()
{
g_UseSaveData = 1;
if (g_EmulatorRecording) {
savestates_select_filename(progress_file);
savestates_job |= SAVESTATE; // Save a in progress savestate for easy resume.
Header.total_vi = (unsigned int) l_CurrentVI;
Header.rerecord_count ++;
Header.fps = fpsByCountrycode();
Header.input_samples = (unsigned int) l_TotalSamples;
fseek(RecordingFile, 0L, SEEK_SET);
if (!fwrite(&Header, MUP_HEADER_SIZE, 1, RecordingFile) == 1) {
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Failed to update header in .m64 file."));
}
fseek(RecordingFile, 0L, SEEK_END);
fclose(RecordingFile);
g_EmulatorRecording = 0;
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Recording Ended."));
l_TotalSamples = 0;
l_CurrentSample = 0;
l_CurrentVI = 0;
}
if (g_EmulatorPlayback) {
fclose(PlaybackFile);
g_EmulatorPlayback = 0;
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Playback Ended."));
if (g_ReadOnlyPlayback == 0) {
ResumeEof(playback_file);
l_TotalSamples = Header.input_samples;
l_CurrentSample = 0; // TODO
l_CurrentVI = Header.total_vi;
} else {
l_TotalSamples = 0;
l_CurrentSample = 0;
l_CurrentVI = 0;
}
}
}
void _NewVI()
{
l_CurrentVI++;
}
void _GetKeys( int Control, BUTTONS *Keys )
{
// Since we handle input here now, we always want to start with getKeys.
getKeys(Control, Keys);
if (g_EmulatorPlayback) {
// hack: assume only 1 controller
if (Control == 0) {
if (!fread(Keys,sizeof(BUTTONS),1,PlaybackFile) == 1) {
EndPlaybackAndRecording();
}
} else {
memset(Keys,0,sizeof(BUTTONS));
}
// it doesn't matter if there isn't 4 controllers plugged in,
// our sample stops at this point.
// TODO: Not working properly.
// Handled by the 'if (length == 0)' above ...
if (Control == 3) {
l_CurrentSample++;
if (l_CurrentSample > l_TotalSamples) {
EndPlaybackAndRecording();
}
}
// read keys from file.
}
if (g_EmulatorRecording) {
if (!fwrite(Keys,sizeof(BUTTONS),1,RecordingFile) == 1) {
main_message(1, 1, 1, OSD_BOTTOM_LEFT, tr("Failed to write keypress"));
}
}
// print out the data of the controller input
if (Control == 0) {
memcpy(&l_LastInput,&Keys->Value,sizeof(int));
InputToString();
if ((Keys->Value != 0) && (g_EmulatorPlayback || g_EmulatorRecording)) {
l_TotalSamples ++;
main_message(1, 1, 1, OSD_TOP_LEFT, "%s", l_InputDisplay);
}
}
}
void CleanUpSaveFiles ()
{
char *filename;
filename = malloc(strlen(get_savespath())+strlen("CurrentPlayback")+4+1);
sprintf(filename,"%sCurrentPlayback.sra",get_savespath());
remove(filename);
sprintf(filename,"%sCurrentPlayback.eep",get_savespath());
remove(filename);
sprintf(filename,"%sCurrentPlayback.fla",get_savespath());
remove(filename);
sprintf(filename,"%sCurrentPlayback.mpk",get_savespath());
remove(filename);
free(filename);
}
char* getCtrlStrInternal(int controller)
{
char *result;
result = malloc(32);
if (Controls[controller].Present) {
sprintf(result,"Present");
if(Controls[controller].Plugin == PLUGIN_MEMPAK) {
strcat(result, " with mempak");
}
if(Controls[controller].Plugin == PLUGIN_RUMBLE_PAK) {
strcat(result, " with rumble");
}
} else {
sprintf(result,"Disconnected");
}
return result;
}
char* getCtrlStrHeader(int controller, unsigned int controller_flags)
{
char *result;
result = malloc(32);
if (controller_flags & (CONTROLLER_PRESENT << controller)) {
sprintf(result,"Present");
if (controller_flags & (CONTROLLER_MEMPACK << controller)) {
strcat(result," with mempak");
}
if (controller_flags & (CONTROLLER_RUMBLEPACK << controller)) {
strcat(result," with rumble pak");
}
} else {
sprintf(result,"Disconnected");
}
return result;
}
char* getGfxName()
{
char *result;
result = plugin_name_by_filename(config_get_string("Gfx Plugin", ""));
return result;
}
char* getInputName()
{
char *result;
result = plugin_name_by_filename(config_get_string("Input Plugin", ""));
return result;
}
char* getAudioName()
{
char *result;
result = plugin_name_by_filename(config_get_string("Audio Plugin", ""));
return result;
}
char* getRspName()
{
char *result;
result = plugin_name_by_filename(config_get_string("RSP Plugin", ""));
return result;
}
int fpsByCountrycode()
{
switch(ROM_HEADER->Country_code&0xFF)
{
case 0x44:
case 0x46:
case 0x49:
case 0x50:
case 0x53:
case 0x55:
case 0x58:
case 0x59:
return 25;
break;
case 0x37:
case 0x41:
case 0x45:
case 0x4a:
return 30;
break;
}
printf( "[VCR]: Warning - unknown country code, using 30 FPS for video.\n" );
return 30;
}