nestopia/source/core/NstHomebrew.cpp
rdanbrook b8e4f1f501 The homebrew module
The homebrew module offers configurable ports useful to homebrew game
developers.

By reading and writing to new ports at configurable addresses, a rom may
* write to stdout
* write to stderr
* exit the emulator with a given exit status

These can be used to create automated test suites.
2018-07-31 20:53:00 -04:00

307 lines
5.6 KiB
C++

////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2018-2018 Phil Smith
//
// 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 <algorithm>
#include <cstdlib>
#include <iostream>
#include "NstCpu.hpp"
#include "NstHomebrew.hpp"
namespace Nes
{
namespace Core
{
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Homebrew::Homebrew(Cpu& c)
: cpu(c)
, exitAddress(0)
, exitSet(false)
, exitPort(NULL)
, stdOutAddress(0)
, stdOutSet(false)
, stdOutPort(NULL)
, stdErrAddress(0)
, stdErrSet(false)
, stdErrPort(NULL)
{}
Homebrew::~Homebrew()
{
ClearPorts();
}
void Homebrew::Reset()
{
ActivateExitPort();
ActivateStdOutPort();
ActivateStdErrPort();
}
void Homebrew::ClearPorts()
{
ClearExitPort();
ClearStdOutPort();
ClearStdErrPort();
}
Result Homebrew::SetExitPort
(
const word address,
const bool activate
)
{
if
(
exitSet
&& exitAddress == address
&& (!activate || exitPort != NULL)
)
return RESULT_NOP;
ClearExitPort();
exitAddress = address;
exitSet = true;
if (activate)
return ActivateExitPort();
else
return RESULT_OK;
}
Result Homebrew::ClearExitPort()
{
exitSet = false;
if (exitPort != NULL)
{
cpu.Unlink
(
exitAddress,
this,
&Homebrew::Peek_Exit,
&Homebrew::Poke_Exit
);
exitPort = NULL;
return RESULT_OK;
}
else
return RESULT_NOP;
}
Result Homebrew::SetStdOutPort
(
const word address,
const bool activate
)
{
if
(
stdOutSet
&& stdOutAddress == address
&& (!activate || stdOutPort != NULL)
)
return RESULT_NOP;
ClearStdOutPort();
stdOutAddress = address;
stdOutSet = true;
if (activate)
return ActivateStdOutPort();
else
return RESULT_OK;
}
Result Homebrew::ClearStdOutPort()
{
stdOutSet = false;
if (stdOutPort != NULL)
{
cpu.Unlink
(
stdOutAddress,
this,
&Homebrew::Peek_StdOut,
&Homebrew::Poke_StdOut
);
stdOutPort = NULL;
return RESULT_OK;
}
else
return RESULT_NOP;
}
Result Homebrew::SetStdErrPort
(
const word address,
const bool activate
)
{
if
(
stdErrSet
&& stdErrAddress == address
&& (!activate || stdErrPort != NULL)
)
return RESULT_NOP;
ClearStdErrPort();
stdErrAddress = address;
stdErrSet = true;
if (activate)
return ActivateStdErrPort();
else
return RESULT_OK;
}
Result Homebrew::ClearStdErrPort()
{
stdErrSet = false;
if (stdErrPort != NULL)
{
cpu.Unlink
(
stdErrAddress,
this,
&Homebrew::Peek_StdErr,
&Homebrew::Poke_StdErr
);
stdErrPort = NULL;
return RESULT_OK;
}
else
return RESULT_NOP;
}
dword Homebrew::NumPorts() const
{
return (exitPort != NULL ? 1 : 0)
+ (stdOutPort != NULL ? 1 : 0)
+ (stdErrPort != NULL ? 1 : 0);
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
NES_PEEK_A(Homebrew,Exit)
{
return exitPort->Peek( address );
}
NES_POKE_D(Homebrew,Exit)
{
std::exit( data );
}
NES_PEEK_A(Homebrew,StdOut)
{
return stdOutPort->Peek( address );
}
NES_POKE_D(Homebrew,StdOut)
{
std::cout << (static_cast<unsigned char>(data & 0xFF));
if (data == '\n')
std::cout << std::flush;
}
NES_PEEK_A(Homebrew,StdErr)
{
return stdErrPort->Peek( address );
}
NES_POKE_D(Homebrew,StdErr)
{
std::cerr << (static_cast<unsigned char>(data & 0xFF));
if (data == '\n')
std::cerr << std::flush;
}
Result Homebrew::ActivateExitPort()
{
if (exitSet && exitPort == NULL)
{
exitPort = cpu.Link
(
exitAddress,
Cpu::LEVEL_HIGH,
this,
&Homebrew::Peek_Exit,
&Homebrew::Poke_Exit
);
return RESULT_OK;
}
else
return RESULT_NOP;
}
Result Homebrew::ActivateStdOutPort()
{
if (stdOutSet && stdOutPort == NULL)
{
stdOutPort = cpu.Link
(
stdOutAddress,
Cpu::LEVEL_HIGH,
this,
&Homebrew::Peek_StdOut,
&Homebrew::Poke_StdOut
);
return RESULT_OK;
}
else
return RESULT_NOP;
}
Result Homebrew::ActivateStdErrPort()
{
if (stdErrSet && stdErrPort == NULL)
{
stdErrPort = cpu.Link
(
stdErrAddress,
Cpu::LEVEL_HIGH,
this,
&Homebrew::Peek_StdErr,
&Homebrew::Poke_StdErr
);
return RESULT_OK;
}
else
return RESULT_NOP;
}
}
}