mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
530 lines
11 KiB
C++
530 lines
11 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 "NstInpDevice.hpp"
|
|
#include "NstInpFamilyKeyboard.hpp"
|
|
#include "../NstCpu.hpp"
|
|
#include "../NstHook.hpp"
|
|
#include "../NstFile.hpp"
|
|
#include "../api/NstApiTapeRecorder.hpp"
|
|
|
|
namespace Nes
|
|
{
|
|
namespace Core
|
|
{
|
|
namespace Input
|
|
{
|
|
class FamilyKeyboard::DataRecorder
|
|
{
|
|
public:
|
|
|
|
explicit DataRecorder(Cpu&);
|
|
~DataRecorder();
|
|
|
|
Result Record();
|
|
Result Play();
|
|
|
|
void SaveState(State::Saver&,dword) const;
|
|
void LoadState(State::Loader&);
|
|
|
|
private:
|
|
|
|
NST_NO_INLINE void Start();
|
|
NST_NO_INLINE Result Stop(bool);
|
|
|
|
NES_DECL_HOOK( Tape );
|
|
|
|
enum
|
|
{
|
|
MAX_LENGTH = SIZE_4096K,
|
|
TAPE_CLOCK = 32000
|
|
};
|
|
|
|
enum Status
|
|
{
|
|
STOPPED,
|
|
PLAYING,
|
|
RECORDING
|
|
};
|
|
|
|
qaword cycles;
|
|
Cpu& cpu;
|
|
dword multiplier;
|
|
dword clock;
|
|
Status status;
|
|
Vector<byte> stream;
|
|
dword pos;
|
|
uint in;
|
|
uint out;
|
|
File file;
|
|
|
|
public:
|
|
|
|
bool IsStopped() const
|
|
{
|
|
return status == STOPPED;
|
|
}
|
|
|
|
bool IsRecording() const
|
|
{
|
|
return status == RECORDING;
|
|
}
|
|
|
|
bool IsPlaying() const
|
|
{
|
|
return status == PLAYING;
|
|
}
|
|
|
|
bool Playable() const
|
|
{
|
|
return stream.Size();
|
|
}
|
|
|
|
Result Stop()
|
|
{
|
|
return Stop( false );
|
|
}
|
|
|
|
void Reset()
|
|
{
|
|
clock = 0;
|
|
Stop( false );
|
|
}
|
|
|
|
void Poke(uint data)
|
|
{
|
|
out = data;
|
|
}
|
|
|
|
uint Peek() const
|
|
{
|
|
return in;
|
|
}
|
|
|
|
NST_SINGLE_CALL void EndFrame()
|
|
{
|
|
if (!clock)
|
|
return;
|
|
|
|
if (multiplier)
|
|
{
|
|
const qaword frame = qaword(cpu.GetFrameCycles()) * multiplier;
|
|
NST_VERIFY( cycles >= frame );
|
|
|
|
if (cycles > frame)
|
|
cycles -= frame;
|
|
else
|
|
cycles = 0;
|
|
}
|
|
else
|
|
{
|
|
clock = 0;
|
|
cpu.RemoveHook( Hook(this,&DataRecorder::Hook_Tape) );
|
|
}
|
|
}
|
|
};
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("s", on)
|
|
#endif
|
|
|
|
FamilyKeyboard::FamilyKeyboard(Cpu& c,bool connectDataRecorder)
|
|
:
|
|
Device (c,Api::Input::FAMILYKEYBOARD),
|
|
dataRecorder (connectDataRecorder ? new DataRecorder(c) : NULL)
|
|
{
|
|
FamilyKeyboard::Reset();
|
|
}
|
|
|
|
FamilyKeyboard::DataRecorder::DataRecorder(Cpu& c)
|
|
: cycles(0), cpu(c), multiplier(0), clock(0), status(STOPPED), pos(0), in(0), out(0)
|
|
{
|
|
file.Load( File::TAPE, stream, MAX_LENGTH );
|
|
}
|
|
|
|
FamilyKeyboard::~FamilyKeyboard()
|
|
{
|
|
delete dataRecorder;
|
|
}
|
|
|
|
FamilyKeyboard::DataRecorder::~DataRecorder()
|
|
{
|
|
Stop( true );
|
|
|
|
if (stream.Size())
|
|
file.Save( File::TAPE, stream.Begin(), stream.Size() );
|
|
}
|
|
|
|
void FamilyKeyboard::Reset()
|
|
{
|
|
scan = 0;
|
|
mode = 0;
|
|
|
|
if (dataRecorder)
|
|
dataRecorder->Reset();
|
|
}
|
|
|
|
void FamilyKeyboard::SaveState(State::Saver& saver,const byte id) const
|
|
{
|
|
saver.Begin( AsciiId<'F','B'>::R(0,0,id) );
|
|
saver.Begin( AsciiId<'K','B','D'>::V ).Write8( mode | (scan << 1) ).End();
|
|
|
|
if (dataRecorder)
|
|
dataRecorder->SaveState( saver, AsciiId<'D','T','R'>::V );
|
|
|
|
saver.End();
|
|
}
|
|
|
|
void FamilyKeyboard::DataRecorder::SaveState(State::Saver& state,const dword baseChunk) const
|
|
{
|
|
if (stream.Size() || status != STOPPED)
|
|
{
|
|
state.Begin( baseChunk );
|
|
|
|
if (status == PLAYING)
|
|
{
|
|
state.Begin( AsciiId<'P','L','Y'>::V ).Write32( pos ).Write8( in ).Write32( cycles ).Write32( multiplier ).End();
|
|
}
|
|
else if (status == RECORDING)
|
|
{
|
|
state.Begin( AsciiId<'R','E','C'>::V ).Write8( out ).Write32( cycles ).Write32( multiplier ).End();
|
|
}
|
|
|
|
if (stream.Size())
|
|
state.Begin( AsciiId<'D','A','T'>::V ).Write32( stream.Size() ).Compress( stream.Begin(), stream.Size() ).End();
|
|
|
|
state.End();
|
|
}
|
|
}
|
|
|
|
void FamilyKeyboard::LoadState(State::Loader& loader,const dword id)
|
|
{
|
|
if (dataRecorder)
|
|
dataRecorder->Stop();
|
|
|
|
if (id == AsciiId<'F','B'>::V)
|
|
{
|
|
while (const dword chunk = loader.Begin())
|
|
{
|
|
switch (chunk)
|
|
{
|
|
case AsciiId<'K','B','D'>::V:
|
|
{
|
|
const uint data = loader.Read8();
|
|
|
|
mode = data & 0x1;
|
|
scan = data >> 1 & 0xF;
|
|
|
|
if (scan > 9)
|
|
scan = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case AsciiId<'D','T','R'>::V:
|
|
|
|
NST_VERIFY( dataRecorder );
|
|
|
|
if (dataRecorder)
|
|
dataRecorder->LoadState( loader );
|
|
|
|
break;
|
|
}
|
|
|
|
loader.End();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FamilyKeyboard::DataRecorder::LoadState(State::Loader& state)
|
|
{
|
|
Stop( true );
|
|
|
|
while (const dword chunk = state.Begin())
|
|
{
|
|
switch (chunk)
|
|
{
|
|
case AsciiId<'P','L','Y'>::V:
|
|
|
|
NST_VERIFY( status == STOPPED );
|
|
|
|
if (status == STOPPED)
|
|
{
|
|
status = PLAYING;
|
|
pos = state.Read32();
|
|
in = state.Read8() & 0x2;
|
|
|
|
cycles = state.Read32();
|
|
|
|
if (const dword multiplier = state.Read32())
|
|
cycles = cycles * (cpu.GetClockDivider() * TAPE_CLOCK) / multiplier;
|
|
else
|
|
cycles = 0;
|
|
}
|
|
break;
|
|
|
|
case AsciiId<'R','E','C'>::V:
|
|
|
|
NST_VERIFY( status == STOPPED );
|
|
|
|
if (status == STOPPED)
|
|
{
|
|
status = RECORDING;
|
|
out = state.Read8();
|
|
|
|
cycles = state.Read32();
|
|
|
|
if (const dword multiplier = state.Read32())
|
|
cycles = cycles * (cpu.GetClockDivider() * TAPE_CLOCK) / multiplier;
|
|
else
|
|
cycles = 0;
|
|
}
|
|
break;
|
|
|
|
case AsciiId<'D','A','T'>::V:
|
|
{
|
|
const dword size = state.Read32();
|
|
NST_VERIFY( size > 0 && size <= MAX_LENGTH );
|
|
|
|
if (size > 0 && size <= MAX_LENGTH)
|
|
{
|
|
stream.Resize( size );
|
|
state.Uncompress( stream.Begin(), size );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
state.End();
|
|
}
|
|
|
|
if (status == PLAYING)
|
|
{
|
|
NST_VERIFY( pos < stream.Size() );
|
|
|
|
if (pos < stream.Size())
|
|
{
|
|
Start();
|
|
}
|
|
else
|
|
{
|
|
status = STOPPED;
|
|
cycles = 0;
|
|
pos = 0;
|
|
in = 0;
|
|
}
|
|
}
|
|
else if (status == RECORDING)
|
|
{
|
|
Start();
|
|
}
|
|
}
|
|
|
|
Result FamilyKeyboard::PlayTape()
|
|
{
|
|
return dataRecorder ? dataRecorder->Play() : RESULT_ERR_NOT_READY;
|
|
}
|
|
|
|
Result FamilyKeyboard::RecordTape()
|
|
{
|
|
return dataRecorder ? dataRecorder->Record() : RESULT_ERR_NOT_READY;
|
|
}
|
|
|
|
Result FamilyKeyboard::StopTape()
|
|
{
|
|
return dataRecorder ? dataRecorder->Stop() : RESULT_NOP;
|
|
}
|
|
|
|
bool FamilyKeyboard::IsTapeRecording() const
|
|
{
|
|
return dataRecorder ? dataRecorder->IsRecording() : false;
|
|
}
|
|
|
|
bool FamilyKeyboard::IsTapePlaying() const
|
|
{
|
|
return dataRecorder ? dataRecorder->IsPlaying() : false;
|
|
}
|
|
|
|
bool FamilyKeyboard::IsTapeStopped() const
|
|
{
|
|
return dataRecorder ? dataRecorder->IsStopped() : false;
|
|
}
|
|
|
|
bool FamilyKeyboard::IsTapePlayable() const
|
|
{
|
|
return dataRecorder ? dataRecorder->Playable() : false;
|
|
}
|
|
|
|
Result FamilyKeyboard::DataRecorder::Record()
|
|
{
|
|
if (status == RECORDING)
|
|
return RESULT_NOP;
|
|
|
|
if (status == PLAYING)
|
|
return RESULT_ERR_NOT_READY;
|
|
|
|
status = RECORDING;
|
|
stream.Destroy();
|
|
|
|
Start();
|
|
|
|
return RESULT_OK;
|
|
}
|
|
|
|
Result FamilyKeyboard::DataRecorder::Play()
|
|
{
|
|
if (status == PLAYING)
|
|
return RESULT_NOP;
|
|
|
|
if (status == RECORDING || !Playable())
|
|
return RESULT_ERR_NOT_READY;
|
|
|
|
status = PLAYING;
|
|
|
|
Start();
|
|
|
|
return RESULT_OK;
|
|
}
|
|
|
|
NST_NO_INLINE void FamilyKeyboard::DataRecorder::Start()
|
|
{
|
|
clock = cpu.GetClockBase();
|
|
multiplier = cpu.GetClockDivider() * TAPE_CLOCK;
|
|
|
|
cpu.AddHook( Hook(this,&DataRecorder::Hook_Tape) );
|
|
|
|
Api::TapeRecorder::eventCallback( status == PLAYING ? Api::TapeRecorder::EVENT_PLAYING : Api::TapeRecorder::EVENT_RECORDING );
|
|
}
|
|
|
|
NST_NO_INLINE Result FamilyKeyboard::DataRecorder::Stop(const bool removeHook)
|
|
{
|
|
if (removeHook)
|
|
cpu.RemoveHook( Hook(this,&DataRecorder::Hook_Tape) );
|
|
|
|
if (status == STOPPED)
|
|
return RESULT_NOP;
|
|
|
|
status = STOPPED;
|
|
cycles = 0;
|
|
multiplier = 0;
|
|
in = 0;
|
|
out = 0;
|
|
pos = 0;
|
|
|
|
Api::TapeRecorder::eventCallback( Api::TapeRecorder::EVENT_STOPPED );
|
|
|
|
return RESULT_OK;
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("", on)
|
|
#endif
|
|
|
|
void FamilyKeyboard::EndFrame()
|
|
{
|
|
if (dataRecorder)
|
|
dataRecorder->EndFrame();
|
|
}
|
|
|
|
void FamilyKeyboard::Poke(const uint data)
|
|
{
|
|
if (dataRecorder)
|
|
dataRecorder->Poke( data );
|
|
|
|
if (data & COMMAND_KEY)
|
|
{
|
|
const uint out = data & COMMAND_SCAN;
|
|
|
|
if (mode && !out && ++scan > 9)
|
|
scan = 0;
|
|
|
|
mode = out >> 1;
|
|
|
|
if (data & COMMAND_RESET)
|
|
scan = 0;
|
|
}
|
|
}
|
|
|
|
uint FamilyKeyboard::Peek(uint port)
|
|
{
|
|
if (port == 0)
|
|
{
|
|
return dataRecorder ? dataRecorder->Peek() : 0;
|
|
}
|
|
else if (input && scan < 9)
|
|
{
|
|
Controllers::FamilyKeyboard::callback( input->familyKeyboard, scan, mode );
|
|
return ~uint(input->familyKeyboard.parts[scan]) & 0x1E;
|
|
}
|
|
else
|
|
{
|
|
return 0x1E;
|
|
}
|
|
}
|
|
|
|
NES_HOOK(FamilyKeyboard::DataRecorder,Tape)
|
|
{
|
|
for (const qaword next = qaword(cpu.GetCycles()) * multiplier; cycles < next; cycles += clock)
|
|
{
|
|
if (status == PLAYING)
|
|
{
|
|
if (pos < stream.Size())
|
|
{
|
|
const uint data = stream[pos++];
|
|
|
|
if (data >= 0x8C)
|
|
{
|
|
in = 0x2;
|
|
}
|
|
else if (data <= 0x74)
|
|
{
|
|
in = 0x0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Stop( false );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NST_ASSERT( status == RECORDING );
|
|
|
|
if (stream.Size() < MAX_LENGTH)
|
|
{
|
|
stream.Append( (out & 0x7) == 0x7 ? 0x90 : 0x70 );
|
|
}
|
|
else
|
|
{
|
|
Stop( false );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|