#line 1 "nekot1/alloc64.c"
#include "nekot1/private.h"

#define MAGIC64 0x1EE1

typedef struct header64 {
    gword magic;
    struct header64* next;
} H64;

H64* root64;

void* gAlloc64() {
    H64* ptr = gNULL;
    gbyte cc_value = gIrqSaveAndDisable();

    if (root64) {
        gAssert(root64->magic == MAGIC64);
        ptr = root64;
        root64 = root64->next;
        ptr->magic = 0;
    }

    gIrqRestore(cc_value);
    return ptr;
}

void gFree64(void* ptr) {
    if (!ptr) return;
    H64* h = (H64*) ptr;

    gbyte cc_value = gIrqSaveAndDisable();

    h->next = root64;
    h->magic = MAGIC64;
    root64 = h;

    gIrqRestore(cc_value);
}

void Reset64() {
    root64 = gNULL;
}

void Alloc64_Init() {
    memset_words(0x0200, 0x3F3F, 256);
    Reset64();
     for (gword p = 0x0200; p < 0x0400; p+=64) {
        gFree64((void*)p);
     }
}
#line 1 "nekot1/bonobo.c"
#if NET_TYPE_bonobo

#include "nekot1/private.h"

#define bCONTROL 0xFF68
#define bSTATUS 0xFF69
#define bDATA 0xFF6A

#define bOKAY   0
#define bNOTYET 1

// Low Level Hardware Interface

void bSendControl(gbyte c) {
    gbyte status;

    gPoke1(bCONTROL, c);

    for (gword i = 0; i < 55555; i++) {
        status = gPeek1(bCONTROL);

        // Good status is 'b' for bonobo.
        if (status == 'b') {
            return;
        }
    }
    gFatal("BONOBO", status);
}

void bSendData(const gbyte *p, gword n) {
    for (gword i = 0; i < n; i++) {
        gPoke1(bDATA, p[i]);
    }
}

void bReceiveData(gbyte *p, gword n) {
    for (gword i = 0; i < n; i++) {
        p[i] = gPeek1(bDATA);
    }
}

// Mid Level Operations

gword bQueryAvailableFromMcp() {
    gword x;
    bSendControl(250);
    bReceiveData((gbyte*)&x, sizeof x);
    return x;
}

void bReadFromMcp(gbyte* p, gword n) {
    gAssert(1 <= n);
    gAssert(n <= 100);
    bSendControl(n);
    bReceiveData(p, n);
}

void bWriteToMcp(const gbyte* p, gword n) {
    gAssert(1 <= n);
    gAssert(n <= 100);

    bSendControl(n + 100);
    bSendData(p, n);
    bSendControl(251);  // Push
}

// High Level Interface

errnum BonoboRecvChunkTry( gbyte* buf, gword n) {
    gbyte cc_value = gIrqSaveAndDisable();
    gAssert(1 <= n);
    gAssert(n <= 100);

    gword available = bQueryAvailableFromMcp();
    if (n > available) {
        gIrqRestore(cc_value);
        return bNOTYET;
    }
    bReadFromMcp(buf, n);
    gIrqRestore(cc_value);
    return bOKAY;
}

void BonoboSend(const gbyte* addr, gword n) {
    gbyte cc_value = gIrqSaveAndDisable();
    gAssert(1 <= n);
    gAssert(n <= 100);
    bWriteToMcp(addr, n);
    gIrqRestore(cc_value);
}

void Bonobo_Init() {
    bSendControl(252);  // Probe for Bonobo Hardware
    PutChar('B');
}

#endif // NET_TYPE_bonobo
#line 1 "nekot1/breakkey.c"
#include "nekot1/private.h"

static void DoBreak(void) {
    if (gKern.focus_game) {
        // FOCUS IS IN GAME.
        // Switch focus to chat.
        gKern.focus_game = gFALSE;
        NowSwitchToChatScreen();
    } else {
        // FOCUS IS IN CHAT.
        if (gKern.in_game) {
            // Switch focus to game.
            gKern.focus_game = gTRUE;
            NowSwitchToGameScreen();
        } else {
            // Probaby already in chat, but force it again.
            gKern.focus_game = gFALSE;
            NowSwitchToChatScreen();
        }
    }
}

// For Coco1, 2, or 3 keyboard.
#define BREAKKEY_PROBE_BIT 0x04
#define BREAKKEY_SENSE_BIT 0x40

// Break_Handler is called on interrupt.
void Breakkey_Handler(void) {
    // Place the keyboard probe signal for BREAK
    const gbyte probe = ~(gbyte)BREAKKEY_PROBE_BIT;
    gPoke1(Pia0PortB, probe);

    // Read the sense port and check the bit.
    gbyte sense = gPeek1(Pia0PortA);

    // Debounce.
    if ((sense & BREAKKEY_SENSE_BIT) == 0) {
        // BREAK KEY DOWN
        if (!Breakkey.break_key_was_down) {
            Breakkey.break_key_was_down = gTRUE;
            DoBreak();
        }
        SpinBreakkey();
    } else {
        // BREAK KEY UP
        Breakkey.break_key_was_down = gFALSE;
    }
}
#line 1 "nekot1/config.c"
#include "nekot1/private.h"

// config.c


struct config gConfig = {
    .ram_limit = RAM_LIMIT,
#if NET_TYPE_cocoio
    .net_type = "cocoio",
#endif
#if NET_TYPE_bonobo
    .net_type = "bonobo",
#endif
};
#line 1 "nekot1/console.c"
#include "nekot1/private.h"

#include <stdarg.h>

#define SLOW_CONSOLE 0
gword volatile slow_her_down;

static void AdvanceCursor() {
    ++Console.cursor;
    while (Console.cursor >= PANE_LIMIT) {
        // Scroll Pane upward
        for (gword p = PANE_BEGIN; p < PANE_LIMIT-32; p+=2) {
            gPoke2(p, gPeek2(p+32));
        }
        // Clear bottom Pane line
        for (gword p = PANE_LIMIT-32; p < PANE_LIMIT; p+=2) {
            gPoke2(p, 0x2020);
        }
        // Move cursor back into bottom Pane line.
        Console.cursor -= 32;
    }
    gPoke1(Console.cursor, 0xFF);
#if SLOW_CONSOLE
    for (gword i = 0; i < SLOW_CONSOLE; i++) {
        slow_her_down++;
    }
#endif
}

void PutRawByte(gbyte x) {
    gPoke1(Console.cursor, x);
    AdvanceCursor();
}
void PutChar(char c) {
    gPoke1(Console.cursor, 0x20);

    gbyte x = (gbyte)c; // Unsigned!
    if (x == '\n') {
            while (Console.cursor < PANE_LIMIT-1) {
                PutChar(' ');
            }
            PutChar(' ');
    } else if (x < 32) {
        // Ingore other control chars.
    } else if (x < 96) {
            PutRawByte(63&x);  // Upper case.
    } else if (x < 128) {
            PutRawByte(x-96); // Lower case.
    } else {
            PutRawByte(x);  // Semigraphics.
    }
}

void PutStr(const char* s) {
    for (; *s; s++) {
        PutChar(*s);
    }
}

#if 1
char HexAlphabet[] = "0123456789ABCDEF";

void PutHex(gword x) {
  if (x > 15u) {
    PutHex(x >> 4u);
  }
  PutChar(HexAlphabet[15u & x]);
}
#endif
gbyte DivMod10(gword x, gword* out_div) {  // returns mod
  gword div = 0;
  while (x >= 10000) x -= 10000, div += 1000;
  while (x >= 1000) x -= 1000, div += 100;
  while (x >= 100) x -= 100, div += 10;
  while (x >= 10) x -= 10, div++;
  *out_div = div;
  return (gbyte)x;
}
void PutDec(gword x) {
  gword div;
  if (x > 9u) {
    // eschew div // PutDec(x / 10u);
    DivMod10(x, &div);
    PutDec(div);
  }
  // eschew mod // PutChar('0' + (gbyte)(x % 10u));
  PutChar('0' + DivMod10(x, &div));
}
#if 1
void PutSigned(int x) {
    if (x<0) {
        x = -x;
        PutChar('-');
    }
    PutDec(x);
}
#endif
#if 1
void Printf(const char* format, ...) {
    gbyte cc_value = gIrqSaveAndDisable();

    va_list ap;
    va_start(ap, format);

    for (const char* s = format; *s; s++) {
        if (*s < ' ') {
            PutChar('\n');
        } else if (*s != '%') {
            PutChar(*s);
        } else {
            s++;
            switch (*s) {
            case 'd':
#if 0
                {
                    int x = va_arg(ap, int);
                    PutSigned(x);
                }
                break;
#endif
            case 'u':
                {
                    gword x = va_arg(ap, gword);
                    PutDec(x);
                    PutChar('.');
                }
                break;
#if 1
            case 'x':
                {
                    gword x = va_arg(ap, gword);
                    PutChar('$');
                    PutHex(x);
                }
                break;
#endif
            case 's':
                {
                    char* x = va_arg(ap, char*);
                    PutStr(x);
                }
                break;
            default:
                PutChar(*s);
            }; // end switch
       }  // end if
    }
    gIrqRestore(cc_value);
}
#endif

void Console_Init() {
#if 1
    Console.cursor = PANE_LIMIT - 32;
    PutStr(" \n\n");
    gPoke1(Console.cursor, 0xFF);
#endif
    // gPoke2(0, AdvanceCursor);

    // Draw a greenish bar across the top of the Console.
    for (gword p = CONSOLE_BEGIN; p < PANE_BEGIN; p+=2) {
        gPoke2(p, 0x8C8C);  // greenish (in RGB or Composite) top bar
    }
#if 0
    // Fill the body of the screen with spaces.
    for (gword p = PANE_BEGIN; p < PANE_LIMIT; p+=2) {
        gPoke2(p, 0x2020);
    }
#endif
    // Draw a blueish bar across the bottom of the Console.
    for (gword p = PANE_LIMIT; p < CONSOLE_LIMIT; p+=2) {
        gPoke2(p, 0xA3A3);  // blueish (in RGB or Composite) bottom bar
    }
    // Console.cursor = PANE_LIMIT - 32;
    gPoke1(Console.cursor, 0xFF);
}
#line 1 "nekot1/irq.c"
#include "nekot1/private.h"

void Network_Handler(void);
void KeyboardHandler(void);

gfunc Irq_FocusGameSchedule[6] = {
    Network_Handler,
    Breakkey_Handler,

    Network_Handler,
    Breakkey_Handler,

    Network_Handler,
    Breakkey_Handler,
};

gfunc Irq_PassiveGameSchedule[6] = {
    Network_Handler,
    Breakkey_Handler,

    Network_Handler,
    KeyboardHandler,

    Network_Handler,
    KeyboardHandler,
};

gfunc Irq_FocusShellSchedule[6] = {
    KeyboardHandler,
    Breakkey_Handler,

    KeyboardHandler,
    Breakkey_Handler,

    KeyboardHandler,
    Breakkey_Handler,
};

void Irq_Handler() {
    gKern.in_irq = gTRUE;
    SpinIrq();

    // Clear the VSYNC IRQ by reading PortB output register.
    gword const clear_irq = Pia0PortB;
    (void) gPeek1(clear_irq);

    Real_IncrementTicks();
    Breakkey_Handler();

    gAssert(gReal.ticks < 6);
    if (gKern.focus_game) {
// Console_Printf("F");
        Irq_FocusGameSchedule[gReal.ticks]();
    } else if (gKern.in_game) {
        Irq_PassiveGameSchedule[gReal.ticks]();
    } else {
// Console_Printf("$");
        Irq_FocusShellSchedule[gReal.ticks]();
    }

    gKern.in_irq = gFALSE;
}

void Irq_Handler_Wrapper() {
    asm volatile("\n"
        "  .globl _Irq_Handler_entry  \n"
        "_Irq_Handler_entry:  \n"
        "  JSR _Irq_Handler  \n"
        "  RTI  \n");
    gPoke2(0, Irq_Handler);
}
#line 1 "nekot1/kern.c"
#include "nekot1/private.h"

// kern.c

void FatalSpin(const char *why) {
    volatile gbyte* p = (volatile gbyte*) Cons;

    // Work around GCC infinite loop bug.
    while (gALWAYS) {
        gPoke2(p, gPeek2(p) + 1);
    }
}

void gFatal(const char* why, gword arg) {
    gDisableIrq();
    // Reify things
    gPoke2(0, &gFatal);
    gPoke2(0, &why);
    gPoke2(2, &arg);

    NowSwitchToChatScreen();
    PutStr("\nFATAL ");
    PutDec(arg);
    PutStr(": ");
    PutStr(why);
    PutStr("\n$");

    const gbyte* p = (const gbyte*)gPeek2(4);
    PutHex( (gword)p );
    PutStr(": ");
    for (gbyte i = 0; i < 32; i++) {
        PutHex(p[i]);
        if ((i&3)==3) PutChar(',');
        if ((i&15)==15) PutChar(';');
        PutChar(' ');
    }
    PutChar('$');
    FatalSpin(why);
}

void gFatalSWI1() {
    asm volatile("sts %0" :: "m" (gKern.saved_stack_pointer));
    gFatal("SWI", gKern.saved_stack_pointer);
}
void gFatalSWI2() {
    asm volatile("sts %0" :: "m" (gKern.saved_stack_pointer));
    gFatal("SWI2", gKern.saved_stack_pointer);
}
void gFatalSWI3() {
    asm volatile("sts %0" :: "m" (gKern.saved_stack_pointer));
    gFatal("SWI3", gKern.saved_stack_pointer);
}
void gFatalNMI() {
    asm volatile("sts %0" :: "m" (gKern.saved_stack_pointer));
    gFatal("NMI", gKern.saved_stack_pointer);
}
void gFatalFIRQ() {
    asm volatile("sts %0" :: "m" (gKern.saved_stack_pointer));
    gFatal("FIRQ", gKern.saved_stack_pointer);
}

// StartTask begins the given function entry,
// which must never return,
// as a foreground task,
// whether or not we were in the IRQ handler
// when we got here.
// Unless the entry is ChatTask, it starts 
// with in_game mode set to gTRUE.
void StartTask(gword entry) {
    gDisableIrq();

    if (!entry) {
        entry = (gword)ChatTask;
    }

    // Zero the Game's BSS.
    memset_words(GAME_BSS_BEGIN, 0, (GAME_BSS_LIMIT-GAME_BSS_BEGIN)>>1);

    if (entry == (gword)ChatTask) {
        gKern.in_game = gFALSE;
        gKern.focus_game = gFALSE;
    } else {
        gKern.in_game = gTRUE;
        gKern.focus_game = gTRUE;
        // Until the game changes the display,
        // you get an Orange Console.
        gTextScreen(Cons, COLORSET_ORANGE);
    }

    // Set SWI Traps at start of memory (scratchpad).
    memset_words(0x0000u, 0x3F3F, 8);
    // TODO: between app's low water and high water.
#if 0
    // Breaks on 16K coco2:
    memset_words(0x4000u, 0x3F3F, (0xFE00u - 0x4000u)>>1);
#endif

    Alloc64_Init();  // Eight chunks of 64 to be ready.

    gKern.in_irq = gFALSE;
    asm volatile("\n"
        "  ldx   %0      \n"  // entry point
        "  lds   #$01FE  \n"  // Reset the stack
        "  andcc #^$50   \n"  // Allow interrupts.
        "  jmp   ,X      \n"  // There is no way back.
        : // outputs
        : "m" (entry) // input %0
    );
    // Never returns.
}

gbyte gIrqSaveAndDisable() {
    gbyte cc_value;
    asm volatile("\n"
        "  tfr cc,b  \n"
        "  stb %0  \n"
        "  orcc #$10  \n"
        : "=m" (cc_value)  // outputs
        :  // inputs
        : "b"  // Clobbers B
    );
    return cc_value;
}

void gIrqRestore(gbyte cc_value) {
    asm volatile("\n"
        "  ldb %0  \n"
        "  tfr b,cc  \n"
        :  // outputs
        : "m" (cc_value)  // inputs
        : "b", "cc"  // clobbers
    );
}

void xAfterSetup(gfunc loop, gword* final_, gword* final_startup) {
    // Prove that setup is no longer used.
    for (gword w = 2+(gword)final_; w < (gword)final_startup; w++) {
        gPoke1(w, 0x3F);  // a bunch of SWI traps.
    }

    asm volatile("\n"
        "  ldx   %0           \n"  // get parameter `loop` before resetting the stack.
        "  lds   #$01FE       \n"  // Reset the stack!
        "  pshs  X            \n"  // Save X
        "LOOP_FOREVER:        \n"
        "  ldx   ,S           \n"  // Restore X
        "  jsr   ,X           \n"  // Call loop,
        "  bra  LOOP_FOREVER  \n"  // repeatedly.
        : // outputs
        : "m" (loop) // inputs
    );
    // Never returns.
}

void ChatTask() {
    // debugging blocks
    // volatile gbyte* p = (volatile gbyte*)Cons;
    // while (gKern.always_true) { p[33]++; }
    // while (!gKern.always_true) { p[35]++; }
    
    NowSwitchToChatScreen();

    while (gALWAYS) {
        gAssert(!gKern.in_game);
        gAssert(!gKern.in_irq);

        CheckReceived();
        SpinChatTask();
    }
}

// Only in Game Mode
void Network_Handler() {
    gAssert(gKern.in_game);
    CheckReceived();
}

void Kern_Init() {
    gKern.in_game = gFALSE;
    gKern.focus_game = gFALSE;
    gKern.in_irq = gFALSE;
    gKern.always_true = gTRUE;
}
#line 1 "nekot1/keyboard.c"
#include "nekot1/private.h"

static gbool ScanReturnChanged(gbyte* p, gbyte* prev) {
    const gword out_port = Pia0PortB;
    const gword in_port = Pia0PortA;
    gbool z = gFALSE;

    gbyte sense = 0x01;
    while (sense) {
        gPoke1(out_port, ~(gbyte)sense);
        *p = 0x7F & ~gPeek1(in_port);
        if (*p++ != *prev++) {
            z = gTRUE; // found a difference.
        }
        sense <<= 1;
    }
    SpinKeyboardScan();
    return z;
}

static void SendKeyboardPacket(gbyte* p) {
    struct quint q = {NEKOT_KEYSCAN, 8, 0};
    NET_Send( (gbyte*) &q, sizeof q );
    NET_Send( p, 8 );
}

void KeyboardHandler() {
    if (gKern.focus_game) return;
    gAssert(gKern.in_irq);

    gbyte current = Keyboard.current_matrix;
    gbyte other = !current;

    gbool changed = ScanReturnChanged(Keyboard.matrix[current], Keyboard.matrix[other]);
    if (changed) {
        SendKeyboardPacket(Keyboard.matrix[current]);
    }

    Keyboard.current_matrix = other;
}
#line 1 "nekot1/main.c"
#include "nekot1/private.h"

// main.c

// Use our alternate data sections.
gword _More0 gZEROED; // not .bss
gword _More1 gZEROED = 0x9998; // not .data
gword _Final __attribute__ ((section (".final"))) = 0x9990;
gword _Final_Startup __attribute__ ((section (".final.startup"))) = 0x9991;

// Hardware Reference -- Thanks, Tim!
//     https://tlindner.macmess.org/?page_id=767

// pia_reset_sequence table traced from coco3 startup.
struct pia_reset_sequence {
    gword addr;
    gbyte value;
} pia_reset_sequence[] gSETUP_DATA = {
    { 0xff21, 0x00 },  // choose data direction
    { 0xff23, 0x00 },  // choose data direction
    { 0xff20, 0xfe },  // bit 0 input; rest are outputs.
    { 0xff22, 0xfa },  // bit 1 and bits 3-7 are outputs.
    { 0xff21, 0x34 },
    { 0xff23, 0x34 },
    { 0xff22, 0x00 },  // output all 0s on Pia1 PortB
    { 0xff20, 0x02 },
    { 0xff01, 0x00 },  // choose data direction
    { 0xff03, 0x00 },  // choose data direction
    { 0xff00, 0x00 },  // inputs
    { 0xff02, 0xff },  // outputs
    { 0xff01, 0x34 },
    { 0xff03, 0x34 },
    { 0 }
};


// gime_reset_sequence table traced from coco3 startup.
struct gime_reset_sequence {
    gword addr;
    gbyte value;
} gime_reset_sequence[] gSETUP_DATA = {
// Generated by copico-bonobo/v2.4/firmware/c$ py gime_init_gen.py 
  { 0xff90, 0xcc },
  { 0xff91, 0x00 },
  { 0xff92, 0x00 },
  { 0xff93, 0x00 },
  { 0xff94, 0xff },
  { 0xff95, 0xff },
  { 0xff96, 0x00 },
  { 0xff97, 0x00 },
  { 0xff98, 0x00 },
  { 0xff99, 0x00 },
  { 0xff9a, 0x00 },
  { 0xff9b, 0x00 },
  { 0xff9c, 0x0f },
  { 0xff9d, 0xe0 },
  { 0xff9e, 0x00 },
  { 0xff9f, 0x00 },
  { 0xffa0, 0x38 },
  { 0xffa1, 0x39 },
  { 0xffa2, 0x3a },
  { 0xffa3, 0x3b },
  { 0xffa4, 0x3c },
  { 0xffa5, 0x3d },
  { 0xffa6, 0x3e },
  { 0xffa7, 0x3f },
  { 0xffa8, 0x38 },
  { 0xffa9, 0x30 },
  { 0xffaa, 0x31 },
  { 0xffab, 0x32 },
  { 0xffac, 0x33 },
  { 0xffad, 0x3d },
  { 0xffae, 0x35 },
  { 0xffaf, 0x3f },
  { 0xffb0, 0x12 },
  { 0xffb1, 0x24 },
  { 0xffb2, 0x0b },
  { 0xffb3, 0x07 },
  { 0xffb4, 0x3f },
  { 0xffb5, 0x1f },
  { 0xffb6, 0x09 },
  { 0xffb7, 0x26 },
  { 0xffb8, 0x00 },
  { 0xffb9, 0x12 },
  { 0xffba, 0x00 },
  { 0xffbb, 0x3f },
  { 0xffbc, 0x00 },
  { 0xffbd, 0x12 },
  { 0xffbe, 0x00 },
  { 0xffbf, 0x26 },
  { 0 }
};

void Delay(gword n) {
    for (gword i = 0; i < n; i++) {
        asm volatile(" mul \n mul \n mul \n mul \n mul" : : : "d");
    }
}

void ColdPrint(const char* s) {
    // PutStr("\xF5 ");
    PutStr("*** ");
    PutStr(s);  
    PutChar('\n');

    // Assuming PIA0 is already set up for keyboard scanning:
    gPoke1(KEYBOARD_PROBE, ~(1<<4)); // Probe for Down Arrow
    gbyte sense = ((1<<3) & ~gPeek1(KEYBOARD_SENSE));
    if (sense) {
        Delay(15000);
    } else {
        Delay(1000);
    }
}

#if 0
void SamInit() {
    // ram_sizer marks the extra SAM bit that sets our ram size.
    gword ram_sizer = 0;

    // SAM has a setting for 4K, 16K, or 64K.
    // Probe for keys 0 (4K) and 1 (64K).  Default is 16K.
    gPoke1(KEYBOARD_PROBE, ~1); // probe for '0'
    gbyte zero_key = ~(0x10 & gPeek1(KEYBOARD_SENSE)); // sense for '0'

    gPoke1(KEYBOARD_PROBE, ~2); // probe for '0'
    gbyte one_key = ~(0x10 & gPeek1(KEYBOARD_SENSE)); // sense for '1'

    if (zero_key) {
        // M=00 for 4K
        ram_sizer = 0;  // No extra bit.
    } else if (one_key) {
        // M=10 for 64K
        ram_sizer = 0xFFDC;
    } else {
        // M=01 for 16K
        ram_sizer = 0xFFDA;
    }

    // For p the even numbers in the magic SAM range:
    for (gword p = 0xFFC0; p < 0xFFE0; p+=2) {
        // FFD[EF] should be FFDF for "TY=1" for "all ram, no rom".
        gbyte yes = (p == 0xFFDE || p == ram_sizer); 
        gPoke1(p+yes, 0);
    }
}
#endif

void entry_wrapper() {
    asm volatile("\n"
        "  .globl entry \n"
        "entry:         \n"
        "  orcc #$50    \n"  // No IRQs, FIRQs, for now.
        "  lds #$01FE   \n"  // Reset the stack
        "  jmp _main    \n"
        );
}

extern void embark(void);

gword PinDownGlobalNames[] gSETUP_DATA = {
    // Taking the address of `embark` prevents it from being
    // inlined inside main.  Only things inlined inside main
    // are in the area called `.text.startup` which gets
    // recycled after setup, when we embark.
    (gword) embark,

    // Taking the address of other things in the kernel
    // prevents them from being optimized away.
    // Things that might be referenced by games
    // need to exist in the compiled kernel!
    (gword) Breakkey_Handler,
    (gword) Irq_Handler,
    (gword) Irq_Handler_entry,
    (gword) Irq_Handler_Wrapper,
    (gword) Network_Handler,
    (gword) gAlloc64,
    (gword) gFree64,
    (gword) Reset64,
    (gword) gSendCast,
    (gword) gReceiveCast64,

    (gword) gTextScreen,
    (gword) gPMode1Screen,
    (gword) gModeScreen,
    (gword) xAfterSetup,
    (gword) xSendControlPacket,
    (gword) gNetworkLog,
    (gword) gFatal,
    (gword) PutStr,
    (gword) PutChar,

    (gword) memset,
    (gword) memcpy,
    (gword) memset_words,
    (gword) memcpy_words,
    (gword) strlen,

    (gword) entry_wrapper,
    (gword) &_More0,
    (gword) &_More1,
    (gword) &_Final,
    (gword) &_Final_Startup,
    (gword) &gScore,
    (gword) &gReal,
    (gword) &gWall,
    (gword) &gConfig,
};

void PlaceOpcodeJMP(gword at, gfunc to) {
    gPoke1(at+0, OPCODE_JMP_Extended);
    gPoke2(at+1, to);
}

// Interrupt Relays
gword coco2_relays[] gSETUP_DATA = {
    0x0100,
    0x0103,
    0x010F,
    0x010C,
    0x0106,
    0x0109,
};
gword coco3_relays[] gSETUP_DATA = {
    0xFEEE,
    0xFEF1,
    0xFEF4,
    0xFEF7,
    0xFEFA,
    0xFEFD,
};
gfunc handlers[] gSETUP_DATA = {
    gFatalSWI3,
    gFatalSWI2,
    gFatalFIRQ,
    Irq_Handler_entry,
    gFatalSWI1,
    gFatalNMI,
};

char StrNekotOS[] gSETUP_DATA = "NEKOTOS ... ";
char StrReady[] gSETUP_DATA = " READY";

void SplashRestore(int x, int y) {
    if (x < 0) return;
    if (y < 0) return;
    if (x >= 32) return;
    if (y >= 16) return;
    gPoke1(Cons + 32*y + x, gPeek1(0x200 + 32*y + x));
}

void SplashSet(int x, int y) {
    if (x < 0) return;
    if (y < 0) return;
    if (x >= 32) return;
    if (y >= 16) return;
    gPoke1(Cons + 32*y + x, 0xFF);
}

void Splash() {
    memcpy_words(0x0200, 0x0400, 256);

        for (int i = 0; i < 16; i++) {
            for (int x = 16 - i; x < 16 + i; x++) {
                int y1 = 8 - i;
                int y2 = 8 + i;
                SplashSet(x, y1);
                SplashSet(x, y2);
            }
            for (int y = 8 - i; y < 8 + i; y++) {
                int x1 = 16 - i;
                int x2 = 16 + i;
                SplashSet(x1, y);
                SplashSet(x2, y);
            }
            Delay(100);

            for (int x = 16 - i; x < 16 + i; x++) {
                int y1 = 8 - i;
                int y2 = 8 + i;
                SplashRestore(x, y1);
                SplashRestore(x, y2);
            }
            for (int y = 8 - i; y < 8 + i; y++) {
                int x1 = 16 - i;
                int x2 = 16 + i;
                SplashRestore(x1, y);
                SplashRestore(x2, y);
            }
        }
}

void setup(void) {
    memset_words(0x0000, 0, 0x40); // .bss
    for (struct pia_reset_sequence *p = pia_reset_sequence; p->addr; p++) {
        gPoke1(p->addr, p->value);
    }
    gPoke1(0xFF90, 0x88);
    gPoke1(0xFF91, 0x00);

    //Delay(10000);
    // Sam Pre-Init to TEXT AT 0x0400 for Cold Boot
#if 1
    for (gword p = 0xFFC0; p < 0xFFD4; p+=2) {
        gbyte yes = (p == 0xFFC8);
        gPoke1(p+yes, 0);
    }
#endif
    //Delay(10000);
    Splash();

    Console_Init();
    ColdPrint("INIT");

#if 0
    Vdg_Init();
    ColdPrint("VDG");
#endif

    // --------- BEGIN COLD SCREEN --------

    //memset_words(0x0000, 0x3020, 0x40);   // Initial Spoonfed Screen
    //memset_words(0x0080, 0xF3F3, 16);
    //memset_words(0x00A0, 0x2D2D, 32);
    //memset_words(0x00E0, 0xFCFC, 16);
    // memset_words(0x0200, 0x3320, 0x100);  // Eventual NekotOS Console.
    // memset_words(0x0400, 0x3420, 0x80);  // ROM boot screen.
    // Delay(10000);
    ColdPrint(StrNekotOS);

    // Redirect the 6 Interrupt Relays to our handlers.
    for (gbyte i = 0; i < 6; i++) {
        PlaceOpcodeJMP(coco2_relays[i], handlers[i]);
        PlaceOpcodeJMP(coco3_relays[i], handlers[i]);
    }
    ColdPrint("VECT");

    // Coco2 16K, CocoIO:         Works with D5 or D5.
    // Coco3 (6309/512K) CocoIO:  Works with D4 or D5.

    // Coco2 16K, Bonobo:         Works with D5 or D5.
    // Coco3 (6309/512K) Bonobo:  Works with D4 or D5.

    // D4 chooses "lower 32K" for $0000-$7FFF.
    // D5 chooses "upper 32K" for $0000-$7FFF.
    if (0) { // D5?
        gPoke1(0xFFD5, 0);
        ColdPrint("D5");
    } else {
        gPoke1(0xFFD4, 0);
        ColdPrint("D4");
    }

    gPoke1(0xFFD6, 0);  // slow CPU speed.
    ColdPrint("D6");
    gPoke1(0xFFD8, 0);
    ColdPrint("D8");

    // DA and DC are for memory size.
    gPoke1(0xFFDB, 0);
    ColdPrint("DB");
    gPoke1(0xFFDC, 0);
    ColdPrint("DC");

    gPoke1(0xFFDF, 0);
    ColdPrint("DF");  // all RAM

    for (struct gime_reset_sequence *p = gime_reset_sequence; p->addr; p++) {
        gPoke1(p->addr, p->value);
    }
    ColdPrint("GIME");
    // Coco3 in Compatibility Mode.
    gPoke1(0xFF90, 0x88);
    gPoke1(0xFF91, 0x00);
    ColdPrint("COMPAT");

    // memset_words(0x0200, 0, 0x80); // vdg console p1
    // memset_words(0x0300, 0, 0x80); // vdg console p2
    ColdPrint("ZERO");

    // The post-linker puts Version Hash at $0118.
    // Copy Version Hash down to page 0, at $0018.
    memcpy((void*)0x0018, (void*)0x0118, 8);
    ColdPrint("HASH");

    Kern_Init();
    ColdPrint("KERN");

    Vdg_Init();
    ColdPrint("VDG");

    // --------- END COLD SCREEN --------
    // X Console_Init();
    // X Vdg_Init();

    ColdPrint(StrNekotOS);

    // memset_words(0x0400, 0, 0x80); // chunks of 64-gbyte
    // Alloc64_Init();  // first 4 chunks in 0x04XX.

    Spin_Init();
    Network_Init();
    HelloMCP();

    // gPeek1(KEYBOARD_PROBE);  // Clear VSYNC IRQ // TODO
    gPoke1(0xFF03, 0x35);    // +1: Enable VSYNC (FS) IRQ

    ColdPrint(StrReady);
    Delay(3000);
}

void embark(void) {
    // Wipe out the setup/startup code, to prove it is never needed again.
    memset_words( (gword)&_Final, 0x3F3F, ((gword)&_Final_Startup - (gword)&_Final)>>1);

    StartTask((gword)ChatTask); // Start the no-game task.
}

int main() {
    gPin(entry_wrapper);
    gPin(PinDownGlobalNames);

    setup();
    embark();

    // NOT REACHED
    gFatal("MAIN", 0);
}
#line 1 "nekot1/network.c"
#include "nekot1/private.h"

// SendPacket sends a Quint Header and a payload
// of 0 to 64 bytes.
void SendPacket(gbyte cmd, gword p, const gbyte* pay, gbyte size) {
    size = (size > 64) ? 64 : size;

    gbyte qbuf[5];
    qbuf[0] = cmd;
    gPoke2(qbuf+1, size);
    gPoke2(qbuf+3, p);

    gbyte cc_value = gIrqSaveAndDisable();
    NET_Send(qbuf, 5);
    NET_Send(pay, size);
    gIrqRestore(cc_value);
}

// gSendCast is used by games to send gamecasts to other cocos
// in the same game shard.  The size is the payload size.
// It does not count the first two bytes of the struct,
// sender & flags, which are set by the system.
// The receiver is not told the payload size that was sent.
// So use (say) the first byte of the payload for the payload size,
// if you want the receiver to know it.
void gSendCast(const struct gamecast* pay, gbyte size) {
    gAssert(size >= 0);
    gAssert(size <= 60);
    size += 2;  // for two header gbytes.
    SendPacket(NEKOT_GAMECAST, 0, (const gbyte*)pay, size);
}

// Games call this indirectly to send control packets
// to the MCP.
void xSendControlPacket(gword p, const gbyte* pay, gword size) {
    SendPacket(NEKOT_CONTROL, p, pay, size);
}

// Games can log a message with the network.
// Dont spam it too badly!
void gNetworkLog(const char* s) {
    SendPacket(CMD_LOG, 0, (const gbyte*)s, strlen(s));
}

gbool need_recv_payload;
gbool need_to_start_task;
gword task_to_start;

struct gamecast *recvcast_root;

void ExecuteReceivedCommand(const gbyte* quint) {
    gbyte cmd = quint[0];
    gword n = gPeek2(quint+1);
    gword p = gPeek2(quint+3);

    if (cmd == CMD_DATA) {
        // If we ever send CMD_ECHO, expect CMD_DATA.
    } else if (cmd == NEKOT_MEMCPY) { // 65
        gbyte six[6];
        gAssert(n==6);
        errnum e2 = NET_RecvChunkTry((gbyte*)six, n);
        if (e2==NOTYET) return;  // do not let need_recv_payload get falsified.
        if (e2) gFatal("E-M",e2);

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"

        void* dst = (void*)gPeek2(six);
        void* src = (void*)gPeek2(six+2);
        gword siz = gPeek2(six+4);
        memcpy((char*)dst, (char*)src, siz);

#pragma GCC diagnostic pop

    } else if (cmd == NEKOT_POKE) { // 66
        errnum e3 = NET_RecvChunkTry((gbyte*)p, n);
        if (e3==NOTYET) return;  // do not let need_recv_payload get falsified.
        if (e3) gFatal("E-P",e3);

    } else if (cmd == NEKOT_CALL) { // 67
        gAssert(n==0);
        gfunc fn = (gfunc)p;
        fn();

    } else if (cmd == NEKOT_LAUNCH) { // 68
        gAssert(n==0);
        task_to_start = p;
        need_to_start_task = gTRUE;

    } else if (cmd == NEKOT_GAMECAST) { // 71
        gbyte cc_value = gIrqSaveAndDisable();

        struct gamecast* chunk = (struct gamecast*) gAlloc64();
        if (!chunk) {
            gFatal("RECV CAST NOMEM", 0);
        }
        gAssert(2 <= n);   // two mandatory header bytes, and
        gAssert(n <= 62);  // up to 60 payload bytes.

        errnum e4 = NET_RecvChunkTry(chunk->payload, n);
        if (e4==NOTYET) {
            gIrqRestore(cc_value);
            return;  // do not let need_recv_payload get falsified.
        }
        if (e4) gFatal("E-C",e4);

        // Need to append the chunk to the end of the chain!
        chunk->next = gNULL;
        if (recvcast_root) {
            struct gamecast* ptr;
            // Find the end of the chain, which has no ->next.
            for (ptr = recvcast_root; ptr->next; ptr = ptr->next) {}
            // The new chunk is now the ->next.
            ptr->next = chunk;
        } else {
            // No chunks in the list yet, so we become the first.
            recvcast_root = chunk;
        }

        chunk->next = recvcast_root;
        recvcast_root = chunk;

        gIrqRestore(cc_value);
    } else {
        gFatal("XRC", cmd);
    }

    need_recv_payload = gFALSE;
}

void CheckReceived() {
    gbyte cc_value = gIrqSaveAndDisable();
    gbyte quint[5];

    if (!need_recv_payload) {
        gbyte err = NET_RecvChunkTry(quint, 5);
        if (err==NOTYET) goto RESTORE;
        if (err) gFatal("RECV", err);
        need_recv_payload = gTRUE;
    }

#if NETWORK_CLICK
    gPoke1(0xFF22, Vdg.shadow_pia1portb | 0x02);  // 1-bit click
#endif
    ExecuteReceivedCommand(quint);
#if NETWORK_CLICK
    gPoke1(0xFF22, Vdg.shadow_pia1portb | 0x00);  // 1-bit click
#endif

    if (need_to_start_task) {
        need_to_start_task = gFALSE;
        StartTask(task_to_start);
        // Note StartTask never returns.
        // It will restart the stack, allow IRQs, and launch the task.
    }

RESTORE:
    gIrqRestore(cc_value);
}

struct gamecast* gReceiveCast64() {
    struct gamecast *ptr = gNULL;

    gbyte cc_value = gIrqSaveAndDisable();
    if (recvcast_root) {
        ptr = recvcast_root;
        recvcast_root = ptr->next;
    }
    gIrqRestore(cc_value);

    ptr->next = gNULL;
    return ptr;
}

#define DOUBLE_BYTE(W)  (gbyte)((gword)(W) >> 8), (gbyte)(gword)(W)

void HelloMCP() {
    gbyte hello[] = {
        'n', 'e', 'k', 'o', 't', '1', '\0', '\0',
        DOUBLE_BYTE(Cons),
        DOUBLE_BYTE(gMAX_PLAYERS),
        DOUBLE_BYTE(&gScore),
        DOUBLE_BYTE(&gWall),
    };

    PutChar('[');
    PutStr(hello);
    SendPacket(CMD_HELLO_NEKOT, 1, hello, sizeof hello);
    PutChar('H');
    SendPacket(CMD_HELLO_NEKOT, 2, 0x0118, 8);  // Hash of NekotOS
    PutChar(']');
}

void Network_Init() {
    NET_Init();
    PutChar('N');
}
#line 1 "nekot1/prelude.c"
#include "nekot1/private.h"

void memcpy_words(gword dest, gword src, gword num_words) {
    for (gword i = 0; i < num_words; i++) {
        gPoke2(dest, gPeek2(src));
        dest += 2;
        src += 2;
    }
}

void memset_words(gword p, gword value, gword num_words) {
    for (gword i = 0; i<num_words; i++) {
        gPoke2(p, value);
        p += 2;
    }
}

void* memset(void* dest, int value, size_t count) {
    gbyte* p = dest;
    gbyte v = (gbyte)value;
    for (gword i = 0; i < count; i++) {
        *p++ = v;
    }
    return dest;
}

void* memcpy(void* dest, const void* src, size_t count) {
    const gbyte* s = src;
    gbyte* p = dest;
    for (gword i = 0; i < count; i++) {
        *p++ = *s++;
    }
    return dest;
}

int strlen(const char* s) {
    const char* p = s;
    while (*p) ++p;
    return p-s;
}
#line 1 "nekot1/real.c"
#include "nekot1/private.h"

struct real gReal;

#define  R  gReal

static void IncrementSeconds() {
    SpinRealSeconds();

    ++R.seconds;

    if (gKern.in_game) SendPartialScores();

    // Now go increment the Wall Time.
    Wall_IncrementSecond();
}

static void IncrementDecis() {
    SpinRealDecis();

    if (R.decis < 9) {
        ++R.decis;
    } else {
        R.decis = 0;
        IncrementSeconds();
    }
}

void Real_IncrementTicks() {
    if (R.ticks < 5) {
        ++R.ticks;
    } else {
        R.ticks = 0;
        IncrementDecis();
    }
}

#undef R
#line 1 "nekot1/score.c"
#include "nekot1/private.h"

struct score gScore gZEROED;

void SendPartialScores() {
    gPoke2(0, SendPartialScores);

    return;  // for now, skip this.  why buggy?

    gAssert(gKern.in_game);
    gAssert(gKern.in_irq);
    gbyte dirty = gFALSE;
    gbyte np = gScore.number_of_players;
    if (!np) return;

    PutStr("SPS/");
    PutDec(np);
        //int* o = gScore.old_partials;
        //int* p = gScore.partials;
    {

    PutChar('@');
    PutDec((gword)gScore.old_partials);
    PutChar('p');
    PutDec((gword)gScore.partials);

        for (gbyte i = 0; i < np; i++) {
    PutChar('#');
    PutDec(i);
    PutChar(',');
    PutDec(gScore.old_partials[i]);
    PutChar(',');
    PutDec(gScore.partials[i]);
    PutChar(';');
            // if (*o != *p) //
            if (gScore.old_partials[i] != gScore.partials[i]) {
    PutChar('!');
                dirty = gTRUE;
                break;
            }
            //++o;
            //++p;
        }
    }

    if (dirty) {
        xSendControlPacket('S', (gbyte*) gScore.partials, np<<1);
        memcpy(gScore.old_partials, gScore.partials, sizeof gScore.partials);
        gFatal("TMP", sizeof gScore.partials);
    }
}
#line 1 "nekot1/vdg.c"
#include "nekot1/private.h"

// See page 248 in
// "Assembly Language Programming for the Color Computer (1985)(Laurence A Tepolt)(pdf)".
// Our TextMode is Display Mode "AI".
// Our PMode1 is Display Mode "G3C".
//
// The vdg_op_mode are written to $FF22 (Pia1PortB).
// The low three bits can be set to 0s.
// (Note that Bit 1 is the 1-Bit Speaker Output.)
//
// See page 3 in "CoCo Hardware Reference.pdf".
// The sam_control_bits are called "FFC0-FFC5 Video Display Mode".

static void NowSwitchDisplayMode(gbyte* fb, gbyte vdg_op_mode, gbyte sam_control_bits) {
    vdg_op_mode &= 0xF8;  // only top 5 bits matter.

    gbyte cc_value = gIrqSaveAndDisable();

    Vdg.shadow_pia1portb = vdg_op_mode;
    gPoke1(0xFF22, vdg_op_mode);  // Set VDG bits.

// Console_Printf(" D[%d,%d,%d] ", fb, vdg_op_mode, sam_control_bits);

    // Set the framebuffer address.
    {
        gword bit = 0x0200;  // Start with bit F0 (CoCo Hardware Reference.pdf)
        for (gbyte i=0; i<14; i+=2) {  // 7 iterations.
            gbool b = (((gword)fb & bit) != 0); 
            gPoke1(0xFFC6 + i + b, 0); // 0xFFC6 is F0.
            bit <<= 1;
        }
    }

    // Set the V2, V1, V0 SAM bits.
    {
        gbyte bit = 0x01;
        for (gbyte i=0; i<6; i+=2) {  // 3 iterations.
            gbool b = ((sam_control_bits & bit) != 0); 
            gPoke1(0xFFC0 + i + b, 0); // 0xFFC0 is V0.
            bit <<= 1;
        }
    }

    gIrqRestore(cc_value);
}
// Effective immediately.
static void NowSwitchToDisplayText(gbyte* fb, gbyte colorset) {
    NowSwitchDisplayMode(fb, (colorset?8:0), 0);
}
// Effective immediately.
void NowSwitchToGameScreen() {
    gwob w;
    w.w = Vdg.game_mode;
    NowSwitchDisplayMode(Vdg.game_framebuffer, w.b[0], w.b[1]);
}

// These are for Game Mode.
// They remember what to change back to, after Chat mode.
void gTextScreen(gbyte* fb, gbyte colorset) {
    Vdg.game_mode = (colorset? 0x0800: 0x0000);
    Vdg.game_framebuffer = fb;
    if (gKern.focus_game) NowSwitchToGameScreen();
}
void gPMode1Screen(gbyte* fb, gbyte colorset) {
    Vdg.game_mode = (colorset? 0xC804: 0xC004);
    Vdg.game_framebuffer = fb;
    if (gKern.focus_game) NowSwitchToGameScreen();
}
void gModeScreen(gbyte* fb, gword mode_code) {
    Vdg.game_mode = mode_code;
    Vdg.game_framebuffer = fb;
    if (gKern.focus_game) NowSwitchToGameScreen();
}

void NowSwitchToChatScreen() {
    NowSwitchToDisplayText(
        Cons,
        gKern.in_game ? COLORSET_ORANGE : COLORSET_GREEN
    );
}

void Vdg_Init() {
    NowSwitchToChatScreen();
}
#line 1 "nekot1/wall.c"
#include "nekot1/private.h"

struct wall gWall;

#define  W  gWall

static void Wall_IncrementDay() {
    // Rather than do Gregorian Date calculations,
    // we let the server tell us what the next day is.
    W.day = W.next_day;  
    W.month = W.next_month;  
    W.year2000 = W.next_year2000;  
    memcpy((gbyte*)W.dow, W.next_dow, 3);
    memcpy((gbyte*)W.moy, W.next_moy, 3);
}

static void Wall_IncrementHour() {
    if (W.hour < 23) {
        ++W.hour;
    } else {
        W.hour = 0;
        Wall_IncrementDay();
    }
}

static void Wall_IncrementMinute() {
    if (W.minute < 59) {
        ++W.minute;
    } else {
        W.minute = 0;
        Wall_IncrementHour();
    }
}

void Wall_IncrementSecond() {
    if (W.second < 59) {
        ++W.second;
    } else {
        W.second = 0;
        Wall_IncrementMinute();
    }
}

#undef  W
#line 1 "nekot1/wiznet.c"
#if NET_TYPE_cocoio

#include "nekot1/private.h"

#include "nekot1/w5100s_defs.h"

// How to talk to the four hardware ports.
struct wiz_port {
  volatile gbyte command;
  volatile gword addr;
  volatile gbyte data;
};

// Axiom uses Socket 1 (of 0 thru 3).
#define B 0x0500u   // Socket Base
#define T 0x4800u  // Transmit ring
#define R 0x6800u  // Receive ring

//////////////////////////////////////

gbyte WizGet1(gword reg) {
  WIZ->addr = reg;
  return WIZ->data;
}
gword WizGet2(gword reg) {
  WIZ->addr = reg;
  gbyte z_hi = WIZ->data;
  gbyte z_lo = WIZ->data;
  return ((gword)(z_hi) << 8) + (gword)z_lo;
}
void WizGetN(gword reg, void* buffer, gword size) {
  volatile struct wiz_port* wiz = WIZ;
  gbyte* to = (gbyte*)buffer;
  wiz->addr = reg;
  for (gword i = size; i; i--) {
    *to++ = wiz->data;
  }
}
void WizPut1(gword reg, gbyte value) {
  WIZ->addr = reg;
  WIZ->data = value;
}
void WizPut2(gword reg, gword value) {
  WIZ->addr = reg;
  WIZ->data = (gbyte)(value >> 8);
  WIZ->data = (gbyte)(value);
}
void WizPutN(gword reg, const void* data, gword size) {
  volatile struct wiz_port* wiz = WIZ;
  const gbyte* from = (const gbyte*)data;
  wiz->addr = reg;
  for (gword i = size; i; i--) {
    wiz->data = *from++;
  }
}

gword WizTicks() { return WizGet2(0x0082 /*TCNTR Tick Counter*/); }
gbyte WizTocks() { return WizGet1(0x0082 /*TCNTR Tick Counter*/); }

gbool ValidateWizPort(struct wiz_port* p) {
  gbyte status = p->command;
  return (status == 3);
}

void FindWizPort() {
    struct wiz_port* p = (void*)0xFF68;
    if (ValidateWizPort(p)) {
        Wiznet.wiz_port = p;
        return;
    }
    p = (void*)0xFF78;
    if (ValidateWizPort(p)) {
        Wiznet.wiz_port = p;
        return;
    }
    Wiznet.wiz_port = 0;
}

//////////////////////////////////////////

void WizIssueCommand(gbyte cmd) {
  WizPut1(B + SK_CR, cmd);
  while (WizGet1(B + SK_CR)) {
  }
}

void WizWaitStatus(gbyte want) {
  gbyte status;
  gbyte stuck = 200;
  do {
    status = WizGet1(B + SK_SR);
    if (!--stuck) gFatal("W", status);
  } while (status != want);
}


////////////////////////////////////////
////////////////////////////////////////

// returns tx_ptr
tx_ptr_t WizReserveToSend( gword n) {
  // PrintH("ResTS %d;", n);
  // Wait until free space is available.
  gword free_size;
  do {
    free_size = WizGet2(B + SK_TX_FSR0);
    // PrintH("Res free %d;", free_size);
  } while (free_size < n);

  return WizGet2(B + SK_TX_WR0) & RING_MASK;
}

tx_ptr_t WizDataToSend( tx_ptr_t tx_ptr,
                       const char* data, gword n) {
  gword begin = tx_ptr;   // begin: Beneath RING_SIZE.
  gword end = begin + n;  // end:  Sum may not be beneath RING_SIZE.

  if (end >= RING_SIZE) {
    gword first_n = RING_SIZE - begin;
    gword second_n = n - first_n;
    WizPutN(T + begin, data, first_n);
    WizPutN(T, data + first_n, second_n);
  } else {
    WizPutN(T + begin, data, n);
  }
  return (n + tx_ptr) & RING_MASK;
}
tx_ptr_t WizBytesToSend( tx_ptr_t tx_ptr,
                        const gbyte* data, gword n) {
  return WizDataToSend(tx_ptr, (char*)data, n);
}

void WizFinalizeSend( gword n) {
  gword tx_wr = WizGet2(B + SK_TX_WR0);
  tx_wr += n;
  WizPut2(B + SK_TX_WR0, tx_wr);
  WizIssueCommand(SK_CR_SEND);
}

errnum WizCheck() {
  gbyte ir = WizGet1(B + SK_IR);  // Socket Interrupt Register.
  if (ir & SK_IR_TOUT) {         // Timeout?
    return SK_IR_TOUT;
  }
  if (ir & SK_IR_DISC) {  // Disconnect?
    return SK_IR_DISC;
  }
  return OKAY;
}
errnum WizSendChunk( char* data, gword n) {
  // PrintH("Ax WizSendChunk %d@%d : %d %d %d %d %d", n, data, data[0], data[1],
  // data[2], data[3], data[4]);
  errnum e = WizCheck();
  if (e) return e;

  tx_ptr_t tx_ptr = WizReserveToSend(n);
  WizDataToSend(tx_ptr, data, n);
  WizFinalizeSend(n);
  // PrintH("Ax WSC.");
  return OKAY;
}

void WizSend(const gbyte* addr, gword size) {
    gbyte cc_value = gIrqSaveAndDisable();

  errnum e = WizCheck();
  if (e) gFatal("WizSend.WizCheck", e);

    tx_ptr_t t = WizReserveToSend(size);
    t = WizBytesToSend(t, addr, size);
    WizFinalizeSend(size);

    gIrqRestore(cc_value);
}

//////////////////////////////////////////////////////////////////

errnum WizRecvGetBytesWaiting(gword* bytes_waiting_out) {
  errnum e = WizCheck();
  if (e) return e;

  *bytes_waiting_out = WizGet2(B + SK_RX_RSR0);  // Unread Received Size.
  return OKAY;
}

errnum WizRecvChunkTry( gbyte* buf, gword n) {
  gword bytes_waiting = 0;
  errnum e = WizRecvGetBytesWaiting(& bytes_waiting);
  if (e) return e;
  if (bytes_waiting < n) return NOTYET;

  gword rd = WizGet2(B + SK_RX_RD0);
  gword begin = rd & RING_MASK;  // begin: Beneath RING_SIZE.
  gword end = begin + n;         // end: Sum may not be beneath RING_SIZE.

  if (end >= RING_SIZE) {
    gword first_n = RING_SIZE - begin;
    gword second_n = n - first_n;
    WizGetN(R + begin, buf, first_n);
    WizGetN(R, buf + first_n, second_n);
  } else {
    WizGetN(R + begin, buf, n);
  }

  WizPut2(B + SK_RX_RD0, rd + n);
  WizIssueCommand(SK_CR_RECV);  // Inform socket of changed SK_RX_RD.
  return OKAY;
}

errnum WizRecvChunk( gbyte* buf, gword n) {
  // PrintH("WizRecvChunk %d...", n);
  errnum e;
  do {
    e = WizRecvChunkTry(buf, n);
  } while (e == NOTYET);
  // PrintH("WRC %d: %d %d %d %d %d.", n, buf[0], buf[1], buf[2], buf[3],
  // buf[4]);
  return e;
}
errnum WizRecvChunkBytes( gbyte* buf, gword n) {
  return WizRecvChunk(buf, n);
}

////////////////////////////////////////
////////////////////////////////////////

void Wiznet_Init() {
    volatile gbyte* p = Cons + WIZNET_BAR_LOCATION;
    p[-1] = 'W';
    FindWizPort();
    if ((gword)Wiznet.wiz_port == 0xFF68u) {
         p[0] = '6';
    } else if ((gword)Wiznet.wiz_port == 0xFF78u) {
         p[0] = '7';
    }
    PutChar('W');
    PutChar(p[0]);
}

#endif // NET_TYPE_cocoio
