From a5745813e442b66ae6eed30bba81d1b3dd5cf634 Mon Sep 17 00:00:00 2001 From: "Juan J. Martinez" Date: Sat, 17 Apr 2021 22:05:24 +0100 Subject: Initial public release --- .gitignore | 10 + COPYING | 20 + Makefile | 76 ++ README.md | 77 +++ example/test.bin | Bin 0 -> 20 bytes example/test.h | 22 + example/test.sfx | 5 + main.cpp | 414 +++++++++++ player.h | 39 ++ player/Makefile | 20 + player/README.md | 13 + player/bin2h.py | 40 ++ player/player.z80 | 93 +++ sdcc/Makefile | 13 + sdcc/README.MD | 23 + sdcc/beeper.h | 49 ++ sdcc/beeper.z80 | 158 +++++ sfx.c | 356 ++++++++++ sfx.h | 46 ++ zymosis.c | 1985 +++++++++++++++++++++++++++++++++++++++++++++++++++++ zymosis.h | 214 ++++++ 21 files changed, 3673 insertions(+) create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README.md create mode 100644 example/test.bin create mode 100644 example/test.h create mode 100644 example/test.sfx create mode 100644 main.cpp create mode 100644 player.h create mode 100644 player/Makefile create mode 100644 player/README.md create mode 100755 player/bin2h.py create mode 100644 player/player.z80 create mode 100644 sdcc/Makefile create mode 100644 sdcc/README.MD create mode 100644 sdcc/beeper.h create mode 100644 sdcc/beeper.z80 create mode 100644 sfx.c create mode 100644 sfx.h create mode 100644 zymosis.c create mode 100644 zymosis.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..983f42d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.o +*.swp +*~ +*.rel +*.opt +*.bin +*.ap +imgui.ini +sfxed +Makefile.deps diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..47867bc --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ +Beeper engine +Copyright (C) 2021 by Juan J. Martinez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1f5add0 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +# CONFIG +IMGUI_DIR = $(HOME)/src/imgui +IMGUI_FILE_DIALOG_DIR = $(HOME)/src/ImGuiFileDialog +# END OF CONFIG + +TAG := $(shell git describe --abbrev=0 --tags ${TAG_COMMIT} 2>/dev/null || true) +COMMIT := $(shell git rev-parse --short HEAD) +DATE := $(shell git log -1 --format=%cd --date=format:"%Y%m%d") +VERSION := $(TAG) + +ifeq ($(VERSION),) + VERSION := dev-$(COMMIT) ($(DATE)) +else + VERSION += ($(DATE)) +endif + +CFLAGS = -O2 -s -Wall `sdl2-config --cflags` +CXXFLAGS = -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends -I$(IMGUI_FILE_DIALOG_DIR) +CXXFLAGS += -O2 -s -Wall `sdl2-config --cflags` -DAPP_VERSION="\"$(VERSION)\"" +LIBS = -lGL -ldl `sdl2-config --libs` + +# cross-build for windows +ifeq ($(CROSS_BUILD), Win) #LINUX + CC = i686-w64-mingw32-gcc + CXX = i686-w64-mingw32-g++ + CFLAGS += -D__USE_MINGW_ANSI_STDIO + LIBS = -lgdi32 -lopengl32 -limm32 `sdl2-config --libs --static-libs` -static +endif + +SOURCES = main.cpp sfx.c +SOURCES += zymosis.c +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_sdl.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp +SOURCES += $(IMGUI_FILE_DIALOG_DIR)/ImGuiFileDialog.cpp + +SOURCES += $(IMGUI_DIR)/examples/libs/gl3w/GL/gl3w.c +CXXFLAGS += -I$(IMGUI_DIR)/examples/libs/gl3w -DIMGUI_IMPL_OPENGL_LOADER_GL3W + +BIN = sfxed +OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) + +all: $(BIN) + +%.o: %.c + $(CC) $(CFLAGS) $< -c -o $@ + +%.o: %.cpp + $(CXX) $(CXXFLAGS) $< -c -o $@ + +%.o:$(IMGUI_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_FILE_DIALOG_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/backends/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/examples/libs/gl3w/GL/%.c + $(CC) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/examples/libs/glad/src/%.c + $(CC) $(CXXFLAGS) -c -o $@ $< + +sfxed: $(OBJS) + $(CXX) $(OBJS) $(CXXFLAGS) $(LIBS) -o $@ + +.PHONY: clean +clean: + make -C player clean + rm -f $(BIN) $(OBJS) Makefile.deps + +Makefile.deps: + $(CXX) $(CXXFLAGS) -MM $(SOURCES) > Makefile.deps + +include Makefile.deps diff --git a/README.md b/README.md new file mode 100644 index 0000000..71d82e2 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# Beeper engine + +This is simple beeper engine for the ZX Spectrum 48K (or later), designed to be +run from an interrupt handler. + +This project was inspired by the works of Steve Turner (of Graftgold fame) and +Shiru. I made this for myself (to be used in my games), and I'm releasing it +in case it is useful to someone else. + +The player supports three types of effects: + +* Silence (stops the sound) +* Tone (square wave) +* Noise (random) + +Tone and noise require: + +* the number of frames (ints) to play the effect +* the frequency; the higher the value, the more time the effect will use in the int +* an optional slide to apply to the frequency +* the next sound to play in a chain, or zero to stop (silence) + +It is possible to chain multiple sounds and have a loop. To break the loop, +just play "silence" (queue effect 0) or another effect out of the loop. + +The beeper library is documented in `sdcc/beeper.h`, and it provides three +functions: + +* `beeper_init` to set the effect data and initialize the engine +* `beeper_queue` to schedule an effect to be played starting on next interrupt + (0 for silence, the effects start on 1) +* `beeper_play` to be called from the interrupt handler on each interrupt + +The engine comes with a simple GUI editor (`sfxed`) to design the effects on a PC. + +There are some binaries on this website: [https://github.com/reidrac/beeper-int-zx](https://github.com/reidrac/beeper-int-zx) + +`sfxed` uses the Zymosis Z80 CPU emulation engine to execute the player and +collect audio samples to be played using SDL. The sound emulation should be +accurate enough! + +The GUI is built with ImGui. + +The editor can export the effect data in binary form, so it can be included in +any assembler project, and as a C include file (for example to be used with +SDCC). + +Check the `example` directory for an example. + +## Building the player + +Check the README file in `sdcc` directory. + +The code is provided in assembler for SDCC, to be built as a library. It should +be easay to convert to other assemblers. + +## Building the editor + +Edit the `Makefile` and change the following variables to point to the corerct directory: + +- `IMGUI_DIR`: a checkout of ImGui; see: https://github.com/ocornut/imgui +- `IMGUI_FILE_DIALOG_DIR`: a checkout of ImGuiFileDialog "Lib_Only"; see: https://github.com/aiekick/ImGuiFileDialog + +To build on Linux, you'll need: +- GNU Make, GCC +- SDL2 for development (e.g. libsdl2-dev in Debian) + +When all the requirements are satisfied, just run `make`. + +## License + +This software is distributed under MIT license. See COPYING file. + +**TL;DR**: the only condition is that you are required to preserve the copyright +and license notices. Licensed works, modifications, and larger works may be +distributed under different terms and without source code; this includes any game +made with the help of this software. diff --git a/example/test.bin b/example/test.bin new file mode 100644 index 0000000..e906a2e Binary files /dev/null and b/example/test.bin differ diff --git a/example/test.h b/example/test.h new file mode 100644 index 0000000..d59e2b9 --- /dev/null +++ b/example/test.h @@ -0,0 +1,22 @@ +#ifndef _SFX_H +#define _SFX_H + +enum sfx_enum { + // laser + SFX1 = 1, + // zap + SFX2, + // drill + SFX3, + // explo + SFX4, +}; + +const struct beeper_sfx sfx_table[] = { + { 1, 32, 120, 252, 0 }, + { 2, 16, 12, 0, 0 }, + { 1, 32, 1, 0, 0 }, + { 2, 32, 128, 255, 0 }, +}; + +#endif /* _SFX_H */ diff --git a/example/test.sfx b/example/test.sfx new file mode 100644 index 0000000..869ce53 --- /dev/null +++ b/example/test.sfx @@ -0,0 +1,5 @@ +;SFXv1 +laser 1 32 120 -4 0 +zap 2 16 12 0 0 +drill 1 32 1 0 0 +explo 2 32 -128 -1 0 diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..40c607c --- /dev/null +++ b/main.cpp @@ -0,0 +1,414 @@ + +// sfxed for beeper engine +// Copyright (C) 2021 by Juan J. Martinez +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#include "imgui.h" +#include "imgui_impl_sdl.h" +#include "imgui_impl_opengl3.h" + +#include "ImGuiFileDialog.h" + +#include +#include + +#include "SDL.h" + +#include + +extern "C" { +#include "sfx.h" +} + +#define APP_NAME "SFX Editor" +#define APP_URL "https://github.com/reidrac/beeper-int-zx" + +void show_help(char *argv0) { + printf("Usage: %s [options] \n\n" + "Available options:\n" + " -h, --help this help text\n" + " -V, --version show version and exit\n\n", + argv0); +} + +#define MAX_ENTRIES 255 +BeeperSfx sfx[MAX_ENTRIES]; +int entries; + +void add_entry(uint8_t index) +{ + if (index != entries) + for (int i = entries; i > index; i--) + { + sfx[i].type = sfx[i - 1].type; + sfx[i].frames = sfx[i - 1].frames; + sfx[i].freq = sfx[i - 1].freq; + sfx[i].slide = sfx[i - 1].slide; + sfx[i].next = sfx[i - 1].next; + strcpy(sfx[i].name, sfx[i - 1].name); + if (sfx[i].next) + sfx[i].next++; + } + + sfx[index].type = 1; + sfx[index].frames = 12; + sfx[index].freq = 32; + sfx[index].slide = 0; + sfx[index].next = 0; + strcpy(sfx[index].name, "sfx"); + entries++; +} + +void remove_entry(uint8_t index) +{ + if (index + 1 == entries) + { + entries--; + return; + } + + for (int i = index + 1; i < entries; i++) + { + sfx[i - 1].type = sfx[i].type; + sfx[i - 1].frames = sfx[i].frames; + sfx[i - 1].freq = sfx[i].freq; + sfx[i - 1].slide = sfx[i].slide; + sfx[i - 1].next = sfx[i].next; + strcpy(sfx[i - 1].name, sfx[i].name); + } + + entries--; + for (int i = 0; i < entries; i++) + if (sfx[i].next == index + 1) + sfx[i].next = 0; + else if (sfx[i].next > index + 1) + sfx[i].next--; +} + +int main(int argc, char *argv[]) +{ + char *filename = NULL; + char *error = NULL; + + for (int i = 1; i < argc; i++) + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) + { + show_help(argv[0]); + return 0; + } + else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version")) + { + printf(APP_NAME " " APP_VERSION "\n" + APP_URL "\n"); + return 0; + } + else if (!filename) + filename = strdup(argv[i]); + else + { + fprintf(stderr, "ERROR: unsupported option '%s', try -h\n", argv[i]); + return 1; + } + + entries = 0; + if (filename) + { + entries = load_sfx(filename, sfx, MAX_ENTRIES); + if (entries == -1) + return 1; + } + else + add_entry(0); + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) != 0) + { + fprintf(stderr, "ERROR: %s\n", SDL_GetError()); + return 1; + } + + // GL 3.0 + GLSL 130 + const char* glsl_version = "#version 130"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + + // Create window with graphics context + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_Window* window = SDL_CreateWindow(APP_NAME " " APP_VERSION, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 750, 360, window_flags); + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); // Enable vsync + + // Initialize OpenGL loader + bool err = gl3wInit() != 0; + if (err) + { + fprintf(stderr, "ERROR: failed to initialize OpenGL loader!\n"); + return 1; + } + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForOpenGL(window, gl_context); + ImGui_ImplOpenGL3_Init(glsl_version); + + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + const char * types_names[] = { "Silence", "Tone", "Noise" }; + + bool done = false; + while (!done) + { + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_QUIT) + done = true; + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) + done = true; + } + + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(window); + ImGui::NewFrame(); + + if (ImGuiFileDialog::Instance()->Display("OpenFileDlgKey")) + { + if (ImGuiFileDialog::Instance()->IsOk()) + { + std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); + if (filename) + free(filename); + filename = strdup(filePathName.c_str()); + entries = load_sfx(filename, sfx, MAX_ENTRIES); + if (entries == -1) + { + entries = 0; + add_entry(0); + if (filename) + free(filename); + filename = NULL; + + error = strdup("Failed to load the SFX file!\n\nIt is possible that the file is corrupt or is not a sfx file."); + } + } + ImGuiFileDialog::Instance()->Close(); + } + + if (ImGuiFileDialog::Instance()->Display("SaveAsFileDlgKey")) + { + if (ImGuiFileDialog::Instance()->IsOk()) + { + std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); + if (filename) + free(filename); + filename = strdup(filePathName.c_str()); + if (save_sfx(filename, sfx, entries) == -1) + error = strdup("Failed to save the SFX file!\n\nPlease double check that you have permissions to save on that location."); + } + ImGuiFileDialog::Instance()->Close(); + } + + if (ImGuiFileDialog::Instance()->Display("ExportFileDlgKey")) + { + if (ImGuiFileDialog::Instance()->IsOk()) + { + std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); + if (export_sfx((char *)filePathName.c_str(), sfx, entries) == -1) + error = strdup("Failed to export!\n\nPlease double check that you have permissions to save on that location."); + } + ImGuiFileDialog::Instance()->Close(); + } + + ImGui::Begin(filename ? filename : "", NULL, 0); + + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("New")) + { + if (filename) + free(filename); + filename = NULL; + entries = 0; + add_entry(0); + } + if (ImGui::MenuItem("Open", "")) + ImGuiFileDialog::Instance()->OpenDialog("OpenFileDlgKey", + "Open File", ".sfx", "."); + if (ImGui::MenuItem("Save", "", false, filename != NULL)) + { + if (save_sfx(filename, sfx, entries) == -1) + error = strdup("Failed to save the SFX file!\n\nPlease double check that you have permissions to save on that location."); + } + if (ImGui::MenuItem("Save As")) + ImGuiFileDialog::Instance()->OpenDialog("SaveAsFileDlgKey", + "Save As", ".sfx", "."); + + ImGui::Separator(); + if (ImGui::MenuItem("Quit", "Alt+F4")) + done = true; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Export")) { + if (ImGui::MenuItem("Binary")) + ImGuiFileDialog::Instance()->OpenDialog("ExportFileDlgKey", + "Export As", ".bin", "."); + if (ImGui::MenuItem("C include")) + ImGuiFileDialog::Instance()->OpenDialog("ExportFileDlgKey", + "Export As", ".h", "."); + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } + + if (ImGui::BeginTable("effects table", 8, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg)) + { + ImGui::TableSetupColumn("No."); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableSetupColumn("Frames", ImGuiTableColumnFlags_WidthFixed, 60.0f); + ImGui::TableSetupColumn("Freq", ImGuiTableColumnFlags_WidthFixed, 60.0f); + ImGui::TableSetupColumn("Slide", ImGuiTableColumnFlags_WidthFixed, 60.0f); + ImGui::TableSetupColumn("Next", ImGuiTableColumnFlags_WidthFixed, 60.0f); + ImGui::TableHeadersRow(); + + for (int i = 0; i < entries; i++) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%02d", i + 1); + + ImGui::PushID(i); + + ImGui::TableNextColumn(); + ImGui::PushID("name"); + ImGui::InputText("", sfx[i].name, 9, ImGuiInputTextFlags_CharsNoBlank); + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::PushID("type"); + ImGui::Combo("", &sfx[i].type, types_names, IM_ARRAYSIZE(types_names), IM_ARRAYSIZE(types_names)); + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::PushID("fames"); + ImGui::InputScalar("", ImGuiDataType_U8, &sfx[i].frames); + ImGui::PopID(); + ImGui::TableNextColumn(); + ImGui::PushID("freq"); + ImGui::InputScalar("", ImGuiDataType_U8, &sfx[i].freq); + ImGui::PopID(); + ImGui::TableNextColumn(); + ImGui::PushID("slide"); + ImGui::InputScalar("", ImGuiDataType_S8, &sfx[i].slide); + ImGui::PopID(); + ImGui::TableNextColumn(); + ImGui::PushID("next"); + ImGui::InputScalar("", ImGuiDataType_U8, &sfx[i].next); + ImGui::PopID(); + ImGui::TableNextColumn(); + + if (sfx[i].frames == 0) + sfx[i].frames = 1; + if (sfx[i].freq == 0) + sfx[i].freq = 1; + + if (sfx[i].next == i + 1 || sfx[i].next > entries) + { + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(255, 0, 0, 100)); + ImGui::Button("Play"); + ImGui::PopStyleColor(); + } + else if (ImGui::Button("Play")) + play_sfx(i + 1, sfx, entries); + ImGui::SameLine(); + if (entries < MAX_ENTRIES) + { + if (ImGui::Button("+")) + add_entry(i + 1); + } + if (entries > 1) + { + ImGui::SameLine(); + if (ImGui::Button("-")) + remove_entry(i); + } + + ImGui::PopID(); + } + ImGui::EndTable(); + } + + if (error && !ImGui::IsPopupOpen("Error")) + ImGui::OpenPopup("Error"); + if (ImGui::BeginPopupModal("Error", NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text(error); + ImGui::Separator(); + if (ImGui::Button("OK", ImVec2(120, 0))) + { + if (error) + free(error); + error = NULL; + ImGui::CloseCurrentPopup(); + } + ImGui::SetItemDefaultFocus(); + ImGui::EndPopup(); + } + + ImGui::End(); + + // Rendering + ImGui::Render(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + SDL_GL_SwapWindow(window); + } + + // Cleanup + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + SDL_GL_DeleteContext(gl_context); + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} diff --git a/player.h b/player.h new file mode 100644 index 0000000..815edcf --- /dev/null +++ b/player.h @@ -0,0 +1,39 @@ +/* file: player.bin */ +#define PLAYER_LEN 221 + +#ifdef LOCAL +const unsigned char player[] = { +0xf3, 0x31, 0x00, 0x00, 0xfb, 0x21, 0x00, 0x7d, +0x22, 0xe2, 0x50, 0x21, 0x00, 0xfe, 0x36, 0xfd, +0x5d, 0x54, 0x13, 0x01, 0x01, 0x01, 0xed, 0xb0, +0x3e, 0xfe, 0xed, 0x47, 0xed, 0x5e, 0x21, 0xfd, +0xfd, 0x11, 0x41, 0x50, 0x3e, 0xc3, 0x77, 0x23, +0x73, 0x23, 0x72, 0xfb, 0x3a, 0xff, 0x7c, 0x6f, +0xcd, 0x60, 0x50, 0x3a, 0xdd, 0x50, 0xb7, 0x20, +0xfa, 0x76, 0x76, 0x76, 0x76, 0xd3, 0xff, 0xf3, +0x76, 0x08, 0xe5, 0xdd, 0xe5, 0xfd, 0xe5, 0xc5, +0xd5, 0xcd, 0x84, 0x50, 0xd1, 0xc1, 0xfd, 0xe1, +0xdd, 0xe1, 0xe1, 0x08, 0xfb, 0xc9, 0xf3, 0x22, +0xe2, 0x50, 0xaf, 0x32, 0xdd, 0x50, 0xfb, 0xc9, +0xf3, 0x7d, 0xcd, 0x67, 0x50, 0xfb, 0xc9, 0x32, +0xdd, 0x50, 0xb7, 0xc8, 0x3d, 0x2a, 0xe2, 0x50, +0x4d, 0x44, 0x26, 0x00, 0x6f, 0x54, 0x5d, 0x29, +0x29, 0x19, 0x09, 0x11, 0xdd, 0x50, 0x01, 0x05, +0x00, 0xed, 0xb0, 0xc9, 0x3a, 0xdd, 0x50, 0xb7, +0xc8, 0x3d, 0x28, 0x18, 0x3d, 0xc0, 0x3a, 0xdf, +0x50, 0x57, 0x06, 0x00, 0xcd, 0xc8, 0x50, 0xe6, +0x10, 0xd3, 0xfe, 0x4a, 0x05, 0x28, 0x18, 0x0d, +0x20, 0xfa, 0x18, 0xf0, 0x3a, 0xdf, 0x50, 0x57, +0xaf, 0x47, 0xd3, 0xfe, 0xee, 0x10, 0x4a, 0x05, +0x28, 0x05, 0x0d, 0x20, 0xfa, 0x18, 0xf3, 0x3a, +0xe1, 0x50, 0x21, 0xde, 0x50, 0x35, 0x28, 0xa7, +0x3a, 0xe0, 0x50, 0x82, 0x32, 0xdf, 0x50, 0xc9, +0x21, 0xa1, 0xf3, 0x7c, 0x1f, 0x7d, 0x1f, 0xac, +0x67, 0x7d, 0x1f, 0x7c, 0x1f, 0xad, 0x6f, 0xac, +0x67, 0x22, 0xc9, 0x50, 0xc9 +}; + +#else +extern const unsigned char player[]; + +#endif diff --git a/player/Makefile b/player/Makefile new file mode 100644 index 0000000..c837be5 --- /dev/null +++ b/player/Makefile @@ -0,0 +1,20 @@ +all: player.bin + +CC = sdcc +AS = sdasz80 +AR = sdar +CFLAGS = -mz80 --Werror --fsigned-char --std-sdcc99 --opt-code-speed +LDFLAGS = --no-std-crt0 --fomit-frame-pointer + +%.rel: %.z80 + $(AS) -g -o $@ $< + +player.bin: player.rel + $(CC) $(CFLAGS) $(LDFLAGS) --code-loc 20480 --data-loc 0 -o player.ihx $< + hex2bin -p 00 player.ihx + ./bin2h.py player.bin player > ../player.h + +.PHONY: clean +clean: + rm -f *.rel *.ihx *.bin *.map *.noi *.lk + diff --git a/player/README.md b/player/README.md new file mode 100644 index 0000000..d9f0c96 --- /dev/null +++ b/player/README.md @@ -0,0 +1,13 @@ +This builds a player to be run from sfxed emulator. + +Changing this is not completely supported, but if you really want to do it, it +is possible! + +## Build instructions + +This requires in your PATH: + +* SDCC +* Python 3 +* hex2bin; for example [you can use this version](https://github.com/reidrac/ubox-msx-lib/tree/master/tools/hex2bin-2.0) + diff --git a/player/bin2h.py b/player/bin2h.py new file mode 100755 index 0000000..d0395fd --- /dev/null +++ b/player/bin2h.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +from argparse import ArgumentParser + +__version__ = "1.0" + + +def main(): + + parser = ArgumentParser(description="Bin to H converter", + epilog="Copyright (C) 2014-2021 Juan J Martinez ", + ) + + parser.add_argument("--version", action="version", + version="%(prog)s " + __version__) + parser.add_argument("file", help="file to convert") + parser.add_argument("id", help="variable to use") + + args = parser.parse_args() + + with open(args.file, "rb") as fd: + data = bytearray(fd.read()) + + data_out = "" + for part in range(0, len(data), 8): + if data_out: + data_out += ",\n" + data_out += ', '.join(["0x%02x" % b for b in data[part: part + 8]]) + + print("/* file: %s */" % args.file) + print("#define %s_LEN %d\n" % (args.id.upper(), len(data))) + print("#ifdef LOCAL") + print("const unsigned char %s[] = {\n%s\n};\n" % (args.id, data_out)) + print("#else") + print("extern const unsigned char %s[];\n" % args.id) + print("#endif") + + +if __name__ == "__main__": + main() diff --git a/player/player.z80 b/player/player.z80 new file mode 100644 index 0000000..d6fed1a --- /dev/null +++ b/player/player.z80 @@ -0,0 +1,93 @@ +ISR_TABLE_START = 0xfe00 +ISR_TABLE_START_LO = 0xfe +ISR_TABLE_VALUE = 0xfd +ISR_TABLE_START_JP = 0xfdfd + +EFX_TABLE_ADDR = 32000 +EFX_IN_ADDR = EFX_TABLE_ADDR - 1 + +.area _HOME +.area _CODE +.area _INITIALIZER +.area _GSINIT +.area _GSFINAL + +.area _DATA +.area _INITIALIZED +.area _BSEG +.area _BSS +.area _HEAP + +.area _CODE + +_main:: + di + ld sp, #0 + ei + + ld hl, #EFX_TABLE_ADDR + ld (sfx_data), hl + + ld hl, #ISR_TABLE_START + ld (hl), #ISR_TABLE_VALUE + ld e, l + ld d, h + inc de + ld bc, #257 + ldir + + ld a, #ISR_TABLE_START_LO + ld i, a + im 2 + + ld hl, #ISR_TABLE_START_JP + ld de, #isr + ld a, #0xc3 + ld (hl), a + inc hl + ld (hl), e + inc hl + ld (hl), d + ei + + ld a, (EFX_IN_ADDR) + ld l, a + call _beeper_queue + +wait:: + ld a, (sfx_type) + or a + jr nz, wait + + halt + halt + halt + halt + + out (0xff), a + + di + halt + +isr: + ex af,af + push hl + push ix + push iy + push bc + push de + + call _beeper_play + + pop de + pop bc + pop iy + pop ix + pop hl + ex af,af + ei + + ret + +.include "../sdcc/beeper.z80" + diff --git a/sdcc/Makefile b/sdcc/Makefile new file mode 100644 index 0000000..3445ca7 --- /dev/null +++ b/sdcc/Makefile @@ -0,0 +1,13 @@ +all: beeper.lib + +AS=sdasz80 +AR=sdar + +beeper.lib: beeper.z80 + $(AS) -o $< + $(AR) -rcD $@ beeper.rel + +.PHONY: clean +clean: + rm -f *.rel *.bin *.lib + diff --git a/sdcc/README.MD b/sdcc/README.MD new file mode 100644 index 0000000..7ec00a3 --- /dev/null +++ b/sdcc/README.MD @@ -0,0 +1,23 @@ +This is the main implementation of the beeper engine, to be used with SDCC +compiler. + +The assembler syntax specific to this compiler suite, but it should be +easy to convert to your favourite assembler. + +Feel free to contribute your port! + +## Build instructions + +Ensure that SDCC is in your path and run `make`. + +Include this directory in your include and library paths, and link with the +beeper library. + +For example: +``` +CFLAGS += -I$(BEEPER_LIB_DIR) +LDFLAGS += -L$(BEEPER_LIB_DIR) -lbeeper +``` + +It is recommended that the beeper code runs from non-contended memory. + diff --git a/sdcc/beeper.h b/sdcc/beeper.h new file mode 100644 index 0000000..fff5dad --- /dev/null +++ b/sdcc/beeper.h @@ -0,0 +1,49 @@ +// Beeper engine +// Copyright (C) 2021 by Juan J. Martinez +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#ifndef _BEEPER_H +#define _BEEPER_H + +#include + +struct beeper_sfx { + uint8_t type; + uint8_t frames; + uint8_t freq; + uint8_t slide; + uint8_t next; +}; + +// to init the beeper engine, provide a pointer to the effect table +void beeper_init(const struct beeper_sfx *efx_table) __z88dk_fastcall; + +// to queue a new effect +// efx_no is... +// +// 0: no effect (stops sound) +// 1: index 0 of the effect table +// 2: ... +// +void beeper_queue(uint8_t efx_no) __z88dk_fastcall; + +// to be called in the INT handler; call beeper_init first! +void beeper_play(); +#endif diff --git a/sdcc/beeper.z80 b/sdcc/beeper.z80 new file mode 100644 index 0000000..b5b82e7 --- /dev/null +++ b/sdcc/beeper.z80 @@ -0,0 +1,158 @@ +; Beeper engine +; Copyright (C) 2021 by Juan J. Martinez +; +; Permission is hereby granted, free of charge, to any person obtaining a copy +; of this software and associated documentation files (the "Software"), to deal +; in the Software without restriction, including without limitation the rights +; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +; copies of the Software, and to permit persons to whom the Software is +; furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included in +; all copies or substantial portions of the Software. +; +; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +; THE SOFTWARE. +; +.globl _beeper_init +.globl _beeper_queue +.globl _beeper_play + +_beeper_init:: + di + ld (sfx_data), hl + xor a + ld (sfx_type), a + ei + ret + +_beeper_queue:: + di + ld a, l + call queue_next + ei + ret + +queue_next: + ld (sfx_type), a + or a + ret z + + dec a + + ld hl, (sfx_data) + ld c, l + ld b, h + + ld h, #0 + ld l, a + ld d, h + ld e, l + add hl, hl + add hl, hl + add hl, de + add hl, bc + + ld de, #sfx_type + ld bc, #5 + ldir + ret + +_beeper_play:: + ld a, (sfx_type) + or a + ret z + + dec a + jr z, tone + + dec a + ; shouldn't happen! + ret nz + + ; noise + ld a, (sfx_freq) + ld d, a + + ld b, #0 + +noise_loop: + call rnd + and #0x10 + ; FIXME: border ? + out (0xfe), a + + ld c, d +noise_freq_loop: + dec b + jr z, noise_done + dec c + jr nz, noise_freq_loop + jr noise_loop + +tone: + ld a, (sfx_freq) + ld d, a + + xor a + ld b, a + +tone_loop: + ; FIXME: border ? + out (0xfe), a + xor #0x10 + + ld c, d +freq_loop: + dec b + jr z, tone_done + dec c + jr nz, freq_loop + jr tone_loop + +tone_done: +noise_done: + ld a, (sfx_next) + ld hl, #sfx_frames + dec (hl) + jr z, queue_next + + ; freq change (slide) + ld a, (sfx_freq_chg) + add d + ld (sfx_freq), a + + ret + +rnd: + ld hl, #0xf3a1 + ld a, h + rra + ld a, l + rra + xor h + ld h, a + ld a, l + rra + ld a, h + rra + xor l + ld l, a + xor h + ld h, a + ld (rnd + 1), hl + ret + +sfx_type: .ds 1 +sfx_frames: .ds 1 +sfx_freq: .ds 1 +sfx_freq_chg: .ds 1 +sfx_next: .ds 1 + +sfx_data: .ds 2 + diff --git a/sfx.c b/sfx.c new file mode 100644 index 0000000..2008de5 --- /dev/null +++ b/sfx.c @@ -0,0 +1,356 @@ +// sfxed for beeper engine +// Copyright (C) 2021 by Juan J. Martinez +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#include +#include +#include + +#include "SDL.h" + +#include "zymosis.h" + +#define LOCAL +#include "player.h" +#undef LOCAL + +#include "sfx.h" + +#define PLAYER_ADDR 20480 +#define EFX_TABLE_ADDR 32000 +#define EFX_IN_ADDR (EFX_TABLE_ADDR - 1) + +// 48K timings +#define TSTATES_PER_FRAME 69888 +#define TSTATE_STEP 16 + +uint8_t memory[65536]; +uint8_t sound_state; +uint8_t exit_state; + +#define MAX_SAMPLES (44100*5) +uint16_t samples[MAX_SAMPLES]; +uint32_t nsamples; + +int load_sfx(char *filename, BeeperSfx *table, uint8_t n) +{ + FILE *fd; + char header[8]; + uint8_t entries = 0; + uint8_t type; + + fd = fopen(filename, "rt"); + if (!fd) + { + fprintf(stderr, "ERROR: failed to load %s\n", filename); + return -1; + } + + if (fgets(header, 7, fd) == NULL || strcmp(header, ";SFXv1")) + { + fprintf(stderr, "ERROR: %s doesn't look like a valid SFX file\n", filename); + return -1; + } + + memset(table, 0, sizeof(struct beeper_sfx) * n); + + while (!feof(fd)) + { + if (fscanf(fd, "%8s %hhd %hhd %hhd %hhd %hhd\n", + table[entries].name, + &type, + &table[entries].frames, + &table[entries].freq, + &table[entries].slide, + &table[entries].next + ) != 6) + { + fprintf(stderr, "ERROR: failed to load %s\n", filename); + fclose(fd); + return -1; + } + // prevent invalid frequency + table[entries].freq = table[entries].freq ? table[entries].freq : 1; + // move to int + table[entries].type = type < 3 ? type : 0; + + entries++; + if (entries == n) + { + fprintf(stderr, "WARN: read max %d entries\n", entries); + break; + } + } + + fclose(fd); + + return entries; +} + +int save_sfx(char *filename, BeeperSfx *table, uint8_t n) +{ + FILE *fd; + + fd = fopen(filename, "wt"); + if (!fd) + { + fprintf(stderr, "ERROR: failed to save %s\n", filename); + return -1; + } + + if (fprintf(fd, ";SFXv1\n") != 7) + { + fprintf(stderr, "ERROR: failed to write to %s\n", filename); + return -1; + } + + for (int i = 0; i < n; i++) + { + if (fprintf(fd, "%s %hhd %hhd %hhd %hhd %hhd\n", + table[i].name, + table[i].type, + table[i].frames, + table[i].freq, + table[i].slide, + table[i].next + ) == -1) + { + fprintf(stderr, "ERROR: failed to write %s\n", filename); + fclose(fd); + return -1; + } + } + + fclose(fd); + + return n; +} + +int export_c(char *filename, BeeperSfx *table, uint8_t n) +{ + FILE *fd; + + fd = fopen(filename, "wt"); + if (!fd) + { + fprintf(stderr, "ERROR: failed to save %s\n", filename); + return -1; + } + + fprintf(fd, "#ifndef _SFX_H\n#define _SFX_H\n\n"); + + fprintf(fd, "enum sfx_enum {\n"); + for (int i = 0; i < n; i++) + { + fprintf(fd, "\t// %s\n", table[i].name); + if (i == 0) + fprintf(fd, "\tSFX%d = 1,\n", i + 1); + else + fprintf(fd, "\tSFX%d,\n", i + 1); + } + fprintf(fd, "};\n\n"); + + fprintf(fd, "const struct beeper_sfx sfx_table[] = {\n"); + for (int i = 0; i < n; i++) + { + if (fprintf(fd, "\t{ %hhu, %hhu, %hhu, %hhu, %hhu },\n", + table[i].type, + table[i].frames, + table[i].freq, + table[i].slide, + table[i].next + ) == -1) + { + fprintf(stderr, "ERROR: failed to write %s\n", filename); + fclose(fd); + return -1; + } + } + fprintf(fd, "};\n\n"); + + fprintf(fd, "#endif /* _SFX_H */\n"); + + fclose(fd); + + return n; +} + +int export_bin(char *filename, BeeperSfx *table, uint8_t n) +{ + FILE *fd; + + fd = fopen(filename, "wb"); + if (!fd) + { + fprintf(stderr, "ERROR: failed to save %s\n", filename); + return -1; + } + + for (int i = 0; i < n; i++) + { + if (fwrite(&table[i].type, 1, 1, fd) != 1 + || fwrite(&table[i].frames, 1, 1, fd) != 1 + || fwrite(&table[i].freq, 1, 1, fd) != 1 + || fwrite(&table[i].slide, 1, 1, fd) != 1 + || fwrite(&table[i].next, 1, 1, fd) != 1) + { + fclose(fd); + fprintf(stderr, "ERROR: failed to write %s\n", filename); + return -1; + } + } + + fclose(fd); + + return n; +} + +int export_sfx(char *filename, BeeperSfx *table, uint8_t n) +{ + if (filename[strlen(filename) - 1] == 'h') + return export_c(filename, table, n); + else + return export_bin(filename, table, n); +} + +void z80_mem_write(Z80Info *z80, uint16_t addr, uint8_t value, Z80MemIOType mio) +{ + memory[addr] = value; +} + +uint8_t z80_mem_read(Z80Info *z80, uint16_t addr, Z80MemIOType mio) +{ + return memory[addr]; +} + + +uint8_t z80_port_in(Z80Info *z80, uint16_t port, Z80PIOType pio) +{ + return 0; +} + +void z80_port_out(Z80Info *z80, uint16_t port, uint8_t value, Z80PIOType pio) +{ + switch (port & 0xff) + { + case 0xfe: + sound_state = (value & 16); + break; + + default: + exit_state = 1; + break; + } +} + +SDL_AudioDeviceID dev = 0; + +uint8_t play_samples() +{ + SDL_AudioSpec want, have; + + SDL_memset(&want, 0, sizeof(want)); + want.freq = 44100; + want.format = AUDIO_S16; + want.channels = 1; + want.samples = 4096; + + if (!dev) { + dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); + if (dev == 0) { + fprintf(stderr, "ERROR: failed to open audio: %s", SDL_GetError()); + return 1; + } + SDL_PauseAudioDevice(dev, 0); + } + else + SDL_ClearQueuedAudio(dev); + + if (SDL_QueueAudio(dev, samples, nsamples * 2) != 0) + fprintf(stderr, "ERROR: playback error: %s\n", SDL_GetError()); + + return 0; +} + +int play_sfx(uint8_t index, BeeperSfx *table, uint8_t n) +{ + int i; + uint32_t states, real, out; + int32_t next_int; + Z80Info z80; + uint8_t *p; + + if (index > n) + return 1; + + memset(&z80, 0, sizeof(Z80Info)); + memset(memory, 0, 65536); + memcpy(memory + PLAYER_ADDR, player, PLAYER_LEN); + + p = &memory[EFX_TABLE_ADDR]; + for (int i = 0; i < n; i++) + { + *p++ = table[i].type & 0xff; + *p++ = table[i].frames; + *p++ = table[i].freq; + *p++ = table[i].slide; + *p++ = table[i].next; + } + memory[EFX_IN_ADDR] = index; + + Z80_ResetCallbacks(&z80); + + z80.memReadFn = z80_mem_read; + z80.memWriteFn = z80_mem_write; + z80.portInFn = z80_port_in; + z80.portOutFn = z80_port_out; + + Z80_Reset(&z80); + + sound_state = 0; + exit_state = 0; + nsamples = 0; + next_int = TSTATES_PER_FRAME; + states = TSTATE_STEP; + while (!exit_state) + { + for (i = 0, out = 0; i < 4; i++) + { + real = Z80_ExecuteTS(&z80, states); + next_int -= real; + + states = TSTATE_STEP + (TSTATE_STEP - real); + out += sound_state * 16384 / 16; + + if (next_int < TSTATE_STEP) + { + next_int += TSTATES_PER_FRAME; + Z80_Interrupt(&z80); + } + } + + samples[nsamples++] = out >> 2; + + if (nsamples > MAX_SAMPLES) + break; + } + + return play_samples(); +} diff --git a/sfx.h b/sfx.h new file mode 100644 index 0000000..95ae833 --- /dev/null +++ b/sfx.h @@ -0,0 +1,46 @@ +// sfxed for beeper engine +// Copyright (C) 2021 by Juan J. Martinez +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#ifndef _SFX_H +#define _SFX_H + +#include + +struct beeper_sfx +{ + //uint8_t type; + int type; // for the combo + uint8_t frames; + uint8_t freq; + uint8_t slide; + uint8_t next; + + char name[9]; +}; + +typedef struct beeper_sfx BeeperSfx; + +int load_sfx(char *filename, BeeperSfx *table, uint8_t n); +int save_sfx(char *filename, BeeperSfx *table, uint8_t n); +int export_sfx(char *filename, BeeperSfx *table, uint8_t n); +int play_sfx(uint8_t index, BeeperSfx *table, uint8_t n); + +#endif //_SFX_H diff --git a/zymosis.c b/zymosis.c new file mode 100644 index 0000000..83ca583 --- /dev/null +++ b/zymosis.c @@ -0,0 +1,1985 @@ +/* + * Z80 CPU emulation engine v0.0.3b + * coded by Ketmar // Vampire Avalon + * + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + */ +#include + +#include "zymosis.h" + + +/******************************************************************************/ +/* some funny tables */ +static int tablesInitialized = 0; +static uint8_t parityTable[256]; +static uint8_t sz53Table[256]; /* bits 3, 5 and 7 of result, Z flag */ +static uint8_t sz53pTable[256]; /* bits 3, 5 and 7 of result, Z and P flags */ + + +/******************************************************************************/ +void Z80_InitTables (void) +{ + if (!tablesInitialized) + { + int f; + /***/ + for (f = 0; f <= 255; ++f) + { + int n, p; + /***/ + sz53Table[f] = (f & Z80_FLAG_S35); + for (n = f, p = 0; n != 0; n >>= 1) p ^= n & 0x01; + parityTable[f] = (p ? 0 : Z80_FLAG_PV); + sz53pTable[f] = (sz53Table[f] | parityTable[f]); + } + sz53Table[0] |= Z80_FLAG_Z; + sz53pTable[0] |= Z80_FLAG_Z; + /***/ + tablesInitialized = 1; + } +} + + +void Z80_ResetCallbacks (Z80Info *z80) +{ + if (!tablesInitialized) Z80_InitTables(); + z80->memReadFn = NULL; + z80->memWriteFn = NULL; + z80->contentionFn = NULL; + z80->portInFn = NULL; + z80->portOutFn = NULL; + z80->portContentionFn = NULL; + z80->retiFn = NULL; + z80->retnFn = NULL; + z80->trapEDFn = NULL; + z80->pagerFn = NULL; + z80->checkBPFn = NULL; +} + + +/* seems that all regs (and memptr) should be set to 'all 1' here, but i don't care */ +void Z80_Reset (Z80Info *z80) +{ + if (!tablesInitialized) Z80_InitTables(); + z80->bc.w = z80->de.w = z80->hl.w = z80->af.w = z80->sp.w = z80->ix.w = z80->iy.w = 0; + z80->bcx.w = z80->dex.w = z80->hlx.w = z80->afx.w = 0; + z80->pc = z80->prev_pc = z80->org_pc = 0; + z80->memptr.w = 0; + z80->regI = z80->regR = 0; + z80->iff1 = z80->iff2 = 0; + z80->im = 0; + z80->halted = 0; + z80->prev_was_EIDDR = 0; + z80->tstates = 0; + z80->dd = &z80->hl; +} + + +/******************************************************************************/ +#define Z80_EXX(_z80) do { \ + uint16_t t = (_z80)->bc.w; (_z80)->bc.w = (_z80)->bcx.w; (_z80)->bcx.w = t; \ + t = (_z80)->de.w; (_z80)->de.w = (_z80)->dex.w; (_z80)->dex.w = t; \ + t = (_z80)->hl.w; (_z80)->hl.w = (_z80)->hlx.w; (_z80)->hlx.w = t; \ +} while (0) + +#define Z80_EXAFAF(_z80) do { \ + uint16_t t = (_z80)->af.w; (_z80)->af.w = (_z80)->afx.w; (_z80)->afx.w = t; \ +} while (0) + + +/******************************************************************************/ +/* simulate contented memory access */ +/* (tstates = tstates+contention+1)*cnt */ +/* (Z80Info *z80, uint16_t addr, int tstates, Z80MemIOType mio) */ +#define Z80_Contention(_z80,_addr,_tstates,_mio) do { \ + if ((_z80)->contentionFn != NULL) (_z80)->contentionFn((_z80), (_addr), (_tstates), (_mio)); else (_z80)->tstates += (_tstates); \ +} while (0) + + +#define Z80_ContentionBy1(_z80,_addr,_cnt) do { \ + if ((z80)->contentionFn != NULL) { \ + int _f; \ + for (_f = (_cnt); _f-- > 0; (_z80)->contentionFn((_z80), (_addr), 1, Z80_MREQ_NONE|Z80_MEMIO_OTHER)) ; \ + } else { \ + (_z80)->tstates += (_cnt); \ + } \ +} while (0) + + +#define Z80_ContentionIRBy1(_z80,_cnt) Z80_ContentionBy1((_z80), (((uint16_t)(_z80)->regI)<<8)|((_z80)->regR), (_cnt)) +#define Z80_ContentionPCBy1(_z80,_cnt) Z80_ContentionBy1((_z80), (_z80)->pc, (_cnt)) + + +/******************************************************************************/ +static ZYMOSIS_INLINE uint8_t Z80_PortIn (Z80Info *z80, uint16_t port) +{ + uint8_t value; + /***/ + if (z80->portContentionFn != NULL) + { + z80->portContentionFn(z80, port, 1, Z80_PIOFLAG_IN | Z80_PIOFLAG_EARLY); + z80->portContentionFn(z80, port, 2, Z80_PIOFLAG_IN); + } + else + { + z80->tstates += 3; + } + value = z80->portInFn(z80, port, Z80_PIO_NORMAL); + ++z80->tstates; + return value; +} + + +static ZYMOSIS_INLINE void Z80_PortOut (Z80Info *z80, uint16_t port, uint8_t value) +{ + if (z80->portContentionFn != NULL) + { + z80->portContentionFn(z80, port, 1, Z80_PIOFLAG_EARLY); + } + else + { + ++z80->tstates; + } + z80->portOutFn(z80, port, value, Z80_PIO_NORMAL); + if (z80->portContentionFn != NULL) + { + z80->portContentionFn(z80, port, 2, 0); + ++z80->tstates; + } + else + { + z80->tstates += 3; + } +} + + +/******************************************************************************/ +#define Z80_PeekBI(_z80,_addr) (_z80)->memReadFn((_z80), (_addr), Z80_MEMIO_OTHER) +#define Z80_PeekB(_z80,_addr) (_z80)->memReadFn((_z80), (_addr), Z80_MEMIO_DATA) +/*#define Z80_PeekWI(_z80,_addr) (((uint16_t)Z80_PeekBI((_z80), (_addr)))|(((uint16_t)Z80_PeekBI((_z80), ((_addr)+1)&0xffff))<<8)) */ + +#define Z80_PokeBI(_z80,_addr,_byte) (_z80)->memWriteFn((_z80), (_addr), (_byte), Z80_MEMIO_OTHER) +#define Z80_PokeB(_z80,_addr,_byte) (_z80)->memWriteFn((_z80), (_addr), (_byte), Z80_MEMIO_DATA) + +/* t1: setting /MREQ & /RD */ +/* t2: memory read */ +/*#define Z80_PeekB3T(_z80,_addr) (Z80_Contention(_z80, (_addr), 3, Z80_MREQ_READ|Z80_MEMIO_DATA), Z80_PeekB(_z80, (_addr))) */ +static ZYMOSIS_INLINE uint8_t Z80_PeekB3T (Z80Info *z80, uint16_t addr) +{ + Z80_Contention(z80, addr, 3, Z80_MREQ_READ | Z80_MEMIO_DATA); + return Z80_PeekB(z80, addr); +} + +static ZYMOSIS_INLINE uint8_t Z80_PeekB3TA (Z80Info *z80, uint16_t addr) +{ + Z80_Contention(z80, addr, 3, Z80_MREQ_READ | Z80_MEMIO_OPCARG); + return Z80_PeekB(z80, addr); +} + +/* t1: setting /MREQ & /WR */ +/* t2: memory write */ +#define Z80_PokeB3T(_z80,_addr,_byte) do { \ + Z80_Contention((_z80), (_addr), 3, Z80_MREQ_WRITE|Z80_MEMIO_DATA); \ + Z80_PokeB((_z80), (_addr), (_byte)); \ +} while (0) + + +static ZYMOSIS_INLINE uint16_t Z80_PeekW6T (Z80Info *z80, uint16_t addr) +{ + uint16_t res = Z80_PeekB3T(z80, addr); + return res | (((uint16_t)Z80_PeekB3T(z80, (addr + 1) & 0xffff)) << 8); +} + +static ZYMOSIS_INLINE void Z80_PokeW6T (Z80Info *z80, uint16_t addr, uint16_t value) +{ + Z80_PokeB3T(z80, addr, value & 0xff); + Z80_PokeB3T(z80, (addr + 1) & 0xffff, (value >> 8) & 0xff); +} + +static ZYMOSIS_INLINE void Z80_PokeW6TInv (Z80Info *z80, uint16_t addr, uint16_t value) +{ + Z80_PokeB3T(z80, (addr + 1) & 0xffff, (value >> 8) & 0xff); + Z80_PokeB3T(z80, addr, value & 0xff); +} + +static ZYMOSIS_INLINE uint16_t Z80_GetWordPC (Z80Info *z80, int wait1) +{ + uint16_t res = Z80_PeekB3TA(z80, z80->pc); + /***/ + z80->pc = (z80->pc + 1) & 0xffff; + res |= ((uint16_t)Z80_PeekB3TA(z80, z80->pc)) << 8; + if (wait1) Z80_ContentionPCBy1(z80, wait1); + z80->pc = (z80->pc + 1) & 0xffff; + return res; +} + +static ZYMOSIS_INLINE uint16_t Z80_Pop6T (Z80Info *z80) +{ + uint16_t res = Z80_PeekB3T(z80, z80->sp.w); + /***/ + z80->sp.w = (z80->sp.w + 1) & 0xffff; + res |= ((uint16_t)Z80_PeekB3T(z80, z80->sp.w)) << 8; + z80->sp.w = (z80->sp.w + 1) & 0xffff; + return res; +} + +/* 3 T states write high byte of PC to the stack and decrement SP */ +/* 3 T states write the low byte of PC and jump to #0066 */ +static ZYMOSIS_INLINE void Z80_Push6T (Z80Info *z80, uint16_t value) +{ + z80->sp.w = (((int32_t)z80->sp.w) - 1) & 0xffff; + Z80_PokeB3T(z80, z80->sp.w, (value >> 8) & 0xff); + z80->sp.w = (((int32_t)z80->sp.w) - 1) & 0xffff; + Z80_PokeB3T(z80, z80->sp.w, value & 0xff); +} + + +/******************************************************************************/ +static ZYMOSIS_INLINE void Z80_ADC_A (Z80Info *z80, uint8_t b) +{ + uint16_t new, o = z80->af.a; + /***/ + z80->af.a = (new = o + b + (z80->af.f & Z80_FLAG_C)) & 0xff; /* Z80_FLAG_C is 0x01, so it's safe */ + z80->af.f = + sz53Table[new & 0xff] | + (new > 0xff ? Z80_FLAG_C : 0) | + ((o ^ (~b)) & (o ^ new) & 0x80 ? Z80_FLAG_PV : 0) | + ((o & 0x0f) + (b & 0x0f) + (z80->af.f & Z80_FLAG_C) >= 0x10 ? Z80_FLAG_H : 0); +} + +static ZYMOSIS_INLINE void Z80_SBC_A (Z80Info *z80, uint8_t b) +{ + uint16_t new, o = z80->af.a; + /***/ + z80->af.a = (new = ((int32_t)o - (int32_t)b - (int32_t)(z80->af.f & Z80_FLAG_C)) & 0xffff) & 0xff; /* Z80_FLAG_C is 0x01, so it's safe */ + z80->af.f = + Z80_FLAG_N | + sz53Table[new & 0xff] | + (new > 0xff ? Z80_FLAG_C : 0) | + ((o ^ b) & (o ^ new) & 0x80 ? Z80_FLAG_PV : 0) | + ((int32_t)(o & 0x0f) - (int32_t)(b & 0x0f) - (int32_t)(z80->af.f & Z80_FLAG_C) < 0 ? Z80_FLAG_H : 0); +} + + +static ZYMOSIS_INLINE void Z80_ADD_A (Z80Info *z80, uint8_t b) +{ + z80->af.f &= ~Z80_FLAG_C; + Z80_ADC_A(z80, b); +} + +static ZYMOSIS_INLINE void Z80_SUB_A (Z80Info *z80, uint8_t b) +{ + z80->af.f &= ~Z80_FLAG_C; + Z80_SBC_A(z80, b); +} + +static ZYMOSIS_INLINE void Z80_CP_A (Z80Info *z80, uint8_t b) +{ + uint8_t o = z80->af.a, new = ((int32_t)o - (int32_t)b) & 0xff; + /***/ + z80->af.f = + Z80_FLAG_N | + (new & Z80_FLAG_S) | + (b & Z80_FLAG_35) | + (new == 0 ? Z80_FLAG_Z : 0) | + (o < b ? Z80_FLAG_C : 0) | + ((o ^ b) & (o ^ new) & 0x80 ? Z80_FLAG_PV : 0) | + ((int32_t)(o & 0x0f) - (int32_t)(b & 0x0f) < 0 ? Z80_FLAG_H : 0); +} + + +#define Z80_AND_A(_z80,_b) ((_z80)->af.f = sz53pTable[(_z80)->af.a&=(_b)]|Z80_FLAG_H) +#define Z80_OR_A(_z80,_b) ((_z80)->af.f = sz53pTable[(_z80)->af.a|=(_b)]) +#define Z80_XOR_A(_z80,_b) ((_z80)->af.f = sz53pTable[(_z80)->af.a^=(_b)]) + + +/* carry unchanged */ +static ZYMOSIS_INLINE uint8_t Z80_DEC8 (Z80Info *z80, uint8_t b) +{ + z80->af.f &= Z80_FLAG_C; + z80->af.f |= Z80_FLAG_N | + (b == 0x80 ? Z80_FLAG_PV : 0) | + (b & 0x0f ? 0 : Z80_FLAG_H) | + sz53Table[(((int)b) - 1) & 0xff]; + return (((int)b) - 1) & 0xff; +} + +/* carry unchanged */ +static ZYMOSIS_INLINE uint8_t Z80_INC8 (Z80Info *z80, uint8_t b) +{ + z80->af.f &= Z80_FLAG_C; + z80->af.f |= + (b == 0x7f ? Z80_FLAG_PV : 0) | + ((b + 1) & 0x0f ? 0 : Z80_FLAG_H ) | + sz53Table[(b + 1) & 0xff]; + return ((b + 1) & 0xff); +} + + +/* cyclic, carry reflects shifted bit */ +static ZYMOSIS_INLINE void Z80_RLCA (Z80Info *z80) +{ + uint8_t c = ((z80->af.a >> 7) & 0x01); + /***/ + z80->af.a = (z80->af.a << 1) | c; + z80->af.f = c | (z80->af.a & Z80_FLAG_35) | (z80->af.f & (Z80_FLAG_PV | Z80_FLAG_Z | Z80_FLAG_S)); +} + +/* cyclic, carry reflects shifted bit */ +static ZYMOSIS_INLINE void Z80_RRCA (Z80Info *z80) +{ + uint8_t c = (z80->af.a & 0x01); + /***/ + z80->af.a = (z80->af.a >> 1) | (c << 7); + z80->af.f = c | (z80->af.a & Z80_FLAG_35) | (z80->af.f & (Z80_FLAG_PV | Z80_FLAG_Z | Z80_FLAG_S)); +} + + +/* cyclic thru carry */ +static ZYMOSIS_INLINE void Z80_RLA (Z80Info *z80) +{ + uint8_t c = ((z80->af.a >> 7) & 0x01); + /***/ + z80->af.a = (z80->af.a << 1) | (z80->af.f & Z80_FLAG_C); + z80->af.f = c | (z80->af.a & Z80_FLAG_35) | (z80->af.f & (Z80_FLAG_PV | Z80_FLAG_Z | Z80_FLAG_S)); +} + +/* cyclic thru carry */ +static ZYMOSIS_INLINE void Z80_RRA (Z80Info *z80) +{ + uint8_t c = (z80->af.a & 0x01); + /***/ + z80->af.a = (z80->af.a >> 1) | ((z80->af.f & Z80_FLAG_C) << 7); + z80->af.f = c | (z80->af.a & Z80_FLAG_35) | (z80->af.f & (Z80_FLAG_PV | Z80_FLAG_Z | Z80_FLAG_S)); +} + +/* cyclic thru carry */ +static ZYMOSIS_INLINE uint8_t Z80_RL (Z80Info *z80, uint8_t b) +{ + uint8_t c = (b >> 7)&Z80_FLAG_C; + /***/ + z80->af.f = sz53pTable[(b = ((b << 1) & 0xff) | (z80->af.f & Z80_FLAG_C))] | c; + return b; +} + + +static ZYMOSIS_INLINE uint8_t Z80_RR (Z80Info *z80, uint8_t b) +{ + uint8_t c = (b & 0x01); + /***/ + z80->af.f = sz53pTable[(b = (b >> 1) | ((z80->af.f & Z80_FLAG_C) << 7))] | c; + return b; +} + +/* cyclic, carry reflects shifted bit */ +static ZYMOSIS_INLINE uint8_t Z80_RLC (Z80Info *z80, uint8_t b) +{ + uint8_t c = ((b >> 7)&Z80_FLAG_C); + /***/ + z80->af.f = sz53pTable[(b = ((b << 1) & 0xff) | c)] | c; + return b; +} + +/* cyclic, carry reflects shifted bit */ +static ZYMOSIS_INLINE uint8_t Z80_RRC (Z80Info *z80, uint8_t b) +{ + uint8_t c = (b & 0x01); + /***/ + z80->af.f = sz53pTable[(b = (b >> 1) | (c << 7))] | c; + return b; +} + +static ZYMOSIS_INLINE uint8_t Z80_SLA (Z80Info *z80, uint8_t b) +{ + uint8_t c = ((b >> 7) & 0x01); + /***/ + z80->af.f = sz53pTable[(b <<= 1)] | c; + return b; +} + +static ZYMOSIS_INLINE uint8_t Z80_SRA (Z80Info *z80, uint8_t b) +{ + uint8_t c = (b & 0x01); + /***/ + z80->af.f = sz53pTable[(b = (b >> 1) | (b & 0x80))] | c; + return b; +} + +static ZYMOSIS_INLINE uint8_t Z80_SLL (Z80Info *z80, uint8_t b) +{ + uint8_t c = ((b >> 7) & 0x01); + /***/ + z80->af.f = sz53pTable[(b = (b << 1) | 0x01)] | c; + return b; +} + +static ZYMOSIS_INLINE uint8_t Z80_SLR (Z80Info *z80, uint8_t b) +{ + uint8_t c = (b & 0x01); + /***/ + z80->af.f = sz53pTable[(b >>= 1)] | c; + return b; +} + + +/* ddvalue+value */ +static ZYMOSIS_INLINE uint16_t Z80_ADD_DD (Z80Info *z80, uint16_t value, uint16_t ddvalue) +{ + static const uint8_t hct[8] = { 0, Z80_FLAG_H, Z80_FLAG_H, Z80_FLAG_H, 0, 0, 0, Z80_FLAG_H }; + uint32_t res = (uint32_t)value + (uint32_t)ddvalue; + uint8_t b = ((value & 0x0800) >> 11) | ((ddvalue & 0x0800) >> 10) | ((res & 0x0800) >> 9); + /***/ + z80->memptr.w = (ddvalue + 1) & 0xffff; + z80->af.f = + (z80->af.f & (Z80_FLAG_PV | Z80_FLAG_Z | Z80_FLAG_S)) | + (res > 0xffff ? Z80_FLAG_C : 0) | + ((res >> 8)&Z80_FLAG_35) | + hct[b]; + return res; +} + +/* ddvalue+value */ +static ZYMOSIS_INLINE uint16_t Z80_ADC_DD (Z80Info *z80, uint16_t value, uint16_t ddvalue) +{ + uint8_t c = (z80->af.f & Z80_FLAG_C); + uint32_t new = (uint32_t)value + (uint32_t)ddvalue + (uint32_t)c; + uint16_t res = (new & 0xffff); + /***/ + z80->memptr.w = (ddvalue + 1) & 0xffff; + z80->af.f = + ((res >> 8)&Z80_FLAG_S35) | + (res == 0 ? Z80_FLAG_Z : 0) | + (new > 0xffff ? Z80_FLAG_C : 0) | + ((value ^ ((~ddvalue) & 0xffff)) & (value ^ new) & 0x8000 ? Z80_FLAG_PV : 0) | + ((value & 0x0fff) + (ddvalue & 0x0fff) + c >= 0x1000 ? Z80_FLAG_H : 0); + return res; +} + +/* ddvalue-value */ +static ZYMOSIS_INLINE uint16_t Z80_SBC_DD (Z80Info *z80, uint16_t value, uint16_t ddvalue) +{ + uint16_t res; + uint8_t tmpB = z80->af.a; + /***/ + z80->memptr.w = (ddvalue + 1) & 0xffff; + z80->af.a = ddvalue & 0xff; + Z80_SBC_A(z80, value & 0xff); + res = z80->af.a; + z80->af.a = (ddvalue >> 8) & 0xff; + Z80_SBC_A(z80, (value >> 8) & 0xff); + res |= (z80->af.a << 8); + z80->af.a = tmpB; + z80->af.f = (res ? z80->af.f & (~Z80_FLAG_Z) : z80->af.f | Z80_FLAG_Z); + return res; +} + + +static ZYMOSIS_INLINE void Z80_BIT (Z80Info *z80, uint8_t bit, uint8_t num, int mptr) +{ + z80->af.f = + Z80_FLAG_H | + (z80->af.f & Z80_FLAG_C) | + (num & Z80_FLAG_35) | + (num & (1 << bit) ? 0 : Z80_FLAG_PV | Z80_FLAG_Z) | + (bit == 7 ? num&Z80_FLAG_S : 0); + if (mptr) z80->af.f = (z80->af.f & ~Z80_FLAG_35) | (z80->memptr.h & Z80_FLAG_35); +} + + +static ZYMOSIS_INLINE void Z80_DAA (Z80Info *z80) +{ + uint8_t tmpI = 0, tmpC = (z80->af.f & Z80_FLAG_C), tmpA = z80->af.a; + /***/ + if ((z80->af.f & Z80_FLAG_H) || (tmpA & 0x0f) > 9) tmpI = 6; + if (tmpC != 0 || tmpA > 0x99) tmpI |= 0x60; + if (tmpA > 0x99) tmpC = Z80_FLAG_C; + if (z80->af.f & Z80_FLAG_N) Z80_SUB_A(z80, tmpI); + else Z80_ADD_A(z80, tmpI); + z80->af.f = (z80->af.f & ~(Z80_FLAG_C | Z80_FLAG_PV)) | tmpC | parityTable[z80->af.a]; +} + + +static ZYMOSIS_INLINE void Z80_RRD_A (Z80Info *z80) +{ + uint8_t tmpB = Z80_PeekB3T(z80, z80->hl.w); + /*IOP(4)*/ + z80->memptr.w = (z80->hl.w + 1) & 0xffff; + Z80_ContentionBy1(z80, z80->hl.w, 4); + Z80_PokeB3T(z80, z80->hl.w, (z80->af.a << 4) | (tmpB >> 4)); + z80->af.a = (z80->af.a & 0xf0) | (tmpB & 0x0f); + z80->af.f = (z80->af.f & Z80_FLAG_C) | sz53pTable[z80->af.a]; +} + +static ZYMOSIS_INLINE void Z80_RLD_A (Z80Info *z80) +{ + uint8_t tmpB = Z80_PeekB3T(z80, z80->hl.w); + /*IOP(4)*/ + z80->memptr.w = (z80->hl.w + 1) & 0xffff; + Z80_ContentionBy1(z80, z80->hl.w, 4); + Z80_PokeB3T(z80, z80->hl.w, (tmpB << 4) | (z80->af.a & 0x0f)); + z80->af.a = (z80->af.a & 0xf0) | (tmpB >> 4); + z80->af.f = (z80->af.f & Z80_FLAG_C) | sz53pTable[z80->af.a]; +} + + +static ZYMOSIS_INLINE void Z80_LD_A_IR (Z80Info *z80, uint8_t ir) +{ + z80->af.a = ir; + z80->prev_was_EIDDR = -1; + Z80_ContentionIRBy1(z80, 1); + z80->af.f = sz53Table[z80->af.a] | (z80->af.f & Z80_FLAG_C) | (z80->iff2 ? Z80_FLAG_PV : 0); +} + + +/******************************************************************************/ +#define INC_R (z80->regR = ((z80->regR+1)&0x7f)|(z80->regR&0x80)) + +#define SET_TRUE_CC \ + switch ((opcode>>3)&0x07) { \ + case 0: trueCC = (z80->af.f&Z80_FLAG_Z) == 0; break; \ + case 1: trueCC = (z80->af.f&Z80_FLAG_Z) != 0; break; \ + case 2: trueCC = (z80->af.f&Z80_FLAG_C) == 0; break; \ + case 3: trueCC = (z80->af.f&Z80_FLAG_C) != 0; break; \ + case 4: trueCC = (z80->af.f&Z80_FLAG_PV) == 0; break; \ + case 5: trueCC = (z80->af.f&Z80_FLAG_PV) != 0; break; \ + case 6: trueCC = (z80->af.f&Z80_FLAG_S) == 0; break; \ + case 7: trueCC = (z80->af.f&Z80_FLAG_S) != 0; break; \ + } + +#define INC_PC (z80->pc = (z80->pc+1)&0xffff) +#define DEC_PC (z80->pc = ((int32_t)(z80->pc)-1)&0xffff) + +#define INC_W(n) ((n) = ((n)+1)&0xffff) +#define DEC_W(n) ((n) = ((int32_t)(n)-1)&0xffff) + +#define XADD_W(n,v) ((n) = ((n)+v)&0xffff) +#define XSUB_W(n,v) ((n) = ((int32_t)(n)-v)&0xffff) + +#define ZADD_W(n,v) ((n) = ((int32_t)(n)+v)&0xffff) + +#define ZADD_WX(n,v) (((int32_t)(n)+v)&0xffff) + +#define INC_B(n) ((n) = ((n)+1)&0xff) +#define DEC_B(n) ((n) = ((int32_t)(n)-1)&0xff) + +/* t1: setting /MREQ & /RD */ +/* t2: memory read */ +/* t3, t4: decode command, increment R */ +#define GET_OPCODE(_opc) do { \ + Z80_Contention(z80, z80->pc, 4, Z80_MREQ_READ|Z80_MEMIO_OPCODE); \ + if (z80->evenM1 && (z80->tstates&0x01)) ++z80->tstates; \ + (_opc) = z80->memReadFn(z80, z80->pc, Z80_MEMIO_OPCODE); \ + z80->pc = (z80->pc+1)&0xffff; \ + z80->regR = ((z80->regR+1)&0x7f)|(z80->regR&0x80); \ +} while (0) + +#define GET_OPCODE_EXT(_opc) do { \ + Z80_Contention(z80, z80->pc, 4, Z80_MREQ_READ|Z80_MEMIO_OPCEXT); \ + (_opc) = z80->memReadFn(z80, z80->pc, Z80_MEMIO_OPCEXT); \ + z80->pc = (z80->pc+1)&0xffff; \ + z80->regR = ((z80->regR+1)&0x7f)|(z80->regR&0x80); \ +} while (0) + + +#define CBX_REPEATED (opcode&0x10) +#define CBX_BACKWARD (opcode&0x08) + + +void Z80_Execute (Z80Info *z80) +{ + uint8_t opcode; + int gotDD, trueCC; /* booleans */ + int disp; + uint8_t tmpB, tmpC, rsrc, rdst; + uint16_t tmpW = 0; /* shut up the compiler; it's wrong but stubborn */ + /***/ + while (z80->tstates < z80->next_event_tstate) + { + if (z80->pagerFn != NULL) z80->pagerFn(z80); + if (z80->checkBPFn != NULL && z80->checkBPFn(z80)) return; + z80->prev_pc = z80->org_pc; + z80->org_pc = z80->pc; + /* read opcode -- OCR(4) */ + GET_OPCODE(opcode); + z80->prev_was_EIDDR = 0; + disp = gotDD = 0; + z80->dd = &z80->hl; + if (z80->halted) + { + DEC_W(z80->pc); + continue; + } + /***/ + if (opcode == 0xdd || opcode == 0xfd) + { + static const uint32_t withIndexBmp[8] = {0x00, 0x700000, 0x40404040, 0x40bf4040, 0x40404040, 0x40404040, 0x0800, 0x00}; + /* IX/IY prefix */ + z80->dd = (opcode == 0xdd ? &z80->ix : &z80->iy); + /* read opcode -- OCR(4) */ + GET_OPCODE_EXT(opcode); + /* test if this instruction have (HL) */ + if (withIndexBmp[opcode >> 5] & (1 << (opcode & 0x1f))) + { + /* 3rd byte is always DISP here */ + disp = Z80_PeekB3TA(z80, z80->pc); + if (disp > 127) disp -= 256; + INC_PC; + z80->memptr.w = ZADD_WX(z80->dd->w, disp); + } + else if (opcode == 0xdd && opcode == 0xfd) + { + /* double prefix; restart main loop */ + z80->prev_was_EIDDR = 1; + continue; + } + gotDD = 1; + } + /* instructions */ + if (opcode == 0xed) + { + z80->dd = &z80->hl; /* а нас -- рать! */ + /* read opcode -- OCR(4) */ + GET_OPCODE_EXT(opcode); + switch (opcode) + { + /* LDI, LDIR, LDD, LDDR */ + case 0xa0: + case 0xb0: + case 0xa8: + case 0xb8: + tmpB = Z80_PeekB3T(z80, z80->hl.w); + Z80_PokeB3T(z80, z80->de.w, tmpB); + /*MWR(5)*/ + Z80_ContentionBy1(z80, z80->de.w, 2); + DEC_W(z80->bc.w); + tmpB = (tmpB + z80->af.a) & 0xff; + /***/ + z80->af.f = + (tmpB & Z80_FLAG_3) | (z80->af.f & (Z80_FLAG_C | Z80_FLAG_Z | Z80_FLAG_S)) | + (z80->bc.w != 0 ? Z80_FLAG_PV : 0) | + (tmpB & 0x02 ? Z80_FLAG_5 : 0); + /***/ + if (CBX_REPEATED) + { + if (z80->bc.w != 0) + { + /*IOP(5)*/ + Z80_ContentionBy1(z80, z80->de.w, 5); + /* do it again */ + XSUB_W(z80->pc, 2); + z80->memptr.w = (z80->pc + 1) & 0xffff; + } + } + if (!CBX_BACKWARD) + { + INC_W(z80->hl.w); + INC_W(z80->de.w); + } + else + { + DEC_W(z80->hl.w); + DEC_W(z80->de.w); + } + break; + /* CPI, CPIR, CPD, CPDR */ + case 0xa1: + case 0xb1: + case 0xa9: + case 0xb9: + /* MEMPTR */ + if (CBX_REPEATED && (!(z80->bc.w == 1 || Z80_PeekBI(z80, z80->hl.w) == z80->af.a))) + { + z80->memptr.w = ZADD_WX(z80->org_pc, 1); + } + else + { + z80->memptr.w = ZADD_WX(z80->memptr.w, (CBX_BACKWARD ? -1 : 1)); + } + /***/ + tmpB = Z80_PeekB3T(z80, z80->hl.w); + /*IOP(5)*/ + Z80_ContentionBy1(z80, z80->hl.w, 5); + DEC_W(z80->bc.w); + /***/ + z80->af.f = + Z80_FLAG_N | + (z80->af.f & Z80_FLAG_C) | + (z80->bc.w != 0 ? Z80_FLAG_PV : 0) | + ((int32_t)(z80->af.a & 0x0f) - (int32_t)(tmpB & 0x0f) < 0 ? Z80_FLAG_H : 0); + /***/ + tmpB = ((int32_t)z80->af.a - (int32_t)tmpB) & 0xff; + /***/ + z80->af.f |= + (tmpB == 0 ? Z80_FLAG_Z : 0) | + (tmpB & Z80_FLAG_S); + /***/ + if (z80->af.f & Z80_FLAG_H) tmpB = ((uint16_t)tmpB - 1) & 0xff; + z80->af.f |= (tmpB & Z80_FLAG_3) | (tmpB & 0x02 ? Z80_FLAG_5 : 0); + /***/ + if (CBX_REPEATED) + { + /* repeated */ + if ((z80->af.f & (Z80_FLAG_Z | Z80_FLAG_PV)) == Z80_FLAG_PV) + { + /*IOP(5)*/ + Z80_ContentionBy1(z80, z80->hl.w, 5); + /* do it again */ + XSUB_W(z80->pc, 2); + } + } + if (CBX_BACKWARD) DEC_W(z80->hl.w); + else INC_W(z80->hl.w); + break; + /* OUTI, OTIR, OUTD, OTDR */ + case 0xa3: + case 0xb3: + case 0xab: + case 0xbb: + DEC_B(z80->bc.b); + /* fallthru */ + /* INI, INIR, IND, INDR */ + case 0xa2: + case 0xb2: + case 0xaa: + case 0xba: + z80->memptr.w = ZADD_WX(z80->bc.w, (CBX_BACKWARD ? -1 : 1)); + /*OCR(5)*/ + Z80_ContentionIRBy1(z80, 1); + if (opcode & 0x01) + { + /* OUT* */ + tmpB = Z80_PeekB3T(z80, z80->hl.w);/*MRD(3)*/ + Z80_PortOut(z80, z80->bc.w, tmpB); + tmpW = ZADD_WX(z80->hl.w, (CBX_BACKWARD ? -1 : 1)); + tmpC = (tmpB + tmpW) & 0xff; + } + else + { + /* IN* */ + tmpB = Z80_PortIn(z80, z80->bc.w); + Z80_PokeB3T(z80, z80->hl.w, tmpB);/*MWR(3)*/ + DEC_B(z80->bc.b); + if (CBX_BACKWARD) tmpC = ((int32_t)tmpB + (int32_t)z80->bc.c - 1) & 0xff; + else tmpC = (tmpB + z80->bc.c + 1) & 0xff; + } + /***/ + z80->af.f = + (tmpB & 0x80 ? Z80_FLAG_N : 0) | + (tmpC < tmpB ? Z80_FLAG_H | Z80_FLAG_C : 0) | + parityTable[(tmpC & 0x07)^z80->bc.b] | + sz53Table[z80->bc.b]; + /***/ + if (CBX_REPEATED) + { + /* repeating commands */ + if (z80->bc.b != 0) + { + uint16_t a = (opcode & 0x01 ? z80->bc.w : z80->hl.w); + /***/ + /*IOP(5)*/ + Z80_ContentionBy1(z80, a, 5); + /* do it again */ + XSUB_W(z80->pc, 2); + } + } + if (CBX_BACKWARD) DEC_W(z80->hl.w); + else INC_W(z80->hl.w); + break; + /* not strings, but some good instructions anyway */ + default: + if ((opcode & 0xc0) == 0x40) + { + /* 0x40...0x7f */ + switch (opcode & 0x07) + { + /* IN r8,(C) */ + case 0: + z80->memptr.w = ZADD_WX(z80->bc.w, 1); + tmpB = Z80_PortIn(z80, z80->bc.w); + z80->af.f = sz53pTable[tmpB] | (z80->af.f & Z80_FLAG_C); + switch ((opcode >> 3) & 0x07) + { + case 0: + z80->bc.b = tmpB; + break; + case 1: + z80->bc.c = tmpB; + break; + case 2: + z80->de.d = tmpB; + break; + case 3: + z80->de.e = tmpB; + break; + case 4: + z80->hl.h = tmpB; + break; + case 5: + z80->hl.l = tmpB; + break; + case 7: + z80->af.a = tmpB; + break; + /* 6 affects only flags */ + } + break; + /* OUT (C),r8 */ + case 1: + z80->memptr.w = ZADD_WX(z80->bc.w, 1); + switch ((opcode >> 3) & 0x07) + { + case 0: + tmpB = z80->bc.b; + break; + case 1: + tmpB = z80->bc.c; + break; + case 2: + tmpB = z80->de.d; + break; + case 3: + tmpB = z80->de.e; + break; + case 4: + tmpB = z80->hl.h; + break; + case 5: + tmpB = z80->hl.l; + break; + case 7: + tmpB = z80->af.a; + break; + default: + tmpB = 0; + break; /*6*/ + } + Z80_PortOut(z80, z80->bc.w, tmpB); + break; + /* SBC HL,rr/ADC HL,rr */ + case 2: + /*IOP(4),IOP(3)*/ + Z80_ContentionIRBy1(z80, 7); + switch ((opcode >> 4) & 0x03) + { + case 0: + tmpW = z80->bc.w; + break; + case 1: + tmpW = z80->de.w; + break; + case 2: + tmpW = z80->hl.w; + break; + default: + tmpW = z80->sp.w; + break; + } + z80->hl.w = (opcode & 0x08 ? Z80_ADC_DD(z80, tmpW, z80->hl.w) : Z80_SBC_DD(z80, tmpW, z80->hl.w)); + break; + /* LD (nn),rr/LD rr,(nn) */ + case 3: + tmpW = Z80_GetWordPC(z80, 0); + z80->memptr.w = (tmpW + 1) & 0xffff; + if (opcode & 0x08) + { + /* LD rr,(nn) */ + switch ((opcode >> 4) & 0x03) + { + case 0: + z80->bc.w = Z80_PeekW6T(z80, tmpW); + break; + case 1: + z80->de.w = Z80_PeekW6T(z80, tmpW); + break; + case 2: + z80->hl.w = Z80_PeekW6T(z80, tmpW); + break; + case 3: + z80->sp.w = Z80_PeekW6T(z80, tmpW); + break; + } + } + else + { + /* LD (nn),rr */ + switch ((opcode >> 4) & 0x03) + { + case 0: + Z80_PokeW6T(z80, tmpW, z80->bc.w); + break; + case 1: + Z80_PokeW6T(z80, tmpW, z80->de.w); + break; + case 2: + Z80_PokeW6T(z80, tmpW, z80->hl.w); + break; + case 3: + Z80_PokeW6T(z80, tmpW, z80->sp.w); + break; + } + } + break; + /* NEG */ + case 4: + tmpB = z80->af.a; + z80->af.a = 0; + Z80_SUB_A(z80, tmpB); + break; + /* RETI/RETN */ + case 5: + /*RETI: 0x4d, 0x5d, 0x6d, 0x7d*/ + /*RETN: 0x45, 0x55, 0x65, 0x75*/ + z80->iff1 = z80->iff2; + z80->memptr.w = z80->pc = Z80_Pop6T(z80); + if (opcode & 0x08) + { + /* RETI */ + if (z80->retiFn != NULL && z80->retiFn(z80, opcode)) return; + } + else + { + /* RETN */ + if (z80->retnFn != NULL && z80->retnFn(z80, opcode)) return; + } + break; + /* IM n */ + case 6: + switch (opcode) + { + case 0x56: + case 0x76: + z80->im = 1; + break; + case 0x5e: + case 0x7e: + z80->im = 2; + break; + default: + z80->im = 0; + break; + } + break; + /* specials */ + case 7: + switch (opcode) + { + /* LD I,A */ + case 0x47: + /*OCR(5)*/ + Z80_ContentionIRBy1(z80, 1); + z80->regI = z80->af.a; + break; + /* LD R,A */ + case 0x4f: + /*OCR(5)*/ + Z80_ContentionIRBy1(z80, 1); + z80->regR = z80->af.a; + break; + /* LD A,I */ + case 0x57: + Z80_LD_A_IR(z80, z80->regI); + break; + /* LD A,R */ + case 0x5f: + Z80_LD_A_IR(z80, z80->regR); + break; + /* RRD */ + case 0x67: + Z80_RRD_A(z80); + break; + /* RLD */ + case 0x6F: + Z80_RLD_A(z80); + break; + } + } + } + else + { + /* slt and other traps */ + if (z80->trapEDFn != NULL && z80->trapEDFn(z80, opcode)) return; + } + break; + } + continue; + } /* 0xed done */ + /***/ + if (opcode == 0xcb) + { + /* shifts and bit operations */ + /* read opcode -- OCR(4) */ + if (!gotDD) + { + GET_OPCODE_EXT(opcode); + } + else + { + Z80_Contention(z80, z80->pc, 3, Z80_MREQ_READ | Z80_MEMIO_OPCEXT); + opcode = z80->memReadFn(z80, z80->pc, Z80_MEMIO_OPCEXT); + Z80_ContentionPCBy1(z80, 2); + INC_PC; + } + if (gotDD) + { + tmpW = ZADD_WX(z80->dd->w, disp); + tmpB = Z80_PeekB3T(z80, tmpW); + Z80_ContentionBy1(z80, tmpW, 1); + } + else + { + switch (opcode & 0x07) + { + case 0: + tmpB = z80->bc.b; + break; + case 1: + tmpB = z80->bc.c; + break; + case 2: + tmpB = z80->de.d; + break; + case 3: + tmpB = z80->de.e; + break; + case 4: + tmpB = z80->hl.h; + break; + case 5: + tmpB = z80->hl.l; + break; + case 6: + tmpB = Z80_PeekB3T(z80, z80->hl.w); + Z80_Contention(z80, z80->hl.w, 1, Z80_MREQ_READ | Z80_MEMIO_DATA); + break; + case 7: + tmpB = z80->af.a; + break; + } + } + switch ((opcode >> 3) & 0x1f) + { + case 0: + tmpB = Z80_RLC(z80, tmpB); + break; + case 1: + tmpB = Z80_RRC(z80, tmpB); + break; + case 2: + tmpB = Z80_RL(z80, tmpB); + break; + case 3: + tmpB = Z80_RR(z80, tmpB); + break; + case 4: + tmpB = Z80_SLA(z80, tmpB); + break; + case 5: + tmpB = Z80_SRA(z80, tmpB); + break; + case 6: + tmpB = Z80_SLL(z80, tmpB); + break; + case 7: + tmpB = Z80_SLR(z80, tmpB); + break; + default: + switch ((opcode >> 6) & 0x03) + { + case 1: + Z80_BIT(z80, (opcode >> 3) & 0x07, tmpB, (gotDD || (opcode & 0x07) == 6)); + break; + case 2: + tmpB &= ~(1 << ((opcode >> 3) & 0x07)); + break; /* RES */ + case 3: + tmpB |= (1 << ((opcode >> 3) & 0x07)); + break; /* SET */ + } + break; + } + /***/ + if ((opcode & 0xc0) != 0x40) + { + /* BITs are not welcome here */ + if (gotDD) + { + /* tmpW was set earlier */ + if ((opcode & 0x07) != 6) Z80_PokeB3T(z80, tmpW, tmpB); + } + switch (opcode & 0x07) + { + case 0: + z80->bc.b = tmpB; + break; + case 1: + z80->bc.c = tmpB; + break; + case 2: + z80->de.d = tmpB; + break; + case 3: + z80->de.e = tmpB; + break; + case 4: + z80->hl.h = tmpB; + break; + case 5: + z80->hl.l = tmpB; + break; + case 6: + Z80_PokeB3T(z80, ZADD_WX(z80->dd->w, disp), tmpB); + break; + case 7: + z80->af.a = tmpB; + break; + } + } + continue; + } /* 0xcb done */ + /* normal things */ + switch (opcode & 0xc0) + { + /* 0x00..0x3F */ + case 0x00: + switch (opcode & 0x07) + { + /* misc,DJNZ,JR,JR cc */ + case 0: + if (opcode & 0x30) + { + /* branches */ + if (opcode & 0x20) + { + /* JR cc */ + switch ((opcode >> 3) & 0x03) + { + case 0: + trueCC = (z80->af.f & Z80_FLAG_Z) == 0; + break; + case 1: + trueCC = (z80->af.f & Z80_FLAG_Z) != 0; + break; + case 2: + trueCC = (z80->af.f & Z80_FLAG_C) == 0; + break; + case 3: + trueCC = (z80->af.f & Z80_FLAG_C) != 0; + break; + default: + trueCC = 0; + break; + } + } + else + { + /* DJNZ/JR */ + if ((opcode & 0x08) == 0) + { + /* DJNZ */ + /*OCR(5)*/ + Z80_ContentionIRBy1(z80, 1); + DEC_B(z80->bc.b); + trueCC = (z80->bc.b != 0); + } + else + { + /* JR */ + trueCC = 1; + } + } + /***/ + disp = Z80_PeekB3TA(z80, z80->pc); + if (trueCC) + { + /* execute branch (relative) */ + /*IOP(5)*/ + if (disp > 127) disp -= 256; + Z80_ContentionPCBy1(z80, 5); + INC_PC; + ZADD_W(z80->pc, disp); + z80->memptr.w = z80->pc; + } + else + { + INC_PC; + } + } + else + { + /* EX AF,AF' or NOP */ + if (opcode != 0) Z80_EXAFAF(z80); + } + break; + /* LD rr,nn/ADD HL,rr */ + case 1: + if (opcode & 0x08) + { + /* ADD HL,rr */ + /*IOP(4),IOP(3)*/ + Z80_ContentionIRBy1(z80, 7); + switch ((opcode >> 4) & 0x03) + { + case 0: + z80->dd->w = Z80_ADD_DD(z80, z80->bc.w, z80->dd->w); + break; + case 1: + z80->dd->w = Z80_ADD_DD(z80, z80->de.w, z80->dd->w); + break; + case 2: + z80->dd->w = Z80_ADD_DD(z80, z80->dd->w, z80->dd->w); + break; + case 3: + z80->dd->w = Z80_ADD_DD(z80, z80->sp.w, z80->dd->w); + break; + } + } + else + { + /* LD rr,nn */ + tmpW = Z80_GetWordPC(z80, 0); + switch ((opcode >> 4) & 0x03) + { + case 0: + z80->bc.w = tmpW; + break; + case 1: + z80->de.w = tmpW; + break; + case 2: + z80->dd->w = tmpW; + break; + case 3: + z80->sp.w = tmpW; + break; + } + } + break; + /* LD xxx,xxx */ + case 2: + switch ((opcode >> 3) & 0x07) + { + /* LD (BC),A */ + case 0: + Z80_PokeB3T(z80, z80->bc.w, z80->af.a); + z80->memptr.l = (z80->bc.c + 1) & 0xff; + z80->memptr.h = z80->af.a; + break; + /* LD A,(BC) */ + case 1: + z80->af.a = Z80_PeekB3T(z80, z80->bc.w); + z80->memptr.w = (z80->bc.w + 1) & 0xffff; + break; + /* LD (DE),A */ + case 2: + Z80_PokeB3T(z80, z80->de.w, z80->af.a); + z80->memptr.l = (z80->de.e+1) & 0xff; + z80->memptr.h = z80->af.a; + break; + /* LD A,(DE) */ + case 3: + z80->af.a = Z80_PeekB3T(z80, z80->de.w); + z80->memptr.w = (z80->de.w + 1) & 0xffff; + break; + /* LD (nn),HL */ + case 4: + tmpW = Z80_GetWordPC(z80, 0); + z80->memptr.w = (tmpW + 1) & 0xffff; + Z80_PokeW6T(z80, tmpW, z80->dd->w); + break; + /* LD HL,(nn) */ + case 5: + tmpW = Z80_GetWordPC(z80, 0); + z80->memptr.w = (tmpW + 1) & 0xffff; + z80->dd->w = Z80_PeekW6T(z80, tmpW); + break; + /* LD (nn),A */ + case 6: + tmpW = Z80_GetWordPC(z80, 0); + z80->memptr.l = (tmpW + 1) & 0xff; + z80->memptr.h = z80->af.a; + Z80_PokeB3T(z80, tmpW, z80->af.a); + break; + /* LD A,(nn) */ + case 7: + tmpW = Z80_GetWordPC(z80, 0); + z80->memptr.w = (tmpW + 1) & 0xffff; + z80->af.a = Z80_PeekB3T(z80, tmpW); + break; + } + break; + /* INC rr/DEC rr */ + case 3: + /*OCR(6)*/ + Z80_ContentionIRBy1(z80, 2); + if (opcode & 0x08) + { + /*DEC*/ + switch ((opcode >> 4) & 0x03) + { + case 0: + DEC_W(z80->bc.w); + break; + case 1: + DEC_W(z80->de.w); + break; + case 2: + DEC_W(z80->dd->w); + break; + case 3: + DEC_W(z80->sp.w); + break; + } + } + else + { + /*INC*/ + switch ((opcode >> 4) & 0x03) + { + case 0: + INC_W(z80->bc.w); + break; + case 1: + INC_W(z80->de.w); + break; + case 2: + INC_W(z80->dd->w); + break; + case 3: + INC_W(z80->sp.w); + break; + } + } + break; + /* INC r8 */ + case 4: + switch ((opcode >> 3) & 0x07) + { + case 0: + z80->bc.b = Z80_INC8(z80, z80->bc.b); + break; + case 1: + z80->bc.c = Z80_INC8(z80, z80->bc.c); + break; + case 2: + z80->de.d = Z80_INC8(z80, z80->de.d); + break; + case 3: + z80->de.e = Z80_INC8(z80, z80->de.e); + break; + case 4: + z80->dd->h = Z80_INC8(z80, z80->dd->h); + break; + case 5: + z80->dd->l = Z80_INC8(z80, z80->dd->l); + break; + case 6: + if (gotDD) + { + DEC_PC; + Z80_ContentionPCBy1(z80, 5); + INC_PC; + } + tmpW = ZADD_WX(z80->dd->w, disp); + tmpB = Z80_PeekB3T(z80, tmpW); + Z80_ContentionBy1(z80, tmpW, 1); + tmpB = Z80_INC8(z80, tmpB); + Z80_PokeB3T(z80, tmpW, tmpB); + break; + case 7: + z80->af.a = Z80_INC8(z80, z80->af.a); + break; + } + break; + /* DEC r8 */ + case 5: + switch ((opcode >> 3) & 0x07) + { + case 0: + z80->bc.b = Z80_DEC8(z80, z80->bc.b); + break; + case 1: + z80->bc.c = Z80_DEC8(z80, z80->bc.c); + break; + case 2: + z80->de.d = Z80_DEC8(z80, z80->de.d); + break; + case 3: + z80->de.e = Z80_DEC8(z80, z80->de.e); + break; + case 4: + z80->dd->h = Z80_DEC8(z80, z80->dd->h); + break; + case 5: + z80->dd->l = Z80_DEC8(z80, z80->dd->l); + break; + case 6: + if (gotDD) + { + DEC_PC; + Z80_ContentionPCBy1(z80, 5); + INC_PC; + } + tmpW = ZADD_WX(z80->dd->w, disp); + tmpB = Z80_PeekB3T(z80, tmpW); + Z80_ContentionBy1(z80, tmpW, 1); + tmpB = Z80_DEC8(z80, tmpB); + Z80_PokeB3T(z80, tmpW, tmpB); + break; + case 7: + z80->af.a = Z80_DEC8(z80, z80->af.a); + break; + } + break; + /* LD r8,n */ + case 6: + tmpB = Z80_PeekB3TA(z80, z80->pc); + INC_PC; + switch ((opcode >> 3) & 0x07) + { + case 0: + z80->bc.b = tmpB; + break; + case 1: + z80->bc.c = tmpB; + break; + case 2: + z80->de.d = tmpB; + break; + case 3: + z80->de.e = tmpB; + break; + case 4: + z80->dd->h = tmpB; + break; + case 5: + z80->dd->l = tmpB; + break; + case 6: + if (gotDD) + { + DEC_PC; + Z80_ContentionPCBy1(z80, 2); + INC_PC; + } + tmpW = ZADD_WX(z80->dd->w, disp); + Z80_PokeB3T(z80, tmpW, tmpB); + break; + case 7: + z80->af.a = tmpB; + break; + } + break; + /* swim-swim-hungry */ + case 7: + switch ((opcode >> 3) & 0x07) + { + case 0: + Z80_RLCA(z80); + break; + case 1: + Z80_RRCA(z80); + break; + case 2: + Z80_RLA(z80); + break; + case 3: + Z80_RRA(z80); + break; + case 4: + Z80_DAA(z80); + break; + case 5: /* CPL */ + z80->af.a ^= 0xff; + z80->af.f = (z80->af.a & Z80_FLAG_35) | (Z80_FLAG_N | Z80_FLAG_H) | (z80->af.f & (Z80_FLAG_C | Z80_FLAG_PV | Z80_FLAG_Z | Z80_FLAG_S)); + break; + case 6: /* SCF */ + z80->af.f = (z80->af.f & (Z80_FLAG_PV | Z80_FLAG_Z | Z80_FLAG_S)) | (z80->af.a & Z80_FLAG_35) | Z80_FLAG_C; + break; + case 7: /* CCF */ + tmpB = z80->af.f & Z80_FLAG_C; + z80->af.f = (z80->af.f & (Z80_FLAG_PV | Z80_FLAG_Z | Z80_FLAG_S)) | (z80->af.a & Z80_FLAG_35); + z80->af.f |= tmpB ? Z80_FLAG_H : Z80_FLAG_C; + break; + } + break; + } + break; + /* 0x40..0x7F (LD r8,r8) */ + case 0x40: + if (opcode == 0x76) + { + z80->halted = 1; /* HALT */ + DEC_W(z80->pc); + continue; + } + rsrc = (opcode & 0x07); + rdst = ((opcode >> 3) & 0x07); + switch (rsrc) + { + case 0: + tmpB = z80->bc.b; + break; + case 1: + tmpB = z80->bc.c; + break; + case 2: + tmpB = z80->de.d; + break; + case 3: + tmpB = z80->de.e; + break; + case 4: + tmpB = (gotDD && rdst == 6 ? z80->hl.h : z80->dd->h); + break; + case 5: + tmpB = (gotDD && rdst == 6 ? z80->hl.l : z80->dd->l); + break; + case 6: + if (gotDD) + { + DEC_PC; + Z80_ContentionPCBy1(z80, 5); + INC_PC; + } + tmpW = ZADD_WX(z80->dd->w, disp); + tmpB = Z80_PeekB3T(z80, tmpW); + break; + case 7: + tmpB = z80->af.a; + break; + } + switch (rdst) + { + case 0: + z80->bc.b = tmpB; + break; + case 1: + z80->bc.c = tmpB; + break; + case 2: + z80->de.d = tmpB; + break; + case 3: + z80->de.e = tmpB; + break; + case 4: + if (gotDD && rsrc == 6) z80->hl.h = tmpB; + else z80->dd->h = tmpB; + break; + case 5: + if (gotDD && rsrc == 6) z80->hl.l = tmpB; + else z80->dd->l = tmpB; + break; + case 6: + if (gotDD) + { + DEC_PC; + Z80_ContentionPCBy1(z80, 5); + INC_PC; + } + tmpW = ZADD_WX(z80->dd->w, disp); + Z80_PokeB3T(z80, tmpW, tmpB); + break; + case 7: + z80->af.a = tmpB; + break; + } + break; + /* 0x80..0xBF (ALU A,r8) */ + case 0x80: + switch (opcode & 0x07) + { + case 0: + tmpB = z80->bc.b; + break; + case 1: + tmpB = z80->bc.c; + break; + case 2: + tmpB = z80->de.d; + break; + case 3: + tmpB = z80->de.e; + break; + case 4: + tmpB = z80->dd->h; + break; + case 5: + tmpB = z80->dd->l; + break; + case 6: + if (gotDD) + { + DEC_PC; + Z80_ContentionPCBy1(z80, 5); + INC_PC; + } + tmpW = ZADD_WX(z80->dd->w, disp); + tmpB = Z80_PeekB3T(z80, tmpW); + break; + case 7: + tmpB = z80->af.a; + break; + } + switch ((opcode >> 3) & 0x07) + { + case 0: + Z80_ADD_A(z80, tmpB); + break; + case 1: + Z80_ADC_A(z80, tmpB); + break; + case 2: + Z80_SUB_A(z80, tmpB); + break; + case 3: + Z80_SBC_A(z80, tmpB); + break; + case 4: + Z80_AND_A(z80, tmpB); + break; + case 5: + Z80_XOR_A(z80, tmpB); + break; + case 6: + Z80_OR_A(z80, tmpB); + break; + case 7: + Z80_CP_A(z80, tmpB); + break; + } + break; + /* 0xC0..0xFF */ + case 0xC0: + switch (opcode & 0x07) + { + /* RET cc */ + case 0: + Z80_ContentionIRBy1(z80, 1); + SET_TRUE_CC + if (trueCC) z80->memptr.w = z80->pc = Z80_Pop6T(z80); + break; + /* POP rr/special0 */ + case 1: + if (opcode & 0x08) + { + /* special 0 */ + switch ((opcode >> 4) & 0x03) + { + /* RET */ + case 0: + z80->memptr.w = z80->pc = Z80_Pop6T(z80); + break; + /* EXX */ + case 1: + Z80_EXX(z80); + break; + /* JP (HL) */ + case 2: + z80->pc = z80->dd->w; + break; + /* LD SP,HL */ + case 3: + /*OCR(6)*/ + Z80_ContentionIRBy1(z80, 2); + z80->sp.w = z80->dd->w; + break; + } + } + else + { + /* POP rr */ + tmpW = Z80_Pop6T(z80); + switch ((opcode >> 4) & 0x03) + { + case 0: + z80->bc.w = tmpW; + break; + case 1: + z80->de.w = tmpW; + break; + case 2: + z80->dd->w = tmpW; + break; + case 3: + z80->af.w = tmpW; + break; + } + } + break; + /* JP cc,nn */ + case 2: + SET_TRUE_CC + z80->memptr.w = Z80_GetWordPC(z80, 0); + if (trueCC) z80->pc = z80->memptr.w; + break; + /* special1/special3 */ + case 3: + switch ((opcode >> 3) & 0x07) + { + /* JP nn */ + case 0: + z80->memptr.w = z80->pc = Z80_GetWordPC(z80, 0); + break; + /* OUT (n),A */ + case 2: + tmpW = Z80_PeekB3TA(z80, z80->pc); + INC_PC; + z80->memptr.l = (tmpW + 1) & 0xff; + z80->memptr.h = z80->af.a; + tmpW |= (((uint16_t)(z80->af.a)) << 8); + Z80_PortOut(z80, tmpW, z80->af.a); + break; + /* IN A,(n) */ + case 3: + tmpW = (((uint16_t)(z80->af.a)) << 8) | Z80_PeekB3TA(z80, z80->pc); + INC_PC; + z80->memptr.w = (tmpW + 1) & 0xffff; + z80->af.a = Z80_PortIn(z80, tmpW); + break; + /* EX (SP),HL */ + case 4: + /*SRL(3),SRH(4)*/ + tmpW = Z80_PeekW6T(z80, z80->sp.w); + Z80_ContentionBy1(z80, (z80->sp.w + 1) & 0xffff, 1); + /*SWL(3),SWH(5)*/ + Z80_PokeW6TInv(z80, z80->sp.w, z80->dd->w); + Z80_ContentionBy1(z80, z80->sp.w, 2); + z80->memptr.w = z80->dd->w = tmpW; + break; + /* EX DE,HL */ + case 5: + tmpW = z80->de.w; + z80->de.w = z80->hl.w; + z80->hl.w = tmpW; + break; + /* DI */ + case 6: + z80->iff1 = z80->iff2 = 0; + break; + /* EI */ + case 7: + z80->iff1 = z80->iff2 = 1; + z80->prev_was_EIDDR = 1; + break; + } + break; + /* CALL cc,nn */ + case 4: + SET_TRUE_CC + z80->memptr.w = Z80_GetWordPC(z80, trueCC); + if (trueCC) + { + Z80_Push6T(z80, z80->pc); + z80->pc = z80->memptr.w; + } + break; + /* PUSH rr/special2 */ + case 5: + if (opcode & 0x08) + { + if (((opcode >> 4) & 0x03) == 0) + { + /* CALL */ + z80->memptr.w = tmpW = Z80_GetWordPC(z80, 1); + Z80_Push6T(z80, z80->pc); + z80->pc = tmpW; + } + } + else + { + /* PUSH rr */ + /*OCR(5)*/ + Z80_ContentionIRBy1(z80, 1); + switch ((opcode >> 4) & 0x03) + { + case 0: + tmpW = z80->bc.w; + break; + case 1: + tmpW = z80->de.w; + break; + case 2: + tmpW = z80->dd->w; + break; + default: + tmpW = z80->af.w; + break; + } + Z80_Push6T(z80, tmpW); + } + break; + /* ALU A,n */ + case 6: + tmpB = Z80_PeekB3TA(z80, z80->pc); + INC_PC; + switch ((opcode >> 3) & 0x07) + { + case 0: + Z80_ADD_A(z80, tmpB); + break; + case 1: + Z80_ADC_A(z80, tmpB); + break; + case 2: + Z80_SUB_A(z80, tmpB); + break; + case 3: + Z80_SBC_A(z80, tmpB); + break; + case 4: + Z80_AND_A(z80, tmpB); + break; + case 5: + Z80_XOR_A(z80, tmpB); + break; + case 6: + Z80_OR_A(z80, tmpB); + break; + case 7: + Z80_CP_A(z80, tmpB); + break; + } + break; + /* RST nnn */ + case 7: + /*OCR(5)*/ + Z80_ContentionIRBy1(z80, 1); + Z80_Push6T(z80, z80->pc); + z80->memptr.w = z80->pc = opcode & 0x38; + break; + } + break; + } /* end switch */ + } +} + + +int32_t Z80_ExecuteStep (Z80Info *z80) +{ + int32_t one = z80->next_event_tstate, ots = z80->tstates; + /***/ + z80->next_event_tstate = ots + 1; + Z80_Execute(z80); + z80->next_event_tstate = one; + return z80->tstates - ots; +} + + +int32_t Z80_ExecuteTS (Z80Info *z80, int32_t tstates) +{ + if (tstates > 0) + { + z80->tstates = 0; + z80->next_event_tstate = tstates; + Z80_Execute(z80); + return z80->tstates; + } + return 0; +} + + +/******************************************************************************/ +/* changes z80->tstates if interrupt occurs */ +int Z80_Interrupt (Z80Info *z80) +{ + uint16_t a; + int ots = z80->tstates; + /***/ + if (z80->prev_was_EIDDR < 0) + { + z80->prev_was_EIDDR = 0; /* Z80 bug */ + z80->af.f &= ~Z80_FLAG_PV; + } + if (z80->prev_was_EIDDR || !z80->iff1) return 0; /* not accepted */ + if (z80->halted) + { + z80->halted = 0; + INC_PC; + } + z80->iff1 = z80->iff2 = 0; /* disable interrupts */ + /***/ + switch ((z80->im &= 0x03)) + { + case 3: /* ??? */ + z80->im = 0; + case 0: /* take instruction from the bus (for now we assume that reading from bus always returns 0xff) */ + /* with a CALL nnnn on the data bus, it takes 19 cycles: */ + /* M1 cycle: 7 T to acknowledge interrupt (where exactly data bus reading occures?) */ + /* M2 cycle: 3 T to read low byte of 'nnnn' from data bus */ + /* M3 cycle: 3 T to read high byte of 'nnnn' and decrement SP */ + /* M4 cycle: 3 T to write high byte of PC to the stack and decrement SP */ + /* M5 cycle: 3 T to write low byte of PC and jump to 'nnnn' */ + z80->tstates += 6; + case 1: /* just do RST #38 */ + INC_R; + z80->tstates += 7; /* M1 cycle: 7 T to acknowledge interrupt and decrement SP */ + /* M2 cycle: 3 T states write high byte of PC to the stack and decrement SP */ + /* M3 cycle: 3 T states write the low byte of PC and jump to #0038 */ + Z80_Push6T(z80, z80->pc); + z80->memptr.w = z80->pc = 0x38; + break; + case 2: + INC_R; + z80->tstates += 7; /* M1 cycle: 7 T to acknowledge interrupt and decrement SP */ + /* M2 cycle: 3 T states write high byte of PC to the stack and decrement SP */ + /* M3 cycle: 3 T states write the low byte of PC */ + Z80_Push6T(z80, z80->pc); + /* M4 cycle: 3 T to read high byte from the interrupt vector */ + /* M5 cycle: 3 T to read low byte from bus and jump to interrupt routine */ + a = (((uint16_t)z80->regI) << 8) | 0xff; + z80->memptr.w = z80->pc = Z80_PeekW6T(z80, a); + break; + } + return z80->tstates - ots; /* accepted */ +} + + +int Z80_NMI (Z80Info *z80) +{ + int ots = z80->tstates; + /***/ + /* emulate Z80 bug with interrupted LD A,I/R */ + /*if (z80->prev_was_EIDDR < 0) { z80->prev_was_EIDDR = 0; z80->af.f &= ~Z80_FLAG_PV; }*/ + /*if (z80->prev_was_EIDDR) return 0;*/ + z80->prev_was_EIDDR = 0; /* don't care */ + if (z80->halted) + { + z80->halted = 0; + INC_PC; + } + INC_R; + z80->iff1 = 0; /* IFF2 is not changed */ + z80->tstates += 5; /* M1 cycle: 5 T states to do an opcode read and decrement SP */ + /* M2 cycle: 3 T states write high byte of PC to the stack and decrement SP */ + /* M3 cycle: 3 T states write the low byte of PC and jump to #0066 */ + Z80_Push6T(z80, z80->pc); + z80->memptr.w = z80->pc = 0x66; + return z80->tstates - ots; +} + + +/******************************************************************************/ +uint16_t Z80_Pop (Z80Info *z80) +{ + uint16_t res = Z80_PeekBI(z80, z80->sp.w); + /***/ + z80->sp.w = (z80->sp.w + 1) & 0xffff; + res |= ((uint16_t)Z80_PeekBI(z80, z80->sp.w)) << 8; + z80->sp.w = (z80->sp.w + 1) & 0xffff; + return res; +} + + +void Z80_Push (Z80Info *z80, uint16_t value) +{ + z80->sp.w = (((int32_t)z80->sp.w) - 1) & 0xffff; + Z80_PokeBI(z80, z80->sp.w, (value >> 8) & 0xff); + z80->sp.w = (((int32_t)z80->sp.w) - 1) & 0xffff; + Z80_PokeBI(z80, z80->sp.w, value & 0xff); +} diff --git a/zymosis.h b/zymosis.h new file mode 100644 index 0000000..6b2bb9a --- /dev/null +++ b/zymosis.h @@ -0,0 +1,214 @@ +/* + * Z80 CPU emulation engine v0.0.3b + * coded by Ketmar // Vampire Avalon + * + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + */ +#ifndef _ZYMOSIS_H_ +#define _ZYMOSIS_H_ + +#define ZYMOSIS_LITTLE_ENDIAN + +/* define either ZYMOSIS_LITTLE_ENDIAN or ZYMOSIS_BIG_ENDIAN */ + +#if !defined(ZYMOSIS_LITTLE_ENDIAN) && !defined(ZYMOSIS_BIG_ENDIAN) +# error wtf?! Zymosis endiannes is not defined! +#endif + +#if defined(ZYMOSIS_LITTLE_ENDIAN) && defined(ZYMOSIS_BIG_ENDIAN) +# error wtf?! Zymosis endiannes double defined! are you nuts? +#endif + +#if defined(__GNUC__) +# ifndef ZYMOSIS_PACKED +# define ZYMOSIS_PACKED __attribute__((packed)) __attribute__((gcc_struct)) +# endif +# ifndef ZYMOSIS_INLINE +# define ZYMOSIS_INLINE __inline +# endif +#else +# ifndef ZYMOSIS_PACKED +# define ZYMOSIS_PACKED +# endif +# ifndef ZYMOSIS_INLINE +# define ZYMOSIS_INLINE +# endif +#endif + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/* flag masks */ +enum { + Z80_FLAG_C = 0x01, + Z80_FLAG_N = 0x02, + Z80_FLAG_PV= 0x04, + Z80_FLAG_3 = 0x08, + Z80_FLAG_H = 0x10, + Z80_FLAG_5 = 0x20, + Z80_FLAG_Z = 0x40, + Z80_FLAG_S = 0x80, + + Z80_FLAG_35 = Z80_FLAG_3|Z80_FLAG_5, + Z80_FLAG_S35 = Z80_FLAG_S|Z80_FLAG_3|Z80_FLAG_5 +}; + + +typedef union ZYMOSIS_PACKED { + uint16_t w; +#ifdef ZYMOSIS_LITTLE_ENDIAN + struct ZYMOSIS_PACKED { uint8_t c, b; }; + struct ZYMOSIS_PACKED { uint8_t e, d; }; + struct ZYMOSIS_PACKED { uint8_t l, h; }; + struct ZYMOSIS_PACKED { uint8_t f, a; }; + struct ZYMOSIS_PACKED { uint8_t xl, xh; }; + struct ZYMOSIS_PACKED { uint8_t yl, yh; }; +#else + struct ZYMOSIS_PACKED { uint8_t b, c; }; + struct ZYMOSIS_PACKED { uint8_t d, e; }; + struct ZYMOSIS_PACKED { uint8_t h, l; }; + struct ZYMOSIS_PACKED { uint8_t a, f; }; + struct ZYMOSIS_PACKED { uint8_t xh, xl; }; + struct ZYMOSIS_PACKED { uint8_t yh, yl; }; +#endif +} Z80WordReg; + + +typedef enum { + Z80_MEMIO_OPCODE = 0x00, /* reading opcode */ + Z80_MEMIO_OPCEXT = 0x01, /* 'ext' opcode (after CB/ED/DD/FD prefix) */ + Z80_MEMIO_OPCARG = 0x02, /* opcode argument (jump destination, register value, etc) */ + Z80_MEMIO_DATA = 0x03, /* reading/writing data */ + Z80_MEMIO_OTHER = 0x04, /* other 'internal' reads (for memptr, etc; don't do contention, breakpoints or so) */ + Z80_MEMIO_MASK = 0x0f, + /* values for memory contention */ + Z80_MREQ_NONE = 0x00, + Z80_MREQ_WRITE = 0x10, + Z80_MREQ_READ = 0x20, + Z80_MREQ_MASK = 0xf0 +} Z80MemIOType; + + +typedef enum { + Z80_PIO_NORMAL = 0x00, /* normal call in Z80 execution loop */ + Z80_PIO_INTERNAL = 0x01, /* call from debugger or other place outside of Z80 execution loop */ + /* flags for port contention */ + Z80_PIOFLAG_IN = 0x10, /* doing 'in' if set */ + Z80_PIOFLAG_EARLY = 0x20 /* 'early' port contetion, if set */ +} Z80PIOType; + + +typedef struct Z80Info Z80Info; + +/* will be called when memory contention is necessary */ +/* must increase z80->tstates to at least 'tstates' arg */ +/* mio: Z80_MEMIO_xxx | Z80_MREQ_xxx */ +/* Zymosis will never call this CB for Z80_MEMIO_OTHER memory acces */ +typedef void (*Z80ContentionCB) (Z80Info *z80, uint16_t addr, int tstates, Z80MemIOType mio); + +/* will be called when port contention is necessary */ +/* must increase z80->tstates to at least 'tstates' arg */ +/* pio: can contain only Z80_PIOFLAG_xxx flags */ +/* `tstates` is always 1 when Z80_PIOFLAG_EARLY is set and 2 otherwise */ +typedef void (*Z80PortContentionCB) (Z80Info *z80, uint16_t port, int tstates, Z80PIOType pio); + +/* miot: only Z80_MEMIO_xxx, no need in masking */ +typedef uint8_t (*Z80MemReadCB) (Z80Info *z80, uint16_t addr, Z80MemIOType miot); +typedef void (*Z80MemWriteCB) (Z80Info *z80, uint16_t addr, uint8_t value, Z80MemIOType miot); + +/* pio: only Z80_PIO_xxx, no need in masking */ +typedef uint8_t (*Z80PortInCB) (Z80Info *z80, uint16_t port, Z80PIOType pio); +typedef void (*Z80PortOutCB) (Z80Info *z80, uint16_t port, uint8_t value, Z80PIOType pio); + +/* return !0 to exit immediately */ +typedef int (*Z80EDTrapCB) (Z80Info *z80, uint8_t trapCode); + +/* return !0 to break */ +typedef int (*Z80CheckBPCB) (Z80Info *z80); + +typedef void (*Z80PagerCB) (Z80Info *z80); + + +struct Z80Info { + /* registers */ + Z80WordReg bc, de, hl, af, sp, ix, iy; + /* alternate registers */ + Z80WordReg bcx, dex, hlx, afx; + Z80WordReg *dd; /* pointer to current HL/IX/IY (inside this struct) for the current command */ + Z80WordReg memptr; + uint16_t pc; /* program counter */ + uint16_t prev_pc; /* first byte of the previous command */ + uint16_t org_pc; /* first byte of the current command */ + uint8_t regI; + uint8_t regR; + int iff1, iff2; /* boolean */ + uint8_t im; /* IM (0-2) */ + int halted; /* boolean; is CPU halted? main progam must manually reset this flag when it's appropriate */ + int32_t tstates; /* t-states passed from previous interrupt (0-...) */ + int32_t next_event_tstate; /* Z80Execute() will exit when tstates>=next_event_tstate */ + int prev_was_EIDDR; /* 1: previous instruction was EI/FD/DD? (they blocks /INT); -1: prev vas LD A,I or LD A,R */ + /* Zymosis will reset this flag only if it executed at least one instruction */ + int evenM1; /* boolean; emulate 128K/Scorpion M1 contention? */ + Z80MemReadCB memReadFn; + Z80MemWriteCB memWriteFn; + Z80ContentionCB contentionFn; /* can be NULL */ + /* port I/O functions should add 4 t-states by themselves */ + Z80PortInCB portInFn; /* in: +3; do read; +1 */ + Z80PortOutCB portOutFn; /* out: +1; do out; +3 */ + Z80PortContentionCB portContentionFn; /* can be NULL */ + /* RETI/RETN traps; called with opcode, *AFTER* iff changed and return address set; return !0 to break execution */ + Z80EDTrapCB retiFn; + Z80EDTrapCB retnFn; + /***/ + Z80EDTrapCB trapEDFn; /* can be NULL */ + /* called when invalid ED command found */ + /* PC points to the next instruction */ + /* trapCode=0xFB: */ + /* .SLT trap */ + /* HL: address to load; */ + /* A: A --> level number */ + /* return: CARRY complemented --> error */ + Z80PagerCB pagerFn; /* can be NULL */ + /* pagerFn is called before fetching opcode to allow, for example, TR-DOS ROM paging in/out */ + Z80CheckBPCB checkBPFn; /* can be NULL */ + /* checkBPFn is called just after pagerFn (before fetching opcode) */ + /* emulator can check various breakpoint conditions there */ + /* and return non-zero to immediately stop executing and return from Z80_Execute[XXX]() */ + /***/ + void *user; /* arbitrary user data */ +}; + + +/******************************************************************************/ +/* Z80InitTables() should be called before anyting else! */ +extern void Z80_InitTables (void); /* this will be automatically called by Z80_Reset() */ + +extern void Z80_ResetCallbacks (Z80Info *z80); +extern void Z80_Reset (Z80Info *z80); +extern void Z80_Execute (Z80Info *z80); +extern int32_t Z80_ExecuteStep (Z80Info *z80); /* returns number of executed ticks */ +extern int Z80_Interrupt (Z80Info *z80); /* !0: interrupt was accepted (returns # of t-states eaten); changes z80->tstates if interrupt occurs */ +extern int Z80_NMI (Z80Info *z80); /* !0: interrupt was accepted (returns # of t-states eaten); changes z80->tstates if interrupt occurs */ + +/* without contention, using Z80_MEMIO_OTHER */ +extern uint16_t Z80_Pop (Z80Info *z80); +extern void Z80_Push (Z80Info *z80, uint16_t value); + +/* execute at least 'tstates' t-states; return real number of executed t-states */ +/* WARNING: this function resets both z80->tstates and z80->next_event_tstate! */ +extern int32_t Z80_ExecuteTS (Z80Info *z80, int32_t tstates); + + +#ifdef __cplusplus +} +#endif +#endif -- cgit v1.2.3