Compare commits

...

93 Commits

Author SHA1 Message Date
afa59cdb5a - [FIX] [zx_dialog_joystick] No es podia seleccionar quin joystick configurar
- [FIX] al recrear una finestra de vegades es perdia la textura de les fonts de la UI.
2025-08-16 18:22:02 +02:00
6837392c53 - [NEW] Ja funciona el joystick tipus Kempston 2025-08-16 16:59:26 +02:00
1fdfeedacc - [NEW] Es pot accedir al dialeg de configuracio dels joysticks des del menú 2025-08-16 11:23:16 +02:00
8201d51668 - [NEW] Dialeg per a configurar els joysticks 2025-08-16 10:09:42 +02:00
864c6b929d - [NEW] Soport per a joystick tipo Sinclair 2025-08-15 22:58:53 +02:00
0530c255c1 - Quatre mamonaes que he fet en el portatil 2025-08-15 18:17:09 +02:00
78a4c84db2 - [FIX] [zx_disk] Usava el index dels sectors en compte del id.
- [ONGOING] READ DELETED DATA command (0x0c) (no funciona)
2025-08-07 19:02:31 +02:00
5516f463bf - [NEW] Càrrega de disks des del debuger
- [FIX] Al carregar un nou disk s'alliberava mal la memòria
2025-08-07 17:53:01 +02:00
b69da56526 - Afegit lagueirtofile pa compilar FASTuosament
- [NEW] [zx_disk] funcions de debug
- [FIX] current_byte era un uint8_t i tenia que ser al menys uint16_t, per això fallava el READ DATA.
2025-08-07 16:06:42 +02:00
884e509d67 - [NEW] [zx_disk] READ DATA command (0x06) 2025-08-07 10:35:49 +02:00
8b36807e3b - [NEW] [zx_disk] Càrrega d'arxius .DSK (nomes extended, hardcoded)
- [NEW] [zx_disk] READ ID command (0x0A)
2025-08-06 20:02:54 +02:00
6791916f75 [NEW] [zx_disk] SEEK command (0x0f) [completat] 2025-08-06 10:31:02 +02:00
90b34749d1 - [NEW] [zx_disk] RECALIBRATE command (0x07)
- [NEW] [zx_disk] SENSE INTERRUPT STATUS command (0x08)
- [ONGOING] [zx_disk] SEEK command (0x0f)
2025-08-05 17:05:32 +02:00
40c825cdb9 - [NEW] [zx_disk] SPECIFY command (0x03)
- [NEW] [zx_disk] SENSE DRIVE STATUS command (0x04)
2025-08-05 11:31:36 +02:00
ade97a9a70 - [NEW] Mode +2A/+3 funcionant, a falta de la unitat de disc 2025-08-05 08:24:45 +02:00
9eb8662ec7 - Llevats mòduls que ja no s'usaven i afegits DSKs del goldenaxe, normals i amb protecció 2025-07-31 13:36:07 +02:00
1db0c52e1a - Enorme reestructuració del codi per a que el fluxe comence a ser mes racional
- [NEW] mòdul zx_system per a gestionar la vida i canvi de systemes (48K, 128K...)
2025-07-30 13:01:01 +02:00
2775da3d53 - [NEW] Càrrega "instantànea" de TAPs 2025-07-29 12:51:40 +02:00
2a0febc6b7 - [NEW] Les finestres guarden la posició i el zoom 2025-07-29 11:29:35 +02:00
913450fadb - [NEW] Zoom per a la pantalla retina
- Treballant en la càrrega instantànea de TAPs
2025-07-29 10:05:06 +02:00
fee07b6e1b - [NEW] [ui] incoffset() i panel()
- [NEW] mòdul ay_viewer
- [NEW] [ay-3-8912] afegits mètodes de debug
- [FIX] [ay-3-8912]Arreglats uns quants tipus de dades
- [FIX] [ay-3-8912]Arreglat el càlcul de la frequència (en realitat amplitud) dels tonos
- [FIX] [ay-3-8912] Arreglat el algoritme de càlcul del roido
2025-07-25 13:08:20 +02:00
662583be36 - Ja sona! pero les freqüències estàn mal (massa altes). Demà més. 2025-07-24 22:30:47 +02:00
9725e58d92 - Adaptat el mòdul 'ay-3-8912' per a usar zx_speaker, pero encara no fa roidets, a vore si demà trobe el perquè 2025-07-24 21:37:01 +02:00
91a8933544 - [NEW] mòdul 'zx_speaker' per a unificar la eixida de só 2025-07-24 19:39:57 +02:00
780afbc6a8 - [FIX] Arreglat el audio del 48K amb lo aprés del emu de gameboy 2025-07-24 18:41:09 +02:00
1cde51f2d4 - Treballant en el só 2025-07-24 12:42:23 +02:00
42da652aef - [NEW] Implementada decodificació parcial dels ports, tal i com ho fa el spectrum 2025-07-24 08:37:20 +02:00
0df17bf4c9 - [NEW] Comencem a implementar el AY-3-8912 2025-07-23 22:00:58 +02:00
6e3e8e9b69 - [FIX] Corregit el acces als port amb OUT
- [NEW] Nou model de gestió de memòria
2025-07-23 13:51:51 +02:00
3fd28136f6 - [NEW] ui::placetext() i ui::placechar, pa ficar text en qualsevol pixel
- [NEW] [Z80Analize] backspace esborra tots els tags
- [NEW] [zx-128bankviewer] Es mostra quina pàgina de memòria està asignada a cada bank
- [FIX] [zx_128mem] es filtra el port al que escolta la memòria
- [NEW] [zx_screen] Es mostra en quina pantalla estem (normal o shadow)
- [FIX] [zx_screen] tots els tipos per al calcul de adreces passats a uint32_t
- [NEW] [zx_ula] afegides combinacions de cursors per a major comoditat
2025-07-22 13:31:21 +02:00
4b4e1df8f9 - What's the story morning commit 2025-07-22 06:27:56 +02:00
13354b855d - the big mergecheit 2025-07-21 19:25:29 +02:00
300f95803e - [FIX] No calculava be l'adreça de la ROM actual en 128K
- [FIX] La ROM accedeix al pot 0x7ffd "the torerous menner", he tingut que ficar un apanyo
2025-07-21 14:00:31 +02:00
ab476a19b1 - [NEW] Implementat el mòdul de memòria per al ZX Spectrum 128K
- Primera prova falla, mirar la conexió al port 0x7ffd, que pareix que no ana.
2024-12-20 13:18:55 +01:00
184389a89e - [NEW] zx_screen preparat per al ZX Spectrum 128k
- [NEW] Afegida rom del ZX Spectrum 128K
2024-12-20 12:15:26 +01:00
fe36a970c2 - [NEW] Frequència del Z80 configurable 2024-12-20 11:24:58 +01:00
4a0e2b3b7d - [NEW] Adaptat zx_screen per a que reba el offset en memoria on està la memòria de video, per a ser mes modular. Pero crec que les diferencies en el timing van a fer necessari un modul nou per a cada spectrum. Ja vorem quan estiga mes descansat. 2024-12-19 22:33:58 +01:00
68843ab6b3 - [FIX] Solventats els bugs de la memòria modular amb classes 2024-12-19 20:57:28 +01:00
dbd80694aa - [NEW] Treballant en modularitzar la memòria, per a començar a implementar soport per als demes Spectrums 2024-12-19 17:36:22 +01:00
da4c692283 - [NEW] Implementació cutre i que no funciona de encendre el cassette al fer LOAD"", pero es que me pire a casa 2024-12-18 17:34:12 +01:00
b45c93d8a2 - [FIX] Ja ignora correctament combinacions incorrectes de opcodes DD i FD. 2024-12-18 14:53:44 +01:00
6f45044a9a - [CHG] En proves: fer un IN a un port no usat ara torna 0x00 en compte de 0xFF. Probablement hi haurà que revertir-ho.
- [NEW] Amb F9 es pot ficar o llevar un breakpoint en l'adreça on estiga el cursor del desensamblador.
- [NEW] Nou comando de la consola "show analyzer"
- [FIX] Quan es fa un full refresh mentres se debugga no ha de causar interrupcions. A més, ara mantenim els t_states i el punter a pantalla.
- [FIX] La instrucció CPIR llegia mal la memòria apuntada per HL al considerar si hi havia coincidencia
2024-12-18 13:22:56 +01:00
bdec53eb97 - [FIX] Els opcodes DD amb paràmetre de 16 bits mostraven mal l'adreça
- [FIX] Al carregar un estat el contador de programa no mantenia l'adreça correcta
- [NEW] Afegit al analitzador la visualització de escritura de dades
- [NEW] Deshabilite les interrupcions al entrar a una interrupció. No se si fa falta.
- [NEW] Afegit el Batman de Jon Ritman pa provar
2024-12-17 17:40:42 +01:00
dfcc0a26fe - [FIX] El cursor es mostra sempre damunt de les finestres zx_screen i z80debug
- [FIX] el dibuixat del crosshair en la finestra z80analyze causaba potencialment una escritura fora de rang
- [NEW] Refresc de la finestra z80analyze millorat
2024-12-17 12:03:04 +01:00
620cd8d88c - [FIX] Si no hi havia coincidencia la busqueda no paraba mai
- [FIX] RLCA i RRCA sempre activaben el flag de carry, independentment del valor
2024-12-17 09:32:55 +01:00
83b6782078 [NEW] Inclos un mode de pantalla en que s'actualitze al instant en compte de segons el retraç del crt, per a debuggar que se pinta 2024-12-16 16:31:53 +01:00
f7a7b0692d - [NEW] Inspector de memòria. Pasar per damunt d'un valor per a vore'l en 16bits, les dos parts de 8 bits, i amb signe. 2024-12-15 12:44:23 +01:00
0a758bbb33 - [NEW] F12 to StepOut() on debugger (break on RET, RETI or RETN)
- [FIX] Fixed visualizacion of some IX and IY opcodes
- [NEW] Scroll on memory viewer with mouse wheel
- [NEW] While debugging, on each step the screen refreshes, so screen redraw can be seen while happening
- [NEW] Search for sequences of bytes. Example: "search AB1140" to search for the sequece $AB $11 $40. "search next" to continue searching.
2024-12-14 20:46:46 +01:00
14d047cbb9 - [NEW] En el debugger, en el visor de memòria, tambe es veu cada byte del color del seu tag
- [NEW] En el debugger, en el visor de breakpoints, se marca uno si estem en eixe breakpoint
- [NEW] Opció per a sincronitzar el cursor del visor de memòria amb el cursor del desensamblador
- [NEW] Click on breakpoint to goto its address
- [FIX] IX, IX bit, IY i IY bit opcodes with displacement wher shown wrong on the disassembler
- [NEW] Symbols module shows a symbol highlighted if the cursor is on its address
- [NEW] Click on a symbol to move the cursor to its address
2024-12-14 12:22:30 +01:00
6768c01c81 - [NEW] La posició del cursor se marca amb un requadre blau (no relleno) en el desensamblador
- [NEW] setcursor corregeix l'adreça si no coincideix amb una instrucció
- [FIX] Corregits els opcodes FD34 i FD35
- [NEW] Ara en el analitzador es borra o es fixa la memòria amb DELETE i RETURN
- [NEW] Fent click en un pixel del analitzador du a la posició de memòria que toca en el desensamblador
- [NEW] ui::printvoidrect()
- [NEW] la finestra del analitzador pilla el foco nomes amb pasar per damunt
- [NEW] Fent click en una linea del desensamblador fica o lleva un breakpoint en eixa adreça
- [FIX] les accions en el analitzador actualitzen la finestra del debugger
- [FIX] El cursor del analitzador nomes es deu moure al estar damunt de al finestra del analitzador
- [FIX] El desensamblador, quan el tag de memoria era MIXED no reconixia part de la instrucció
- [FIX] El desensamblador usava el color incorrecte per a codi amb el tag REPEAT
2024-12-14 09:31:52 +01:00
c9aceeb387 - [NEW] Més opcions de control del etiquetat de la memòria
- [FIX] Les instruccions DD34 i DD35 no pillaven un byte signed, sino unsigned. Han de haber-ne més. REPASAR.
- [ONGOING] Preparant el analyzer per a tindre diverses visualitzacions de la memòria
- [NEW] El debugger ara mostra el etiquetat de la memòria en el desensamblador
- [FIX] El cursor ja se torna a vore en el debugger
2024-12-13 13:52:41 +01:00
8c197d5519 - [FIX] Al fer break on interrupt de vegades se passava de instruccions
- [NEW] el analitzador pot mostrar les instruccions repetides des de l'ultim estat
- [NEW] gestió de opcodes usats
2024-12-12 22:44:44 +01:00
c70c3652bf - [NEW] Afegida l'opció de "break on interrupt" 2024-12-11 18:03:17 +01:00
52de24a076 - [NEW] Added option for break on interrupt 2024-12-11 16:38:40 +01:00
231bb1f1ac - [NEW] Ara es pot especificar que una tecla està pulsada o no desde la consola (ula kedown X o ula keyup X) 2024-12-11 16:00:25 +01:00
1c6bf95953 - [NEW] Ara es poden afegir i llevar simbols des de la consola 2024-12-11 15:21:55 +01:00
18949140fd - [NEW] La consola mostra un log dels ultims comandos i resultats, com un terminal
- [NEW] La consola te un històric de comandos executats, com un terminal (navegar abm cursors amunt i avall)
2024-12-11 15:06:38 +01:00
5f6ebbff31 - [NEW] La finestra de debug es pot redimensionar i el contingut s'ajusta
- [NEW] Es poden redimensionar les seccions de la finestra de debug
- [NEW] Afegit visor de simbols al debugger
2024-12-11 13:39:29 +01:00
085712437e - [NEW] Durant l'execució la finestra de debug està dimmada
- [NEW] La finestra del spectrum mostra en el titol si està stoppada l'execució
- [FIX] Durante el debuguech la finestra del spectrum ara s'actualitza
2024-12-10 16:42:01 +01:00
f08fbf9e8b - [NEW] Les accións de execució (stop, cont, step, next) funcionen desde qualsevol finestra
- [FIX] Tancara la finestra de debug no fa que la execució continue
- [NEW] El foco va a la finestra que mes convé
- [FIX] El breakpoints i el Next mostren la instrucció que toca
2024-12-10 16:28:50 +01:00
f462afe56c - [FIX] Al anar de 10 en 10 steps de vegades se botaba breakpoints
- [FIX] Ara cada renderer te la seua textura de font
- [CHG] Continuar l'execució ja no tanca el debugger
- [NEW] En la memòria no tocada actual o en avanç, se "adivina" quina es la instrucció
- [FIX] Resetejar el spectrum borrava la ROM
- [NEW] Anar avant o arrere en el temps mou el cursor del desensamblador
2024-12-10 13:56:24 +01:00
68d53af1b4 - [NEW] Se mostren les etiquetes en les adreces de la finestra de debug
- [FIX] En la finestra de desensamblat el cursor sempre anava una instrucció per darrere
- [ONGOING] 'Next' funciona intermitentment, no entenc perqué
2024-12-09 22:06:17 +01:00
2f4e79bc50 - [NEW] Gestió dels events de cada finestra per separat
- [NEW] Symbols per a etiquetar adreces
- [NEW] Mapa de memòria "tocada"
- [NEW] En el mapa de memòria al passar el ratolí mostra l'adreça
- [NEW] En el mapa de memòria es mostra en roig la posició del contador de programa
- [NEW] Reemplaç en els opcodes de adreces conegudes per la seua etiqueta
2024-12-09 15:33:35 +01:00
8fd2eecb85 - [CHG] mode berserk passa a per Fast Tape. sistema de Opcions.
- [NEW] Opció per a parar execució al acabar de carregar una cinta.
- [NEW] Opció per a parar l'execució al trobar una instrucció del Z80 no vàlida
- [NEW] Savestate del Fernando Martin, per a provar més ràpid.
- [NEW] Treballant en el sistema d'anàlisi visual del codi
2024-12-08 22:57:03 +01:00
edf8728b04 - [FIX] El debugger se agarrotava quan duia un rato
- [FIX] No calculava correctament l'adreça a la que anar en una interrupció de mode 2
- [NEW] Afegit commando "goto adreça" al debugger
2024-12-06 12:23:30 +01:00
970aaa518f - [FIX] ui module should only update renderer when it actually changes
- [NEW] Disassembly window now can scroll with cursors or mouse wheel
- [NEW] Added valgrind script and supporting file
2024-12-06 11:53:31 +01:00
80a8d3b0cd - [NEW] Ara es veuen també els registres I i R en el debugger
- [FIX] Arreglada la gestió de les interrupcions en mode 2
2024-12-06 09:38:53 +01:00
c0f9fa9933 - [NEW] While debugging you can go back/forward in time
- [NEW] Step by step execution with F6
- [NEW] Memory is tagged as code or data while executing, so later it can be properly disassembled
- [NEW] "reg X value" to set the value of X register
- [FIX] IX opcode table had errors
- [FIX] opcodes with two parameters where printed incorrectly on the disassembler
- [FIX] opcodes can't be lager than 4 bytes
- [CHG] Berserk mode and fernando martin TAP by default, to help with debugging
2024-12-05 17:28:10 +01:00
cce38449a5 - Mes informació quan arriba un opcode no vàlid
- Més informació respecte a la càrrega de cassette
2024-12-04 22:19:46 +01:00
b1d04f21f7 - Codi mega ampastrat, treballant en el jittering del só, en que s'update un poc la pantalla en berserk mode, i està petant el zx_tape i no se com ho fà, pero me gonne a casa... 2024-12-04 13:56:24 +01:00
e0bb34052f - [FIX] la cpu ja actualitza el registre R com toca
- [FIX] EI no activa les interrupcions fins a després de la següent instrucció, like in real life
- [FIX] actualitzar el offset de la UI després de usar el debugger
- [ONGOING] Depurant el jittering del só. Ara executa 10 instruccions de CPu per cada bucle, per a que vaja mes apresa i no es retrase el cheneraor de só
- Afegit el TAP de Las Tres Luces de Glaurung pa provar
2024-12-04 13:12:58 +01:00
486bd648d3 - Sistema de menus operatiu, falta ficar-los tots
- Afegit .tap del Fernando Martín. Investigar perqué falla.
2024-12-03 22:09:52 +01:00
2ebe1916a4 - Treballant en el menu del joc 2024-12-03 15:29:05 +01:00
eee5753a7f - Afegit el modo paused per a quan estiga el menú fora 2024-12-03 13:41:17 +01:00
c84b8c7a6a - Separades les funcions de pintar la UI del mòdul "z80debug" i ficades en el seu propi mòdul "ui" 2024-12-03 13:21:37 +01:00
bb947f25b5 - Ja es pot tancar la finestra amb el botó de tancar finestra normal
- Ara la finestra de debug nomes es mostra mentre s'està debugant
- [NEW] "peek address" i "poke address value" des de la consola, per a modificar o consultar la memòria
2024-12-02 21:55:04 +01:00
810cdf4ecb - [NEW] Ja es pot especificar zoom i fullscreen seguint el standard Jailer 2024-12-02 18:38:04 +01:00
cbbf39c6cc - [FIX] No se podía carregar una nova cinta perque no s'alliberaba l'anterior
- [FIX] no se tornava el bit6 correcte en el port 0xFE (i el teclat "anava mal" segons com el mirares)
2024-12-02 17:45:42 +01:00
18406d4332 - Afegim el tap i el savestate de Alien 8 per a provar
- Provant el berserk mode
- Medint els t-states de altra forma
- iff1, iff2 i im afegits al array de registres de la cpu
- [NEW] getRegs() del modul z80
- [NEW] loadstate() i savestate() al modul z80debug
- [NEW] "load arxiu" i "save arxiu" en consola per a carregar i guardar savestates
- [ONGOING] "tape load arxiu" i "tape play" per a canviar de cinta i playarla
- Buffer de audio més gran. Ara el buffer es circular. Continuem intentant desfer-se del jittering
2024-12-02 15:32:09 +01:00
4a9b13126b - Push de rigor 2024-12-02 09:04:21 +01:00
c6b6830c5c - [FIX] Ja s'escolta el só de nou.
- Augmentat el buffer de so per a evitar en lo posible els talls.
- Purgat del buffer si arriva al màxim.
2024-12-01 22:10:48 +01:00
7cb6ae527b - Treballant en que funcione al temps correcte
- Treballant en el só
2024-12-01 21:50:20 +01:00
7eb5df248f - [NEW] load binary to memory from inside or as arguments to exe
- Trying berserk
2024-04-25 06:41:35 +02:00
b05ce14a95 - [CHG] Per ara llevem la càrrega ràpida (que no va)
- [NEW] Afegit el Manic Miner pa provar
2024-04-23 06:38:11 +02:00
06734c3af4 - [ONGOING] Berserk Mode! 2024-04-22 14:56:27 +02:00
0bd7c841d9 - [NEW] IM2 teòricament funcionant 2024-04-22 14:46:47 +02:00
9831d3e8bc -[FIX] Arreglat el problema de les linies negres 2024-04-22 14:46:09 +02:00
43ee57221a -[FIX] El despaçament de les instruccions de les tables IX, IX_BIT, IY i IY_BIT no pillava el signe 2024-04-22 13:11:29 +02:00
9c2bf54c83 - [FIX] gestió incorrecta del carry en ADC8() 2024-04-22 10:02:28 +02:00
917531b60d - [FIX] Estava mirant mal el flag de BRIGHT 2024-04-22 10:01:49 +02:00
908beaf293 - [NEW] Cambiada la gestió del teclat per a facilitar certes combinacions amb teclats normals 2024-04-22 10:01:20 +02:00
9b603604e9 - [CHG] zx_screen pinta t_state a t_state, per a que es vegen les ralles al carregar
- [FIX] Ja carrega blocs capçalera i blocs programa. Falla el següent...
2024-04-21 21:53:34 +02:00
ddcb40b289 - Deixe el Abu Simbel ací per a anar fent proves de càrrega
- [NEW] Mòdul zx_tape, encara no carrega correctament les cintes.
- [CHG] Gestió completa del só pasada a la ULA, i de la pantalla a zxscreen
2024-04-20 09:32:57 +02:00
72 changed files with 6138 additions and 891 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
z80
.vscode/*
build/*

BIN
128k.rom Normal file

Binary file not shown.

View File

@@ -5,4 +5,4 @@ run: compile
./z80
debug: compile
gdb z80
gdb -ex run z80

BIN
ROBOCOP1.TAP Normal file

Binary file not shown.

BIN
abusimbel.tap Normal file

Binary file not shown.

BIN
alien8.sav Normal file

Binary file not shown.

BIN
alien8.tap Normal file

Binary file not shown.

255
ay-3-8912.cpp Normal file
View File

@@ -0,0 +1,255 @@
#include "ay-3-8912.h"
#include "z80.h"
#include <SDL2/SDL.h>
#include "zx_ula.h"
#define MIXER_REG_A_TONE 0x01
#define MIXER_REG_B_TONE 0x02
#define MIXER_REG_C_TONE 0x04
#define MIXER_REG_A_NOISE 0x08
#define MIXER_REG_B_NOISE 0x10
#define MIXER_REG_C_NOISE 0x20
#define MIXER_REG_PORT_A 0x40
#define MIXER_REG_PORT_B 0x80
#define ENVELOPE_HOLD 0x01
#define ENVELOPE_ALTERNATE 0x02
#define ENVELOPE_ATTACK 0x04
#define ENVELOPE_CONTINUE 0x08
namespace audio
{
bool enabled = false;
float cycles_per_sample;
uint8_t volume_table[16] {0,2,3,4,6,8,11,16,23,32,45,64,90,128,180,255};
SDL_AudioDeviceID sdlAudioDevice;
uint8_t selected_register {0};
uint8_t registers[16];
uint32_t channel_a_tone_freq;
uint32_t channel_a_tone_freq_counter;
uint8_t channel_a_tone_level;
uint8_t channel_a_level;
uint32_t channel_b_tone_freq;
uint32_t channel_b_tone_freq_counter;
uint8_t channel_b_tone_level;
uint8_t channel_b_level;
uint32_t channel_c_tone_freq;
uint32_t channel_c_tone_freq_counter;
uint8_t channel_c_tone_level;
uint8_t channel_c_level;
uint32_t noise_freq;
uint32_t noise_freq_counter;
uint8_t noise_level;
uint32_t shiftreg;
uint32_t envelope_freq;
uint32_t envelope_freq_counter;
int8_t envelope_volume;
int8_t envelope_direction;
void select_register(int port, int val)
{
selected_register = val & 0xf;
}
int read_register(int port)
{
return registers[selected_register];
}
void write_register(int port, int val)
{
registers[selected_register] = val;
switch (selected_register) {
case 0:
case 1: {
uint32_t freq = uint32_t(registers[0]) + (uint32_t(registers[1]) * 256);
channel_a_tone_freq = (freq==0?1:freq)<<4;
channel_a_tone_freq_counter = 0;
break;
}
case 2:
case 3: {
uint32_t freq = uint32_t(registers[2]) + (uint32_t(registers[3]) * 256);
channel_b_tone_freq = (freq==0?1:freq)<<4;
channel_b_tone_freq_counter = 0;
break;
}
case 4:
case 5: {
uint32_t freq = uint32_t(registers[4]) + (uint32_t(registers[5]) * 256);
channel_c_tone_freq = (freq==0?1:freq)<<4;
channel_c_tone_freq_counter = 0;
break;
}
case 6: {
uint32_t freq = (registers[6] & 0x1f);
noise_freq = (freq==0?1:freq)<<4;
noise_freq_counter = 0;
break;
}
case 11:
case 12: {
uint32_t freq = registers[11] | (registers[12] << 8);
envelope_freq = (freq==0?1:freq)<<4;
break;
}
case 13:
if (registers[13]&ENVELOPE_ATTACK) {
envelope_volume = 0;
envelope_direction = 1;
} else {
envelope_volume = 15;
envelope_direction = -1;
}
}
}
void init()
{
reset();
z80::connect_port(0xfffd, 0xffff, audio::read_register, audio::select_register);
z80::connect_port(0xbffd, 0xffff, nullptr, audio::write_register);
}
void reset()
{
enabled = true;
selected_register = 0;
for (int i=0; i<16;++i) registers[i]=0;
}
void update(uint32_t dt)
{
if (!enabled) return;
//dt = dt >> 1;
// Oscillate (0-1) channel A tone level given its frequency
channel_a_tone_freq_counter+=dt;
if (channel_a_tone_freq_counter >= channel_a_tone_freq) {
channel_a_tone_freq_counter -= channel_a_tone_freq;
channel_a_tone_level = channel_a_tone_level ^ 1;
}
// Oscillate (0-1) channel B tone level given its frequency
channel_b_tone_freq_counter+=dt;
if (channel_b_tone_freq_counter >= channel_b_tone_freq) {
channel_b_tone_freq_counter -= channel_b_tone_freq;
channel_b_tone_level = channel_b_tone_level ^ 1;
}
// Oscillate (0-1) channel C tone level given its frequency
channel_c_tone_freq_counter+=dt;
if (channel_c_tone_freq_counter >= channel_c_tone_freq) {
channel_c_tone_freq_counter -= channel_c_tone_freq;
channel_c_tone_level = channel_c_tone_level ^ 1;
}
// Oscillate (0-1) noise level given its frequency and shift register
noise_freq_counter+=dt;
if (noise_freq_counter >= noise_freq) {
noise_freq_counter -= noise_freq;
//noise_level = noise_level ^ shiftreg;
//uint32_t newbit = shiftreg ^ (shiftreg >> 3);
//shiftreg = ((shiftreg >> 1) & 0xffff) | ((newbit << 16) & 0x10000);
shiftreg = (shiftreg * 2 + 1) ^ (((shiftreg >> 16) ^ (shiftreg >> 13)) & 1);
noise_level = ((shiftreg >> 16) & 1);
}
// Develop (0-15) envelope volume given its frequency and shape
envelope_freq_counter+=dt;
if (envelope_freq_counter >= envelope_freq) {
envelope_freq_counter -= envelope_freq;
envelope_volume += envelope_direction;
if ( (envelope_volume > 15) || (envelope_volume < 0) ) {
switch(registers[13]&0xf) {
case 8:
case 12:
envelope_volume &= 0x0f;
break;
case 10:
case 14:
envelope_direction = -envelope_direction;
envelope_volume += envelope_direction;
case 11:
case 13:
envelope_direction = 0;
envelope_volume = 15;
default:
envelope_direction = 0;
envelope_volume = 0;
}
}
}
}
uint8_t get_sample()
{
// Mix tone and noise on channel A given register 7 values
channel_a_level = 1;
if ((registers[7] & MIXER_REG_A_TONE)==0) channel_a_level &= channel_a_tone_level;
if ((registers[7] & MIXER_REG_A_NOISE)==0) channel_a_level &= noise_level;
// Mix tone and noise on channel B given register 7 values
channel_b_level = 1;
if ((registers[7] & MIXER_REG_B_TONE)==0) channel_b_level &= channel_b_tone_level;
if ((registers[7] & MIXER_REG_B_NOISE)==0) channel_b_level &= noise_level;
// Mix tone and noise on channel C given register 7 values
channel_c_level = 1;
if ((registers[7] & MIXER_REG_C_TONE)==0) channel_c_level &= channel_c_tone_level;
if ((registers[7] & MIXER_REG_C_NOISE)==0) channel_c_level &= noise_level;
//zx_ula::set_border_color(channel_a_tone_level&0xf);
const uint8_t channel_a_volume = (registers[8]&0x10) ? envelope_volume : registers[8]&0xf;
const uint8_t channel_b_volume = (registers[9]&0x10) ? envelope_volume : registers[9]&0xf;
const uint8_t channel_c_volume = (registers[10]&0x10) ? envelope_volume : registers[10]&0xf;
const uint8_t channel_a_sample = volume_table[(channel_a_level&1) * channel_a_volume] >> 1;
const uint8_t channel_b_sample = volume_table[(channel_b_level&1) * channel_b_volume] >> 1;
const uint8_t channel_c_sample = volume_table[(channel_c_level&1) * channel_c_volume] >> 1;
uint8_t sample = (channel_a_sample+channel_b_sample+channel_c_sample)&0xff;
return sample;
}
namespace debug
{
uint8_t get_register(uint8_t num) { return registers[num]; }
uint32_t get_channel_a_tone_freq() { return channel_a_tone_freq; }
uint32_t get_channel_a_tone_freq_counter() { return channel_a_tone_freq_counter; }
uint8_t get_channel_a_tone_level() { return channel_a_tone_level; }
uint8_t get_channel_a_level() { return channel_a_level; }
uint32_t get_channel_b_tone_freq() { return channel_b_tone_freq; }
uint32_t get_channel_b_tone_freq_counter() { return channel_b_tone_freq_counter; }
uint8_t get_channel_b_tone_level() { return channel_b_tone_level; }
uint8_t get_channel_b_level() { return channel_b_level; }
uint32_t get_channel_c_tone_freq() { return channel_c_tone_freq; }
uint32_t get_channel_c_tone_freq_counter() { return channel_c_tone_freq_counter; }
uint8_t get_channel_c_tone_level() { return channel_c_tone_level; }
uint8_t get_channel_c_level() { return channel_c_level; }
uint32_t get_noise_freq() { return noise_freq; }
uint32_t get_noise_freq_counter() { return noise_freq_counter; }
uint8_t get_noise_level() { return noise_level; }
uint32_t get_shiftreg() { return shiftreg; }
uint32_t get_envelope_freq() { return envelope_freq; }
uint32_t get_envelope_freq_counter() { return envelope_freq_counter; }
int8_t get_envelope_volume() { return envelope_volume; }
int8_t get_envelope_direction() { return envelope_direction; }
}
}

45
ay-3-8912.h Normal file
View File

@@ -0,0 +1,45 @@
#pragma once
#include <stdint.h>
namespace audio
{
void select_register(int port, int val);
int read_register(int port);
void write_register(int port, int val);
void init();
void reset();
void update(uint32_t dt);
uint8_t get_sample();
namespace debug
{
uint8_t get_register(uint8_t num);
uint32_t get_channel_a_tone_freq();
uint32_t get_channel_a_tone_freq_counter();
uint8_t get_channel_a_tone_level();
uint8_t get_channel_a_level();
uint32_t get_channel_b_tone_freq();
uint32_t get_channel_b_tone_freq_counter();
uint8_t get_channel_b_tone_level();
uint8_t get_channel_b_level();
uint32_t get_channel_c_tone_freq();
uint32_t get_channel_c_tone_freq_counter();
uint8_t get_channel_c_tone_level();
uint8_t get_channel_c_level();
uint32_t get_noise_freq();
uint32_t get_noise_freq_counter();
uint8_t get_noise_level();
uint32_t get_shiftreg();
uint32_t get_envelope_freq();
uint32_t get_envelope_freq_counter();
int8_t get_envelope_volume();
int8_t get_envelope_direction();
}
}

88
ay_viewer.cpp Normal file
View File

@@ -0,0 +1,88 @@
#include "ay_viewer.h"
#include "ui.h"
#include "ay-3-8912.h"
void ay_viewer::show()
{
if (!win)
{
win = SDL_CreateWindow("AY-3-8912 Viewer", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 512, 512, SDL_WINDOW_SHOWN);
ren = SDL_CreateRenderer(win, -1, 0);
tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 512, 512);
uitex = ui::createtexture(ren);
//ui::window::registerWindow(SDL_GetWindowID(win), handleEvent);
}
refresh();
}
void ay_viewer::refresh()
{
if (!win) return;
ui::setrenderer(ren, uitex);
/*
Uint32 *pixels;
int pitch;
SDL_LockTexture(tex, NULL, (void**)&pixels, &pitch);
for (int i=0; i<65536; ++i)
{
//uint8_t tag = z80::getMemTag(i);
//pixels[i] = tag==MEMTAG_NONE ? 0x808080 : tag==MEMTAG_DATA ? 0x0000FF : tag==MEMTAG_MIXED ? 0xFF00FF : 0x00FF00;
uint32_t none_color = i<0x4000 ? 0x101010 : i<0x5800 ? 0x202020 : i<0x5b00 ? 0x404040 : 0x808080;
uint8_t tag = mem::getTag(i);
pixels[i] = !(tag & MEMTAG_TOUCHED) ? none_color : (tag & MEMTAG_TINST) ? 0x00FF00 : (tag & MEMTAG_TREPEAT) ? 0xFF0000 : 0x0000FF;
}
pixels[z80::getPC()] = 0xFFFFFF;
SDL_UnlockTexture(tex);
SDL_RenderCopy(ren, tex, NULL, NULL);
*/
SDL_SetRenderDrawColor(ren, 30, 30, 30, 255);
SDL_RenderClear(ren);
char temp[256];
ui::panel(0,0,9,18,"REG:");
for (int i=0; i<16; ++i) {
sprintf(temp, "%2u: %3u", i, audio::debug::get_register(i));
ui::printtxt(0,i,temp, COLOR_WHITE);
}
ui::panel(9,0,18,18,"CHANNEL A:");
sprintf(temp, "TONE FREQ: %4u", audio::debug::get_channel_a_tone_freq());
ui::printtxt(0,0,temp, COLOR_WHITE);
sprintf(temp, "COUNTER: %4u", audio::debug::get_channel_a_tone_freq_counter());
ui::printtxt(0,1,temp, COLOR_WHITE);
sprintf(temp, "TONE LEVL: %1u", audio::debug::get_channel_a_tone_level());
ui::printtxt(0,2,temp, COLOR_WHITE);
sprintf(temp, "LEVEL: %1u", audio::debug::get_channel_a_level());
ui::printtxt(0,3,temp, COLOR_WHITE);
SDL_RenderPresent(ren);
}
void ay_viewer::hide()
{
//ui::window::unregisterWindow(SDL_GetWindowID(win));
SDL_DestroyTexture(uitex); uitex = nullptr;
SDL_DestroyTexture(tex); tex = nullptr;
SDL_DestroyRenderer(ren); ren = nullptr;
SDL_DestroyWindow(win); win = nullptr;
}
void ay_viewer::focus()
{
if (win) {
SDL_RaiseWindow(win);
refresh();
}
}
bool ay_viewer::handleEvent(SDL_Event *e)
{
return true;
}

12
ay_viewer.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "z80viewer.h"
class ay_viewer : public z80viewer
{
public:
void show();
void refresh();
void hide();
void focus();
bool handleEvent(SDL_Event *e);
};

BIN
batman.sav Normal file

Binary file not shown.

BIN
batmanritman.tap Normal file

Binary file not shown.

BIN
boom.sav Normal file

Binary file not shown.

View File

@@ -1,51 +0,0 @@
#include <ncurses.h>
#include <stdlib.h>
int main()
{
initscr();
if (has_colors() == FALSE) {
endwin();
printf("Your terminal does not support color\n");
exit(1);
}
int w, h;
getmaxyx(stdscr, h, w);
start_color();
//init_pair(1, COLOR_WHITE, COLOR_BLACK);
init_pair(1, COLOR_YELLOW, COLOR_BLUE);
WINDOW *win_regs = newwin(10,20,0,0);
WINDOW *win_mem = newwin(10,w-20,0,20);
WINDOW *win_code = newwin(h-10,w,10,0);
refresh();
box(win_mem,0,0);
mvwprintw(win_mem, 0, 2, " MEMORY: ");
mvwprintw(win_mem, 1, 2, "%ix%i", w, h);
wrefresh(win_mem);
box(win_regs,0,0);
mvwprintw(win_regs, 0, 2, " REGISTERS: ");
mvwprintw(win_regs, 1, 2, "AF: 00");
mvwprintw(win_regs, 2, 2, "BC: 00");
mvwprintw(win_regs, 3, 2, "DE: 00");
mvwprintw(win_regs, 4, 2, "HL: 00");
wrefresh(win_regs);
box(win_code,0,0);
wattron(win_code, COLOR_PAIR(1));
mvwprintw(win_code, 1, 1, "Hello world %s !!!", "hola");
wattroff(win_code, COLOR_PAIR(1));
wrefresh(win_code);
refresh();
getch();
endwin();
return 0;
}

BIN
fer.sav Normal file

Binary file not shown.

BIN
fernandomartin.tap Normal file

Binary file not shown.

275
file.cpp Normal file
View File

@@ -0,0 +1,275 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include "file.h"
#include <sys/stat.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>
#include <vector>
#ifndef _WIN32
#include <pwd.h>
#endif
#define DEFAULT_FILENAME "data.jf2"
#define DEFAULT_FOLDER "data/"
#define CONFIG_FILENAME "config.txt"
namespace file
{
struct file_t
{
std::string path;
uint32_t size;
uint32_t offset;
};
std::vector<file_t> toc;
/* El std::map me fa coses rares, vaig a usar un good old std::vector amb una estructura key,value propia i au, que sempre funciona */
struct keyvalue_t {
std::string key, value;
};
char *resource_filename = NULL;
char *resource_folder = NULL;
int file_source = SOURCE_FILE;
char scratch[255];
static std::string config_folder;
std::vector<keyvalue_t> config;
void setResourceFilename(const char *str) {
if (resource_filename != NULL) free(resource_filename);
resource_filename = (char*)malloc(strlen(str)+1);
strcpy(resource_filename, str);
}
void setResourceFolder(const char *str) {
if (resource_folder != NULL) free(resource_folder);
resource_folder = (char*)malloc(strlen(str)+1);
strcpy(resource_folder, str);
}
void setSource(const int src) {
file_source = src%2; // mod 2 so it always is a valid value, 0 (file) or 1 (folder)
if (src==SOURCE_FOLDER && resource_folder==NULL) setResourceFolder(DEFAULT_FOLDER);
}
bool getDictionary() {
if (resource_filename == NULL) setResourceFilename(DEFAULT_FILENAME);
std::ifstream fi (resource_filename, std::ios::binary);
if (!fi.is_open()) return false;
char header[4];
fi.read(header, 4);
uint32_t num_files, toc_offset;
fi.read((char*)&num_files, 4);
fi.read((char*)&toc_offset, 4);
fi.seekg(toc_offset);
for (uint i=0; i<num_files; ++i)
{
uint32_t file_offset, file_size;
fi.read( (char*)&file_offset, 4 );
fi.read( (char*)&file_size, 4 );
uint8_t path_size;
fi.read( (char*)&path_size, 1 );
char file_name[path_size+1];
fi.read( file_name, path_size );
file_name[path_size] = 0;
std::string filename = file_name;
toc.push_back({filename, file_size, file_offset});
}
fi.close();
return true;
}
char *getFilenameWithFolder(const char* filename) {
strcpy(scratch, resource_folder);
strcat(scratch, filename);
return scratch;
}
FILE *getFilePointer(const char *resourcename, int& filesize, const bool binary) {
if (file_source==SOURCE_FILE and toc.size()==0) {
if (not getDictionary()) setSource(SOURCE_FOLDER);
}
FILE *f;
if (file_source==SOURCE_FILE) {
bool found = false;
uint32_t count = 0;
while( !found && count < toc.size() ) {
found = ( std::string(resourcename) == toc[count].path );
if( !found ) count++;
}
if( !found ) {
perror("El recurs no s'ha trobat en l'arxiu de recursos");
exit(1);
}
filesize = toc[count].size;
f = fopen(resource_filename, binary?"rb":"r");
if (not f) {
perror("No s'ha pogut obrir l'arxiu de recursos");
exit(1);
}
fseek(f, toc[count].offset, SEEK_SET);
} else {
f = fopen(getFilenameWithFolder(resourcename), binary?"rb":"r");
fseek(f, 0, SEEK_END);
filesize = ftell(f);
fseek(f, 0, SEEK_SET);
}
return f;
}
char *getFileBuffer(const char *resourcename, int& filesize, const bool zero_terminate) {
FILE *f = getFilePointer(resourcename, filesize, true);
char* buffer = (char*)malloc(zero_terminate?filesize+1:filesize);
fread(buffer, filesize, 1, f);
if (zero_terminate) buffer[filesize]=0;
fclose(f);
return buffer;
}
// Crea la carpeta del sistema donde guardar datos
void setConfigFolder(const char *foldername)
{
#ifdef _WIN32
config_folder = std::string(getenv("APPDATA")) + "/" + foldername;
#elif __APPLE__
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
config_folder = std::string(homedir) + "/Library/Application Support/" + foldername;
#elif __linux__
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
config_folder = std::string(homedir) + "/." + foldername;
#endif
struct stat st = {0};
if (stat(config_folder.c_str(), &st) == -1)
{
#ifdef _WIN32
int ret = mkdir(config_folder.c_str());
#else
int ret = mkdir(config_folder.c_str(), S_IRWXU);
#endif
if (ret == -1)
{
printf("ERROR CREATING CONFIG FOLDER.");
exit(EXIT_FAILURE);
}
}
}
const char *getConfigFolder() {
std::string folder = config_folder + "/";
return folder.c_str();
}
void loadConfigValues() {
config.clear();
std::string config_file = config_folder + "/config.txt";
FILE *f = fopen(config_file.c_str(), "r");
if (!f) return;
char line[1024];
while (fgets(line, sizeof(line), f)) {
char *value = strchr(line, '=');
if (value) {
*value='\0'; value++;
value[strlen(value)-1] = '\0';
config.push_back({line, value});
}
}
fclose(f);
}
void saveConfigValues() {
std::string config_file = config_folder + "/config.txt";
FILE *f = fopen(config_file.c_str(), "w");
if (f) {
for (auto pair : config) {
fprintf(f, "%s=%s\n", pair.key.c_str(), pair.value.c_str());
}
fclose(f);
}
}
const char* getConfigValueString(const char *key) {
if (config.empty()) loadConfigValues();
for (auto pair : config) {
if (pair.key == std::string(key)) {
strcpy(scratch, pair.value.c_str());
return scratch;
}
}
return NULL;
}
const int getConfigValueInteger(const char *key, const int default_value)
{
const char* value = getConfigValueString(key);
if (!value) return default_value;
return atoi(value);
}
const float getConfigValueFloat(const char *key, const float default_value)
{
const char* value = getConfigValueString(key);
if (!value) return default_value;
return atof(value);
}
const bool getConfigValueBool(const char *key, const bool default_value)
{
const char* value = getConfigValueString(key);
if (!value) return default_value;
return strcmp(value, "true")==0?true:false;
}
void setConfigValueString(const char* key, const char* value) {
if (config.empty()) loadConfigValues();
for (auto &pair : config) {
if (pair.key == std::string(key)) {
pair.value = value;
saveConfigValues();
return;
}
}
config.push_back({key, value});
saveConfigValues();
return;
}
void setConfigValueInteger(const char* key, const int value)
{
char tmp[256];
sprintf(tmp, "%i", value);
setConfigValueString(key, tmp);
}
void setConfigValueFloat(const char* key, const float value)
{
char tmp[256];
sprintf(tmp, "%.2f", value);
setConfigValueString(key, tmp);
}
void setConfigValueBool(const char* key, const bool value)
{
setConfigValueString(key, value?"true":"false");
}
}

27
file.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <stdio.h>
#define SOURCE_FILE 0
#define SOURCE_FOLDER 1
namespace file
{
void setConfigFolder(const char *foldername);
const char *getConfigFolder();
void setResourceFilename(const char *str);
void setResourceFolder(const char *str);
void setSource(const int src);
FILE *getFilePointer(const char *resourcename, int& filesize, const bool binary=false);
char *getFileBuffer(const char *resourcename, int& filesize, const bool zero_terminate=false);
const char* getConfigValueString(const char *key);
const int getConfigValueInteger(const char *key, const int default_value=0);
const float getConfigValueFloat(const char *key, const float default_value=0.0f);
const bool getConfigValueBool(const char *key, const bool default_value=false);
void setConfigValueString(const char* key, const char* value);
void setConfigValueInteger(const char* key, const int value);
void setConfigValueFloat(const char* key, const float value);
void setConfigValueBool(const char* key, const bool value);
}

203
gamepad.cpp Normal file
View File

@@ -0,0 +1,203 @@
#include "gamepad.h"
#include <SDL2/SDL.h>
#include "z80.h"
#include "zx_ula.h"
#include <vector>
#define GAMEPAD_BUTTON_LEFT 0
#define GAMEPAD_BUTTON_RIGHT 1
#define GAMEPAD_BUTTON_UP 2
#define GAMEPAD_BUTTON_DOWN 3
#define GAMEPAD_BUTTON_FIRE 4
namespace gamepad
{
struct gamepad_t
{
SDL_GameController *gamepad;
int32_t index;
uint8_t type;
bool buttons[5] { false, false, false, false, false };
};
std::vector<gamepad_t> gamepads;
bool kempston_active = false;
int kempston_port_in(int port)
{
for (auto gamepad: gamepads)
{
if (gamepad.type == GAMEPAD_TYPE_KEMPSTON)
{
uint8_t result = 0;
if (gamepad.buttons[GAMEPAD_BUTTON_FIRE]) result |= 0x10;
if (gamepad.buttons[GAMEPAD_BUTTON_UP]) result |= 0x08;
if (gamepad.buttons[GAMEPAD_BUTTON_DOWN]) result |= 0x04;
if (gamepad.buttons[GAMEPAD_BUTTON_LEFT]) result |= 0x02;
if (gamepad.buttons[GAMEPAD_BUTTON_RIGHT]) result |= 0x01;
return result;
}
}
return 0;
}
void init()
{
z80::connect_port(0x1f, 0x001f, kempston_port_in, nullptr);
}
uint8_t getNextType()
{
uint8_t next_type = GAMEPAD_TYPE_SINCLAIR_1;
bool found = false;
while (!found)
{
found = true;
for (auto gamepad : gamepads)
{
if (gamepad.type == next_type)
{
found = false;
next_type++;
if (next_type == GAMEPAD_TYPE_CUSTOM) return next_type;
break;
}
}
}
return next_type;
}
uint8_t add(int32_t index, uint8_t type)
{
if (SDL_IsGameController(index)) {
gamepad_t gamepad;
gamepad.gamepad = SDL_GameControllerOpen(index);
if (SDL_GameControllerGetAttached(gamepad.gamepad) == SDL_TRUE) SDL_GameControllerEventState(SDL_ENABLE);
gamepad.index = index;
gamepad.type = ( (type == GAMEPAD_TYPE_ANY) ? getNextType() : type );
gamepads.push_back(gamepad);
}
return gamepads.size()-1;
}
void remove(int32_t index)
{
SDL_GameController *game_controller = SDL_GameControllerFromInstanceID(index);
for (int i=0; i<gamepads.size(); ++i)
{
if (gamepads[i].gamepad == game_controller) {
gamepads.erase(gamepads.begin()+i);
break;
}
}
SDL_GameControllerClose(game_controller);
}
void buttonDown(int32_t index, uint8_t button)
{
SDL_GameController *game_controller = SDL_GameControllerFromInstanceID(index);
for (auto& gamepad : gamepads)
{
if (gamepad.gamepad == game_controller)
{
switch(button)
{
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: gamepad.buttons[GAMEPAD_BUTTON_LEFT]=true; break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: gamepad.buttons[GAMEPAD_BUTTON_RIGHT]=true; break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: gamepad.buttons[GAMEPAD_BUTTON_DOWN]=true; break;
case SDL_CONTROLLER_BUTTON_DPAD_UP: gamepad.buttons[GAMEPAD_BUTTON_UP]=true; break;
case SDL_CONTROLLER_BUTTON_A: gamepad.buttons[GAMEPAD_BUTTON_FIRE]=true; break;
}
switch(gamepad.type)
{
case GAMEPAD_TYPE_SINCLAIR_1:
switch(button)
{
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: zx_ula::keydown('1'); break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: zx_ula::keydown('2'); break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: zx_ula::keydown('3'); break;
case SDL_CONTROLLER_BUTTON_DPAD_UP: zx_ula::keydown('4'); break;
case SDL_CONTROLLER_BUTTON_A: zx_ula::keydown('5'); break;
}
break;
case GAMEPAD_TYPE_SINCLAIR_2:
switch(button)
{
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: zx_ula::keydown('6'); break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: zx_ula::keydown('7'); break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: zx_ula::keydown('8'); break;
case SDL_CONTROLLER_BUTTON_DPAD_UP: zx_ula::keydown('9'); break;
case SDL_CONTROLLER_BUTTON_A: zx_ula::keydown('0'); break;
}
break;
}
return;
}
}
}
void buttonUp(int32_t index, uint8_t button)
{
SDL_GameController *game_controller = SDL_GameControllerFromInstanceID(index);
for (auto& gamepad : gamepads)
{
if (gamepad.gamepad == game_controller)
{
switch(button)
{
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: gamepad.buttons[GAMEPAD_BUTTON_LEFT]=false; break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: gamepad.buttons[GAMEPAD_BUTTON_RIGHT]=false; break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: gamepad.buttons[GAMEPAD_BUTTON_DOWN]=false; break;
case SDL_CONTROLLER_BUTTON_DPAD_UP: gamepad.buttons[GAMEPAD_BUTTON_UP]=false; break;
case SDL_CONTROLLER_BUTTON_A: gamepad.buttons[GAMEPAD_BUTTON_FIRE]=false; break;
}
switch(gamepad.type)
{
case GAMEPAD_TYPE_SINCLAIR_1:
switch(button)
{
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: zx_ula::keyup('1'); break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: zx_ula::keyup('2'); break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: zx_ula::keyup('3'); break;
case SDL_CONTROLLER_BUTTON_DPAD_UP: zx_ula::keyup('4'); break;
case SDL_CONTROLLER_BUTTON_A: zx_ula::keyup('5'); break;
}
break;
case GAMEPAD_TYPE_SINCLAIR_2:
switch(button)
{
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: zx_ula::keyup('6'); break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: zx_ula::keyup('7'); break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: zx_ula::keyup('8'); break;
case SDL_CONTROLLER_BUTTON_DPAD_UP: zx_ula::keyup('9'); break;
case SDL_CONTROLLER_BUTTON_A: zx_ula::keyup('0'); break;
}
break;
}
return;
}
}
}
uint8_t getNumGamepads()
{
return gamepads.size();
}
uint8_t getGamepadType(uint8_t index)
{
if (gamepads.size()>index) return gamepads[index].type;
return 0;
}
void setGamepadType(uint8_t index, uint8_t type)
{
if (gamepads.size()>index) gamepads[index].type = type;
}
}

22
gamepad.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
#define GAMEPAD_TYPE_ANY 0
#define GAMEPAD_TYPE_SINCLAIR_1 1
#define GAMEPAD_TYPE_SINCLAIR_2 2
#define GAMEPAD_TYPE_KEMPSTON 3
#define GAMEPAD_TYPE_FULLER 4
#define GAMEPAD_TYPE_CUSTOM 5
namespace gamepad
{
void init();
uint8_t add(int32_t index, uint8_t type);
void remove(int32_t index);
void buttonDown(int32_t index, uint8_t button);
void buttonUp(int32_t index, uint8_t button);
uint8_t getNumGamepads();
uint8_t getGamepadType(uint8_t index);
void setGamepadType(uint8_t index, uint8_t type);
}

BIN
glaurung.sav Normal file

Binary file not shown.

BIN
glaurung.tap Normal file

Binary file not shown.

BIN
goldenaxe1.dsk Normal file

Binary file not shown.

BIN
goldenaxe1_lock.dsk Normal file

Binary file not shown.

BIN
goldenaxe2.dsk Normal file

Binary file not shown.

BIN
goldenaxe2_lock.dsk Normal file

Binary file not shown.

5
lagueirtofile Normal file
View File

@@ -0,0 +1,5 @@
libs = -lSDL2
cppflags = -g
executable = z80
sourcepath = .
buildpath = build

362
main.cpp
View File

@@ -1,117 +1,317 @@
#include <stdint.h>
#include <stdio.h>
#include "zx_system.h"
#include "z80.h"
#include "z80dis.h"
#include "z80debug.h"
#include "zx_ula.h"
#include "zx_speaker.h"
#include "zx_screen.h"
#include "zx_tape.h"
#include <SDL2/SDL.h>
#include <string.h>
#include "ui.h"
#include "ui_menu.h"
#include "z80analyze.h"
#include "ui_window.h"
#include "zx_mem.h"
#include "z80viewer.h"
//#include "zx_128bankviewer.h"
//#include "zx_128pageviewer.h"
#include "ay-3-8912.h"
#include "ay_viewer.h"
#include "file.h"
#include "gamepad.h"
#include "zx_dialog_joystick.h"
uint8_t memory[65536];
uint32_t t = 0;
uint16_t ts = 0;
uint8_t ft = 0;
uint32_t fps=0;
uint32_t fps_time=0;
uint32_t time = 0;
uint32_t t_states = 0;
namespace actions
{
void exitMenu()
{
const uint8_t dt = z80::step();
z80debug::cont();
zxscreen::refresh(dt);
}
uint8_t test = 0;
void exitButNotContinue()
{
// do nothing
}
int fastload(int value)
{
zx_tape::toggleOption(ZXTAPE_OPTION_FAST_LOAD);
return zx_tape::getOption(ZXTAPE_OPTION_FAST_LOAD);
}
int stopatend(int value)
{
zx_tape::toggleOption(ZXTAPE_OPTION_STOP_AT_END);
return zx_tape::getOption(ZXTAPE_OPTION_STOP_AT_END);
}
int decZoom(int value)
{
zxscreen::decZoom();
return 0;
}
int incZoom(int value)
{
zxscreen::incZoom();
return 0;
}
int fullscreen(int value)
{
zxscreen::toggleFullscreen();
return zxscreen::getFullscreen();
}
int fullrefresh(int value)
{
zxscreen::toggleFullRefresh();
return zxscreen::getFullRefresh();
}
int showAnalyzer(int value)
{
z80analyze::show();
return 0;
}
int mode48K(int value)
{
zx_system::reset(ZX_48K);
return 0;
}
int mode128K(int value)
{
zx_system::reset(ZX_128K);
return 0;
}
int modePlus3(int value)
{
zx_system::reset(ZX_PLUS3);
return 0;
}
int reset(int value)
{
z80::reset();
return 0;
}
int exit(int value)
{
zx_system::shutdown();
return 0;
}
int configureJoysticks(int value)
{
ui::setDialog(dialogs::joysticks::show);
ui::menu::exitButNotContinue();
return 0;
}
}
void init_menu()
{
ui::menu::init();
ui::menu::setexitcallback(actions::exitMenu);
int menu = ui::menu::addsubmenu("FILE");
ui::menu::addoption(menu, "LOAD TAPE", nullptr);
ui::menu::addoption(menu, "SAVE TAPE", nullptr);
ui::menu::addseparator(menu);
ui::menu::addoption(menu, "LOAD STATE", nullptr);
ui::menu::addoption(menu, "SAVE STATE", nullptr);
ui::menu::addseparator(menu);
ui::menu::addoption(menu, "EXIT", actions::exit);
menu = ui::menu::addsubmenu("SYSTEM");
ui::menu::addoption(menu, "ZX 48K", actions::mode48K);
ui::menu::addoption(menu, "ZX 128K/+2", actions::mode128K);
ui::menu::addoption(menu, "ZX +2A/+3", actions::modePlus3);
ui::menu::addseparator(menu);
ui::menu::addoption(menu, "RESET", actions::reset);
menu = ui::menu::addsubmenu("TAPE");
ui::menu::addbooloption(menu, "FAST LOAD", zx_tape::getOption(ZXTAPE_OPTION_FAST_LOAD), actions::fastload);
ui::menu::addbooloption(menu, "STOP AT END", zx_tape::getOption(ZXTAPE_OPTION_STOP_AT_END), actions::stopatend);
menu = ui::menu::addsubmenu("SCREEN");
ui::menu::addoption(menu, "DEC ZOOM", actions::decZoom);
ui::menu::addoption(menu, "INC ZOOM", actions::incZoom);
ui::menu::addbooloption(menu, "FULLSCREEN", zxscreen::getFullscreen(), actions::fullscreen);
ui::menu::addseparator(menu);
ui::menu::addbooloption(menu, "FULL REFRESH", zxscreen::getFullRefresh(), actions::fullrefresh);
menu = ui::menu::addsubmenu("EMULATION");
ui::menu::addbooloption(menu, "STOP ON INVALID OP", z80::getOption(Z80_OPTION_STOP_ON_INVALID), actions::decZoom);
ui::menu::addoption(menu, "SHOW ANALYZER", actions::showAnalyzer);
ui::menu::addoption(menu, "CONFIGURE JOYSTICKS", actions::configureJoysticks);
}
int main(int argc, char *argv[])
{
FILE* f = fopen("48.rom", "rb");
fread(memory, 1024, 16, f);
fclose(f);
z80::reset(memory);
z80::connect_port(0xfe, zx_ula::port_in, zx_ula::port_out);
file::setConfigFolder("z80");
SDL_Init(SDL_INIT_EVERYTHING);
z80debug::show();
zxscreen::show();
init_menu();
z80debug::init();
zx_ula::sound_init();
//uint32_t update_freq =
zx_system::init(ZX_48K);
gamepad::init();
zx_tape::load("ROBOCOP1.TAP");
//if (argc==3) { z80debug::loadngo(argv[1], argv[2]); }
//z80debug::stop();
bool should_exit = false;
SDL_Event e;
while (!should_exit)
time = SDL_GetTicks();
t_states = 0;
bool first_time = true;
while (!zx_system::shuttingDown())
{
while (SDL_PollEvent(&e))
{
if (e.type == SDL_QUIT) { should_exit=true; break; }
if (z80debug::debugging()) {
if ((e.type==SDL_WINDOWEVENT) && ((e.window.event==SDL_WINDOWEVENT_SHOWN) || (e.window.event==SDL_WINDOWEVENT_EXPOSED))) {
z80debug::refresh();
zxscreen::refresh();
}
if (e.type == SDL_KEYDOWN) {
if (e.key.keysym.scancode==SDL_SCANCODE_ESCAPE) {
should_exit=true; break;
} else if (e.key.keysym.scancode==SDL_SCANCODE_F10) {
t += z80::step();
if (t>=69888) { t=0; z80::interrupt(); }
z80debug::refresh();
zxscreen::refresh();
} else if (e.key.keysym.scancode==SDL_SCANCODE_F11) {
t += z80debug::next();
fps=0;
fps_time = SDL_GetTicks();
zxscreen::refresh();
} else if (e.key.keysym.scancode==SDL_SCANCODE_F5) {
t += z80::step();
bool result = true;
if (e.type == SDL_QUIT) { zx_system::shutdown(); break; }
if (e.type == SDL_JOYDEVICEADDED) {
const uint8_t index = gamepad::add(e.jdevice.which, GAMEPAD_TYPE_ANY);
printf("JOYSTICK CONECTAT: %i\n", e.jdevice.which);
if (!first_time) dialogs::joysticks::init(index);
}
if (e.type == SDL_JOYDEVICEREMOVED) {
gamepad::remove(e.jdevice.which);
printf("JOYSTICK DESCONECTAT: %i\n", e.jdevice.which);
}
if (e.type == SDL_CONTROLLERBUTTONDOWN) {
gamepad::buttonDown(e.jbutton.which, e.jbutton.button);
}
if (e.type == SDL_CONTROLLERBUTTONUP) {
gamepad::buttonUp(e.jbutton.which, e.jbutton.button);
}
if (e.type == SDL_MOUSEBUTTONDOWN) result = ui::window::sendEvent(e.button.windowID, &e);
if (e.type == SDL_MOUSEBUTTONUP) result = ui::window::sendEvent(e.button.windowID, &e);
if (e.type == SDL_MOUSEMOTION) result = ui::window::sendEvent(e.motion.windowID, &e);
if (e.type == SDL_WINDOWEVENT) result = ui::window::sendEvent(e.window.windowID, &e);
if (e.type == SDL_MOUSEWHEEL) result = ui::window::sendEvent(e.wheel.windowID, &e);
if (e.type == SDL_TEXTINPUT) result = ui::window::sendEvent(e.text.windowID, &e);
if (e.type == SDL_KEYDOWN) {
if (e.key.keysym.scancode==SDL_SCANCODE_F5) {
if (z80debug::debugging()) {
z80debug::history::gototop();
const uint8_t dt = z80::step();
z80debug::cont();
fps=0;
fps_time = SDL_GetTicks();
zxscreen::refresh();
} else if (e.key.keysym.scancode==SDL_SCANCODE_RETURN) {
z80debug::executeConsole();
} else if (e.key.keysym.scancode==SDL_SCANCODE_BACKSPACE) {
z80debug::DeleteCharConsole();
zxscreen::refresh(dt);
}
}
if (e.type == SDL_TEXTINPUT) {
z80debug::sendToConsole(e.text.text);
}
} else {
if (e.type == SDL_KEYDOWN) {
if (e.key.keysym.scancode==SDL_SCANCODE_F8) {
} else if (e.key.keysym.scancode==SDL_SCANCODE_F8) {
if (!z80debug::debugging()) {
z80debug::stop();
zxscreen::refresh();
zxscreen::redraw();
} else {
z80debug::show();
}
z80viewer::refreshAll();
} else if (e.key.keysym.scancode==SDL_SCANCODE_F10) {
if (z80debug::debugging()) {
z80debug::show();
z80debug::history::gototop();
const uint8_t dt = z80::step();
z80debug::refresh();
zxscreen::fullrefresh();
zxscreen::redraw();
z80analyze::refresh();
z80viewer::refreshAll();
}
} else if (e.key.keysym.scancode==SDL_SCANCODE_F11) {
if (z80debug::debugging()) {
z80debug::show();
z80debug::history::gototop();
const uint8_t dt = z80debug::next();
z80debug::refresh();
zxscreen::refresh(dt);
zxscreen::redraw();
z80analyze::refresh();
z80viewer::refreshAll();
}
} else if (e.key.keysym.scancode==SDL_SCANCODE_F12) {
if (z80debug::debugging()) {
z80debug::show();
z80debug::history::gototop();
const uint8_t dt = z80debug::stepout();
z80debug::refresh();
zxscreen::refresh(dt);
zxscreen::redraw();
z80analyze::refresh();
z80viewer::refreshAll();
}
}
result = ui::window::sendEvent(e.key.windowID, &e);
}
if (e.type == SDL_MOUSEBUTTONUP && e.button.button==1) ui::setClicked(true);
if (!result)
zx_system::shutdown(); break;
}
if (!z80debug::debugging()) {
if (z80debug::isbreak(z80::getPC(), 9)) {
z80debug::stop();
zxscreen::refresh();
} else {
uint8_t t_states = z80::step();
t += t_states;
ts += t_states;
if (ts>=400) {
ts-=400;
zx_ula::sound_update();
}
if (t>=69888) {
ft++;
if (ft==16) { ft=0; zxscreen::flash(); }
/*
fps++;
if (SDL_GetTicks() - fps_time >= 1000) {
printf("FPS: %i\n", fps);
fps = 0;
fps_time = SDL_GetTicks();
}
*/
t=0;
zxscreen::refresh();
z80::interrupt();
first_time = false;
if (!z80debug::debugging() && !z80debug::paused()) {
// En cada bucle fem 10 pasos de la CPU, sino s'ofega (jo en veig 5)
for (int i=0;i<5;++i) {
if (z80debug::isbreak(z80::getPC(), 9)) {
z80debug::stop();
zxscreen::redraw();
break;
} else {
uint8_t dt = z80::step();
t_states += dt;
zx_system::update(dt);
zxscreen::refresh(dt);
if (z80debug::debugging()) break;
}
}
uint32_t update_freq = z80::getClock()/10;
if (t_states>=update_freq)
{
// Esperem a que es compleixca el temps corresponent als t states executats
while (SDL_GetTicks()<time+100) {}
t_states -= update_freq;
time = SDL_GetTicks();
z80analyze::refresh();
z80viewer::refreshAll();
}
z80analyze::refresh(true);
} else if (z80debug::paused()) {
zxscreen::redraw(false);
if (ui::hasDialog())
ui::callDialog();
else
ui::menu::show();
zxscreen::present();
}
ui::setClicked(false);
}
return 0;

BIN
manic.tap Normal file

Binary file not shown.

BIN
plus3.rom Normal file

Binary file not shown.

13
symbols-glaurung.txt Normal file
View File

@@ -0,0 +1,13 @@
0x5e38 STACK
0x9a01 DRW_ROOM
0x9f00 DRW_SCO_HERO
0x9f0f VAR_VIDES
0x9f8d DRW_SCO_INV
0xa00a DRW_SCO_LIVS
0xa01a DRW_SCO_NBAG
0xa022 DRW_SCO_NARW
0xa02a DRW_SCO_PNTS
0xc661 CLEAR_SCR
0xed5e MAINLOOP
0xf2b1 DRW_HERO
0xfed5 COUNT500

1
symbols.txt Normal file
View File

@@ -0,0 +1 @@
0x6580 START

27
test.asm Normal file
View File

@@ -0,0 +1,27 @@
.org $8000
ei
START:
ld a, 2
out ($fe), a
ld b, 255
ld de, TILES
ld hl, $4000
LOOP:
ld a, (de)
ld (hl), a
inc e
inc h
djnz LOOP
ld a, (de)
ld hl, $5800
ld (hl), a
ld a, 0
out ($fe), a
halt
jr START
TILES: db $81, $42, $24, $18, $18, $24, $42, $81, $4d

171
ui.cpp Normal file
View File

@@ -0,0 +1,171 @@
#include "ui.h"
#include <SDL2/SDL.h>
namespace ui
{
uint8_t colors[16][3] = {
{0,0,0},
{0,0,128},
{0,128,0},
{0,128,128},
{128,0,0},
{128,0,128},
{255,128,0},
{128,128,128},
{30,30,30},
{0,0,255},
{0,255,0},
{0,255,255},
{255,0,0},
{255,0,255},
{255,255,0},
{255,255,255},
};
SDL_Renderer *ren = nullptr;
SDL_Texture *tex = nullptr;
uint8_t offset_x = 0;
uint8_t offset_y = 0;
bool clicked = false;
void (*dialog)(void) = nullptr;
SDL_Texture * createtexture(SDL_Renderer *renderer)
{
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, SDL_LoadBMP("font.bmp"));
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
return texture;
}
void setrenderer(SDL_Renderer *renderer, SDL_Texture *texture)
{
ren = renderer;
tex = texture;
offset_x = offset_y = 0;
}
void setoffset(uint8_t x, uint8_t y)
{
offset_x = x;
offset_y = y;
}
void incoffset(uint8_t x, uint8_t y)
{
offset_x += x;
offset_y += y;
}
uint8_t getOffsetX()
{
return offset_x;
}
uint8_t getOffsetY()
{
return offset_y;
}
bool mouseInside(int x, int y, int w, int h)
{
int mx, my;
Uint32 mb = SDL_GetMouseState(&mx, &my);
mx=mx/CHR_W; my=my/CHR_H;
mx -= offset_x;
my -= offset_y;
return (mx >= x) && (mx < x+w) && (my >= y) && (my < y+h);
}
void panel(int x, int y, int w, int h, const char *title)
{
ui::setoffset(x, y);
ui::box(0,0,w,h,COLOR_WHITE);
ui::printrect(2,0, strlen(title)+2,1, COLOR_DARK);
ui::printtxt(3,0, title, COLOR_WHITE);
ui::incoffset(1, 1);
}
void box(int x, int y, int w, int h, uint8_t color)
{
SDL_Rect rect {((offset_x+x)*CHR_W)+3, ((offset_y+y)*CHR_H)+6, w*CHR_W-6, h*CHR_H-13};
SDL_SetRenderDrawColor(ren, colors[color][0], colors[color][1], colors[color][2], 255);
SDL_RenderDrawRect(ren, &rect);
}
void printrect(int x, int y, int w, int h, uint8_t color)
{
SDL_Rect rect {(offset_x+x)*CHR_W, (offset_y+y)*CHR_H, w*CHR_W, h*CHR_H};
SDL_SetRenderDrawColor(ren, colors[color][0], colors[color][1], colors[color][2], 255);
SDL_RenderFillRect(ren, &rect);
}
void printvoidrect(int x, int y, int w, int h, uint8_t color)
{
SDL_Rect rect {(offset_x+x)*CHR_W, (offset_y+y)*CHR_H, w*CHR_W, h*CHR_H};
SDL_SetRenderDrawColor(ren, colors[color][0], colors[color][1], colors[color][2], 255);
SDL_RenderDrawRect(ren, &rect);
}
void printchar(int x, int y, char chr, uint8_t color)
{
if (color != 255) SDL_SetRenderDrawColor(ren, colors[color][0], colors[color][1], colors[color][2], 255);
if (chr==32) return;
if (chr<32 || chr>127) chr = '.';
SDL_Rect src {((chr-32)&0xf)*CHR_W, ((chr-32)>>4)*CHR_H, CHR_W, CHR_H};
SDL_Rect dst {(offset_x+x)*CHR_W, (offset_y+y)*CHR_H, CHR_W, CHR_H};
SDL_RenderCopy(ren, tex, &src, &dst);
}
void printtxt(int x, int y, const char *text, uint8_t color)
{
SDL_SetTextureColorMod(tex, colors[color][0], colors[color][1], colors[color][2]);
for (int i=0; i<strlen(text);++i) if (text[i]!=32) printchar(x+i, y, text[i]);
}
void placechar(int x, int y, char chr, uint8_t color)
{
if (color != 255) SDL_SetRenderDrawColor(ren, colors[color][0], colors[color][1], colors[color][2], 255);
if (chr==32) return;
if (chr<32 || chr>127) chr = '.';
SDL_Rect src {((chr-32)&0xf)*CHR_W, ((chr-32)>>4)*CHR_H, CHR_W, CHR_H};
SDL_Rect dst {x, y, CHR_W*2, CHR_H*2};
SDL_RenderCopy(ren, tex, &src, &dst);
}
void placetxt(int x, int y, const char *text, uint8_t color)
{
SDL_SetTextureColorMod(tex, colors[color][0], colors[color][1], colors[color][2]);
for (int i=0; i<strlen(text);++i) if (text[i]!=32) placechar(x+i*CHR_W*2, y, text[i]);
}
void setClicked(const bool value)
{
clicked = value;
}
const bool getClicked()
{
return clicked;
}
void setDialog(void(*new_dialog)(void))
{
ui:dialog = new_dialog;
}
bool hasDialog()
{
return dialog != nullptr;
}
void callDialog()
{
if (dialog) dialog();
}
}

50
ui.h Normal file
View File

@@ -0,0 +1,50 @@
#pragma once
#include <SDL2/SDL.h>
namespace ui
{
#define CHR_W 6
#define CHR_H 13
#define COLOR_BLACK 0
#define COLOR_DARK_BLUE 1
#define COLOR_GREEN 2
#define COLOR_TEAL 3
#define COLOR_BROWN 4
#define COLOR_PURPLE 5
#define COLOR_ORANGE 6
#define COLOR_GRAY 7
#define COLOR_DARK 8
#define COLOR_BLUE 9
#define COLOR_LIME 10
#define COLOR_CYAN 11
#define COLOR_RED 12
#define COLOR_MAGENTA 13
#define COLOR_YELLOW 14
#define COLOR_WHITE 15
SDL_Texture * createtexture(SDL_Renderer *renderer);
void setrenderer(SDL_Renderer *renderer, SDL_Texture *texture);
void setoffset(uint8_t x, uint8_t y);
void incoffset(uint8_t x, uint8_t y);
uint8_t getOffsetX();
uint8_t getOffsetY();
bool mouseInside(int x, int y, int w, int h);
void panel(int x, int y, int w, int h, const char *title);
void box(int x, int y, int w, int h, uint8_t color);
void printrect(int x, int y, int w, int h, uint8_t color);
void printvoidrect(int x, int y, int w, int h, uint8_t color);
void printchar(int x, int y, char chr, uint8_t color=255);
void printtxt(int x, int y, const char *text, uint8_t color);
void placechar(int x, int y, char chr, uint8_t color=255);
void placetxt(int x, int y, const char *text, uint8_t color);
void setClicked(const bool value);
const bool getClicked();
void setDialog(void(*new_dialog)(void));
bool hasDialog();
void callDialog();
}

151
ui_menu.cpp Normal file
View File

@@ -0,0 +1,151 @@
#include "ui_menu.h"
#include "ui.h"
#include <vector>
#include <string>
#include "zx_screen.h"
namespace ui
{
namespace menu
{
#define OPTION_TYPE_NORMAL 0
#define OPTION_TYPE_SEPARATOR 1
#define OPTION_TYPE_BOOLEAN 2
struct option_t
{
std::string label;
int type;
int value;
int (*callback)(int);
};
struct menu_t
{
std::string label;
SDL_Rect rect;
std::vector<option_t> options;
};
void(*exit_callback)(void) = nullptr;
std::vector<menu_t> menus;
int visible_menu = -1;
int menu_x = 0;
bool do_not_exit = false;
void init()
{
// No se si hi ha algo que fer ací...
}
void show()
{
ui::setoffset(0,0);
int mx, my;
Uint32 mb = SDL_GetMouseState(&mx, &my);
mx=mx/CHR_W; my=my/CHR_H;
int w;
ui::printrect(0, 0, 320, 1, COLOR_BLACK);
int opt_pos=1;
int index=0;
for (auto &menu : menus)
{
const int text_size = (menu.label.size()+2);
uint8_t text_color = COLOR_WHITE;
if (my<1 && mx>=opt_pos && mx<opt_pos+text_size)
{
ui::printrect(opt_pos-1, 0, text_size, 1, COLOR_WHITE);
text_color = COLOR_BLACK;
visible_menu = index;
menu_x = opt_pos-1;
}
ui::printtxt(opt_pos, 0, menu.label.c_str(), text_color);
opt_pos+=text_size;
index++;
}
if (visible_menu!=-1)
{
opt_pos=2;
menu_t &m = menus[visible_menu];
ui::printrect(menu_x, 1, 22, m.options.size()+2, COLOR_BLACK);
ui::box(menu_x, 1, 22, m.options.size()+2, COLOR_WHITE);
for (auto &option : m.options)
{
if (option.type!=OPTION_TYPE_SEPARATOR)
{
const int text_size = (option.label.size()+2);
uint8_t text_color = COLOR_WHITE;
if (my==opt_pos && mx>=menu_x && mx<menu_x+20)
{
ui::printrect(menu_x+1, opt_pos, 20, 1, COLOR_WHITE);
text_color = COLOR_BLACK;
if (ui::getClicked())
{
if (option.callback) option.value = option.callback(option.value);
if (exit_callback && !do_not_exit) exit_callback();
do_not_exit = false;
return;
}
}
ui::printtxt(menu_x+1, opt_pos, option.label.c_str(), text_color);
if (option.type==OPTION_TYPE_BOOLEAN)
{
const char *check = option.value?"[X]":"[ ]";
ui::printtxt(menu_x+18, opt_pos, check, text_color);
}
}
opt_pos++;
}
}
}
void setexitcallback(void(*callback)(void))
{
exit_callback = callback;
}
void exitButNotContinue()
{
do_not_exit = true;
}
const int addsubmenu(const char *label)
{
menu_t m;
m.label = label;
m.rect = {0, 0, 100, 0};
menus.push_back(m);
return menus.size()-1;
}
void addoption(int menu, const char *label, int(*callback)(int))
{
option_t op;
op.type = OPTION_TYPE_NORMAL;
op.label = label;
op.callback = callback;
menus[menu].options.push_back(op);
menus[menu].rect.h += 15;
}
void addbooloption(int menu, const char *label, bool default_value, int(*callback)(int))
{
option_t op;
op.type = OPTION_TYPE_BOOLEAN;
op.label = label;
op.value = default_value?1:0;
op.callback = callback;
menus[menu].options.push_back(op);
menus[menu].rect.h += 15;
}
void addseparator(int menu)
{
option_t op;
op.type = OPTION_TYPE_SEPARATOR;
menus[menu].options.push_back(op);
}
}
}

16
ui_menu.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
namespace ui
{
namespace menu
{
void init();
void show();
void setexitcallback(void(*callback)(void));
void exitButNotContinue();
const int addsubmenu(const char *label);
void addoption(int menu, const char *label, int(*callback)(int));
void addbooloption(int menu, const char *label, bool default_value, int(*callback)(int));
void addseparator(int menu);
}
}

33
ui_window.cpp Normal file
View File

@@ -0,0 +1,33 @@
#include "ui_window.h"
#include <vector>
namespace ui
{
namespace window
{
struct window_t
{
Uint32 window;
bool (*callback)(SDL_Event*);
};
std::vector<window_t> windows;
void registerWindow(Uint32 window, bool(*callback)(SDL_Event*))
{
for (auto win : windows) if (win.window == window) return;
windows.push_back((window_t){window, callback});
}
void unregisterWindow(Uint32 window)
{
for (auto win = windows.begin(); win != windows.end(); win++) if ((*win).window == window) { windows.erase(win); return; }
}
bool sendEvent(Uint32 window, SDL_Event *e)
{
for (auto win : windows) if (win.window == window) return win.callback(e);
return true;
}
}
}

12
ui_window.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <SDL2/SDL.h>
namespace ui
{
namespace window
{
void registerWindow(Uint32 window, bool(*callback)(SDL_Event *e));
void unregisterWindow(Uint32 window);
bool sendEvent(Uint32 window, SDL_Event *e);
}
}

12
valgrind-sup.txt Normal file
View File

@@ -0,0 +1,12 @@
{
ignore_unversioned_libs
Memcheck:Leak
...
obj:*/lib*/lib*.so
}
{
ignore_versioned_libs
Memcheck:Leak
...
obj:*/lib*/lib*.so.*
}

2
valgrind.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
valgrind --tool=memcheck --leak-check=full --leak-resolution=high --suppressions=valgrind-sup.txt ./z80

1066
z80.cpp

File diff suppressed because it is too large Load Diff

42
z80.h
View File

@@ -4,14 +4,26 @@
namespace z80
{
void reset(uint8_t* mem);
void connect_port(int num, int (*in_ptr)(int), void (*out_ptr)(int,int));
#define Z80_OPTION_STOP_ON_INVALID 0
#define Z80_OPTION_BREAK_ON_INTERRUPT 1
#define Z80_OPTION_BREAK_ON_RET 2
#define Z80_NUM_OPTIONS 3
void init(uint32_t freq);
void reset();
void clearPorts();
void setClock(uint32_t freq);
uint32_t getClock();
//void reset(uint8_t* mem);
void connect_port(uint16_t num, uint16_t mask, int (*in_ptr)(int), void (*out_ptr)(int,int));
void interrupt();
uint32_t step();
uint8_t *getMem();
//uint8_t *getMem();
uint8_t *getRegs();
uint16_t getAF(const bool alt=false);
uint16_t getBC(const bool alt=false);
uint16_t getDE(const bool alt=false);
@@ -21,4 +33,26 @@ namespace z80
uint16_t getIY();
uint16_t getSP();
uint16_t getPC();
uint8_t getI();
uint8_t getR();
void setPC(const uint16_t addr);
/*
uint8_t getMemTag(const uint16_t addr);
void setMemTag(const uint16_t addr, const uint8_t value);
void clearMemTag();
uint8_t getMemTouched(const uint16_t addr);
void clearMemTouched();
void fixMemTouched();
*/
const bool getOption(const int option);
void setOption(const int option, const bool value);
void toggleOption(const int option);
void resetStackedCalls();
}

144
z80analyze.cpp Normal file
View File

@@ -0,0 +1,144 @@
#include "z80analyze.h"
#include "z80.h"
#include "z80debug.h"
#include "zx_mem.h"
#include <SDL2/SDL.h>
#include "ui_window.h"
#include "ui.h"
namespace z80analyze
{
SDL_Window *win = nullptr;
SDL_Renderer *ren = nullptr;
SDL_Texture *tex = nullptr;
SDL_Texture *uitex = nullptr;
uint16_t address = 0;
int mx, my;
bool needs_refresh = true;
void refreshTitle();
bool handleEvent(SDL_Event *e)
{
if (e->type == SDL_MOUSEBUTTONUP)
{
//if (z80::getMemTag(address)!=MEMTAG_INST) address = find_previous_opcode(address);
z80debug::setcursor(address);
z80debug::refresh();
/*if (e->button.button == 1)
z80::clearMemTouched();
//z80::clearMemTag();
else
z80::fixMemTouched();*/
refresh();
}
if (e->type == SDL_MOUSEMOTION)
{
if (e->motion.windowID == SDL_GetWindowID(win)) {
SDL_ShowCursor(SDL_DISABLE);
mx = e->motion.x/2;
my = e->motion.y/2;
refreshTitle();
needs_refresh=true;
focus();
}
}
if (e->type == SDL_KEYDOWN) {
if (e->key.keysym.scancode == SDL_SCANCODE_RETURN) {
/*
z80::fixMemTouched();
refresh();
z80debug::refresh();
*/
} else if (e->key.keysym.scancode == SDL_SCANCODE_BACKSPACE) {
const uint32_t size = mem::getSize();
//for (int i=0; i<size; ++i) mem::setTag(i, mem::getTag(i) & ~MEMTAG_TOUCHED);
for (int i=0; i<size; ++i) mem::setTag(i, MEMTAG_NONE);
refresh();
z80debug::refresh();
}
}
return true;
}
void show()
{
if (!win)
{
win = SDL_CreateWindow("Z80 Analyzer", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 512, 512, SDL_WINDOW_SHOWN);
ren = SDL_CreateRenderer(win, -1, 0);
tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 256, 256);
uitex = ui::createtexture(ren);
ui::window::registerWindow(SDL_GetWindowID(win), handleEvent);
}
refresh();
}
char tmp[10];
void refreshTitle()
{
if (mx>=0 && my>=0 && mx<256 && my<256) {
address = mx+my*256;
SDL_SetWindowTitle(win, SDL_itoa(address, tmp, 16));
}
}
void refresh(const bool conditional)
{
if (!win) return;
if (conditional && !needs_refresh) return;
needs_refresh = false;
ui::setrenderer(ren, uitex);
Uint32 *pixels;
int pitch;
SDL_LockTexture(tex, NULL, (void**)&pixels, &pitch);
for (int i=0; i<65536; ++i)
{
//uint8_t tag = z80::getMemTag(i);
//pixels[i] = tag==MEMTAG_NONE ? 0x808080 : tag==MEMTAG_DATA ? 0x0000FF : tag==MEMTAG_MIXED ? 0xFF00FF : 0x00FF00;
uint32_t none_color = i<0x4000 ? 0x101010 : i<0x5800 ? 0x202020 : i<0x5b00 ? 0x404040 : 0x808080;
uint8_t tag = mem::getTag(i);
pixels[i] = !(tag & MEMTAG_TOUCHED) ? none_color : (tag & MEMTAG_TINST) ? 0x00FF00 : (tag & MEMTAG_TREPEAT) ? 0xFF0000 : 0x0000FF;
}
pixels[z80::getPC()] = 0xFFFFFF;
if (mx>=0 && my>=0 && mx<256 && my<256) {
if (mx>2) pixels[(mx-2)+(my)*256] = 0x000000;
if (mx>1) pixels[(mx-1)+(my)*256] = 0x000000;
if (mx<255) pixels[(mx+1)+(my)*256] = 0x000000;
if (mx<254) pixels[(mx+2)+(my)*256] = 0x000000;
if (my>1) pixels[(mx)+(my-1)*256] = 0x000000;
if (my>2) pixels[(mx)+(my-2)*256] = 0x000000;
if (my<255) pixels[(mx)+(my+1)*256] = 0x000000;
if (my<254) pixels[(mx)+(my+2)*256] = 0x000000;
}
SDL_UnlockTexture(tex);
SDL_RenderCopy(ren, tex, NULL, NULL);
SDL_RenderPresent(ren);
refreshTitle();
}
void hide()
{
ui::window::unregisterWindow(SDL_GetWindowID(win));
SDL_DestroyTexture(uitex); uitex = nullptr;
SDL_DestroyTexture(tex); tex = nullptr;
SDL_DestroyRenderer(ren); ren = nullptr;
SDL_DestroyWindow(win); win = nullptr;
}
void focus()
{
if (win) {
SDL_RaiseWindow(win);
refresh();
}
}
}

9
z80analyze.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
namespace z80analyze
{
void show();
void refresh(const bool conditional=false);
void hide();
void focus();
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,18 +3,51 @@
namespace z80debug
{
void init();
void show();
void focus();
void hide();
void pause();
void stop();
void cont();
const bool debugging();
const bool paused();
void setmemmodified(const uint16_t addr);
void refresh();
void sendToConsole(const char* text);
void sendToConsoleLog(const char *text);
void sendMoreToConsoleLog(const char *text);
void DeleteCharConsole();
void executeConsole();
const bool isbreak(const uint16_t address, const uint8_t type=1);
uint32_t next();
uint32_t stepout();
void savestate(const char *filename);
void loadstate(const char *filename);
void loadngo(const char* filename, const char* addr);
void setcursor(const uint16_t address);
void cursorfwd();
void cursorback();
void useOpcode(const uint8_t opcode, const uint8_t base);
void clearUsedOpcodes();
void markUsedOpcodes();
const int getNumOpcodesUsed();
void printOpcodesUsed();
void search(const char *seq=nullptr);
namespace history
{
void reset();
void store();
void gototop();
void goforward();
void goback();
}
}

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,14 @@
namespace z80dis
{
void loadSymbols();
void saveSymbols();
const char *getAsm(const uint16_t pos);
const char *getOpcode(const uint16_t pos);
const int getOpcodeSize(const uint16_t pos);
const char *getSymbol(const uint16_t pos);
void setSymbol(const uint16_t pos, const char *sym);
const int getNumSymbols();
const uint16_t getSymbolAddress(const int pos);
}

33
z80viewer.cpp Normal file
View File

@@ -0,0 +1,33 @@
#include "z80viewer.h"
#include "ui.h"
#include "ui_window.h"
std::vector<viewer_t> z80viewer::viewers;
void z80viewer::registerViewer(const char *name, z80viewer *viewer)
{
viewer_t v;
strcpy(v.name, name);
v.viewer = viewer;
viewers.push_back( v );
}
z80viewer *z80viewer::getViewer(const char *name)
{
for (auto v : viewers)
{
if (strcmp(name, v.name)==0) return v.viewer;
}
return nullptr;
}
void z80viewer::refreshAll()
{
for (auto v : viewers) v.viewer->refresh();
}
bool z80viewer::handleEvents(SDL_Event *e)
{
for (auto v : viewers) v.viewer->handleEvent(e);
return true;
}

34
z80viewer.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include <SDL2/SDL.h>
#include <vector>
class z80viewer;
struct viewer_t
{
char name[12];
z80viewer *viewer;
};
class z80viewer
{
public:
virtual void show() = 0;
virtual void refresh() = 0;
virtual void hide() = 0;
virtual void focus() = 0;
virtual bool handleEvent(SDL_Event *e) = 0;
static void registerViewer(const char *name, z80viewer *viewer);
static z80viewer *getViewer(const char *name);
static void refreshAll();
static bool handleEvents(SDL_Event *e);
protected:
SDL_Window *win = nullptr;
SDL_Renderer *ren = nullptr;
SDL_Texture *tex = nullptr;
SDL_Texture *uitex = nullptr;
static std::vector<viewer_t> viewers;
};

80
zx_128bankviewer.cpp Normal file
View File

@@ -0,0 +1,80 @@
#include "zx_128bankviewer.h"
#include "z80.h"
#include "zx_mem.h"
//#include "zx_128mem.h"
#include "ui.h"
//#include "ui_window.h"
void zx_128bankviewer::show()
{
if (!win)
{
win = SDL_CreateWindow("Z80 Bank Viewer", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 128, 512, SDL_WINDOW_SHOWN);
ren = SDL_CreateRenderer(win, -1, 0);
tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 128, 512);
uitex = ui::createtexture(ren);
//ui::window::registerWindow(SDL_GetWindowID(win), handleEvent);
}
refresh();
}
void zx_128bankviewer::refresh()
{
if (!win) return;
ui::setrenderer(ren, uitex);
Uint32 *pixels;
int pitch;
SDL_LockTexture(tex, NULL, (void**)&pixels, &pitch);
for (int i=0; i<65536; ++i)
{
//uint8_t tag = z80::getMemTag(i);
//pixels[i] = tag==MEMTAG_NONE ? 0x808080 : tag==MEMTAG_DATA ? 0x0000FF : tag==MEMTAG_MIXED ? 0xFF00FF : 0x00FF00;
uint32_t none_color = i<0x4000 ? 0x101010 : i<0x5800 ? 0x202020 : i<0x5b00 ? 0x404040 : 0x808080;
uint8_t tag = mem::getTag(i);
pixels[i] = !(tag & MEMTAG_TOUCHED) ? none_color : (tag & MEMTAG_TINST) ? 0x00FF00 : (tag & MEMTAG_TREPEAT) ? 0xFF0000 : 0x0000FF;
}
pixels[z80::getPC()] = 0xFFFFFF;
SDL_UnlockTexture(tex);
SDL_RenderCopy(ren, tex, NULL, NULL);
/*
char temp[256];
zx_128mem* mem = ((zx_128mem*)z80mem::get());
sprintf(temp, "%u", mem->getPage(0));
ui::placetxt(1,1,temp, COLOR_WHITE);
sprintf(temp, "%u", mem->getPage(1));
ui::placetxt(1,129,temp, COLOR_WHITE);
sprintf(temp, "%u", mem->getPage(2));
ui::placetxt(1,257,temp, COLOR_WHITE);
sprintf(temp, "%u", mem->getPage(3));
ui::placetxt(1,385,temp, COLOR_WHITE);
*/
SDL_RenderPresent(ren);
}
void zx_128bankviewer::hide()
{
//ui::window::unregisterWindow(SDL_GetWindowID(win));
SDL_DestroyTexture(tex); tex = nullptr;
SDL_DestroyRenderer(ren); ren = nullptr;
SDL_DestroyWindow(win); win = nullptr;
}
void zx_128bankviewer::focus()
{
if (win) {
SDL_RaiseWindow(win);
refresh();
}
}
bool zx_128bankviewer::handleEvent(SDL_Event *e)
{
return true;
}

12
zx_128bankviewer.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "z80viewer.h"
class zx_128bankviewer : public z80viewer
{
public:
void show();
void refresh();
void hide();
void focus();
bool handleEvent(SDL_Event *e);
};

61
zx_128pageviewer.cpp Normal file
View File

@@ -0,0 +1,61 @@
#include "zx_128pageviewer.h"
#include "z80.h"
#include "zx_mem.h"
void zx_128pageviewer::show()
{
if (!win)
{
win = SDL_CreateWindow("ZX128 Page Viewer", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1024, 128, SDL_WINDOW_SHOWN);
ren = SDL_CreateRenderer(win, -1, 0);
tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 1024, 128);
//ui::window::registerWindow(SDL_GetWindowID(win), handleEvent);
}
refresh();
}
void zx_128pageviewer::refresh()
{
if (!win) return;
Uint32 *pixels;
int pitch;
const uint8_t *tags = mem::rawTagPtr(0);
SDL_LockTexture(tex, NULL, (void**)&pixels, &pitch);
for (int i=0; i<131072; ++i)
{
const int x = (i&0x7f) + (((i>>14)&0x07)*128);
const int y = (i>>7)&0x7f;
uint32_t none_color = 0x808080; //i<0x4000 ? 0x101010 : i<0x5800 ? 0x202020 : i<0x5b00 ? 0x404040 : 0x808080;
uint8_t tag = tags[i];
pixels[x+y*1024] = !(tag & MEMTAG_TOUCHED) ? none_color : (tag & MEMTAG_TINST) ? 0x00FF00 : (tag & MEMTAG_TREPEAT) ? 0xFF0000 : 0x0000FF;
}
//pixels[z80::getPC()] = 0xFFFFFF;
SDL_UnlockTexture(tex);
SDL_RenderCopy(ren, tex, NULL, NULL);
SDL_RenderPresent(ren);
}
void zx_128pageviewer::hide()
{
//ui::window::unregisterWindow(SDL_GetWindowID(win));
SDL_DestroyTexture(tex); tex = nullptr;
SDL_DestroyRenderer(ren); ren = nullptr;
SDL_DestroyWindow(win); win = nullptr;
}
void zx_128pageviewer::focus()
{
if (win) {
SDL_RaiseWindow(win);
refresh();
}
}
bool zx_128pageviewer::handleEvent(SDL_Event *e)
{
return true;
}

13
zx_128pageviewer.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include "z80viewer.h"
class zx_128pageviewer : public z80viewer
{
public:
void show();
void refresh();
void hide();
void focus();
bool handleEvent(SDL_Event *e);
};

81
zx_dialog_joystick.cpp Normal file
View File

@@ -0,0 +1,81 @@
#include "zx_dialog_joystick.h"
#include "ui.h"
#include "z80debug.h"
#include "gamepad.h"
namespace dialogs
{
namespace joysticks
{
int32_t selected = 0;
void init(int32_t index)
{
if (index != -1) selected = index;
ui::setDialog(dialogs::joysticks::show);
z80debug::pause();
}
void show()
{
uint8_t back_color = COLOR_DARK;
uint8_t front_color = COLOR_WHITE;
ui::setoffset(0,0);
ui::printrect(36, 15, 46, 12, COLOR_DARK);
ui::panel(36,15,46,12,"CONFIGURE JOYSTICKS:");
if (ui::mouseInside(34,9,7,1)) {
back_color = COLOR_WHITE;
front_color = COLOR_BLACK;
if (ui::getClicked()) { ui::setDialog(nullptr); z80debug::cont(); }
}
ui::printrect(34, 9, 7, 1, back_color);
ui::printtxt(35,9,"CLOSE", front_color);
ui::panel(38,17,20,8,"JOYSTICKS:");
const int num_gamepads = gamepad::getNumGamepads();
if (selected>=num_gamepads) selected = 0;
for (int i=0; i<num_gamepads; ++i)
{
char name[] = "JOYSTICK 0";
name[9] = 48+i;
back_color = COLOR_DARK;
front_color = COLOR_WHITE;
if (ui::mouseInside(0,i,18,1)) {
back_color = COLOR_BLACK;
if (ui::getClicked()) {
selected = i;
}
}
if (selected==i) {
back_color = COLOR_WHITE;
front_color = COLOR_BLACK;
}
ui::printrect(0,i,18,1, back_color);
ui::printtxt(0,i,name, front_color);
}
ui::panel(60,17,20,8,"TYPE:");
const int selected_type = gamepad::getGamepadType(selected);
const char* types[5] = {"SINCLAIR 1", "SINCLAIR 2", "KEMPSTON", "FULLER", "CUSTOM"};
for (int i=0;i<5;++i)
{
back_color = COLOR_DARK;
front_color = COLOR_WHITE;
if (ui::mouseInside(0,i,18,1)) {
back_color = COLOR_BLACK;
if (ui::getClicked()) {
gamepad::setGamepadType(selected, i+1);
}
}
if (i==selected_type-1) {
back_color = COLOR_WHITE;
front_color = COLOR_BLACK;
}
ui::printrect(0,i,18,1, back_color);
ui::printtxt(0,i,types[i],front_color);
}
}
}
}

11
zx_dialog_joystick.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include <stdint.h>
namespace dialogs
{
namespace joysticks
{
void init(int32_t index);
void show();
}
}

694
zx_disk.cpp Normal file
View File

@@ -0,0 +1,694 @@
#include "zx_disk.h"
#include "z80.h"
#include <stdio.h>
#include <stdlib.h>
#define ZX_FDC_MODE_IDLE 0
#define ZX_FDC_MODE_COMMAND 1
#define ZX_FDC_MODE_EXECUTION 2
#define ZX_FDC_MODE_RESULT 3
#define ZX_FDC_DATA_DIRECTION_INPUT 0x00
#define ZX_FDC_DATA_DIRECTION_OUTPUT 0x40
#define ZX_FDC_COMMAND_MASK 0x1F
#define ZX_FDC_COMMAND_SPECIFY 0x03
#define ZX_FDC_COMMAND_SENSE_DRIVE_STATUS 0x04
#define ZX_FDC_COMMAND_RECALIBRATE 0x07
#define ZX_FDC_COMMAND_SENSE_INTERRUPT_STATUS 0x08
#define ZX_FDC_COMMAND_SEEK 0x0F
#define ZX_FDC_COMMAND_READ_ID 0x0A
#define ZX_FDC_COMMAND_READ_DATA 0x06
#define ZX_FDC_COMMAND_READ_DELETED_DATA 0x0C
#define ZX_FDC_MAIN_DRIVE0_BUSY 1
#define ZX_FDC_MAIN_DRIVE1_BUSY 2
#define ZX_FDC_MAIN_DRIVE2_BUSY 4
#define ZX_FDC_MAIN_DRIVE3_BUSY 8
#define ZX_FDC_MAIN_BUSY 16
#define ZX_FDC_MAIN_EXEC_MODE 32
#define ZX_FDC_MAIN_DATA_DIR 64 // 0: CPU->FDC 1: FDC->CPU
#define ZX_FDC_MAIN_DATA_READY 128
namespace zx_disk
{
struct sector_t
{
uint8_t id { 0 };
uint8_t size { 0 };
uint8_t st1 { 0 };
uint8_t st2 { 0 };
uint16_t data_length{ 0 };
uint8_t *data { nullptr };
};
struct track_t
{
uint8_t size { 0 };
uint8_t sector_size { 0 };
uint8_t num_sectors { 0 };
uint8_t filler_byte { 0x35 };
sector_t *sectors { nullptr };
};
struct disk_t
{
uint8_t num_tracks { 0 };
uint8_t num_sides { 0 };
track_t *tracks { nullptr };
};
disk_t disk;
uint8_t mode = ZX_FDC_MODE_IDLE;
uint8_t call_count = 0;
//uint8_t data_direction = 0x00; // 0x40
uint8_t fdd0busy = 0;
bool command_success = false;
uint8_t srt, hlt, hut, nd;
bool seeking = false;
uint8_t current_head = 0;
uint8_t current_drive = 0;
uint8_t current_track = 0;
uint8_t current_sector = 0;
uint16_t current_byte = 0;
uint16_t bytes_to_read = 0;
uint8_t eot = 0;
uint8_t st1 = 0;
uint8_t st2 = 0;
int zx_fdc_main_status_port_in(int port);
int zx_fdc_data_port_in(int port);
void zx_fdc_data_port_out(int port, int val);
void init()
{
z80::connect_port(0x2ffd, 0xf002, zx_fdc_main_status_port_in, nullptr);
z80::connect_port(0x3ffd, 0xf002, zx_fdc_data_port_in, zx_fdc_data_port_out);
load("goldenaxe1.dsk");
}
void reset()
{
}
void load(const char *filename)
{
FILE *f = fopen(filename, "rb");
if (!f) return;
fseek(f, 0, SEEK_END);
const int file_size = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *buffer = (uint8_t*)malloc(file_size);
fread(buffer, file_size, 1, f);
fclose(f);
if (disk.tracks) {
for (int i=0; i<disk.num_tracks; ++i) {
for (int j=0; j<disk.tracks[i].num_sectors; ++j) {
free(disk.tracks[i].sectors[j].data);
}
free(disk.tracks[i].sectors);
}
free(disk.tracks);
}
// Hardcoded to read extended disks. Also, no security checks at all. Fuck the police.
disk.num_tracks = buffer[0x30];
disk.num_sides = buffer[0x31];
disk.tracks = (track_t*)malloc(disk.num_tracks*sizeof(track_t));
for (int i=0; i<disk.num_tracks; ++i) disk.tracks[i].size = buffer[0x34+i];
uint32_t pos = 0x100;
for (int i=0; i<disk.num_tracks; ++i)
{
track_t &track = disk.tracks[i];
track.sector_size = buffer[pos+0x14];
track.num_sectors = buffer[pos+0x15];
track.filler_byte = buffer[pos+0x17];
track.sectors = (sector_t*)malloc(track.num_sectors*sizeof(sector_t));
pos += 0x18;
for (int j=0; j<track.num_sectors; ++j)
{
sector_t &sector = track.sectors[j];
sector.id = buffer[pos+0x02];
sector.size = buffer[pos+0x03];
sector.st1 = buffer[pos+0x04];
sector.st2 = buffer[pos+0x05];
sector.data_length = buffer[pos+0x06] + uint16_t((buffer[pos+0x07])<<8);
if (sector.data_length==0) sector.data_length = uint16_t(sector.size)<<8;
pos += 8;
}
if (pos&0xff) pos = (pos & 0xffffff00) + 0x100;
for (int j=0; j<track.num_sectors; ++j)
{
sector_t &sector = track.sectors[j];
const uint16_t size = uint16_t(sector.size)<<8;
sector.data = (uint8_t*)malloc(size);
for (int k=0; k<size; ++k) sector.data[k] = buffer[pos++];
}
}
}
uint8_t find_sector_id(uint8_t sector_id)
{
track_t &track = disk.tracks[current_track];
for (int j=0; j<track.num_sectors; ++j)
{
if (track.sectors[j].id == sector_id) return j;
}
return 0xff;
}
uint8_t main_status_register()
{
const uint8_t val = (fdd0busy) |
((mode==ZX_FDC_MODE_IDLE) ? 0x00 : 0x10) |
((mode==ZX_FDC_MODE_EXECUTION) ? 0x20 : 0x00) |
((mode==ZX_FDC_MODE_RESULT) ? 0x40 : 0x00) |
(0x80);
return val;
}
int zx_fdc_main_status_port_in(int port)
{
//if (mode != ZX_FDC_MODE_EXECUTION) printf("FDC port 0x2ffd IN\n");
return main_status_register();
}
uint8_t ST0()
{
return current_drive | (current_head<<2) | (seeking ? 0x20 : 0x00) | (fdd0busy ? 0x00 : 0x80);
}
uint8_t ST3()
{
const bool ready = (current_head==0) && (current_drive==0);
return current_drive | (current_head<<2) | ((current_track==0)?0x10:0) |
(ready?0x20:0) | 0x00; // TS
}
void start_command(uint8_t command);
uint8_t (*process_current_command)(uint8_t) = nullptr;
uint8_t process_command_unknown(uint8_t command);
uint8_t process_command_specify(uint8_t command);
uint8_t process_command_sense_drive_status(uint8_t command);
uint8_t process_command_recalibrate(uint8_t command);
uint8_t process_command_sense_interrupt_status(uint8_t command);
uint8_t process_command_seek(uint8_t command);
uint8_t process_command_read_id(uint8_t command);
uint8_t process_command_read_data(uint8_t command);
int zx_fdc_data_port_in(int port)
{
//if (mode != ZX_FDC_MODE_EXECUTION) printf("FDC port 0x3ffd IN\n");
if (mode == ZX_FDC_MODE_COMMAND) {
printf("IGNORED!\n");
return 0;
}
if (process_current_command) return process_current_command(0);
return 0;
}
void zx_fdc_data_port_out(int port, int val)
{
//printf("FDC port 0x3ffd OUT: 0x%02x\n", val);
if (mode == ZX_FDC_MODE_RESULT) {
printf("IGNORED!\n");
return;
}
if (process_current_command)
process_current_command(val);
else
start_command(val);
}
void start_command(uint8_t command)
{
if ((fdd0busy) && ((command & ZX_FDC_COMMAND_MASK) != ZX_FDC_COMMAND_SENSE_INTERRUPT_STATUS)) return;
mode = ZX_FDC_MODE_COMMAND;
call_count = 1;
debug::composeCommand(command);
switch(command & ZX_FDC_COMMAND_MASK)
{
case ZX_FDC_COMMAND_SPECIFY:
process_current_command = process_command_specify;
break;
case ZX_FDC_COMMAND_SENSE_DRIVE_STATUS:
process_current_command = process_command_sense_drive_status;
break;
case ZX_FDC_COMMAND_RECALIBRATE:
process_current_command = process_command_recalibrate;
fdd0busy = 1;
break;
case ZX_FDC_COMMAND_SENSE_INTERRUPT_STATUS:
process_current_command = process_command_sense_interrupt_status;
mode = ZX_FDC_MODE_RESULT;
call_count = 0;
break;
case ZX_FDC_COMMAND_SEEK:
process_current_command = process_command_seek;
fdd0busy = 1;
seeking = true;
break;
case ZX_FDC_COMMAND_READ_ID:
process_current_command = process_command_read_id;
break;
case ZX_FDC_COMMAND_READ_DATA:
process_current_command = process_command_read_data;
break;
case ZX_FDC_COMMAND_READ_DELETED_DATA:
process_current_command = process_command_read_data;
break;
default:
{
debug::printCommand();
process_current_command = process_command_unknown;
mode = ZX_FDC_MODE_RESULT;
call_count = 0;
}
}
}
// ===================================================================
// FDC COMMAND: UNKNOWN
// ===================================================================
uint8_t process_command_unknown(uint8_t command)
{
process_current_command = nullptr;
mode = ZX_FDC_MODE_IDLE;
const uint8_t val = ST0();
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
debug::printResult();
return val;
}
// ===================================================================
// FDC COMMAND: SPECIFY (0x03)
// ===================================================================
uint8_t process_command_specify(uint8_t command)
{
debug::composeCommand(command);
switch (call_count) {
case 1:
srt = (command & 0xf0) >> 4;
hut = command & 0x0f;
call_count++;
break;
case 2:
hlt = (command & 0xfe) >> 1;
nd = command & 0x01;
call_count=0;
process_current_command = nullptr;
mode = ZX_FDC_MODE_IDLE;
debug::printCommand();
break;
}
return 0;
}
// ===================================================================
// FDC COMMAND: SENSE DRIVE STATUS (0x04)
// ===================================================================
uint8_t process_command_sense_drive_status(uint8_t command)
{
switch (mode)
{
case ZX_FDC_MODE_COMMAND:
current_head = (command & 0x4)>>2;
current_drive = command & 0x3;
call_count = 0;
mode = ZX_FDC_MODE_RESULT;
debug::composeCommand(command);
debug::printCommand();
break;
case ZX_FDC_MODE_RESULT:
{
process_current_command = nullptr;
mode = ZX_FDC_MODE_IDLE;
const uint8_t val = ST3();
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
debug::printResult();
return val;
}
}
return 0;
}
// ===================================================================
// FDC COMMAND: RECALIBRATE (0x07)
// ===================================================================
uint8_t process_command_recalibrate(uint8_t command)
{
switch (mode)
{
case ZX_FDC_MODE_COMMAND:
current_drive = command & 0x3;
call_count = 0;
current_track = 0;
seeking = true;
process_current_command = nullptr;
mode = ZX_FDC_MODE_IDLE;
debug::composeCommand(command);
debug::printCommand();
break;
}
return 0;
}
// ===================================================================
// FDC COMMAND: SENSE INTERRUPT STATUS (0x08)
// ===================================================================
uint8_t process_command_sense_interrupt_status(uint8_t command)
{
switch (call_count)
{
case 0:
{
debug::printCommand();
call_count++;
const uint8_t val = ST0();
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 1:
process_current_command = nullptr;
fdd0busy = 0;
seeking = false;
call_count = 0;
mode = ZX_FDC_MODE_IDLE;
//printf("--> (returning 0x%02x)\n", current_track);
debug::composeResult(current_track);
debug::printResult();
return current_track;
}
return 0;
}
// ===================================================================
// FDC COMMAND: SEEK (0x0F)
// ===================================================================
uint8_t process_command_seek(uint8_t command)
{
debug::composeCommand(command);
switch (call_count)
{
case 1:
{
current_head = (command & 0x4)>>2;
current_drive = command & 0x3;
call_count++;
break;
}
case 2:
current_track = command;
process_current_command = nullptr;
call_count = 0;
mode = ZX_FDC_MODE_IDLE;
debug::printCommand();
break;
}
return 0;
}
// ===================================================================
// FDC COMMAND: READ ID (0x0A)
// ===================================================================
uint8_t process_command_read_id(uint8_t command)
{
switch (mode)
{
case ZX_FDC_MODE_COMMAND:
current_head = (command & 0x4)>>2;
current_drive = command & 0x3;
call_count = 0;
current_sector = 0;
mode = ZX_FDC_MODE_RESULT;
debug::composeCommand(command);
debug::printCommand();
break;
case ZX_FDC_MODE_RESULT:
{
switch (call_count)
{
case 0:
{
call_count++;
fdd0busy = 1;
const uint8_t val = ST0();
fdd0busy = 0;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 1:
{
call_count++;
const uint8_t val = 0x00; // ST1
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 2:
{
call_count++;
const uint8_t val = 0x00; // ST2
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 3:
{
call_count++;
const uint8_t val = current_track;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 4:
{
call_count++;
const uint8_t val = current_head;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 5:
{
call_count++;
const uint8_t val = disk.tracks[current_track].sectors[current_sector].id;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 6:
{
call_count = 0;
process_current_command = nullptr;
mode = ZX_FDC_MODE_IDLE;
const uint8_t val = disk.tracks[current_track].sectors[current_sector].size;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
debug::printResult();
return val;
}
}
}
}
return 0;
}
// ===================================================================
// FDC COMMAND: READ DATA (0x06)
// ===================================================================
uint8_t process_command_read_data(uint8_t command)
{
switch (mode)
{
case ZX_FDC_MODE_COMMAND:
debug::composeCommand(command);
switch (call_count)
{
case 1:
call_count++;
current_head = (command & 0x4)>>2;
current_drive = command & 0x3;
break;
case 2:
call_count++;
current_track = command;
break;
case 3:
call_count++;
current_head = command;
break;
case 4:
call_count++;
current_sector = find_sector_id(command);
break;
case 5:
call_count++;
bytes_to_read = command;
break;
case 6:
call_count++;
eot = command;
break;
case 7:
call_count++;
break;
case 8:
if ( (bytes_to_read==0) && (command != 0xff) ) bytes_to_read = command;
bytes_to_read = bytes_to_read << 8;
current_byte = 0;
call_count = 0;
mode = ZX_FDC_MODE_EXECUTION;
debug::printCommand();
break;
}
break;
case ZX_FDC_MODE_EXECUTION:
{
const uint8_t val = disk.tracks[current_track].sectors[current_sector].data[current_byte];
current_byte++;
if (current_byte == disk.tracks[current_track].sectors[current_sector].data_length) {
current_byte = 0;
current_sector = disk.tracks[current_track].sectors[current_sector].id;
current_sector++;
current_sector = find_sector_id(current_sector);
if (current_sector == 0xff) {
current_track++;
current_sector = 0;
}
}
bytes_to_read--;
if (bytes_to_read==0) {
st1 = disk.tracks[current_track].sectors[current_sector].st1;
st2 = disk.tracks[current_track].sectors[current_sector].st2;
mode = ZX_FDC_MODE_RESULT;
}
return val;
break;
}
case ZX_FDC_MODE_RESULT:
{
switch (call_count)
{
case 0:
{
call_count++;
fdd0busy = 1;
const uint8_t val = ST0();
fdd0busy = 0;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 1:
{
call_count++;
const uint8_t val = st1;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 2:
{
call_count++;
const uint8_t val = st2;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 3:
{
call_count++;
const uint8_t val = current_track;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 4:
{
call_count++;
const uint8_t val = current_head;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 5:
{
call_count++;
const uint8_t val = disk.tracks[current_track].sectors[current_sector].id;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
return val;
}
case 6:
{
call_count = 0;
process_current_command = nullptr;
mode = ZX_FDC_MODE_IDLE;
const uint8_t val = disk.tracks[current_track].sectors[current_sector].size;
//printf("--> (returning 0x%02x)\n", val);
debug::composeResult(val);
debug::printResult();
return val;
}
}
break;
}
}
return 0;
}
namespace debug
{
uint8_t values[9];
uint8_t num_values = 0;
void composeCommand(const uint8_t value)
{
values[num_values++] = value;
}
void composeResult(const uint8_t value)
{
values[num_values++] = value;
}
void printCommand()
{
printf("FDC COMMAND %02x: ( ", values[0] & 0x1f);
for (int i=0; i<num_values; ++i) printf("%02x ", values[i]);
printf(")\n");
num_values = 0;
}
void printResult()
{
printf("FDC RESULT: ( ");
for (int i=0; i<num_values; ++i) printf("%02x ", values[i]);
printf(")\n");
num_values = 0;
}
}
}

18
zx_disk.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <stdint.h>
namespace zx_disk
{
void init();
void reset();
void load(const char *filename);
namespace debug
{
void composeCommand(const uint8_t value);
void composeResult(const uint8_t value);
void printCommand();
void printResult();
}
}

240
zx_mem.cpp Normal file
View File

@@ -0,0 +1,240 @@
#include "zx_mem.h"
#include <stdlib.h>
#include "z80.h"
#include "zx_screen.h"
#define ZX_128MEM_PAGE 0x07
#define ZX_128MEM_SCREEN 0x08
#define ZX_128MEM_ROM 0x10
#define ZX_128MEM_DISPAG 0x20
#define ZX_2A_3_PAGING_MODE 0x01
#define ZX_2A_3_HROM 0x04
#define ZX_2A_3_SPECIAL 0x06
namespace mem
{
uint8_t mode = ZX_48K;
uint32_t ram_size = 48*1024;
uint32_t rom_size = 16*1024;
uint8_t *ram = nullptr;
uint8_t *rom = nullptr;
uint8_t *slot[8];
bool writable[8];
uint8_t config_128K = 0;
uint8_t config_2A_3 = 0;
void zx_128_port_out(int port, int val);
void zx_2A_3_port_out(int port, int val);
void init(uint8_t mode)
{
mem::mode = mode;
reset();
}
void reset()
{
if (ram) free(ram);
if (rom) free(rom);
FILE* f;
switch(mode)
{
case ZX_48K:
ram_size = 48*1024;
ram = (uint8_t*)malloc(ram_size);
for (int i=0; i<ram_size; ++i) ram[i] = 0;
//for (int i=0; i<65536; ++i) tags[i] = MEMTAG_NONE;
rom_size = 16*1024;
rom = (uint8_t*)malloc(rom_size);
f = fopen("48.rom", "rb");
fread(rom, rom_size, 1, f);
fclose(f);
slot[0] = &rom[0*8192]; slot[1] = &rom[1*8192];
slot[2] = &ram[0*8192]; slot[3] = &ram[1*8192];
slot[4] = &ram[2*8192]; slot[5] = &ram[3*8192];
slot[6] = &ram[4*8192]; slot[7] = &ram[5*8192];
writable[0] = writable[1] = false;
for (int i=2;i<8;++i) writable[i] = true;
break;
case ZX_128K:
ram_size = 128*1024;
ram = (uint8_t*)malloc(ram_size);
for (int i=0x0000; i<ram_size; ++i) ram[i] = 0;
rom_size = 32*1024;
rom = (uint8_t*)malloc(rom_size);
f = fopen("128k.rom", "rb");
fread(rom, rom_size, 1, f);
fclose(f);
slot[0] = &rom[0*8192]; slot[1] = &rom[1*8192];
slot[2] = &ram[10*8192]; slot[3] = &ram[11*8192];
slot[4] = &ram[4*8192]; slot[5] = &ram[5*8192];
slot[6] = &ram[0*8192]; slot[7] = &ram[1*8192];
writable[0] = writable[1] = false;
for (int i=2;i<8;++i) writable[i] = true;
z80::connect_port(0x7ffd, 0x8002, nullptr, mem::zx_128_port_out);
break;
case ZX_PLUS3:
ram_size = 128*1024;
ram = (uint8_t*)malloc(ram_size);
for (int i=0x0000; i<ram_size; ++i) ram[i] = 0;
rom_size = 64*1024;
rom = (uint8_t*)malloc(rom_size);
f = fopen("plus3.rom", "rb");
fread(rom, rom_size, 1, f);
fclose(f);
slot[0] = &rom[0*8192]; slot[1] = &rom[1*8192];
slot[2] = &ram[10*8192]; slot[3] = &ram[11*8192];
slot[4] = &ram[4*8192]; slot[5] = &ram[5*8192];
slot[6] = &ram[0*8192]; slot[7] = &ram[1*8192];
writable[0] = writable[1] = false;
for (int i=2;i<8;++i) writable[i] = true;
z80::connect_port(0x7ffd, 0xc002, nullptr, mem::zx_128_port_out);
z80::connect_port(0x1ffd, 0xf002, nullptr, mem::zx_2A_3_port_out);
break;
}
}
uint8_t readMem(uint16_t address)
{
const uint8_t slot_num = address >> 13;
const uint16_t displacement = address & 0x1fff;
return slot[slot_num][displacement];
}
void writeMem(uint16_t address, uint8_t value)
{
const uint8_t slot_num = address >> 13;
if (!writable[slot_num]) return;
const uint16_t displacement = address & 0x1fff;
slot[slot_num][displacement] = value;
}
void loadMem(uint16_t address, uint16_t len, uint8_t *buffer)
{
}
uint8_t getTag(uint16_t address)
{
return 0;
}
void setTag(uint16_t address, uint8_t value)
{
}
void saveState(FILE* f)
{
fwrite(ram, 0xc000, 1, f);
}
void loadState(FILE* f)
{
fread(ram, 0xc000, 1, f);
}
uint32_t getSize()
{
return 0;
}
uint8_t *rawPtr(uint32_t address)
{
return &ram[address];
}
uint8_t *rawTagPtr(uint32_t address)
{
return nullptr;
}
void zx_128_port_out(int port, int val)
{
if (port != 0x7ffd) return;
if (config_128K & ZX_128MEM_DISPAG) return;
const bool shadow = config_128K & ZX_128MEM_SCREEN;
config_128K = val;
if (config_2A_3 & ZX_2A_3_PAGING_MODE) return;
if (config_128K & ZX_128MEM_SCREEN) {
if (!shadow) zxscreen::setBaseAddresses(0x4000*7, 0x1800+0x4000*7);
} else {
if (shadow) zxscreen::setBaseAddresses(0x4000*5, 0x1800+0x4000*5);
}
uint8_t hrom = (config_2A_3 & ZX_2A_3_HROM) ? 4 : 0;
if (config_128K & ZX_128MEM_ROM) {
slot[0] = &rom[(hrom+2)*8192]; slot[1] = &rom[(hrom+3)*8192];
} else {
slot[0] = &rom[(hrom+0)*8192]; slot[1] = &rom[(hrom+1)*8192];
}
const uint8_t slot3 = (config_128K&ZX_128MEM_PAGE)*2;
slot[6] = &ram[slot3*8192]; slot[7] = &ram[(slot3+1)*8192];
}
void zx_2A_3_port_out(int port, int val)
{
if (port != 0x1ffd) return;
if (config_128K & ZX_128MEM_DISPAG) return;
config_2A_3 = val;
if (config_2A_3 & ZX_2A_3_PAGING_MODE) {
for (int i=0;i<8;++i) writable[i] = true;
switch ((config_2A_3 & ZX_2A_3_SPECIAL)>>1)
{
case 0:
slot[0] = &ram[0*8192]; slot[1] = &ram[1*8192];
slot[2] = &ram[2*8192]; slot[3] = &ram[3*8192];
slot[4] = &ram[4*8192]; slot[5] = &ram[5*8192];
slot[6] = &ram[6*8192]; slot[7] = &ram[7*8192];
break;
case 1:
slot[0] = &ram[8*8192]; slot[1] = &ram[9*8192];
slot[2] = &ram[10*8192]; slot[3] = &ram[11*8192];
slot[4] = &ram[12*8192]; slot[5] = &ram[13*8192];
slot[6] = &ram[14*8192]; slot[7] = &ram[15*8192];
break;
case 2:
slot[0] = &ram[8*8192]; slot[1] = &ram[9*8192];
slot[2] = &ram[10*8192]; slot[3] = &ram[11*8192];
slot[4] = &ram[12*8192]; slot[5] = &ram[13*8192];
slot[6] = &ram[6*8192]; slot[7] = &ram[7*8192];
break;
case 3:
slot[0] = &ram[8*8192]; slot[1] = &ram[9*8192];
slot[2] = &ram[14*8192]; slot[3] = &ram[15*8192];
slot[4] = &ram[12*8192]; slot[5] = &ram[13*8192];
slot[6] = &ram[6*8192]; slot[7] = &ram[7*8192];
break;
}
} else {
writable[0] = writable[1] = false;
for (int i=2;i<8;++i) writable[i] = true;
uint8_t hrom = (config_2A_3 & ZX_2A_3_HROM) ? 4 : 0;
if (config_128K & ZX_128MEM_ROM) {
slot[0] = &rom[(hrom+2)*8192]; slot[1] = &rom[(hrom+3)*8192];
} else {
slot[0] = &rom[(hrom+0)*8192]; slot[1] = &rom[(hrom+1)*8192];
}
}
}
}

40
zx_mem.h Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include <stdint.h>
#include <stdio.h>
#include "zx_system.h"
#define MEMTAG_NONE 0x00
#define MEMTAG_DATA 0x01
#define MEMTAG_INST 0x02
#define MEMTAG_CODE 0x04
#define MEMTAG_IGNORE 0x08
#define MEMTAG_TDATA 0x10
#define MEMTAG_TINST 0x20
#define MEMTAG_TREPEAT 0x40
#define MEMTAG_MODIFIED 0x80
#define MEMTAG_KNOWN 0x07
#define MEMTAG_TOUCHED 0x70
namespace mem
{
void init(uint8_t mode);
void reset();
uint8_t readMem(uint16_t address);
void writeMem(uint16_t address, uint8_t value);
void loadMem(uint16_t address, uint16_t len, uint8_t *buffer);
uint8_t getTag(uint16_t address);
void setTag(uint16_t address, uint8_t value);
void saveState(FILE* f);
void loadState(FILE* f);
uint32_t getSize();
uint8_t *rawPtr(uint32_t address);
uint8_t *rawTagPtr(uint32_t address);
}

View File

@@ -1,7 +1,14 @@
#include "zx_screen.h"
#include "z80.h"
#include "zx_mem.h"
#include "zx_ula.h"
#include <SDL2/SDL.h>
#include "zx_tape.h"
#include "ui_window.h"
#include "z80debug.h"
#include "ui.h"
#include "file.h"
//#include "zx_128mem.h"
namespace zxscreen
{
@@ -12,64 +19,390 @@ namespace zxscreen
SDL_Window *win = nullptr;
SDL_Renderer *ren = nullptr;
SDL_Texture *tex = nullptr;
SDL_Texture *uitex = nullptr;
bool _flash = false;
int mode = SCREEN_MODE_48K;
uint32_t t_states_total = 69888;
uint32_t t_states_per_scanline = 224;
uint32_t vsync_lines = 16;
bool interrupt_enabled = true;
void flash() { _flash = not _flash; }
uint8_t zoom = 2;
bool fullscreen = false;
bool full_refresh = true;
int fullscreen_scale = 1;
SDL_Rect dest_rect;
void show()
uint32_t time=0;
uint32_t t_screen = 0;
uint8_t t_flash = 0;
bool flash = false;
uint32_t pixel_base_addr = 0x4000;
uint32_t color_base_addr = 0x5800;
uint32_t *pixel_addr = nullptr; //[69888];
uint32_t *color_addr = nullptr; //[69888];
uint8_t zx_pixels[352*296];
uint8_t *ptr_pixel = zx_pixels;
#define SCREEN_SYNC 0xFFFF
#define SCREEN_BORDER 0xFFFE
void saveWindowConfiguration()
{
if (win) return;
win = SDL_CreateWindow("ZX Spectrum Screen", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 352, 288, SDL_WINDOW_RESIZABLE);
ren = SDL_CreateRenderer(win, -1, 0);
tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 352, 288);
refresh();
file::setConfigValueInteger("screen_zoom", zoom);
int x, y;
SDL_GetWindowPosition(win, &x, &y);
file::setConfigValueInteger("screen_x", x);
file::setConfigValueInteger("screen_y", y);
}
void refresh()
void create_tables()
{
const uint8_t* memory = z80::getMem();
const uint8_t border_color = zx_ula::get_border_color();
//memory+=0x4000;
uint32_t count = 0;
Uint32* pixels;
int pitch;
SDL_LockTexture(tex, NULL, (void**)&pixels, &pitch);
if (pixel_addr) free(pixel_addr);
if (color_addr) free(color_addr);
pixel_addr = (uint32_t*)malloc(t_states_total*sizeof(uint32_t));
color_addr = (uint32_t*)malloc(t_states_total*sizeof(uint32_t));
uint32_t *ptr_pixel = pixel_addr;
uint32_t *ptr_color = color_addr;
// vsync
for (int i=0; i<t_states_per_scanline*vsync_lines;++i) { *(ptr_pixel++) = 0; *(ptr_color++) = SCREEN_SYNC; } // En el 128K 16 passa a ser 15 i 224 passa a ser 228
// Upper border
for (int i=0; i<352*48;++i) *(pixels++) = palette[border_color];
for (int i=0; i<48;++i) {
// hsync
for (int j=0;j<t_states_per_scanline-176;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = SCREEN_SYNC; } // En el 128K 48 passsa a ser 52
//border
for (int j=0;j<176;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = SCREEN_BORDER; count+=2; }
}
// scanlines
for (uint8_t y=0; y<192; ++y)
{
// hsync
for (int j=0;j<t_states_per_scanline-176;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = SCREEN_SYNC; } // En el 128K 48 passsa a ser 52
// Left border
for (int j=0;j<48;++j) *(pixels++) = palette[border_color];
for (int j=0;j<24;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = SCREEN_BORDER; count+=2; }
// Actual screen
for (uint8_t x=0;x<32;++x)
{
uint8_t color = memory[0x5800 + x + (y>>3)*32];
uint16_t address = 0x4000 | (x&0x1f) | ((y&0x7)<<8) | ((y&0x38)<<2) | ((y&0xc0)<<5);
uint8_t block = memory[address];
uint8_t c1 = color&0x7, c2 = (color>>3)&0x7;
if ((color&0x80) && _flash) { c1=c2; c2=color&0x7; }
for (int i=0;i<8;++i)
uint16_t color = /*0x5800 +*/ x + (y>>3)*32;
uint16_t address = /*0x4000 |*/ (x&0x1f) | ((y&0x7)<<8) | ((y&0x38)<<2) | ((y&0xc0)<<5);
for (int i=7;i>0;i-=2)
{
*(pixels++)=(block&0x80) ? palette[c1] : palette[c2];
block = block<<1;
*(ptr_pixel++) = (address & 0x1FFF) | (i << 13);
*(ptr_color++) = color;
count+=2;
}
}
// Right border
for (int j=0;j<48;++j) *(pixels++) = palette[border_color];
for (int j=0;j<24;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = SCREEN_BORDER; count+=2; }
}
// Lower border
for (int i=0; i<352*48;++i) *(pixels++)=palette[border_color];
for (int i=0; i<56;++i) {
// hsync
for (int j=0;j<t_states_per_scanline-176;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = SCREEN_SYNC; } // En el 128K 48 passsa a ser 52
//border
for (int j=0;j<176;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = SCREEN_BORDER; count+=2; }
}
//printf("COUNT: %i\n", count);
}
bool eventHandler(SDL_Event *e)
{
if (e->type==SDL_WINDOWEVENT) {
//int x, y;
//SDL_GetWindowPosition(win, &x, &y);
//char tmp[256];
//sprintf(tmp, " %ix%i", x, y);
//setTitle(tmp);
if (e->window.event==SDL_WINDOWEVENT_CLOSE) {
saveWindowConfiguration();
return false;
} else if (e->window.event==SDL_WINDOWEVENT_FOCUS_LOST || e->window.event==SDL_WINDOWEVENT_MOVED) {
saveWindowConfiguration();
} else if ((e->window.event==SDL_WINDOWEVENT_SHOWN) || (e->window.event==SDL_WINDOWEVENT_EXPOSED)) {
redraw();
}
}
if (!z80debug::debugging()) {
if (z80debug::paused()) {
if (e->type == SDL_KEYDOWN) {
if (e->key.keysym.scancode==SDL_SCANCODE_ESCAPE) {
ui::setDialog(nullptr);
const uint8_t dt = z80::step();
z80debug::cont();
zxscreen::refresh(dt);
}
}
} else {
if (e->type == SDL_KEYDOWN) {
if (e->key.keysym.scancode==SDL_SCANCODE_ESCAPE) {
z80debug::pause();
zxscreen::redraw();
} else if (e->key.keysym.scancode==SDL_SCANCODE_F1) {
zxscreen::decZoom();
} else if (e->key.keysym.scancode==SDL_SCANCODE_F2) {
zxscreen::incZoom();
} else if (e->key.keysym.scancode==SDL_SCANCODE_F3) {
zxscreen::toggleFullscreen();
} else if (e->key.keysym.scancode==SDL_SCANCODE_F6) {
zx_tape::play();
} else if (e->key.keysym.scancode==SDL_SCANCODE_F7) {
zx_tape::rewind();
}
}
}
}
if (e->type == SDL_MOUSEMOTION) {
SDL_ShowCursor(true);
}
return true;
}
void reinit()
{
saveWindowConfiguration();
if (win) ui::window::unregisterWindow(SDL_GetWindowID(win));
if (tex) SDL_DestroyTexture(tex);
if (uitex) SDL_DestroyTexture(uitex);
if (ren) SDL_DestroyRenderer(ren);
if (win) SDL_DestroyWindow(win);
zoom = file::getConfigValueInteger("screen_zoom", 1);
const int x = file::getConfigValueInteger("screen_x", SDL_WINDOWPOS_UNDEFINED);
const int y = file::getConfigValueInteger("screen_y", SDL_WINDOWPOS_UNDEFINED);
const int z = fullscreen ? 1 : zoom;
win = SDL_CreateWindow("ZX Spectrum Screen", x, y, 352*z, 296*z, fullscreen?SDL_WINDOW_FULLSCREEN_DESKTOP:SDL_WINDOW_ALLOW_HIGHDPI);
ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);
tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 352, 296);
uitex = ui::createtexture(ren);
ui::window::registerWindow(SDL_GetWindowID(win), eventHandler);
if (fullscreen)
{
int w, h;
SDL_GetWindowSize(win, &w, &h);
fullscreen_scale = h/296;
dest_rect.w = 352 * fullscreen_scale;
dest_rect.h = 296 * fullscreen_scale;
dest_rect.x = (w - dest_rect.w)/2;
dest_rect.y = (h - dest_rect.h)/2;
}
else
{
dest_rect.x = dest_rect.y = 0;
dest_rect.w = 352 * zoom;
dest_rect.h = 296 * zoom;
}
focus();
}
void setBaseAddresses(const uint32_t pixeladdr, const uint32_t coloraddr)
{
pixel_base_addr = pixeladdr;
color_base_addr = coloraddr;
}
void init(int mode)
{
zxscreen::mode = mode;
if (mode==SCREEN_MODE_48K) {
setBaseAddresses(0x0000, 0x1800);
t_states_total = 69888;
t_states_per_scanline = 224;
vsync_lines = 16;
} else if (mode==SCREEN_MODE_128K) {
setBaseAddresses(0x14000, 0x15800);
t_states_total = 70908;
t_states_per_scanline = 228;
vsync_lines = 15;
}
create_tables();
ptr_pixel = zx_pixels;
t_screen = t_flash = 0;
flash = false;
reinit();
}
void focus()
{
if (win)
{
SDL_RaiseWindow(win);
redraw();
}
}
void refresh(const uint32_t dt)
{
const uint8_t* pixel_mem = mem::rawPtr(pixel_base_addr);
const uint8_t* color_mem = mem::rawPtr(color_base_addr);
const uint8_t border_color = zx_ula::get_border_color();
for (int i=0;i<dt;++i)
{
if (color_addr[t_screen] != SCREEN_SYNC)
{
if (color_addr[t_screen] == SCREEN_BORDER) {
*(ptr_pixel++) = border_color;
*(ptr_pixel++) = border_color;
} else {
uint8_t color = *(color_mem + color_addr[t_screen]); // z80mem::get()->readMem(color_base_addr + color_addr[t_screen]);
uint8_t c1 = color&0x7, c2 = (color>>3)&0x7;
if ((color&0x80) && flash) { c1=c2; c2=color&0x7; }
if ((color&0x40)) { c1 |= 0x8; c2 |= 0x8; }
uint16_t address = /*(0x4000) |*/ (pixel_addr[t_screen]&0x1FFF);
uint8_t mask = 1 << (pixel_addr[t_screen]>>13);
uint8_t block = *(pixel_mem + address); // z80mem::get()->readMem(pixel_base_addr + address);
*(ptr_pixel++)=(block&mask) ? c1 : c2;
mask>>=1;
*(ptr_pixel++)=(block&mask) ? c1 : c2;
}
}
t_screen++;
if (t_screen>=t_states_total) {
t_flash++;
if (t_flash==16) { t_flash=0; flash = !flash; }
t_screen=0;
ptr_pixel = zx_pixels;
redraw();
if (interrupt_enabled) z80::interrupt();
}
}
}
void fullrefresh()
{
uint32_t tmp = t_screen;
t_screen = 0;
uint8_t * tmp_ptr = ptr_pixel;
ptr_pixel = zx_pixels;
interrupt_enabled = false;
refresh(t_states_total);
interrupt_enabled = true;
ptr_pixel = tmp_ptr;
t_screen = tmp;
}
void debugrefresh(const uint32_t dt)
{
if (full_refresh) fullrefresh(); else refresh(dt);
}
void redraw(const bool present)
{
if (zx_tape::getplaying() && zx_tape::getOption(ZXTAPE_OPTION_FAST_LOAD)) return;
ui::setrenderer(ren, uitex);
Uint32* pixels;
int pitch;
SDL_LockTexture(tex, NULL, (void**)&pixels, &pitch);
for (int i=0; i<352*296;++i) *(pixels++) = palette[zx_pixels[i]];
SDL_UnlockTexture(tex);
SDL_RenderCopy(ren, tex, NULL, NULL);
if (fullscreen)
{
SDL_SetRenderDrawColor(ren, 0, 0, 0, 255);
SDL_RenderClear(ren);
}
// Pintem la textura a pantalla
SDL_RenderCopy(ren, tex, NULL, &dest_rect);
//zx_128mem* mem = ((zx_128mem*)z80mem::get());
//ui::printtxt(0,0,mem->getShadowScreen()?"SHADOW":"NORMAL", COLOR_WHITE);
if (present)
SDL_RenderPresent(ren);
else
{
SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(ren, 0, 0, 0, 128);
SDL_Rect rect {0,0,352*zoom,296*zoom};
if (fullscreen) SDL_GetWindowSize(win, &rect.w, &rect.h);
SDL_RenderFillRect(ren, &rect);
}
}
void present()
{
SDL_RenderPresent(ren);
}
void setTitle(const char* title)
{
char tmp[256];
strcpy(tmp, "ZX Spectrum Screen");
strcat(tmp, title);
SDL_SetWindowTitle(win, tmp);
}
void setZoom(const int value)
{
if (value < 1) return;
SDL_DisplayMode dm;
SDL_GetCurrentDisplayMode(0, &dm);
if (352*value > dm.w) return;
if (296*value > dm.h) return;
zoom = value;
reinit();
}
void incZoom()
{
setZoom(zoom+1);
}
void decZoom()
{
setZoom(zoom-1);
}
void toggleFullscreen()
{
fullscreen = !fullscreen;
reinit();
}
const bool getFullscreen()
{
return fullscreen;
}
void toggleFullRefresh()
{
full_refresh = !full_refresh;
}
const bool getFullRefresh()
{
return full_refresh;
}
SDL_Renderer *getrenderer()
{
return ren;
}
}

View File

@@ -1,8 +1,29 @@
#pragma once
#include <SDL2/SDL.h>
#define SCREEN_MODE_48K 0
#define SCREEN_MODE_128K 1
namespace zxscreen
{
void show();
void refresh();
void flash();
void init(int mode);
void setBaseAddresses(const uint32_t pixeladdr, const uint32_t coloraddr);
void reinit();
void focus();
void refresh(const uint32_t dt);
void fullrefresh();
void debugrefresh();
void redraw(const bool present=true);
void present();
void setTitle(const char* title);
void incZoom();
void decZoom();
void toggleFullscreen();
const bool getFullscreen();
void toggleFullRefresh();
const bool getFullRefresh();
SDL_Renderer *getrenderer();
}

82
zx_speaker.cpp Normal file
View File

@@ -0,0 +1,82 @@
#include "zx_speaker.h"
#include <SDL2/SDL.h>
#include "z80.h"
#include <vector>
namespace speaker
{
uint16_t sampling_freq = 44100;
uint16_t audio_buffer_size = 2048;
SDL_AudioDeviceID sdlAudioDevice = 0;
uint8_t *sound_buffer = nullptr;
uint16_t sound_pos=0;
uint16_t t_sound=0;
std::vector<uint8_t(*)()> sources;
float cycles_per_sample;
void init(const uint16_t freq, const uint16_t buffer_size)
{
if (sound_buffer || sdlAudioDevice) quit();
sampling_freq = freq;
audio_buffer_size = buffer_size;
SDL_AudioSpec audioSpec{sampling_freq, AUDIO_U8, 1, 0, (uint16_t)(audio_buffer_size>>2), 0, 0, NULL, NULL};
sdlAudioDevice = SDL_OpenAudioDevice(NULL, 0, &audioSpec, NULL, 0);
cycles_per_sample = z80::getClock() / sampling_freq;
sound_buffer = (uint8_t*)malloc(audio_buffer_size);
enable();
}
void quit()
{
disable();
sources.clear();
if (sound_buffer) {
free(sound_buffer);
sound_buffer = nullptr;
}
if (sdlAudioDevice) {
SDL_CloseAudioDevice(sdlAudioDevice);
sdlAudioDevice = 0;
}
}
void enable()
{
SDL_PauseAudioDevice(sdlAudioDevice, 0);
}
void disable()
{
SDL_PauseAudioDevice(sdlAudioDevice, 1);
}
void register_source(uint8_t(*callback)())
{
sources.push_back(callback);
}
void update(const uint32_t dt)
{
t_sound += dt;
if (t_sound>=cycles_per_sample) {
t_sound-=cycles_per_sample;
uint32_t sample = 0;
for (auto callback : sources) sample += callback();
sample /= sources.size();
sound_buffer[(sound_pos++)&(audio_buffer_size-1)] = sample;
}
if (sound_pos>=1000) {
SDL_QueueAudio(sdlAudioDevice, sound_buffer, sound_pos);
sound_pos = 0;
while (SDL_GetQueuedAudioSize(sdlAudioDevice) > (audio_buffer_size<<1) ) {}
}
}
}

12
zx_speaker.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <stdint.h>
namespace speaker
{
void init(const uint16_t freq = 44100, const uint16_t buffer_size = 2048);
void quit();
void enable();
void disable();
void register_source(uint8_t(*callback)());
void update(const uint32_t dt);
}

123
zx_system.cpp Normal file
View File

@@ -0,0 +1,123 @@
#include "zx_system.h"
#include "z80.h"
#include "zx_mem.h"
#include "zx_ula.h"
#include "zx_screen.h"
#include "zx_tape.h"
#include "zx_speaker.h"
#include "ay-3-8912.h"
#include "zx_disk.h"
#include <vector>
namespace zx_system
{
bool resetting = true;
bool shutting_down = false;
uint8_t current_mode = ZX_48K;
uint8_t new_mode = ZX_48K;
std::vector<void(*)(uint32_t)> updatables;
int init(const uint8_t mode)
{
updatables.clear();
z80::clearPorts();
resetting = false;
switch(mode)
{
case ZX_NOCHANGE:
{
z80::reset();
break;
}
case ZX_48K:
{
const uint32_t clock = 3500000;
z80::init(clock);
z80::connect_port(0xfe, 0x0001, zx_ula::port_in, zx_ula::port_out);
mem::init(ZX_48K);
zxscreen::init(SCREEN_MODE_48K);
speaker::init();
speaker::register_source(zx_ula::get_sample);
registerUpdatable(zx_tape::update);
registerUpdatable(speaker::update);
//registerUpdatable(zxscreen::refresh);
return clock / 10;
break;
}
case ZX_128K:
{
const uint32_t clock = 3546900;
z80::init(clock);
z80::connect_port(0xfe, 0x0001, zx_ula::port_in, zx_ula::port_out);
mem::init(ZX_128K);
zxscreen::init(SCREEN_MODE_128K);
audio::init();
speaker::init();
speaker::register_source(zx_ula::get_sample);
speaker::register_source(audio::get_sample);
registerUpdatable(zx_tape::update);
registerUpdatable(audio::update);
registerUpdatable(speaker::update);
//registerUpdatable(zxscreen::refresh);
return clock / 10;
break;
}
case ZX_PLUS3:
{
const uint32_t clock = 3546900;
z80::init(clock);
z80::connect_port(0xfe, 0x0001, zx_ula::port_in, zx_ula::port_out);
mem::init(ZX_PLUS3);
zxscreen::init(SCREEN_MODE_128K);
audio::init();
speaker::init();
speaker::register_source(zx_ula::get_sample);
speaker::register_source(audio::get_sample);
zx_disk::init();
registerUpdatable(zx_tape::update);
registerUpdatable(audio::update);
registerUpdatable(speaker::update);
//registerUpdatable(zxscreen::refresh);
return clock / 10;
break;
}
}
return 0;
}
void reset(const uint8_t mode)
{
new_mode = mode;
resetting = true;
}
void shutdown()
{
shutting_down = true;
}
const bool shuttingDown()
{
if (resetting) init(new_mode);
return shutting_down;
}
void registerUpdatable(void(*callback)(uint32_t))
{
updatables.push_back(callback);
}
void update(uint32_t dt)
{
for (auto& call : updatables) call(dt);
}
}

19
zx_system.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <stdint.h>
#define ZX_NOCHANGE 0x00
#define ZX_48K 0x01
#define ZX_128K 0x02
#define ZX_PLUS3 0x03
#define ZX_NEXT 0x04
namespace zx_system
{
int init(const uint8_t mode);
void reset(const uint8_t mode);
void shutdown();
const bool shuttingDown();
void registerUpdatable(void(*callback)(uint32_t));
void update(uint32_t dt);
}

283
zx_tape.cpp Normal file
View File

@@ -0,0 +1,283 @@
#include "zx_tape.h"
#include "zx_ula.h"
#include "zx_screen.h"
#include "zx_mem.h"
#include "z80debug.h"
#include "z80.h"
#include <vector>
#include <stdio.h>
#include <stdlib.h>
namespace zx_tape
{
#define PULSE_PILOT 0
#define PULSE_SYNC1 1
#define PULSE_SYNC2 2
#define PULSE_DATA 3
#define PULSE_CHECKSUM 4
#define PULSE_SYNC3 5
#define PULSE_WAIT 6
#define PULSE_LEN_ZERO 855
#define PULSE_LEN_ONE 1710
#define PULSE_LEN_PILOT 2168
#define PULSE_LEN_SYNC1 667
#define PULSE_LEN_SYNC2 735
#define PULSE_LEN_SYNC3 954
#define PULSE_COUNT_PILOT_HEADER 8063
#define PULSE_COUNT_PILOT_DATA 3223
struct block_t
{
uint16_t length;
uint8_t *data;
};
bool playing = false;
bool loaded = false;
bool options[ZXTAPE_NUM_OPTIONS] = { true, true };
std::vector<block_t> blocks;
uint8_t current_block = 0;
uint16_t block_pos=0;
uint8_t current_bit=0;
uint8_t current_section = PULSE_PILOT;
uint16_t current_pulse = 0;
uint32_t pulse_pos = 0;
uint8_t pulse_level = 0;
void load(const char* filename)
{
if (!blocks.empty()) for (auto block : blocks) free(block.data);
blocks.clear();
FILE *f = fopen(filename, "rb");
if (!f) return;
while (!feof(f))
{
block_t block;
fread(&block.length, 2, 1, f);
//fread(&block.flag, 1, 1, f);
//block.length -= 2; // substract flag and checksum
block.data = (uint8_t *)malloc(block.length);
fread(block.data, block.length, 1, f);
//fread(&block.checksum, 1, 1, f);
blocks.push_back(block);
}
fclose(f);
loaded = true;
playing = false;
rewind();
}
void play()
{
if (!loaded) return;
playing = !playing; //true;
}
void stop()
{
playing = false;
//berserk_mode = false;
}
void rewind()
{
if (!loaded) return;
current_block = block_pos = current_bit = 0;
current_section = PULSE_PILOT;
current_pulse = pulse_pos = 0;
pulse_level = 1;
}
void update(const uint32_t dt)
{
if (!playing) return;
pulse_pos += dt;
if (current_section == PULSE_PILOT)
{
const uint16_t pulse_count = blocks[current_block].data[0]<128 ? PULSE_COUNT_PILOT_HEADER : PULSE_COUNT_PILOT_DATA;
if (pulse_pos >= PULSE_LEN_PILOT )
{
pulse_pos -= PULSE_LEN_PILOT;
current_pulse++;
pulse_level = pulse_level?0:1;
if (current_pulse == pulse_count && pulse_level==0)
{
pulse_level = 0;
}
else if (current_pulse >= pulse_count)
{
current_pulse = 0;
pulse_level = 1;
current_section = PULSE_SYNC1;
printf("going to pulse_sync1..%i.\n", current_block);
}
}
}
if (current_section == PULSE_SYNC1)
{
if (pulse_pos >= PULSE_LEN_SYNC1)
{
pulse_pos -= PULSE_LEN_SYNC1;
pulse_level = 0;
current_section = PULSE_SYNC2;
printf("going to pulse_sync2..%i.\n", current_block);
}
}
if (current_section == PULSE_SYNC2)
{
if (pulse_pos >= PULSE_LEN_SYNC2)
{
pulse_pos -= PULSE_LEN_SYNC2;
pulse_level = 1;
current_section = PULSE_DATA;
printf("going to pulse_data..%i.\n", current_block);
}
}
static int level[2] = {0, 0};
if (current_section == PULSE_DATA)
{
level[pulse_level]+=dt;
const uint8_t datum = blocks[current_block].data[block_pos];
const uint16_t pulse_len = (datum & (0x80>>current_bit)) == 0 ? PULSE_LEN_ZERO : PULSE_LEN_ONE;
if (pulse_pos >= pulse_len)
{
pulse_pos =0;//-= pulse_len;
pulse_level--;
if (pulse_level>=2)
{
pulse_level = 1;
//printf("%i\n",current_bit);
level[0]=level[1]=0;
current_bit++;
if (current_bit>=8)
{
current_bit = 0;
block_pos++;
if (block_pos>=blocks[current_block].length)
{
block_pos = 0;
current_section = PULSE_SYNC3;
printf("going to pulse_sync3..%i.\n", current_block);
}
}
}
}
}
/*if (current_section == PULSE_CHECKSUM)
{
const uint8_t datum = 1;//blocks[current_block].checksum;
const uint16_t pulse_len = (datum & (0x80>>current_bit)) == 0 ? PULSE_LEN_ZERO : PULSE_LEN_ONE;
if (pulse_pos >= pulse_len)
{
pulse_pos -= pulse_len;
pulse_level--;
if (pulse_level>=2)
{
pulse_level = 1;
current_bit++;
if (current_bit>=8)
{
current_bit = 0;
current_section = PULSE_SYNC3;
}
}
}
}*/
if (current_section == PULSE_SYNC3)
{
if (pulse_pos >= PULSE_LEN_SYNC3)
{
pulse_pos -= PULSE_LEN_SYNC3;
pulse_level = 0;
current_section = PULSE_WAIT;
printf("going to pulse_wait..%i (%i).\n", current_block, blocks[current_block].length);
}
}
if (current_section == PULSE_WAIT)
{
pulse_level = 0;
if (pulse_pos >= z80::getClock())
{
pulse_pos = 0;
current_section = PULSE_PILOT;
pulse_level = 1;
current_block++;
if (current_block>=blocks.size())
{
printf("end\n");
stop();
rewind();
if (options[ZXTAPE_OPTION_STOP_AT_END]) z80debug::stop();
}
else
{
//zxscreen::fullrefresh();
printf("going to pulse_pilot on block %i...\n", current_block);
}
}
}
zx_ula::set_ear(pulse_level);
}
const bool getplaying() { return playing; }
void report()
{
if (current_block >= blocks.size()) return;
const int percent = (float(block_pos)/float(blocks[current_block].length))*100;
printf("tape loading: %i%\n", percent);
}
uint16_t fastLoad(const uint8_t block_type, const uint16_t address, const uint16_t length)
{
block_pos=0;
current_bit=0;
current_section = PULSE_PILOT;
current_pulse = 0;
pulse_pos = 0;
pulse_level = 0;
if (blocks[current_block].data[0] != block_type ||
blocks[current_block].length != length+2) {
printf("ERROR: Tape data not consistent with expectation\n");
z80debug::stop();
}
for (int i=0;i<length;++i) {
mem::writeMem(address+i, blocks[current_block].data[i+1]);
}
current_block++;
return address + length;
}
const bool getOption(const int option)
{
return options[option];
}
void setOption(const int option, const bool value)
{
options[option] = value;
}
void toggleOption(const int option)
{
options[option] = !options[option];
}
}

22
zx_tape.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
namespace zx_tape
{
#define ZXTAPE_OPTION_FAST_LOAD 0
#define ZXTAPE_OPTION_STOP_AT_END 1
#define ZXTAPE_NUM_OPTIONS 2
void load(const char* filename);
void play();
void stop();
void rewind();
void update(const uint32_t dt);
const bool getplaying();
void report();
uint16_t fastLoad(const uint8_t block_type, const uint16_t address, const uint16_t length);
const bool getOption(const int option);
void setOption(const int option, const bool value);
void toggleOption(const int option);
}

View File

@@ -1,88 +1,215 @@
#include "zx_ula.h"
#include "z80.h"
#include <SDL2/SDL.h>
namespace zx_ula
{
#define KEY_SHIFT 0
#define KEY_Z 1
#define KEY_X 2
#define KEY_C 3
#define KEY_V 4
#define KEY_A 5
#define KEY_S 6
#define KEY_D 7
#define KEY_F 8
#define KEY_G 9
#define KEY_Q 10
#define KEY_W 11
#define KEY_E 12
#define KEY_R 13
#define KEY_T 14
#define KEY_1 15
#define KEY_2 16
#define KEY_3 17
#define KEY_4 18
#define KEY_5 19
#define KEY_0 20
#define KEY_9 21
#define KEY_8 22
#define KEY_7 23
#define KEY_6 24
#define KEY_P 25
#define KEY_O 26
#define KEY_I 27
#define KEY_U 28
#define KEY_Y 29
#define KEY_RETURN 30
#define KEY_L 31
#define KEY_K 32
#define KEY_J 33
#define KEY_H 34
#define KEY_SPACE 35
#define KEY_SYMBOL 36
#define KEY_M 37
#define KEY_N 38
#define KEY_B 39
uint8_t zx_keyboard[40];
uint8_t virtual_keyboard[40] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
char key_to_keyboard[40] {
'<','Z','X','C','V',
'A','S','D','F','G',
'Q','W','E','R','T',
'1','2','3','4','5',
'0','9','8','7','6',
'P','O','I','U','Y',
'-','L','K','J','H',
'.',',','M','N','B'
};
static uint8_t border_color = 0;
static uint8_t ear = 0;
static uint8_t mic = 0;
int port_in(int port)
void update_zx_keyboard()
{
const uint8_t *keys = SDL_GetKeyboardState(NULL);
// Normal keypresses
zx_keyboard[KEY_SHIFT] = virtual_keyboard[KEY_SHIFT] | keys[SDL_SCANCODE_LSHIFT] | keys[SDL_SCANCODE_RSHIFT];
zx_keyboard[KEY_Z] = virtual_keyboard[KEY_Z] | keys[SDL_SCANCODE_Z];
zx_keyboard[KEY_X] = virtual_keyboard[KEY_X] | keys[SDL_SCANCODE_X];
zx_keyboard[KEY_C] = virtual_keyboard[KEY_C] | keys[SDL_SCANCODE_C];
zx_keyboard[KEY_V] = virtual_keyboard[KEY_V] | keys[SDL_SCANCODE_V];
zx_keyboard[KEY_A] = virtual_keyboard[KEY_A] | keys[SDL_SCANCODE_A];
zx_keyboard[KEY_S] = virtual_keyboard[KEY_S] | keys[SDL_SCANCODE_S];
zx_keyboard[KEY_D] = virtual_keyboard[KEY_D] | keys[SDL_SCANCODE_D];
zx_keyboard[KEY_F] = virtual_keyboard[KEY_F] | keys[SDL_SCANCODE_F];
zx_keyboard[KEY_G] = virtual_keyboard[KEY_G] | keys[SDL_SCANCODE_G];
zx_keyboard[KEY_Q] = virtual_keyboard[KEY_Q] | keys[SDL_SCANCODE_Q];
zx_keyboard[KEY_W] = virtual_keyboard[KEY_W] | keys[SDL_SCANCODE_W];
zx_keyboard[KEY_E] = virtual_keyboard[KEY_E] | keys[SDL_SCANCODE_E];
zx_keyboard[KEY_R] = virtual_keyboard[KEY_R] | keys[SDL_SCANCODE_R];
zx_keyboard[KEY_T] = virtual_keyboard[KEY_T] | keys[SDL_SCANCODE_T];
zx_keyboard[KEY_1] = virtual_keyboard[KEY_1] | keys[SDL_SCANCODE_1] | keys[SDL_SCANCODE_KP_1];
zx_keyboard[KEY_2] = virtual_keyboard[KEY_2] | keys[SDL_SCANCODE_2] | keys[SDL_SCANCODE_KP_2];
zx_keyboard[KEY_3] = virtual_keyboard[KEY_3] | keys[SDL_SCANCODE_3] | keys[SDL_SCANCODE_KP_3];
zx_keyboard[KEY_4] = virtual_keyboard[KEY_4] | keys[SDL_SCANCODE_4] | keys[SDL_SCANCODE_KP_4];
zx_keyboard[KEY_5] = virtual_keyboard[KEY_5] | keys[SDL_SCANCODE_5] | keys[SDL_SCANCODE_KP_5];
zx_keyboard[KEY_0] = virtual_keyboard[KEY_0] | keys[SDL_SCANCODE_0] | keys[SDL_SCANCODE_KP_0];
zx_keyboard[KEY_9] = virtual_keyboard[KEY_9] | keys[SDL_SCANCODE_9] | keys[SDL_SCANCODE_KP_9];
zx_keyboard[KEY_8] = virtual_keyboard[KEY_8] | keys[SDL_SCANCODE_8] | keys[SDL_SCANCODE_KP_8];
zx_keyboard[KEY_7] = virtual_keyboard[KEY_7] | keys[SDL_SCANCODE_7] | keys[SDL_SCANCODE_KP_7];
zx_keyboard[KEY_6] = virtual_keyboard[KEY_6] | keys[SDL_SCANCODE_6] | keys[SDL_SCANCODE_KP_6];
zx_keyboard[KEY_P] = virtual_keyboard[KEY_P] | keys[SDL_SCANCODE_P];
zx_keyboard[KEY_O] = virtual_keyboard[KEY_O] | keys[SDL_SCANCODE_O];
zx_keyboard[KEY_I] = virtual_keyboard[KEY_I] | keys[SDL_SCANCODE_I];
zx_keyboard[KEY_U] = virtual_keyboard[KEY_U] | keys[SDL_SCANCODE_U];
zx_keyboard[KEY_Y] = virtual_keyboard[KEY_Y] | keys[SDL_SCANCODE_Y];
zx_keyboard[KEY_RETURN] = virtual_keyboard[KEY_RETURN] | keys[SDL_SCANCODE_KP_ENTER] | keys[SDL_SCANCODE_RETURN];
zx_keyboard[KEY_L] = virtual_keyboard[KEY_L] | keys[SDL_SCANCODE_L];
zx_keyboard[KEY_K] = virtual_keyboard[KEY_K] | keys[SDL_SCANCODE_K];
zx_keyboard[KEY_J] = virtual_keyboard[KEY_J] | keys[SDL_SCANCODE_J];
zx_keyboard[KEY_H] = virtual_keyboard[KEY_H] | keys[SDL_SCANCODE_H];
zx_keyboard[KEY_SPACE] = virtual_keyboard[KEY_SPACE] | keys[SDL_SCANCODE_SPACE];
zx_keyboard[KEY_SYMBOL] = virtual_keyboard[KEY_SYMBOL] | keys[SDL_SCANCODE_LCTRL] | keys[SDL_SCANCODE_RCTRL];
zx_keyboard[KEY_M] = virtual_keyboard[KEY_M] | keys[SDL_SCANCODE_M];
zx_keyboard[KEY_N] = virtual_keyboard[KEY_N] | keys[SDL_SCANCODE_N];
zx_keyboard[KEY_B] = virtual_keyboard[KEY_B] | keys[SDL_SCANCODE_B];
// Keys in a normal keyboard that ara combinations in the zx one
if (keys[SDL_SCANCODE_BACKSPACE]) zx_keyboard[KEY_SHIFT] = zx_keyboard[KEY_0] = 1;
if (keys[SDL_SCANCODE_PERIOD]) zx_keyboard[KEY_SYMBOL] = zx_keyboard[KEY_M] = 1;
if (keys[SDL_SCANCODE_UP]) zx_keyboard[KEY_SHIFT] = zx_keyboard[KEY_7] = 1;
if (keys[SDL_SCANCODE_DOWN]) zx_keyboard[KEY_SHIFT] = zx_keyboard[KEY_6] = 1;
if (keys[SDL_SCANCODE_LEFT]) zx_keyboard[KEY_SHIFT] = zx_keyboard[KEY_5] = 1;
if (keys[SDL_SCANCODE_RIGHT]) zx_keyboard[KEY_SHIFT] = zx_keyboard[KEY_8] = 1;
}
int port_in(int port)
{
const uint8_t h_addr = (port>>8);
uint8_t result = 0xff;
uint8_t result = ear ? 0xff : 0xbf;
update_zx_keyboard();
if (!(h_addr & ~0xfe))
{
result &= ~(
keys[SDL_SCANCODE_LSHIFT]*0x01 +
keys[SDL_SCANCODE_Z]*0x02 +
keys[SDL_SCANCODE_X]*0x04 +
keys[SDL_SCANCODE_C]*0x08 +
keys[SDL_SCANCODE_V]*0x10 );
zx_keyboard[KEY_SHIFT]*0x01 +
zx_keyboard[KEY_Z]*0x02 +
zx_keyboard[KEY_X]*0x04 +
zx_keyboard[KEY_C]*0x08 +
zx_keyboard[KEY_V]*0x10 );
}
if (!(h_addr & ~0xfd))
{
result &= ~(
keys[SDL_SCANCODE_A]*0x01 +
keys[SDL_SCANCODE_S]*0x02 +
keys[SDL_SCANCODE_D]*0x04 +
keys[SDL_SCANCODE_F]*0x08 +
keys[SDL_SCANCODE_G]*0x10 );
zx_keyboard[KEY_A]*0x01 +
zx_keyboard[KEY_S]*0x02 +
zx_keyboard[KEY_D]*0x04 +
zx_keyboard[KEY_F]*0x08 +
zx_keyboard[KEY_G]*0x10 );
}
if (!(h_addr & ~0xfb))
{
result &= ~(
keys[SDL_SCANCODE_Q]*0x01 +
keys[SDL_SCANCODE_W]*0x02 +
keys[SDL_SCANCODE_E]*0x04 +
keys[SDL_SCANCODE_R]*0x08 +
keys[SDL_SCANCODE_T]*0x10 );
zx_keyboard[KEY_Q]*0x01 +
zx_keyboard[KEY_W]*0x02 +
zx_keyboard[KEY_E]*0x04 +
zx_keyboard[KEY_R]*0x08 +
zx_keyboard[KEY_T]*0x10 );
}
if (!(h_addr & ~0xf7))
{
result &= ~(
keys[SDL_SCANCODE_1]*0x01 +
keys[SDL_SCANCODE_2]*0x02 +
keys[SDL_SCANCODE_3]*0x04 +
keys[SDL_SCANCODE_4]*0x08 +
keys[SDL_SCANCODE_5]*0x10 );
zx_keyboard[KEY_1]*0x01 +
zx_keyboard[KEY_2]*0x02 +
zx_keyboard[KEY_3]*0x04 +
zx_keyboard[KEY_4]*0x08 +
zx_keyboard[KEY_5]*0x10 );
}
if (!(h_addr & ~0xef))
{
result &= ~(
keys[SDL_SCANCODE_0]*0x01 +
keys[SDL_SCANCODE_9]*0x02 +
keys[SDL_SCANCODE_8]*0x04 +
keys[SDL_SCANCODE_7]*0x08 +
keys[SDL_SCANCODE_6]*0x10 );
zx_keyboard[KEY_0]*0x01 +
zx_keyboard[KEY_9]*0x02 +
zx_keyboard[KEY_8]*0x04 +
zx_keyboard[KEY_7]*0x08 +
zx_keyboard[KEY_6]*0x10 );
}
if (!(h_addr & ~0xdf))
{
result &= ~(
keys[SDL_SCANCODE_P]*0x01 +
keys[SDL_SCANCODE_O]*0x02 +
keys[SDL_SCANCODE_I]*0x04 +
keys[SDL_SCANCODE_U]*0x08 +
keys[SDL_SCANCODE_Y]*0x10 );
zx_keyboard[KEY_P]*0x01 +
zx_keyboard[KEY_O]*0x02 +
zx_keyboard[KEY_I]*0x04 +
zx_keyboard[KEY_U]*0x08 +
zx_keyboard[KEY_Y]*0x10 );
}
if (!(h_addr & ~0xbf))
{
result &= ~(
keys[SDL_SCANCODE_RETURN]*0x01 +
keys[SDL_SCANCODE_L]*0x02 +
keys[SDL_SCANCODE_K]*0x04 +
keys[SDL_SCANCODE_J]*0x08 +
keys[SDL_SCANCODE_H]*0x10 );
zx_keyboard[KEY_RETURN]*0x01 +
zx_keyboard[KEY_L]*0x02 +
zx_keyboard[KEY_K]*0x04 +
zx_keyboard[KEY_J]*0x08 +
zx_keyboard[KEY_H]*0x10 );
}
if (!(h_addr & ~0x7f))
{
result &= ~(
keys[SDL_SCANCODE_SPACE]*0x01 +
keys[SDL_SCANCODE_RSHIFT]*0x02 +
keys[SDL_SCANCODE_M]*0x04 +
keys[SDL_SCANCODE_N]*0x08 +
keys[SDL_SCANCODE_B]*0x10 );
zx_keyboard[KEY_SPACE]*0x01 +
zx_keyboard[KEY_SYMBOL]*0x02 +
zx_keyboard[KEY_M]*0x04 +
zx_keyboard[KEY_N]*0x08 +
zx_keyboard[KEY_B]*0x10 );
}
return result;
}
@@ -90,46 +217,36 @@ namespace zx_ula
void port_out(int port, int val)
{
border_color = val & 0x7;
mic = (val>>3)&0x1;
ear = (val>>4)&0x1;
mic = (val&0x08)?0:1;
ear = val&0x10?1:0;
//printf("EAR:%i MIC:%i\n", ear, mic);
}
uint8_t get_border_color() { return border_color; }
void set_border_color(uint8_t col) { border_color = col; }
uint8_t get_ear() { return ear; }
void set_ear(const uint8_t val) { ear = val; }
SDL_AudioDeviceID sdlAudioDevice;
uint8_t sound_buffer[1024];
uint16_t sound_pos;
void audioCallback(void * userdata, uint8_t * stream, int len)
uint8_t get_sample()
{
const uint16_t top = sound_pos < len ? sound_pos : len;
memcpy(stream, sound_buffer, top);
sound_pos=0;
return ear*128;
}
void sound_init()
const int getKey(const char key)
{
SDL_AudioSpec audioSpec{11025, AUDIO_U8, 1, 0, 220, 0, 0, &audioCallback, NULL};
sdlAudioDevice = SDL_OpenAudioDevice(NULL, 0, &audioSpec, NULL, 0);
for (int i=0; i<40; ++i) if (key_to_keyboard[i] == key) return i;
return 0;
}
void sound_enable()
void keydown(const char key)
{
SDL_PauseAudioDevice(sdlAudioDevice, 0);
virtual_keyboard[getKey(key)] = 1;
}
void sound_disable()
void keyup(const char key)
{
SDL_PauseAudioDevice(sdlAudioDevice, 1);
virtual_keyboard[getKey(key)] = 0;
}
void sound_update()
{
sound_buffer[sound_pos++] = ear*128;
}
}

View File

@@ -8,10 +8,12 @@ namespace zx_ula
void port_out(int port, int val);
uint8_t get_border_color();
void set_border_color(uint8_t col);
uint8_t get_ear();
void set_ear(const uint8_t val);
void sound_init();
void sound_enable();
void sound_disable();
void sound_update();
uint8_t get_sample();
void keydown(const char key);
void keyup(const char key);
}