Compare commits

..

83 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
70 changed files with 5731 additions and 906 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
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

337
main.cpp
View File

@@ -1,100 +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 time = 0;
uint32_t t_states = 0;
namespace actions
{
void exitMenu()
{
const uint8_t dt = z80::step();
z80debug::cont();
zxscreen::refresh(dt);
}
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");
zx_tape::load("manic.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::redraw();
}
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) {
const uint8_t dt = z80::step();
z80debug::refresh();
zxscreen::refresh(dt);
zxscreen::redraw();
} else if (e.key.keysym.scancode==SDL_SCANCODE_F11) {
const uint8_t dt = z80debug::next();
zxscreen::refresh(dt);
zxscreen::redraw();
} else if (e.key.keysym.scancode==SDL_SCANCODE_F5) {
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();
zxscreen::refresh(dt);
} else if (e.key.keysym.scancode==SDL_SCANCODE_RETURN) {
z80debug::executeConsole();
} else if (e.key.keysym.scancode==SDL_SCANCODE_BACKSPACE) {
z80debug::DeleteCharConsole();
}
}
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::redraw();
} else {
z80debug::show();
}
if (e.key.keysym.scancode==SDL_SCANCODE_F12) {
zx_tape::play();
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();
}
if (e.key.keysym.scancode==SDL_SCANCODE_F11) {
zx_tape::rewind();
} 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::redraw();
} else {
//if (z80::getPC()==0x05C8) zx_tape::go_berserk();
uint8_t dt = z80::step();
zx_tape::update(dt);
//if (!zx_tape::berserk()) {
zx_ula::sound_update(dt);
//}
zxscreen::refresh(dt);
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
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

1060
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,6 +19,20 @@ namespace zxscreen
SDL_Window *win = nullptr;
SDL_Renderer *ren = nullptr;
SDL_Texture *tex = nullptr;
SDL_Texture *uitex = nullptr;
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;
uint8_t zoom = 2;
bool fullscreen = false;
bool full_refresh = true;
int fullscreen_scale = 1;
SDL_Rect dest_rect;
uint32_t time=0;
@@ -19,45 +40,63 @@ namespace zxscreen
uint8_t t_flash = 0;
bool flash = false;
int pixels_draw = 0;
uint32_t pixel_base_addr = 0x4000;
uint32_t color_base_addr = 0x5800;
uint16_t pixel_addr[69888];
uint16_t color_addr[69888];
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()
{
file::setConfigValueInteger("screen_zoom", zoom);
int x, y;
SDL_GetWindowPosition(win, &x, &y);
file::setConfigValueInteger("screen_x", x);
file::setConfigValueInteger("screen_y", y);
}
void create_tables()
{
uint32_t count = 0;
uint16_t *ptr_pixel = pixel_addr;
uint16_t *ptr_color = color_addr;
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<224*16;++i) { *(ptr_pixel++) = 0; *(ptr_color++) = 0; }
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<48;++i) {
// hsync
for (int j=0;j<48;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = 0; }
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++) = 1; count+=2; }
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<48;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = 0; }
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<24;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = 1; count+=2; }
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)
{
uint16_t color = 0x5800 + x + (y>>3)*32;
uint16_t address = 0x4000 | (x&0x1f) | ((y&0x7)<<8) | ((y&0x38)<<2) | ((y&0xc0)<<5);
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)
{
*(ptr_pixel++) = (address & 0x1FFF) | (i << 13);
@@ -67,88 +106,303 @@ namespace zxscreen
}
// Right border
for (int j=0;j<24;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = 1; count+=2; }
for (int j=0;j<24;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = SCREEN_BORDER; count+=2; }
}
// Lower border
for (int i=0; i<56;++i) {
// hsync
for (int j=0;j<48;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = 0; }
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++) = 1; count+=2; }
for (int j=0;j<176;++j) { *(ptr_pixel++) = 0; *(ptr_color++) = SCREEN_BORDER; count+=2; }
}
printf("COUNT: %i\n", count);
//printf("COUNT: %i\n", count);
}
void show()
bool eventHandler(SDL_Event *e)
{
if (win) return;
win = SDL_CreateWindow("ZX Spectrum Screen", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 352, 296, SDL_WINDOW_SHOWN);
ren = SDL_CreateRenderer(win, -1, 0);
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);
create_tables();
redraw();
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 refresh(const uint8_t dt)
void setBaseAddresses(const uint32_t pixeladdr, const uint32_t coloraddr)
{
const uint8_t* memory = z80::getMem();
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] != 0)
if (color_addr[t_screen] != SCREEN_SYNC)
{
if (color_addr[t_screen] == 1)
{
if (color_addr[t_screen] == SCREEN_BORDER) {
*(ptr_pixel++) = border_color;
*(ptr_pixel++) = border_color;
//pixels_draw+=2;
}
else
{
uint8_t color = memory[color_addr[t_screen]];
} 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);
uint16_t address = /*(0x4000) |*/ (pixel_addr[t_screen]&0x1FFF);
uint8_t mask = 1 << (pixel_addr[t_screen]>>13);
uint8_t block = memory[address];
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;
}
pixels_draw+=2;
}
t_screen++;
/*if (pixels_draw>352*296)
{
printf("PIXELS OVERFLOW: %i\n", pixels_draw);
}*/
if (t_screen>=69888) {
//printf("PIXELS DRAWN: %i\n", pixels_draw);
pixels_draw=0;
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();
//while (SDL_GetTicks()-time < 20) {}
//time = SDL_GetTicks();
z80::interrupt();
if (interrupt_enabled) z80::interrupt();
}
}
}
void redraw()
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,9 +1,29 @@
#pragma once
#include <stdint.h>
#include <SDL2/SDL.h>
#define SCREEN_MODE_48K 0
#define SCREEN_MODE_128K 1
namespace zxscreen
{
void show();
void refresh(const uint8_t dt);
void redraw();
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);
}

View File

@@ -1,5 +1,9 @@
#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>
@@ -33,7 +37,7 @@ namespace zx_tape
bool playing = false;
bool loaded = false;
bool berserk_mode = false;
bool options[ZXTAPE_NUM_OPTIONS] = { true, true };
std::vector<block_t> blocks;
uint8_t current_block = 0;
@@ -47,8 +51,9 @@ namespace zx_tape
void load(const char* filename)
{
//[TODO] Free memory that might be taken by previous tape
if (!blocks.empty()) for (auto block : blocks) free(block.data);
blocks.clear();
FILE *f = fopen(filename, "rb");
if (!f) return;
while (!feof(f))
@@ -78,7 +83,7 @@ namespace zx_tape
void stop()
{
playing = false;
berserk_mode = false;
//berserk_mode = false;
}
void rewind()
@@ -90,7 +95,7 @@ namespace zx_tape
pulse_level = 1;
}
void update(const uint8_t dt)
void update(const uint32_t dt)
{
if (!playing) return;
@@ -113,6 +118,7 @@ namespace zx_tape
current_pulse = 0;
pulse_level = 1;
current_section = PULSE_SYNC1;
printf("going to pulse_sync1..%i.\n", current_block);
}
}
}
@@ -124,6 +130,7 @@ namespace zx_tape
pulse_pos -= PULSE_LEN_SYNC1;
pulse_level = 0;
current_section = PULSE_SYNC2;
printf("going to pulse_sync2..%i.\n", current_block);
}
}
@@ -134,6 +141,7 @@ namespace zx_tape
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};
@@ -160,6 +168,7 @@ namespace zx_tape
{
block_pos = 0;
current_section = PULSE_SYNC3;
printf("going to pulse_sync3..%i.\n", current_block);
}
}
}
@@ -194,6 +203,7 @@ namespace zx_tape
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);
}
}
@@ -201,16 +211,24 @@ namespace zx_tape
if (current_section == PULSE_WAIT)
{
pulse_level = 0;
if (pulse_pos >= 3500000)
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);
}
}
}
@@ -218,7 +236,48 @@ namespace zx_tape
zx_ula::set_ear(pulse_level);
}
void go_berserk() { berserk_mode = true; }
const bool berserk() { return berserk_mode; }
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];
}
}

View File

@@ -3,11 +3,20 @@
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 uint8_t dt);
void go_berserk();
const bool berserk();
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,4 +1,5 @@
#include "zx_ula.h"
#include "z80.h"
#include <SDL2/SDL.h>
namespace zx_ula
@@ -52,7 +53,17 @@ namespace zx_ula
#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;
@@ -62,63 +73,69 @@ namespace zx_ula
const uint8_t *keys = SDL_GetKeyboardState(NULL);
// Normal keypresses
zx_keyboard[KEY_SHIFT] = keys[SDL_SCANCODE_LSHIFT] | keys[SDL_SCANCODE_RSHIFT];
zx_keyboard[KEY_Z] = keys[SDL_SCANCODE_Z];
zx_keyboard[KEY_X] = keys[SDL_SCANCODE_X];
zx_keyboard[KEY_C] = keys[SDL_SCANCODE_C];
zx_keyboard[KEY_V] = keys[SDL_SCANCODE_V];
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] = keys[SDL_SCANCODE_A];
zx_keyboard[KEY_S] = keys[SDL_SCANCODE_S];
zx_keyboard[KEY_D] = keys[SDL_SCANCODE_D];
zx_keyboard[KEY_F] = keys[SDL_SCANCODE_F];
zx_keyboard[KEY_G] = keys[SDL_SCANCODE_G];
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] = keys[SDL_SCANCODE_Q];
zx_keyboard[KEY_W] = keys[SDL_SCANCODE_W];
zx_keyboard[KEY_E] = keys[SDL_SCANCODE_E];
zx_keyboard[KEY_R] = keys[SDL_SCANCODE_R];
zx_keyboard[KEY_T] = keys[SDL_SCANCODE_T];
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] = keys[SDL_SCANCODE_1] | keys[SDL_SCANCODE_KP_1];
zx_keyboard[KEY_2] = keys[SDL_SCANCODE_2] | keys[SDL_SCANCODE_KP_2];
zx_keyboard[KEY_3] = keys[SDL_SCANCODE_3] | keys[SDL_SCANCODE_KP_3];
zx_keyboard[KEY_4] = keys[SDL_SCANCODE_4] | keys[SDL_SCANCODE_KP_4];
zx_keyboard[KEY_5] = keys[SDL_SCANCODE_5] | keys[SDL_SCANCODE_KP_5];
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] = keys[SDL_SCANCODE_0] | keys[SDL_SCANCODE_KP_0];
zx_keyboard[KEY_9] = keys[SDL_SCANCODE_9] | keys[SDL_SCANCODE_KP_9];
zx_keyboard[KEY_8] = keys[SDL_SCANCODE_8] | keys[SDL_SCANCODE_KP_8];
zx_keyboard[KEY_7] = keys[SDL_SCANCODE_7] | keys[SDL_SCANCODE_KP_7];
zx_keyboard[KEY_6] = keys[SDL_SCANCODE_6] | keys[SDL_SCANCODE_KP_6];
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] = keys[SDL_SCANCODE_P];
zx_keyboard[KEY_O] = keys[SDL_SCANCODE_O];
zx_keyboard[KEY_I] = keys[SDL_SCANCODE_I];
zx_keyboard[KEY_U] = keys[SDL_SCANCODE_U];
zx_keyboard[KEY_Y] = keys[SDL_SCANCODE_Y];
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] = keys[SDL_SCANCODE_KP_ENTER] | keys[SDL_SCANCODE_RETURN];
zx_keyboard[KEY_L] = keys[SDL_SCANCODE_L];
zx_keyboard[KEY_K] = keys[SDL_SCANCODE_K];
zx_keyboard[KEY_J] = keys[SDL_SCANCODE_J];
zx_keyboard[KEY_H] = keys[SDL_SCANCODE_H];
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] = keys[SDL_SCANCODE_SPACE];
zx_keyboard[KEY_SYMBOL] = keys[SDL_SCANCODE_LCTRL] | keys[SDL_SCANCODE_RCTRL];
zx_keyboard[KEY_M] = keys[SDL_SCANCODE_M];
zx_keyboard[KEY_N] = keys[SDL_SCANCODE_N];
zx_keyboard[KEY_B] = keys[SDL_SCANCODE_B];
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 = ear ? 0xbf : 0xff;
uint8_t result = ear ? 0xff : 0xbf;
update_zx_keyboard();
@@ -200,53 +217,36 @@ namespace zx_ula
void port_out(int port, int val)
{
border_color = val & 0x7;
mic = (val&0x08)==0;
ear = val&0x10;
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;
uint16_t t_sound=0;
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);
if (top<len) memchr(&stream[top], sound_buffer[top-1], len-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(const uint8_t dt)
{
t_sound += dt;
if (t_sound>=317) {
t_sound-=317;
sound_buffer[sound_pos++] = ear*128;
}
}
}

View File

@@ -8,11 +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(const uint8_t dt);
uint8_t get_sample();
void keydown(const char key);
void keyup(const char key);
}