AppleWin/source/frontends/ncurses/world.cpp

600 lines
13 KiB
C++
Raw Normal View History

#include "StdAfx.h"
#include <ncurses.h>
#include <signal.h>
#include <iostream>
#include "Common.h"
#include "Disk.h"
#include "Video.h"
#include "CPU.h"
#include "Log.h"
#include "DiskImageHelper.h"
#include "Memory.h"
#include "linux/interface.h"
#include "frontends/ncurses/nframe.h"
#include "frontends/ncurses/colors.h"
#include "frontends/ncurses/asciiart.h"
#include "frontends/ncurses/input.h"
namespace
{
std::shared_ptr<Frame> frame;
std::shared_ptr<GraphicsColors> colors;
std::shared_ptr<ASCIIArt> asciiArt;
std::shared_ptr<Input> input;
int g_nTrackDrive1 = -1;
int g_nTrackDrive2 = -1;
int g_nSectorDrive1 = -1;
int g_nSectorDrive2 = -1;
TCHAR g_sTrackDrive1 [8] = TEXT("??");
TCHAR g_sTrackDrive2 [8] = TEXT("??");
TCHAR g_sSectorDrive1[8] = TEXT("??");
TCHAR g_sSectorDrive2[8] = TEXT("??");
Disk_Status_e g_eStatusDrive1 = DISK_STATUS_OFF;
Disk_Status_e g_eStatusDrive2 = DISK_STATUS_OFF;
LPBYTE g_pTextBank1; // Aux
LPBYTE g_pTextBank0; // Main
LPBYTE g_pHiresBank1;
LPBYTE g_pHiresBank0;
#define SW_80COL (g_uVideoMode & VF_80COL)
#define SW_DHIRES (g_uVideoMode & VF_DHIRES)
#define SW_HIRES (g_uVideoMode & VF_HIRES)
#define SW_80STORE (g_uVideoMode & VF_80STORE)
#define SW_MIXED (g_uVideoMode & VF_MIXED)
#define SW_PAGE2 (g_uVideoMode & VF_PAGE2)
#define SW_TEXT (g_uVideoMode & VF_TEXT)
BYTE nextKey = 0;
bool keyReady = false;
bool g_bTextFlashState = false;
unsigned __int64 g_nJoyCntrResetCycle = 0; // Abs cycle that joystick counters were reset
const double PDL_CNTR_INTERVAL = 2816.0 / 255.0; // 11.04 (From KEGS)
double alpha = 10.0;
double F = 0;
void sig_handler(int signo)
{
// Ctrl-C
// is there a race condition here?
// is it a problem?
nextKey = 0x83;
keyReady = true;
}
chtype mapCharacter(BYTE ch)
{
const char low = ch & 0x7f;
const char high = ch & 0x80;
chtype result = low;
const int code = low >> 5;
switch (code)
{
case 0: // 00 - 1F
result += 0x40; // UPPERCASE
break;
case 1: // 20 - 3F
// SPECIAL CHARACTER
break;
case 2: // 40 - 5F
// UPPERCASE
break;
case 3: // 60 - 7F
// LOWERCASE
if (high == 0 && g_nAltCharSetOffset == 0)
{
result -= 0x40;
}
break;
}
if (result == 0x7f)
{
result = ACS_CKBOARD;
}
result != A_BLINK;
if (!high)
{
if ((g_nAltCharSetOffset == 0) && (low >= 0x40))
{
// result |= A_BLINK; // does not work on my terminal
if (g_bTextFlashState)
{
result |= A_REVERSE;
}
}
else
{
result |= A_REVERSE;
}
}
return result;
}
void VideoUpdateFlash()
{
static UINT nTextFlashCnt = 0;
++nTextFlashCnt;
if (nTextFlashCnt == 16) // Flash rate = 0.5 * 60 / 16 Hz (as we need 2 changes for a period)
{
nTextFlashCnt = 0;
g_bTextFlashState = !g_bTextFlashState;
}
}
ULONG lastUpdate = 0;
void updateSpeaker()
{
const ULONG dCycles = g_nCumulativeCycles - lastUpdate;
const double dt = dCycles / CLK_6502;
const double coeff = exp(- alpha * dt);
F = F * coeff;
lastUpdate = g_nCumulativeCycles;
}
typedef bool (*VideoUpdateFuncPtr_t)(int, int, int, int, int);
bool Update40ColCell (int x, int y, int xpixel, int ypixel, int offset)
{
frame->init(24, 40);
asciiArt->init(1, 1);
BYTE ch = *(g_pTextBank0+offset);
WINDOW * win = frame->getWindow();
const chtype ch2 = mapCharacter(ch);
mvwaddch(win, 1 + y, 1 + x, ch2);
return true;
}
bool Update80ColCell (int x, int y, int xpixel, int ypixel, int offset)
{
frame->init(24, 80);
asciiArt->init(1, 2);
BYTE ch1 = *(g_pTextBank1+offset);
BYTE ch2 = *(g_pTextBank0+offset);
WINDOW * win = frame->getWindow();
const chtype ch12 = mapCharacter(ch1);
mvwaddch(win, 1 + y, 1 + 2 * x, ch12);
const chtype ch22 = mapCharacter(ch2);
mvwaddch(win, 1 + y, 1 + 2 * x + 1, ch22);
return true;
}
bool UpdateLoResCell (int x, int y, int xpixel, int ypixel, int offset)
{
BYTE val = *(g_pTextBank0+offset);
const int pair = colors->getPair(val);
WINDOW * win = frame->getWindow();
wcolor_set(win, pair, NULL);
if (frame->getColumns() == 40)
{
mvwaddstr(win, 1 + y, 1 + x, "\u2580");
}
else
{
mvwaddstr(win, 1 + y, 1 + 2 * x, "\u2580\u2580");
}
wcolor_set(win, 0, NULL);
return true;
}
bool UpdateDLoResCell (int x, int y, int xpixel, int ypixel, int offset)
{
return true;
}
bool UpdateHiResCell (int x, int y, int xpixel, int ypixel, int offset)
{
const BYTE * base = g_pHiresBank0 + offset;
const ASCIIArt::array_char_t & chs = asciiArt->getCharacters(base);
const auto shape = chs.shape();
const size_t rows = shape[0];
const size_t cols = shape[1];
frame->init(24 * rows, 40 * cols);
WINDOW * win = frame->getWindow();
for (size_t i = 0; i < rows; ++i)
{
for (size_t j = 0; j < cols; ++j)
{
const int pair = colors->getGrey(chs[i][j].foreground, chs[i][j].background);
wcolor_set(win, pair, NULL);
mvwaddstr(win, 1 + rows * y + i, 1 + cols * x + j, chs[i][j].c);
}
}
wcolor_set(win, 0, NULL);
return true;
}
bool UpdateDHiResCell (int x, int y, int xpixel, int ypixel, int offset)
{
return true;
}
}
double g_relativeSpeed = 1.0;
void output(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
WINDOW * win = frame->getBuffer();
vwprintw(win, fmt, args);
wrefresh(win);
}
void FrameRefresh()
{
WINDOW * status = frame->getStatus();
mvwprintw(status, 1, 2, "D1: %d, %s, %s", g_eStatusDrive1, g_sTrackDrive1, g_sSectorDrive1);
mvwprintw(status, 2, 2, "D2: %d, %s, %s", g_eStatusDrive2, g_sTrackDrive2, g_sSectorDrive2);
// approximate
const double frequency = 0.5 * alpha * F;
mvwprintw(status, 1, 20, "%5.fHz", frequency);
mvwprintw(status, 2, 20, "%5.1f%%", 100 * g_relativeSpeed);
wrefresh(status);
}
void FrameDrawDiskLEDS(HDC x)
{
DiskGetLightStatus(&g_eStatusDrive1, &g_eStatusDrive2);
FrameRefresh();
}
void FrameDrawDiskStatus(HDC x)
{
if (mem == NULL)
return;
// We use the actual drive since probing from memory doesn't tell us anything we don't already know.
// DOS3.3 ProDOS
// Drive $B7EA $BE3D
// Track $B7EC LC1 $D356
// Sector $B7ED LC1 $D357
// RWTS LC1 $D300
int nActiveFloppy = DiskGetCurrentDrive();
int nDisk1Track = DiskGetTrack(0);
int nDisk2Track = DiskGetTrack(1);
// Probe known OS's for Track/Sector
int isProDOS = mem[ 0xBF00 ] == 0x4C;
bool isValid = true;
// Try DOS3.3 Sector
if ( !isProDOS )
{
int nDOS33track = mem[ 0xB7EC ];
int nDOS33sector = mem[ 0xB7ED ];
if ((nDOS33track >= 0 && nDOS33track < 40)
&& (nDOS33sector >= 0 && nDOS33sector < 16))
{
/**/ if (nActiveFloppy == 0) g_nSectorDrive1 = nDOS33sector;
else if (nActiveFloppy == 1) g_nSectorDrive2 = nDOS33sector;
}
else
isValid = false;
}
else // isProDOS
{
// we can't just read from mem[ 0xD357 ] since it might be bank-switched from ROM
// and we need the Language Card RAM
// memrom[ 0xD350 ] = " ERROR\x07\x00" Applesoft error message
// T S
int nProDOStrack = *MemGetMainPtr( 0xC356 ); // LC1 $D356
int nProDOSsector = *MemGetMainPtr( 0xC357 ); // LC1 $D357
if ((nProDOStrack >= 0 && nProDOStrack < 40)
&& (nProDOSsector >= 0 && nProDOSsector < 16))
{
/**/ if (nActiveFloppy == 0) g_nSectorDrive1 = nProDOSsector;
else if (nActiveFloppy == 1) g_nSectorDrive2 = nProDOSsector;
}
else
isValid = false;
}
g_nTrackDrive1 = nDisk1Track;
g_nTrackDrive2 = nDisk2Track;
if( !isValid )
{
if (nActiveFloppy == 0) g_nSectorDrive1 = -1;
else g_nSectorDrive2 = -1;
}
snprintf( g_sTrackDrive1 , sizeof(g_sTrackDrive1 ), "%2d", g_nTrackDrive1 );
if (g_nSectorDrive1 < 0) snprintf( g_sSectorDrive1, sizeof(g_sSectorDrive1), "??" );
else snprintf( g_sSectorDrive1, sizeof(g_sSectorDrive1), "%2d", g_nSectorDrive1 );
snprintf( g_sTrackDrive2 , sizeof(g_sTrackDrive2), "%2d", g_nTrackDrive2 );
if (g_nSectorDrive2 < 0) snprintf( g_sSectorDrive2, sizeof(g_sSectorDrive2), "??" );
else snprintf( g_sSectorDrive2, sizeof(g_sSectorDrive2), "%2d", g_nSectorDrive2 );
FrameRefresh();
}
void FrameRefreshStatus(int x, bool)
{
// std::cerr << "Status: " << x << std::endl;
}
void VideoInitialize()
{
setlocale(LC_ALL, "");
initscr();
colors.reset(new GraphicsColors(20, 20, 32));
curs_set(0);
noecho();
cbreak();
set_escdelay(0);
frame.reset(new Frame());
asciiArt.reset(new ASCIIArt());
input.reset(new Input("/dev/input/by-id/usb-©Microsoft_Corporation_Controller_1BBE3DB-event-joystick"));
signal(SIGINT, sig_handler);
}
void VideoUninitialize()
{
endwin();
}
void VideoRedrawScreen()
{
VideoUpdateFlash();
updateSpeaker();
FrameRefresh();
const int displaypage2 = (SW_PAGE2) == 0 ? 0 : 1;
g_pHiresBank1 = MemGetAuxPtr (0x2000 << displaypage2);
g_pHiresBank0 = MemGetMainPtr(0x2000 << displaypage2);
g_pTextBank1 = MemGetAuxPtr (0x400 << displaypage2);
g_pTextBank0 = MemGetMainPtr(0x400 << displaypage2);
VideoUpdateFuncPtr_t update = SW_TEXT
? SW_80COL
? Update80ColCell
: Update40ColCell
: SW_HIRES
? (SW_DHIRES && SW_80COL)
? UpdateDHiResCell
: UpdateHiResCell
: (SW_DHIRES && SW_80COL)
? UpdateDLoResCell
: UpdateLoResCell;
int y = 0;
int ypixel = 0;
while (y < 20) {
int offset = ((y & 7) << 7) + ((y >> 3) * 40);
int x = 0;
int xpixel = 0;
while (x < 40) {
update(x, y, xpixel, ypixel, offset + x);
++x;
xpixel += 14;
}
++y;
ypixel += 16;
}
if (SW_MIXED)
update = SW_80COL ? Update80ColCell
: Update40ColCell;
while (y < 24) {
int offset = ((y & 7) << 7) + ((y >> 3) * 40);
int x = 0;
int xpixel = 0;
while (x < 40) {
update(x, y, xpixel, ypixel, offset + x);
++x;
xpixel += 14;
}
++y;
ypixel += 16;
}
wrefresh(frame->getWindow());
}
int ProcessKeyboard()
{
const int inch = wgetch(frame->getWindow());
int ch = ERR;
switch (inch)
{
case ERR:
break;
case '\n':
ch = 0x0d; // ENTER
break;
case KEY_BACKSPACE:
case KEY_LEFT:
ch = 0x08;
break;
case KEY_RIGHT:
ch = 0x15;
break;
case KEY_UP:
ch = 0x0b;
break;
case KEY_DOWN:
ch = 0x0a;
break;
case 0x14a: // DEL
ch = 0x7f;
break;
case 0x221: // ALT - LEFT
asciiArt->changeColumns(-1);
break;
case 0x230: // ALT - RIGHT
asciiArt->changeColumns(+1);
break;
case 0x236:
asciiArt->changeRows(-1);
break;
case 0x20D:
asciiArt->changeRows(+1);
break;
default:
if (inch < 0x80)
{
ch = inch;
// Standard for Apple II is Upper case
if (ch >= 'A' && ch <= 'Z')
{
ch += 'a' - 'A';
}
else if (ch >= 'a' && ch <= 'z')
{
ch -= 'a' - 'A';
}
}
}
if (ch != ERR)
{
nextKey = ch | 0x80;
keyReady = true;
return ERR;
}
else
{
// pass it back
return inch;
}
}
void ProcessInput()
{
input->poll();
}
BYTE KeybGetKeycode ()
{
return 0;
}
BYTE __stdcall KeybReadData (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
return nextKey;
}
BYTE __stdcall KeybReadFlag (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
BYTE result = keyReady ? nextKey : 0;
nextKey = 0;
return result;
}
BYTE __stdcall JoyReadButton(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
addr &= 0xFF;
BOOL pressed = 0;
switch (addr)
{
case 0x61:
pressed = input->getButton(0);
break;
case 0x62:
pressed = input->getButton(1);
break;
case 0x63:
break;
}
return MemReadFloatingBus(pressed, nCyclesLeft);
}
BYTE __stdcall JoyReadPosition(WORD pc, WORD address, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
const int nJoyNum = (address & 2) ? 1 : 0; // $C064..$C067
CpuCalcCycles(nCyclesLeft);
BOOL nPdlCntrActive = 0;
if (nJoyNum == 0)
{
int axis = address & 1;
int pdl = input->getAxis(axis);
// This is from KEGS. It helps games like Championship Lode Runner & Boulderdash
if (pdl >= 255)
pdl = 280;
nPdlCntrActive = g_nCumulativeCycles <= (g_nJoyCntrResetCycle + (unsigned __int64) ((double)pdl * PDL_CNTR_INTERVAL));
}
return MemReadFloatingBus(nPdlCntrActive, nCyclesLeft);
}
BYTE __stdcall JoyResetPosition(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
CpuCalcCycles(nCyclesLeft);
g_nJoyCntrResetCycle = g_nCumulativeCycles;
return MemReadFloatingBus(nCyclesLeft);
}
// Speaker
BYTE __stdcall SpkrToggle (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft)
{
CpuCalcCycles(nCyclesLeft);
updateSpeaker();
F += 1;
return MemReadFloatingBus(nCyclesLeft);
}