/* Copyright (C) 1997-2007 ZSNES Team ( zsKnight, _Demo_, pagefault, Nach ) http://www.zsnes.com http://sourceforge.net/projects/zsnes https://zsnes.bountysource.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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 for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef __UNIXSDL__ #include "gblhdr.h" #define DIR_SLASH "/" #else #include #include #include #include #include #include #ifdef __WIN32__ #include #else #include #endif #define DIR_SLASH "\\" #endif #include #include "gblvars.h" #include "asm_call.h" #include "zpath.h" #include "cfg.h" #include "zmovie.h" #include "chips/dsp4emu.h" #include "input.h" #define NUMCONV_FR3 #define NUMCONV_FW3 #include "numconv.h" #ifdef __MSDOS__ #define clim() __asm__ __volatile__ ("cli"); #define stim() __asm__ __volatile__ ("sti"); #else #define clim() #define stim() #endif void SA1UpdateDPageC(), unpackfunct(), repackfunct(); void PrepareOffset(), ResetOffset(), initpitch(), UpdateBanksSDD1(); void procexecloop(), outofmemory(); extern unsigned char cacheud, ccud, intrset, cycpl, cycphb, xdbt, xpbt, xp; extern unsigned char xe, xirqb, debugger, curnmi; extern unsigned short curypos, stackand, stackor, xat, xst, xdt, xxt, xyt, xpc; extern unsigned int Curtableaddr, cycpblt; static void copy_snes_data(unsigned char **buffer, void (*copy_func)(unsigned char **, void *, size_t)) { //65816 status, etc. copy_func(buffer, &curcyc, 1); copy_func(buffer, &curypos, 2); copy_func(buffer, &cacheud, 1); copy_func(buffer, &ccud, 1); copy_func(buffer, &intrset, 1); copy_func(buffer, &cycpl, 1); copy_func(buffer, &cycphb, 1); copy_func(buffer, &spcon, 1); copy_func(buffer, &stackand, 2); copy_func(buffer, &stackor, 2); copy_func(buffer, &xat, 2); copy_func(buffer, &xdbt, 1); copy_func(buffer, &xpbt, 1); copy_func(buffer, &xst, 2); copy_func(buffer, &xdt, 2); copy_func(buffer, &xxt, 2); copy_func(buffer, &xyt, 2); copy_func(buffer, &xp, 1); copy_func(buffer, &xe, 1); copy_func(buffer, &xpc, 2); copy_func(buffer, &xirqb, 1); copy_func(buffer, &debugger, 1); copy_func(buffer, &Curtableaddr, 4); copy_func(buffer, &curnmi, 1); //SPC Timers copy_func(buffer, &cycpbl, 4); copy_func(buffer, &cycpblt, 4); //SNES PPU Register status copy_func(buffer, &sndrot, 3019); } static void copy_spc_data(unsigned char **buffer, void (*copy_func)(unsigned char **, void *, size_t)) { //SPC stuff, DSP stuff copy_func(buffer, SPCRAM, PHspcsave); copy_func(buffer, &BRRBuffer, PHdspsave); copy_func(buffer, &DSPMem, sizeof(DSPMem)); } static void copy_extra_data(unsigned char **buffer, void (*copy_func)(unsigned char **, void *, size_t)) { copy_func(buffer, &soundcycleft, 4); copy_func(buffer, &curexecstate, 4); copy_func(buffer, &nmiprevaddrl, 4); copy_func(buffer, &nmiprevaddrh, 4); copy_func(buffer, &nmirept, 4); copy_func(buffer, &nmiprevline, 4); copy_func(buffer, &nmistatus, 4); copy_func(buffer, &joycontren, 4); copy_func(buffer, &NextLineCache, 1); copy_func(buffer, &spc700read, 10*4); copy_func(buffer, &timer2upd, 4); copy_func(buffer, &xa, 14*4); copy_func(buffer, &spcnumread, 1); copy_func(buffer, &opcd, 6*4); copy_func(buffer, &HIRQCycNext, 4); copy_func(buffer, &HIRQNextExe, 1); copy_func(buffer, &oamaddr, 14*4); copy_func(buffer, &prevoamptr, 1); } static size_t load_save_size; enum copy_state_method { csm_save_zst_new, csm_load_zst_new, csm_load_zst_old, csm_save_rewind, csm_load_rewind }; extern unsigned char *SPC7110PackPtr; static void copy_state_data(unsigned char *buffer, void (*copy_func)(unsigned char **, void *, size_t), enum copy_state_method method) { copy_snes_data(&buffer, copy_func); //WRAM (128k), VRAM (64k) copy_func(&buffer, wramdata, 8192*16); copy_func(&buffer, vram, 4096*16); if (spcon) { copy_spc_data(&buffer, copy_func); /* if (buffer) //Rewind stuff { copy_func(&buffer, &echoon0, PHdspsave2); } */ } if (C4Enable) { copy_func(&buffer, C4Ram, 2048*4); } if (SFXEnable) { copy_func(&buffer, sfxramdata, 8192*16); copy_func(&buffer, &SfxR0, PHnum2writesfxreg); } if (SA1Enable) { copy_func(&buffer, &SA1Mode, PHnum2writesa1reg); copy_func(&buffer, SA1RAMArea, 8192*16); if (method != csm_load_zst_old) { copy_func(&buffer, &SA1Status, 3); copy_func(&buffer, &SA1xpc, 1*4); copy_func(&buffer, &sa1dmaptr, 2*4); } } if (DSP1Enable && (method != csm_load_zst_old)) { copy_func(&buffer, &DSP1COp, 70+128); copy_func(&buffer, &Op00Multiplicand, 3*4+128); copy_func(&buffer, &Op10Coefficient, 4*4+128); copy_func(&buffer, &Op04Angle, 4*4+128); copy_func(&buffer, &Op08X, 5*4+128); copy_func(&buffer, &Op18X, 5*4+128); copy_func(&buffer, &Op28X, 4*4+128); copy_func(&buffer, &Op0CA, 5*4+128); copy_func(&buffer, &Op02FX, 11*4+3*4+28*8+128); copy_func(&buffer, &Op0AVS, 5*4+14*8+128); copy_func(&buffer, &Op06X, 6*4+10*8+4+128); copy_func(&buffer, &Op01m, 4*4+128); copy_func(&buffer, &Op0DX, 6*4+128); copy_func(&buffer, &Op03F, 6*4+128); copy_func(&buffer, &Op14Zr, 9*4+128); copy_func(&buffer, &Op0EH, 4*4+128); } if (SETAEnable) { copy_func(&buffer, setaramdata, 256*16); //Todo: copy the SetaCmdEnable? For completeness we should do it //but currently we ignore it anyway. } if (SPC7110Enable) { copy_func(&buffer, SPC7110PackPtr, 65536); copy_func(&buffer, &SPCMultA, PHnum2writespc7110reg); } if (DSP4Enable) { copy_func(&buffer, &DSP4.waiting4command, sizeof(DSP4.waiting4command)); copy_func(&buffer, &DSP4.half_command, sizeof(DSP4.half_command)); copy_func(&buffer, &DSP4.command, sizeof(DSP4.command)); copy_func(&buffer, &DSP4.in_count, sizeof(DSP4.in_count)); copy_func(&buffer, &DSP4.in_index, sizeof(DSP4.in_index)); copy_func(&buffer, &DSP4.out_count, sizeof(DSP4.out_count)); copy_func(&buffer, &DSP4.out_index, sizeof(DSP4.out_index)); copy_func(&buffer, &DSP4.parameters, sizeof(DSP4.parameters)); copy_func(&buffer, &DSP4.output, sizeof(DSP4.output)); copy_func(&buffer, &DSP4_vars.DSP4_Logic, sizeof(DSP4_vars.DSP4_Logic)); copy_func(&buffer, &DSP4_vars.lcv, sizeof(DSP4_vars.lcv)); copy_func(&buffer, &DSP4_vars.distance, sizeof(DSP4_vars.distance)); copy_func(&buffer, &DSP4_vars.raster, sizeof(DSP4_vars.raster)); copy_func(&buffer, &DSP4_vars.segments, sizeof(DSP4_vars.segments)); copy_func(&buffer, &DSP4_vars.world_x, sizeof(DSP4_vars.world_x)); copy_func(&buffer, &DSP4_vars.world_y, sizeof(DSP4_vars.world_y)); copy_func(&buffer, &DSP4_vars.world_dx, sizeof(DSP4_vars.world_dx)); copy_func(&buffer, &DSP4_vars.world_dy, sizeof(DSP4_vars.world_dy)); copy_func(&buffer, &DSP4_vars.world_ddx, sizeof(DSP4_vars.world_ddx)); copy_func(&buffer, &DSP4_vars.world_ddy, sizeof(DSP4_vars.world_ddy)); copy_func(&buffer, &DSP4_vars.world_xenv, sizeof(DSP4_vars.world_xenv)); copy_func(&buffer, &DSP4_vars.world_yofs, sizeof(DSP4_vars.world_yofs)); copy_func(&buffer, &DSP4_vars.view_x1, sizeof(DSP4_vars.view_x1)); copy_func(&buffer, &DSP4_vars.view_y1, sizeof(DSP4_vars.view_y1)); copy_func(&buffer, &DSP4_vars.view_x2, sizeof(DSP4_vars.view_x2)); copy_func(&buffer, &DSP4_vars.view_y2, sizeof(DSP4_vars.view_y2)); copy_func(&buffer, &DSP4_vars.view_dx, sizeof(DSP4_vars.view_dx)); copy_func(&buffer, &DSP4_vars.view_dy, sizeof(DSP4_vars.view_dy)); copy_func(&buffer, &DSP4_vars.view_xofs1, sizeof(DSP4_vars.view_xofs1)); copy_func(&buffer, &DSP4_vars.view_yofs1, sizeof(DSP4_vars.view_yofs1)); copy_func(&buffer, &DSP4_vars.view_xofs2, sizeof(DSP4_vars.view_xofs2)); copy_func(&buffer, &DSP4_vars.view_yofs2, sizeof(DSP4_vars.view_yofs2)); copy_func(&buffer, &DSP4_vars.view_yofsenv, sizeof(DSP4_vars.view_yofsenv)); copy_func(&buffer, &DSP4_vars.view_turnoff_x, sizeof(DSP4_vars.view_turnoff_x)); copy_func(&buffer, &DSP4_vars.view_turnoff_dx, sizeof(DSP4_vars.view_turnoff_dx)); copy_func(&buffer, &DSP4_vars.viewport_cx, sizeof(DSP4_vars.viewport_cx)); copy_func(&buffer, &DSP4_vars.viewport_cy, sizeof(DSP4_vars.viewport_cy)); copy_func(&buffer, &DSP4_vars.viewport_left, sizeof(DSP4_vars.viewport_left)); copy_func(&buffer, &DSP4_vars.viewport_right, sizeof(DSP4_vars.viewport_right)); copy_func(&buffer, &DSP4_vars.viewport_top, sizeof(DSP4_vars.viewport_top)); copy_func(&buffer, &DSP4_vars.viewport_bottom, sizeof(DSP4_vars.viewport_bottom)); copy_func(&buffer, &DSP4_vars.sprite_x, sizeof(DSP4_vars.sprite_x)); copy_func(&buffer, &DSP4_vars.sprite_y, sizeof(DSP4_vars.sprite_y)); copy_func(&buffer, &DSP4_vars.sprite_attr, sizeof(DSP4_vars.sprite_attr)); copy_func(&buffer, &DSP4_vars.sprite_size, sizeof(DSP4_vars.sprite_size)); copy_func(&buffer, &DSP4_vars.sprite_clipy, sizeof(DSP4_vars.sprite_clipy)); copy_func(&buffer, &DSP4_vars.sprite_count, sizeof(DSP4_vars.sprite_count)); copy_func(&buffer, &DSP4_vars.poly_clipLf, sizeof(DSP4_vars.poly_clipLf)); copy_func(&buffer, &DSP4_vars.poly_clipRt, sizeof(DSP4_vars.poly_clipRt)); copy_func(&buffer, &DSP4_vars.poly_ptr, sizeof(DSP4_vars.poly_ptr)); copy_func(&buffer, &DSP4_vars.poly_raster, sizeof(DSP4_vars.poly_raster)); copy_func(&buffer, &DSP4_vars.poly_top, sizeof(DSP4_vars.poly_top)); copy_func(&buffer, &DSP4_vars.poly_bottom, sizeof(DSP4_vars.poly_bottom)); copy_func(&buffer, &DSP4_vars.poly_cx, sizeof(DSP4_vars.poly_cx)); copy_func(&buffer, &DSP4_vars.poly_start, sizeof(DSP4_vars.poly_start)); copy_func(&buffer, &DSP4_vars.poly_plane, sizeof(DSP4_vars.poly_plane)); copy_func(&buffer, &DSP4_vars.OAM_attr, sizeof(DSP4_vars.OAM_attr)); copy_func(&buffer, &DSP4_vars.OAM_index, sizeof(DSP4_vars.OAM_index)); copy_func(&buffer, &DSP4_vars.OAM_bits, sizeof(DSP4_vars.OAM_bits)); copy_func(&buffer, &DSP4_vars.OAM_RowMax, sizeof(DSP4_vars.OAM_RowMax)); copy_func(&buffer, &DSP4_vars.OAM_Row, sizeof(DSP4_vars.OAM_Row)); } if (method != csm_load_zst_old) { copy_extra_data(&buffer, copy_func); //We don't load SRAM from new states if box isn't checked if ((method != csm_load_zst_new) || SRAMState) { copy_func(&buffer, sram, ramsize); } if ((method == csm_save_rewind) || (method == csm_load_rewind)) { copy_func(&buffer, &tempesi, 4); copy_func(&buffer, &tempedi, 4); copy_func(&buffer, &tempedx, 4); copy_func(&buffer, &tempebp, 4); } } } static void memcpyinc(unsigned char **dest, void *src, size_t len) { memcpy(*dest, src, len); *dest += len; } static void memcpyrinc(unsigned char **src, void *dest, size_t len) { memcpy(dest, *src, len); *src += len; } extern unsigned int RewindTimer, DblRewTimer; extern unsigned char EMUPause; unsigned char *StateBackup = 0; unsigned char AllocatedRewindStates, LatestRewindPos, EarliestRewindPos; bool RewindPosPassed; size_t rewind_state_size, cur_zst_size, old_zst_size; extern unsigned char romispal; void zmv_rewind_save(size_t, bool); void zmv_rewind_load(size_t, bool); void ClearCacheCheck() { memset(vidmemch2, 1, sizeof(vidmemch2)); memset(vidmemch4, 1, sizeof(vidmemch4)); memset(vidmemch8, 1, sizeof(vidmemch8)); } //Code to handle special frames for pausing, and desync checking unsigned char *SpecialPauseBackup = 0, PauseFrameMode = 0; /* Pause frame modes 0 - no pause frame stored 1 - pause frame ready to be stored 2 - pause frame stored 3 - pause frame ready for reload */ void BackupPauseFrame() { if (SpecialPauseBackup) { copy_state_data(SpecialPauseBackup, memcpyinc, csm_save_rewind); PauseFrameMode = 2; } } void RestorePauseFrame() { if (SpecialPauseBackup) { copy_state_data(SpecialPauseBackup, memcpyrinc, csm_load_rewind); //ClearCacheCheck(); PauseFrameMode = 0; } } void DeallocPauseFrame() { if (SpecialPauseBackup) { free(SpecialPauseBackup); } } #define ActualRewindFrames (unsigned int)(RewindFrames * (romispal ? 10 : 12)) void BackupCVFrame() { unsigned char *RewindBufferPos = StateBackup + LatestRewindPos*rewind_state_size; if (MovieProcessing == MOVIE_PLAYBACK) { zmv_rewind_save(LatestRewindPos, true); } else if (MovieProcessing == MOVIE_RECORD) { zmv_rewind_save(LatestRewindPos, false); } copy_state_data(RewindBufferPos, memcpyinc, csm_save_rewind); if (RewindPosPassed) { EarliestRewindPos = (EarliestRewindPos+1)%AllocatedRewindStates; RewindPosPassed = false; } // printf("Backing up in #%u, earliest: #%u, allocated: %u\n", LatestRewindPos, EarliestRewindPos, AllocatedRewindStates); LatestRewindPos = (LatestRewindPos+1)%AllocatedRewindStates; if (LatestRewindPos == EarliestRewindPos) { RewindPosPassed = true; } RewindTimer = ActualRewindFrames; DblRewTimer += (DblRewTimer) ? 0 : ActualRewindFrames; // printf("New backup slot: #%u, timer %u, check %u\n", LatestRewindPos, RewindTimer, DblRewTimer); } void RestoreCVFrame() { unsigned char *RewindBufferPos; if (LatestRewindPos != ((EarliestRewindPos+1)%AllocatedRewindStates)) { if (DblRewTimer > ActualRewindFrames) { if (LatestRewindPos == 1 || AllocatedRewindStates == 1) { LatestRewindPos = AllocatedRewindStates-1; } else { LatestRewindPos = (LatestRewindPos) ? LatestRewindPos-2 : AllocatedRewindStates-2; } } else { LatestRewindPos = (LatestRewindPos) ? LatestRewindPos-1 : AllocatedRewindStates-1; } } else { LatestRewindPos = EarliestRewindPos; } RewindBufferPos = StateBackup + LatestRewindPos*rewind_state_size; //printf("Restoring from #%u, earliest: #%u\n", LatestRewindPos, EarliestRewindPos); if (MovieProcessing == MOVIE_RECORD) { zmv_rewind_load(LatestRewindPos, false); } else { if (MovieProcessing == MOVIE_PLAYBACK) { zmv_rewind_load(LatestRewindPos, true); } if (PauseRewind || EMUPause) { PauseFrameMode = EMUPause = true; } } copy_state_data(RewindBufferPos, memcpyrinc, csm_load_rewind); ClearCacheCheck(); LatestRewindPos = (LatestRewindPos+1)%AllocatedRewindStates; RewindTimer = ActualRewindFrames; DblRewTimer = 2*ActualRewindFrames; } void SetupRewindBuffer() { //For special rewind case to help out pauses DeallocPauseFrame(); SpecialPauseBackup = malloc(rewind_state_size); //For standard rewinds if (StateBackup) { free(StateBackup); } for (; RewindStates; RewindStates--) { StateBackup = 0; StateBackup = (unsigned char *)malloc(rewind_state_size*RewindStates); if (StateBackup) { break; } } AllocatedRewindStates = RewindStates; } void DeallocRewindBuffer() { if (StateBackup) { free(StateBackup); } } static size_t state_size; static void state_size_tally(unsigned char **dest, void *src, size_t len) { state_size += len; } void InitRewindVars() { unsigned char almost_useless_array[1]; //An array is needed for copy_state_data to give the correct size state_size = 0; copy_state_data(almost_useless_array, state_size_tally, csm_save_rewind); rewind_state_size = state_size; SetupRewindBuffer(); LatestRewindPos = 0; EarliestRewindPos = 0; RewindPosPassed = false; RewindTimer = 1; DblRewTimer = 1; } void InitRewindVarsForMovie() { LatestRewindPos = 0; EarliestRewindPos = 0; RewindPosPassed = false; RewindTimer = 1; DblRewTimer = 1; } //This is used to preserve system load state between game loads static unsigned char *BackupSystemBuffer = 0; void BackupSystemVars() { unsigned char *buffer; if (!BackupSystemBuffer) { state_size = 0; copy_snes_data(&buffer, state_size_tally); copy_spc_data(&buffer, state_size_tally); copy_extra_data(&buffer, state_size_tally); BackupSystemBuffer = (unsigned char *)malloc(state_size); } if (BackupSystemBuffer) { buffer = BackupSystemBuffer; copy_snes_data(&buffer, memcpyinc); copy_spc_data(&buffer, memcpyinc); copy_extra_data(&buffer, memcpyinc); } } void RestoreSystemVars() { if (BackupSystemBuffer) { unsigned char *buffer = BackupSystemBuffer; InitRewindVars(); copy_snes_data(&buffer, memcpyrinc); copy_spc_data(&buffer, memcpyrinc); copy_extra_data(&buffer, memcpyrinc); } } void DeallocSystemVars() { if (BackupSystemBuffer) { free(BackupSystemBuffer); } } extern unsigned int spcBuffera; extern unsigned int Voice0BufPtr, Voice1BufPtr, Voice2BufPtr, Voice3BufPtr; extern unsigned int Voice4BufPtr, Voice5BufPtr, Voice6BufPtr, Voice7BufPtr; extern unsigned int spcPCRam, spcRamDP; void PrepareSaveState() { spcPCRam -= (unsigned int)SPCRAM; spcRamDP -= (unsigned int)SPCRAM; Voice0BufPtr -= spcBuffera; Voice1BufPtr -= spcBuffera; Voice2BufPtr -= spcBuffera; Voice3BufPtr -= spcBuffera; Voice4BufPtr -= spcBuffera; Voice5BufPtr -= spcBuffera; Voice6BufPtr -= spcBuffera; Voice7BufPtr -= spcBuffera; } extern unsigned int SA1Stat; extern unsigned char IRAM[2049], *SA1Ptr, *SA1RegPCS, *CurBWPtr, *SA1BWPtr; extern unsigned char *SNSBWPtr; void SaveSA1() { SA1Stat &= 0xFFFFFF00; SA1Ptr -= (unsigned int)SA1RegPCS; if (SA1RegPCS == IRAM) { SA1Stat = (SA1Stat & 0xFFFFFF00) + 1; } if (SA1RegPCS == IRAM-0x3000) { SA1Stat = (SA1Stat & 0xFFFFFF00) + 2; } SA1RegPCS -= (unsigned int)romdata; CurBWPtr -= (unsigned int)romdata; SA1BWPtr -= (unsigned int)romdata; SNSBWPtr -= (unsigned int)romdata; } void RestoreSA1() { SA1RegPCS += (unsigned int)romdata; CurBWPtr += (unsigned int)romdata; SA1BWPtr += (unsigned int)romdata; SNSBWPtr += (unsigned int)romdata; if ((SA1Stat & 0xFF) == 1) { SA1RegPCS = IRAM; } if ((SA1Stat & 0xFF) == 2) { SA1RegPCS = IRAM-0x3000; } SA1Ptr += (unsigned int)SA1RegPCS; SA1RAMArea = romdata + 4096*1024; } #define ResState(Voice_BufPtr) \ Voice_BufPtr += spcBuffera; \ if (Voice_BufPtr >= spcBuffera + 65536*4) \ { \ Voice_BufPtr = spcBuffera; \ } void ResetState() { spcPCRam += (unsigned int)SPCRAM; spcRamDP += (unsigned int)SPCRAM; ResState(Voice0BufPtr); ResState(Voice1BufPtr); ResState(Voice2BufPtr); ResState(Voice3BufPtr); ResState(Voice4BufPtr); ResState(Voice5BufPtr); ResState(Voice6BufPtr); ResState(Voice7BufPtr); } extern unsigned int SfxRomBuffer, SfxCROM; extern unsigned int SfxLastRamAdr, SfxRAMMem, MsgCount, MessageOn; extern unsigned char AutoIncSaveSlot, cbitmode, NoPictureSave; extern char *Msgptr; extern unsigned short PrevPicture[64*56]; static FILE *fhandle; void CapturePicture(); static void write_save_state_data(unsigned char **dest, void *data, size_t len) { fwrite(data, 1, len, fhandle); } static const char zst_header_old[] = "ZSNES Save State File V0.6\x1a\x3c"; static const char zst_header_cur[] = "ZSNES Save State File V143\x1a\x8f"; void calculate_state_sizes() { state_size = 0; copy_state_data(0, state_size_tally, csm_save_zst_new); cur_zst_size = state_size + sizeof(zst_header_cur)-1; state_size = 0; copy_state_data(0, state_size_tally, csm_load_zst_old); old_zst_size = state_size + sizeof(zst_header_old)-1; } unsigned int current_zst = 0; unsigned int newest_zst = 0; time_t newestfiledate; char *zst_name() { static char buffer[7]; if ((MovieProcessing == MOVIE_PLAYBACK) || (MovieProcessing == MOVIE_RECORD)) { sprintf(buffer, "%.2d.zst", current_zst); return(buffer); } strcpy(buffer, "zst"); if (current_zst) { buffer[2] = (current_zst%10)+'0'; if (current_zst > 9) { buffer[1] = (current_zst/10)+'0'; } } setextension(ZStateName, buffer); return(ZStateName); } void zst_determine_newest() { struct stat filestat; if ((MovieProcessing == MOVIE_PLAYBACK) || (MovieProcessing == MOVIE_RECORD)) { mzt_chdir_up(); } if (!stat_dir(ZSramPath, zst_name(), &filestat) && filestat.st_mtime > newestfiledate) { newestfiledate = filestat.st_mtime; newest_zst = current_zst; } if ((MovieProcessing == MOVIE_PLAYBACK) || (MovieProcessing == MOVIE_RECORD)) { mzt_chdir_down(); } } void zst_init() { if (LatestSave) { for (current_zst = 0; current_zst < 100; current_zst++) { zst_determine_newest(); } current_zst = newest_zst; zst_name(); } } int zst_exists() { int ret; if ((MovieProcessing == MOVIE_PLAYBACK) || (MovieProcessing == MOVIE_RECORD)) { mzt_chdir_up(); } ret = access_dir(ZSramPath, zst_name(), F_OK) ? 0 : 1; if ((MovieProcessing == MOVIE_PLAYBACK) || (MovieProcessing == MOVIE_RECORD)){ mzt_chdir_down(); } return(ret); } static bool zst_save_compressed(FILE *fp) { size_t data_size = cur_zst_size - (sizeof(zst_header_cur)-1); unsigned char *buffer = 0; bool worked = false; if ((buffer = (unsigned char *)malloc(data_size))) { //Compressed buffer which must be at least 0.1% larger than source buffer plus 12 bytes //We devide by 1000 then add an extra 1 as a quick way to get a buffer large enough when //using integer division unsigned long compressed_size = data_size + data_size/1000 + 13; unsigned char *compressed_buffer = 0; if ((compressed_buffer = (unsigned char *)malloc(compressed_size))) { copy_state_data(buffer, memcpyinc, csm_save_zst_new); if (compress2(compressed_buffer, &compressed_size, buffer, data_size, Z_BEST_COMPRESSION) == Z_OK) { fwrite3(compressed_size, fp); fwrite(compressed_buffer, 1, compressed_size, fp); worked = true; } free(compressed_buffer); } free(buffer); } if (!worked) //Compression failed for whatever reason { fwrite3(cur_zst_size | 0x00800000, fp); //Uncompressed ZST will never break 8MB } return(worked); } void zst_save(FILE *fp, bool Thumbnail, bool Compress) { PrepareOffset(); PrepareSaveState(); unpackfunct(); if (SFXEnable) { SfxRomBuffer -= SfxCROM; SfxLastRamAdr -= SfxRAMMem; } if (SA1Enable) { SaveSA1(); //Convert SA-1 stuff to standard, non displacement format } if (!Compress || !zst_save_compressed(fp)) //If we don't want compressed or compression failed { fwrite(zst_header_cur, 1, sizeof(zst_header_cur)-1, fp); //-1 for null fhandle = fp; //Set global file handle copy_state_data(0, write_save_state_data, csm_save_zst_new); if (Thumbnail) { CapturePicture(); fwrite(PrevPicture, 1, 64*56*sizeof(unsigned short), fp); } } if (SFXEnable) { SfxRomBuffer += SfxCROM; SfxLastRamAdr += SfxRAMMem; } if (SA1Enable) { RestoreSA1(); //Convert back SA-1 stuff } ResetOffset(); ResetState(); } /* Merges all the passed strings into buffer. Make sure to pass an extra parameter as 0 after all the strings. Copies at most buffer_len characters. Result is always null terminated. Returns how many bytes are needed to store all strings. Thus if return is <= buffer_len, everything was copied. */ static size_t string_merge(char *buffer, size_t buffer_len, ...) { char *s; size_t copied = 0, needed = 0; va_list ap; va_start(ap, buffer_len); if (buffer && buffer_len) { *buffer = 0; } while ((s = va_arg(ap, char *))) { needed += strlen(s); if (buffer && (copied+1 < buffer_len)) { strncpy(buffer+copied, s, buffer_len-copied); buffer[buffer_len-1] = 0; copied += strlen(buffer+copied); } } va_end(ap); return(needed+1); } static char txtmsg[30]; void set_state_message(char *prefix, char *suffix) { char num[3]; sprintf(num, "%d", current_zst); string_merge(txtmsg, sizeof(txtmsg), prefix, isextension(ZStateName, "zss") ? "AUTO" : num, suffix, 0); Msgptr = txtmsg; MessageOn = MsgCount; } void statesaver() { if (MovieProcessing == MOVIE_RECORD) { //'Auto increment savestate slot' code current_zst += AutoIncSaveSlot; current_zst %= 100; if (mzt_save(current_zst, (cbitmode && !NoPictureSave) ? true : false, false)) { set_state_message("RR STATE ", " SAVED."); } else { current_zst += 100-AutoIncSaveSlot; current_zst %= 100; } return; } if ((MovieProcessing == MOVIE_PLAYBACK) || (MovieProcessing == MOVIE_DUMPING_NEW)) { //'Auto increment savestate slot' code current_zst += AutoIncSaveSlot; current_zst %= 100; if (mzt_save(current_zst, (cbitmode && !NoPictureSave) ? true : false, true)) { set_state_message("RR STATE ", " SAVED."); } else { current_zst += 100-AutoIncSaveSlot; current_zst %= 100; } return; } clim(); //'Auto increment savestate slot' code if(!isextension(ZStateName, "zss")) { current_zst += (char) AutoIncSaveSlot; current_zst %= 100; zst_name(); } if ((fhandle = fopen_dir(ZSramPath, ZStateName, "wb"))) { zst_save(fhandle, (bool)(cbitmode && !NoPictureSave), false); fclose(fhandle); //Display message onscreen, 'STATE XX SAVED.' set_state_message("STATE ", " SAVED."); } else { //Display message onscreen, 'UNABLE TO SAVE.' Msgptr = "UNABLE TO SAVE."; MessageOn = MsgCount; if(!isextension(ZStateName, "zss")) { current_zst += 100-(char) AutoIncSaveSlot; current_zst %= 100; zst_name(); } } stim(); } extern unsigned int Totalbyteloaded, SfxMemTable[256], SfxCPB; extern unsigned int SfxPBR, SfxROMBR, SfxRAMBR, SCBRrel, SfxSCBR; extern unsigned char pressed[256+128+64], multchange, ioportval, SDD1Enable; extern unsigned char nexthdma; static void read_save_state_data(unsigned char **dest, void *data, size_t len) { load_save_size += fread(data, 1, len, fhandle); } static bool zst_load_compressed(FILE *fp, size_t compressed_size) { unsigned long data_size = cur_zst_size - (sizeof(zst_header_cur)-1); unsigned char *buffer = 0; bool worked = false; if ((buffer = (unsigned char *)malloc(data_size))) { unsigned char *compressed_buffer = 0; if ((compressed_buffer = (unsigned char *)malloc(compressed_size))) { fread(compressed_buffer, 1, compressed_size, fp); if (uncompress(buffer, &data_size, compressed_buffer, compressed_size) == Z_OK) { copy_state_data(buffer, memcpyrinc, csm_load_zst_new); worked = true; } free(compressed_buffer); } free(buffer); } return(worked); } bool zst_load(FILE *fp, size_t Compressed) { size_t zst_version = 0; if (Compressed) { if (!zst_load_compressed(fp, Compressed)) { return(false); } } else { char zst_header_check[sizeof(zst_header_cur)-1]; Totalbyteloaded += fread(zst_header_check, 1, sizeof(zst_header_check), fp); if (!memcmp(zst_header_check, zst_header_cur, sizeof(zst_header_check)-2)) { zst_version = 143; //v1.43+ } if (!memcmp(zst_header_check, zst_header_old, sizeof(zst_header_check)-2)) { zst_version = 60; //v0.60 - v1.42 } if (!zst_version) { return(false); } //Pre v0.60 saves are no longer loaded load_save_size = 0; fhandle = fp; //Set global file handle copy_state_data(0, read_save_state_data, (zst_version == 143) ? csm_load_zst_new: csm_load_zst_old ); Totalbyteloaded += load_save_size; } if (SFXEnable) { SfxCPB = SfxMemTable[(SfxPBR & 0xFF)]; SfxCROM = SfxMemTable[(SfxROMBR & 0xFF)]; SfxRAMMem = (unsigned int)sfxramdata + ((SfxRAMBR & 0xFF) << 16); SfxRomBuffer += SfxCROM; SfxLastRamAdr += SfxRAMMem; SCBRrel = (SfxSCBR << 10) + (unsigned int)sfxramdata; } if (SA1Enable) { RestoreSA1(); //Convert back SA-1 stuff SA1UpdateDPageC(); } if (SDD1Enable) { UpdateBanksSDD1(); } //Clear cache check if state loaded ClearCacheCheck(); if (zst_version < 143) //Set new vars which old states did not have { prevoamptr = 0xFF; ioportval = 0xFF; spcnumread = 0; } if (MovieProcessing != MOVIE_RECORD) { nexthdma = 0; } repackfunct(); initpitch(); ResetOffset(); ResetState(); procexecloop(); return(true); } //Wrapper for above bool zst_compressed_loader(FILE *fp) { size_t data_size = fread3(fp); return((data_size & 0x00800000) ? zst_load(fp, 0) : zst_load(fp, data_size)); } #define PH65816regsize 36 void zst_sram_load(FILE *fp) { fseek(fp, sizeof(zst_header_cur)-1 + PH65816regsize + 199635, SEEK_CUR); if (spcon) { fseek(fp, PHspcsave + PHdspsave + sizeof(DSPMem), SEEK_CUR); } if (C4Enable) { fseek(fp, 8192, SEEK_CUR); } if (SFXEnable) { fseek(fp, PHnum2writesfxreg + 131072, SEEK_CUR); } if (SA1Enable) { fseek(fp, PHnum2writesa1reg, SEEK_CUR); fread(SA1RAMArea, 1, 131072, fp); // SA-1 sram fseek(fp, 15, SEEK_CUR); } if (DSP1Enable) { fseek(fp, 2874, SEEK_CUR); } if (SETAEnable) { fread(setaramdata, 1, 4096, fp); } // SETA sram if (SPC7110Enable) { fseek(fp, PHnum2writespc7110reg + 65536, SEEK_CUR); } if (DSP4Enable) {fseek(fp, 1294, SEEK_CUR); } fseek(fp, 220, SEEK_CUR); if (ramsize) { fread(sram, 1, ramsize, fp); } // normal sram } void zst_sram_load_compressed(FILE *fp) { size_t compressed_size = fread3(fp); if (compressed_size & 0x00800000) { zst_sram_load(fp); } else { unsigned long data_size = cur_zst_size - (sizeof(zst_header_cur)-1); unsigned char *buffer = 0; if ((buffer = (unsigned char *)malloc(data_size))) { unsigned char *compressed_buffer = 0; if ((compressed_buffer = (unsigned char *)malloc(compressed_size))) { fread(compressed_buffer, 1, compressed_size, fp); if (uncompress(buffer, &data_size, compressed_buffer, compressed_size) == Z_OK) { unsigned char *data = buffer + PH65816regsize + 199635; if (spcon) { data += PHspcsave + PHdspsave + sizeof(DSPMem); } if (C4Enable) { data += 8192; } if (SFXEnable) { data += PHnum2writesfxreg + 131072; } if (SA1Enable) { data += PHnum2writesa1reg; memcpyrinc(&data, SA1RAMArea, 131072); // SA-1 sram data += 15; } if (DSP1Enable) { data += 2874; } if (SETAEnable) { memcpyrinc(&data, setaramdata, 4096); } // SETA sram if (SPC7110Enable) { data += PHnum2writespc7110reg + 65536; } if (DSP4Enable) { data += 1294; } data += 220; if (ramsize) { memcpyrinc(&data, sram, ramsize); } // normal sram } free(compressed_buffer); } free(buffer); } } } extern unsigned char Voice0Disable, Voice1Disable, Voice2Disable, Voice3Disable; extern unsigned char Voice4Disable, Voice5Disable, Voice6Disable, Voice7Disable; void stateloader(char *statename, bool keycheck, bool xfercheck) { extern unsigned char PauseLoad; if (keycheck) { pressed[1] = 0; pressed[KeyLoadState] = 2; multchange = 1; MessageOn = MsgCount; } if (MZTForceRTR == RTR_REPLAY_TO_RECORD && (MovieProcessing == MOVIE_PLAYBACK)) { MovieRecord(); } else if (MZTForceRTR == RTR_RECORD_TO_REPLAY && (MovieProcessing == MOVIE_RECORD)) { MovieStop(); MoviePlay(); } switch (MovieProcessing) { case MOVIE_PLAYBACK: if (mzt_load(current_zst, true)) { Msgptr = "CHAPTER LOADED."; MessageOn = MsgCount; } else { set_state_message("UNABLE TO LOAD STATE ", "."); } return; case MOVIE_RECORD: if (mzt_load(current_zst, false)) { set_state_message("RR STATE ", " LOADED."); if (PauseLoad || EMUPause) { PauseFrameMode = EMUPause = true; } } else { set_state_message("UNABLE TO LOAD STATE ", "."); } return; case MOVIE_OLD_PLAY: { extern unsigned char CMovieExt; size_t fname_len = strlen(statename); setextension(statename, "zmv"); if (isdigit(CMovieExt)) { statename[fname_len-1] = CMovieExt; } } case MOVIE_ENDING_DUMPING: case MOVIE_DUMPING_NEW: case MOVIE_DUMPING_OLD: return; break; } clim(); if(!isextension(ZStateName, "zss")) { zst_name(); } //Actual state loading code if ((fhandle = fopen_dir(ZSramPath, statename, "rb"))) { if (xfercheck) { Totalbyteloaded = 0; } if (zst_load(fhandle, 0)) { set_state_message("STATE ", " LOADED."); // 'STATE XX LOADED.' if (PauseLoad || EMUPause) { PauseFrameMode = EMUPause = true; } } else { set_state_message("STATE ", " TOO OLD."); // 'STATE X TOO OLD.' - I don't think this is always accurate -Nach } fclose(fhandle); } else { set_state_message("UNABLE TO LOAD STATE ", "."); // 'UNABLE TO LOAD STATE XX.' } Voice0Disable = Voice1Disable = Voice2Disable = Voice3Disable = 1; Voice4Disable = Voice5Disable = Voice6Disable = Voice7Disable = 1; stim(); } void debugloadstate() { stateloader(ZStateName, 0, 0); } void loadstate() { stateloader(ZStateName, 1, 0); } void loadstate2() { stateloader(ZStateName, 0, 1); } void LoadSecondState() { setextension(ZStateName, "zss"); loadstate2(); zst_name(); } void SaveSecondState() { setextension(ZStateName, "zss"); statesaver(); zst_name(); } extern unsigned char CHIPBATT, sramsavedis, *sram2, nosaveSRAM; void SaveCombFile(); // Sram saving void SaveSramData() { extern unsigned int sramb4save; if (*ZSaveName && (!SRAMSave5Sec || sramb4save)) { FILE *fp = 0; unsigned char special = 0; unsigned int *data_to_save; setextension(ZSaveName, "srm"); if (ramsize && !sramsavedis) { if (SFXEnable) { data_to_save=sfxramdata; special = 1; } else if (SA1Enable) { data_to_save = (unsigned int *)SA1RAMArea; special=1; } else if (SETAEnable) { data_to_save = setaramdata; special=1; } else { data_to_save = sram; } if (!special || CHIPBATT) { clim(); if (!nosaveSRAM && (fp = fopen_dir(ZSramPath, ZSaveName,"wb"))) { fwrite(data_to_save, 1, ramsize, fp); fclose(fp); } if (!nosaveSRAM && *ZSaveST2Name && (fp = fopen_dir(ZSramPath, ZSaveST2Name, "wb"))) { fwrite(sram2, 1, ramsize, fp); fclose(fp); } stim(); } } sramb4save = 0; } SaveCombFile(); } extern bool SramExists; void OpenSramFile() { FILE *fp; setextension(ZSaveName, "srm"); if ((fp = fopen_dir(ZSramPath, ZSaveName, "rb"))) { fread(sram, 1, ramsize, fp); fclose(fp); SramExists = true; if (*ZSaveST2Name && (fp = fopen_dir(ZSramPath, ZSaveST2Name, "rb"))) { fread(sram2, 1, ramsize, fp); fclose(fp); } } else { SramExists = false; } } /* SPC File Format - Invented by _Demo_ & zsKnight Cleaned up by Nach 00000h-00020h - File Header : SNES-SPC700 Sound File Data v0.00 (33 bytes) 00021h-00023h - 0x1a,0x1a,0x1a (3 bytes) 00024h - 10 (1 byte) 00025h - PC Register value (1 Word) 00027h - A Register Value (1 byte) 00028h - X Register Value (1 byte) 00029h - Y Register Value (1 byte) 0002Ah - Status Flags Value (1 byte) 0002Bh - Stack Register Value (1 byte) 0002Ch-0002Dh - Reserved (1 byte) 0002Eh-0004Dh - SubTitle/Song Name (32 bytes) 0004Eh-0006Dh - Title of Game (32 bytes) 0006Eh-0007Dh - Name of Dumper (32 bytes) 0007Eh-0009Dh - Comments (32 bytes) 0009Eh-000A1h - Date the SPC was Dumped (4 bytes) 000A2h-000A8h - Reserved (7 bytes) 000A9h-000ACh - Length of SPC in seconds (4 bytes) 000ADh-000AFh - Fade out length in milliseconds (3 bytes) 000B0h-000CFh - Author of Song (32 bytes) 000D0h - Default Channel Disables (0 = enable, 1 = disable) (1 byte) 000D1h - Emulator used to dump .spc file (1 byte) (0 = UNKNOWN, 1 = ZSNES, 2 = SNES9X) (Note : Contact the authors if you're an snes emu author with an .spc capture in order to assign you a number) 000D2h-000FFh - Reserved (46 bytes) 00100h-100FFh - SPCRam (64 KB) 10100h-101FFh - DSPRam (256 bytes) */ extern unsigned char spcextraram[64]; extern unsigned char spcP, spcA, spcX, spcY, spcS, spcNZ; extern unsigned int infoloc; char spcsaved[16]; void savespcdata() { size_t fname_len; unsigned int i = 0; setextension(ZSaveName, "spc"); fname_len = strlen(ZSaveName); while (i < 100) { if (i) { sprintf(ZSaveName-1+fname_len - ((i < 10) ? 0 : 1), "%d", i); } if (access_dir(ZSpcPath, ZSaveName, F_OK)) { break; } i++; } if (i < 100) { FILE *fp = fopen_dir(ZSpcPath, ZSaveName, "wb"); if (fp) { unsigned char ssdatst[256]; time_t t = time(0); struct tm *lt = localtime(&t); //Assemble N/Z flags into P spcP &= 0xFD; if (!spcNZ) { spcP |= 2; } spcP &= 0x7F; if (spcNZ & 0x80) { spcP |= 0x80; } strcpy((char *)ssdatst, "SNES-SPC700 Sound File Data v0.30"); //00000h - File Header : SNES-SPC700 Sound File Data v0.00 ssdatst[0x21] = ssdatst[0x22] = ssdatst[0x23] = 0x1a; //00021h - 0x1a,0x1a,0x1a ssdatst[0x24] = 10; //00024h - 10 *((unsigned short *)(ssdatst+0x25)) = spcPCRam-(unsigned int)SPCRAM; //00025h - PC Register value (1 Word) ssdatst[0x27] = spcA; //00027h - A Register Value (1 byte) ssdatst[0x28] = spcX; //00028h - X Register Value (1 byte) ssdatst[0x29] = spcY; //00029h - Y Register Value (1 byte) ssdatst[0x2A] = spcP; //0002Ah - Status Flags Value (1 byte) ssdatst[0x2B] = spcS; //0002Bh - Stack Register Value (1 byte) ssdatst[0x2C] = 0; //0002Ch - Reserved ssdatst[0x2D] = 0; //0002Dh - Reserved PrepareSaveState(); memset(ssdatst+0x2E, 0, 32); //0002Eh-0004Dh - SubTitle/Song Name memset(ssdatst+0x4E, 0, 32); //0004Eh-0006Dh - Title of Game memcpy(ssdatst+0x4E, ((unsigned char *)romdata)+infoloc, 21); memset(ssdatst+0x6E, 0, 16); //0006Eh-0007Dh - Name of Dumper memset(ssdatst+0x7E, 0, 32); //0007Eh-0009Dh - Comments //0009Eh-000A1h - Date the SPC was Dumped ssdatst[0x9E] = lt->tm_mday; ssdatst[0x9F] = lt->tm_mon+1; ssdatst[0xA0] = (lt->tm_year+1900) & 0xFF; ssdatst[0xA1] = ((lt->tm_year+1900) >> 8) & 0xFF; memset(ssdatst+0xA2, 0, 7); //000A2h-000A8h - Reserved memset(ssdatst+0xA9, 0, 4); //000A9h-000ACh - Length of SPC in seconds memset(ssdatst+0xAD, 0, 3); //000ADh-000AFh - Fade out time in milliseconds memset(ssdatst+0xB0, 0, 32); //000B0h-000CFh - Author of Song //Set Channel Disables ssdatst[0xD0] = 0; //000D0h - Default Channel Disables (0 = enable, 1 = disable) if (Voice0Disable) { ssdatst[0xD0] |= BIT(0); } if (Voice1Disable) { ssdatst[0xD0] |= BIT(1); } if (Voice2Disable) { ssdatst[0xD0] |= BIT(2); } if (Voice3Disable) { ssdatst[0xD0] |= BIT(3); } if (Voice4Disable) { ssdatst[0xD0] |= BIT(4); } if (Voice5Disable) { ssdatst[0xD0] |= BIT(5); } if (Voice6Disable) { ssdatst[0xD0] |= BIT(6); } if (Voice7Disable) { ssdatst[0xD0] |= BIT(7); } ssdatst[0xD1] = 1; //000D1h - Emulator used to dump .spc file memset(ssdatst+0xD2, 0, 46); //000D2h-000FFh - Reserved fwrite(ssdatst, 1, sizeof(ssdatst), fp); fwrite(SPCRAM, 1, 65536, fp); //00100h-100FFh - SPCRam fwrite(DSPMem, 1, 192, fp); //10100h-101FFh - DSPRam fwrite(spcextraram, 1, 64, fp); //Seems DSPRam is split in two, but I don't get what's going on here fclose(fp); ResetState(); sprintf(spcsaved, "%s FILE SAVED.", ZSaveName+fname_len-3); } } } void SaveGameSpecificInput() { if (!*ZSaveName) { psr_cfg_run(write_input_vars, ZCfgPath, "zinput.cfg"); } if (GameSpecificInput && *ZSaveName) { setextension(ZSaveName, "inp"); psr_cfg_run(write_input_vars, ZSramPath, ZSaveName); } } void LoadGameSpecificInput() { if (GameSpecificInput && *ZSaveName) { psr_cfg_run(read_input_vars, ZCfgPath, "zinput.cfg"); setextension(ZSaveName, "inp"); psr_cfg_run(read_input_vars, ZSramPath, ZSaveName); } }