Files
PokeMe64/include/transferpak/TransferPakManager.h

126 lines
4.2 KiB
C++
Executable File

#ifndef _TRANSFERPAKMANAGER_H
#define _TRANSFERPAKMANAGER_H
#include <libdragon.h>
#ifdef __GNUC__
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif
enum class TransferPakMode
{
ROM,
RAM
};
/** @brief Transfer Pak command block size (32 bytes) */
#define TPAK_BLOCK_SIZE 0x20
/**
* @brief This class manages the N64 transfer pak
* Both SRAM and ROM access are implemented in the same class here
* because the transfer pak itself has some shenanigans:
* - it has its own banking mechanism independent of the cartridge banks -> It can only access 16 KB of the gameboy address space at a time. However, libdragon
* takes care of most of the heavy lifting.
* - It can't do single byte read/writes. Everything must be done in multiples of 32 bytes.
* - switching transfer pak banks influences whether you are reading/writing the right addresses for ROM or SRAM.
*
* So you can't implement ROM and SRAM reading in separate classes.
* TransferPakManager manages and keeps track of all this and abstracts this complexity.
*/
class TransferPakManager
{
public:
TransferPakManager();
~TransferPakManager();
joypad_port_t getPort() const;
void setPort(joypad_port_t port);
bool hasTransferPak();
bool isPoweredOn() const;
bool setPower(bool on);
uint8_t getStatus();
bool readCartridgeHeader(gameboy_cartridge_header& cartridgeHeader);
/**
* @brief This function switches the Gameboy ROM bank index
* WARNING: it switches to transfer pak bank 0
*/
void switchGBROMBank(uint8_t bankIndex);
/**
* @brief Returns whether we called setRAMEnabled(true) on this TransferPakManager instance before
*/
bool isRAMEnabled() const;
/**
* @brief This function enables/disables gameboy RAM/RTC access.
* WARNING: it switches to transfer pak bank 0
*/
void setRAMEnabled(bool enabled);
/**
* @brief This function switches the Gameboy RAM bank index
* WARNING: it switches to transfer pak bank 1
*/
void switchGBSRAMBank(uint8_t bankIndex);
/**
* For MBC1 controllers, this will effect the ROM or RAM modes, depending on what is written in $6000-$7FFF.
* If the mode is 0 (default), the RAM bank will be locked to 0, but the extended banks (bankindex > 0x1F)
* can be accessed in the 0x0000 - 0x3FFF range (thereby replacing bank 0) by modifying the 0x4000-0x5FFF registers.
* However, in this mode, the RAM bank is locked to bank 0
*
* If the mode is 1, the RAM bank can be switched, but the extended rom banks (bankindex > 0x1F) can't be switched to.
* In this mode 0x0000 - 0x3FFF is locked to rom bank 0.
* Sources:
* - https://retrocomputing.stackexchange.com/questions/11732/how-does-the-gameboys-memory-bank-switching-work
* - https://gbdev.io/pandocs/MBC1.html
*
* For our use case though, we don't need mode 0.
*/
void switchMBC1BankingMode(uint8_t mode);
/**
* @brief This function reads data from the specified gameboy address
*/
void read(uint16_t gbAddress, uint8_t* data, uint16_t size);
/**
* @brief This function reads data from the specified SRAM bank offset
*/
void readSRAM(uint16_t SRAMBankOffset, uint8_t *data, uint16_t size);
/**
* @brief This function writes the given data to the given SRAMBankOffset
* The write is cached and won't be written until a new write outside the current 32 byte block
* is triggered or finishWrites() is called by the user.
*
* WARNING: Don't forget to call finishWrites() when you're done writing! Otherwise corruption will occur
* due to the cached writes
*/
void writeSRAM(uint16_t SRAMBankOffset, const uint8_t *data, uint16_t size);
/**
* @brief This function writes the current writeBuffer immediately
*/
void finishWrites();
protected:
private:
joypad_port_t port_;
bool isPoweredOn_;
bool ramEnabled_;
uint8_t currentSRAMBank_;
uint16_t readBufferBankOffset_;
uint16_t writeBufferSRAMBankOffset_;
uint8_t readBuffer_[TPAK_BLOCK_SIZE];
uint8_t writeBuffer_[TPAK_BLOCK_SIZE];
};
#endif