diff --git a/.gitignore b/.gitignore index 2c115ef..e9931fb 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,10 @@ build/* # Allow example file !examples/ZX80-4K/simple.o + +# Exclude extra build directories +build_2040/* +build_2350/* + +#Code counter +.VSCodeCounter/* diff --git a/CMakeLists.txt b/CMakeLists.txt index c5589d1..edec8fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,6 +160,7 @@ else() endif() # Determine video system and name uf2 +set (EXTRA_CHECK "-Wextra") if ((${PICO_BOARD} STREQUAL "dviboard") OR (${PICO_BOARD} STREQUAL "olimexpcboard") OR (${PICO_BOARD} STREQUAL "wspizeroboard")) target_compile_definitions(${PROJECT} PUBLIC -DDVI_1BPP_BIT_REVERSE=1 @@ -221,6 +222,7 @@ else() target_link_libraries(${PROJECT} mod_scanvideo_dpi) else () target_link_libraries(${PROJECT} pico_scanvideo_dpi) + set (EXTRA_CHECK "") endif() endif() @@ -244,7 +246,7 @@ endif() # always support Chroma target_compile_definitions(${PROJECT} PRIVATE -DSUPPORT_CHROMA -DLOAD_AND_SAVE) -target_compile_options(${PROJECT} PRIVATE -Wall) +target_compile_options(${PROJECT} PRIVATE -Wall ${EXTRA_CHECK}) target_link_libraries(${PROJECT} pico_multicore diff --git a/PicoDVI b/PicoDVI index 4b4d03f..046ac4a 160000 --- a/PicoDVI +++ b/PicoDVI @@ -1 +1 @@ -Subproject commit 4b4d03f655f3c90a97313af6fd47d10acc5d6a14 +Subproject commit 046ac4aed3b2c7a35d33e1bb715ac4152fea61b7 diff --git a/README.md b/README.md index d6d9c2b..c4ff810 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,11 @@ + Emulates user defined graphics, including CHR$128 and QS User Defined Graphics + Emulates the [Chroma 80](http://www.fruitcake.plus.com/Sinclair/ZX80/Chroma/ZX80_ChromaInterface.htm) and [Chroma 81](http://www.fruitcake.plus.com/Sinclair/ZX81/Chroma/ChromaInterface.htm) interfaces to allow a colour display. Also supports the enhanced TV sound provided by Chroma + Emulation runs at accurate speed of a 3.25MHz ZX81 ++ Supports saving of snapshots. No need to restart from the beginning after you die in a game! + Optionally emulates real-time ZX81/80 program load and save with realistic sound and graphics + Emulates European and US configuration (i.e. emulates 50Hz and 60Hz ZX81) + Supports larger ZX81 generated displays of over 320 by 240 pixels (40 character width and 30 character height) -+ Load `.p`, `.81`, `.o`, `.80` and `.p81` files from micro SD Card. Save `.p` and `.o` files ++ Load `.p`, `.81`, `.o`, `.s` (picozx81 snapshot), `.80` and `.p81` files from micro SD Card. Save `.p`, `.o` and `.s` files + Can display at 640x480 or 720x576 (for an authentic display on a UK TV) + 720x576 can be configured to run at a frame rate to match the "real" ZX81 (~50.65 Hz). + An interlaced mode can be selected to display interlaced images with minimal flicker @@ -204,6 +205,8 @@ The following can be configured: | ACB | Enables ACB stereo if sound card enabled | Off | | | NTSC | Enables emulation of NTSC (60Hz display refresh)| Off | As for the "real" ZX81, SLOW mode is slower when NTSC is selected| | VTOL | Specifies the tolerance in lines of the emulated TV display detecting vertical sync| 25 | See notes below| +| FiveSevenSix | Enables the generation of a 720x576p display @ 50Hz `On` , or 720x576p display @ 50.65Hz `Match`. If set to `Off` a 640x480 display @ 60Hz is produced | Off | + **Notes:** 1. The "real" QS UDG board had a manual switch to enable / disable. In the emulator, if `QSUDG` is selected, it is assumed to be switched on after the first write to the memory mapped address range (0x8400 to 0x87ff) @@ -213,6 +216,7 @@ The following can be configured: 5. The "Big Bang" ROM can double the speed of BASIC programs 6. The Waveshare LCD 2.8 board has no sound capabilities 7. The `TV` sound option emulates the sound generated through the TV speaker by VSYNC pulses. The `CHROMA` sound option emulates the sound generated through the TV speaker by the Chroma interface when VSYNC pulses are not frame synchronised +8. When `FiveSevenSix` is specified in the `[default]` section of the `config.ini` file in the root directory of the SD Card it sets the display resolution and refresh rate of picozx81 at start-up. If set for a specific program, then the resolution and refresh rate is set when the program is loaded. If necessary,picozx81 will reset and restart with the requested display before the program is loaded ### Joystick In addition a USB joystick,and on some boards a 9-pin joystick, can be configured to generated key presses @@ -227,14 +231,13 @@ In addition a USB joystick,and on some boards a 9-pin joystick, can be configure Notes: ENTER and SPACE can be used to represent the New Line and Space keys, respectively ### Extra configuration options -Eight extra options apply across all programs and can only be set in the `[default]` section of the `config.ini` file in the root directory of the SD Card +Nine extra options apply across all programs and can only be set in the `[default]` section of the `config.ini` file in the root directory of the SD Card | Item | Description | Default Value | | --- | --- | --- | -| FiveSevenSix | Enables the generation of a 720x576p display @ 50Hz `On` , or 720x576p display @ 50.65Hz `Match`. If set to `Off` a 640x480 display @ 60Hz is produced | Off | | Dir | Sets the initial default directory to load and save programs | / | | Load | Specifies the name of a program to load automatically on boot in the directory given by `Dir` | "" | | DoubleShift | Enables the generation of function key presses on a 40 key ZX80 or ZX81 keyboard. See [here](#function-key-menu)| On | -| AllFiles| When set, all files are initially displayed when the [Load Menu](#f2---load) is selected. When off only files with extensions `.p`, `.o`, `.81`, `.80` and `.p81` are initially displayed|Off| +| AllFiles| When set, all files are initially displayed when the [Load Menu](#f2---load) is selected. When off only files with extensions `.p`, `.o`, `.s`, `.81`, `.80` and `.p81` are initially displayed|Off| | MenuBorder | Enables a border area (in characters) for the [Load](#f2---load) and [Pause](#f4---pause) menus, useful when using a display with overscan. Range 0 to 2| 1 | | LoadUsingROM | Runs the Sinclair ROM routines to load a file in real-time. Authentic loading visual and audio effects are emulated | OFF | | SaveUsingROM | Runs the Sinclair ROM routines to save a file in real-time. Authentic saving visual and audio effects are emulated | OFF | @@ -268,11 +271,13 @@ The original ZX80/ZX81 40 key keyboard does not have function keys. A "double sh 2. Shift is released, without another key being pressed 3. Within one second shift is pressed again 4. Shift is released, without another key being pressed -5. To generate a function key, within one second, a numeric key in the range `1` to `8` is pressed without shift being pressed. If `0` is pressed `Escape` is generated +5. To generate a function key, within one second, a numeric key in the range `1` to `9` is pressed without shift being pressed. If `0` is pressed `Escape` is generated This mechanism is enabled by default. To disable it set `DoubleShift` to `Off` in the configuration file ### F1 - Reset -Hard resets the emulator. It is equivalent to removing and reconnecting the power +Hard resets the emulator. It is equivalent to removing and reconnecting the power from the emulated computer. Note that the configuration of the computer remains unchanged, so it will restart with the same memory size, sound settings, display settings, ROM type etc that it had prior to the reset. + +The current directory on the SD Card is not changed by pressing F1 ### F2 - Load A menu displaying directories and files that can be loaded is displayed, using the ZX81 font. Any sound that is playing is paused. Directory names are prepended by `<` and appended by `>` e.g. `` @@ -280,12 +285,12 @@ If the name of a file or directory is too long to display in full it is truncate + The display can be navigated using the `up`, `down` and `enter` keys. The `7` key also generates `up` and the `6` key also generates `down` + For directories with a large number of files it is possible to move to the next page of files by using the `right`, `Page Down` or `8` key. To move to the previous page of files use the `left`, `Page Up` or `5` key -+ Press `A` to display all files in the directory. Press `P` to only display files with extensions `.p`, `.o`, `.81`, `.80` and .`p81` ++ Press `A` to display all files in the directory. Press `P` to only display files with extensions `.p`, `.o`, `.s`, `.81`, `.80` and .`p81` + Press `enter` whilst a directory entry is selected to move to that directory + Press `enter` when a file is selected to load that file + Press `Escape`, `space`, `Q` or `0` to return to the emulation without changing directory or loading a new program ### F3 - View Emulator Configuration -Displays the current emulator status. Any sound that is playing is paused. Note that this display is read only, no changes to the configuration can be made. Press `Escape`, `space`, `Q` or `0` to exit back to the running emulator +Displays the current emulator status. Any sound that is playing is paused. Note that this display is read only, no changes to the configuration can be made. Press `Escape`, `space`, `Q` or `0` to exit back to the running emulator. Note that the displayed directory path may be truncated due to space constraints ### F4 - Pause Pauses the emulation. Handy if the phone rings during a gaming session! `P` is XORed into the 4 corners of the display to indicate that the emulator is paused. Press `Escape`, `space`, `Q` or `0` to end the pause and return to the running emulator ### F5 - Display Keyboard Overlay @@ -306,13 +311,22 @@ The changes are *not* written back to the config files, so will be lost when the Allows the impact of changes to display resolution and frequency to be seen without editing config files. If a change is made and the menu is then exited by pressing `Enter` the Pico will reboot and use the new display mode. The changes are *not* written back to the main config files, so any changes will be lost on subsequent reboots. On the LCD builds the display resolution is fixed and only the frequency can be changed + +### F9 - Save snapshot +Saves a snapshot of the current running program to SD-Card. The file is saved to the current directory. A screen is displayed to allow a filename to be entered. If the filename does not end with `.s` it is automatically appended. If a file of the specified name already exists in the current directory, it is overwritten +without warning. Snapshot files are designed so that they can be loaded onto any device running picozx81. + +Use the `F2` command and select the file to reload the snapshot. If necessary the pico will reboot to set the correct display resolution and frequency + ## Loading and saving options -The emulator supports loading `.p`, `.81`, `.o`, `.80` and `.p81` files from micro SD Card. It can save in `.p` and `.o` format. +The emulator supports loading `.p`, `.81`, `.o`, `.s`, `.80` and `.p81` files from micro SD Card. It can save in `.p`, `.o` and `.s` formats. ``.o` and `.p` are the standard ZX81 formats. `.s` is a snapshot format specific to picozx81. Files to be loaded should only contain characters that are in the ZX81 or ZX80 character set ### Load There are 3 ways to load files: #### 1. Via the F2 menu The user can navigate the SD card directory and select a file to load. The emulator is configured to the settings specified for the file in the `config.ini` files, reset and the new file loaded + +**Note:** This is the only supported way to load snapshot (`.s`) files. The configuration information is stored as part of the snapshot file, it is not loaded separately from `config.ini` #### 2. Via `LOAD ""` (ZX81) or `LOAD` (ZX80) If the user enters the `LOAD` command without specifying a file name the SD Card directory menu is displayed and a file to load can be selected. The emulator is configured to the settings specified for the file in the `config.ini` files. Unlike for option 1, the emulator is only reset if the configuration differs. This, for example, allows for RAMTOP to be manually set before loading a program #### 3. Via `LOAD "program-name"` (ZX81 only) @@ -321,7 +335,7 @@ If a file name is specified, then `.p` is appended and an attempt is made to loa If the supplied filename, with `.p` appended, does not exist, then the `LOAD` fails with error `D`. This is similar to a "real" ZX81, where if a named file is not on a tape, the computer will attempt to load until the user aborts by pressing `BREAK`, generating error `D` ### Save #### ZX81 -To save a program the `SAVE "Filename"` command is used. If `"Filename"` has no extension then `.p` is appended to the supplied file name. The file is saved in the current directory. If a file of the specified name already exists, it is overwritten +To save a program the `SAVE "Filename"` command is used. If `"Filename"` has no extension then `.p` is appended to the supplied file name. The file is saved in the current directory. If a file of the specified name already exists, it is overwritten without warning. #### ZX80 To save a program the `SAVE` command is used. `SAVE` does not take a file name, so mechanisms are provided to supply a file name. When `SAVE` is executed the emulator scans the program for a `REM` statement of the form: @@ -337,6 +351,8 @@ The ZX80 keyboard image is automatically displayed to make it easier to enter no Press `ENTER` to exit the screen and use the filename, `.o` is appended if not supplied. Press `Esc` or `SHIFT 1` to leave the screen without setting a filename The program is saved to the current directory. If no valid file name is supplied a default filename of `"zx80prog.o"` is used. Any existing file with the same name is overwritten +#### Snapshots +The only way to save a snapshot of a running ZX80 or ZX81 program is by pressing `F9` ### Loading and Saving Memory Blocks When emulating a ZX81, extensions are provided to `LOAD` and `SAVE` to support the loading and saving of memory blocks. The syntax is similar to that used by [ZXpand](https://github.com/charlierobson/ZXpand-Vitamins/wiki/ZXpand---Online-Manual) @@ -523,12 +539,15 @@ To enable chroma support set LowRAM on, and Memory to 48kB + The Chroma 80 4K ROM versions use a loader program to set the colour data and then load the main program. The loader available in the supplied links uses a custom tape load routine that is not detected by picozx81. To create a compatible loader that uses that standard 4K ROM load routine use the [Chroma Program Enhancement Creator](http://www.fruitcake.plus.com/Sinclair/ZX81/Chroma/ChromaInterface_Software_ChromaProgramEnhancementCreator.htm). As the 4K ROM `LOAD` command does not take an argument, picozx81 will display the contents of the current directory when the loader program is run. Select the main program from the directory list and it will load and run, using the colour data loaded earlier # Building **Notes:** -+ Prebuilt executable files for the 7 supported board types can be found [here](uf2/) -+ If a **zip** of the source files is downloaded from GitHub, it will be **incomplete**, as the zip will not contain the submodules. Zip files of the submodules would have to be downloaded separately from GitHub. It is easier to clone recursive the repository, as described in the following section ++ Prebuilt executable files for the 9 supported board types can be found [here](uf2/) ++ If a **zip** of the source files is downloaded from GitHub, it will be **incomplete**, as the zip will not contain the submodules. Zip files of the submodules would have to be downloaded separately from GitHub. It is easier to recursively clone the repository, as described in the following section ### To build: 1. Install the Raspberry Pi Pico toolchain and SDK. SDK 2.x must be used - Instructions to do this for several operating systems can be found by downloading [this pdf](https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf). Chapter 2 covers installation on the Raspberry Pi, chapter 9 describes the process for other operating systems + Instructions to do this for several operating systems can be found by downloading [this pdf](https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf). Chapter 2 covers installation on the Raspberry Pi, chapter 9 describes the process for other operating systems. + + **Note:** At time of writing SDK2.1 had two issues that impacted picozx81. To build using SDK 2.1 use the develop branch, and revert the TinyUSB submodule to that included in SDK 2.0. Alternatively use SDK 2.0 + 2. The vga display uses the `pico_scanvideo` library, which is part of the `pico-extras` repository. Clone this repository, including submodules `git clone --recursive https://github.com/raspberrypi/pico-extras.git` @@ -566,7 +585,7 @@ This will be named `picozx81_vga_rp2040.uf2` 7. Populate a micro SD Card with files you wish to run. Optionally add `config.ini` files to the SD Card. See [here](examples) for examples of config files **Notes:** -+ To build for the RP2350 append -DPICO_MCU=rp2350 to the CMake command. The resulting `uf2` file will include rp2355 in its name ++ To build for the RP2350 append -DPICO_MCU=rp2350 to the CMake command. The resulting `uf2` file will include rp2350 in its name + The [`buildall`](buildall) script in the root directory of `picozx81` will build `uf2` files for all supported combinations of mcu and board types + To debug using OpenOCD build and install OpenOCD as described in [Getting Started with Raspberry Pi Pico-series](https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf) + If debugging using MS Visual Studio Code then install the Raspberry Pi Pico extension. Commands loaded by this extension are used to determine the active MCU type in `launch.json` @@ -600,7 +619,7 @@ This will be named `picozx81_vga_rp2040.uf2` + The Waveshare Pico-ResTouch-LCD-2.8 board has a touch controller, but the emulator does not support its use ### Olimex RP2040-PICO-PC + The Olimex RP2040-PICO-PC board does not supply 5v to DVI pin 18. This may result in the board not being detected by some TVs. If necessary short the SJ1 connector so 5V is supplied -+ If SJ1 is shorted the board may be back powered by some TVs. This can cause the board to not start correctly. If this happens either connect the power before attaching the HDMI cable, or press the reset button on the board ++ If SJ1 is shorted the board may be back powered by some TVs. This can cause the board to not start correctly. If this happens either connect the power before attaching the HDMI cable, or press the reset button on the board. It may be possible to avoid back powering by connecting a diode across the SJ1 connections, rather than shorting them ### Cytron Maker + The Cytron Maker Pi Pico has an onboard piezo buzzer. The audio quality is poor, but it can be used instead of speakers. If the buzzer is enabled (using the switch on the maker board) ensure that ACB Stereo is disabled @@ -660,7 +679,7 @@ By default the display for the Waveshare Pico-ResTouch-LCD-2.8 is configured rot With boards with connectors supplied for enough free GPIO pins it is possible to attach a 9 pin connector and then plug-in and use "in period" 9-pin joysticks ### Supported Boards -The lcdmaker, vgamaker222c, picomitevga, pizero and picozx builds support the connection of a 9-pin joystick connector +The lcdmaker, vgamaker222c, picomitevga, pizero, Olimexpc and picozx builds support the connection of a 9-pin joystick connector ### Obtaining a 9-pin interface Solderless 9-Pin connectors can be sourced from e.g. ebay or [amazon](https://www.amazon.co.uk/sourcing-map-Breakout-Connector-Solderless/dp/B07MMMGGXP) @@ -672,8 +691,20 @@ Solderless 9-Pin connectors can be sourced from e.g. ebay or [amazon](https://ww | vgamaker222c | GP20 | GP21 | GP22 | GP26 | GP27 | Ground | | picomitevga | GP3 | GP4 | GP5 | GP22 | GP26 | Ground | | pizero | GP11 | GP12 | GP10 | GP15 | GP13 | Ground | +| Olimexpc | GP20 | GP21 | GP8 | GP9 | GP5 | Ground | The picozx board has a 9-pin joystick port connector built in +#### Olimex UEXT1 connections +For the RP2040-PICO-PC the pins required for the joystick connection are on the UEXT1 connector. The mapping is: +| Joystick Pin number | UEXT1 Pin Number | +| --- | --- | +| 1 | 3 | +| 2 | 4 | +| 3 | 6 | +| 4 | 5 | +| 6 | 10 | +| 8 | 2 | + ### Enabling the joystick To enable the nine pin joystick set `NinePinJoystick` to `On` in the `[default]` section of the `config.ini` file in the root directory @@ -700,6 +731,26 @@ Code to convert a ZX8x keyboard to USB can be found at [ZX81_USB_KBD](https://gi To access the function menus from a ZX80/81 keyboard the `doubleshift` configuration option must be enabled The picozx board does support keyboard and joystick. This is achieved by using every available GPIO pin, and using VGA222 with CSYNC, together with mono audio +## Snapshot file format +The snapshot (`.s`) file format stores the state of the emulator, so that excution can continue at a later date, potentially on an instance of picozx81 running on a different board type. To achieve this most of the picozx81 variables are saved. The 64 kB of emulated memory is also saved, together with the state of the AY and beeper sound emaulators. + +The display mode (resolution, refresh rate, NTSC vs PAL, whether the display is blanked etc) is saved, but the actual video buffers are not. The rationale for this is 3 fold: +1. The display buffers differ between LCD and non LCD displays, storing them would make supporting moving between board types more difficult +2. Storing 4 display and 4 chroma buffers would more than double the size of the `.s` file +3. The emulator recreates the display every 1/50th of a second from the data that is saved in the `.s` file + +Clearly, the `.s` file is dependent on the internal picozx81 variables. Future changes to the picozx81 code may result in a change to the format of a `.s` file. To allow newer versions of picozx81 to load older versions of `.s` file, the `.s` file stores version information in its header + +### File header format +Data is stored in little endian format. The first 3 values in the snapshot format are: +#### Identifier +`S` `N` `A` `P` in ASCII (4 * 8 bit) +#### Version +Major (16 bits) and minor (16 bit) version numbers +### Video Mode +Identifies resolution and refresh rate (8 bit) + +The header is followed by the state data. A full `.s` file is roughly 66kB in size ## Performance and constraints In an ideal world the latest versions of the excellent sz81 or EightyOne emulators would have been ported. An initial port showed that they are too processor intensive for an (overclocked) ARM M0+. An earlier version of sz81 ([2.1.8](https://github.com/ikjordan/sz81_2_1_8)) was used as a basis, with some Z80 timing corrections and back porting of the 207 tstate counter code from the latest sz81 (2.3.12). See [here](#applications-tested) for a list of applications tested @@ -722,6 +773,7 @@ This emulator offers the following over MCUME: + Emulation runs at full speed of a 3.25MHz ZX81 + Ability to save files + Ability to load a program without reset ++ Ability to save and load snapshots + Support for Hi-res and pseudo Hi-res graphics + Support for multiple DVI, VGA and LCD boards + Support for Chroma 80 and Chroma 81 diff --git a/boards/olimexpcboard.h b/boards/olimexpcboard.h index 0bde02f..a06d0fd 100644 --- a/boards/olimexpcboard.h +++ b/boards/olimexpcboard.h @@ -59,6 +59,14 @@ #define PICO_AUDIO_PWM_R_PIN OLIMEXPCBOARD_PWM_R_PIN #endif +#define NINEPIN_JOYSTICK + +#define NINEPIN_UP 20 // UEXT1 Pin 3 +#define NINEPIN_DOWN 21 // UEXT1 Pin 4 +#define NINEPIN_LEFT 8 // UEXT1 Pin 6 +#define NINEPIN_RIGHT 9 // UEXT1 Pin 5 +#define NINEPIN_BUTTON 5 // UEXT1 Pin 10 + // UEXT1 Pin 2 is Ground #define PICO_OLIMEXPC_BOARD diff --git a/buildall b/buildall index d547592..e861cfb 100755 --- a/buildall +++ b/buildall @@ -1,12 +1,23 @@ #!/bin/bash +# Get the build type - in case different build types are needed for Pico and Pico 2 in the future +build_type() +{ + if [[ $1 == "rp2040" ]] + then + echo "Release" + else + echo "Release" + fi +} #build a board type build_uf2() { board=$1"board .." uf2="picozx81_"$1"_"$2".uf2" + type=$(build_type $2) - cmake -DHDMI_SOUND=OFF -DPICOZX_LCD=OFF -DCMAKE_BUILD_TYPE=Release -DTIME_SPARE=OFF -DPICO_BOARD=${board} -DPICO_MCU=$2 + cmake -DHDMI_SOUND=OFF -DPICOZX_LCD=OFF -DCMAKE_BUILD_TYPE=${type} -DTIME_SPARE=OFF -DFLASH_LED=OFF -DPICO_BOARD=${board} -DPICO_MCU=$2 make -j4 cp ./${uf2} ../uf2/${uf2} } @@ -14,10 +25,10 @@ build_uf2() build_uf2_hdmi() { board=$1"board .." - mcu=$2 uf2="picozx81_"$1"_hdmi_sound_"$2".uf2" + type=$(build_type $2) - cmake -DHDMI_SOUND=ON -DPICOZX_LCD=OFF -DCMAKE_BUILD_TYPE=Release -DTIME_SPARE=OFF -DPICO_BOARD=${board} -DPICO_MCU=$2 + cmake -DHDMI_SOUND=ON -DPICOZX_LCD=OFF -DCMAKE_BUILD_TYPE=${type} -DTIME_SPARE=OFF -DFLASH_LED=OFF -DPICO_BOARD=${board} -DPICO_MCU=$2 make -j4 cp ./${uf2} ../uf2/${uf2} } @@ -25,23 +36,25 @@ build_uf2_hdmi() build_uf2_lcd() { board=$1"board .." - mcu=$2 uf2="picozx81_"$1"_lcd_"$2".uf2" + type=$(build_type $2) - cmake -DHDMI_SOUND=OFF -DPICOZX_LCD=ON -DCMAKE_BUILD_TYPE=Release -DTIME_SPARE=OFF -DPICO_BOARD=${board} -DPICO_MCU=$2 + cmake -DHDMI_SOUND=OFF -DPICOZX_LCD=ON -DCMAKE_BUILD_TYPE=${type} -DTIME_SPARE=OFF -DFLASH_LED=OFF -DPICO_BOARD=${board} -DPICO_MCU=$2 make -j4 cp ./${uf2} ../uf2/${uf2} } -# Create the directories -mkdir -p build_2040 -mkdir -p build_2350 +# Create the directories from scratch +rm -rf build_2040 +rm -rf build_2350 +mkdir build_2040 +mkdir build_2350 mkdir -p uf2 # Build all PicoZX81 variants for rp2040 cd build_2040 -build_uf2 vga rp2040 +build_uf2 vga rp2040 build_uf2 dvi rp2040 build_uf2 picomitevga rp2040 build_uf2 olimexpc rp2040 diff --git a/display/display_common.c b/display/display_common.c index 930bb1f..f807aac 100644 --- a/display/display_common.c +++ b/display/display_common.c @@ -8,6 +8,7 @@ #include "display_priv.h" #include "zx80bmp.h" #include "zx81bmp.h" +#include "../src/emusnap.h" #ifdef SUPPORT_CHROMA // Structure for chroma buffers @@ -423,6 +424,28 @@ void __not_in_flash_func(newFrame)(void) mutex_exit(&next_frame_mutex); } +bool display_save_snap(void) +{ + if (!emu_FileWriteBytes(&blank, sizeof(blank))) return false; + if (!emu_FileWriteBytes(&blank_colour, sizeof(blank_colour))) return false; + if (!emu_FileWriteBytes(&keyboard, sizeof(keyboard))) return false; + if (!emu_FileWriteBytes(&showKeyboard, sizeof(showKeyboard))) return false; + if (!emu_FileWriteBytes(&interlace, sizeof(interlace))) return false; + if (!emu_FileWriteBytes(&no_skip, sizeof(no_skip))) return false; + return true; +} + +bool display_load_snap(void) +{ + if (!emu_FileReadBytes(&blank, sizeof(blank))) return false; + if (!emu_FileReadBytes(&blank_colour, sizeof(blank_colour))) return false; + if (!emu_FileReadBytes(&keyboard, sizeof(keyboard))) return false; + if (!emu_FileReadBytes(&showKeyboard, sizeof(showKeyboard))) return false; + if (!emu_FileReadBytes(&interlace, sizeof(interlace))) return false; + if (!emu_FileReadBytes(&no_skip, sizeof(no_skip))) return false; + return true; +} + // // Private functions // diff --git a/display/display_lcd.c b/display/display_lcd.c index 4a1b356..fc38f38 100644 --- a/display/display_lcd.c +++ b/display/display_lcd.c @@ -326,7 +326,7 @@ static void __not_in_flash_func(render_loop)() } // 256 pixels of keyboard, 2 bits per pixel - for (int i=0; i<(keyboard->width >> 2); ++i) + for (unsigned int i=0; i<(keyboard->width >> 2); ++i) { uint8_t byte = keyboard->pixel_data[(y - keyboard_y) * (keyboard->width>>2) + i]; for (int x=6; x>=2; x-=4) @@ -372,7 +372,7 @@ static void __not_in_flash_func(render_loop)() } // 256 pixels of keyboard, 2 bits per pixel - for (int i=0; i<(keyboard->width >> 2); ++i) + for (unsigned int i=0; i<(keyboard->width >> 2); ++i) { uint8_t byte = keyboard->pixel_data[(y - keyboard_y) * (keyboard->width>>2) + i]; for (int x=6; x>=2; x-=4) @@ -512,6 +512,8 @@ void core1_main(void) static bool __not_in_flash_func(timer_callback)(repeating_timer_t *rt) { + (void)rt; + // Trigger new frame sem_release(&frame_sync); return true; diff --git a/inih b/inih index 4e618f7..63a302c 160000 --- a/inih +++ b/inih @@ -1 +1 @@ -Subproject commit 4e618f77d4bae216865c5abd972d99b1ba5031e2 +Subproject commit 63a302cfe53f087e3c44233cc2f08f05aa29e4c6 diff --git a/scanvideo/pico_scanvideo_dpi/scanvideo.c b/scanvideo/pico_scanvideo_dpi/scanvideo.c index 69fb0e5..9faf98e 100644 --- a/scanvideo/pico_scanvideo_dpi/scanvideo.c +++ b/scanvideo/pico_scanvideo_dpi/scanvideo.c @@ -424,7 +424,7 @@ inline static void list_remove(full_scanline_buffer_t **phead, full_scanline_buf static inline uint32_t scanline_id_after(uint32_t scanline_id) { uint32_t tmp = scanline_id & 0xffffu; - if (tmp < video_mode.height - 1) { + if (tmp < (uint32_t)(video_mode.height - 1)) { return scanline_id + 1; } else { return scanline_id + 0x10000u - tmp; @@ -570,15 +570,19 @@ static inline void abort_all_dma_channels_assuming_no_irq_preemption() { // work around it in software, but we want to suppress the IRQ afterwards anyway, so // as long as the spurious IRQ doesn't get taken here, then the h/w issue is of no problem dma_hw->abort = PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK; - // note that relying on the abort bits is no longer safe, as it may get cleared before the spurious IRQ happens - // // wait for abort(s) to complete - // while (dma_hw->abort & PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK) tight_loop_contents(); + // note that relying on the abort bits is not safe on RP2040, as it may get cleared before the spurious IRQ happens + // wait for abort(s) to complete +#if !PICO_RP2040 + // fixed after RP2040 + while (dma_hw->abort & PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK) tight_loop_contents(); +#else while (dma_channel_is_busy(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL)) tight_loop_contents(); #if PICO_SCANVIDEO_PLANE_COUNT > 1 while (dma_channel_is_busy(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2)) tight_loop_contents(); #if PICO_SCANVIDEO_PLANE_COUNT > 2 while (dma_channel_is_busy(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3)) tight_loop_contents(); #endif +#endif #endif // we don't want any pending completion IRQ which may have happened in the interim dma_hw->ints0 = PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK; @@ -739,6 +743,7 @@ void __video_most_time_critical_func(prepare_for_active_scanline_irqs_enabled)() pio_sm_clear_fifos(video_pio, PICO_SCANVIDEO_SCANLINE_SM); } if (video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM].instr != PIO_WAIT_IRQ4) { + // we don't know where we were, so me should also make sure OSR is empty, we certainly haven't sent any data yet // hmm the problem here is we don't know if we should wait or not, because that is purely based on timing.. // - if irq not posted, and we wait: GOOD // - if irq not posted and we don't wait: BAD. early line @@ -1068,6 +1073,7 @@ extern bool scanvideo_in_vblank() { } static uint __no_inline_not_in_flash_func(default_scanvideo_scanline_repeat_count_fn)(uint32_t scanline_id) { + (void)scanline_id; return 1; } @@ -1697,6 +1703,8 @@ bool scanvideo_setup_with_timing(const scanvideo_mode_t *mode, const scanvideo_t bool video_24mhz_composable_adapt_for_mode(const scanvideo_pio_program_t *program, const scanvideo_mode_t *mode, scanvideo_scanline_buffer_t *missing_scanline_buffer, uint16_t *modifiable_instructions) { + (void)program; + int delay0 = 2 * mode->xscale - 2; int delay1 = delay0 + 1; valid_params_if(SCANVIDEO_DPI, delay0 <= 31); @@ -1752,10 +1760,16 @@ bool video_24mhz_composable_adapt_for_mode(const scanvideo_pio_program_t *progra bool video_default_adapt_for_mode(const scanvideo_pio_program_t *program, const scanvideo_mode_t *mode, uint16_t *modifiable_instructions) { + (void)program; + (void)mode; + (void)modifiable_instructions; + return true; } void scanvideo_default_configure_pio(pio_hw_t *pio, uint sm, uint offset, pio_sm_config *config, bool overlay) { + (void)offset; + pio_sm_set_consecutive_pindirs(pio, sm, PICO_SCANVIDEO_COLOR_PIN_BASE, PICO_SCANVIDEO_COLOR_PIN_COUNT, true); sm_config_set_out_pins(config, PICO_SCANVIDEO_COLOR_PIN_BASE, PICO_SCANVIDEO_COLOR_PIN_COUNT); sm_config_set_out_shift(config, true, true, 32); // autopull diff --git a/src/emuapi.cpp b/src/emuapi.cpp index e2f0d17..6637a5f 100755 --- a/src/emuapi.cpp +++ b/src/emuapi.cpp @@ -7,15 +7,19 @@ #include #include "tusb.h" #include "hid_usb.h" -#include "display.h" #include "emuapi.h" #include "emuvideo.h" +#include "emusound.h" #include "emupriv.h" #include "ini.h" #include "iopins.h" +#ifdef PICO_SPI_LCD_SD_SHARE +#include "display.h" +#endif + /******************************** * Menu file loader UI ********************************/ @@ -29,11 +33,12 @@ static bool fsinit = false; ********************************/ bool emu_FileOpen(const char *filepath, const char *mode) { - (void)mode; // Ignore mode, always open as read only + BYTE access = (*mode == 'w') ? (FA_WRITE | FA_CREATE_ALWAYS) : FA_READ; + bool retval = false; printf("FileOpen %s\n", filepath); - FRESULT res = f_open(&file, filepath, FA_READ); + FRESULT res = f_open(&file, filepath, access); if (res == FR_OK) { retval = true; @@ -45,7 +50,7 @@ bool emu_FileOpen(const char *filepath, const char *mode) return (retval); } -int emu_FileRead(void *buf, int size, int offset) +int emu_FileRead(void* buf, int size, int offset) { unsigned int read = 0; @@ -60,7 +65,7 @@ int emu_FileRead(void *buf, int size, int offset) } } - if (f_read(&file, (void *)buf, size, &read) != FR_OK) + if (f_read(&file, buf, size, &read) != FR_OK) { printf("emu_FileRead read failed\n"); read = 0; @@ -68,11 +73,66 @@ int emu_FileRead(void *buf, int size, int offset) } else { - printf("emu_FileRead negative size %i\n", size); + printf("emu_FileRead illegal size %i\n", size); } return read; } +int emu_FileReadBytes(void* buf, unsigned int size) +{ + unsigned int read = 0; + + if (size > 0) + { + + if (f_read(&file, buf, size, &read) != FR_OK) + { + printf("emu_FileReadBytes read failed\n"); + read = 0; + } + else + { + if (size != read) + { + printf("emu_FileReadBytes, not all read\n"); + } + } + } + else + { + printf("emu_FileReadBytes illegal size %i\n", size); + } + return read; +} + +int emu_FileWriteBytes(const void* buf, unsigned int size) +{ + unsigned int write = 0; + + if (size > 0) + { + + if (f_write(&file, buf, size, &write) != FR_OK) + { + printf("emu_FileWriteBytes write failed\n"); + write = 0; + + } + else + { + if (size != write) + { + printf("emu_FileWriteBytes, not all written\n"); + } + } + } + else + { + printf("emu_FileReadBytes illegal size %i\n", size); + } + return write; +} + void emu_FileClose(void) { f_close(&file); @@ -110,6 +170,7 @@ bool emu_SaveFile(const char *filepath, void *buf, int size) return true; } + /******************************** * Initialization ********************************/ @@ -479,6 +540,7 @@ static bool setDirectory(const char* dir) { if (dir[0]) { + // Append a final forward slash if necessary if (dir[strlen(dir) - 1] != '/') { if (strncasecmp(dirPath, dir, strlen(dir) - 1) || @@ -510,6 +572,57 @@ static bool setDirectory(const char* dir) retVal = true; } } + + // If changed, prune the directory of any navigation, do this after + // the copy, as the string passed in is const + if (retVal) + { + char* updir = 0; + do + { + // search for "../" + // Note that if "../" is present then we know that the path is valid + updir = strstr(dirPath, "../"); + + if (updir) + { + // manually search back for the previous directory separator + // There will not be one for root + char* start = updir-2; + + while ((*start != '/') && (start != dirPath)) + { + --start; + } + + // Start of path does not have '/' + start += (start != dirPath) ? 1 : 0; + + // Now close the gap, copying up to and including the null terminator + do + { + *start++ = updir[3]; + } while (updir++[3]); + } + } while (updir != 0); // In case moved up multiple directories + + // Strip out all "./" + do + { + updir = strstr(dirPath, "./"); + + if (updir) + { + // Close the gap + do + { + updir[0] = updir[2]; + } while (updir++[2]); + + printf("Modified . dir %s\n", dirPath); + } + } while (updir != 0); + } } return retVal; } @@ -570,6 +683,8 @@ void emu_SetSound(int soundType) general.sound = soundType; specific.sound = soundType; #else + (void)soundType; + general.sound = SOUND_TYPE_NONE; specific.sound = SOUND_TYPE_NONE; #endif @@ -623,7 +738,7 @@ void emu_SetCHR128(bool chr128) specific.CHR128 = chr128; } -void emu_SetRebootMode(FiveSevenSix_T mode) +void emu_SetRebootMode(FiveSevenSix_T mode, const char* dirname, const char* filename) { char filepath[MAX_FULLPATH_LEN]; @@ -640,12 +755,31 @@ void emu_SetRebootMode(FiveSevenSix_T mode) f_write(&file, filepath, strlen(filepath), &bw); sprintf(filepath, "FiveSevenSix = %s\n", (mode == MATCH) ? "MATCH" : (mode == ON) ? "ON" : "OFF"); f_write(&file, filepath, strlen(filepath), &bw); + if (filename) + { + sprintf(filepath, "Load = %s\n", filename); + f_write(&file, filepath, strlen(filepath), &bw); + + if (dirname) + { + sprintf(filepath, "Dir = %s\n", dirname); + f_write(&file, filepath, strlen(filepath), &bw); + } + } f_close(&file); + EMU_UNLOCK_SDCARD + // Set the watchdog to trigger a reboot watchdog_enable(10, true); + + // Stop until reboot + sleep_ms(50); + } + else + { + EMU_UNLOCK_SDCARD } - EMU_UNLOCK_SDCARD } static char convert(const char *val) @@ -842,6 +976,22 @@ static int handler(void *user, const char *section, const char *name, // Defaults to off c->conf->extendFile = isEnabled(value); } + else if (!strcasecmp(name, "FiveSevenSix")) + { + if (!strcasecmp(value, "Match")) + { + c->conf->fiveSevenSix = MATCH; + } + else if (isEnabled(value)) + { + c->conf->fiveSevenSix = ON; + } + else + { + // Defaults to off + c->conf->fiveSevenSix = OFF; + } + } else if ((!strcasecmp(section, "default")) && c->root) { // Following only allowed in default section of root config @@ -878,22 +1028,6 @@ static int handler(void *user, const char *section, const char *name, } c->conf->menuBorder = res; } - else if (!strcasecmp(name, "FiveSevenSix")) - { - if (!strcasecmp(value, "Match")) - { - c->conf->fiveSevenSix = MATCH; - } - else if (isEnabled(value)) - { - c->conf->fiveSevenSix = ON; - } - else - { - // Defaults to off - c->conf->fiveSevenSix = OFF; - } - } else if ((!strcasecmp(name, "NinePinJoystick"))) { // Defaults to off @@ -1063,6 +1197,8 @@ void emu_ReadDefaultValues(void) } // Note this should be called after the filename has been validated +// If a display resolution or refresh change is needed this may trigger +// a reboot void emu_ReadSpecificValues(const char *filename) { ConfHandler_T hand; @@ -1087,6 +1223,15 @@ void emu_ReadSpecificValues(const char *filename) strcat(config, CONFIG_FILE); ini_parse_fatfs(config, handler, &hand); + + // Determine whether a reboot is required + if (specific.fiveSevenSix != used.fiveSevenSix) + { + emu_SetRebootMode(specific.fiveSevenSix, emu_GetDirectory(), &filename[strlen(dirPath)]); + // emu_SetRebootMode will never return + // The board will reboot and load the file + } + // determine whether a reset is required resetNeeded = ((specific.M1NOT != used.M1NOT) || (specific.loadUsingROM != used.loadUsingROM) || @@ -1099,6 +1244,190 @@ void emu_ReadSpecificValues(const char *filename) } } +/******************************** + * Snapshot + ********************************/ +#define SNAPSHOT_ID 0x50414E53 // Little endian 'SNAP' +#define SUPPORTED_VERSION 0x00010001 // Major and minor versions +#define SECOND_OFFSET 57 // Start of second data section + +#ifdef __cplusplus +extern "C" { +#endif +extern bool save_snap_zx8x(void); +extern bool load_snap_zx8x(uint32_t version); +extern bool save_snap_z80(void); +extern bool load_snap_z80(uint32_t version); +extern bool display_save_snap(void); +extern bool display_load_snap(uint32_t version); + +#ifdef __cplusplus +} +#endif + +bool emu_loadSnapshotSpecific(const char* filename, const char* fullpathname) +{ + bool ret = false; + FiveSevenSix_T state; + printf("emu_loadSnapshotSpecific %s \n", fullpathname); + + EMU_LOCK_SDCARD + if (!(f_open(&file, fullpathname, FA_READ))) + { + // Check identifer + uint32_t id; + if (!emu_FileReadBytes(&id, sizeof(id)) || id != SNAPSHOT_ID) + { + printf("emu_loadSnapshotSpecific wrong id\n"); + } + else if (!emu_FileReadBytes(&id, sizeof(id)) || (id != SUPPORTED_VERSION)) + { + printf("emu_loadSnapshotSpecific wrong version %li\n", id); + } + else if (!emu_FileReadBytes(&state, sizeof(state)) || state != emu_576Requested()) + { + printf("emu_loadSnapshotSpecific wrong display type - triggering reboot\n"); + emu_SetRebootMode(state, emu_GetDirectory(), filename); + } + else if (!emu_FileReadBytes(&specific, sizeof(specific))) + { + printf("emu_loadSnapshotSpecific read specific failed\n"); + } + else if (f_tell(&file) != SECOND_OFFSET) + { + printf("emu_loadSnapshotSpecific wrong data size %lli\n", f_tell(&file)); + } + else + { + ret = true; + } + f_close(&file); + } + else + { + printf("file open failed\n"); + } + EMU_UNLOCK_SDCARD + return ret; +} + +bool emu_loadSnapshotData(const char* fullpathname) +{ + bool ret = false; + printf("emu_loadSnapshotData %s \n", fullpathname); + + EMU_LOCK_SDCARD + if (!(f_open(&file, fullpathname, FA_READ))) + { + // Check identifer + uint32_t id; + uint32_t version; + + if (!emu_FileReadBytes(&id, sizeof(id)) || id != SNAPSHOT_ID) + { + printf("emu_loadSnapshotData wrong id\n"); + } + else if (!emu_FileReadBytes(&version, sizeof(version)) || version != SUPPORTED_VERSION) + { + printf("emu_loadSnapshotData wrong version %li\n", version); + } + else if (f_lseek(&file, SECOND_OFFSET) || (f_tell(&file) != SECOND_OFFSET)) + { + printf("emu_loadSnapshotData move to start of second data failed\n"); + } + else if (!display_load_snap(version)) + { + printf("display_load_snap failed\n"); + } + else if (!load_snap_z80(version)) + { + printf("load_snap_z80 failed\n"); + } + else if (!load_snap_zx8x(version)) + { + printf("load_snap_zx8x failed\n"); + } + else if (!emu_sndLoadSnap(version)) + { + printf("emu_sndLoadSnap failed\n"); + } + else + { + ret = true; + } + f_close(&file); + } + else + { + printf("file open failed\n"); + } + EMU_UNLOCK_SDCARD + return ret; +} + +bool emu_saveSnapshot(const char* fullpathname) +{ + bool ret = false; + FiveSevenSix_T state = emu_576Requested(); + + printf("saveSnapshot %s \n", fullpathname); + + EMU_LOCK_SDCARD + if (!(f_open(&file, fullpathname, FA_CREATE_ALWAYS | FA_WRITE))) + { + // write identifer + uint32_t id = SNAPSHOT_ID; + uint32_t version = SUPPORTED_VERSION; + if (!emu_FileWriteBytes(&id, sizeof(id))) + { + printf("emu_saveSnapshot write id failed\n"); + } + else if (!emu_FileWriteBytes(&version, sizeof(version))) + { + printf("emu_saveSnapshot write version failed\n"); + } + else if (!emu_FileWriteBytes(&state, sizeof(state))) + { + printf("emu_saveSnapshot write display state failed\n"); + } + else if (!emu_FileWriteBytes(&specific, sizeof(specific))) + { + printf("emu_saveSnapshot write specific failed\n"); + } + else if (f_tell(&file) != SECOND_OFFSET) + { + printf("emu_saveSnapshot wrong offset - %lli\n", f_tell(&file)); + } + else if (!display_save_snap()) + { + printf("display_save_snap failed\n"); + } + else if (!save_snap_z80()) + { + printf("save_snap_z80 failed\n"); + } + else if (!save_snap_zx8x()) + { + printf("save_snap_zx8x failed\n"); + } + else if (!emu_sndSaveSnap()) + { + printf("emu_sndSaveSnap failed\n"); + } + else + { + ret = true; + } + f_close(&file); + } + else + { + printf("file open failed\n"); + } + EMU_UNLOCK_SDCARD + return ret; +} + /******************************** * Joystick and file test ********************************/ diff --git a/src/emuapi.h b/src/emuapi.h index 61c4f88..aea43aa 100755 --- a/src/emuapi.h +++ b/src/emuapi.h @@ -3,6 +3,7 @@ #include #include +#include "emusnap.h" #define MAX_FILENAME_LEN 64 #define MAX_DIRECTORY_LEN 128 @@ -43,6 +44,10 @@ extern unsigned int emu_FileSize(const char * filepath); extern bool emu_SaveFile(const char * filepath, void * buf, int size); extern bool emu_EndsWith(const char * s, const char * suffix); +extern bool emu_loadSnapshotSpecific(const char* filename, const char* fullpathname); +extern bool emu_loadSnapshotData(const char* fullpathname); +extern bool emu_saveSnapshot(const char* fullpathname); + extern const char* emu_GetLoadName(void); extern void emu_SetLoadName(const char* name); extern const char* emu_GetDirectory(void); @@ -105,7 +110,7 @@ extern void emu_SetSaveROM(bool saveROM); extern void emu_SetQSUDG(bool qsudg); extern void emu_SetCHR128(bool chr128); -extern void emu_SetRebootMode(FiveSevenSix_T mode); +extern void emu_SetRebootMode(FiveSevenSix_T mode, const char* dirname, const char* filename); extern void emu_WaitFor50HzTimer(void); diff --git a/src/emukeyboard.cpp b/src/emukeyboard.cpp index 10c2162..1d5fe16 100644 --- a/src/emukeyboard.cpp +++ b/src/emukeyboard.cpp @@ -180,6 +180,8 @@ void emu_KeyboardScan(void* data) } } #endif +#else + (void)(data); #endif } @@ -245,6 +247,8 @@ static inline void __not_in_flash_func(device_keyscan_row)(void) static bool __not_in_flash_func(timer_callback)(repeating_timer_t *rt) { + (void)rt; + device_keyscan_row(); return true; } diff --git a/src/emusnap.h b/src/emusnap.h new file mode 100644 index 0000000..0e2facd --- /dev/null +++ b/src/emusnap.h @@ -0,0 +1,15 @@ +#ifndef EMUSNAP_H +#define EMUSNAP_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern int emu_FileReadBytes(void* buf, unsigned int size); +extern int emu_FileWriteBytes(const void* buf, unsigned int size); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/emusound.cpp b/src/emusound.cpp index db933b9..1f7ad09 100644 --- a/src/emusound.cpp +++ b/src/emusound.cpp @@ -232,6 +232,7 @@ static void __not_in_flash_func(pwmInterruptHandler)() static bool __not_in_flash_func(audio_timer_callback)(struct repeating_timer *t) { + (void)(t); static uint32_t call_count = 0; static int cnt = 0; @@ -455,3 +456,21 @@ void emu_sndSilence(void) soundBuffer16[i] = ZEROSOUND; } } + +bool emu_sndSaveSnap(void) +{ + if (!sound_save_snap()) return false; + if (!emu_FileWriteBytes(&queued_sound_type, sizeof(queued_sound_type))) return false; + if (!emu_FileWriteBytes(&queued_play, sizeof(queued_play))) return false; + + return true; +} + +bool emu_sndLoadSnap(uint32_t version) +{ + if (!sound_load_snap(version)) return false; + if (!emu_FileReadBytes(&queued_sound_type, sizeof(queued_sound_type))) return false; + if (!emu_FileReadBytes(&queued_play, sizeof(queued_play))) return false; + + return true; +} diff --git a/src/emusound.h b/src/emusound.h index 48f7d9d..68f4709 100644 --- a/src/emusound.h +++ b/src/emusound.h @@ -11,6 +11,10 @@ extern void emu_sndGenerateSamples(void); extern void emu_sndSilence(void); extern uint16_t emu_sndGetSampleRate(void); extern void emu_sndQueueChange(bool playSound, int queued_sound_type); + +extern bool emu_sndSaveSnap(void); +extern bool emu_sndLoadSnap(uint32_t version); + #ifdef __cplusplus } #endif diff --git a/src/emuvideo.h b/src/emuvideo.h index d7788cc..72dc13d 100644 --- a/src/emuvideo.h +++ b/src/emuvideo.h @@ -2,7 +2,6 @@ #define EMUVIDEO_H #include -#include "emuapi.h" #ifdef __cplusplus extern "C" { diff --git a/src/hid_usb.c b/src/hid_usb.c index 55571f4..07a2f10 100644 --- a/src/hid_usb.c +++ b/src/hid_usb.c @@ -435,7 +435,7 @@ bool hidReadUsbKeyboard(uint8_t* special, bool usedouble) for(unsigned int i = 0; i < 6; ++i) { if (usedouble && (doubleshift == 2) && (!shift) && - (((report.keycode[i] >= HID_KEY_1) && (report.keycode[i] <= HID_KEY_8)) || (report.keycode[i] == HID_KEY_0))) + (((report.keycode[i] >= HID_KEY_1) && (report.keycode[i] <= HID_KEY_9)) || (report.keycode[i] == HID_KEY_0))) { if (report.keycode[i] != HID_KEY_0) { @@ -465,7 +465,7 @@ bool hidReadUsbKeyboard(uint8_t* special, bool usedouble) else { // Check for non Sinclair keys here: - if (((report.keycode[i] >= HID_KEY_F1) && (report.keycode[i] <= HID_KEY_F8)) || + if (((report.keycode[i] >= HID_KEY_F1) && (report.keycode[i] <= HID_KEY_F9)) || (report.keycode[i] == HID_KEY_ESCAPE)) { *special = report.keycode[i]; diff --git a/src/loadp.c b/src/loadp.c index 8d32a16..157d9e8 100644 --- a/src/loadp.c +++ b/src/loadp.c @@ -139,9 +139,9 @@ int loadPGetBit(void) pulse_count++; // Determine what comes after the 0 pulse - int max_count = (bit_state == ZERO_BIT) ? LOADP_ZERO_COUNT : LOADP_ONE_COUNT; + uint32_t max_count = (bit_state == ZERO_BIT) ? LOADP_ZERO_COUNT : LOADP_ONE_COUNT; - if (max_count == pulse_count) + if (max_count == pulse_count) { pulse_state = SILENCE_PULSE; pulse_length_max = LOADP_SILENCE_LENGTH; diff --git a/src/menu.cpp b/src/menu.cpp index 532b9f8..a7831dd 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -7,6 +7,7 @@ #include "emuvideo.h" #include "zx80rom.h" #include "zx81rom.h" +#include "zx8x.h" #include "hid_usb.h" #include "display.h" @@ -90,7 +91,7 @@ static bool getFile(char* inout, uint index, bool* direct); static void showModify(PositionF6_T pos, ModifyF6_T* modify); static void showRestart(PositionF7_T pos, RestartF7_T* restart); static void showReboot(FiveSevenSix_T mode); -static void showSave(const uint8_t* name, uint len, uint cursor, uint col, uint row); +static void showSave(const char* name, uint len, uint cursor, uint col, uint row); static void setConvert(bool zx80); static bool wasBlank = false; @@ -298,7 +299,7 @@ bool loadMenu(void) #define MAX_SAVE 30 #define ROW_SPACING 12 -bool saveMenu(uint8_t* save, uint length) +bool saveMenu(char* save, uint length, bool zx80) { uint8_t key = 0; uint8_t detected = 0; @@ -343,8 +344,16 @@ bool saveMenu(uint8_t* save, uint length) } else { - writeString("Save", col - 2, row - ROW_SPACING - 1); - writeString("====", col - 2, row - ROW_SPACING); + if (zx80) + { + writeString("Save ZX80", col - 5, row - ROW_SPACING - 1); + writeString("=========", col - 5, row - ROW_SPACING); + } + else + { + writeString("Save Snapshot", col - 7, row - ROW_SPACING - 1); + writeString("=============", col - 7, row - ROW_SPACING); + } showSave(save, len, cursor, col - (MAX_SAVE >> 1), row + ROW_SPACING); @@ -1056,14 +1065,40 @@ void rebootMenu(void) } while ((key != HID_KEY_ENTER) && (key != HID_KEY_ESCAPE)); debounceExit(key == HID_KEY_ENTER); - endMenu(false); + // Check here to avoid briefly displaying old screen before reboot if (key == HID_KEY_ENTER) { if (initialMode != mode) { // Write file and reboot - emu_SetRebootMode(mode); + emu_SetRebootMode(mode, NULL, NULL); + } + } + endMenu(false); +} + +// Save a snapshot (f9) +void snapMenu(void) +{ + char* snap_path = z8x_getFilenameDirectory(); + int path_len = strlen(snap_path); + + if (saveMenu(&snap_path[path_len], MAX_SAVE, false)) + { + // Check for extension and add .s if not present + if (strlen(&snap_path[path_len])) + { + if (!emu_EndsWith(snap_path, ".s")) + { + strcat(snap_path, ".s"); + } + + // Write the data + if (!emu_saveSnapshot(snap_path)) + { + printf("emu_saveSnapshot failed\n"); + } } } } @@ -1316,7 +1351,7 @@ static void showReboot(FiveSevenSix_T mode) #endif } -static void showSave(const uint8_t* name, uint len, uint cursor, uint col, uint row) +static void showSave(const char* name, uint len, uint cursor, uint col, uint row) { for (uint i=0; i= first) && (count < (first + fullrow))) { @@ -1663,7 +1698,7 @@ static bool getFile(char* inout, uint index, bool* direct) if ((strlen(fno.fname) < MAX_FILENAME_LEN) && ((emu_EndsWith(fno.fname, ".o") || emu_EndsWith(fno.fname, ".p") || emu_EndsWith(fno.fname, ".80") || emu_EndsWith(fno.fname, ".81")) || - emu_EndsWith(fno.fname, ".p81") || allfiles)) + emu_EndsWith(fno.fname, ".p81") || emu_EndsWith(fno.fname, ".s") || allfiles)) { ++count; } diff --git a/src/menu.h b/src/menu.h index fc75936..d00e8b7 100644 --- a/src/menu.h +++ b/src/menu.h @@ -11,7 +11,8 @@ extern void pauseMenu(void); extern bool modifyMenu(void); extern bool restartMenu(void); extern void rebootMenu(void); -extern bool saveMenu(uint8_t* save, uint length); +extern void snapMenu(void); +extern bool saveMenu(char* save, uint length, bool zx80); #ifdef __cplusplus } #endif diff --git a/src/pico81.cpp b/src/pico81.cpp index 0af7211..b48a5f8 100755 --- a/src/pico81.cpp +++ b/src/pico81.cpp @@ -119,9 +119,14 @@ static void mainLoop(void) break; case HID_KEY_F8: + // May not return from this call rebootMenu(); - // Never return from this call break; + + case HID_KEY_F9: + snapMenu(); + break; + } } } diff --git a/src/sound.c b/src/sound.c index 1bbf505..be0f037 100644 --- a/src/sound.c +++ b/src/sound.c @@ -46,6 +46,7 @@ #include "sound.h" #include "z80.h" #include "iopins.h" +#include "emusnap.h" /* configuration */ /* Always generate two channels, even if mono */ @@ -53,7 +54,7 @@ int sound_enabled=0; int sound_stereo_acb=0; /* 1 for ACB stereo, else 0 */ -/* sound_type is in common.c */ +/* sound_type is in z80.c */ #define AY_CLOCK_QUICKSILVA (3250000>>2) @@ -426,11 +427,12 @@ static void sound_beeper_reset(void) beeper_tick_incr=(1<<24)/SAMPLE_FREQ; } +static int rng=1; +static int noise_toggle=1; +static int env_level=0; + static void __not_in_flash_func(sound_ay_overlay)(int16_t* buff) { - static int rng=1; - static int noise_toggle=1; - static int env_level=0; int tone_level[3]; int mixer,envshape; int f,g,level; @@ -589,3 +591,72 @@ static void __not_in_flash_func(sound_ay_overlay)(int16_t* buff) } } } + +bool sound_save_snap(void) +{ + if (!emu_FileWriteBytes(&sound_enabled, sizeof(sound_enabled))) return false; + if (!emu_FileWriteBytes(&sound_stereo_acb, sizeof(sound_stereo_acb))) return false; + if (!emu_FileWriteBytes(&beeper_tick, sizeof(beeper_tick))) return false; + if (!emu_FileWriteBytes(&beeper_tick_incr, sizeof(beeper_tick_incr))) return false; + if (!emu_FileWriteBytes(&sound_oldpos, sizeof(sound_oldpos))) return false; + if (!emu_FileWriteBytes(&sound_fillpos, sizeof(sound_fillpos))) return false; + if (!emu_FileWriteBytes(&sound_oldval, sizeof(sound_oldval))) return false; + if (!emu_FileWriteBytes(&sound_oldval_orig, sizeof(sound_oldval_orig))) return false; + if (!emu_FileWriteBytes(&beeper_last_subpos, sizeof(beeper_last_subpos))) return false; + if (!emu_FileWriteBytes(&ay_noise_tick, sizeof(ay_noise_tick))) return false; + if (!emu_FileWriteBytes(&ay_env_tick, sizeof(ay_env_tick))) return false; + if (!emu_FileWriteBytes(&ay_env_subcycles, sizeof(ay_env_subcycles))) return false; + if (!emu_FileWriteBytes(&ay_tick_incr, sizeof(ay_tick_incr))) return false; + if (!emu_FileWriteBytes(&ay_noise_period, sizeof(ay_noise_period))) return false; + if (!emu_FileWriteBytes(&ay_env_period, sizeof(ay_env_period))) return false; + if (!emu_FileWriteBytes(&env_held, sizeof(env_held))) return false; + if (!emu_FileWriteBytes(&env_alternating, sizeof(env_alternating))) return false; + if (!emu_FileWriteBytes(&change, sizeof(change))) return false; + if (!emu_FileWriteBytes(&ay_change_count, sizeof(ay_change_count))) return false; + + if (!emu_FileWriteBytes(&rng, sizeof(rng))) return false; + if (!emu_FileWriteBytes(&noise_toggle, sizeof(noise_toggle))) return false; + if (!emu_FileWriteBytes(&env_level, sizeof(env_level))) return false; + + if (!emu_FileWriteBytes(ay_tone_levels, sizeof(uint16_t) * 16)) return false; + if (!emu_FileWriteBytes(ay_tone_tick, sizeof(unsigned int) * 3)) return false; + if (!emu_FileWriteBytes(ay_tone_period, sizeof(unsigned int) * 3)) return false; + if (!emu_FileWriteBytes(sound_ay_registers, sizeof(unsigned char) * 16)) return false; + return true; +} + +bool sound_load_snap(uint32_t version) +{ + (void)version; + + if (!emu_FileReadBytes(&sound_enabled, sizeof(sound_enabled))) return false; + if (!emu_FileReadBytes(&sound_stereo_acb, sizeof(sound_stereo_acb))) return false; + if (!emu_FileReadBytes(&beeper_tick, sizeof(beeper_tick))) return false; + if (!emu_FileReadBytes(&beeper_tick_incr, sizeof(beeper_tick_incr))) return false; + if (!emu_FileReadBytes(&sound_oldpos, sizeof(sound_oldpos))) return false; + if (!emu_FileReadBytes(&sound_fillpos, sizeof(sound_fillpos))) return false; + if (!emu_FileReadBytes(&sound_oldval, sizeof(sound_oldval))) return false; + if (!emu_FileReadBytes(&sound_oldval_orig, sizeof(sound_oldval_orig))) return false; + if (!emu_FileReadBytes(&beeper_last_subpos, sizeof(beeper_last_subpos))) return false; + if (!emu_FileReadBytes(&ay_noise_tick, sizeof(ay_noise_tick))) return false; + if (!emu_FileReadBytes(&ay_env_tick, sizeof(ay_env_tick))) return false; + if (!emu_FileReadBytes(&ay_env_subcycles, sizeof(ay_env_subcycles))) return false; + if (!emu_FileReadBytes(&ay_tick_incr, sizeof(ay_tick_incr))) return false; + if (!emu_FileReadBytes(&ay_noise_period, sizeof(ay_noise_period))) return false; + if (!emu_FileReadBytes(&ay_env_period, sizeof(ay_env_period))) return false; + if (!emu_FileReadBytes(&env_held, sizeof(env_held))) return false; + if (!emu_FileReadBytes(&env_alternating, sizeof(env_alternating))) return false; + if (!emu_FileReadBytes(&change, sizeof(change))) return false; + if (!emu_FileReadBytes(&ay_change_count, sizeof(ay_change_count))) return false; + + if (!emu_FileReadBytes(&rng, sizeof(rng))) return false; + if (!emu_FileReadBytes(&noise_toggle, sizeof(noise_toggle))) return false; + if (!emu_FileReadBytes(&env_level, sizeof(env_level))) return false; + + if (!emu_FileReadBytes(ay_tone_levels, sizeof(uint16_t) * 16)) return false; + if (!emu_FileReadBytes(ay_tone_tick, sizeof(unsigned int) * 3)) return false; + if (!emu_FileReadBytes(ay_tone_period, sizeof(unsigned int) * 3)) return false; + if (!emu_FileReadBytes(sound_ay_registers, sizeof(unsigned char) * 16)) return false; + + return true; +} diff --git a/src/sound.h b/src/sound.h index 921b103..39ffebd 100644 --- a/src/sound.h +++ b/src/sound.h @@ -42,6 +42,8 @@ extern void sound_frame(uint16_t* buff); extern void sound_beeper(int on); extern void sound_change_type(int new_sound_type); +extern bool sound_save_snap(void); +extern bool sound_load_snap(uint32_t version); #ifdef __cplusplus } diff --git a/src/z80.c b/src/z80.c index 0ca125d..1118a66 100644 --- a/src/z80.c +++ b/src/z80.c @@ -23,13 +23,14 @@ #include "pico.h" /* For not in flash */ #include "z80.h" #include +#include "emuapi.h" #include "emuvideo.h" #include "emusound.h" #include "display.h" #define parity(a) (partable[a]) -unsigned char partable[256] = { +unsigned char partable[256] = { // Constant, but want to be in RAM 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, @@ -59,7 +60,6 @@ static unsigned char* scrnbmpc_new = 0; static int vsx = 0; static int vsy = 0; -static int nrmvideo = 1; int ay_reg = 0; int LastInstruction; bool frameNotSync = true; @@ -234,13 +234,12 @@ void resetZ80(void) a1 = f1 = b1 = c1 = d1 = e1 = h1 = l1 = i = iff1 = iff2 = im = r = 0; ixoriy = new_ixoriy = 0; ix= iy = sp = pc = 0; - tstates = radjust = 0; + radjust = 0; intsample = 0; m1cycles = 0; tstates = 0; ts = 0; vsx = vsy = 0; - nrmvideo = 0; RasterX = 0; RasterY = 0; psync = 1; @@ -251,6 +250,7 @@ void resetZ80(void) /* ULA */ NMI_generator = 0; + nmi_pending = 0; hsync_pending = 0; VSYNC_state = HSYNC_state = 0; @@ -1252,3 +1252,248 @@ static void __not_in_flash_func(anyout)(void) } } } + +bool save_snap_z80(void) +{ + if (!emu_FileWriteBytes(&a, sizeof(a))) return false; + if (!emu_FileWriteBytes(&f, sizeof(f))) return false; + if (!emu_FileWriteBytes(&b, sizeof(b))) return false; + if (!emu_FileWriteBytes(&c, sizeof(c))) return false; + if (!emu_FileWriteBytes(&d, sizeof(d))) return false; + if (!emu_FileWriteBytes(&e, sizeof(e))) return false; + if (!emu_FileWriteBytes(&h, sizeof(h))) return false; + if (!emu_FileWriteBytes(&l, sizeof(l))) return false; + + if (!emu_FileWriteBytes(&a1, sizeof(a1))) return false; + if (!emu_FileWriteBytes(&f1, sizeof(f1))) return false; + if (!emu_FileWriteBytes(&b1, sizeof(b1))) return false; + if (!emu_FileWriteBytes(&c1, sizeof(c1))) return false; + if (!emu_FileWriteBytes(&d1, sizeof(d1))) return false; + if (!emu_FileWriteBytes(&e1, sizeof(e1))) return false; + if (!emu_FileWriteBytes(&h1, sizeof(h1))) return false; + if (!emu_FileWriteBytes(&l1, sizeof(l1))) return false; + if (!emu_FileWriteBytes(&i, sizeof(i))) return false; + if (!emu_FileWriteBytes(&iff1, sizeof(iff1))) return false; + if (!emu_FileWriteBytes(&iff2, sizeof(iff2))) return false; + if (!emu_FileWriteBytes(&im, sizeof(im))) return false; + if (!emu_FileWriteBytes(&r, sizeof(r))) return false; + + if (!emu_FileWriteBytes(&ixoriy, sizeof(ixoriy))) return false; + if (!emu_FileWriteBytes(&new_ixoriy, sizeof(new_ixoriy))) return false; + + if (!emu_FileWriteBytes(&ix, sizeof(ix))) return false; + if (!emu_FileWriteBytes(&iy, sizeof(iy))) return false; + if (!emu_FileWriteBytes(&sp, sizeof(sp))) return false; + if (!emu_FileWriteBytes(&pc, sizeof(pc))) return false; + + if (!emu_FileWriteBytes(&radjust, sizeof(radjust))) return false; + if (!emu_FileWriteBytes(&intsample, sizeof(intsample))) return false; + if (!emu_FileWriteBytes(&op, sizeof(op))) return false; + if (!emu_FileWriteBytes(&m1cycles, sizeof(m1cycles))) return false; + + if (!emu_FileWriteBytes(&tstates, sizeof(tstates))) return false; + if (!emu_FileWriteBytes(&ts, sizeof(ts))) return false; + if (!emu_FileWriteBytes(&vsx, sizeof(vsx))) return false; + if (!emu_FileWriteBytes(&vsy, sizeof(vsy))) return false; + + if (!emu_FileWriteBytes(&FRAME_SCAN, sizeof(FRAME_SCAN))) return false; + if (!emu_FileWriteBytes(&VSYNC_TOLERANCEMIN, sizeof(VSYNC_TOLERANCEMIN))) return false; + if (!emu_FileWriteBytes(&VSYNC_TOLERANCEMAX, sizeof(VSYNC_TOLERANCEMAX))) return false; + + if (!emu_FileWriteBytes(&RasterX, sizeof(RasterX))) return false; + if (!emu_FileWriteBytes(&RasterY, sizeof(RasterY))) return false; + if (!emu_FileWriteBytes(&psync, sizeof(psync))) return false; + if (!emu_FileWriteBytes(&sync_len, sizeof(sync_len))) return false; + + if (!emu_FileWriteBytes(&running_rom, sizeof(running_rom))) return false; + if (!emu_FileWriteBytes(&frameNotSync, sizeof(frameNotSync))) return false; + if (!emu_FileWriteBytes(&ay_reg, sizeof(ay_reg))) return false; + if (!emu_FileWriteBytes(&LastInstruction, sizeof(LastInstruction))) return false; + + if (!emu_FileWriteBytes(&NMI_generator, sizeof(NMI_generator))) return false; + if (!emu_FileWriteBytes(&nmi_pending, sizeof(nmi_pending))) return false; + if (!emu_FileWriteBytes(&hsync_pending, sizeof(hsync_pending))) return false; + if (!emu_FileWriteBytes(&VSYNC_state, sizeof(VSYNC_state))) return false; + if (!emu_FileWriteBytes(&HSYNC_state, sizeof(HSYNC_state))) return false; + + if (!emu_FileWriteBytes(&S_RasterX, sizeof(S_RasterX))) return false; + if (!emu_FileWriteBytes(&S_RasterY, sizeof(S_RasterY))) return false; + + if (!emu_FileWriteBytes(&videoFlipFlop1Q, sizeof(videoFlipFlop1Q))) return false; + if (!emu_FileWriteBytes(&videoFlipFlop2Q, sizeof(videoFlipFlop2Q))) return false; + if (!emu_FileWriteBytes(&videoFlipFlop3Q, sizeof(videoFlipFlop3Q))) return false; + if (!emu_FileWriteBytes(&videoFlipFlop3Clear, sizeof(videoFlipFlop3Clear))) return false; + if (!emu_FileWriteBytes(&prevVideoFlipFlop3Q, sizeof(prevVideoFlipFlop3Q))) return false; + + if (!emu_FileWriteBytes(&vsyncFound, sizeof(vsyncFound))) return false; + if (!emu_FileWriteBytes(&lineClockCarryCounter, sizeof(lineClockCarryCounter))) return false; + + if (!emu_FileWriteBytes(&scanline_len, sizeof(scanline_len))) return false; + if (!emu_FileWriteBytes(&sync_type, sizeof(sync_type))) return false; + if (!emu_FileWriteBytes(&sync_len, sizeof(sync_len))) return false; + if (!emu_FileWriteBytes(&nosync_lines, sizeof(nosync_lines))) return false; + + // Process interlace emu_VideoSetInterlace(); + + if (!emu_FileWriteBytes(&chromamode, sizeof(chromamode))) return false; +#ifdef SUPPORT_CHROMA + if (!emu_FileWriteBytes(&bordercolour, sizeof(bordercolour))) return false; + if (!emu_FileWriteBytes(&bordercolournew, sizeof(bordercolournew))) return false; +#else + uint16_t dummy = 0; + if (!emu_FileWriteBytes(&dummy, sizeof(dummy))) return false; +#endif + + if (!emu_FileWriteBytes(&dest, sizeof(dest))) return false; + if (!emu_FileWriteBytes(&adjustStartX, sizeof(adjustStartX))) return false; + if (!emu_FileWriteBytes(&adjustStartY, sizeof(adjustStartY))) return false; + if (!emu_FileWriteBytes(&startX, sizeof(startX))) return false; + if (!emu_FileWriteBytes(&startY, sizeof(startY))) return false; + if (!emu_FileWriteBytes(&syncX, sizeof(syncX))) return false; + if (!emu_FileWriteBytes(&endX, sizeof(endX))) return false; + if (!emu_FileWriteBytes(&endY, sizeof(endY))) return false; + + if (!emu_FileWriteBytes(&psync, sizeof(psync))) return false; + if (!emu_FileWriteBytes(&sync_len, sizeof(sync_len))) return false; + if (!emu_FileWriteBytes(&rowcounter, sizeof(rowcounter))) return false; + if (!emu_FileWriteBytes(&sync_len, sizeof(sync_len))) return false; + if (!emu_FileWriteBytes(&hsync_counter, sizeof(hsync_counter))) return false; + if (!emu_FileWriteBytes(&rowcounter_hold, sizeof(rowcounter_hold))) return false; + + if (!emu_FileWriteBytes(&sound_type, sizeof(sound_type))) return false; + if (!emu_FileWriteBytes(&m1not, sizeof(m1not))) return false; + if (!emu_FileWriteBytes(&useWRX, sizeof(useWRX))) return false; + if (!emu_FileWriteBytes(&UDGEnabled, sizeof(UDGEnabled))) return false; + if (!emu_FileWriteBytes(&useQSUDG, sizeof(useQSUDG))) return false; + if (!emu_FileWriteBytes(&LowRAM, sizeof(LowRAM))) return false; + if (!emu_FileWriteBytes(&chr128, sizeof(chr128))) return false; + if (!emu_FileWriteBytes(&useNTSC, sizeof(useNTSC))) return false; + if (!emu_FileWriteBytes(&frameSync, sizeof(frameSync))) return false; + if (!emu_FileWriteBytes(&running_rom, sizeof(running_rom))) return false; + + if (!emu_FileWriteBytes(&scanlineCounter, sizeof(scanlineCounter))) return false; + + return true; +} + +bool load_snap_z80(uint32_t version) +{ + (void)version; + + if (!emu_FileReadBytes(&a, sizeof(a))) return false; + if (!emu_FileReadBytes(&f, sizeof(f))) return false; + if (!emu_FileReadBytes(&b, sizeof(b))) return false; + if (!emu_FileReadBytes(&c, sizeof(c))) return false; + if (!emu_FileReadBytes(&d, sizeof(d))) return false; + if (!emu_FileReadBytes(&e, sizeof(e))) return false; + if (!emu_FileReadBytes(&h, sizeof(h))) return false; + if (!emu_FileReadBytes(&l, sizeof(l))) return false; + + if (!emu_FileReadBytes(&a1, sizeof(a1))) return false; + if (!emu_FileReadBytes(&f1, sizeof(f1))) return false; + if (!emu_FileReadBytes(&b1, sizeof(b1))) return false; + if (!emu_FileReadBytes(&c1, sizeof(c1))) return false; + if (!emu_FileReadBytes(&d1, sizeof(d1))) return false; + if (!emu_FileReadBytes(&e1, sizeof(e1))) return false; + if (!emu_FileReadBytes(&h1, sizeof(h1))) return false; + if (!emu_FileReadBytes(&l1, sizeof(l1))) return false; + if (!emu_FileReadBytes(&i, sizeof(i))) return false; + if (!emu_FileReadBytes(&iff1, sizeof(iff1))) return false; + if (!emu_FileReadBytes(&iff2, sizeof(iff2))) return false; + if (!emu_FileReadBytes(&im, sizeof(im))) return false; + if (!emu_FileReadBytes(&r, sizeof(r))) return false; + + if (!emu_FileReadBytes(&ixoriy, sizeof(ixoriy))) return false; + if (!emu_FileReadBytes(&new_ixoriy, sizeof(new_ixoriy))) return false; + + if (!emu_FileReadBytes(&ix, sizeof(ix))) return false; + if (!emu_FileReadBytes(&iy, sizeof(iy))) return false; + if (!emu_FileReadBytes(&sp, sizeof(sp))) return false; + if (!emu_FileReadBytes(&pc, sizeof(pc))) return false; + + if (!emu_FileReadBytes(&radjust, sizeof(radjust))) return false; + if (!emu_FileReadBytes(&intsample, sizeof(intsample))) return false; + if (!emu_FileReadBytes(&op, sizeof(op))) return false; + if (!emu_FileReadBytes(&m1cycles, sizeof(m1cycles))) return false; + + if (!emu_FileReadBytes(&tstates, sizeof(tstates))) return false; + if (!emu_FileReadBytes(&ts, sizeof(ts))) return false; + if (!emu_FileReadBytes(&vsx, sizeof(vsx))) return false; + if (!emu_FileReadBytes(&vsy, sizeof(vsy))) return false; + + if (!emu_FileReadBytes(&FRAME_SCAN, sizeof(FRAME_SCAN))) return false; + if (!emu_FileReadBytes(&VSYNC_TOLERANCEMIN, sizeof(VSYNC_TOLERANCEMIN))) return false; + if (!emu_FileReadBytes(&VSYNC_TOLERANCEMAX, sizeof(VSYNC_TOLERANCEMAX))) return false; + + if (!emu_FileReadBytes(&RasterX, sizeof(RasterX))) return false; + if (!emu_FileReadBytes(&RasterY, sizeof(RasterY))) return false; + if (!emu_FileReadBytes(&psync, sizeof(psync))) return false; + if (!emu_FileReadBytes(&sync_len, sizeof(sync_len))) return false; + + if (!emu_FileReadBytes(&running_rom, sizeof(running_rom))) return false; + if (!emu_FileReadBytes(&frameNotSync, sizeof(frameNotSync))) return false; + if (!emu_FileReadBytes(&ay_reg, sizeof(ay_reg))) return false; + if (!emu_FileReadBytes(&LastInstruction, sizeof(LastInstruction))) return false; + + if (!emu_FileReadBytes(&NMI_generator, sizeof(NMI_generator))) return false; + if (!emu_FileReadBytes(&nmi_pending, sizeof(nmi_pending))) return false; + if (!emu_FileReadBytes(&hsync_pending, sizeof(hsync_pending))) return false; + if (!emu_FileReadBytes(&VSYNC_state, sizeof(VSYNC_state))) return false; + if (!emu_FileReadBytes(&HSYNC_state, sizeof(HSYNC_state))) return false; + + if (!emu_FileReadBytes(&S_RasterX, sizeof(S_RasterX))) return false; + if (!emu_FileReadBytes(&S_RasterY, sizeof(S_RasterY))) return false; + + if (!emu_FileReadBytes(&videoFlipFlop1Q, sizeof(videoFlipFlop1Q))) return false; + if (!emu_FileReadBytes(&videoFlipFlop2Q, sizeof(videoFlipFlop2Q))) return false; + if (!emu_FileReadBytes(&videoFlipFlop3Q, sizeof(videoFlipFlop3Q))) return false; + if (!emu_FileReadBytes(&videoFlipFlop3Clear, sizeof(videoFlipFlop3Clear))) return false; + if (!emu_FileReadBytes(&prevVideoFlipFlop3Q, sizeof(prevVideoFlipFlop3Q))) return false; + + if (!emu_FileReadBytes(&vsyncFound, sizeof(vsyncFound))) return false; + if (!emu_FileReadBytes(&lineClockCarryCounter, sizeof(lineClockCarryCounter))) return false; + + if (!emu_FileReadBytes(&scanline_len, sizeof(scanline_len))) return false; + if (!emu_FileReadBytes(&sync_type, sizeof(sync_type))) return false; + if (!emu_FileReadBytes(&sync_len, sizeof(sync_len))) return false; + if (!emu_FileReadBytes(&nosync_lines, sizeof(nosync_lines))) return false; + + if (!emu_FileReadBytes(&chromamode, sizeof(chromamode))) return false; +#ifdef SUPPORT_CHROMA + if (!emu_FileReadBytes(&bordercolour, sizeof(bordercolour))) return false; + if (!emu_FileReadBytes(&bordercolournew, sizeof(bordercolournew))) return false; +#else + uint16_t dummy = 0; + if (!emu_FileReadBytes(&dummy, sizeof(dummy))) return false; +#endif + + if (!emu_FileReadBytes(&dest, sizeof(dest))) return false; + if (!emu_FileReadBytes(&adjustStartX, sizeof(adjustStartX))) return false; + if (!emu_FileReadBytes(&adjustStartY, sizeof(adjustStartY))) return false; + if (!emu_FileReadBytes(&startX, sizeof(startX))) return false; + if (!emu_FileReadBytes(&startY, sizeof(startY))) return false; + if (!emu_FileReadBytes(&syncX, sizeof(syncX))) return false; + if (!emu_FileReadBytes(&endX, sizeof(endX))) return false; + if (!emu_FileReadBytes(&endY, sizeof(endY))) return false; + + if (!emu_FileReadBytes(&psync, sizeof(psync))) return false; + if (!emu_FileReadBytes(&sync_len, sizeof(sync_len))) return false; + if (!emu_FileReadBytes(&rowcounter, sizeof(rowcounter))) return false; + if (!emu_FileReadBytes(&sync_len, sizeof(sync_len))) return false; + if (!emu_FileReadBytes(&hsync_counter, sizeof(hsync_counter))) return false; + if (!emu_FileReadBytes(&rowcounter_hold, sizeof(rowcounter_hold))) return false; + + if (!emu_FileReadBytes(&sound_type, sizeof(sound_type))) return false; + if (!emu_FileReadBytes(&m1not, sizeof(m1not))) return false; + if (!emu_FileReadBytes(&useWRX, sizeof(useWRX))) return false; + if (!emu_FileReadBytes(&UDGEnabled, sizeof(UDGEnabled))) return false; + if (!emu_FileReadBytes(&useQSUDG, sizeof(useQSUDG))) return false; + if (!emu_FileReadBytes(&LowRAM, sizeof(LowRAM))) return false; + if (!emu_FileReadBytes(&chr128, sizeof(chr128))) return false; + if (!emu_FileReadBytes(&useNTSC, sizeof(useNTSC))) return false; + if (!emu_FileReadBytes(&frameSync, sizeof(frameSync))) return false; + if (!emu_FileReadBytes(&running_rom, sizeof(running_rom))) return false; + + if (!emu_FileReadBytes(&scanlineCounter, sizeof(scanlineCounter))) return false; + return true; +} diff --git a/src/z80.h b/src/z80.h index 599f737..69b2e66 100644 --- a/src/z80.h +++ b/src/z80.h @@ -39,6 +39,7 @@ extern void execZX80(void); extern void setDisplayBoundaries(void); extern void setEmulatedTV(bool fiftyHz, uint16_t vtol); + #ifdef SUPPORT_CHROMA void adjustChroma(bool start); #endif diff --git a/src/zx8x.c b/src/zx8x.c index 08a4786..598bb54 100755 --- a/src/zx8x.c +++ b/src/zx8x.c @@ -31,8 +31,6 @@ bool parseNumber(const char* input, byte mem[MEMORYRAM_SIZE]; unsigned char *memptr[64]; int memattr[64]; -int nmigen=0,hsyncgen=0,vsync=0; -int signal_int_flag=0; int ramsize=16; /* the keyboard state and other */ @@ -49,6 +47,7 @@ unsigned char chroma_set=0; #endif static bool resetRequired = false; +static bool load_snap = false; /* the ZX81 char is used to index into this, to give the ascii. * awkward chars are mapped to '_' (underscore), and the ZX81's @@ -68,6 +67,8 @@ static char zx2ascii[64]={ static char tapename[64]={0}; +static void set_mem_attribute_and_ptr(void); + unsigned int __not_in_flash_func(in)(int h, int l) { if ((h == 0x7f) && (l == 0xef)) @@ -219,6 +220,12 @@ void __not_in_flash_func(out)(int h, int l, int a) static char fname[256]; +char* z8x_getFilenameDirectory(void) +{ + strcpy(fname, emu_GetDirectory()); + return fname; +} + bool load_p(int name_addr, bool defer_rom) { int max_read; @@ -349,9 +356,16 @@ bool load_p(int name_addr, bool defer_rom) // so adjust, if file was not picked from menu if (!from_menu) { + // Extract the directory char* nameStrt = strrchr(fname, '/'); + + // Temporarily null terminate to isolate the directory if (nameStrt) *nameStrt = 0; - emu_SetDirectory(nameStrt ? fname : "/"); + + // Set the directory,allowing for case when in root directory + emu_SetDirectory(nameStrt ? fname : ""); + + // Restore the original full path and name if (nameStrt) *nameStrt = '/'; } // Load the settings for this file @@ -528,7 +542,7 @@ bool save_p(int name_addr, bool defer_rom) // Display the ZX80 save screen uint length = strlen(fname); - if (!saveMenu((uint8_t*)(&fname[length]), sizeof(fname) - length - 1)) + if (!saveMenu(&fname[length], sizeof(fname) - length - 1, true)) { // A default name strcat(fname,"zx80prog.o"); @@ -682,13 +696,8 @@ void rom4kPatches() #endif } -static void initmem() +static void initmem(void) { - int f; - int count; - int ramtmp = ramsize; - bool odd=false; - if(rom4k) { memset(mem+0x1000,0,0xf000); @@ -697,9 +706,17 @@ static void initmem() { memset(mem+0x2000,0,0xe000); } + set_mem_attribute_and_ptr(); +} + +static void set_mem_attribute_and_ptr(void) +{ + int f; + int ramtmp = ramsize; + bool odd = false; + int count = 0; /* ROM setup */ - count=0; for(f=0;f<16;f++) { memattr[f]=memattr[32+f]=0; @@ -804,6 +821,14 @@ static void initmem() void z8x_Init(void) { + // Load the specific data for a snapshot + if (load_snap) + { + strcpy(fname, emu_GetDirectory()); + strcat(fname, tapename); + emu_loadSnapshotSpecific(tapename, fname); + } + // Get machine type and memory zx80 = emu_ZX80Requested(); rom4k = emu_ROM4KRequested(); @@ -851,6 +876,12 @@ void z8x_Init(void) resetZ80(); emu_sndInit(sound_type != SOUND_TYPE_NONE, true); + + if (load_snap) + { + emu_loadSnapshotData(fname); + load_snap = false; + } } void z8x_updateValues(void) @@ -889,6 +920,7 @@ void z8x_Start(const char * filename) char c; autoload = 0; + load_snap = false; if (filename) { @@ -900,30 +932,37 @@ void z8x_Start(const char * filename) strcpy(fname, emu_GetDirectory()); strcat(fname, tapename); - EMU_LOCK_SDCARD - - if (emu_FileOpen(fname, "r+b")) + if (emu_EndsWith(tapename, ".s")) { - int fsize = emu_FileRead(&c, 1, 0); - if (!fsize) - { - printf("Read %s failed. No autoload\n", filename); - } - else + load_snap = true; + } + else + { + EMU_LOCK_SDCARD + + if (emu_FileOpen(fname, "r+b")) { - autoload = 1; + int fsize = emu_FileRead(&c, 1, 0); + if (!fsize) + { + printf("Read %s failed. No autoload\n", filename); + } + else + { + autoload = 1; - // Read the new specific values - emu_ReadSpecificValues(fname); + // Read the new specific values + emu_ReadSpecificValues(fname); - // Determine the computer type from the ending - do not change for .p81 - if (!emu_EndsWith(fname, ".p81")) - { - emu_SetRom4K(emu_EndsWith(fname, ".o") || emu_EndsWith(fname, ".80")); + // Determine the computer type from the ending - do not change for .p81 + if (!emu_EndsWith(fname, ".p81")) + { + emu_SetRom4K(emu_EndsWith(fname, ".o") || emu_EndsWith(fname, ".80")); + } } + emu_FileClose(); + EMU_UNLOCK_SDCARD } - emu_FileClose(); - EMU_UNLOCK_SDCARD } } } @@ -996,3 +1035,56 @@ char *strzx80_to_ascii(int memaddr) return translated; } + +bool save_snap_zx8x(void) +{ + if (!emu_FileWriteBytes(mem, sizeof(byte) * MEMORYRAM_SIZE)) return false; + // memptr and memattr recreated when loaded + + if (!emu_FileWriteBytes(keyboard, sizeof(uint8_t) * 8)) return false; + if (!emu_FileWriteBytes(&ramsize, sizeof(ramsize))) return false; + if (!emu_FileWriteBytes(&zx80, sizeof(zx80))) return false; + if (!emu_FileWriteBytes(&rom4k, sizeof(rom4k))) return false; + if (!emu_FileWriteBytes(&autoload, sizeof(autoload))) return false; + if (!emu_FileWriteBytes(&chromamode, sizeof(chromamode))) return false; +#ifdef SUPPORT_CHROMA + if (!emu_FileWriteBytes(&bordercolour, sizeof(bordercolour))) return false; + if (!emu_FileWriteBytes(&bordercolournew, sizeof(bordercolournew))) return false; + if (!emu_FileWriteBytes(&fullcolour, sizeof(fullcolour))) return false; + if (!emu_FileWriteBytes(&chroma_set, sizeof(chroma_set))) return false; +#else + uint32_t dummy = 0; + if (!emu_FileWriteBytes(&dummy, sizeof(dummy))) return false; +#endif + if (!emu_FileWriteBytes(&resetRequired, sizeof(resetRequired))) return false; + + return true; +} + +bool load_snap_zx8x(uint32_t version) +{ + (void) version; + + if (!emu_FileReadBytes(mem, sizeof(byte) * MEMORYRAM_SIZE)) return false; + // memptr and memattr recreated when loaded + + if (!emu_FileReadBytes(keyboard, sizeof(uint8_t) * 8)) return false; + if (!emu_FileReadBytes(&ramsize, sizeof(ramsize))) return false; + if (!emu_FileReadBytes(&zx80, sizeof(zx80))) return false; + if (!emu_FileReadBytes(&rom4k, sizeof(rom4k))) return false; + if (!emu_FileReadBytes(&autoload, sizeof(autoload))) return false; + if (!emu_FileReadBytes(&chromamode, sizeof(chromamode))) return false; +#ifdef SUPPORT_CHROMA + if (!emu_FileReadBytes(&bordercolour, sizeof(bordercolour))) return false; + if (!emu_FileReadBytes(&bordercolournew, sizeof(bordercolournew))) return false; + if (!emu_FileReadBytes(&fullcolour, sizeof(fullcolour))) return false; + if (!emu_FileReadBytes(&chroma_set, sizeof(chroma_set))) return false; +#else + uint32_t dummy = 0; + if (!emu_FileReadBytes(&dummy, sizeof(dummy))) return false; +#endif + if (!emu_FileReadBytes(&resetRequired, sizeof(resetRequired))) return false; + + set_mem_attribute_and_ptr(); + return true; +} diff --git a/src/zx8x.h b/src/zx8x.h index 25d8120..193c1e1 100644 --- a/src/zx8x.h +++ b/src/zx8x.h @@ -9,6 +9,7 @@ extern void z8x_Init(void); extern bool z8x_Step(void); extern void z8x_Start(const char * filename); extern void z8x_updateValues(void); +extern char* z8x_getFilenameDirectory(void); #ifdef __cplusplus } diff --git a/uf2/picozx81_dvi_hdmi_sound_rp2040.uf2 b/uf2/picozx81_dvi_hdmi_sound_rp2040.uf2 index 331bfa5..15783e7 100644 Binary files a/uf2/picozx81_dvi_hdmi_sound_rp2040.uf2 and b/uf2/picozx81_dvi_hdmi_sound_rp2040.uf2 differ diff --git a/uf2/picozx81_dvi_hdmi_sound_rp2350.uf2 b/uf2/picozx81_dvi_hdmi_sound_rp2350.uf2 index 06723fe..ace9acc 100644 Binary files a/uf2/picozx81_dvi_hdmi_sound_rp2350.uf2 and b/uf2/picozx81_dvi_hdmi_sound_rp2350.uf2 differ diff --git a/uf2/picozx81_dvi_rp2040.uf2 b/uf2/picozx81_dvi_rp2040.uf2 index 79a5ece..aef9c15 100644 Binary files a/uf2/picozx81_dvi_rp2040.uf2 and b/uf2/picozx81_dvi_rp2040.uf2 differ diff --git a/uf2/picozx81_dvi_rp2350.uf2 b/uf2/picozx81_dvi_rp2350.uf2 index 986af55..eec11f7 100644 Binary files a/uf2/picozx81_dvi_rp2350.uf2 and b/uf2/picozx81_dvi_rp2350.uf2 differ diff --git a/uf2/picozx81_lcdmaker_rp2040.uf2 b/uf2/picozx81_lcdmaker_rp2040.uf2 index 7e82caa..e351678 100644 Binary files a/uf2/picozx81_lcdmaker_rp2040.uf2 and b/uf2/picozx81_lcdmaker_rp2040.uf2 differ diff --git a/uf2/picozx81_lcdws28_rp2040.uf2 b/uf2/picozx81_lcdws28_rp2040.uf2 index 638c212..363cf2a 100644 Binary files a/uf2/picozx81_lcdws28_rp2040.uf2 and b/uf2/picozx81_lcdws28_rp2040.uf2 differ diff --git a/uf2/picozx81_lcdws28_rp2350.uf2 b/uf2/picozx81_lcdws28_rp2350.uf2 index 48402ca..d6be68c 100644 Binary files a/uf2/picozx81_lcdws28_rp2350.uf2 and b/uf2/picozx81_lcdws28_rp2350.uf2 differ diff --git a/uf2/picozx81_olimexpc_hdmi_sound_rp2040.uf2 b/uf2/picozx81_olimexpc_hdmi_sound_rp2040.uf2 index ae66460..333f26a 100644 Binary files a/uf2/picozx81_olimexpc_hdmi_sound_rp2040.uf2 and b/uf2/picozx81_olimexpc_hdmi_sound_rp2040.uf2 differ diff --git a/uf2/picozx81_olimexpc_hdmi_sound_rp2350.uf2 b/uf2/picozx81_olimexpc_hdmi_sound_rp2350.uf2 index 618c2aa..77dbb15 100644 Binary files a/uf2/picozx81_olimexpc_hdmi_sound_rp2350.uf2 and b/uf2/picozx81_olimexpc_hdmi_sound_rp2350.uf2 differ diff --git a/uf2/picozx81_olimexpc_rp2040.uf2 b/uf2/picozx81_olimexpc_rp2040.uf2 index 8f34c5d..2f3f353 100644 Binary files a/uf2/picozx81_olimexpc_rp2040.uf2 and b/uf2/picozx81_olimexpc_rp2040.uf2 differ diff --git a/uf2/picozx81_olimexpc_rp2350.uf2 b/uf2/picozx81_olimexpc_rp2350.uf2 index 80b7dfd..8baec72 100644 Binary files a/uf2/picozx81_olimexpc_rp2350.uf2 and b/uf2/picozx81_olimexpc_rp2350.uf2 differ diff --git a/uf2/picozx81_picomitevga_rp2040.uf2 b/uf2/picozx81_picomitevga_rp2040.uf2 index fe920fb..15cf039 100644 Binary files a/uf2/picozx81_picomitevga_rp2040.uf2 and b/uf2/picozx81_picomitevga_rp2040.uf2 differ diff --git a/uf2/picozx81_picomitevga_rp2350.uf2 b/uf2/picozx81_picomitevga_rp2350.uf2 index 1eee667..e7fba0e 100644 Binary files a/uf2/picozx81_picomitevga_rp2350.uf2 and b/uf2/picozx81_picomitevga_rp2350.uf2 differ diff --git a/uf2/picozx81_picozx_lcd_rp2040.uf2 b/uf2/picozx81_picozx_lcd_rp2040.uf2 index ba47ecc..670b8d7 100644 Binary files a/uf2/picozx81_picozx_lcd_rp2040.uf2 and b/uf2/picozx81_picozx_lcd_rp2040.uf2 differ diff --git a/uf2/picozx81_picozx_rp2040.uf2 b/uf2/picozx81_picozx_rp2040.uf2 index acc6148..91e50b9 100644 Binary files a/uf2/picozx81_picozx_rp2040.uf2 and b/uf2/picozx81_picozx_rp2040.uf2 differ diff --git a/uf2/picozx81_picozxreal_rp2040.uf2 b/uf2/picozx81_picozxreal_rp2040.uf2 index fa5f1cb..86c2da6 100644 Binary files a/uf2/picozx81_picozxreal_rp2040.uf2 and b/uf2/picozx81_picozxreal_rp2040.uf2 differ diff --git a/uf2/picozx81_vga332_rp2040.uf2 b/uf2/picozx81_vga332_rp2040.uf2 index 4719834..b697506 100644 Binary files a/uf2/picozx81_vga332_rp2040.uf2 and b/uf2/picozx81_vga332_rp2040.uf2 differ diff --git a/uf2/picozx81_vga332_rp2350.uf2 b/uf2/picozx81_vga332_rp2350.uf2 index f2e5a52..f11e23f 100644 Binary files a/uf2/picozx81_vga332_rp2350.uf2 and b/uf2/picozx81_vga332_rp2350.uf2 differ diff --git a/uf2/picozx81_vga_rp2040.uf2 b/uf2/picozx81_vga_rp2040.uf2 index 05ebfcb..5cf0145 100644 Binary files a/uf2/picozx81_vga_rp2040.uf2 and b/uf2/picozx81_vga_rp2040.uf2 differ diff --git a/uf2/picozx81_vga_rp2350.uf2 b/uf2/picozx81_vga_rp2350.uf2 index a88e011..8e20e21 100644 Binary files a/uf2/picozx81_vga_rp2350.uf2 and b/uf2/picozx81_vga_rp2350.uf2 differ diff --git a/uf2/picozx81_vgamaker222c_rp2040.uf2 b/uf2/picozx81_vgamaker222c_rp2040.uf2 index 0160090..9ab52cb 100644 Binary files a/uf2/picozx81_vgamaker222c_rp2040.uf2 and b/uf2/picozx81_vgamaker222c_rp2040.uf2 differ diff --git a/uf2/picozx81_wspizero_hdmi_sound_rp2040.uf2 b/uf2/picozx81_wspizero_hdmi_sound_rp2040.uf2 index 0aac318..48ab506 100644 Binary files a/uf2/picozx81_wspizero_hdmi_sound_rp2040.uf2 and b/uf2/picozx81_wspizero_hdmi_sound_rp2040.uf2 differ