activegs-ios/kegs/Src/SaveState.cpp

1017 lines
20 KiB
C++
Raw Blame History

/*
ActiveGS, Copyright 2004-2016 Olivier Goguel, https://github.com/ogoguel/ActiveGS
Based on Kegs, Copyright 2004 Kent Dickey, https://kegs.sourceforge.net
This code is covered by the GNU GPL licence
*/
#ifdef _WIN32
# define _WINSOCKAPI_
#endif
#include "StdString.h"
#include "defc.h"
#include "SaveState.h"
#include "adb.h"
#include "async_event.h"
#include "clock.h"
#include "iwm.h"
#include "moremem.h"
#include "paddles.h"
#include "scc.h"
#include "sim65816.h"
#include "sound.h"
#include "video.h"
#include "compression.h"
#undef debug_printf
#define debug_printf(X,...)
s_savestate g_savestate;
void x_sleep(int _ms)
{
#ifdef WIN32
Sleep(_ms);
#else
usleep(_ms);
#endif
}
int savedState::maxSize=0;
savedState::savedState()
{
param.vbl=0;
}
void savedState::release()
{
if (isEmpty()) return ;
adb.release();
async_event.release();
clock.release();
iwm.release();
moremem.release();
paddles.release();
scc.release();
sim65816.release();
sound.release();
video.release();
offscreen.release();
param.vbl = 0;
}
void serialize::release()
{
g_compression.remove_job(this);
if (rledata)
{
x_free(rledata,rlesize,fastalloc);
rledata = NULL;
}
if (data)
{
x_free(data,size,fastalloc);
data=NULL;
}
}
void savedState::save(int _fullsave, int _usefastalloc)
{
#if (!defined(MAC) || defined(ACTIVEGSPLUGIN) ) && !defined(ACTIVEGS_NOSAVESTATE)
release();
fastalloc = _usefastalloc;
int size = 0;
param.dcycs = g_sim65816.g_last_vbl_dcycs;
param.vbl = g_sim65816.g_vbl_count;
param.memory_size = g_sim65816.g_mem_size_total ;
param.rom = 0 ;
debug_printf("capturing (%d) vbl:%d dcycs:%f last_vbl_dcys:%f\n",_fullsave,vbl,dcycs,g_sim65816.g_last_vbl_dcycs);
fullsave = _fullsave;
if (_fullsave)
{
size += g_adb.out(&adb,fastalloc);
size += g_async_event.out(&async_event,fastalloc);
size += g_clock.out(&clock,fastalloc);
size += g_iwm.out(&iwm,fastalloc);
size += g_moremem.out(&moremem,fastalloc);
size += g_paddles.out(&paddles,fastalloc);
size += g_scc.out(&scc,fastalloc);
size += g_sim65816.out(&sim65816,fastalloc);
size += g_sound.out(&sound,fastalloc);
size += g_video.out(&video,fastalloc);
}
#ifdef VIDEO_SINGLEVLINE
#define STEP 1
#else
#define STEP 2
#endif
// sauvegarde l'offscreen (1 ligne sur 2)
int wb = g_kimage_offscreen.width_act * ( g_kimage_offscreen.mdepth >> 3 );
offscreen.size = wb * g_kimage_offscreen.height / STEP ;
offscreen.fastalloc = fastalloc;
offscreen.data = (byte*)x_malloc( offscreen.size,fastalloc );
if (!offscreen.data)
x_fatal_exit("out of memory error!");
byte* ptrs = (byte*)g_kimage_offscreen.data_ptr;
byte* ptrd = (byte*)offscreen.data;
for(int h=0;h<g_kimage_offscreen.height;h+=STEP)
{
memcpy(ptrd,ptrs,wb);
ptrd+=wb;
ptrs+=wb*STEP;
}
size += offscreen.size;
if (maxSize<size)
maxSize=size;
// g_compression.add_job(&offscreen);
#endif
}
extern void refresh_offscreen();
void savedState::display()
{
if (isEmpty()) return ;
if (offscreen.data==NULL)
{
refresh_offscreen();
refresh_video(1);
return ;
}
// static double lastcycs=0.0;
extern void drawText(Kimage* _dst, const char* text,int _x,int _y);
#if !defined(MAC) || defined(ACTIVEGSPLUGIN)
offscreen.expand();
// g<>n<EFBFBD>re une kimage temporaire
Kimage temp;
init_kimage(&temp,0,s_video.g_screen_depth, s_video.g_screen_mdepth);
temp.width_req = X_A2_WINDOW_WIDTH;
temp.width_act = X_A2_WINDOW_WIDTH;
temp.height = X_A2_WINDOW_HEIGHT;
int pb = temp.mdepth >> 3;
temp.data_ptr = (byte*)malloc(X_A2_WINDOW_WIDTH*X_A2_WINDOW_HEIGHT*pb);
#ifdef VIDEO_SINGLEVLINE
// restore l'offscreen <20> l'identique
int wb = temp.width_act * pb;
byte* ptrs = (byte*)offscreen.data;
byte* ptrd = (byte*)temp.data_ptr;
for(int h=0;h<g_kimage_offscreen.height;h++)
{
memcpy(ptrd,ptrs,wb);
ptrd+=wb;
ptrs+=wb;
}
#else
// restore l'offscreen (1 ligne sur 2 dupliqu<71>e)
int wb = temp.width_act * pb;
byte* ptrs = (byte*)offscreen.data;
byte* ptrd = (byte*)temp.data_ptr;
for(int h=0;h<g_kimage_offscreen.height;h+=2)
{
memcpy(ptrd,ptrs,wb);
ptrd+=wb;
memcpy(ptrd,ptrs,wb);
ptrd+=wb;
ptrs+=wb;
}
#endif
// push l'image => pour profiter des effets post prod
x_push_kimage(&temp,0,0,0,0,temp.width_req,temp.height);
offscreen.releaseuncompressed();
// lib<69>re le fichier temp
free(temp.data_ptr );
refresh_video(1);
// double dtime = get_dtime();
// double delay = dtime - lastcycs;
// lastcycs = dtime;
// debug_printf("displaying frame:%d (%f)\n",vbl,delay);
#endif
}
void s_savestate::handleKey(int _key,int _isup)
{
#if (!defined(MAC) || defined(ACTIVEGSPLUGIN)) && !defined(ACTIVEGS_NOSAVESTATE)
if (!_isup && (_key == 0x3B || _key == 0x3C || _key == 0x24 /*RET*/|| _key == 0x60 /*VK_F5*/ ) )
{
if (_key == 0x24 || _key == 0x60)
{
r_sim65816.set_state(RUNNING);
return ;
}
int next ;
if (iCurState==-1)
{
// TODO A AMELIORER
iCurState = (MAX_STATE+iNextState -1 )%MAX_STATE;
}
if (_key==0x3B)
{
targetStateSens = 0;
// v<>rifie qu'il y a encore une clef
int next = (MAX_STATE+iCurState -1 )%MAX_STATE;
while(next != iNextState )
{
if (states[next].isEmpty())
// plus de clef
break;
if (states[next].fullsave)
{
targetStateSens = -1;
break;
}
next = (MAX_STATE+next -1 )%MAX_STATE;
}
if (!targetStateSens)
printf("begin rewind\n");
}
else
{
targetStateSens = +1;
next = (MAX_STATE+iCurState +1 )%MAX_STATE;
if (next == iNextState || states[next].isEmpty() )
{
targetStateSens = 0;
iCurState=-1;
printf("end rewind\n");
return ;
}
}
}
#endif
}
int s_savestate::getSavedStateVBL()
{
return state.param.vbl;
}
void s_savestate::saveState(const char* _filename)
{
if (r_sim65816.get_state() ==IN_PAUSE)
{
// en pause, sauve imm<6D>diatement (sauf si on est en mode rewind
if (iCurState!=-1)
{
printf("cannot save state if rewind has started");
return ;
}
if (_filename==NULL)
{
// save to memory
state.save(1,0);
debug_printf("save state @%d \n",state->vbl);
}
else
{
savedState temp;
temp.save(1,0);
temp.writeToDisk(_filename);
}
return ;
}
stateActionRequired=STATE_SAVE;
filename = _filename;
}
void s_savestate::restoreState(const char* _filename)
{
if (r_sim65816.get_state() ==IN_PAUSE)
{
// en pause, on peut faire le restore imm<6D>diatement (si on est pas en mode rewind)
if (iCurState!=-1)
{
printf("cannot restore state if rewind has started");
return ;
}
if (_filename==NULL)
{
if (!state.isEmpty())
{
// on est revenu <20> une position quelconque : le rewind n'a plus de sens
state.restore();
reset_rewind();
}
}
else
{
savedState temp;
int ret = temp.loadFromDisk(_filename);
if (ret)
{
temp.restore();
reset_rewind();
}
}
return ;
}
// pr<70>pare le save <20> la prochaine boucle
stateActionRequired=STATE_RESTORE;
filename = _filename;
}
void s_savestate::delete_state(int pos)
{
if (states[pos].isEmpty()) return ;
states[pos].release();
// d<>truit les <20>tat pr<70>sents jusqu'<27> la prochaine clef
int deleteState=(pos +1 )%MAX_STATE;
while(deleteState!=pos)
{
if (states[deleteState].isEmpty()) break;
if (states[deleteState].fullsave)
// on a trouv<75> la prochaine clef : on s'arrete
break;
// d<>truit les <20>cran interm<72>diaires
states[deleteState].release();
debug_printf("deleting state:%d\n",deleteState);
deleteState=(deleteState +1 )%MAX_STATE;
}
}
void s_savestate::handleState()
{
// double d = get_dtime();
#if (!defined(MAC) || defined(ACTIVEGSPLUGIN)) && !defined(ACTIVEGS_NOSAVESTATE)
if (r_sim65816.should_emulator_terminate())
{
printf("ignoring handleState : emulator is quitting...\n");
return ;
}
iCurState = -1;
int b = r_sim65816.get_rewind_enable();
int savescreen = ( b && g_sim65816.g_vbl_count >= nextScreenVBL ) ;
int s = r_sim65816.get_state();
int fullsave = b && ( g_sim65816.g_vbl_count>=nextStateVBL || s==IN_PAUSE) ;
if (savescreen||fullsave)
{
nextScreenVBL = g_sim65816.g_vbl_count + VBL_DELAY_BETWEEN_SCREEN ;
delete_state(iNextState);
// s'assure que l'on a assez de m<>moire libre
int n = (iNextState +1 )%MAX_STATE;
while( 1)
{
int f = get_free_memory_size();
if ( f> (int)savedState::maxSize*1.20 )
break;
if (!states[n].isEmpty())
{
debug_printf("only %dKB remaining : trying to free space\n",f/1024);
delete_state(n);
debug_printf("now %dKB!\n",get_free_memory_size()/1204);
}
n = (n +1 )%MAX_STATE;
if (n == iNextState)
{
x_fatal_exit("err: out of memory error");
}
}
// capture l'<27>tat courant
states[iNextState].save(fullsave,1);
// positionne la prochaine place libre
iNextState = (iNextState +1 )%MAX_STATE;
if (fullsave)
nextStateVBL = g_sim65816.g_vbl_count+NB_SCREEN_PER_STATE*VBL_DELAY_BETWEEN_SCREEN;
}
else
g_compression.process_jobs(1);
switch( stateActionRequired)
{
case STATE_SAVE:
// if (filename.IsEmpty())
if (filename[0]==0)
{
state.save(1,0);
debug_printf("save state @ \n",state->vbl);
}
else
{
savedState temp;
temp.save(1,0);
temp.writeToDisk(filename);
}
stateActionRequired = STATE_NONE;
break;
case STATE_RESTORE:
// if (filename.IsEmpty())
if (filename[0]==0)
{
if (!state.isEmpty())
{
// on est revenu <20> une position quelconque : le rewind n'a plus de sens
state.restore();
reset_rewind();
}
}
else
{
savedState temp;
int ret = temp.loadFromDisk(filename);
if (ret)
{
temp.restore();
reset_rewind();
}
}
stateActionRequired = STATE_NONE;
break ;
case STATE_NONE:
// if (r_sim65816.get_state() == IN_PAUSE)
if (s == PAUSE_REQUESTED ) // !!!! L'ETAT PEUT AVOIR CHANGE DEPUIS LE DEBUT DE LA FONCTION : NE LE RELIT PAS!
{
r_sim65816.set_state(IN_PAUSE);
printf("in pause (cache=%dMB, maxStateSize=%dMB)\n",cacheSize/(1024*1024),savedState::maxSize/(1024*1024));
while(r_sim65816.get_state() == IN_PAUSE || ( iCurState!=-1 && !states[iCurState].isEmpty() && !states[iCurState].fullsave ) )
{
if (r_sim65816.should_emulator_terminate())
{
printf("aborting PAUSE mode...\n");
r_sim65816.set_state(RUNNING);
return;
}
x_sleep(1); // 50hz
if (targetStateSens)
{
double dtime = get_dtime();
double delaysincelastrefresh = dtime - lastrefreshcycs;
if (delaysincelastrefresh > VBL_DELAY_BETWEEN_SCREEN/2/60.0)
{
if (targetStateSens>0)
iCurState = (iCurState +1 )%MAX_STATE;
else
iCurState = (MAX_STATE+iCurState -1 )%MAX_STATE;
states[iCurState].display();
if (states[iCurState].fullsave)
targetStateSens = 0;
else
lastrefreshcycs = dtime;
}
}
}
printf("out pause\n");
if (iCurState!=-1)
{
if (!states[iCurState].isEmpty())
states[iCurState].restore();
int n = (iCurState+1)%MAX_STATE;
while(n!=iNextState)
{
delete_state(n);
n = ( n + 1 ) % MAX_STATE;
}
iNextState = (iCurState+1)%MAX_STATE;
nextScreenVBL = g_sim65816.g_vbl_count + VBL_DELAY_BETWEEN_SCREEN;
nextStateVBL = g_sim65816.g_vbl_count + NB_SCREEN_PER_STATE*VBL_DELAY_BETWEEN_SCREEN;
}
}
break;
}
#endif
// double delay = get_dtime() - d;
// g_graph.add_graph(1,delay,g_sim65816.g_vbl_count);
}
#define CHECK_RESTORE_VAR(CLASS,VAR) \
if ( g_##CLASS.VAR != ((s_##CLASS*)(##CLASS.data))->##VAR) \
printf("serialization error for var "#CLASS"."#VAR" %d vs. %d",g_##CLASS.VAR,((s_##CLASS*)(##CLASS.data))->##VAR);
#define CHECK_RESTORE_CLASS(CLASS) \
if ( sizeof(s_##CLASS) != ((s_##CLASS*)(##CLASS.data))->_size) \
printf("serialization error for class "#CLASS" %d vs. %d\n",sizeof(s_##CLASS),((s_##CLASS*)(##CLASS.data))->_size );
#define SAVE_VERSION 0x01020304
void serialize::save(const char*_name,void* _fileptr)
{
FILE* f= (FILE*)_fileptr;
fwrite(_name,1,4,f);
word32 version = SAVE_VERSION;
fwrite(&version,1,sizeof(version),f);
fwrite(&size,1,sizeof(size),f);
fwrite(data,1,size,f);
}
int serialize::load(const char*_name,void* _fileptr)
{
char name[5];
memset(name,0,5);
FILE* f= (FILE*)_fileptr;
fread(name,1,4,f);
if (strcmp(name,_name))
{
printf("#%s save state name mismatch(%s vs %s)\n",_name,_name,name);
return 0;
}
word32 version = 0;
fread(&version,1,sizeof(version),f);
if (version != SAVE_VERSION)
{
printf("#%s save state version mismatch(%X vs %X)\n",_name,version,SAVE_VERSION);
return 0;
}
int nbread = fread(&size,1,sizeof(size),f);
if (nbread !=4)
{
printf("#%s save state size mismatch(%d)\n",_name,size);
return 0;
}
fastalloc = 0;
data = (void*)malloc(size);
nbread = fread(data,1,size,f);
if (nbread!=size)
{
printf("#%s save state data mismatch(%d vs %d)\n",_name,nbread,size);
return 0;
}
return 1;
}
void savedState::writeToDisk(const char* _filename)
{
if (isEmpty())
{
printf("nothing to save");
return ;
}
if (_filename==NULL)
{
printf("no filename");
return ;
}
FILE* f= fopen(_filename,"wb");
if (f==NULL)
{
printf("cannot create file");
return ;
}
fwrite(&param,1,sizeof(savedStateParam),f);
adb.save("adb ",f);
async_event.save("asyn",f);
clock.save("cloc",f);
iwm.save("iwm ",f);
moremem.save("more",f);
paddles.save("padd",f);
scc.save("scc ",f);
sim65816.save("sim6",f);
sound.save("soun",f);
video.save("vide",f);
// offscreen.save("offs",f); ne sauve pas l'offscreen
fclose(f);
}
int savedState::loadFromDisk(const char* _filename)
{
release();
int ret = loadFromDiskInternal(_filename);
if (!ret)
release();
return ret;
}
int savedState::loadFromDiskInternal(const char* _filename)
{
FILE* f= fopen(_filename,"rb");
if (f==NULL)
{
printf("cannot load %s\n",_filename);
return 0;
}
int nbread = fread(&param,1,sizeof(savedStateParam),f);
if (nbread!=sizeof(savedStateParam))
{
printf("could not read savedStateParam\n");
return 0;
}
if (param.size != sizeof(savedStateParam))
{
printf("alignement mismatch\n");
return 0;
}
if (param.version != SAVEDSTATE_SIG)
{
printf("invalid sig\n");
return 0;
}
int ret = adb.load("adb ",f);
if (ret==0)
return 0;
ret = async_event.load("asyn",f);
if (ret==0)
return 0;
clock.load("cloc",f);
if (ret==0)
return 0;
iwm.load("iwm ",f);
if (ret==0)
return 0;
moremem.load("more",f);
if (ret==0)
return 0;
paddles.load("padd",f);
if (ret==0)
return 0;
scc.load("scc ",f);
if (ret==0)
return 0;
sim65816.load("sim6",f);
if (ret==0)
return 0;
sound.load("soun",f);
if (ret==0)
return 0;
video.load("vide",f);
if (ret==0)
return 0;
offscreen.release();
/*
offscreen.load("offs",f);
if (ret==0)
return 0;
*/
return 1;
}
void savedState::restore()
{
//writeToDisk("d:\\temp\\test.bin");
if (isEmpty())
{
printf("nothing to restore!\n");
return ;
}
#if (!defined(MAC) || defined(ACTIVEGSPLUGIN)) && !defined(ACTIVEGS_NOSAVESTATE)
debug_printf("current state :vbl:%d dcycs:%f last_vbl_dcys:%f\n",g_sim65816.g_vbl_count,g_sim65816.g_cur_dcycs,g_sim65816.g_last_vbl_dcycs);
#ifdef WIN32
CHECK_RESTORE_CLASS(sim65816);
CHECK_RESTORE_VAR(sim65816,g_mem_size_total);
#endif
g_adb.in(&adb);
g_async_event.in(&async_event);
g_clock.in(&clock);
g_iwm.in(&iwm);
g_moremem.in(&moremem);
g_paddles.in(&paddles);
g_scc.in(&scc);
g_sim65816.in(&sim65816);
g_sound.in(&sound);
g_video.in(&video);
r_sim65816.request_update_emulator_runtime_config();
display();
printf("restoring vbl:%d dcycs:%f last_vbl_dcys:%f\n",param.vbl,param.dcycs,g_sim65816.g_last_vbl_dcycs);
#endif
}
void s_savestate::init()
{
#ifdef WIN32
MEMORYSTATUS memStatus = {0};
::GlobalMemoryStatus(&memStatus);
DWORD dwTotalPhys =memStatus.dwTotalPhys;
printf("Total physical memory: %u MB\n", dwTotalPhys / (1024*1024) );
#endif
reset_rewind();
stateActionRequired=STATE_NONE;
state.release();
#ifdef CACHE_SIZE
cache = (byte*)malloc(CACHE_SIZE);
printf("Nb State = %d\n",MAX_STATE);
printf("Max replay = %fs\n",(float)MAX_STATE/60.0);
printf("Key duration = %fs\n",(float)(NB_SCREEN_PER_STATE*VBL_DELAY_BETWEEN_SCREEN)/60.0);
#else
cache=NULL;
#endif
cachefree = 0;
cachepos = 0;
cacheSize= 0;
}
void* s_savestate::x_free(void* _data,int _size, int _fastalloc)
{
if (!cache || !_fastalloc)
{
free(_data);
return NULL;
}
int pos = (intptr_t)_data - (intptr_t)cache;
// xxxx#######xxFREEE....POS#####
//printf("freeing :%d (size:%d)\n",pos,_size);
// POSxxxxxx######xxxxFREEE
if (cachepos<=pos || cachepos>cachefree)
{
cachepos = pos + _size;
// printf("newpos:%d\n",cachepos);
}
cacheSize -= _size;
return NULL;
}
int s_savestate::get_free_memory_size()
{
#ifdef CACHE_SIZE
int p = cachepos;
int f = cachefree;
if (f>=p)
// #####POS.....FREE#######
return MAX(p,CACHE_SIZE-f);
else
// ....FREE#####POS...
return p-f;
#else
return 0;
#endif
}
void* x_free(void* _data,int _size, int _fastalloc)
{
return g_savestate.x_free(_data,_size,_fastalloc);
}
void* s_savestate::x_malloc(int _size, int _fastalloc)
{
if (!cache || !_fastalloc)
{
void* p = (void*)malloc(_size);
if (!p)
x_fatal_exit("Out of Memory Error : relaunch with rewind mode off");
cacheSize+=_size;
return p;
}
#ifndef CACHE_SIZE
// ne doit jamais arriver
return NULL;
#else
int c = cachefree;
if (c >= cachepos)
{
// ......POS#####################FREE.........
if (c+_size > CACHE_SIZE)
{
// on boucle
c = 0;
// FREEE......POS#####################00000000
if (c+_size > cachepos)
{
printf("x_malloc : out of memory error\n");
return NULL;
}
}
else
{
// ok
}
}
else
{
// ######FREE......POS##########################
if (c+_size < cachepos)
{
// il y a de la place entre free & pos = on l'utilise
// ok
}
else
{
printf("x_malloc: out of memory error\n");
return NULL;
}
}
cachefree = c+_size;
cacheSize += _size;
// printf("alloc size:%d ptr:%d (cachepos:%d cachefree:%d)\n",_size,c,cachepos,cachefree);
return &cache[c];
#endif
}
void* x_malloc(int _size, int _fastalloc)
{
return g_savestate.x_malloc(_size,_fastalloc);
}
void* x_realloc(void* ptr,int _newsize, int _formersize)
{
printf("x_realloc not implemented\n");
return ptr;
/*
void*p =(void*)x_malloc(_newsize);
memcpy(p,ptr,_newsize);
x_free(ptr,_formersize);
return p;
*/
}
void s_savestate::reset_state()
{
state.release();
}
void s_savestate::reset_rewind()
{
printf("reset_rewind\n");
for(int i=0;i<MAX_STATE;i++)
states[i].release();
// reset mempory pointers
cachefree=0;
cachepos=0;
iNextState = 0;
// reset recording vbl
nextScreenVBL = 0;
nextStateVBL = 0;
}
void s_savestate::shut()
{
reset_rewind();
state.release();
if (cache)
{
free(cache);
cache=NULL;
}
}
s_savestate::s_savestate()
{
stateActionRequired = 0;
iNextState = 0;
iCurState = 0 ;
nextStateVBL= 0;
nextScreenVBL = 0;
targetStateSens = 0;
lastrefreshcycs = 0.0;
cache = NULL;
cachepos = 0;
cachefree = 0;
cacheSize = 0;
}
s_savestate::~s_savestate()
{
}