mirror of
https://github.com/OpenEmu/Nestopia-Core.git
synced 2025-11-01 11:09:19 +00:00
618 lines
12 KiB
C++
618 lines
12 KiB
C++
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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;
|
|
}
|
|
}
|
|
}
|