[nostalgia] Break part of core out into Turbine and TeaGBA libraries
This commit is contained in:
@@ -1,16 +1,12 @@
|
||||
enable_language(CXX ASM)
|
||||
set_source_files_properties(core.arm.cpp irq.arm.cpp PROPERTIES COMPILE_FLAGS -marm)
|
||||
set_source_files_properties(gfx.cpp PROPERTIES COMPILE_FLAGS -marm)
|
||||
target_sources(
|
||||
NostalgiaCore PRIVATE
|
||||
bios.s
|
||||
core.arm.cpp
|
||||
core.cpp
|
||||
gfx.cpp
|
||||
irq.arm.cpp
|
||||
irq.s
|
||||
panic.cpp
|
||||
)
|
||||
target_link_libraries(
|
||||
NostalgiaCore PUBLIC
|
||||
GbaStartup
|
||||
)
|
||||
TeaGBA
|
||||
)
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/array.hpp>
|
||||
#include <ox/std/types.hpp>
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Interrupt Handler
|
||||
|
||||
using interrupt_handler = void (*)();
|
||||
#define REG_ISR *reinterpret_cast<interrupt_handler*>(0x0300'7FFC)
|
||||
#define REG_IE *reinterpret_cast<volatile uint16_t*>(0x0400'0200)
|
||||
#define REG_IF *reinterpret_cast<volatile uint16_t*>(0x0400'0202)
|
||||
#define REG_IME *reinterpret_cast<volatile uint16_t*>(0x0400'0208)
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Display Registers
|
||||
|
||||
#define REG_DISPCTL *reinterpret_cast<volatile uint32_t*>(0x0400'0000)
|
||||
#define REG_DISPSTAT *reinterpret_cast<volatile uint32_t*>(0x0400'0004)
|
||||
#define REG_VCOUNT *reinterpret_cast<volatile uint32_t*>(0x0400'0006)
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Timers
|
||||
|
||||
#define REG_TIMER0 *reinterpret_cast<volatile uint16_t*>(0x0400'0100)
|
||||
#define REG_TIMER0CTL *reinterpret_cast<volatile uint16_t*>(0x0400'0102)
|
||||
|
||||
#define REG_TIMER1 *reinterpret_cast<volatile uint16_t*>(0x0400'0104)
|
||||
#define REG_TIMER1CTL *reinterpret_cast<volatile uint16_t*>(0x0400'0106)
|
||||
|
||||
#define REG_TIMER2 *reinterpret_cast<volatile uint16_t*>(0x0400'0108)
|
||||
#define REG_TIMER2CTL *reinterpret_cast<volatile uint16_t*>(0x0400'010a)
|
||||
|
||||
#define REG_TIMER3 *reinterpret_cast<volatile uint16_t*>(0x0400'010c)
|
||||
#define REG_TIMER3CTL *reinterpret_cast<volatile uint16_t*>(0x0400'010e)
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// background registers
|
||||
|
||||
// background control registers
|
||||
using BgCtl = uint16_t;
|
||||
#define REG_BG0CTL *reinterpret_cast<volatile BgCtl*>(0x0400'0008)
|
||||
#define REG_BG1CTL *reinterpret_cast<volatile BgCtl*>(0x0400'000a)
|
||||
#define REG_BG2CTL *reinterpret_cast<volatile BgCtl*>(0x0400'000c)
|
||||
#define REG_BG3CTL *reinterpret_cast<volatile BgCtl*>(0x0400'000e)
|
||||
|
||||
[[nodiscard]]
|
||||
inline auto ®BgCtl(auto bgIdx) noexcept {
|
||||
return *reinterpret_cast<volatile BgCtl*>(0x0400'0008 + 2 * bgIdx);
|
||||
}
|
||||
|
||||
// background horizontal scrolling registers
|
||||
#define REG_BG0HOFS *reinterpret_cast<volatile uint32_t*>(0x0400'0010)
|
||||
#define REG_BG1HOFS *reinterpret_cast<volatile uint32_t*>(0x0400'0014)
|
||||
#define REG_BG2HOFS *reinterpret_cast<volatile uint32_t*>(0x0400'0018)
|
||||
#define REG_BG3HOFS *reinterpret_cast<volatile uint32_t*>(0x0400'001c)
|
||||
|
||||
[[nodiscard]]
|
||||
inline volatile auto ®BgHofs(auto bgIdx) noexcept {
|
||||
return *reinterpret_cast<volatile uint32_t*>(0x0400'0010 + 4 * bgIdx);
|
||||
}
|
||||
|
||||
// background vertical scrolling registers
|
||||
#define REG_BG0VOFS *reinterpret_cast<volatile uint32_t*>(0x0400'0012)
|
||||
#define REG_BG1VOFS *reinterpret_cast<volatile uint32_t*>(0x0400'0016)
|
||||
#define REG_BG2VOFS *reinterpret_cast<volatile uint32_t*>(0x0400'001a)
|
||||
#define REG_BG3VOFS *reinterpret_cast<volatile uint32_t*>(0x0400'001e)
|
||||
|
||||
[[nodiscard]]
|
||||
inline volatile auto ®BgVofs(auto bgIdx) noexcept {
|
||||
return *reinterpret_cast<volatile uint32_t*>(0x0400'0012 + 4 * bgIdx);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// User Input
|
||||
|
||||
#define REG_GAMEPAD *reinterpret_cast<volatile uint16_t*>(0x0400'0130)
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Memory Addresses
|
||||
|
||||
#define MEM_EWRAM_BEGIN reinterpret_cast<uint8_t*>(0x0200'0000)
|
||||
#define MEM_EWRAM_END reinterpret_cast<uint8_t*>(0x0203'FFFF)
|
||||
|
||||
#define MEM_IWRAM_BEGIN reinterpret_cast<uint8_t*>(0x0300'0000)
|
||||
#define MEM_IWRAM_END reinterpret_cast<uint8_t*>(0x0300'7FFF)
|
||||
|
||||
#define REG_BLNDCTL *reinterpret_cast<uint16_t*>(0x0400'0050)
|
||||
|
||||
#define MEM_BG_PALETTE reinterpret_cast<uint16_t*>(0x0500'0000)
|
||||
#define MEM_SPRITE_PALETTE reinterpret_cast<uint16_t*>(0x0500'0200)
|
||||
|
||||
using BgMapTile = ox::Array<uint16_t, 8192>;
|
||||
#define MEM_BG_TILES reinterpret_cast<BgMapTile*>(0x0600'0000)
|
||||
#define MEM_BG_MAP reinterpret_cast<BgMapTile*>(0x0600'e000)
|
||||
|
||||
#define MEM_SPRITE_TILES reinterpret_cast<uint16_t*>(0x0601'0000)
|
||||
#define MEM_OAM reinterpret_cast<uint64_t*>(0x0700'0000)
|
||||
|
||||
#define MEM_ROM reinterpret_cast<char*>(0x0800'0000)
|
||||
|
||||
#define MEM_SRAM reinterpret_cast<char*>(0x0e00'0000)
|
||||
#define MEM_SRAM_SIZE 65535
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Functions for accessing BIOS calls
|
||||
|
||||
extern "C" {
|
||||
|
||||
// waits for any interrupt
|
||||
void nostalgia_core_halt();
|
||||
|
||||
void nostalgia_core_stop();
|
||||
|
||||
// waits for interrupts specified in interSubs
|
||||
void nostalgia_core_intrwait(unsigned discardExistingIntrs, unsigned intrSubs);
|
||||
|
||||
// waits for vblank interrupt
|
||||
void nostalgia_core_vblankintrwait();
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
//
|
||||
// Copyright 2016 - 2020 gary@drinkingtea.net
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
|
||||
.section .iwram, "ax", %progbits
|
||||
.thumb
|
||||
.align
|
||||
|
||||
.global nostalgia_core_halt
|
||||
.type nostalgia_core_halt, %function
|
||||
nostalgia_core_halt:
|
||||
swi 2
|
||||
bx lr
|
||||
|
||||
.global nostalgia_core_stop
|
||||
.type nostalgia_core_stop, %function
|
||||
nostalgia_core_stop:
|
||||
swi 3
|
||||
bx lr
|
||||
|
||||
.global nostalgia_core_intrwait
|
||||
.type nostalgia_core_intrwait, %function
|
||||
nostalgia_core_intrwait:
|
||||
swi 4
|
||||
bx lr
|
||||
|
||||
.global nostalgia_core_vblankintrwait
|
||||
.type nostalgia_core_vblankintrwait, %function
|
||||
nostalgia_core_vblankintrwait:
|
||||
swi 5
|
||||
bx lr
|
||||
|
||||
// vim: ft=armv4
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/model/desctypes.hpp>
|
||||
#include <ox/std/buffer.hpp>
|
||||
#include <ox/std/size.hpp>
|
||||
|
||||
#include <keel/context.hpp>
|
||||
|
||||
#include <nostalgia/core/context.hpp>
|
||||
|
||||
namespace nostalgia::core::gba {
|
||||
|
||||
class Context: public core::Context {
|
||||
public:
|
||||
bool running = true;
|
||||
|
||||
Context() noexcept = default;
|
||||
Context(Context &other) noexcept = delete;
|
||||
Context(const Context &other) noexcept = delete;
|
||||
Context(const Context &&other) noexcept = delete;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nostalgia/core/config.hpp>
|
||||
#include <nostalgia/core/core.hpp>
|
||||
|
||||
#include "addresses.hpp"
|
||||
#include "context.hpp"
|
||||
#include "bios.hpp"
|
||||
#include "irq.hpp"
|
||||
#include "core.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
extern volatile gba_timer_t g_timerMs;
|
||||
gba_timer_t g_wakeupTime;
|
||||
UpdateHandler g_updateHandler = nullptr;
|
||||
|
||||
ox::Error run(Context *ctx) noexcept {
|
||||
g_wakeupTime = 0;
|
||||
const auto gbaCtx = static_cast<gba::Context*>(ctx);
|
||||
while (gbaCtx->running) {
|
||||
if (g_wakeupTime <= g_timerMs && g_updateHandler) {
|
||||
auto sleepTime = g_updateHandler(ctx);
|
||||
if (sleepTime >= 0) {
|
||||
g_wakeupTime = g_timerMs + static_cast<unsigned>(sleepTime);
|
||||
} else {
|
||||
g_wakeupTime = ~gba_timer_t(0);
|
||||
}
|
||||
}
|
||||
if constexpr(config::GbaEventLoopTimerBased) {
|
||||
// wait for timer interrupt
|
||||
nostalgia_core_intrwait(0, Int_timer0 | Int_timer1 | Int_timer2 | Int_timer3);
|
||||
} else {
|
||||
nostalgia_core_vblankintrwait();
|
||||
}
|
||||
}
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,86 +1,21 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nostalgia/core/config.hpp>
|
||||
#include <nostalgia/core/core.hpp>
|
||||
#include <nostalgia/core/input.hpp>
|
||||
#include "../context.hpp"
|
||||
#include "../gfx.hpp"
|
||||
|
||||
#include "addresses.hpp"
|
||||
#include "bios.hpp"
|
||||
#include "irq.hpp"
|
||||
#include "context.hpp"
|
||||
#include "core.hpp"
|
||||
|
||||
extern "C" void isr();
|
||||
#include "../core.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
// Timer Consts
|
||||
constexpr int NanoSecond = 1000000000;
|
||||
constexpr int MilliSecond = 1000;
|
||||
constexpr int TicksMs59ns = 65535 - (NanoSecond / MilliSecond) / 59.59;
|
||||
ox::Error initGfx(Context *ctx, const InitParams&) noexcept;
|
||||
|
||||
extern UpdateHandler g_updateHandler;
|
||||
|
||||
extern volatile gba_timer_t g_timerMs;
|
||||
|
||||
static void initIrq() noexcept {
|
||||
REG_ISR = isr;
|
||||
REG_IME = 1; // enable interrupts
|
||||
}
|
||||
|
||||
static void initTimer() noexcept {
|
||||
// make timer0 a ~1 millisecond timer
|
||||
REG_TIMER0 = TicksMs59ns;
|
||||
REG_TIMER0CTL = 0b11000000;
|
||||
// enable interrupt for timer0
|
||||
REG_IE = REG_IE | Int_timer0;
|
||||
}
|
||||
|
||||
static ox::Result<std::size_t> findPreloadSection() noexcept {
|
||||
// put the header in the wrong order to prevent mistaking this code for the
|
||||
// media section
|
||||
constexpr auto headerP2 = "DER_____________";
|
||||
constexpr auto headerP1 = "KEEL_PRELOAD_HEA";
|
||||
constexpr auto headerP1Len = ox_strlen(headerP2);
|
||||
constexpr auto headerP2Len = ox_strlen(headerP1);
|
||||
constexpr auto headerLen = headerP1Len + headerP2Len;
|
||||
for (auto current = MEM_ROM; current < reinterpret_cast<char*>(0x0a000000); current += headerLen) {
|
||||
if (ox_memcmp(current, headerP1, headerP1Len) == 0 &&
|
||||
ox_memcmp(current + headerP1Len, headerP2, headerP2Len) == 0) {
|
||||
return reinterpret_cast<std::size_t>(current + headerLen);
|
||||
}
|
||||
}
|
||||
return OxError(1);
|
||||
}
|
||||
|
||||
ox::Result<ox::UniquePtr<Context>> init(ox::UniquePtr<ox::FileSystem> fs, ox::CRStringView appName) noexcept {
|
||||
ox::UPtr<Context> ctx(ox::make<gba::Context>());
|
||||
ctx->rom = std::move(fs);
|
||||
ctx->appName = appName;
|
||||
oxReturnError(initGfx(ctx.get()));
|
||||
initTimer();
|
||||
initIrq();
|
||||
oxReturnError(findPreloadSection().moveTo(&ctx->preloadSectionOffset));
|
||||
ox::Result<ox::UniquePtr<Context>> init(turbine::Context *tctx, const InitParams ¶ms) noexcept {
|
||||
ox::UPtr<Context> ctx = ox::make_unique<Context>();
|
||||
ctx->turbineCtx = tctx;
|
||||
oxReturnError(initGfx(ctx.get(), params));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void setUpdateHandler(Context*, UpdateHandler h) noexcept {
|
||||
g_updateHandler = h;
|
||||
}
|
||||
|
||||
uint64_t ticksMs(Context*) noexcept {
|
||||
return g_timerMs;
|
||||
}
|
||||
|
||||
bool buttonDown(Context*, Key k) noexcept {
|
||||
return k <= Key::GamePad_L && !(REG_GAMEPAD & (1 << static_cast<int>(k)));
|
||||
}
|
||||
|
||||
void shutdown(Context *ctx) noexcept {
|
||||
const auto gbaCtx = static_cast<gba::Context*>(ctx);
|
||||
gbaCtx->running = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/types.hpp>
|
||||
|
||||
#include <nostalgia/core/config.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
using gba_timer_t = ox::Uint<config::GbaTimerBits>;
|
||||
|
||||
}
|
||||
@@ -6,27 +6,26 @@
|
||||
#include <ox/mc/mc.hpp>
|
||||
#include <ox/std/array.hpp>
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/gfx.hpp>
|
||||
#include <teagba/registers.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
#include <turbine/turbine.hpp>
|
||||
|
||||
#include <nostalgia/core/context.hpp>
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
#include "addresses.hpp"
|
||||
#include "bios.hpp"
|
||||
#include "gfx.hpp"
|
||||
#include "irq.hpp"
|
||||
#include "registers.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
struct BgCbbData {
|
||||
unsigned bpp = 4;
|
||||
};
|
||||
static ox::Array<BgCbbData, 4> g_cbbData;
|
||||
|
||||
constexpr auto GbaTileColumns = 32;
|
||||
constexpr auto GbaTileRows = 32;
|
||||
|
||||
constexpr uint16_t DispStat_irq_vblank = 1 << 3;
|
||||
constexpr uint16_t DispStat_irq_hblank = 1 << 4;
|
||||
constexpr uint16_t DispStat_irq_vcount = 1 << 5;
|
||||
|
||||
struct GbaPaletteTarget {
|
||||
static constexpr auto TypeName = Palette::TypeName;
|
||||
static constexpr auto TypeVersion = Palette::TypeVersion;
|
||||
@@ -68,40 +67,14 @@ constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaTileMapTarget> auto *t)
|
||||
return io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
|
||||
}
|
||||
|
||||
ox::Error initGfx(Context*) noexcept {
|
||||
REG_DISPCTL = DispCtl_Mode0
|
||||
| DispCtl_SpriteMap1D
|
||||
| DispCtl_Obj;
|
||||
// tell display to trigger vblank interrupts
|
||||
REG_DISPSTAT = REG_DISPSTAT | DispStat_irq_vblank;
|
||||
// enable vblank interrupt
|
||||
REG_IE = REG_IE | Int_vblank;
|
||||
ox::Error initGfx(Context*, const InitParams&) noexcept {
|
||||
for (auto bgCtl = ®_BG0CTL; bgCtl <= ®_BG3CTL; bgCtl += 2) {
|
||||
bgSetSbb(bgCtl, 28);
|
||||
teagba::bgSetSbb(bgCtl, 28);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error shutdownGfx(Context*) noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
void setWindowTitle(Context*, ox::CRStringView) noexcept {
|
||||
}
|
||||
|
||||
void focusWindow(Context*) noexcept {
|
||||
}
|
||||
|
||||
int getScreenWidth(Context*) noexcept {
|
||||
return 240;
|
||||
}
|
||||
|
||||
int getScreenHeight(Context*) noexcept {
|
||||
return 160;
|
||||
}
|
||||
|
||||
ox::Size getScreenSize(Context*) noexcept {
|
||||
return {240, 160};
|
||||
void shutdownGfx(Context*) noexcept {
|
||||
}
|
||||
|
||||
uint8_t bgStatus(Context*) noexcept {
|
||||
@@ -129,30 +102,35 @@ ox::Error initConsole(Context *ctx) noexcept {
|
||||
constexpr ox::FileAddress PaletteAddr("/Palettes/Charset.npal");
|
||||
setBgStatus(ctx, 0b0001);
|
||||
if (!ctx) {
|
||||
ctx = new (ox_alloca(sizeof(Context))) Context();
|
||||
ctx = new(ox_alloca(sizeof(Context))) Context;
|
||||
oxRequire(rom, keel::loadRom());
|
||||
ox::FileStore32 fs(rom, 32 * ox::units::MB);
|
||||
auto romFs = new (ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
|
||||
new (&ctx->rom) ox::UniquePtr<ox::FileSystem>(romFs);
|
||||
auto romFs = new(ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
|
||||
oxRequireM(tctx, turbine::init(ox::UPtr<ox::FileSystem>(romFs)));
|
||||
ctx->turbineCtx = tctx.release();
|
||||
oxReturnError(loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr));
|
||||
setBgCbb(ctx, 0, 0);
|
||||
return {};
|
||||
} else {
|
||||
oxReturnError(loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr));
|
||||
setBgCbb(ctx, 0, 0);
|
||||
return {};
|
||||
}
|
||||
oxReturnError(loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr));
|
||||
setBgCbb(ctx, 0, 0);
|
||||
return {};
|
||||
}
|
||||
|
||||
void setBgCbb(Context*, unsigned bgIdx, unsigned cbb) noexcept {
|
||||
auto &bgCtl = regBgCtl(bgIdx);
|
||||
const auto &cbbData = g_cbbData[cbb];
|
||||
bgSetBpp(&bgCtl, cbbData.bpp);
|
||||
bgSetCbb(&bgCtl, cbb);
|
||||
teagba::bgSetBpp(&bgCtl, cbbData.bpp);
|
||||
teagba::bgSetCbb(&bgCtl, cbb);
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(Context *ctx,
|
||||
unsigned cbb,
|
||||
const ox::FileAddress &tilesheetAddr,
|
||||
const ox::FileAddress &paletteAddr) noexcept {
|
||||
oxRequire(tsStat, ctx->rom->stat(tilesheetAddr));
|
||||
oxRequire(ts, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(tilesheetAddr));
|
||||
oxRequire(tsStat, ctx->rom().stat(tilesheetAddr));
|
||||
oxRequire(ts, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(tilesheetAddr));
|
||||
GbaTileMapTarget target;
|
||||
target.pal.palette = MEM_BG_PALETTE;
|
||||
target.cbbData = &g_cbbData[cbb];
|
||||
@@ -160,15 +138,15 @@ ox::Error loadBgTileSheet(Context *ctx,
|
||||
oxReturnError(ox::readMC(ts, tsStat.size, &target));
|
||||
// load external palette if available
|
||||
if (paletteAddr) {
|
||||
oxRequire(palStat, ctx->rom->stat(paletteAddr));
|
||||
oxRequire(pal, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(paletteAddr));
|
||||
oxRequire(palStat, ctx->rom().stat(paletteAddr));
|
||||
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
|
||||
oxReturnError(ox::readMC(pal, palStat.size, &target.pal));
|
||||
}
|
||||
// update bpp of all bgs with the updated cbb
|
||||
const auto bpp = g_cbbData[cbb].bpp;
|
||||
iterateBgCtl([bpp, cbb](auto bgCtl) {
|
||||
if (bgCbb(bgCtl) == cbb) {
|
||||
bgSetBpp(bgCtl, bpp);
|
||||
teagba::iterateBgCtl([bpp, cbb](auto bgCtl) {
|
||||
if (teagba::bgCbb(bgCtl) == cbb) {
|
||||
teagba::bgSetBpp(bgCtl, bpp);
|
||||
}
|
||||
});
|
||||
return {};
|
||||
@@ -177,16 +155,16 @@ ox::Error loadBgTileSheet(Context *ctx,
|
||||
ox::Error loadSpriteTileSheet(Context *ctx,
|
||||
const ox::FileAddress &tilesheetAddr,
|
||||
const ox::FileAddress &paletteAddr) noexcept {
|
||||
oxRequire(tsStat, ctx->rom->stat(tilesheetAddr));
|
||||
oxRequire(ts, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(tilesheetAddr));
|
||||
oxRequire(tsStat, ctx->rom().stat(tilesheetAddr));
|
||||
oxRequire(ts, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(tilesheetAddr));
|
||||
GbaTileMapTarget target;
|
||||
target.pal.palette = MEM_SPRITE_PALETTE;
|
||||
target.tileMap = MEM_SPRITE_TILES;
|
||||
oxReturnError(ox::readMC(ts, tsStat.size, &target));
|
||||
// load external palette if available
|
||||
if (paletteAddr) {
|
||||
oxRequire(palStat, ctx->rom->stat(paletteAddr));
|
||||
oxRequire(pal, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(paletteAddr));
|
||||
oxRequire(palStat, ctx->rom().stat(paletteAddr));
|
||||
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
|
||||
oxReturnError(ox::readMC(pal, palStat.size, &target.pal));
|
||||
}
|
||||
return {};
|
||||
@@ -195,8 +173,8 @@ ox::Error loadSpriteTileSheet(Context *ctx,
|
||||
ox::Error loadBgPalette(Context *ctx, unsigned, const ox::FileAddress &paletteAddr) noexcept {
|
||||
GbaPaletteTarget target;
|
||||
target.palette = MEM_BG_PALETTE;
|
||||
oxRequire(palStat, ctx->rom->stat(paletteAddr));
|
||||
oxRequire(pal, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(paletteAddr));
|
||||
oxRequire(palStat, ctx->rom().stat(paletteAddr));
|
||||
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
|
||||
oxReturnError(ox::readMC(pal, palStat.size, &target));
|
||||
return {};
|
||||
}
|
||||
@@ -204,13 +182,12 @@ ox::Error loadBgPalette(Context *ctx, unsigned, const ox::FileAddress &paletteAd
|
||||
ox::Error loadSpritePalette(Context *ctx, unsigned cbb, const ox::FileAddress &paletteAddr) noexcept {
|
||||
GbaPaletteTarget target;
|
||||
target.palette = &MEM_SPRITE_PALETTE[cbb];
|
||||
oxRequire(palStat, ctx->rom->stat(paletteAddr));
|
||||
oxRequire(pal, static_cast<ox::MemFS*>(ctx->rom.get())->directAccess(paletteAddr));
|
||||
oxRequire(palStat, ctx->rom().stat(paletteAddr));
|
||||
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
|
||||
oxReturnError(ox::readMC(pal, palStat.size, &target));
|
||||
return {};
|
||||
}
|
||||
|
||||
// Do NOT use Context in the GBA version of this function.
|
||||
void puts(Context *ctx, int column, int row, ox::CRStringView str) noexcept {
|
||||
const auto col = static_cast<unsigned>(column);
|
||||
for (auto i = 0u; i < str.bytes(); i++) {
|
||||
@@ -231,25 +208,11 @@ void clearTileLayer(Context*, unsigned bgIdx) noexcept {
|
||||
|
||||
[[maybe_unused]]
|
||||
void hideSprite(Context*, unsigned idx) noexcept {
|
||||
oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
|
||||
GbaSpriteAttrUpdate oa;
|
||||
//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
|
||||
teagba::GbaSpriteAttrUpdate oa;
|
||||
oa.attr0 = 2 << 8;
|
||||
oa.idx = idx;
|
||||
// block until g_spriteUpdates is less than buffer len
|
||||
if (g_spriteUpdates >= config::GbaSpriteBufferLen) [[unlikely]] {
|
||||
nostalgia_core_vblankintrwait();
|
||||
}
|
||||
if constexpr(config::GbaEventLoopTimerBased) {
|
||||
REG_IE = REG_IE & ~Int_vblank; // disable vblank interrupt handler
|
||||
g_spriteBuffer[g_spriteUpdates] = oa;
|
||||
REG_IE = REG_IE | Int_vblank; // enable vblank interrupt handler
|
||||
} else {
|
||||
const auto ie = REG_IE; // disable vblank interrupt handler
|
||||
REG_IE = REG_IE & static_cast<uint16_t>(~Int_vblank); // disable vblank interrupt handler
|
||||
g_spriteBuffer[g_spriteUpdates] = oa;
|
||||
REG_IE = ie; // enable vblank interrupt handler
|
||||
}
|
||||
g_spriteUpdates = g_spriteUpdates + 1;
|
||||
teagba::addSpriteUpdate(oa);
|
||||
}
|
||||
|
||||
void setSprite(Context*,
|
||||
@@ -260,8 +223,8 @@ void setSprite(Context*,
|
||||
unsigned spriteShape,
|
||||
unsigned spriteSize,
|
||||
unsigned flipX) noexcept {
|
||||
oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
|
||||
GbaSpriteAttrUpdate oa;
|
||||
//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
|
||||
teagba::GbaSpriteAttrUpdate oa;
|
||||
oa.attr0 = static_cast<uint16_t>(y & ox::onMask<uint8_t>(0b111'1111))
|
||||
| (static_cast<uint16_t>(1) << 10) // enable alpha
|
||||
| (static_cast<uint16_t>(spriteShape) << 14);
|
||||
@@ -270,21 +233,7 @@ void setSprite(Context*,
|
||||
| (static_cast<uint16_t>(spriteSize) << 14);
|
||||
oa.attr2 = static_cast<uint16_t>(tileIdx & ox::onMask<uint16_t>(8));
|
||||
oa.idx = idx;
|
||||
// block until g_spriteUpdates is less than buffer len
|
||||
if (g_spriteUpdates >= config::GbaSpriteBufferLen) [[unlikely]] {
|
||||
nostalgia_core_vblankintrwait();
|
||||
}
|
||||
if constexpr(config::GbaEventLoopTimerBased) {
|
||||
REG_IE = REG_IE & ~Int_vblank; // disable vblank interrupt handler
|
||||
g_spriteBuffer[g_spriteUpdates] = oa;
|
||||
REG_IE = REG_IE | Int_vblank; // enable vblank interrupt handler
|
||||
} else {
|
||||
const auto ie = REG_IE; // disable vblank interrupt handler
|
||||
REG_IE = REG_IE & static_cast<uint16_t>(~Int_vblank); // disable vblank interrupt handler
|
||||
g_spriteBuffer[g_spriteUpdates] = oa;
|
||||
REG_IE = ie; // enable vblank interrupt handler
|
||||
}
|
||||
g_spriteUpdates = g_spriteUpdates + 1;
|
||||
teagba::addSpriteUpdate(oa);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,43 +1,11 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/stddef.hpp>
|
||||
#include <ox/std/types.hpp>
|
||||
|
||||
#include <nostalgia/core/config.hpp>
|
||||
#include "../context.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
enum DispCtl {
|
||||
DispCtl_Mode0 = 0,
|
||||
DispCtl_Mode1 = 1,
|
||||
DispCtl_Mode2 = 2,
|
||||
DispCtl_Mode3 = 3,
|
||||
DispCtl_Mode4 = 4,
|
||||
DispCtl_Mode5 = 5,
|
||||
|
||||
DispCtl_SpriteMap1D = 1 << 6,
|
||||
|
||||
DispCtl_Bg0 = 1 << 8,
|
||||
DispCtl_Bg1 = 1 << 9,
|
||||
DispCtl_Bg2 = 1 << 10,
|
||||
DispCtl_Bg3 = 1 << 11,
|
||||
|
||||
DispCtl_Obj = 1 << 12,
|
||||
};
|
||||
|
||||
struct OX_ALIGN8 GbaSpriteAttrUpdate {
|
||||
uint16_t attr0 = 0;
|
||||
uint16_t attr1 = 0;
|
||||
uint16_t attr2 = 0;
|
||||
uint16_t idx = 0;
|
||||
|
||||
};
|
||||
|
||||
extern volatile uint16_t g_spriteUpdates;
|
||||
extern ox::Array<GbaSpriteAttrUpdate, config::GbaSpriteBufferLen> g_spriteBuffer;
|
||||
|
||||
ox::Error initGfx(Context *ctx, const InitParams &) noexcept;
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
// NOTE: this file is compiled as ARM and not THUMB, so don't but too much in
|
||||
// here
|
||||
|
||||
#include "addresses.hpp"
|
||||
#include "core.hpp"
|
||||
#include "gfx.hpp"
|
||||
#include "irq.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
volatile uint16_t g_spriteUpdates = 0;
|
||||
ox::Array<GbaSpriteAttrUpdate, config::GbaSpriteBufferLen> g_spriteBuffer;
|
||||
|
||||
volatile gba_timer_t g_timerMs = 0;
|
||||
|
||||
}
|
||||
|
||||
using namespace nostalgia::core;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void nostalgia_core_isr_vblank() {
|
||||
// copy g_spriteUpdates to allow it to use a register instead of reading
|
||||
// from memory every iteration of the loop, needed because g_spriteUpdates
|
||||
// is volatile
|
||||
const unsigned updates = g_spriteUpdates;
|
||||
for (unsigned i = 0; i < updates; ++i) {
|
||||
const auto &oa = g_spriteBuffer[i];
|
||||
MEM_OAM[oa.idx] = *reinterpret_cast<const uint64_t*>(&oa);
|
||||
}
|
||||
g_spriteUpdates = 0;
|
||||
if constexpr(config::GbaEventLoopTimerBased) {
|
||||
// disable vblank interrupt until it is needed again
|
||||
REG_IE = REG_IE & ~Int_vblank;
|
||||
}
|
||||
}
|
||||
|
||||
void nostalgia_core_isr_timer0() {
|
||||
g_timerMs = g_timerMs + 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/std/types.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
constexpr uint16_t Int_vblank = 1 << 0;
|
||||
constexpr uint16_t Int_hblank = 1 << 1;
|
||||
constexpr uint16_t Int_vcount = 1 << 2;
|
||||
constexpr uint16_t Int_timer0 = 1 << 3;
|
||||
constexpr uint16_t Int_timer1 = 1 << 4;
|
||||
constexpr uint16_t Int_timer2 = 1 << 5;
|
||||
constexpr uint16_t Int_timer3 = 1 << 6;
|
||||
constexpr uint16_t Int_serial = 1 << 7; // link cable
|
||||
constexpr uint16_t Int_dma0 = 1 << 8;
|
||||
constexpr uint16_t Int_dma1 = 1 << 9;
|
||||
constexpr uint16_t Int_dma2 = 1 << 10;
|
||||
constexpr uint16_t Int_dma3 = 1 << 11;
|
||||
constexpr uint16_t Int_dma4 = 1 << 12;
|
||||
constexpr uint16_t Int_dma5 = 1 << 13;
|
||||
constexpr uint16_t Int_input = 1 << 14; // gamepad
|
||||
constexpr uint16_t Int_cart = 1 << 15; // cartridge removed
|
||||
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
//
|
||||
// Copyright 2016 - 2020 gary@drinkingtea.net
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
|
||||
.section .iwram, "ax", %progbits
|
||||
.arm
|
||||
.align
|
||||
|
||||
.extern nostalgia_core_isr_vblank
|
||||
.extern nostalgia_core_isr_timer0
|
||||
|
||||
.equ REG_IFBIOS, 0x03007ff8
|
||||
|
||||
.equ REG_IE, 0x04000200
|
||||
.equ REG_IF, 0x04000202
|
||||
.equ REG_IME, 0x04000208
|
||||
|
||||
.equ Int_vblank, 1
|
||||
.equ Int_hblank, 2
|
||||
.equ Int_vcount, 4
|
||||
.equ Int_timer0, 8
|
||||
.equ Int_timer1, 16
|
||||
.equ Int_timer2, 32
|
||||
.equ Int_timer3, 64
|
||||
.equ Int_serial, 128 // link cable
|
||||
.equ Int_dma0, 256
|
||||
.equ Int_dma1, 512
|
||||
.equ Int_dma2, 1024
|
||||
.equ Int_dma3, 2048
|
||||
.equ Int_dma4, 4096
|
||||
.equ Int_dma5, 8192
|
||||
.equ Int_input, 16384 // gamepad
|
||||
.equ Int_cart, 32768 // cartridge removed
|
||||
|
||||
|
||||
.global isr
|
||||
.type isr, %function
|
||||
isr:
|
||||
// read IE
|
||||
ldr r0, =#REG_IE
|
||||
ldrh r1, [r0] // r1 becomes IE value
|
||||
ldrh r2, [r0, #2] // r2 becomes IF value
|
||||
and r1, r1, r2 // r1 becomes IE & IF
|
||||
// done with r2 as IF value
|
||||
|
||||
// Acknowledge IRQ in REG_IF
|
||||
strh r1, [r0, #2]
|
||||
ldr r0, =#REG_IFBIOS
|
||||
// Acknowledge IRQ in REG_IFBIOS
|
||||
ldr r2, [r0] // r2 becomes REG_IFBIOS value
|
||||
orr r2, r2, r1
|
||||
str r2, [r0]
|
||||
// done with r2 as IFBIOS value
|
||||
// done with r0 as REG_IFBIOS
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Interrupt Table Begin //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
cmp r1, #Int_timer0
|
||||
ldreq r0, =nostalgia_core_isr_timer0
|
||||
beq isr_call_handler
|
||||
|
||||
cmp r1, #Int_vblank
|
||||
ldreq r0, =nostalgia_core_isr_vblank
|
||||
beq isr_call_handler
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Interrupt Table End //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
bx lr
|
||||
|
||||
isr_call_handler:
|
||||
// clear IME to disable interrupts
|
||||
ldr r2, =#REG_IME
|
||||
mov r1, #0
|
||||
str r1, [r2]
|
||||
|
||||
// enter system mode
|
||||
mrs r1, cpsr
|
||||
bic r1, r1, #0xDF
|
||||
orr r1, r1, #0x1F
|
||||
msr cpsr, r1
|
||||
|
||||
push {lr}
|
||||
ldr lr, =isr_restore
|
||||
bx r0
|
||||
|
||||
isr_restore:
|
||||
pop {lr}
|
||||
|
||||
// re-enter irq mode
|
||||
mrs r0, cpsr
|
||||
bic r0, r0, #0xDF
|
||||
orr r0, r0, #0x92
|
||||
msr cpsr, r0
|
||||
|
||||
// set IME to re-enable interrupts
|
||||
ldr r2, =#REG_IME
|
||||
mov r0, #1
|
||||
str r0, [r2]
|
||||
|
||||
bx lr
|
||||
|
||||
// vim: ft=armv4
|
||||
@@ -7,32 +7,43 @@
|
||||
#include <nostalgia/core/core.hpp>
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
#include "addresses.hpp"
|
||||
#include "bios.hpp"
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/bios.hpp>
|
||||
#include <teagba/gfx.hpp>
|
||||
|
||||
#include "gfx.hpp"
|
||||
|
||||
namespace ox {
|
||||
|
||||
using namespace nostalgia::core;
|
||||
|
||||
void panic(const char*, int, const char *msg, const ox::Error &err) noexcept {
|
||||
oxIgnoreError(initGfx(nullptr));
|
||||
void panic(const char *file, int line, const char *panicMsg, const ox::Error &err) noexcept {
|
||||
oxIgnoreError(initGfx(nullptr, {}));
|
||||
oxIgnoreError(initConsole(nullptr));
|
||||
// enable only BG 0
|
||||
REG_DISPCTL = DispCtl_Bg0;
|
||||
REG_DISPCTL = teagba::DispCtl_Bg0;
|
||||
clearTileLayer(nullptr, 0);
|
||||
ox::BString<23> serr = "Error code: ";
|
||||
serr += static_cast<int64_t>(err);
|
||||
puts(nullptr, 32 + 1, 1, "SADNESS...");
|
||||
puts(nullptr, 32 + 1, 4, "UNEXPECTED STATE:");
|
||||
puts(nullptr, 32 + 2, 6, msg);
|
||||
puts(nullptr, 32 + 2, 6, panicMsg);
|
||||
if (err) {
|
||||
puts(nullptr, 32 + 2, 8, serr);
|
||||
}
|
||||
puts(nullptr, 32 + 1, 15, "PLEASE RESTART THE SYSTEM");
|
||||
// print to terminal if in mGBA
|
||||
oxErrf("\033[31;1;1mPANIC:\033[0m [{}:{}]: {}\n", file, line, panicMsg);
|
||||
if (err.msg) {
|
||||
oxErrf("\tError Message:\t{}\n", err.msg);
|
||||
}
|
||||
oxErrf("\tError Code:\t{}\n", static_cast<ErrorCode>(err));
|
||||
if (err.file != nullptr) {
|
||||
oxErrf("\tError Location:\t{}:{}\n", err.file, err.line);
|
||||
}
|
||||
// disable all interrupt handling and IntrWait on no interrupts
|
||||
REG_IE = 0;
|
||||
nostalgia_core_intrwait(0, 0);
|
||||
teagba_intrwait(0, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "addresses.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
inline auto bgSetSbb(volatile BgCtl *bgCtl, unsigned sbb) noexcept {
|
||||
*bgCtl = (*bgCtl & ~0b11111'0000'0000u) | (sbb << 8);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr unsigned bgPri(BgCtl bgCtl) noexcept {
|
||||
return bgCtl & 1;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
inline auto bgPri(const volatile BgCtl *bgCtl) noexcept {
|
||||
return bgPri(*bgCtl);
|
||||
}
|
||||
|
||||
inline auto bgSetPri(volatile BgCtl *bgCtl, unsigned pri) noexcept {
|
||||
pri = pri & 0b1;
|
||||
*bgCtl = (*bgCtl & ~0b1u) | (pri << 0);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr unsigned bgBpp(BgCtl bgCtl) noexcept {
|
||||
return ((bgCtl >> 7) & 1) ? 8 : 4;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
inline auto bgBpp(const volatile BgCtl *bgCtl) noexcept {
|
||||
return bgBpp(*bgCtl);
|
||||
}
|
||||
|
||||
inline auto bgSetBpp(volatile BgCtl *bgCtl, unsigned bpp) noexcept {
|
||||
constexpr auto Bpp8 = 1 << 7;
|
||||
if (bpp == 4) {
|
||||
*bgCtl = *bgCtl | ((*bgCtl | Bpp8) ^ Bpp8); // set to use 4 bits per pixel
|
||||
} else {
|
||||
*bgCtl = *bgCtl | Bpp8; // set to use 8 bits per pixel
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto bgCbb(BgCtl bgCtl) noexcept {
|
||||
return (bgCtl >> 2) & 0b11u;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
inline auto bgCbb(const volatile BgCtl *bgCtl) noexcept {
|
||||
return bgCbb(*bgCtl);
|
||||
}
|
||||
|
||||
inline auto bgSetCbb(volatile BgCtl *bgCtl, unsigned cbb) noexcept {
|
||||
cbb = cbb & 0b11;
|
||||
*bgCtl = (*bgCtl & ~0b1100u) | (cbb << 2);
|
||||
}
|
||||
|
||||
constexpr void iterateBgCtl(auto cb) noexcept {
|
||||
for (auto bgCtl = ®_BG0CTL; bgCtl <= ®_BG3CTL; bgCtl += 2) {
|
||||
cb(bgCtl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <ox/std/std.hpp>
|
||||
|
||||
namespace ox::heapmgr {
|
||||
|
||||
[[nodiscard]]
|
||||
void *malloc(std::size_t allocSize) noexcept;
|
||||
|
||||
void free(void *ptr) noexcept;
|
||||
|
||||
void initHeap(char *heapBegin, char *heapEnd) noexcept;
|
||||
|
||||
}
|
||||
|
||||
std::map<std::string, int(*)(std::string)> tests = {
|
||||
};
|
||||
|
||||
int main(int argc, const char **args) {
|
||||
int retval = -1;
|
||||
if (argc > 1) {
|
||||
auto testName = args[1];
|
||||
std::string testArg = "";
|
||||
if (args[2]) {
|
||||
testArg = args[2];
|
||||
}
|
||||
if (tests.find(testName) != tests.end()) {
|
||||
retval = tests[testName](testArg);
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user