/core/NstTrackerMovie.cpp
C++ | 617 lines | 476 code | 118 blank | 23 comment | 90 complexity | b020f229f5668496a97e02d09d09d0d6 MD5 | raw file
- ////////////////////////////////////////////////////////////////////////////////////////
- //
- // Nestopia - NES/Famicom emulator written in C++
- //
- // Copyright (C) 2003-2008 Martin Freij
- //
- // This file is part of Nestopia.
- //
- // Nestopia 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; either version 2 of the License, or
- // (at your option) any later version.
- //
- // Nestopia 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 Nestopia; if not, write to the Free Software
- // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- //
- ////////////////////////////////////////////////////////////////////////////////////////
-
- #include <new>
- #include <iostream>
- #include "NstMachine.hpp"
- #include "NstState.hpp"
- #include "NstTrackerMovie.hpp"
- #include "NstZlib.hpp"
- #include "api/NstApiMovie.hpp"
- #include "api/NstApiUser.hpp"
-
- namespace Nes
- {
- namespace Core
- {
- #ifdef NST_MSVC_OPTIMIZE
- #pragma optimize("s", on)
- #endif
-
- class Tracker::Movie::Player
- {
- public:
-
- ~Player();
-
- void Relink();
-
- private:
-
- static dword Validate(State::Loader&,const Cpu&,dword,bool);
-
- enum
- {
- MAX_BUFFER_MASK = 0xFFFFFF,
- OPEN_BUS = 0x40
- };
-
- struct Buffer : Vector<byte>
- {
- dword pos;
-
- Buffer()
- : pos(0) {}
- };
-
- struct Loader : State::Loader
- {
- explicit Loader(std::istream& s)
- : State::Loader(&s,false) {}
-
- bool operator == (std::istream& s) const
- {
- return stream == &s;
- }
- };
-
- NES_DECL_PEEK( Port );
- NES_DECL_POKE( Port );
-
- const Io::Port* ports[2];
- dword frame;
- Buffer buffers[2];
- Loader state;
- Cpu& cpu;
-
- public:
-
- static dword Validate(std::istream& stream,const Cpu& cpu,dword prgCrc)
- {
- Loader state( stream );
- return Validate( state, cpu, prgCrc, true );
- }
-
- Player(std::istream& stream,Cpu& c,const dword prgCrc)
- : frame(0), state(stream), cpu(c)
- {
- Validate( state, cpu, prgCrc, false );
- Relink();
- }
-
- bool operator == (std::istream& stream) const
- {
- return state == stream;
- }
-
- void Stop()
- {
- state.End();
- }
-
- bool Execute(Machine& emulator,EmuLoadState loadState)
- {
- NST_ASSERT( loadState );
-
- if (buffers[0].pos > buffers[0].Size() || buffers[1].pos > buffers[1].Size())
- throw RESULT_ERR_CORRUPT_FILE;
-
- if (frame)
- {
- --frame;
- }
- else for (;;)
- {
- NST_VERIFY( buffers[0].pos == buffers[0].Size() && buffers[1].pos == buffers[1].Size() );
-
- const dword chunk = state.Begin();
-
- if (chunk == AsciiId<'K','E','Y'>::V)
- {
- for (uint i=0; i < 2; ++i)
- {
- buffers[i].pos = 0;
- buffers[i].Clear();
- }
-
- while (const dword subChunk = state.Begin())
- {
- switch (subChunk)
- {
- case AsciiId<'S','A','V'>::V:
-
- (emulator.*loadState)( state, false );
- break;
-
- case AsciiId<'P','T','0'>::V:
- case AsciiId<'P','T','1'>::V:
- {
- const uint i = (subChunk == AsciiId<'P','T','1'>::V);
-
- buffers[i].Resize( state.Read32() & MAX_BUFFER_MASK );
- state.Uncompress( buffers[i].Begin(), buffers[i].Size() );
- break;
- }
-
- case AsciiId<'L','E','N'>::V:
-
- frame = state.Read32();
- NST_VERIFY( frame <= 0xFFFFF );
- break;
- }
-
- state.End();
- }
-
- state.End();
- break;
- }
- else if (chunk)
- {
- state.End();
- }
- else
- {
- return false;
- }
- }
-
- return true;
- }
- };
-
- Tracker::Movie::Player::~Player()
- {
- for (uint i=0; i < 2; ++i)
- cpu.Unlink( 0x4016 + i, this, &Player::Peek_Port, &Player::Poke_Port );
- }
-
- dword Tracker::Movie::Player::Validate(State::Loader& state,const Cpu& cpu,const dword prgCrc,const bool end)
- {
- if (state.Begin() != (AsciiId<'N','S','V'>::V | 0x1AUL << 24))
- throw RESULT_ERR_INVALID_FILE;
-
- const dword length = state.Length();
-
- Region region = REGION_NTSC;
- dword crc = 0;
-
- while (const dword chunk = state.Check())
- {
- if (chunk == AsciiId<'P','A','L'>::V)
- {
- state.Begin();
- region = REGION_PAL;
- state.End();
- }
- else if (chunk == AsciiId<'C','R','C'>::V)
- {
- state.Begin();
- crc = state.Read32();
- state.End();
- }
- else if (chunk & 0xFFFFFF00)
- {
- break;
- }
- else
- {
- throw RESULT_ERR_UNSUPPORTED_FILE_VERSION;
- }
- }
-
- if (end)
- state.End( length );
-
- if (region != cpu.GetRegion())
- throw RESULT_ERR_WRONG_MODE;
-
- if (crc && prgCrc && crc != prgCrc && Api::User::questionCallback( Api::User::QUESTION_NSV_PRG_CRC_FAIL_CONTINUE ) == Api::User::ANSWER_NO)
- throw RESULT_ERR_INVALID_CRC;
-
- return length;
- }
-
- void Tracker::Movie::Player::Relink()
- {
- for (uint i=0; i < 2; ++i)
- ports[i] = cpu.Link( 0x4016 + i, Cpu::LEVEL_HIGHEST, this, &Player::Peek_Port, &Player::Poke_Port );
- }
-
- #ifdef NST_MSVC_OPTIMIZE
- #pragma optimize("", on)
- #endif
-
- NES_PEEK_A(Tracker::Movie::Player,Port)
- {
- address &= 0x1;
- const uint pos = buffers[address].pos++;
-
- if (pos < buffers[address].Size())
- {
- return buffers[address][pos];
- }
- else
- {
- NST_DEBUG_MSG("buffer >> data failed!");
- return OPEN_BUS;
- }
- }
-
- NES_POKE_AD(Tracker::Movie::Player,Port)
- {
- ports[address & 0x1]->Poke( address, data );
- }
-
- #ifdef NST_MSVC_OPTIMIZE
- #pragma optimize("s", on)
- #endif
-
- class Tracker::Movie::Recorder
- {
- public:
-
- ~Recorder();
-
- void Relink();
-
- private:
-
- void BeginKey(Machine&,EmuSaveState);
- void EndKey();
-
- enum
- {
- BAD_FRAME = dword(~0UL),
- MAX_BUFFER_BLOCK = SIZE_1K * 8192UL
- };
-
- typedef Vector<byte> Buffer;
-
- struct Saver : State::Saver
- {
- Saver(std::ostream& s,dword a)
- : State::Saver(&s,true,true,a) {}
-
- bool operator == (std::ostream& s) const
- {
- return stream == &s;
- }
- };
-
- NES_DECL_PEEK( Port );
- NES_DECL_POKE( Port );
-
- const Io::Port* ports[2];
- ibool resync;
- dword frame;
- Buffer buffers[2];
- Saver state;
- Cpu& cpu;
-
- public:
-
- Recorder(std::iostream& stream,Cpu& c,const dword prgCrc,const bool append)
- : resync(true), frame(0), state(stream,append ? Player::Validate(stream,c,prgCrc) : 0), cpu(c)
- {
- if (!append)
- {
- state.Begin( AsciiId<'N','S','V'>::V | 0x1AUL << 24 );
-
- if (cpu.GetRegion() == REGION_PAL)
- state.Begin( AsciiId<'P','A','L'>::V ).End();
-
- if (prgCrc)
- state.Begin( AsciiId<'C','R','C'>::V ).Write32( prgCrc ).End();
- }
-
- Relink();
- }
-
- bool operator == (std::ostream& stream) const
- {
- return state == stream;
- }
-
- void Resync()
- {
- resync = true;
- }
-
- void Stop()
- {
- EndKey();
-
- state.End();
- }
-
- void Execute(Machine& machine,EmuSaveState saveState)
- {
- NST_ASSERT( saveState );
-
- if (frame == BAD_FRAME)
- throw RESULT_ERR_OUT_OF_MEMORY;
-
- if (resync || buffers[0].Size() >= MAX_BUFFER_BLOCK || buffers[1].Size() >= MAX_BUFFER_BLOCK)
- {
- EndKey();
- BeginKey( machine, saveState );
- }
-
- ++frame;
- }
- };
-
- Tracker::Movie::Recorder::~Recorder()
- {
- for (uint i=0; i < 2; ++i)
- cpu.Unlink( 0x4016 + i, this, &Recorder::Peek_Port, &Recorder::Poke_Port );
- }
-
- void Tracker::Movie::Recorder::Relink()
- {
- for (uint i=0; i < 2; ++i)
- ports[i] = cpu.Link( 0x4016 + i, Cpu::LEVEL_HIGHEST, this, &Recorder::Peek_Port, &Recorder::Poke_Port );
- }
-
- void Tracker::Movie::Recorder::BeginKey(Machine& machine,EmuSaveState saveState)
- {
- state.Begin( AsciiId<'K','E','Y'>::V );
-
- if (resync)
- {
- resync = false;
-
- state.Begin( AsciiId<'S','A','V'>::V );
- (machine.*saveState)( state );
- state.End();
- }
- }
-
- void Tracker::Movie::Recorder::EndKey()
- {
- if (frame == BAD_FRAME)
- throw RESULT_ERR_OUT_OF_MEMORY;
-
- if (frame)
- {
- state.Begin( AsciiId<'L','E','N'>::V ).Write32( frame-1 ).End();
- frame = 0;
-
- for (uint i=0; i < 2; ++i)
- {
- if (buffers[i].Size())
- {
- state.Begin( AsciiId<'P','T','0'>::R(0,0,i) ).Write32( buffers[i].Size() ).Compress( buffers[i].Begin(), buffers[i].Size() ).End();
- buffers[i].Clear();
- }
- }
-
- state.End();
- }
- }
-
- #ifdef NST_MSVC_OPTIMIZE
- #pragma optimize("", on)
- #endif
-
- NES_PEEK_A(Tracker::Movie::Recorder,Port)
- {
- const uint data = ports[address & 0x1]->Peek( address );
-
- if (frame != BAD_FRAME)
- {
- try
- {
- buffers[address & 0x1].Append( data );
- }
- catch (...)
- {
- frame = BAD_FRAME;
- }
- }
-
- return data;
- }
-
- NES_POKE_AD(Tracker::Movie::Recorder,Port)
- {
- ports[address & 0x1]->Poke( address, data );
- }
-
- #ifdef NST_MSVC_OPTIMIZE
- #pragma optimize("s", on)
- #endif
-
- Tracker::Movie::Movie(Machine& e,EmuLoadState l,EmuSaveState s,Cpu& c,dword crc)
- :
- player (NULL),
- recorder (NULL),
- emulator (e),
- saveState (s),
- loadState (l),
- cpu (c),
- prgCrc (crc)
- {
- }
-
- Tracker::Movie::~Movie()
- {
- Stop();
- }
-
- bool Tracker::Movie::Record(std::iostream& stream,const bool append)
- {
- if (!Zlib::AVAILABLE)
- throw RESULT_ERR_UNSUPPORTED;
-
- if (player)
- throw RESULT_ERR_NOT_READY;
-
- if (recorder && *recorder == stream)
- return false;
-
- Stop();
-
- recorder = new Recorder( stream, cpu, prgCrc, append );
-
- Api::Movie::eventCallback( Api::Movie::EVENT_RECORDING );
-
- return true;
- }
-
- bool Tracker::Movie::Play(std::istream& stream)
- {
- if (!Zlib::AVAILABLE)
- throw RESULT_ERR_UNSUPPORTED;
-
- if (recorder)
- throw RESULT_ERR_NOT_READY;
-
- if (player && *player == stream)
- return false;
-
- Stop();
-
- player = new Player( stream, cpu, prgCrc );
-
- Api::Movie::eventCallback( Api::Movie::EVENT_PLAYING );
-
- return true;
- }
-
- void Tracker::Movie::Stop()
- {
- Stop( RESULT_OK );
- }
-
- bool Tracker::Movie::Stop(Result result)
- {
- if (recorder || player)
- {
- if (NES_SUCCEEDED(result))
- {
- try
- {
- if (recorder)
- recorder->Stop();
- else
- player->Stop();
- }
- catch (Result r)
- {
- result = r;
- }
- catch (const std::bad_alloc&)
- {
- result = RESULT_ERR_OUT_OF_MEMORY;
- }
- catch (...)
- {
- result = RESULT_ERR_GENERIC;
- }
- }
-
- if (recorder)
- {
- delete recorder;
- recorder = NULL;
-
- Api::Movie::eventCallback( Api::Movie::EVENT_RECORDING_STOPPED, result );
- }
- else
- {
- delete player;
- player = NULL;
-
- Api::Movie::eventCallback( Api::Movie::EVENT_PLAYING_STOPPED, result );
-
- if (NES_FAILED(result))
- return false;
- }
- }
-
- return true;
- }
-
- void Tracker::Movie::Reset()
- {
- if (recorder)
- {
- recorder->Relink();
- }
- else if (player)
- {
- player->Relink();
- }
-
- Resync();
- }
-
- void Tracker::Movie::Resync()
- {
- if (recorder)
- recorder->Resync();
- }
-
- #ifdef NST_MSVC_OPTIMIZE
- #pragma optimize("", on)
- #endif
-
- bool Tracker::Movie::Execute()
- {
- Result result = RESULT_OK;
-
- try
- {
- if (recorder)
- {
- recorder->Execute( emulator, saveState );
- return true;
- }
- else if (player && player->Execute( emulator, loadState ))
- {
- return true;
- }
- }
- catch (Result r)
- {
- result = r;
- }
- catch (const std::bad_alloc&)
- {
- result = RESULT_ERR_OUT_OF_MEMORY;
- }
- catch (...)
- {
- result = RESULT_ERR_GENERIC;
- }
-
- if (!Stop( result ))
- throw result;
-
- return false;
- }
- }
- }