From 035608b04ad0cceb71ba010e70361d718e285222 Mon Sep 17 00:00:00 2001 From: Toby Jaffey Date: Mon, 15 Dec 2025 00:37:12 +0000 Subject: [PATCH] Add agnes NES emulator (very slow). Add -W -H width and height options to host-sdl --- README.md | 1 + apps/Makefile | 2 + apps/agnes/Makefile | 18 + apps/agnes/build.zig | 74 + apps/agnes/build.zig.zon | 40 + apps/agnes/src/SDL_scancode.h | 436 ++++++ apps/agnes/src/agnes.c | 2583 +++++++++++++++++++++++++++++++++ apps/agnes/src/agnes.h | 88 ++ apps/agnes/src/console.zig | 49 + apps/agnes/src/croom.nes | Bin 0 -> 24592 bytes apps/agnes/src/main.zig | 103 ++ apps/agnes/src/uvm.zig | 88 ++ hosts/host-sdl/host-sdl.c | 12 +- precompiled/agnes.bin | Bin 0 -> 88648 bytes 14 files changed, 3491 insertions(+), 3 deletions(-) create mode 100644 apps/agnes/Makefile create mode 100644 apps/agnes/build.zig create mode 100644 apps/agnes/build.zig.zon create mode 100644 apps/agnes/src/SDL_scancode.h create mode 100644 apps/agnes/src/agnes.c create mode 100644 apps/agnes/src/agnes.h create mode 100644 apps/agnes/src/console.zig create mode 100644 apps/agnes/src/croom.nes create mode 100644 apps/agnes/src/main.zig create mode 100644 apps/agnes/src/uvm.zig create mode 100755 precompiled/agnes.bin diff --git a/README.md b/README.md index 93e4754..8af9f87 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ int main(int argc, char *argv[]) { * [apps/zigalloc](apps/zigalloc) Demonstration of using extram with zig allocator * [apps/zigdoom](apps/zigdoom) Port of PureDOOM (making use of Zig to provide an allocator and libc like functions) * [apps/tinygl](apps/tinygl) TinyGL gears (softfp stress test) + * [apps/agnes](apps/agnes) Nintendo Entertainment System emulator (currently very slow) * Assembly sample apps * [apps/hello-asm](apps/hello-asm) Minimal hello world assembly * VM host as an app diff --git a/apps/Makefile b/apps/Makefile index 47a4daf..85e1d05 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -16,6 +16,7 @@ all: (cd gfx && make) (cd zigdoom && make) (cd tinygl && make) + (cd agnes && make) clean: (cd sketch && make clean) @@ -35,4 +36,5 @@ clean: (cd gfx && make clean) (cd zigdoom && make clean) (cd tinygl && make clean) + (cd agnes && make clean) diff --git a/apps/agnes/Makefile b/apps/agnes/Makefile new file mode 100644 index 0000000..e9cb51e --- /dev/null +++ b/apps/agnes/Makefile @@ -0,0 +1,18 @@ +PROJECT=agnes +TOPDIR=../.. + +HEAP_SIZE=$(shell echo "1024 * 1024 * 1" | bc) +HOST_EXTRA=-e ${HEAP_SIZE} -i 99999999 -W 256 -H 240 + +all: + @# zig's objcopy is broken, so use external tool + @# https://ziggit.dev/t/addobjcopy-producing-zero-padding-at-start-of-binary/13384 + zig build -Dheapsize=${HEAP_SIZE} && ${PREFIX}objcopy zig-out/bin/${PROJECT} -O binary ${PROJECT}.bin + +clean: clean_common + rm -rf zig-out .zig-cache + +test: all + ${TOPDIR}/hosts/host-sdl/host-sdl ${HOST_EXTRA} ${PROJECT}.bin + +include ${TOPDIR}/apps/common/makefile.common diff --git a/apps/agnes/build.zig b/apps/agnes/build.zig new file mode 100644 index 0000000..a4e95f5 --- /dev/null +++ b/apps/agnes/build.zig @@ -0,0 +1,74 @@ +const std = @import("std"); +const CrossTarget = @import("std").zig.CrossTarget; +const Target = @import("std").Target; +const Feature = @import("std").Target.Cpu.Feature; + +pub fn build(b: *std.Build) void { + var options = b.addOptions(); + const heapsize = b.option(u32, "heapsize", "heap size in bytes") orelse 0; // -Dheapsize=u32 + options.addOption(u32, "heapsize", heapsize); + + const features = Target.riscv.Feature; + var disabled_features = Feature.Set.empty; + var enabled_features = Feature.Set.empty; + + // disable all CPU extensions + disabled_features.addFeature(@intFromEnum(features.a)); + disabled_features.addFeature(@intFromEnum(features.c)); + disabled_features.addFeature(@intFromEnum(features.d)); + disabled_features.addFeature(@intFromEnum(features.e)); + disabled_features.addFeature(@intFromEnum(features.f)); + // except multiply + enabled_features.addFeature(@intFromEnum(features.m)); + + const target = b.resolveTargetQuery(.{ + .cpu_arch = Target.Cpu.Arch.riscv32, + .os_tag = Target.Os.Tag.freestanding, + .abi = Target.Abi.none, + .cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32}, + .cpu_features_sub = disabled_features, + .cpu_features_add = enabled_features + }); + + const exe = b.addExecutable(.{ + .name = "agnes", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = .ReleaseFast, + }), + }); + exe.root_module.addOptions("buildopts", options); + + // add zeptolibc + const zeptolibc_dep = b.dependency("zeptolibc", .{ + .target = target, + .optimize = .ReleaseFast, + }); + exe.root_module.addImport("zeptolibc", zeptolibc_dep.module("zeptolibc")); + exe.root_module.addIncludePath(zeptolibc_dep.path("include")); + exe.root_module.addIncludePath(zeptolibc_dep.path("include/zeptolibc")); + + exe.addCSourceFiles(.{ + .files = &.{ + "src/agnes.c", + }, + .flags = &.{ "-Wall", "-fno-sanitize=undefined" } + }); + exe.addIncludePath(b.path("src/")); + + b.installArtifact(exe); + + exe.addAssemblyFile(b.path("../common/crt0.S")); + exe.setLinkerScript(b.path("../common/linker.ld")); + exe.addIncludePath(b.path("../../common")); + exe.addIncludePath(b.path("../common")); + + const bin = b.addObjCopy(exe.getEmittedBin(), .{ + .format = .bin, + }); + bin.step.dependOn(&exe.step); + + const copy_bin = b.addInstallBinFile(bin.getOutput(), "agnes.bin"); + b.default_step.dependOn(©_bin.step); +} diff --git a/apps/agnes/build.zig.zon b/apps/agnes/build.zig.zon new file mode 100644 index 0000000..1f5273d --- /dev/null +++ b/apps/agnes/build.zig.zon @@ -0,0 +1,40 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .zigdoom, + .fingerprint = 0xf734231076ec3b9f, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.1", + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + //.minimum_zig_version = "0.11.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + .zeptolibc = .{ + .url = "git+https://github.com/ringtailsoftware/zeptolibc.git#d787abfdd597bee5616a439f12393e11fb370822", + .hash = "zeptolibc-0.0.1-T3flJ4M4AAAEx1K1DS-SmkmuXvJJ3JqnNHIw4Aqo0PfD", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/apps/agnes/src/SDL_scancode.h b/apps/agnes/src/SDL_scancode.h new file mode 100644 index 0000000..3c9ad77 --- /dev/null +++ b/apps/agnes/src/SDL_scancode.h @@ -0,0 +1,436 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_scancode.h + * + * \brief Defines keyboard scancodes. + */ + +#ifndef SDL_scancode_h_ +#define SDL_scancode_h_ + +//#include + +/** + * \brief The SDL keyboard scancode representation. + * + * Values of this type are used to represent keyboard keys, among other places + * in the \link SDL_Keysym::scancode key.keysym.scancode \endlink field of the + * SDL_Event structure. + * + * The values in this enumeration are based on the USB usage page standard: + * https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf + */ +typedef enum +{ + SDL_SCANCODE_UNKNOWN = 0, + + /** + * \name Usage page 0x07 + * + * These values are from usage page 0x07 (USB keyboard page). + */ + /* @{ */ + + SDL_SCANCODE_A = 4, + SDL_SCANCODE_B = 5, + SDL_SCANCODE_C = 6, + SDL_SCANCODE_D = 7, + SDL_SCANCODE_E = 8, + SDL_SCANCODE_F = 9, + SDL_SCANCODE_G = 10, + SDL_SCANCODE_H = 11, + SDL_SCANCODE_I = 12, + SDL_SCANCODE_J = 13, + SDL_SCANCODE_K = 14, + SDL_SCANCODE_L = 15, + SDL_SCANCODE_M = 16, + SDL_SCANCODE_N = 17, + SDL_SCANCODE_O = 18, + SDL_SCANCODE_P = 19, + SDL_SCANCODE_Q = 20, + SDL_SCANCODE_R = 21, + SDL_SCANCODE_S = 22, + SDL_SCANCODE_T = 23, + SDL_SCANCODE_U = 24, + SDL_SCANCODE_V = 25, + SDL_SCANCODE_W = 26, + SDL_SCANCODE_X = 27, + SDL_SCANCODE_Y = 28, + SDL_SCANCODE_Z = 29, + + SDL_SCANCODE_1 = 30, + SDL_SCANCODE_2 = 31, + SDL_SCANCODE_3 = 32, + SDL_SCANCODE_4 = 33, + SDL_SCANCODE_5 = 34, + SDL_SCANCODE_6 = 35, + SDL_SCANCODE_7 = 36, + SDL_SCANCODE_8 = 37, + SDL_SCANCODE_9 = 38, + SDL_SCANCODE_0 = 39, + + SDL_SCANCODE_RETURN = 40, + SDL_SCANCODE_ESCAPE = 41, + SDL_SCANCODE_BACKSPACE = 42, + SDL_SCANCODE_TAB = 43, + SDL_SCANCODE_SPACE = 44, + + SDL_SCANCODE_MINUS = 45, + SDL_SCANCODE_EQUALS = 46, + SDL_SCANCODE_LEFTBRACKET = 47, + SDL_SCANCODE_RIGHTBRACKET = 48, + SDL_SCANCODE_BACKSLASH = 49, /**< Located at the lower left of the return + * key on ISO keyboards and at the right end + * of the QWERTY row on ANSI keyboards. + * Produces REVERSE SOLIDUS (backslash) and + * VERTICAL LINE in a US layout, REVERSE + * SOLIDUS and VERTICAL LINE in a UK Mac + * layout, NUMBER SIGN and TILDE in a UK + * Windows layout, DOLLAR SIGN and POUND SIGN + * in a Swiss German layout, NUMBER SIGN and + * APOSTROPHE in a German layout, GRAVE + * ACCENT and POUND SIGN in a French Mac + * layout, and ASTERISK and MICRO SIGN in a + * French Windows layout. + */ + SDL_SCANCODE_NONUSHASH = 50, /**< ISO USB keyboards actually use this code + * instead of 49 for the same key, but all + * OSes I've seen treat the two codes + * identically. So, as an implementor, unless + * your keyboard generates both of those + * codes and your OS treats them differently, + * you should generate SDL_SCANCODE_BACKSLASH + * instead of this code. As a user, you + * should not rely on this code because SDL + * will never generate it with most (all?) + * keyboards. + */ + SDL_SCANCODE_SEMICOLON = 51, + SDL_SCANCODE_APOSTROPHE = 52, + SDL_SCANCODE_GRAVE = 53, /**< Located in the top left corner (on both ANSI + * and ISO keyboards). Produces GRAVE ACCENT and + * TILDE in a US Windows layout and in US and UK + * Mac layouts on ANSI keyboards, GRAVE ACCENT + * and NOT SIGN in a UK Windows layout, SECTION + * SIGN and PLUS-MINUS SIGN in US and UK Mac + * layouts on ISO keyboards, SECTION SIGN and + * DEGREE SIGN in a Swiss German layout (Mac: + * only on ISO keyboards), CIRCUMFLEX ACCENT and + * DEGREE SIGN in a German layout (Mac: only on + * ISO keyboards), SUPERSCRIPT TWO and TILDE in a + * French Windows layout, COMMERCIAL AT and + * NUMBER SIGN in a French Mac layout on ISO + * keyboards, and LESS-THAN SIGN and GREATER-THAN + * SIGN in a Swiss German, German, or French Mac + * layout on ANSI keyboards. + */ + SDL_SCANCODE_COMMA = 54, + SDL_SCANCODE_PERIOD = 55, + SDL_SCANCODE_SLASH = 56, + + SDL_SCANCODE_CAPSLOCK = 57, + + SDL_SCANCODE_F1 = 58, + SDL_SCANCODE_F2 = 59, + SDL_SCANCODE_F3 = 60, + SDL_SCANCODE_F4 = 61, + SDL_SCANCODE_F5 = 62, + SDL_SCANCODE_F6 = 63, + SDL_SCANCODE_F7 = 64, + SDL_SCANCODE_F8 = 65, + SDL_SCANCODE_F9 = 66, + SDL_SCANCODE_F10 = 67, + SDL_SCANCODE_F11 = 68, + SDL_SCANCODE_F12 = 69, + + SDL_SCANCODE_PRINTSCREEN = 70, + SDL_SCANCODE_SCROLLLOCK = 71, + SDL_SCANCODE_PAUSE = 72, + SDL_SCANCODE_INSERT = 73, /**< insert on PC, help on some Mac keyboards (but + does send code 73, not 117) */ + SDL_SCANCODE_HOME = 74, + SDL_SCANCODE_PAGEUP = 75, + SDL_SCANCODE_DELETE = 76, + SDL_SCANCODE_END = 77, + SDL_SCANCODE_PAGEDOWN = 78, + SDL_SCANCODE_RIGHT = 79, + SDL_SCANCODE_LEFT = 80, + SDL_SCANCODE_DOWN = 81, + SDL_SCANCODE_UP = 82, + + SDL_SCANCODE_NUMLOCKCLEAR = 83, /**< num lock on PC, clear on Mac keyboards + */ + SDL_SCANCODE_KP_DIVIDE = 84, + SDL_SCANCODE_KP_MULTIPLY = 85, + SDL_SCANCODE_KP_MINUS = 86, + SDL_SCANCODE_KP_PLUS = 87, + SDL_SCANCODE_KP_ENTER = 88, + SDL_SCANCODE_KP_1 = 89, + SDL_SCANCODE_KP_2 = 90, + SDL_SCANCODE_KP_3 = 91, + SDL_SCANCODE_KP_4 = 92, + SDL_SCANCODE_KP_5 = 93, + SDL_SCANCODE_KP_6 = 94, + SDL_SCANCODE_KP_7 = 95, + SDL_SCANCODE_KP_8 = 96, + SDL_SCANCODE_KP_9 = 97, + SDL_SCANCODE_KP_0 = 98, + SDL_SCANCODE_KP_PERIOD = 99, + + SDL_SCANCODE_NONUSBACKSLASH = 100, /**< This is the additional key that ISO + * keyboards have over ANSI ones, + * located between left shift and Y. + * Produces GRAVE ACCENT and TILDE in a + * US or UK Mac layout, REVERSE SOLIDUS + * (backslash) and VERTICAL LINE in a + * US or UK Windows layout, and + * LESS-THAN SIGN and GREATER-THAN SIGN + * in a Swiss German, German, or French + * layout. */ + SDL_SCANCODE_APPLICATION = 101, /**< windows contextual menu, compose */ + SDL_SCANCODE_POWER = 102, /**< The USB document says this is a status flag, + * not a physical key - but some Mac keyboards + * do have a power key. */ + SDL_SCANCODE_KP_EQUALS = 103, + SDL_SCANCODE_F13 = 104, + SDL_SCANCODE_F14 = 105, + SDL_SCANCODE_F15 = 106, + SDL_SCANCODE_F16 = 107, + SDL_SCANCODE_F17 = 108, + SDL_SCANCODE_F18 = 109, + SDL_SCANCODE_F19 = 110, + SDL_SCANCODE_F20 = 111, + SDL_SCANCODE_F21 = 112, + SDL_SCANCODE_F22 = 113, + SDL_SCANCODE_F23 = 114, + SDL_SCANCODE_F24 = 115, + SDL_SCANCODE_EXECUTE = 116, + SDL_SCANCODE_HELP = 117, /**< AL Integrated Help Center */ + SDL_SCANCODE_MENU = 118, /**< Menu (show menu) */ + SDL_SCANCODE_SELECT = 119, + SDL_SCANCODE_STOP = 120, /**< AC Stop */ + SDL_SCANCODE_AGAIN = 121, /**< AC Redo/Repeat */ + SDL_SCANCODE_UNDO = 122, /**< AC Undo */ + SDL_SCANCODE_CUT = 123, /**< AC Cut */ + SDL_SCANCODE_COPY = 124, /**< AC Copy */ + SDL_SCANCODE_PASTE = 125, /**< AC Paste */ + SDL_SCANCODE_FIND = 126, /**< AC Find */ + SDL_SCANCODE_MUTE = 127, + SDL_SCANCODE_VOLUMEUP = 128, + SDL_SCANCODE_VOLUMEDOWN = 129, +/* not sure whether there's a reason to enable these */ +/* SDL_SCANCODE_LOCKINGCAPSLOCK = 130, */ +/* SDL_SCANCODE_LOCKINGNUMLOCK = 131, */ +/* SDL_SCANCODE_LOCKINGSCROLLLOCK = 132, */ + SDL_SCANCODE_KP_COMMA = 133, + SDL_SCANCODE_KP_EQUALSAS400 = 134, + + SDL_SCANCODE_INTERNATIONAL1 = 135, /**< used on Asian keyboards, see + footnotes in USB doc */ + SDL_SCANCODE_INTERNATIONAL2 = 136, + SDL_SCANCODE_INTERNATIONAL3 = 137, /**< Yen */ + SDL_SCANCODE_INTERNATIONAL4 = 138, + SDL_SCANCODE_INTERNATIONAL5 = 139, + SDL_SCANCODE_INTERNATIONAL6 = 140, + SDL_SCANCODE_INTERNATIONAL7 = 141, + SDL_SCANCODE_INTERNATIONAL8 = 142, + SDL_SCANCODE_INTERNATIONAL9 = 143, + SDL_SCANCODE_LANG1 = 144, /**< Hangul/English toggle */ + SDL_SCANCODE_LANG2 = 145, /**< Hanja conversion */ + SDL_SCANCODE_LANG3 = 146, /**< Katakana */ + SDL_SCANCODE_LANG4 = 147, /**< Hiragana */ + SDL_SCANCODE_LANG5 = 148, /**< Zenkaku/Hankaku */ + SDL_SCANCODE_LANG6 = 149, /**< reserved */ + SDL_SCANCODE_LANG7 = 150, /**< reserved */ + SDL_SCANCODE_LANG8 = 151, /**< reserved */ + SDL_SCANCODE_LANG9 = 152, /**< reserved */ + + SDL_SCANCODE_ALTERASE = 153, /**< Erase-Eaze */ + SDL_SCANCODE_SYSREQ = 154, + SDL_SCANCODE_CANCEL = 155, /**< AC Cancel */ + SDL_SCANCODE_CLEAR = 156, + SDL_SCANCODE_PRIOR = 157, + SDL_SCANCODE_RETURN2 = 158, + SDL_SCANCODE_SEPARATOR = 159, + SDL_SCANCODE_OUT = 160, + SDL_SCANCODE_OPER = 161, + SDL_SCANCODE_CLEARAGAIN = 162, + SDL_SCANCODE_CRSEL = 163, + SDL_SCANCODE_EXSEL = 164, + + SDL_SCANCODE_KP_00 = 176, + SDL_SCANCODE_KP_000 = 177, + SDL_SCANCODE_THOUSANDSSEPARATOR = 178, + SDL_SCANCODE_DECIMALSEPARATOR = 179, + SDL_SCANCODE_CURRENCYUNIT = 180, + SDL_SCANCODE_CURRENCYSUBUNIT = 181, + SDL_SCANCODE_KP_LEFTPAREN = 182, + SDL_SCANCODE_KP_RIGHTPAREN = 183, + SDL_SCANCODE_KP_LEFTBRACE = 184, + SDL_SCANCODE_KP_RIGHTBRACE = 185, + SDL_SCANCODE_KP_TAB = 186, + SDL_SCANCODE_KP_BACKSPACE = 187, + SDL_SCANCODE_KP_A = 188, + SDL_SCANCODE_KP_B = 189, + SDL_SCANCODE_KP_C = 190, + SDL_SCANCODE_KP_D = 191, + SDL_SCANCODE_KP_E = 192, + SDL_SCANCODE_KP_F = 193, + SDL_SCANCODE_KP_XOR = 194, + SDL_SCANCODE_KP_POWER = 195, + SDL_SCANCODE_KP_PERCENT = 196, + SDL_SCANCODE_KP_LESS = 197, + SDL_SCANCODE_KP_GREATER = 198, + SDL_SCANCODE_KP_AMPERSAND = 199, + SDL_SCANCODE_KP_DBLAMPERSAND = 200, + SDL_SCANCODE_KP_VERTICALBAR = 201, + SDL_SCANCODE_KP_DBLVERTICALBAR = 202, + SDL_SCANCODE_KP_COLON = 203, + SDL_SCANCODE_KP_HASH = 204, + SDL_SCANCODE_KP_SPACE = 205, + SDL_SCANCODE_KP_AT = 206, + SDL_SCANCODE_KP_EXCLAM = 207, + SDL_SCANCODE_KP_MEMSTORE = 208, + SDL_SCANCODE_KP_MEMRECALL = 209, + SDL_SCANCODE_KP_MEMCLEAR = 210, + SDL_SCANCODE_KP_MEMADD = 211, + SDL_SCANCODE_KP_MEMSUBTRACT = 212, + SDL_SCANCODE_KP_MEMMULTIPLY = 213, + SDL_SCANCODE_KP_MEMDIVIDE = 214, + SDL_SCANCODE_KP_PLUSMINUS = 215, + SDL_SCANCODE_KP_CLEAR = 216, + SDL_SCANCODE_KP_CLEARENTRY = 217, + SDL_SCANCODE_KP_BINARY = 218, + SDL_SCANCODE_KP_OCTAL = 219, + SDL_SCANCODE_KP_DECIMAL = 220, + SDL_SCANCODE_KP_HEXADECIMAL = 221, + + SDL_SCANCODE_LCTRL = 224, + SDL_SCANCODE_LSHIFT = 225, + SDL_SCANCODE_LALT = 226, /**< alt, option */ + SDL_SCANCODE_LGUI = 227, /**< windows, command (apple), meta */ + SDL_SCANCODE_RCTRL = 228, + SDL_SCANCODE_RSHIFT = 229, + SDL_SCANCODE_RALT = 230, /**< alt gr, option */ + SDL_SCANCODE_RGUI = 231, /**< windows, command (apple), meta */ + + SDL_SCANCODE_MODE = 257, /**< I'm not sure if this is really not covered + * by any of the above, but since there's a + * special SDL_KMOD_MODE for it I'm adding it here + */ + + /* @} *//* Usage page 0x07 */ + + /** + * \name Usage page 0x0C + * + * These values are mapped from usage page 0x0C (USB consumer page). + * See https://usb.org/sites/default/files/hut1_2.pdf + * + * There are way more keys in the spec than we can represent in the + * current scancode range, so pick the ones that commonly come up in + * real world usage. + */ + /* @{ */ + + SDL_SCANCODE_AUDIONEXT = 258, + SDL_SCANCODE_AUDIOPREV = 259, + SDL_SCANCODE_AUDIOSTOP = 260, + SDL_SCANCODE_AUDIOPLAY = 261, + SDL_SCANCODE_AUDIOMUTE = 262, + SDL_SCANCODE_MEDIASELECT = 263, + SDL_SCANCODE_WWW = 264, /**< AL Internet Browser */ + SDL_SCANCODE_MAIL = 265, + SDL_SCANCODE_CALCULATOR = 266, /**< AL Calculator */ + SDL_SCANCODE_COMPUTER = 267, + SDL_SCANCODE_AC_SEARCH = 268, /**< AC Search */ + SDL_SCANCODE_AC_HOME = 269, /**< AC Home */ + SDL_SCANCODE_AC_BACK = 270, /**< AC Back */ + SDL_SCANCODE_AC_FORWARD = 271, /**< AC Forward */ + SDL_SCANCODE_AC_STOP = 272, /**< AC Stop */ + SDL_SCANCODE_AC_REFRESH = 273, /**< AC Refresh */ + SDL_SCANCODE_AC_BOOKMARKS = 274, /**< AC Bookmarks */ + + /* @} *//* Usage page 0x0C */ + + /** + * \name Walther keys + * + * These are values that Christian Walther added (for mac keyboard?). + */ + /* @{ */ + + SDL_SCANCODE_BRIGHTNESSDOWN = 275, + SDL_SCANCODE_BRIGHTNESSUP = 276, + SDL_SCANCODE_DISPLAYSWITCH = 277, /**< display mirroring/dual display + switch, video mode switch */ + SDL_SCANCODE_KBDILLUMTOGGLE = 278, + SDL_SCANCODE_KBDILLUMDOWN = 279, + SDL_SCANCODE_KBDILLUMUP = 280, + SDL_SCANCODE_EJECT = 281, + SDL_SCANCODE_SLEEP = 282, /**< SC System Sleep */ + + SDL_SCANCODE_APP1 = 283, + SDL_SCANCODE_APP2 = 284, + + /* @} *//* Walther keys */ + + /** + * \name Usage page 0x0C (additional media keys) + * + * These values are mapped from usage page 0x0C (USB consumer page). + */ + /* @{ */ + + SDL_SCANCODE_AUDIOREWIND = 285, + SDL_SCANCODE_AUDIOFASTFORWARD = 286, + + /* @} *//* Usage page 0x0C (additional media keys) */ + + /** + * \name Mobile keys + * + * These are values that are often used on mobile phones. + */ + /* @{ */ + + SDL_SCANCODE_SOFTLEFT = 287, /**< Usually situated below the display on phones and + used as a multi-function feature key for selecting + a software defined function shown on the bottom left + of the display. */ + SDL_SCANCODE_SOFTRIGHT = 288, /**< Usually situated below the display on phones and + used as a multi-function feature key for selecting + a software defined function shown on the bottom right + of the display. */ + SDL_SCANCODE_CALL = 289, /**< Used for accepting phone calls. */ + SDL_SCANCODE_ENDCALL = 290, /**< Used for rejecting phone calls. */ + + /* @} *//* Mobile keys */ + + /* Add any other keys here. */ + + SDL_NUM_SCANCODES = 512 /**< not a key, just marks the number of scancodes + for array bounds */ +} SDL_Scancode; + +#endif /* SDL_scancode_h_ */ diff --git a/apps/agnes/src/agnes.c b/apps/agnes/src/agnes.c new file mode 100644 index 0000000..928af23 --- /dev/null +++ b/apps/agnes/src/agnes.c @@ -0,0 +1,2583 @@ +/* +SPDX-License-Identifier: MIT + +agnes +https://http://github.com/kgabis/agnes +Copyright (c) 2022 Krzysztof Gabis + +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. +*/ + +#define AGNES_AMALGAMATED + +#ifdef _MSC_VER +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif /* _CRT_SECURE_NO_WARNINGS */ +#endif /* _MSC_VER */ + +#include "agnes.h" + +//----------------------------------------------------------------------------- +// Headers +//----------------------------------------------------------------------------- + +//FILE_START:common.h +#ifndef common_h +#define common_h + +#include +#include +#include + +#ifdef AGNES_AMALGAMATED +#define AGNES_INTERNAL static +#else +#define AGNES_INTERNAL +#endif + +#define AGNES_GET_BIT(byte, bit_ix) (((byte) >> (bit_ix)) & 1) + +#endif /* common_h */ +//FILE_END +//FILE_START:agnes_types.h +#ifndef agnes_types_h +#define agnes_types_h + +#ifndef AGNES_AMALGAMATED +#include "common.h" +#include "agnes.h" +#endif + +/************************************ CPU ************************************/ + +typedef enum { + INTERRPUT_NONE = 0, + INTERRUPT_NMI = 1, + INTERRUPT_IRQ = 2 +} cpu_interrupt_t; + +typedef struct cpu { + struct agnes *agnes; + uint16_t pc; + uint8_t sp; + uint8_t acc; + uint8_t x; + uint8_t y; + uint8_t flag_carry; + uint8_t flag_zero; + uint8_t flag_dis_interrupt; + uint8_t flag_decimal; + uint8_t flag_overflow; + uint8_t flag_negative; + uint32_t stall; + uint64_t cycles; + cpu_interrupt_t interrupt; +} cpu_t; + +/************************************ PPU ************************************/ + +typedef struct { + uint8_t y_pos; + uint8_t tile_num; + uint8_t attrs; + uint8_t x_pos; +} sprite_t; + +typedef struct ppu { + struct agnes *agnes; + + uint8_t nametables[4 * 1024]; + uint8_t palette[32]; + + uint8_t screen_buffer[AGNES_SCREEN_HEIGHT * AGNES_SCREEN_WIDTH]; + + int scanline; + int dot; + + uint8_t ppudata_buffer; + uint8_t last_reg_write; + + struct { + uint16_t v; + uint16_t t; + uint8_t x; + uint8_t w; + } regs; + + struct { + bool show_leftmost_bg; + bool show_leftmost_sprites; + bool show_background; + bool show_sprites; + } masks; + + uint8_t nt; + uint8_t at; + uint8_t at_latch; + uint16_t at_shift; + uint8_t bg_hi; + uint8_t bg_lo; + uint16_t bg_hi_shift; + uint16_t bg_lo_shift; + + struct { + uint16_t addr_increment; + uint16_t sprite_table_addr; + uint16_t bg_table_addr; + bool use_8x16_sprites; + bool nmi_enabled; + } ctrl; + + struct { + bool in_vblank; + bool sprite_overflow; + bool sprite_zero_hit; + } status; + + bool is_odd_frame; + + uint8_t oam_address; + uint8_t oam_data[256]; + sprite_t sprites[8]; + int sprite_ixs[8]; + int sprite_ixs_count; +} ppu_t; + +/********************************** MAPPERS **********************************/ + +typedef enum { + MIRRORING_MODE_NONE, + MIRRORING_MODE_SINGLE_LOWER, + MIRRORING_MODE_SINGLE_UPPER, + MIRRORING_MODE_HORIZONTAL, + MIRRORING_MODE_VERTICAL, + MIRRORING_MODE_FOUR_SCREEN +} mirroring_mode_t; + +typedef struct mapper0 { + struct agnes *agnes; + + unsigned prg_bank_offsets[2]; + bool use_chr_ram; + uint8_t chr_ram[8 * 1024]; +} mapper0_t; + +typedef struct mapper1 { + struct agnes *agnes; + + uint8_t shift; + int shift_count; + uint8_t control; + int prg_mode; + int chr_mode; + int chr_banks[2]; + int prg_bank; + unsigned chr_bank_offsets[2]; + unsigned prg_bank_offsets[2]; + bool use_chr_ram; + uint8_t chr_ram[8 * 1024]; + uint8_t prg_ram[8 * 1024]; +} mapper1_t; + +typedef struct mapper2 { + struct agnes *agnes; + + unsigned prg_bank_offsets[2]; + uint8_t chr_ram[8 * 1024]; +} mapper2_t; + +typedef struct mapper4 { + struct agnes *agnes; + + unsigned prg_mode; + unsigned chr_mode; + bool irq_enabled; + int reg_ix; + uint8_t regs[8]; + uint8_t counter; + uint8_t counter_reload; + unsigned chr_bank_offsets[8]; + unsigned prg_bank_offsets[4]; + uint8_t prg_ram[8 * 1024]; + bool use_chr_ram; + uint8_t chr_ram[8 * 1024]; +} mapper4_t; + +/********************************* GAMEPACK **********************************/ + +typedef struct { + const uint8_t *data; + unsigned prg_rom_offset; + unsigned chr_rom_offset; + int prg_rom_banks_count; + int chr_rom_banks_count; + bool has_prg_ram; + unsigned char mapper; +} gamepack_t; + +/******************************** CONTROLLER *********************************/ + +typedef struct controller { + uint8_t state; + uint8_t shift; +} controller_t; + +/*********************************** AGNES ***********************************/ +typedef struct agnes { + cpu_t cpu; + ppu_t ppu; + uint8_t ram[2 * 1024]; + gamepack_t gamepack; + controller_t controllers[2]; + bool controllers_latch; + + union { + mapper0_t m0; + mapper1_t m1; + mapper2_t m2; + mapper4_t m4; + } mapper; + + mirroring_mode_t mirroring_mode; +} agnes_t; + +#endif /* agnes_types_h */ +//FILE_END +//FILE_START:cpu.h +#ifndef cpu_h +#define cpu_h + +#ifndef AGNES_AMALGAMATED +#include "common.h" +#endif + +typedef struct agnes agnes_t; +typedef struct cpu cpu_t; + +AGNES_INTERNAL void cpu_init(cpu_t *cpu, agnes_t *agnes); +AGNES_INTERNAL int cpu_tick(cpu_t *cpu); +AGNES_INTERNAL void cpu_update_zn_flags(cpu_t *cpu, uint8_t val); +AGNES_INTERNAL void cpu_stack_push8(cpu_t *cpu, uint8_t val); +AGNES_INTERNAL void cpu_stack_push16(cpu_t *cpu, uint16_t val); +AGNES_INTERNAL uint8_t cpu_stack_pop8(cpu_t *cpu); +AGNES_INTERNAL uint16_t cpu_stack_pop16(cpu_t *cpu); +AGNES_INTERNAL uint8_t cpu_get_flags(const cpu_t *cpu); +AGNES_INTERNAL void cpu_restore_flags(cpu_t *cpu, uint8_t flags); +AGNES_INTERNAL void cpu_set_dma_stall(cpu_t *cpu); +AGNES_INTERNAL void cpu_trigger_nmi(cpu_t *cpu); +AGNES_INTERNAL void cpu_trigger_irq(cpu_t *cpu); +AGNES_INTERNAL void cpu_write8(cpu_t *cpu, uint16_t addr, uint8_t val); +AGNES_INTERNAL uint8_t cpu_read8(cpu_t *cpu, uint16_t addr); +AGNES_INTERNAL uint16_t cpu_read16(cpu_t *cpu, uint16_t addr); + +#endif /* cpu_h */ +//FILE_END +//FILE_START:ppu.h +#ifndef ppu_h +#define ppu_h + +#ifndef AGNES_AMALGAMATED +#include "common.h" +#endif + +typedef struct agnes agnes_t; +typedef struct ppu ppu_t; + +AGNES_INTERNAL void ppu_init(ppu_t *ppu, agnes_t *agnes); +AGNES_INTERNAL void ppu_tick(ppu_t *ppu, bool *out_new_frame); +AGNES_INTERNAL uint8_t ppu_read_register(ppu_t *ppu, uint16_t reg); +AGNES_INTERNAL void ppu_write_register(ppu_t *ppu, uint16_t addr, uint8_t val); + +#endif /* ppu_h */ +//FILE_END +//FILE_START:instructions.h +#ifndef opcodes_h +#define opcodes_h + +#ifndef AGNES_AMALGAMATED +#include "common.h" +#endif + +typedef enum { + ADDR_MODE_NONE = 0, + ADDR_MODE_ABSOLUTE, + ADDR_MODE_ABSOLUTE_X, + ADDR_MODE_ABSOLUTE_Y, + ADDR_MODE_ACCUMULATOR, + ADDR_MODE_IMMEDIATE, + ADDR_MODE_IMPLIED, + ADDR_MODE_IMPLIED_BRK, + ADDR_MODE_INDIRECT, + ADDR_MODE_INDIRECT_X, + ADDR_MODE_INDIRECT_Y, + ADDR_MODE_RELATIVE, + ADDR_MODE_ZERO_PAGE, + ADDR_MODE_ZERO_PAGE_X, + ADDR_MODE_ZERO_PAGE_Y +} addr_mode_t; + +typedef struct cpu cpu_t; + +typedef int (*instruction_op_fn)(cpu_t *cpu, uint16_t addr, addr_mode_t mode); + +typedef struct { + const char *name; + uint8_t opcode; + uint8_t cycles; + bool page_cross_cycle; + addr_mode_t mode; + instruction_op_fn operation; +} instruction_t; + +AGNES_INTERNAL instruction_t* instruction_get(uint8_t opcode); +AGNES_INTERNAL uint8_t instruction_get_size(addr_mode_t mode); + +#endif /* opcodes_h */ +//FILE_END +//FILE_START:mapper.h +#ifndef mapper_h +#define mapper_h + +#ifndef AGNES_AMALGAMATED +#include "common.h" +#endif + +typedef struct agnes agnes_t; + +AGNES_INTERNAL bool mapper_init(agnes_t *agnes); +AGNES_INTERNAL uint8_t mapper_read(agnes_t *agnes, uint16_t addr); +AGNES_INTERNAL void mapper_write(agnes_t *agnes, uint16_t addr, uint8_t val); +AGNES_INTERNAL void mapper_pa12_rising_edge(agnes_t *agnes); + +#endif /* mapper_h */ +//FILE_END +//FILE_START:mapper0.h +#ifndef mapper0_h +#define mapper0_h + +#ifndef AGNES_AMALGAMATED +#include "common.h" +#endif + +typedef struct mapper0 mapper0_t; +typedef struct agnes agnes_t; + +AGNES_INTERNAL void mapper0_init(mapper0_t *mapper, agnes_t *agnes); +AGNES_INTERNAL uint8_t mapper0_read(mapper0_t *mapper, uint16_t addr); +AGNES_INTERNAL void mapper0_write(mapper0_t *mapper, uint16_t addr, uint8_t val); + +#endif /* mapper0_h */ +//FILE_END +//FILE_START:mapper1.h +#ifndef mapper1_h +#define mapper1_h + +#ifndef AGNES_AMALGAMATED +#include "common.h" +#endif + +typedef struct mapper1 mapper1_t; +typedef struct agnes agnes_t; + +AGNES_INTERNAL void mapper1_init(mapper1_t *mapper, agnes_t *agnes); +AGNES_INTERNAL uint8_t mapper1_read(mapper1_t *mapper, uint16_t addr); +AGNES_INTERNAL void mapper1_write(mapper1_t *mapper, uint16_t addr, uint8_t val); + +#endif /* mapper1_h */ +//FILE_END +//FILE_START:mapper2.h +#ifndef mapper2_h +#define mapper2_h + +#ifndef AGNES_AMALGAMATED +#include "common.h" +#endif + +typedef struct mapper2 mapper2_t; +typedef struct agnes agnes_t; + +AGNES_INTERNAL void mapper2_init(mapper2_t *mapper, agnes_t *agnes); +AGNES_INTERNAL uint8_t mapper2_read(mapper2_t *mapper, uint16_t addr); +AGNES_INTERNAL void mapper2_write(mapper2_t *mapper, uint16_t addr, uint8_t val); + +#endif /* mapper2_h */ +//FILE_END +//FILE_START:mapper4.h +#ifndef mapper4_h +#define mapper4_h + +#ifndef AGNES_AMALGAMATED +#include "common.h" +#endif + +typedef struct mapper4 mapper4_t; +typedef struct agnes agnes_t; + +AGNES_INTERNAL void mapper4_init(mapper4_t *mapper, agnes_t *agnes); +AGNES_INTERNAL uint8_t mapper4_read(mapper4_t *mapper, uint16_t addr); +AGNES_INTERNAL void mapper4_write(mapper4_t *mapper, uint16_t addr, uint8_t val); +AGNES_INTERNAL void mapper4_pa12_rising_edge(mapper4_t *mapper); + +#endif /* mapper4_h */ +//FILE_END + +//----------------------------------------------------------------------------- +// C files +//----------------------------------------------------------------------------- +//FILE_START:agnes.c +//#include +//#include + +#ifndef AGNES_AMALGAMATED +#include "agnes.h" + +#include "common.h" + +#include "agnes_types.h" +#include "cpu.h" +#include "ppu.h" + +#include "mapper.h" +#endif + +#define AGNES_IMPL_VERSION_MAJOR 0 +#define AGNES_IMPL_VERSION_MINOR 2 +#define AGNES_IMPL_VERSION_PATCH 0 + +#if (AGNES_VERSION_MAJOR != AGNES_IMPL_VERSION_MAJOR)\ + || (AGNES_VERSION_MINOR != AGNES_IMPL_VERSION_MINOR)\ + || (AGNES_VERSION_PATCH != AGNES_IMPL_VERSION_PATCH) + #error "Version mismatch" +#endif + +typedef struct { + uint8_t magic[4]; + uint8_t prg_rom_banks_count; + uint8_t chr_rom_banks_count; + uint8_t flags_6; + uint8_t flags_7; + uint8_t prg_ram_banks_count; + uint8_t flags_9; + uint8_t flags_10; + uint8_t zeros[5]; +} ines_header_t; + +typedef struct agnes_state { + agnes_t agnes; +} agnes_state_t; + +static uint8_t get_input_byte(const agnes_input_t* input); + +static agnes_color_t g_colors[64] = { + {0x7c, 0x7c, 0x7c, 0xff}, {0x00, 0x00, 0xfc, 0xff}, {0x00, 0x00, 0xbc, 0xff}, {0x44, 0x28, 0xbc, 0xff}, + {0x94, 0x00, 0x84, 0xff}, {0xa8, 0x00, 0x20, 0xff}, {0xa8, 0x10, 0x00, 0xff}, {0x88, 0x14, 0x00, 0xff}, + {0x50, 0x30, 0x00, 0xff}, {0x00, 0x78, 0x00, 0xff}, {0x00, 0x68, 0x00, 0xff}, {0x00, 0x58, 0x00, 0xff}, + {0x00, 0x40, 0x58, 0xff}, {0x00, 0x00, 0x00, 0xff}, {0x00, 0x00, 0x00, 0xff}, {0x00, 0x00, 0x00, 0xff}, + {0xbc, 0xbc, 0xbc, 0xff}, {0x00, 0x78, 0xf8, 0xff}, {0x00, 0x58, 0xf8, 0xff}, {0x68, 0x44, 0xfc, 0xff}, + {0xd8, 0x00, 0xcc, 0xff}, {0xe4, 0x00, 0x58, 0xff}, {0xf8, 0x38, 0x00, 0xff}, {0xe4, 0x5c, 0x10, 0xff}, + {0xac, 0x7c, 0x00, 0xff}, {0x00, 0xb8, 0x00, 0xff}, {0x00, 0xa8, 0x00, 0xff}, {0x00, 0xa8, 0x44, 0xff}, + {0x00, 0x88, 0x88, 0xff}, {0x00, 0x00, 0x00, 0xff}, {0x00, 0x00, 0x00, 0xff}, {0x00, 0x00, 0x00, 0xff}, + {0xf8, 0xf8, 0xf8, 0xff}, {0x3c, 0xbc, 0xfc, 0xff}, {0x68, 0x88, 0xfc, 0xff}, {0x98, 0x78, 0xf8, 0xff}, + {0xf8, 0x78, 0xf8, 0xff}, {0xf8, 0x58, 0x98, 0xff}, {0xf8, 0x78, 0x58, 0xff}, {0xfc, 0xa0, 0x44, 0xff}, + {0xf8, 0xb8, 0x00, 0xff}, {0xb8, 0xf8, 0x18, 0xff}, {0x58, 0xd8, 0x54, 0xff}, {0x58, 0xf8, 0x98, 0xff}, + {0x00, 0xe8, 0xd8, 0xff}, {0x78, 0x78, 0x78, 0xff}, {0x00, 0x00, 0x00, 0xff}, {0x00, 0x00, 0x00, 0xff}, + {0xfc, 0xfc, 0xfc, 0xff}, {0xa4, 0xe4, 0xfc, 0xff}, {0xb8, 0xb8, 0xf8, 0xff}, {0xd8, 0xb8, 0xf8, 0xff}, + {0xf8, 0xb8, 0xf8, 0xff}, {0xf8, 0xa4, 0xc0, 0xff}, {0xf0, 0xd0, 0xb0, 0xff}, {0xfc, 0xe0, 0xa8, 0xff}, + {0xf8, 0xd8, 0x78, 0xff}, {0xd8, 0xf8, 0x78, 0xff}, {0xb8, 0xf8, 0xb8, 0xff}, {0xb8, 0xf8, 0xd8, 0xff}, + {0x00, 0xfc, 0xfc, 0xff}, {0xf8, 0xd8, 0xf8, 0xff}, {0x00, 0x00, 0x00, 0xff}, {0x00, 0x00, 0x00, 0xff}, +}; + +agnes_t* agnes_make(void) { + agnes_t *agnes = (agnes_t*)malloc(sizeof(*agnes)); + if (!agnes) { + return NULL; + } + memset(agnes, 0, sizeof(*agnes)); + memset(agnes->ram, 0xff, sizeof(agnes->ram)); + return agnes; +} + +bool agnes_load_ines_data(agnes_t *agnes, const void *data, size_t data_size) { + if (data_size < sizeof(ines_header_t)) { + return false; + } + + ines_header_t *header = (ines_header_t*)data; + if (strncmp((char*)header->magic, "NES\x1a", 4) != 0) { + return false; + } + + unsigned prg_rom_offset = sizeof(ines_header_t); + bool has_trainer = AGNES_GET_BIT(header->flags_6, 2); + if (has_trainer) { + prg_rom_offset += 512; + } + agnes->gamepack.chr_rom_banks_count = header->chr_rom_banks_count; + agnes->gamepack.prg_rom_banks_count = header->prg_rom_banks_count; + if (AGNES_GET_BIT(header->flags_6, 3)) { + agnes->mirroring_mode = MIRRORING_MODE_FOUR_SCREEN; + } else { + agnes->mirroring_mode = AGNES_GET_BIT(header->flags_6, 0) ? MIRRORING_MODE_VERTICAL : MIRRORING_MODE_HORIZONTAL; + } + agnes->gamepack.mapper = ((header->flags_6 & 0xf0) >> 4) | (header->flags_7 & 0xf0); + unsigned prg_rom_size = header->prg_rom_banks_count * (16 * 1024); + unsigned chr_rom_size = header->chr_rom_banks_count * (8 * 1024); + unsigned chr_rom_offset = prg_rom_offset + prg_rom_size; + + if ((chr_rom_offset + chr_rom_size) > data_size) { + return false; + } + + agnes->gamepack.data = (const uint8_t *)data; + agnes->gamepack.prg_rom_offset = prg_rom_offset; + agnes->gamepack.chr_rom_offset = chr_rom_offset; + + bool ok = mapper_init(agnes); + if (!ok) { + return false; + } + + cpu_init(&agnes->cpu, agnes); + ppu_init(&agnes->ppu, agnes); + + return true; +} + +void agnes_set_input(agnes_t *agn, const agnes_input_t *input_1, const agnes_input_t *input_2) { + if (input_1 != NULL) { + agn->controllers[0].state = get_input_byte(input_1); + } + if (input_2 != NULL) { + agn->controllers[1].state = get_input_byte(input_2); + } +} + +size_t agnes_state_size() { + return sizeof(agnes_state_t); +} + +void agnes_dump_state(const agnes_t *agnes, agnes_state_t *out_res) { + memmove(out_res, agnes, sizeof(agnes_t)); + out_res->agnes.gamepack.data = NULL; + out_res->agnes.cpu.agnes = NULL; + out_res->agnes.ppu.agnes = NULL; + switch (out_res->agnes.gamepack.mapper) { + case 0: out_res->agnes.mapper.m0.agnes = NULL; break; + case 1: out_res->agnes.mapper.m1.agnes = NULL; break; + case 2: out_res->agnes.mapper.m2.agnes = NULL; break; + case 4: out_res->agnes.mapper.m4.agnes = NULL; break; + } +} + +bool agnes_restore_state(agnes_t *agnes, const agnes_state_t *state) { + const uint8_t *gamepack_data = agnes->gamepack.data; + memmove(agnes, state, sizeof(agnes_t)); + agnes->gamepack.data = gamepack_data; + agnes->cpu.agnes = agnes; + agnes->ppu.agnes = agnes; + switch (agnes->gamepack.mapper) { + case 0: agnes->mapper.m0.agnes = agnes; break; + case 1: agnes->mapper.m1.agnes = agnes; break; + case 2: agnes->mapper.m2.agnes = agnes; break; + case 4: agnes->mapper.m4.agnes = agnes; break; + } + return true; +} + +bool agnes_tick(agnes_t *agnes, bool *out_new_frame) { + int cpu_cycles = cpu_tick(&agnes->cpu); + if (cpu_cycles == 0) { + return false; + } + + int ppu_cycles = cpu_cycles * 3; + for (int i = 0; i < ppu_cycles; i++) { + ppu_tick(&agnes->ppu, out_new_frame); + } + + return true; +} + +bool agnes_next_frame(agnes_t *agnes) { + while (true) { + bool new_frame = false; + bool ok = agnes_tick(agnes, &new_frame); + if (!ok) { + return false; + } + if (new_frame) { + break; + } + } + return true; +} + +agnes_color_t agnes_get_screen_pixel(const agnes_t *agnes, int x, int y) { + int ix = (y * AGNES_SCREEN_WIDTH) + x; + uint8_t color_ix = agnes->ppu.screen_buffer[ix]; + return g_colors[color_ix & 0x3f]; +} + +void agnes_destroy(agnes_t *agnes) { + free(agnes); +} + +static uint8_t get_input_byte(const agnes_input_t* input) { + uint8_t res = 0; + res |= input->a << 0; + res |= input->b << 1; + res |= input->select << 2; + res |= input->start << 3; + res |= input->up << 4; + res |= input->down << 5; + res |= input->left << 6; + res |= input->right << 7; + return res; +} + +//FILE_END +//FILE_START:cpu.c +//#include + +#ifndef AGNES_AMALGAMATED +#include "cpu.h" + +#include "ppu.h" +#include "agnes_types.h" +#include "instructions.h" +#include "mapper.h" +#endif + +static uint16_t cpu_read16_indirect_bug(cpu_t *cpu, uint16_t addr); +static uint16_t get_instruction_operand(cpu_t *cpu, addr_mode_t mode, bool *out_pages_differ); +static int handle_interrupt(cpu_t *cpu); +static bool check_pages_differ(uint16_t a, uint16_t b); + +void cpu_init(cpu_t *cpu, agnes_t *agnes) { + memset(cpu, 0, sizeof(cpu_t)); + cpu->agnes = agnes; + cpu->pc = cpu_read16(cpu, 0xfffc); // RESET + cpu->sp = 0xfd; + cpu_restore_flags(cpu, 0x24); +} + +int cpu_tick(cpu_t *cpu) { + if (cpu->stall > 0) { + cpu->stall--; + return 1; + } + + int cycles = 0; + + if (cpu->interrupt != INTERRPUT_NONE) { + cycles += handle_interrupt(cpu); + } + + uint8_t opcode = cpu_read8(cpu, cpu->pc); + instruction_t *ins = instruction_get(opcode); + if (ins->operation == NULL) { + return 0; + } + + uint8_t ins_size = instruction_get_size(ins->mode); + bool page_crossed = false; + uint16_t addr = get_instruction_operand(cpu, ins->mode, &page_crossed); + + cpu->pc += ins_size; + + cycles += ins->cycles; + cycles += ins->operation(cpu, addr, ins->mode); + + if (page_crossed && ins->page_cross_cycle) { + cycles += 1; + } + + cpu->cycles += cycles; + + return cycles; +} + +void cpu_update_zn_flags(cpu_t *cpu, uint8_t val) { + cpu->flag_zero = val == 0; + cpu->flag_negative = AGNES_GET_BIT(val, 7); +} + +void cpu_stack_push8(cpu_t *cpu, uint8_t val) { + uint16_t addr = 0x0100 + (uint16_t)(cpu->sp); + cpu_write8(cpu, addr, val); + cpu->sp--; +} + +void cpu_stack_push16(cpu_t *cpu, uint16_t val) { + cpu_stack_push8(cpu, val >> 8); + cpu_stack_push8(cpu, val); +} + +uint8_t cpu_stack_pop8(cpu_t *cpu) { + cpu->sp++; + uint16_t addr = 0x0100 + (uint16_t)(cpu->sp); + uint8_t res = cpu_read8(cpu, addr); + return res; +} + +uint16_t cpu_stack_pop16(cpu_t *cpu) { + uint16_t lo = cpu_stack_pop8(cpu); + uint16_t hi = cpu_stack_pop8(cpu); + uint16_t res = (hi << 8) | lo; + return res; +} + +uint8_t cpu_get_flags(const cpu_t *cpu) { + uint8_t res = 0; + res |= cpu->flag_carry << 0; + res |= cpu->flag_zero << 1; + res |= cpu->flag_dis_interrupt << 2; + res |= cpu->flag_decimal << 3; + res |= cpu->flag_overflow << 6; + res |= cpu->flag_negative << 7; + return res; +} + +void cpu_restore_flags(cpu_t *cpu, uint8_t flags) { + cpu->flag_carry = AGNES_GET_BIT(flags, 0); + cpu->flag_zero = AGNES_GET_BIT(flags, 1); + cpu->flag_dis_interrupt = AGNES_GET_BIT(flags, 2); + cpu->flag_decimal = AGNES_GET_BIT(flags, 3); + cpu->flag_overflow = AGNES_GET_BIT(flags, 6); + cpu->flag_negative = AGNES_GET_BIT(flags, 7); +} + +void cpu_trigger_nmi(cpu_t *cpu) { + cpu->interrupt = INTERRUPT_NMI; +} + +void cpu_trigger_irq(cpu_t *cpu) { + if (!cpu->flag_dis_interrupt) { + cpu->interrupt = INTERRUPT_IRQ; + } +} + +void cpu_set_dma_stall(cpu_t *cpu) { + cpu->stall = (cpu->cycles & 0x1) ? 514 : 513; +} + +void cpu_write8(cpu_t *cpu, uint16_t addr, uint8_t val) { + agnes_t *agnes = cpu->agnes; + + if (addr < 0x2000) { + agnes->ram[addr & 0x7ff] = val; + } else if (addr < 0x4000) { + ppu_write_register(&agnes->ppu, 0x2000 | (addr & 0x7), val); + } else if (addr == 0x4014) { + ppu_write_register(&agnes->ppu, 0x4014, val); + } else if (addr == 0x4016) { + agnes->controllers_latch = val & 0x1; + if (agnes->controllers_latch) { + agnes->controllers[0].shift = agnes->controllers[0].state; + agnes->controllers[1].shift = agnes->controllers[1].state; + } + } else if (addr < 0x4018) { // apu and io + + } else if (addr < 0x4020) { // disabled + + } else { + mapper_write(agnes, addr, val); + } +} + +uint8_t cpu_read8(cpu_t *cpu, uint16_t addr) { + agnes_t *agnes = cpu->agnes; + + uint8_t res = 0; + if (addr >= 0x4020) { // moved to top because it's the most common case + res = mapper_read(agnes, addr); + } else if (addr < 0x2000) { + res = agnes->ram[addr & 0x7ff]; + } else if (addr < 0x4000) { + res = ppu_read_register(&agnes->ppu, 0x2000 | (addr & 0x7)); + } else if (addr < 0x4016) { + // apu + } else if (addr < 0x4018) { + int controller = addr & 0x1; // 0: 0x4016, 1: 0x4017 + if (agnes->controllers_latch) { + agnes->controllers[controller].shift = agnes->controllers[controller].state; + } + res = agnes->controllers[controller].shift & 0x1; + agnes->controllers[controller].shift >>= 1; + } + return res; +} + +uint16_t cpu_read16(cpu_t *cpu, uint16_t addr) { + uint8_t lo = cpu_read8(cpu, addr); + uint8_t hi = cpu_read8(cpu, addr + 1); + return (hi << 8) | lo; +} + +static uint16_t cpu_read16_indirect_bug(cpu_t *cpu, uint16_t addr) { + uint8_t lo = cpu_read8(cpu, addr); + uint8_t hi = cpu_read8(cpu, (addr & 0xff00) | ((addr + 1) & 0x00ff)); + return (hi << 8) | lo; +} + +static uint16_t get_instruction_operand(cpu_t *cpu, addr_mode_t mode, bool *out_pages_differ) { + *out_pages_differ = false; + switch (mode) { + case ADDR_MODE_ABSOLUTE: { + return cpu_read16(cpu, cpu->pc + 1); + } + case ADDR_MODE_ABSOLUTE_X: { + uint16_t addr = cpu_read16(cpu, cpu->pc + 1); + uint16_t res = addr + cpu->x; + *out_pages_differ = check_pages_differ(addr, res); + return res; + } + case ADDR_MODE_ABSOLUTE_Y: { + uint16_t addr = cpu_read16(cpu, cpu->pc + 1); + uint16_t res = addr + cpu->y; + *out_pages_differ = check_pages_differ(addr, res); + return res; + } + case ADDR_MODE_IMMEDIATE: { + return cpu->pc + 1; + } + case ADDR_MODE_INDIRECT: { + uint16_t addr = cpu_read16(cpu, cpu->pc + 1); + return cpu_read16_indirect_bug(cpu, addr); + } + case ADDR_MODE_INDIRECT_X: { + uint8_t addr = cpu_read8(cpu, (cpu->pc + 1)); + return cpu_read16_indirect_bug(cpu, (addr + cpu->x) & 0xff); + } + case ADDR_MODE_INDIRECT_Y: { + uint8_t arg = cpu_read8(cpu, cpu->pc + 1); + uint16_t addr2 = cpu_read16_indirect_bug(cpu, arg); + uint16_t res = addr2 + cpu->y; + *out_pages_differ = check_pages_differ(addr2, res); + return res; + } + case ADDR_MODE_ZERO_PAGE: { + return cpu_read8(cpu, cpu->pc + 1); + } + case ADDR_MODE_ZERO_PAGE_X: { + return (cpu_read8(cpu, cpu->pc + 1) + cpu->x) & 0xff; + } + case ADDR_MODE_ZERO_PAGE_Y: { + return (cpu_read8(cpu, cpu->pc + 1) + cpu->y) & 0xff; + } + case ADDR_MODE_RELATIVE: { + uint8_t addr = cpu_read8(cpu, cpu->pc + 1); + if (addr < 0x80) { + return cpu->pc + addr + 2; + } else { + return cpu->pc + addr + 2 - 0x100; + } + } + default: { + return 0; + } + } +} + +static int handle_interrupt(cpu_t *cpu) { + uint16_t addr = 0; + if (cpu->interrupt == INTERRUPT_NMI) { + addr = 0xfffa; + } else if (cpu->interrupt == INTERRUPT_IRQ) { + addr = 0xfffe; + } else { + return 0; + } + cpu->interrupt = INTERRPUT_NONE; + cpu_stack_push16(cpu, cpu->pc); + uint8_t flags = cpu_get_flags(cpu); + cpu_stack_push8(cpu, flags | 0x20); + cpu->pc = cpu_read16(cpu, addr); + cpu->flag_dis_interrupt = true; + return 7; +} + +static bool check_pages_differ(uint16_t a, uint16_t b) { + return (0xff00 & a) != (0xff00 & b); +} +//FILE_END +//FILE_START:ppu.c +//#include +//#include +//#include + +#ifndef AGNES_AMALGAMATED +#include "ppu.h" + +#include "agnes_types.h" +#include "cpu.h" +#include "mapper.h" +#endif + +static void scanline_visible_pre(ppu_t *ppu, bool *out_new_frame); +static void inc_hori_v(ppu_t *ppu); +static void inc_vert_v(ppu_t *ppu); +static void emit_pixel(ppu_t *ppu); +static uint16_t get_bg_color_addr(ppu_t *ppu); +static uint16_t get_sprite_color_addr(ppu_t *ppu, int *out_sprite_ix, bool *out_behind_bg); +static void eval_sprites(ppu_t *ppu); +static void set_pixel_color_ix(ppu_t *ppu, int x, int y, uint8_t color_ix); +static uint8_t ppu_read8(ppu_t *ppu, uint16_t addr); +static void ppu_write8(ppu_t *ppu, uint16_t addr, uint8_t val); +static uint16_t mirror_address(ppu_t *ppu, uint16_t addr); + +static unsigned g_palette_addr_map[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x11, 0x12, 0x13, 0x04, 0x15, 0x16, 0x17, 0x08, 0x19, 0x1a, 0x1b, 0x0c, 0x1d, 0x1e, 0x1f, +}; + +void ppu_init(ppu_t *ppu, agnes_t *agnes) { + memset(ppu, 0, sizeof(ppu_t)); + ppu->agnes = agnes; + + ppu_write_register(ppu, 0x2000, 0); + ppu_write_register(ppu, 0x2001, 0); +} + +void ppu_tick(ppu_t *ppu, bool *out_new_frame) { + bool rendering_enabled = ppu->masks.show_background || ppu->masks.show_sprites; + + // https://wiki.nesdev.com/w/index.php/PPU_frame_timing#Even.2FOdd_Frames + if (rendering_enabled && ppu->is_odd_frame && ppu->dot == 339 && ppu->scanline == 261) { + ppu->dot = 0; + ppu->scanline = 0; + ppu->is_odd_frame = !ppu->is_odd_frame; + } else { + ppu->dot++; + + if (ppu->dot > 340){ + ppu->dot = 0; + ppu->scanline++; + } + + if (ppu->scanline > 261) { + ppu->scanline = 0; + ppu->is_odd_frame = !ppu->is_odd_frame; + } + } + + if (ppu->dot == 0) { + return; + } + + bool scanline_visible = ppu->scanline >= 0 && ppu->scanline < 240; + bool scanline_pre = ppu->scanline == 261; + bool scanline_post = ppu->scanline == 241; + + if (rendering_enabled && (scanline_visible || scanline_pre)) { + scanline_visible_pre(ppu, out_new_frame); + } + + if (ppu->dot == 1) { + if (scanline_pre) { + ppu->status.sprite_overflow = false; + ppu->status.sprite_zero_hit = false; + ppu->status.in_vblank = false; + } else if (scanline_post) { + ppu->status.in_vblank = true; + *out_new_frame = true; + if (ppu->ctrl.nmi_enabled) { + cpu_trigger_nmi(&ppu->agnes->cpu); + } + } + } +} + +static void scanline_visible_pre(ppu_t *ppu, bool *out_new_frame) { + bool scanline_visible = ppu->scanline >= 0 && ppu->scanline < 240; + bool scanline_pre = ppu->scanline == 261; + bool dot_visible = ppu->dot > 0 && ppu->dot <= 256; + bool dot_fetch = ppu->dot <= 256 || (ppu->dot >= 321 && ppu->dot < 337); + + if (scanline_visible && dot_visible) { + emit_pixel(ppu); + } + + if (dot_fetch) { + ppu->bg_lo_shift <<= 1; + ppu->bg_hi_shift <<= 1; + ppu->at_shift = (ppu->at_shift << 2) | (ppu->at_latch & 0x3); + + switch (ppu->dot & 0x7) { + case 1: { + uint16_t addr = 0x2000 | (ppu->regs.v & 0x0fff); + ppu->nt = ppu_read8(ppu, addr); + break; + } + case 3: { + uint16_t v = ppu->regs.v; + uint16_t addr = 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07); + ppu->at = ppu_read8(ppu, addr); + if (ppu->regs.v & 0x40) { + ppu->at = ppu->at >> 4; + } + if (ppu->regs.v & 0x02) { + ppu->at = ppu->at >> 2; + } + break; + } + case 5: { + uint8_t fine_y = ((ppu->regs.v) >> 12) & 0x7; + uint16_t addr = ppu->ctrl.bg_table_addr + (ppu->nt << 4) + fine_y; + ppu->bg_lo = ppu_read8(ppu, addr); + break; + } + case 7: { + uint8_t fine_y = ((ppu->regs.v) >> 12) & 0x7; + uint16_t addr = ppu->ctrl.bg_table_addr + (ppu->nt << 4) + fine_y + 8; + ppu->bg_hi = ppu_read8(ppu, addr); + break; + } + case 0: { + ppu->bg_lo_shift = (ppu->bg_lo_shift & 0xff00) | ppu->bg_lo; + ppu->bg_hi_shift = (ppu->bg_hi_shift & 0xff00) | ppu->bg_hi; + + ppu->at_latch = ppu->at & 0x3; + + if (ppu->dot == 256) { + inc_vert_v(ppu); + } else { + inc_hori_v(ppu); + } + break; + } + default: + break; + } + } + + if (ppu->dot == 257) { + // v: |_...|.F..| |...E|DCBA| = t: |_...|.F..| |...E|DCBA| + ppu->regs.v = (ppu->regs.v & 0xfbe0) | (ppu->regs.t & ~(0xfbe0)); + + if (scanline_visible) { + eval_sprites(ppu); + } else { + ppu->sprite_ixs_count = 0; + } + } + + if (scanline_pre && ppu->dot >= 280 && ppu->dot <= 304) { + // v: |_IHG|F.ED| |CBA.|....| = t: |_IHG|F.ED| |CBA.|....| + ppu->regs.v = (ppu->regs.v & 0x841f) | (ppu->regs.t & ~(0x841f)); + } + + if (ppu->masks.show_background && ppu->masks.show_sprites) { + if ((ppu->ctrl.bg_table_addr == 0x0000 && ppu->dot == 270) // Should be 260 but it caused glitches in Kirby + || (ppu->ctrl.bg_table_addr == 0x1000 && ppu->dot == 324)) { // Not tested so far. + // https://wiki.nesdev.com/w/index.php/MMC3#IRQ_Specifics + // PA12 is 12th bit of PPU address bus that's toggled when switching between + // background and sprite pattern tables (should happen once per scanline). + // This might not work correctly with games using 8x16 sprites + // or games writing to CHR RAM. + mapper_pa12_rising_edge(ppu->agnes); + } + } +} + +#define GET_COARSE_X(v) ((v) & 0x1f) +#define SET_COARSE_X(v, cx) do { v = (((v) & ~0x1f) | ((cx) & 0x1f)); } while (0) +#define GET_COARSE_Y(v) (((v) >> 5) & 0x1f) +#define SET_COARSE_Y(v, cy) do { v = (((v) & ~0x3e0) | ((cy) & 0x1f) << 5); } while (0) +#define GET_FINE_Y(v) ((v) >> 12) +#define SET_FINE_Y(v, fy) do { v = (((v) & ~0x7000) | (((fy) & 0x7) << 12)); } while (0) + +static void inc_hori_v(ppu_t *ppu) { + unsigned cx = GET_COARSE_X(ppu->regs.v); + if (cx == 31) { + SET_COARSE_X(ppu->regs.v, 0); + ppu->regs.v ^= 0x0400; // switch horizontal nametable + } else { + SET_COARSE_X(ppu->regs.v, cx + 1); + } +} + +static void inc_vert_v(ppu_t *ppu) { + unsigned fy = GET_FINE_Y(ppu->regs.v); + if (fy < 7) { + SET_FINE_Y(ppu->regs.v, fy + 1); + } else { + SET_FINE_Y(ppu->regs.v, 0); + unsigned cy = GET_COARSE_Y(ppu->regs.v); + if (cy == 29) { + SET_COARSE_Y(ppu->regs.v, 0); + ppu->regs.v ^= 0x0800; // switch vertical nametable + } else if (cy == 31) { + SET_COARSE_Y(ppu->regs.v, 0); + } else { + SET_COARSE_Y(ppu->regs.v, cy + 1); + } + } +} + +#undef GET_COARSE_X +#undef SET_COARSE_X +#undef GET_COARSE_Y +#undef SET_COARSE_Y +#undef GET_FINE_Y +#undef SET_FINE_Y + +static void eval_sprites(ppu_t *ppu) { + ppu->sprite_ixs_count = 0; + const sprite_t* sprites = (const sprite_t*)ppu->oam_data; + int sprite_height = ppu->ctrl.use_8x16_sprites ? 16 : 8; + for (int i = 0; i < 64; i++) { + const sprite_t* sprite = &sprites[i]; + + if (sprite->y_pos > 0xef || sprite->x_pos > 0xff) { + continue; + } + + int s_y = ppu->scanline - sprite->y_pos; + if (s_y < 0 || s_y >= sprite_height) { + continue; + } + + if (ppu->sprite_ixs_count < 8) { + ppu->sprites[ppu->sprite_ixs_count] = *sprite; + ppu->sprite_ixs[ppu->sprite_ixs_count] = i; + ppu->sprite_ixs_count++; + } else { + ppu->status.sprite_overflow = true; + break; + } + } +} + +static void emit_pixel(ppu_t *ppu) { + const int x = ppu->dot - 1; + const int y = ppu->scanline; + + if (x < 8 && !ppu->masks.show_leftmost_bg && !ppu->masks.show_leftmost_sprites) { + set_pixel_color_ix(ppu, x, y, 63); // 63 is black in my default colour palette + return; + } + + uint16_t bg_color_addr = get_bg_color_addr(ppu); + + int sprite_ix = -1; + bool behind_bg = false; + uint16_t sp_color_addr = get_sprite_color_addr(ppu, &sprite_ix, &behind_bg); + + uint16_t color_addr = 0x3f00; + if (bg_color_addr && sp_color_addr) { + if (sprite_ix == 0 && x != 255) { + ppu->status.sprite_zero_hit = true; + } + color_addr = behind_bg ? bg_color_addr : sp_color_addr; + } else if (bg_color_addr && !sp_color_addr) { + color_addr = bg_color_addr; + } else if (!bg_color_addr && sp_color_addr) { + color_addr = sp_color_addr; + } + + uint8_t output_color_ix = ppu_read8(ppu, color_addr); + set_pixel_color_ix(ppu, x, y, output_color_ix); +} + +static uint16_t get_bg_color_addr(ppu_t *ppu) { + if (!ppu->masks.show_background || (!ppu->masks.show_leftmost_bg && ppu->dot < 9)) { + return 0; + } + + bool hi_bit = AGNES_GET_BIT(ppu->bg_hi_shift, 15 - ppu->regs.x); + bool lo_bit = AGNES_GET_BIT(ppu->bg_lo_shift, 15 - ppu->regs.x); + + if (!lo_bit && !hi_bit) { + return 0; + } + + uint8_t palette = (ppu->at_shift >> (14 - (ppu->regs.x << 1)) & 0x3); + uint8_t palette_ix = ((uint8_t)hi_bit << 1) | (uint8_t)lo_bit; + uint16_t color_address = 0x3f00 | (palette << 2) | palette_ix; + return color_address; +} + +static uint16_t get_sprite_color_addr(ppu_t *ppu, int *out_sprite_ix, bool *out_behind_bg) { + *out_sprite_ix = -1; + *out_behind_bg = false; + + const int x = ppu->dot - 1; + const int y = ppu->scanline; + + if (!ppu->masks.show_sprites || (!ppu->masks.show_leftmost_sprites && x < 8)) { + return 0; + } + + int sprite_height = ppu->ctrl.use_8x16_sprites ? 16 : 8; + uint16_t table = ppu->ctrl.sprite_table_addr; + + for (int i = 0; i < ppu->sprite_ixs_count; i++) { + const sprite_t *sprite = &ppu->sprites[i]; + int s_x = x - sprite->x_pos; + if (s_x < 0 || s_x >= 8) { + continue; + } + + int s_y = y - sprite->y_pos - 1; + + s_x = AGNES_GET_BIT(sprite->attrs, 6) ? 7 - s_x : s_x; // flip hor + s_y = AGNES_GET_BIT(sprite->attrs, 7) ? (sprite_height - 1 - s_y) : s_y; // flip vert + + uint8_t tile_num = sprite->tile_num; + if (ppu->ctrl.use_8x16_sprites) { + table = tile_num & 0x1 ? 0x1000 : 0x0000; + tile_num &= 0xfe; + if (s_y >= 8) { + tile_num += 1; + s_y -= 8; + } + } + + uint16_t offset = table + (tile_num << 4) + s_y; + + uint8_t lo_byte = ppu_read8(ppu, offset); + uint8_t hi_byte = ppu_read8(ppu, offset + 8); + + if (!lo_byte && !hi_byte) { + continue; + } + + bool lo_bit = AGNES_GET_BIT(lo_byte, 7 - s_x); + bool hi_bit = AGNES_GET_BIT(hi_byte, 7 - s_x); + + if (lo_bit || hi_bit) { + *out_sprite_ix = ppu->sprite_ixs[i]; + if (AGNES_GET_BIT(sprite->attrs, 5)) { + *out_behind_bg = true; + } + uint8_t palette_ix = ((uint8_t)hi_bit << 1) | (uint8_t)lo_bit; + uint16_t color_address = 0x3f10 | ((sprite->attrs & 0x3) << 2) | palette_ix; + return color_address; + } + } + return 0; +} + +uint8_t ppu_read_register(ppu_t *ppu, uint16_t addr) { + switch (addr) { + case 0x2002: { // PPUSTATUS + uint8_t res = 0; + res |= ppu->last_reg_write & 0x1f; + res |= ppu->status.sprite_overflow << 5; + res |= ppu->status.sprite_zero_hit << 6; + res |= ppu->status.in_vblank << 7; + ppu->status.in_vblank = false; + // res |= ppu->status_in_vblank + // w: = 0 + ppu->regs.w = 0; + return res; + } + case 0x2004: { // OAMDATA + return ppu->oam_data[ppu->oam_address]; + } + case 0x2007: { // PPUDATA + uint8_t res = 0; + if (ppu->regs.v < 0x3f00) { + res = ppu->ppudata_buffer; + ppu->ppudata_buffer = ppu_read8(ppu, ppu->regs.v); + } else { + res = ppu_read8(ppu, ppu->regs.v); + ppu->ppudata_buffer = ppu_read8(ppu, ppu->regs.v - 0x1000); + } + ppu->regs.v += ppu->ctrl.addr_increment; + return res; + } + } + return 0; +} + +void ppu_write_register(ppu_t *ppu, uint16_t addr, uint8_t val) { + ppu->last_reg_write = val; + switch (addr) { + case 0x2000: { // PPUCTRL + ppu->ctrl.addr_increment = AGNES_GET_BIT(val, 2) ? 32 : 1; + ppu->ctrl.sprite_table_addr = AGNES_GET_BIT(val, 3) ? 0x1000 : 0x0000; + ppu->ctrl.bg_table_addr = AGNES_GET_BIT(val, 4) ? 0x1000 : 0x0000; + ppu->ctrl.use_8x16_sprites = AGNES_GET_BIT(val, 5); + ppu->ctrl.nmi_enabled = AGNES_GET_BIT(val, 7); + + // t: |_...|BA..| |....|....| = d: |....|..BA| + ppu->regs.t = (ppu->regs.t & 0xf3ff) | ((val & 0x03) << 10); + break; + } + case 0x2001: { // PPUMASK + ppu->masks.show_leftmost_bg = AGNES_GET_BIT(val, 1); + ppu->masks.show_leftmost_sprites = AGNES_GET_BIT(val, 2); + ppu->masks.show_background = AGNES_GET_BIT(val, 3); + ppu->masks.show_sprites = AGNES_GET_BIT(val, 4); + break; + } + case 0x2003: { // OAMADDR + ppu->oam_address = val; + break; + } + case 0x2004: { // OAMDATA + ppu->oam_data[ppu->oam_address] = val; + ppu->oam_address++; + break; + } + case 0x2005: { // SCROLL + if (ppu->regs.w) { + // t: |_CBA|..HG| |FED.|....| = d: |HGFE|DCBA| + // w: = 0 + ppu->regs.t = (ppu->regs.t & 0x8fff) | ((val & 0x7) << 12); + ppu->regs.t = (ppu->regs.t & 0xfc1f) | ((val >> 3) << 5); + ppu->regs.w = 0; + } else { + // t: |_...|....| |...H|GFED| = d: HGFED... + // x: CBA = d: |...|..CBA| + // w: = 1 + ppu->regs.t = (ppu->regs.t & 0xffe0) | (val >> 3); + ppu->regs.x = (val & 0x7); + ppu->regs.w = 1; + } + break; + } + case 0x2006: { // PPUADDR + if (ppu->regs.w) { + // t: |_...|....| |HGFE|DCBA| = d: |HGFE|DCBA| + // v = t + // w: = 0 + ppu->regs.t = (ppu->regs.t & 0xff00) | val; + ppu->regs.v = ppu->regs.t; + ppu->regs.w = 0; + } else { + // t: |_.FE|DCBA| |....|....| = d: |..FE|DCBA| + // t: |_X..|....| |....|....| = 0 + // w: = 1 + ppu->regs.t = (ppu->regs.t & 0xc0ff) | ((val & 0x3f) << 8); + ppu->regs.t = ppu->regs.t & 0xbfff; + ppu->regs.w = 1; + } + break; + } + case 0x2007: { // PPUDATA + ppu_write8(ppu, ppu->regs.v, val); + ppu->regs.v += ppu->ctrl.addr_increment; + break; + } + case 0x4014: { // OAMDMA + uint16_t dma_addr = ((uint16_t)val) << 8; + for (int i = 0; i < 256; i++) { + ppu->oam_data[ppu->oam_address] = cpu_read8(&ppu->agnes->cpu, dma_addr); + ppu->oam_address++; + dma_addr++; + } + cpu_set_dma_stall(&ppu->agnes->cpu); + break; + } + } +} + +static void set_pixel_color_ix(ppu_t *ppu, int x, int y, uint8_t color_ix) { + int ix = (y * AGNES_SCREEN_WIDTH) + x; + ppu->screen_buffer[ix] = color_ix; +} + +static uint8_t ppu_read8(ppu_t *ppu, uint16_t addr) { + addr = addr & 0x3fff; + uint8_t res = 0; + if (addr >= 0x3f00) { // $3F00 - $3FFF, palette reads are most common + unsigned palette_ix = g_palette_addr_map[addr & 0x1f]; + res = ppu->palette[palette_ix]; + } else if (addr < 0x2000) { // $0000 - $1FFF + res = mapper_read(ppu->agnes, addr); + } else { // $2000 - $3EFF + uint16_t mirrored_addr = mirror_address(ppu, addr); + res = ppu->nametables[mirrored_addr]; + } + return res; +} + +static void ppu_write8(ppu_t *ppu, uint16_t addr, uint8_t val) { + addr = addr & 0x3fff; + if (addr >= 0x3f00) { // $3F00 - $3FFF + int palette_ix = g_palette_addr_map[addr & 0x1f]; + ppu->palette[palette_ix] = val; + } else if (addr < 0x2000) { // $0000 - $1FFF + mapper_write(ppu->agnes, addr, val); + } else { // $2000 - $3EFF + uint16_t mirrored_addr = mirror_address(ppu, addr); + ppu->nametables[mirrored_addr] = val; + } +} + +static uint16_t mirror_address(ppu_t *ppu, uint16_t addr) { + switch (ppu->agnes->mirroring_mode) + { + case MIRRORING_MODE_HORIZONTAL: return ((addr >> 1) & 0x400) | (addr & 0x3ff); + case MIRRORING_MODE_VERTICAL: return addr & 0x07ff; + case MIRRORING_MODE_SINGLE_LOWER: return addr & 0x3ff; + case MIRRORING_MODE_SINGLE_UPPER: return 0x400 | (addr & 0x3ff); + case MIRRORING_MODE_FOUR_SCREEN: return addr - 0x2000; + default: return 0; + } +} +//FILE_END +//FILE_START:instructions.c +#ifndef AGNES_AMALGAMATED +#include "instructions.h" + +#include "agnes_types.h" +#include "cpu.h" +#endif + +static int op_adc(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_and(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_asl(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_bcc(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_bcs(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_beq(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_bit(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_bmi(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_bne(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_bpl(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_brk(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_bvc(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_bvs(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_clc(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_cld(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_cli(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_clv(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_cmp(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_cpx(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_cpy(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_dec(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_dex(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_dey(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_eor(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_inc(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_inx(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_iny(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_jmp(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_jsr(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_lda(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_ldx(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_ldy(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_lsr(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_nop(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_ora(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_pha(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_php(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_pla(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_plp(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_rol(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_ror(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_rti(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_rts(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_sbc(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_sec(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_sed(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_sei(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_sta(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_stx(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_sty(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_tax(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_tay(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_tsx(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_txa(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_txs(cpu_t *cpu, uint16_t addr, addr_mode_t mode); +static int op_tya(cpu_t *cpu, uint16_t addr, addr_mode_t mode); + +static int take_branch(cpu_t *cpu, uint16_t addr); + +#define INS(OPC, NAME, CYCLES, PCC, OP, MODE) { NAME, OPC, CYCLES, PCC, MODE, OP } +#define INE(OPC) { "ILL", OPC, 1, false, ADDR_MODE_IMPLIED, NULL } + +static instruction_t instructions[256] = { + INS(0x00, "BRK", 7, false, op_brk, ADDR_MODE_IMPLIED_BRK), + INS(0x01, "ORA", 6, false, op_ora, ADDR_MODE_INDIRECT_X), + INE(0x02), + INE(0x03), + INE(0x04), + INS(0x05, "ORA", 3, false, op_ora, ADDR_MODE_ZERO_PAGE), + INS(0x06, "ASL", 5, false, op_asl, ADDR_MODE_ZERO_PAGE), + INE(0x07), + INS(0x08, "PHP", 3, false, op_php, ADDR_MODE_IMPLIED), + INS(0x09, "ORA", 2, false, op_ora, ADDR_MODE_IMMEDIATE), + INS(0x0a, "ASL", 2, false, op_asl, ADDR_MODE_ACCUMULATOR), + INE(0x0b), + INE(0x0c), + INS(0x0d, "ORA", 4, false, op_ora, ADDR_MODE_ABSOLUTE), + INS(0x0e, "ASL", 6, false, op_asl, ADDR_MODE_ABSOLUTE), + INE(0x0f), + INS(0x10, "BPL", 2, true, op_bpl, ADDR_MODE_RELATIVE), + INS(0x11, "ORA", 5, true, op_ora, ADDR_MODE_INDIRECT_Y), + INE(0x12), + INE(0x13), + INE(0x14), + INS(0x15, "ORA", 4, false, op_ora, ADDR_MODE_ZERO_PAGE_X), + INS(0x16, "ASL", 6, false, op_asl, ADDR_MODE_ZERO_PAGE_X), + INE(0x17), + INS(0x18, "CLC", 2, false, op_clc, ADDR_MODE_IMPLIED), + INS(0x19, "ORA", 4, true, op_ora, ADDR_MODE_ABSOLUTE_Y), + INE(0x1a), + INE(0x1b), + INE(0x1c), + INS(0x1d, "ORA", 4, true, op_ora, ADDR_MODE_ABSOLUTE_X), + INS(0x1e, "ASL", 7, false, op_asl, ADDR_MODE_ABSOLUTE_X), + INE(0x1f), + INS(0x20, "JSR", 6, false, op_jsr, ADDR_MODE_ABSOLUTE), + INS(0x21, "AND", 6, false, op_and, ADDR_MODE_INDIRECT_X), + INE(0x22), + INE(0x23), + INS(0x24, "BIT", 3, false, op_bit, ADDR_MODE_ZERO_PAGE), + INS(0x25, "AND", 3, false, op_and, ADDR_MODE_ZERO_PAGE), + INS(0x26, "ROL", 5, false, op_rol, ADDR_MODE_ZERO_PAGE), + INE(0x27), + INS(0x28, "PLP", 4, false, op_plp, ADDR_MODE_IMPLIED), + INS(0x29, "AND", 2, false, op_and, ADDR_MODE_IMMEDIATE), + INS(0x2a, "ROL", 2, false, op_rol, ADDR_MODE_ACCUMULATOR), + INE(0x2b), + INS(0x2c, "BIT", 4, false, op_bit, ADDR_MODE_ABSOLUTE), + INS(0x2d, "AND", 4, false, op_and, ADDR_MODE_ABSOLUTE), + INS(0x2e, "ROL", 6, false, op_rol, ADDR_MODE_ABSOLUTE), + INE(0x2f), + INS(0x30, "BMI", 2, true, op_bmi, ADDR_MODE_RELATIVE), + INS(0x31, "AND", 5, true, op_and, ADDR_MODE_INDIRECT_Y), + INE(0x32), + INE(0x33), + INE(0x34), + INS(0x35, "AND", 4, false, op_and, ADDR_MODE_ZERO_PAGE_X), + INS(0x36, "ROL", 6, false, op_rol, ADDR_MODE_ZERO_PAGE_X), + INE(0x37), + INS(0x38, "SEC", 2, false, op_sec, ADDR_MODE_IMPLIED), + INS(0x39, "AND", 4, true, op_and, ADDR_MODE_ABSOLUTE_Y), + INE(0x3a), + INE(0x3b), + INE(0x3c), + INS(0x3d, "AND", 4, true, op_and, ADDR_MODE_ABSOLUTE_X), + INS(0x3e, "ROL", 7, false, op_rol, ADDR_MODE_ABSOLUTE_X), + INE(0x3f), + INS(0x40, "RTI", 6, false, op_rti, ADDR_MODE_IMPLIED), + INS(0x41, "EOR", 6, false, op_eor, ADDR_MODE_INDIRECT_X), + INE(0x42), + INE(0x43), + INE(0x44), + INS(0x45, "EOR", 3, false, op_eor, ADDR_MODE_ZERO_PAGE), + INS(0x46, "LSR", 5, false, op_lsr, ADDR_MODE_ZERO_PAGE), + INE(0x47), + INS(0x48, "PHA", 3, false, op_pha, ADDR_MODE_IMPLIED), + INS(0x49, "EOR", 2, false, op_eor, ADDR_MODE_IMMEDIATE), + INS(0x4a, "LSR", 2, false, op_lsr, ADDR_MODE_ACCUMULATOR), + INE(0x4b), + INS(0x4c, "JMP", 3, false, op_jmp, ADDR_MODE_ABSOLUTE), + INS(0x4d, "EOR", 4, false, op_eor, ADDR_MODE_ABSOLUTE), + INS(0x4e, "LSR", 6, false, op_lsr, ADDR_MODE_ABSOLUTE), + INE(0x4f), + INS(0x50, "BVC", 2, true, op_bvc, ADDR_MODE_RELATIVE), + INS(0x51, "EOR", 5, true, op_eor, ADDR_MODE_INDIRECT_Y), + INE(0x52), + INE(0x53), + INE(0x54), + INS(0x55, "EOR", 4, false, op_eor, ADDR_MODE_ZERO_PAGE_X), + INS(0x56, "LSR", 6, false, op_lsr, ADDR_MODE_ZERO_PAGE_X), + INE(0x57), + INS(0x58, "CLI", 2, false, op_cli, ADDR_MODE_IMPLIED), + INS(0x59, "EOR", 4, true, op_eor, ADDR_MODE_ABSOLUTE_Y), + INE(0x5a), + INE(0x5b), + INE(0x5c), + INS(0x5d, "EOR", 4, true, op_eor, ADDR_MODE_ABSOLUTE_X), + INS(0x5e, "LSR", 7, false, op_lsr, ADDR_MODE_ABSOLUTE_X), + INE(0x5f), + INS(0x60, "RTS", 6, false, op_rts, ADDR_MODE_IMPLIED), + INS(0x61, "ADC", 6, false, op_adc, ADDR_MODE_INDIRECT_X), + INE(0x62), + INE(0x63), + INE(0x64), + INS(0x65, "ADC", 3, false, op_adc, ADDR_MODE_ZERO_PAGE), + INS(0x66, "ROR", 5, false, op_ror, ADDR_MODE_ZERO_PAGE), + INE(0x67), + INS(0x68, "PLA", 4, false, op_pla, ADDR_MODE_IMPLIED), + INS(0x69, "ADC", 2, false, op_adc, ADDR_MODE_IMMEDIATE), + INS(0x6a, "ROR", 2, false, op_ror, ADDR_MODE_ACCUMULATOR), + INE(0x6b), + INS(0x6c, "JMP", 5, false, op_jmp, ADDR_MODE_INDIRECT), + INS(0x6d, "ADC", 4, false, op_adc, ADDR_MODE_ABSOLUTE), + INS(0x6e, "ROR", 6, false, op_ror, ADDR_MODE_ABSOLUTE), + INE(0x6f), + INS(0x70, "BVS", 2, true, op_bvs, ADDR_MODE_RELATIVE), + INS(0x71, "ADC", 5, true, op_adc, ADDR_MODE_INDIRECT_Y), + INE(0x72), + INE(0x73), + INE(0x74), + INS(0x75, "ADC", 4, false, op_adc, ADDR_MODE_ZERO_PAGE_X), + INS(0x76, "ROR", 6, false, op_ror, ADDR_MODE_ZERO_PAGE_X), + INE(0x77), + INS(0x78, "SEI", 2, false, op_sei, ADDR_MODE_IMPLIED), + INS(0x79, "ADC", 4, true, op_adc, ADDR_MODE_ABSOLUTE_Y), + INE(0x7a), + INE(0x7b), + INE(0x7c), + INS(0x7d, "ADC", 4, true, op_adc, ADDR_MODE_ABSOLUTE_X), + INS(0x7e, "ROR", 7, false, op_ror, ADDR_MODE_ABSOLUTE_X), + INE(0x7f), + INE(0x80), + INS(0x81, "STA", 6, false, op_sta, ADDR_MODE_INDIRECT_X), + INE(0x82), + INE(0x83), + INS(0x84, "STY", 3, false, op_sty, ADDR_MODE_ZERO_PAGE), + INS(0x85, "STA", 3, false, op_sta, ADDR_MODE_ZERO_PAGE), + INS(0x86, "STX", 3, false, op_stx, ADDR_MODE_ZERO_PAGE), + INE(0x87), + INS(0x88, "DEY", 2, false, op_dey, ADDR_MODE_IMPLIED), + INE(0x89), + INS(0x8a, "TXA", 2, false, op_txa, ADDR_MODE_IMPLIED), + INE(0x8b), + INS(0x8c, "STY", 4, false, op_sty, ADDR_MODE_ABSOLUTE), + INS(0x8d, "STA", 4, false, op_sta, ADDR_MODE_ABSOLUTE), + INS(0x8e, "STX", 4, false, op_stx, ADDR_MODE_ABSOLUTE), + INE(0x8f), + INS(0x90, "BCC", 2, true, op_bcc, ADDR_MODE_RELATIVE), + INS(0x91, "STA", 6, false, op_sta, ADDR_MODE_INDIRECT_Y), + INE(0x92), + INE(0x93), + INS(0x94, "STY", 4, false, op_sty, ADDR_MODE_ZERO_PAGE_X), + INS(0x95, "STA", 4, false, op_sta, ADDR_MODE_ZERO_PAGE_X), + INS(0x96, "STX", 4, false, op_stx, ADDR_MODE_ZERO_PAGE_Y), + INE(0x97), + INS(0x98, "TYA", 2, false, op_tya, ADDR_MODE_IMPLIED), + INS(0x99, "STA", 5, false, op_sta, ADDR_MODE_ABSOLUTE_Y), + INS(0x9a, "TXS", 2, false, op_txs, ADDR_MODE_IMPLIED), + INE(0x9b), + INE(0x9c), + INS(0x9d, "STA", 5, false, op_sta, ADDR_MODE_ABSOLUTE_X), + INE(0x9e), + INE(0x9f), + INS(0xa0, "LDY", 2, false, op_ldy, ADDR_MODE_IMMEDIATE), + INS(0xa1, "LDA", 6, false, op_lda, ADDR_MODE_INDIRECT_X), + INS(0xa2, "LDX", 2, false, op_ldx, ADDR_MODE_IMMEDIATE), + INE(0xa3), + INS(0xa4, "LDY", 3, false, op_ldy, ADDR_MODE_ZERO_PAGE), + INS(0xa5, "LDA", 3, false, op_lda, ADDR_MODE_ZERO_PAGE), + INS(0xa6, "LDX", 3, false, op_ldx, ADDR_MODE_ZERO_PAGE), + INE(0xa7), + INS(0xa8, "TAY", 2, false, op_tay, ADDR_MODE_IMPLIED), + INS(0xa9, "LDA", 2, false, op_lda, ADDR_MODE_IMMEDIATE), + INS(0xaa, "TAX", 2, false, op_tax, ADDR_MODE_IMPLIED), + INE(0xab), + INS(0xac, "LDY", 4, false, op_ldy, ADDR_MODE_ABSOLUTE), + INS(0xad, "LDA", 4, false, op_lda, ADDR_MODE_ABSOLUTE), + INS(0xae, "LDX", 4, false, op_ldx, ADDR_MODE_ABSOLUTE), + INE(0xaf), + INS(0xb0, "BCS", 2, true, op_bcs, ADDR_MODE_RELATIVE), + INS(0xb1, "LDA", 5, true, op_lda, ADDR_MODE_INDIRECT_Y), + INE(0xb2), + INE(0xb3), + INS(0xb4, "LDY", 4, false, op_ldy, ADDR_MODE_ZERO_PAGE_X), + INS(0xb5, "LDA", 4, false, op_lda, ADDR_MODE_ZERO_PAGE_X), + INS(0xb6, "LDX", 4, false, op_ldx, ADDR_MODE_ZERO_PAGE_Y), + INE(0xb7), + INS(0xb8, "CLV", 2, false, op_clv, ADDR_MODE_IMPLIED), + INS(0xb9, "LDA", 4, true, op_lda, ADDR_MODE_ABSOLUTE_Y), + INS(0xba, "TSX", 2, false, op_tsx, ADDR_MODE_IMPLIED), + INE(0xbb), + INS(0xbc, "LDY", 4, true, op_ldy, ADDR_MODE_ABSOLUTE_X), + INS(0xbd, "LDA", 4, true, op_lda, ADDR_MODE_ABSOLUTE_X), + INS(0xbe, "LDX", 4, true, op_ldx, ADDR_MODE_ABSOLUTE_Y), + INE(0xbf), + INS(0xc0, "CPY", 2, false, op_cpy, ADDR_MODE_IMMEDIATE), + INS(0xc1, "CMP", 6, false, op_cmp, ADDR_MODE_INDIRECT_X), + INE(0xc2), + INE(0xc3), + INS(0xc4, "CPY", 3, false, op_cpy, ADDR_MODE_ZERO_PAGE), + INS(0xc5, "CMP", 3, false, op_cmp, ADDR_MODE_ZERO_PAGE), + INS(0xc6, "DEC", 5, false, op_dec, ADDR_MODE_ZERO_PAGE), + INE(0xc7), + INS(0xc8, "INY", 2, false, op_iny, ADDR_MODE_IMPLIED), + INS(0xc9, "CMP", 2, false, op_cmp, ADDR_MODE_IMMEDIATE), + INS(0xca, "DEX", 2, false, op_dex, ADDR_MODE_IMPLIED), + INE(0xcb), + INS(0xcc, "CPY", 4, false, op_cpy, ADDR_MODE_ABSOLUTE), + INS(0xcd, "CMP", 4, false, op_cmp, ADDR_MODE_ABSOLUTE), + INS(0xce, "DEC", 6, false, op_dec, ADDR_MODE_ABSOLUTE), + INE(0xcf), + INS(0xd0, "BNE", 2, true, op_bne, ADDR_MODE_RELATIVE), + INS(0xd1, "CMP", 5, true, op_cmp, ADDR_MODE_INDIRECT_Y), + INE(0xd2), + INE(0xd3), + INE(0xd4), + INS(0xd5, "CMP", 4, false, op_cmp, ADDR_MODE_ZERO_PAGE_X), + INS(0xd6, "DEC", 6, false, op_dec, ADDR_MODE_ZERO_PAGE_X), + INE(0xd7), + INS(0xd8, "CLD", 2, false, op_cld, ADDR_MODE_IMPLIED), + INS(0xd9, "CMP", 4, true, op_cmp, ADDR_MODE_ABSOLUTE_Y), + INE(0xda), + INE(0xdb), + INE(0xdc), + INS(0xdd, "CMP", 4, true, op_cmp, ADDR_MODE_ABSOLUTE_X), + INS(0xde, "DEC", 7, false, op_dec, ADDR_MODE_ABSOLUTE_X), + INE(0xdf), + INS(0xe0, "CPX", 2, false, op_cpx, ADDR_MODE_IMMEDIATE), + INS(0xe1, "SBC", 6, false, op_sbc, ADDR_MODE_INDIRECT_X), + INE(0xe2), + INE(0xe3), + INS(0xe4, "CPX", 3, false, op_cpx, ADDR_MODE_ZERO_PAGE), + INS(0xe5, "SBC", 3, false, op_sbc, ADDR_MODE_ZERO_PAGE), + INS(0xe6, "INC", 5, false, op_inc, ADDR_MODE_ZERO_PAGE), + INE(0xe7), + INS(0xe8, "INX", 2, false, op_inx, ADDR_MODE_IMPLIED), + INS(0xe9, "SBC", 2, false, op_sbc, ADDR_MODE_IMMEDIATE), + INS(0xea, "NOP", 2, false, op_nop, ADDR_MODE_IMPLIED), + INE(0xeb), + INS(0xec, "CPX", 4, false, op_cpx, ADDR_MODE_ABSOLUTE), + INS(0xed, "SBC", 4, false, op_sbc, ADDR_MODE_ABSOLUTE), + INS(0xee, "INC", 6, false, op_inc, ADDR_MODE_ABSOLUTE), + INE(0xef), + INS(0xf0, "BEQ", 2, true, op_beq, ADDR_MODE_RELATIVE), + INS(0xf1, "SBC", 5, true, op_sbc, ADDR_MODE_INDIRECT_Y), + INE(0xf2), + INE(0xf3), + INE(0xf4), + INS(0xf5, "SBC", 4, false, op_sbc, ADDR_MODE_ZERO_PAGE_X), + INS(0xf6, "INC", 6, false, op_inc, ADDR_MODE_ZERO_PAGE_X), + INE(0xf7), + INS(0xf8, "SED", 2, false, op_sed, ADDR_MODE_IMPLIED), + INS(0xf9, "SBC", 4, true, op_sbc, ADDR_MODE_ABSOLUTE_Y), + INE(0xfa), + INE(0xfb), + INE(0xfc), + INS(0xfd, "SBC", 4, true, op_sbc, ADDR_MODE_ABSOLUTE_X), + INS(0xfe, "INC", 7, false, op_inc, ADDR_MODE_ABSOLUTE_X), + INE(0xff), +}; + +#undef INE +#undef INS + +instruction_t* instruction_get(uint8_t opc) { + return &instructions[opc]; +} + +uint8_t instruction_get_size(addr_mode_t mode) { + switch (mode) { + case ADDR_MODE_NONE: return 0; + case ADDR_MODE_ABSOLUTE: return 3; + case ADDR_MODE_ABSOLUTE_X: return 3; + case ADDR_MODE_ABSOLUTE_Y: return 3; + case ADDR_MODE_ACCUMULATOR: return 1; + case ADDR_MODE_IMMEDIATE: return 2; + case ADDR_MODE_IMPLIED: return 1; + case ADDR_MODE_IMPLIED_BRK: return 2; + case ADDR_MODE_INDIRECT: return 3; + case ADDR_MODE_INDIRECT_X: return 2; + case ADDR_MODE_INDIRECT_Y: return 2; + case ADDR_MODE_RELATIVE: return 2; + case ADDR_MODE_ZERO_PAGE: return 2; + case ADDR_MODE_ZERO_PAGE_X: return 2; + case ADDR_MODE_ZERO_PAGE_Y: return 2; + default: return 0; + } +} + +static int op_adc(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t old_acc = cpu->acc; + uint8_t val = cpu_read8(cpu, addr); + int res = cpu->acc + val + (uint8_t)cpu->flag_carry; + cpu->acc = (uint8_t)res; + cpu->flag_carry = res > 0xff; + cpu->flag_overflow = !((old_acc ^ val) & 0x80) && ((old_acc ^ cpu->acc) & 0x80); + cpu_update_zn_flags(cpu, cpu->acc); + return 0; +} + +static int op_and(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + cpu->acc = cpu->acc & val; + cpu_update_zn_flags(cpu, cpu->acc); + return 0; +} + +static int op_asl(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + if (mode == ADDR_MODE_ACCUMULATOR) { + cpu->flag_carry = AGNES_GET_BIT(cpu->acc, 7); + cpu->acc = cpu->acc << 1; + cpu_update_zn_flags(cpu, cpu->acc); + } else { + uint8_t val = cpu_read8(cpu, addr); + cpu->flag_carry = AGNES_GET_BIT(val, 7); + val = val << 1; + cpu_write8(cpu, addr, val); + cpu_update_zn_flags(cpu, val); + } + return 0; +} + +static int op_bcc(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + return !cpu->flag_carry ? take_branch(cpu, addr) : 0; +} + +static int op_bcs(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + return cpu->flag_carry ? take_branch(cpu, addr) : 0; +} + +static int op_beq(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + return cpu->flag_zero ? take_branch(cpu, addr) : 0; +} + +static int op_bit(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + uint8_t res = cpu->acc & val; + cpu->flag_zero = res == 0; + cpu->flag_overflow = AGNES_GET_BIT(val, 6); + cpu->flag_negative = AGNES_GET_BIT(val, 7); + return 0; +} + +static int op_bmi(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + return cpu->flag_negative ? take_branch(cpu, addr) : 0; +} + +static int op_bne(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + return !cpu->flag_zero ? take_branch(cpu, addr) : 0; +} + +static int op_bpl(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + return !cpu->flag_negative ? take_branch(cpu, addr) : 0; +} + +static int op_brk(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu_stack_push16(cpu, cpu->pc); + uint8_t flags = cpu_get_flags(cpu); + cpu_stack_push8(cpu, flags | 0x30); + cpu->pc = cpu_read16(cpu, 0xfffe); + cpu->flag_dis_interrupt = true; + return 0; +} + +static int op_bvc(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + return !cpu->flag_overflow ? take_branch(cpu, addr) : 0; +} + +static int op_bvs(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + return cpu->flag_overflow ? take_branch(cpu, addr) : 0; +} + +static int op_clc(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->flag_carry = false; + return 0; +} + +static int op_cld(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->flag_decimal = false; + return 0; +} + +static int op_cli(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->flag_dis_interrupt = false; + return 0; +} + +static int op_clv(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->flag_overflow = false; + return 0; +} + +static int op_cmp(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + cpu_update_zn_flags(cpu, cpu->acc - val); + cpu->flag_carry = cpu->acc >= val; + return 0; +} + +static int op_cpx(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + cpu_update_zn_flags(cpu, cpu->x - val); + cpu->flag_carry = cpu->x >= val; + return 0; +} + +static int op_cpy(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + cpu_update_zn_flags(cpu, cpu->y - val); + cpu->flag_carry = cpu->y >= val; + return 0; +} + +static int op_dec(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + cpu_write8(cpu, addr, val - 1); + cpu_update_zn_flags(cpu, val - 1); + return 0; +} + +static int op_dex(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->x--; + cpu_update_zn_flags(cpu, cpu->x); + return 0; +} + +static int op_dey(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->y--; + cpu_update_zn_flags(cpu, cpu->y); + return 0; +} + +static int op_eor(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + cpu->acc = cpu->acc ^ val; + cpu_update_zn_flags(cpu, cpu->acc); + return 0; +} + +static int op_inc(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + cpu_write8(cpu, addr, val + 1); + cpu_update_zn_flags(cpu, val + 1); + return 0; +} + +static int op_inx(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->x++; + cpu_update_zn_flags(cpu, cpu->x); + return 0; +} + +static int op_iny(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->y++; + cpu_update_zn_flags(cpu, cpu->y); + return 0; +} + +static int op_jmp(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->pc = addr; + return 0; +} + +static int op_jsr(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu_stack_push16(cpu, cpu->pc - 1); + cpu->pc = addr; + return 0; +} + +static int op_lda(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + cpu->acc = val; + cpu_update_zn_flags(cpu, cpu->acc); + return 0; +} + +static int op_ldx(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + cpu->x = val; + cpu_update_zn_flags(cpu, cpu->x); + return 0; +} + +static int op_ldy(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + cpu->y = val; + cpu_update_zn_flags(cpu, cpu->y); + return 0; +} + +static int op_lsr(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + if (mode == ADDR_MODE_ACCUMULATOR) { + cpu->flag_carry = AGNES_GET_BIT(cpu->acc, 0); + cpu->acc = cpu->acc >> 1; + cpu_update_zn_flags(cpu, cpu->acc); + } else { + uint8_t val = cpu_read8(cpu, addr); + cpu->flag_carry = AGNES_GET_BIT(val, 0); + val = val >> 1; + cpu_write8(cpu, addr, val); + cpu_update_zn_flags(cpu, val); + } + return 0; +} + +static int op_nop(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + return 0; +} + +static int op_ora(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + cpu->acc = cpu->acc | val; + cpu_update_zn_flags(cpu, cpu->acc); + return 0; +} + +static int op_pha(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu_stack_push8(cpu, cpu->acc); + return 0; +} + +static int op_php(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t flags = cpu_get_flags(cpu); + cpu_stack_push8(cpu, flags | 0x30); + return 0; +} + +static int op_pla(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->acc = cpu_stack_pop8(cpu); + cpu_update_zn_flags(cpu, cpu->acc); + return 0; +} + +static int op_plp(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t flags = cpu_stack_pop8(cpu); + cpu_restore_flags(cpu, flags); + return 0; +} + +static int op_rol(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t old_carry = cpu->flag_carry; + if (mode == ADDR_MODE_ACCUMULATOR) { + cpu->flag_carry = AGNES_GET_BIT(cpu->acc, 7); + cpu->acc = (cpu->acc << 1) | old_carry; + cpu_update_zn_flags(cpu, cpu->acc); + } else { + uint8_t val = cpu_read8(cpu, addr); + cpu->flag_carry = AGNES_GET_BIT(val, 7); + val = (val << 1) | old_carry; + cpu_write8(cpu, addr, val); + cpu_update_zn_flags(cpu, val); + } + return 0; +} + +static int op_ror(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t old_carry = cpu->flag_carry; + if (mode == ADDR_MODE_ACCUMULATOR) { + cpu->flag_carry = AGNES_GET_BIT(cpu->acc, 0); + cpu->acc = (cpu->acc >> 1) | (old_carry << 7); + cpu_update_zn_flags(cpu, cpu->acc); + } else { + uint8_t val = cpu_read8(cpu, addr); + cpu->flag_carry = AGNES_GET_BIT(val, 0); + val = (val >> 1) | (old_carry << 7); + cpu_write8(cpu, addr, val); + cpu_update_zn_flags(cpu, val); + } + return 0; +} + +static int op_rti(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t flags = cpu_stack_pop8(cpu); + cpu_restore_flags(cpu, flags); + cpu->pc = cpu_stack_pop16(cpu); + return 0; +} + +static int op_rts(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->pc = cpu_stack_pop16(cpu) + 1; + return 0; +} + +static int op_sbc(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + uint8_t val = cpu_read8(cpu, addr); + uint8_t old_acc = cpu->acc; + int res = cpu->acc - val - (cpu->flag_carry ? 0 : 1); + cpu->acc = (uint8_t)res; + cpu_update_zn_flags(cpu, cpu->acc); + cpu->flag_carry = res >= 0; + cpu->flag_overflow = ((old_acc ^ val) & 0x80) && ((old_acc ^ cpu->acc) & 0x80); + return 0; +} + +static int op_sec(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->flag_carry = true; + return 0; +} + +static int op_sed(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->flag_decimal = true; + return 0; +} + +static int op_sei(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->flag_dis_interrupt = true; + return 0; +} + +static int op_sta(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu_write8(cpu, addr, cpu->acc); + return 0; +} + +static int op_stx(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu_write8(cpu, addr, cpu->x); + return 0; +} + +static int op_sty(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu_write8(cpu, addr, cpu->y); + return 0; +} + +static int op_tax(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->x = cpu->acc; + cpu_update_zn_flags(cpu, cpu->x); + return 0; +} + +static int op_tay(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->y = cpu->acc; + cpu_update_zn_flags(cpu, cpu->y); + return 0; +} + +static int op_tsx(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->x = cpu->sp; + cpu_update_zn_flags(cpu, cpu->x); + return 0; +} + +static int op_txa(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->acc = cpu->x; + cpu_update_zn_flags(cpu, cpu->acc); + return 0; +} + +static int op_txs(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->sp = cpu->x; + return 0; +} + +static int op_tya(cpu_t *cpu, uint16_t addr, addr_mode_t mode) { + cpu->acc = cpu->y; + cpu_update_zn_flags(cpu, cpu->acc); + return 0; +} + +static int take_branch(cpu_t *cpu, uint16_t addr) { + bool page_crossed = (cpu->pc & 0xff00) != (addr & 0xff00); + cpu->pc = addr; + return page_crossed ? 2 : 1; +} +//FILE_END +//FILE_START:mapper.c +#ifndef AGNES_AMALGAMATED +#include "mapper0.h" + +#include "agnes_types.h" + +#include "mapper0.h" +#include "mapper1.h" +#include "mapper2.h" +#include "mapper4.h" +#endif + +bool mapper_init(agnes_t *agnes) { + switch (agnes->gamepack.mapper) { + case 0: mapper0_init(&agnes->mapper.m0, agnes); return true; + case 1: mapper1_init(&agnes->mapper.m1, agnes); return true; + case 2: mapper2_init(&agnes->mapper.m2, agnes); return true; + case 4: mapper4_init(&agnes->mapper.m4, agnes); return true; + default: return false; + } +} + +uint8_t mapper_read(agnes_t *agnes, uint16_t addr) { + switch (agnes->gamepack.mapper) { + case 0: return mapper0_read(&agnes->mapper.m0, addr); + case 1: return mapper1_read(&agnes->mapper.m1, addr); + case 2: return mapper2_read(&agnes->mapper.m2, addr); + case 4: return mapper4_read(&agnes->mapper.m4, addr); + default: return 0; + } +} + +void mapper_write(agnes_t *agnes, uint16_t addr, uint8_t val) { + switch (agnes->gamepack.mapper) { + case 0: mapper0_write(&agnes->mapper.m0, addr, val); break; + case 1: mapper1_write(&agnes->mapper.m1, addr, val); break; + case 2: mapper2_write(&agnes->mapper.m2, addr, val); break; + case 4: mapper4_write(&agnes->mapper.m4, addr, val); break; + } +} + +void mapper_pa12_rising_edge(agnes_t *agnes) { + switch (agnes->gamepack.mapper) { + case 4: mapper4_pa12_rising_edge(&agnes->mapper.m4); break; + } +} +//FILE_END +//FILE_START:mapper0.c +#ifndef AGNES_AMALGAMATED +#include "mapper0.h" + +#include "agnes_types.h" +#endif + +void mapper0_init(mapper0_t *mapper, agnes_t *agnes) { + mapper->agnes = agnes; + + mapper->prg_bank_offsets[0] = 0; + mapper->prg_bank_offsets[1] = agnes->gamepack.prg_rom_banks_count > 1 ? (16 * 1024) : 0; + mapper->use_chr_ram = agnes->gamepack.chr_rom_banks_count == 0; +} + +uint8_t mapper0_read(mapper0_t *mapper, uint16_t addr) { + uint8_t res = 0; + if (addr < 0x2000) { + if (mapper->use_chr_ram) { + res = mapper->chr_ram[addr]; + } else { + res = mapper->agnes->gamepack.data[mapper->agnes->gamepack.chr_rom_offset + addr]; + } + } else if (addr >= 0x8000) { + int bank = addr >> 14 & 0x1; + unsigned bank_offset = mapper->prg_bank_offsets[bank]; + unsigned addr_offset = addr & 0x3fff; + unsigned offset = mapper->agnes->gamepack.prg_rom_offset + bank_offset + addr_offset; + res = mapper->agnes->gamepack.data[offset]; + } + return res; +} + +void mapper0_write(mapper0_t *mapper, uint16_t addr, uint8_t val) { + if (mapper->use_chr_ram && addr < 0x2000) { + mapper->chr_ram[addr] = val; + } +} +//FILE_END +//FILE_START:mapper1.c +#ifndef AGNES_AMALGAMATED +#include "mapper1.h" + +#include "agnes_types.h" +#endif + +static void mapper1_write_control(mapper1_t *mapper, uint8_t val); +static void mapper1_set_offsets(mapper1_t *mapper); + +void mapper1_init(mapper1_t *mapper, agnes_t *agnes) { + mapper->agnes = agnes; + + mapper->shift = 0; + mapper->shift_count = 0; + mapper->control = 0; + mapper->prg_mode = 3; + mapper->chr_mode = 0; + mapper->chr_banks[0] = 0; + mapper->chr_banks[1] = 0; + mapper->prg_bank = 0; + mapper->use_chr_ram = agnes->gamepack.chr_rom_banks_count == 0; + + mapper1_set_offsets(mapper); +} + +uint8_t mapper1_read(mapper1_t *mapper, uint16_t addr) { + + uint8_t res = 0; + if (addr < 0x2000) { + if (mapper->use_chr_ram) { + res = mapper->chr_ram[addr]; + } else { + unsigned bank = (addr >> 12) & 0x1; + unsigned bank_offset = mapper->chr_bank_offsets[bank]; + unsigned addr_offset = addr & 0xfff; + unsigned offset = mapper->agnes->gamepack.chr_rom_offset + bank_offset + addr_offset; + res = mapper->agnes->gamepack.data[offset]; + } + } else if (addr >= 0x6000 && addr < 0x8000) { + res = mapper->prg_ram[addr - 0x6000]; + } else if (addr >= 0x8000) { + int bank = (addr >> 14) & 0x1; + unsigned bank_offset = mapper->prg_bank_offsets[bank]; + unsigned addr_offset = addr & 0x3fff; + unsigned offset = mapper->agnes->gamepack.prg_rom_offset + bank_offset + addr_offset; + res = mapper->agnes->gamepack.data[offset]; + } + return res; +} + +void mapper1_write(mapper1_t *mapper, uint16_t addr, uint8_t val) { + + if (addr < 0x2000) { + if (mapper->use_chr_ram) { + mapper->chr_ram[addr] = val; + } + } else if (addr >= 0x6000 && addr < 0x8000) { + mapper->prg_ram[addr - 0x6000] = val; + } else if (addr >= 0x8000) { + if (AGNES_GET_BIT(val, 7)) { + mapper->shift = 0; + mapper->shift_count = 0; + mapper1_write_control(mapper, mapper->control | 0x0c); + mapper1_set_offsets(mapper); + } else { + mapper->shift >>= 1; + mapper->shift = mapper->shift | ((val & 0x1) << 4); + mapper->shift_count++; + if (mapper->shift_count == 5) { + uint8_t shift_val = mapper->shift & 0x1f; + mapper->shift = 0; + mapper->shift_count = 0; + uint8_t reg = (addr >> 13) & 0x3; // bits 13 and 14 select register + switch (reg) { + case 0: mapper1_write_control(mapper, shift_val); break; + case 1: mapper->chr_banks[0] = shift_val; break; + case 2: mapper->chr_banks[1] = shift_val; break; + case 3: mapper->prg_bank = shift_val & 0xf; break; + } + mapper1_set_offsets(mapper); + } + } + } +} + +static void mapper1_write_control(mapper1_t *mapper, uint8_t val) { + mapper->control = val; + switch (val & 0x3) { + case 0: mapper->agnes->mirroring_mode = MIRRORING_MODE_SINGLE_LOWER; break; + case 1: mapper->agnes->mirroring_mode = MIRRORING_MODE_SINGLE_UPPER; break; + case 2: mapper->agnes->mirroring_mode = MIRRORING_MODE_VERTICAL; break; + case 3: mapper->agnes->mirroring_mode = MIRRORING_MODE_HORIZONTAL; break; + } + mapper->prg_mode = (val >> 2) & 0x3; + mapper->chr_mode = (val >> 4) & 0x1; +} + +static void mapper1_set_offsets(mapper1_t *mapper) { + switch (mapper->chr_mode) { + case 0: { + mapper->chr_bank_offsets[0] = (mapper->chr_banks[0] & 0xfe) * (8 * 1024); + mapper->chr_bank_offsets[1] = (mapper->chr_banks[0] & 0xfe) * (8 * 1024) + (4 * 1024); + break; + } + case 1: { + mapper->chr_bank_offsets[0] = mapper->chr_banks[0] * (4 * 1024); + mapper->chr_bank_offsets[1] = mapper->chr_banks[1] * (4 * 1024); + break; + } + } + + switch (mapper->prg_mode) { + case 0: case 1: { + mapper->prg_bank_offsets[0] = (mapper->prg_bank & 0xe) * (32 * 1024); + mapper->prg_bank_offsets[1] = (mapper->prg_bank & 0xe) * (32 * 1024) + (16 * 1024); + break; + } + case 2: { + mapper->prg_bank_offsets[0] = 0; + mapper->prg_bank_offsets[1] = mapper->prg_bank * (16 * 1024); + break; + } + case 3: { + mapper->prg_bank_offsets[0] = mapper->prg_bank * (16 * 1024); + mapper->prg_bank_offsets[1] = (mapper->agnes->gamepack.prg_rom_banks_count - 1) * (16 * 1024); + break; + } + } +} +//FILE_END +//FILE_START:mapper2.c +#ifndef AGNES_AMALGAMATED +#include "mapper2.h" +#include "agnes_types.h" +#endif + +void mapper2_init(mapper2_t *mapper, agnes_t *agnes) { + mapper->agnes = agnes; + mapper->prg_bank_offsets[0] = 0; + mapper->prg_bank_offsets[1] = (agnes->gamepack.prg_rom_banks_count - 1) * (16 * 1024); +} + +uint8_t mapper2_read(mapper2_t *mapper, uint16_t addr) { + uint8_t res = 0; + if (addr < 0x2000) { + res = mapper->chr_ram[addr]; + } else if (addr >= 0x8000) { + int bank = (addr >> 14) & 0x1; + unsigned bank_offset = mapper->prg_bank_offsets[bank]; + unsigned addr_offset = addr & 0x3fff; + unsigned offset = mapper->agnes->gamepack.prg_rom_offset + bank_offset + addr_offset; + res = mapper->agnes->gamepack.data[offset]; + } + return res; +} + +void mapper2_write(mapper2_t *mapper, uint16_t addr, uint8_t val) { + if (addr < 0x2000) { + mapper->chr_ram[addr] = val; + } else if (addr >= 0x8000) { + int bank = val % (mapper->agnes->gamepack.prg_rom_banks_count); + mapper->prg_bank_offsets[0] = bank * (16 * 1024); + } +} +//FILE_END +//FILE_START:mapper4.c +#ifndef AGNES_AMALGAMATED +#include "mapper4.h" + +#include "agnes_types.h" +#include "cpu.h" +#endif + +static void mapper4_write_register(mapper4_t *mapper, uint16_t addr, uint8_t val); +static void mapper4_set_offsets(mapper4_t *mapper); + +void mapper4_init(mapper4_t *mapper, agnes_t *agnes) { + mapper->agnes = agnes; + + mapper->prg_mode = 0; + mapper->chr_mode = 0; + mapper->irq_enabled = false; + mapper->reg_ix = 0; + + mapper->regs[0] = 0; + mapper->regs[1] = 2; + mapper->regs[2] = 4; + mapper->regs[3] = 5; + mapper->regs[4] = 6; + mapper->regs[5] = 7; + mapper->regs[6] = 0; + mapper->regs[7] = 1; + + mapper->counter = 0; + mapper->counter_reload = 0; + mapper->use_chr_ram = agnes->gamepack.chr_rom_banks_count == 0; + + mapper4_set_offsets(mapper); +} + +void mapper4_pa12_rising_edge(mapper4_t *mapper) { + if (mapper->counter == 0) { + mapper->counter = mapper->counter_reload; + } else { + mapper->counter--; + if (mapper->counter == 0 && mapper->irq_enabled) { + cpu_trigger_irq(&mapper->agnes->cpu); + } + } +} + +uint8_t mapper4_read(mapper4_t *mapper, uint16_t addr) { + uint8_t res = 0; + if (addr < 0x2000) { + int bank = (addr >> 10) & 0x7; + unsigned bank_offset = mapper->chr_bank_offsets[bank]; + unsigned addr_offset = addr & 0x3ff; + unsigned offset = bank_offset + addr_offset; + if (mapper->use_chr_ram) { + offset = offset & ((8 * 1024) - 1); + res = mapper->chr_ram[offset]; + } else { + unsigned chr_rom_size = (mapper->agnes->gamepack.chr_rom_banks_count * 8 * 1024); + offset = offset % chr_rom_size; + res = mapper->agnes->gamepack.data[mapper->agnes->gamepack.chr_rom_offset + offset]; + } + } else if (addr >= 0x6000 && addr < 0x8000) { + return mapper->prg_ram[addr - 0x6000]; + } else if (addr >= 0x8000) { + int bank = (addr >> 13) & 0x3; + unsigned bank_offset = mapper->prg_bank_offsets[bank]; + unsigned addr_offset = addr & 0x1fff; + unsigned offset = mapper->agnes->gamepack.prg_rom_offset + bank_offset + addr_offset; + res = mapper->agnes->gamepack.data[offset]; + } + return res; +} + +void mapper4_write(mapper4_t *mapper, uint16_t addr, uint8_t val) { + if (addr < 0x2000 && mapper->use_chr_ram) { + int bank = (addr >> 10) & 0x7; + unsigned bank_offset = mapper->chr_bank_offsets[bank]; + unsigned addr_offset = addr & 0x3ff; + unsigned full_offset = (bank_offset + addr_offset) & ((8 * 1024) - 1); + mapper->chr_ram[full_offset] = val; + } else if (addr >= 0x6000 && addr < 0x8000) { + mapper->prg_ram[addr - 0x6000] = val; + } else if (addr >= 0x8000) { + mapper4_write_register(mapper, addr, val); + } +} + +static void mapper4_write_register(mapper4_t *mapper, uint16_t addr, uint8_t val) { + bool addr_odd = addr & 0x1; + bool addr_even = !addr_odd; + if (addr <= 0x9ffe && addr_even) { // Bank select ($8000-$9FFE, even) + mapper->reg_ix = val & 0x7; + mapper->prg_mode = (val >> 6) & 0x1; + mapper->chr_mode = (val >> 7) & 0x1; + mapper4_set_offsets(mapper); + } else if (addr <= 0x9fff && addr_odd) { // Bank data ($8001-$9FFF, odd) + mapper->regs[mapper->reg_ix] = val; + mapper4_set_offsets(mapper); + } else if (addr <= 0xbffe && addr_even) { // Mirroring ($A000-$BFFE, even) + if (mapper->agnes->mirroring_mode != MIRRORING_MODE_FOUR_SCREEN) { + mapper->agnes->mirroring_mode = (val & 0x1) ? MIRRORING_MODE_HORIZONTAL : MIRRORING_MODE_VERTICAL; + } + } else if (addr <= 0xbfff && addr_odd) { // PRG RAM protect ($A001-$BFFF, odd) + // probably not required (according to https://wiki.nesdev.com/w/index.php/MMC3) + } else if (addr <= 0xdffe && addr_even) { // IRQ latch ($C000-$DFFE, even) + mapper->counter_reload = val; + } else if (addr <= 0xdfff && addr_odd) { // IRQ reload ($C001-$DFFF, odd) + mapper->counter = 0; + } else if (addr <= 0xfffe && addr_even) { // IRQ disable ($E000-$FFFE, even) + mapper->irq_enabled = false; + } else if (addr <= 0xffff && addr_odd) { // IRQ enable ($E001-$FFFF, odd) + mapper->irq_enabled = true; + } +} + +static void mapper4_set_offsets(mapper4_t *mapper) { + switch (mapper->chr_mode) { + case 0: { // R0_1, R0_2, R1_1, R1_2, R2, R3, R4, R5 + mapper->chr_bank_offsets[0] = (mapper->regs[0] & 0xfe) * 1024; + mapper->chr_bank_offsets[1] = (mapper->regs[0] & 0xfe) * 1024 + 1024; + mapper->chr_bank_offsets[2] = (mapper->regs[1] & 0xfe) * 1024; + mapper->chr_bank_offsets[3] = (mapper->regs[1] & 0xfe) * 1024 + 1024; + mapper->chr_bank_offsets[4] = mapper->regs[2] * 1024; + mapper->chr_bank_offsets[5] = mapper->regs[3] * 1024; + mapper->chr_bank_offsets[6] = mapper->regs[4] * 1024; + mapper->chr_bank_offsets[7] = mapper->regs[5] * 1024; + break; + } + case 1: { // R2, R3, R4, R5, R0_1, R0_2, R1_1, R1_2 + mapper->chr_bank_offsets[0] = mapper->regs[2] * 1024; + mapper->chr_bank_offsets[1] = mapper->regs[3] * 1024; + mapper->chr_bank_offsets[2] = mapper->regs[4] * 1024; + mapper->chr_bank_offsets[3] = mapper->regs[5] * 1024; + mapper->chr_bank_offsets[4] = (mapper->regs[0] & 0xfe) * 1024; + mapper->chr_bank_offsets[5] = (mapper->regs[0] & 0xfe) * 1024 + 1024; + mapper->chr_bank_offsets[6] = (mapper->regs[1] & 0xfe) * 1024; + mapper->chr_bank_offsets[7] = (mapper->regs[1] & 0xfe) * 1024 + 1024; + break; + } + } + + switch (mapper->prg_mode) { + case 0: { // R6, R7, -2, -1 + mapper->prg_bank_offsets[0] = mapper->regs[6] * (8 * 1024); + mapper->prg_bank_offsets[1] = mapper->regs[7] * (8 * 1024); + mapper->prg_bank_offsets[2] = (mapper->agnes->gamepack.prg_rom_banks_count - 1) * (16 * 1024); + mapper->prg_bank_offsets[3] = (mapper->agnes->gamepack.prg_rom_banks_count - 1) * (16 * 1024) + (8 * 1024); + break; + } + case 1: { // -2, R7, R6, -1 + mapper->prg_bank_offsets[0] = (mapper->agnes->gamepack.prg_rom_banks_count - 1) * (16 * 1024); + mapper->prg_bank_offsets[1] = mapper->regs[7] * (8 * 1024); + mapper->prg_bank_offsets[2] = mapper->regs[6] * (8 * 1024); + mapper->prg_bank_offsets[3] = (mapper->agnes->gamepack.prg_rom_banks_count - 1) * (16 * 1024) + (8 * 1024); + break; + } + } +} +//FILE_END diff --git a/apps/agnes/src/agnes.h b/apps/agnes/src/agnes.h new file mode 100644 index 0000000..1488e36 --- /dev/null +++ b/apps/agnes/src/agnes.h @@ -0,0 +1,88 @@ +#include +/* +SPDX-License-Identifier: MIT + +agnes +https://http://github.com/kgabis/agnes +Copyright (c) 2022 Krzysztof Gabis + +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 agnes_h +#define agnes_h + + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define AGNES_VERSION_MAJOR 0 +#define AGNES_VERSION_MINOR 2 +#define AGNES_VERSION_PATCH 0 + +#define AGNES_VERSION_STRING "0.2.0" + +#include +#include +#include + +enum { + AGNES_SCREEN_WIDTH = 256, + AGNES_SCREEN_HEIGHT = 240 +}; + +typedef struct { + bool a; + bool b; + bool select; + bool start; + bool up; + bool down; + bool left; + bool right; +} agnes_input_t; + +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} agnes_color_t; + +typedef struct agnes agnes_t; +typedef struct agnes_state agnes_state_t; + +agnes_t* agnes_make(void); +void agnes_destroy(agnes_t *agn); +bool agnes_load_ines_data(agnes_t *agnes, const void *data, size_t data_size); +void agnes_set_input(agnes_t *agnes, const agnes_input_t *input_1, const agnes_input_t *input_2); +size_t agnes_state_size(void); +void agnes_dump_state(const agnes_t *agnes, agnes_state_t *out_res); +bool agnes_restore_state(agnes_t *agnes, const agnes_state_t *state); +bool agnes_tick(agnes_t *agnes, bool *out_new_frame); +bool agnes_next_frame(agnes_t *agnes); + +agnes_color_t agnes_get_screen_pixel(const agnes_t *agnes, int x, int y); + +#ifdef __cplusplus +} +#endif + +#endif /* agnes_h */ diff --git a/apps/agnes/src/console.zig b/apps/agnes/src/console.zig new file mode 100644 index 0000000..c5ae395 --- /dev/null +++ b/apps/agnes/src/console.zig @@ -0,0 +1,49 @@ +const std = @import("std"); +extern fn console_write(data: [*]const u8, len: usize) void; +var wbuf:[4096]u8 = undefined; +var cw = ConsoleWriter.init(&wbuf); +const uvm = @import("uvm.zig"); + +pub const WriteError = error{ Unsupported, NotConnected }; + +pub const ConsoleWriter = struct { + interface: std.Io.Writer, + err: ?WriteError = null, + + fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { + var ret: usize = 0; + + const b = w.buffered(); + uvm.print(b); + _ = w.consume(b.len); + + for (data) |d| { + uvm.print(d); + ret += d.len; + } + + const pattern = data[data.len - 1]; + for (0..splat) |_| { + uvm.print(pattern); + ret += pattern.len; + } + + return ret; + } + + pub fn init(buf: []u8) ConsoleWriter { + return ConsoleWriter{ + .interface = .{ + .buffer = buf, + .vtable = &.{ + .drain = drain, + }, + }, + }; + } +}; + +pub fn getWriter() *std.Io.Writer { + return &cw.interface; +} + diff --git a/apps/agnes/src/croom.nes b/apps/agnes/src/croom.nes new file mode 100644 index 0000000000000000000000000000000000000000..716d3ccdea5d593e4babaa73b87b449bfab4c9e1 GIT binary patch literal 24592 zcmeHv3wTr4mF_+dJ$x)%#~6$Z0w12n3;~%05Wyfp60pHM5+IZ$GjoJ3V_XwtgJLFho!8>!N#mJK0a9{frcHH;;Ai z7u~a1>F5~u*>3}izi%U5}6fP-?+StzI zOUsh1p-WMO3( zR2M5tLX47kFO3j`S`d&!JRO-q3s%Wf+k%I?6=say&Nk+qZCuj0_-v!AQLAGMPo58^ zDSU90VvtsL1&P828HGc(ojFb}vJv(`+Ht-hJ}>P+`dRVcWDGzQra8k6Su@;6Gu$~` zFEgDS9jj%SmXGD*X)*ip^qBd;qao{oWfAe0%OaK#cVOA^N9U!DNk5P-gSk!0!_5Vp z;#8JOzm9}#%0lT%Pl#96hd5R2k3cLnG5^0E0HOOL1Lm}AD^h8TNh1M)2L>yclNJRdU$H}sm0vDax018jY-8Q6NM z;_Z$FP%H=sdZm5uwKE}UT3U>4w+6-2Vr=kikuw#mGg+K3-gYz)!JKxUl{QBGZ^6PJlCmT=BJ9Yb1)7uZ6?$~tP z^_G;^b7p>wkwYeBp7S@nDuFvT9y6Kuo^;@yaDEHhw&GxpN!f64<;s;gR;Yz2`N738 zA(&_TXj6ofy5CVd1!D7q>tSPt4R4)V5u15xd2IR#h7%|4S-xihGC8g!_aTPEl+6nf z&<>fE{E$JJA0cvZek|q8{NVhR1)R-iQu5`)%N^s@PDx>OIaJ7XtPrhxR(8+L{=_rx(5Z~d9u^|H;gi)L!E|MH#DMEm-=>C*4zi{bEjJp2J z{NBxx?BJ5f6sn8ZgkQQMqeDhzVaTL%NFPRJUdN_3aNHA|-)lU!w$~6D6XH;F_&&M7 zz0C)az`HJPfQk$A39b+)2J?E^V^c9v1zd26!4=^{rU*MMi?ST;R$!?4kxzr0j@mEIqAuBhga-b5OOVqnix)c?VPF` z%7b1e{!8KCol^_QU4F_tHU*c&*e_h_0Mdu&q!p*bBsPt**)k@$sen0haHXx*r4XtY z=SSq2pz=;RmNEBP9eHPC<38LJ$)L)lI=V+|g;ej7@fV^0i7Ep+XyVDb8W+b*luqH4 z)9~uFoN#YA!p}>O@Mpzaa^q6f@H&=;2s~CDZ9Y|&G7qwkYhmP-Eo?#DXH_1S+gG#^ zOuESQoE2|VH)f(FVhjZoCbBkaLXJo7v8Wq43Axnh3gj5%_^7$%fxS7VSaRn8E24)1 zrJZ+ZR-{=}!FiH9zoaF6q69eMfV=N)!O{|zmPFmh5+u#EOrm9-Ni=J|w}U`S7+Ml! z-O4y@K8MeAVJ$}uMnuCS&X?NvFzjm9>c=+6wd%W+2**g{-6!%#7~a-L}h z_e{Bop$szRDGVK#Z#FOmOtX^R5Kwgc?jzCq}~4tUjSglXO=6l^lFH z_Gj$@Yz5%%53ejq{3c207JL|w#)sR*5QoOJedM-$Fu(C@q2gvc6WZFG!|WB3OPa( z1C1No!;Md3gM+1r5(-gi_8X-T*X|gWq%Bj+hS>Jh;X+-~rpUE6H%cK)yAy$j%Kl|Y z(AsIwko8^+;b?EZ7R%n~DLoRoMHkMN!FCdIX=8UjbqVcnM#YxT<@RJO znjcFa$@DRvKCJ9so6nvzSa|F#)>1EBPJNQXJ3rcZ%!rvnp!BgTSK7QAm5sp-f_DS0 zA5ZI_yubcQ<;e~vm=FZ(QbHCZI4xknX+at|MGl!;A=QXc<%-dI9PUy-n4e!Gh*?(Dd27yv7~XV1hp=5E`>7OyTXH9bYbT01Ero z@zs$Ox@FNBNmq==S4D(_!*mqG3A$HMINB)4L4fPUxJaHSa7LzG_=-gW6lr zc?-2zpa=j9VX~?LheV)lj&`yS5kyoY$Y}LxqtBaBzU8`%wG?`SKOb0yjOoDYv*IVg zLgz=Tl(oS%&W~0rkE6vZp>`FW85x!bf$*~wk1179=Y+|)tZyVWXkm&Dy+Myd~0;4yI2tN2Ud} z{k9Q%Y$Nh~Ot@|g8c&;AG`uBtC}@;Jaaq+ZNHS8p3$bV)bW9^iI}J<}xuuZMRfUm; zE~*;Gq#*RYb5&KZgpfxLPi&W@PtPS0kqhy1Y;6AFMPUQ>#(aorm(>afuPiE+OTmQ{ z`E~5)niSZxW1%)8zJs7X(YC~rfVBt{%BoMSk;rR{1Qssjv3B7}*Ft^SK%hrnP@l=_ zQ(Z_pu}q;nRefSHH%YL=Bhj{&gV3o#qUQn{hvoL~04K0M2H{l3vEYlSLOO3pA@+{* z*pIVqq=Wq-i6!?6F}fwW*@Hlec!MPO1Q8S@h3#!?`QX^Upzq&bQjDGda7i(RM8$&X zPN6$KI?Vk_9t&>N1>hi*fc)@pQ{DUrY5^`qbIONiET5$GEfix-b zmtaZ^89@L~mR|QUu5jOl!Y6-Wd}jL7)1R8Yxy2UTT)^WN;i-Zy<*DG)1rwB~gU=NF zgYryMLbh9$d^sxEgz0@i=?+B2&v*@ z&nVBdnWepv_7zxd84jy0E(+c{ai~u*p7?g3V(Q?}?p^uT4~x0fmiR8+J~S zkZamAPA}*Dh(9*y<&R)fd6+}Y&PN8vm{PPq!-*H$SA58~%-jnDeT%ZPoNck5;Scj@ zhz22g;Mp5XK8rR+%xholmxxQ49(6J?BZ#Kj%oV=!j>;=eK?h?~+sRF#W$;-9@@Gyn zE$m0ka`?;VN7`0^tjL{p7@EY-+YnKRl76&+gDdp(<#%@X<;zD`am_1n!o?v^KDv^N zWu0HisdR(S^tQCHziH{kVfr`uZ-KEwZqEW4QNDl+<)hCJR&q@amad`io+!ko8Y4MU zn?ysKv2f=*k&pMS~xI^&++47a|apFUr-{R2cc!t6p zAEOYPk3BG^h5aG}&I{Y?!Grtdj3Arjh*}~^kSfSEr@CcOfaN~k-^#jAyxD&-J7zeL z-O53dBb{GeQ>HcdaGgWfl`!dHILE43_$$-R?sdqxLB zJ<8?Y$&r*Gl_5w4pv|U4QjYd;%Cw`soH88+pq_)9$0HEsHIRgGJ$MGyX9suC>7Axb zYr(+M{)=cmJm$O5M(1l>q~Oi5z43?vwQr97Vr+!{!j_+7dh0~rHBc+Xr1xgF2spG7 zXsGb{i&0B#@;9Kd16*eZ27T+q=hqOrgd_{9uOt*0k{b<2IzPLH8=MXU`g9)Vgq9>- z*c1N(?W@X6>U@=>))m1Z3M<3fRLVhi^XJ!ctaza0RnB?mi)$@cFiPhE zPT@}6z9x>P+>f$B>AqxW+-GXhxRj~2Tqn>qE;fgAY>-8K<_;4Kf_lfK9@^l@%^wmq zgN5J>8{y9&GMtV4YwXG%B5MuK{!xPuZ$^F_>&&NgN_%?{ZhEGZ-eSOFz;%G@0A~Qs z02~cC8c+ZfrV}~-ew6=46UC z{A7r>$!vCpI4&o1e#YEU4^7RsWzU!~RwOgh#I)%dBFP?`JuZXIV5jogrZi@pAd;NS znHd?zGndYOu;AC@ucltUaCzyKeOK026nUlEs>%wtBm@mH49LTP<%#Vi5syqh9Jgk`lgpjqbGB6*66d|(@BjgVhi zz6EX9m3sW;sMQ#yio(zyl?-Tlq;g+baoG-`!dvUFtf}1YE0U^fw^Y}7pyk;@A*ESe z4Sjhk5-O9t+p8+8y~P5=t||3ZOOv;Filr@{nxfLl!WJ(?Ry9#nSzCr;XxLu~C711x zipsp63UE+=gmw6PyF=rl`J(2%b}*jDTDLl8`cR0~i>XB2I> zR2KWZ)snZ&SMICu)KE=0TM4Aa;lK|y}4 zqp#vQ$}x0X2`YZ7BPt@;-ijI52E;JMn9&k-97=Y!uogU_4;^@z_4_%dHdo~F`*(nz z-RBJS%N_lilkn;#Sc-X zlCoM~F`N<}R3mMvtkvuu($%QO0$LbZ^^9^j&K7t_b-AGVxJCgoEidy`Ni}tqR3XLH zvm`q2K2>`=gmP+%v!orBwUX*>V4-}rRG{**Ln^MU$cD@%Dgo5Ma*LjuEv%@lESAb@ zi=JCVJ7$-aNpM)uteGT`Wwol^A=Er9<<6b&obQ-FZ(*)rlIA*c9k9c4pQl3F?DhM7 zMbAM|9|FnJW$V`#Zrt!#;bv*gT4B@DC!SckX2a&$l7vVF;i-^bD7>txaVqM|$56-UH zu7w>n=#59UR94hAOst{geK*iKs?! zFH=%EoI+*d5e!LXR5{j|QQ0W>x&*Qr6{=mR?o#FVVCA4;l1{or)g%FWw$j<5jv#*|_G> z$DWWLS-WQah9@>YvY}w@8X8++qg9A&uvIt>RD#g3!wc>=Xu%9-1xLxkP`vhrJ!-tsrEeDl=O}~>#c8m=Y`#S4t-A2+um&d z?%`m^_udK}`Th@%{J(A+DtSl#Gz8Wua%y#5{ zMn?aK@n(Qz&d?lry|)Z8TitRbd_U)(AbuD5GCHR(S6Zh!v9ummwnMm!BDx}&AmGeR zw5S%Ite!1BvTmamAc{&WE6aq(%Dm-RV?5}(vbMqpL(PX73-lqa*P76s!Uk_qX@#!{ z;ZVRU7q+QWr`qVN7kM-R*4Njof23aB?begwP49R6zT0;cKi}=MPneh`O`1Gq>a^+E zvmTf|$B{et!H4F}Uy!$WiEBpA%s+E3e0b6O?|k^qsCQf6{e2%XnywKx`5G~gzh+^x zt`Y0vYb3Ms8ZmzTnj@|CT3*^uuUX6&@V9pAqf^)BJesq1_M@}cIv#broZf8xYs*VX zFPap?Zf+NAlSOeUkrVB$@ji8V3!v0AJtsi`Tcmek}_AvG!0oN7unrW#WDR4$cGW$+{LX5Ppf zc%J8YmS=#OQ|@hXDbBDQZ!}xPG5u2i)c(c&`Tast6EvUCV%y$7tADcN*DPyAft8W) zVho-SOo^=fdc^%)#IgQd{MCK+@ttLVv9s)$WXJZIY7%6#@Gv7Hw5{cy8 z68A(FMkYm=Nc>hq`q9;&wHA^e)jTxt)0du{e&>8g;F*WV{r>8y?|*aW)+byK*ezdu z+IO+H^Y236d2N4VeO=YF#ecDR!`fBLm$?=>AIhCGD<^x}lu20=#*ed&9W$CL#b`jH zGSb9q3A_#`aR0xZh7D{aGopbJ4UA}DL<1ul7}3Cp21Yb6qJa?&jA&p)10xz3(ZGlX zMl>*@fe{UiXkbJGBN`adz=#G$G%%up5e@wRqX8z&he`MkKR49n`(Kso^*SVZB~Pp@ zp&w-@(jVX)N6Q(`ME~ai!T@6{*WH#Q=;BsY|n4$RK&_FEWjt>nD4h}>E|JnHeX+33+vu07CL;6`f zMJePslhvA)m8C-}_dqNf)}f202FjcU!vUHI(IKiDXj>gxRV*IYq1qXhI)rdh^d&lk zoI{is9TE}an!WQI;`sHsyKIUJJF#mxm9{PwAdU-Hj55vRz(ufJkvfC zFVqqp60>=USuIFFN=+xG!FqK7OkV<`Ue>aJJa5LkHIk&mypoFNOJKh`v^l8;pn?R< zTW+*FEH%-bT%@)qbT0a1>m)~m2ymUqg~*ZojV;4X+IV&yN|Gp=%{;F|r&jOOp?UOV)%JC$A_yls6eVgLgy;~j zCK;EMEJ29|F`Zy-C-kd9ly9N)uR|kR!&EUp8bo(LjS5 z-I4_T5?H1xz%VqbGe6y~Ls`bmWBzrRkdx?;kRhsJ@TWt{cL{JE<^k3C)1WGstkv%X zROK+oxn9{Q{}UOYbqyoL^>SR-#rlXuHq`x=ATdtfF00saC9Hk z^h3!|Jt5LKs7Gti?r=mM4%lA;s@l5eK`T*3bU3#VU6FK1L}%HVGeem01f*YLu-Khw z!dWLes{o?|Fmb@xfZcM=p64L8MGGHh8LigT^H**g z@t)GlZ(KP~Ywk(5-$epp5+GziUDXJglgw}gTY)SSm-sJ40j!mg-=R+cE874z34~*k z=KyE0YoP10fijItLt`y{l=MV z-Me?=3wbxXPoCV(CH~RT@c%oanKn0g0R#w#<-SMyyTVcTHcwwyS2!HqzyH@a2P8ty zo!j^NK#bs}M3dFNU6S#BSeDaK1^w*m^6VHt5i5N3)`N>O<)6oY{|@?vuf^k(9?HA= z*DS{px$9*2jZZ!yfx#<#EcH9d_Q8FrsXKQNvo&vFtmtRNl(n$R(|3bwyl~DbDIL1W3acm+TGWOrZ2yI@??KF@)Jx2l-+c2AQlJ& zqE!xuGs|j_L@Q^9omW+b-8Q?; z>5N4K(XczZ%_Dij(I`l4bJP!Ax$^P(lW%{FbiTWLmxDK@r2cw9WEe}T9UX?gjP|)d zZFnF)7!LPUyC#~>T)T1dB*Pg^biOcgnBCbgZx3V*{0^<%$HYkg$t< zW3n+%qU-$M#pB&4yFfl^`%*Vx_ouf%yKn>eZt=GS@Qt{AfBX&L7l`@f8=v)_yZ+go z!66;q_~wIKFMP+24=+g@?nz)9YWy|qGJ$4P~iFG#sK$9vw9{!rz>aW+<_%-S8 zKJ|JkJ5B#pdH<-t#>VjX4%D3y%JezbcThTha+lUY$2BaV-iOdKt^ZwI>wg#5`rpO1 z{tUint0k@fU0myb7uWjV#kKysPTt@DF0S>zi);Pw;@VBmU3u@Gj5Lb(OH0dWi9Q;z z1g;k)+7F|N_QQChy-q^ix6r+dPEng>w_Bfg^_71-^!k&Dq~nhylHNY??En_nMrwLH zk#u}nBH@cCYKqsLej(kFGl?{@%qt6X7Xoj5Z2BIaG);c2@$}Xk@j8Xg^}NK;ddHn@ zw={egm%vZP>wfj-(xgMVq)B@G&E8D(-}OS)T#2Bbdt-4nN9kuisN#&xMRA7NF=;nT znh0~}o$AkMeOfNg1fVZ2OxvyDIqG=g!rZh!ve*B_-{W-?X68s9ZoF=DPA-$V6#S;;WTq8Nnv|3N!ivlBg$oycb9Xnsy*+);V;x&B z$8V?!_kYHH6Yy2=cz^fHSqvcScqxrDeHA}>>rO8BBEB8Y+<82-?J9*x@wm9{rL^6k zmv-k)8l@-Z=4kjr2nU=6{rzh%$NO*G=)Q0PI29W0cc*FfOwu70-_?63m$v^ul|PN& z^XUFHm*ao4XV0ng=i~9olPCMP_^-z2&YkP``{VHf&(g;|;J3iz*|u#5p$7mr-7i$ni{frS^CCyx;f922j*d}Jf)F&`Ug{nfe;LKJtsoW1G$z*&~D zx?DTzSR4mA&3R(<;+LLL^jL)AB_$I+nIb1a` z_>)g?XBxl(5A}q}!o{r1W#f^j@sr*w?;QMz+WrN08NLZe+>*!}a4^`_h4W{CAuMl} zV)z1HTDmFAZbSW?blz@5)V|O^;Iw5r{lDvxoHpCSZJ%{#k;zRLkKF!BcSqQZWx~UC zm*Y4+Ww{zKf0LUw9s#Z=ihsZEs*c9%8o*pumI?ge3dd%m*V=;v12S-2Q67{VZd|<@ z|EE7+Lir7J6)pMcPvc+xBfT~t%~|TWm%OTonm{)Nsfa_f*0Vw;hF zs&RKbIWuSV=@)lfiEZ?#DW0x)TX81hAak!hN(n$f#Ag&a`vh3gq zJHw4x=B#$A5?Lbd(cqYbV@|V$HyYvSQ%AuR5qV~KhTo0O3= z_Q?lkGsdKh897fpF7-Tgwr1W_e-(E0Jhb<0P%&hUPurHe&BIwIEX?(IJZ%H-H|^iI zzcC#9(d+vf8yiJlGP}h9uFvs7gxnA}OT5TJlMYEt7OQZKNa$aqcRkd3jJtFlb-H#wWLWuG~~jAVJ?$!U)Qh$qr#w( zsvzIM(9p{!j$<-cShJuM&g6=_=#5@D0`sh*@(&CSL0ViR6&u-k$=o9=$mFT47!F6_ z7;OWWK%kg#pbgw;?9d+c z_;9)DGNwyUv4M|IeXIsbwWO_W`%ZjzSfN)_XdimzpFg#l#yfKvMvM)7*uAEH;_X=U zefz#Zz--OR?)TSkCvHb3b#Cwzy@jb_dCS3=Ors4V{2N09kllz$772A~_%Wq>ZxV4m zZ_0$@qKysrYs8!?Mh`l|W}XO{+u(m}s@>+0$ix&Za28PtQ13(I?Xk%A(mV@qV2uVL z1^xIWAtlwo$!%@!V^#1g_qt>2j>+1-vA@4x9}}y`SFJC<+^FNknwrYZs&x~7h!K4tuZ1&RJ@ zJwBb(`$JEx0XMyw57745S*ZQPfrCGwIHsSN3A^U?gAQWKTAW+7^+J4|d8(Zt=rrEB zbL-BXuLfaKQW~y7aUya}G-u954>1spK938`_qW>)t^pax_Q9bz_`vNTNA+x(h=mt? zevD&hoU_=(1x}ktIQ(B8>kKLEFq@-Ji-m+8Fj5D}nqx{&;#o#=*y!~qGw{1;PmAOZ zV6WkXngdzZvB?ar7uE2_48(3m0<_+&hPMDdx&8Ag6BtP5-m=8ba?ssBp88P$lCjHo zI^E&FY*?HL-ci47stxzLSB8Fa5X%PYfxF#dtankY+sR-Rr0@YZZ?yp>qMX+&L>9 YoO48S ag_input.right = true, + sdlkeys.SDL_SCANCODE_LEFT => ag_input.left = true, + sdlkeys.SDL_SCANCODE_UP => ag_input.up = true, + sdlkeys.SDL_SCANCODE_DOWN => ag_input.down = true, + sdlkeys.SDL_SCANCODE_Z => ag_input.a = true, + sdlkeys.SDL_SCANCODE_X => ag_input.b = true, + sdlkeys.SDL_SCANCODE_TAB => ag_input.select = true, + sdlkeys.SDL_SCANCODE_RETURN => ag_input.start = true, + else => {}, + } + } else { + switch(scancode) { + sdlkeys.SDL_SCANCODE_RIGHT => ag_input.right = false, + sdlkeys.SDL_SCANCODE_LEFT => ag_input.left = false, + sdlkeys.SDL_SCANCODE_UP => ag_input.up = false, + sdlkeys.SDL_SCANCODE_DOWN => ag_input.down = false, + sdlkeys.SDL_SCANCODE_Z => ag_input.a = false, + sdlkeys.SDL_SCANCODE_X => ag_input.b = false, + sdlkeys.SDL_SCANCODE_TAB => ag_input.select = false, + sdlkeys.SDL_SCANCODE_RETURN => ag_input.start = false, + else => {}, + } + } + } + agnes.agnes_set_input(ag, &ag_input, 0); +} + + +fn submain() !void { + // init zepto with a memory allocator and console writer + zeptolibc.init(uvm.allocator(), consoleWriteFn); + + ag = agnes.agnes_make(); + if (agnes.agnes_load_ines_data(ag, @ptrCast(romData), romData.len)) { + try console.print("load rom ok\n", .{}); + } else { + try console.print("load rom failed\n", .{}); + } + try console.flush(); + + agnes.agnes_set_input(ag, &ag_input, 0); + + while(true) { + if (!agnes.agnes_next_frame(ag)) { + try console.print("Next frame failed!\n", .{}); + try console.flush(); + } + + var i:usize = 0; + for (0..agnes.AGNES_SCREEN_HEIGHT) |y| { + for (0..agnes.AGNES_SCREEN_WIDTH) |x| { + const c = agnes.agnes_get_screen_pixel(ag, @intCast(x), @intCast(y)); + const c_val = @as(u32, @intCast(0xFF)) << 24 | @as(u32, @intCast(c.b)) << 16 | @as(u32, @intCast(c.g)) << 8 | @as(u32, @intCast(c.r)); + gfxFramebuffer[i] = c_val; + i+=1; + } + } + + checkKeys(); + + uvm.render(@ptrCast(&gfxFramebuffer), WIDTH * HEIGHT * 4); + } +} + +export fn main() void { + _ = submain() catch { + uvm.println("Caught err"); + }; +} diff --git a/apps/agnes/src/uvm.zig b/apps/agnes/src/uvm.zig new file mode 100644 index 0000000..250f8f7 --- /dev/null +++ b/apps/agnes/src/uvm.zig @@ -0,0 +1,88 @@ +const uvm32 = @cImport({ + @cDefine("USE_MAIN", "1"); + @cInclude("uvm32_target.h"); +}); +const buildopts = @import("buildopts"); +const std = @import("std"); + +const extram:[*]u8 = @ptrFromInt(uvm32.UVM32_EXTRAM_BASE); +const extram_len = buildopts.heapsize; + +var fba:std.heap.FixedBufferAllocator = .init(extram[0..extram_len]); + +pub fn allocator() std.mem.Allocator { + return fba.allocator(); +} + +pub inline fn syscall(id: u32, param1: u32, param2: u32) u32 { + var val: u32 = undefined; + asm volatile ("ecall" + : [val] "={a2}" (val), + : [param1] "{a0}" (param1), [param2] "{a1}" (param2), + [id] "{a7}" (id), + : .{ .memory = true }); + return val; +} + +pub inline fn renderAudio(audbuf: [*]const i16, len:u32) void { + _ = syscall(uvm32.UVM32_SYSCALL_RENDERAUDIO, @intFromPtr(audbuf), len); +} + +pub inline fn render(fb: [*]const u8, len:u32) void { + _ = syscall(uvm32.UVM32_SYSCALL_RENDER, @intFromPtr(fb), len); +} + +pub inline fn getc() ?u8 { + const key = syscall(uvm32.UVM32_SYSCALL_GETC, 0, 0); + if (key == 0xFFFFFFFF) { + return null; + } else { + return @truncate(key); + } +} + +pub inline fn getkey(code: *u16, pressed:*bool) bool { + const k = syscall(uvm32.UVM32_SYSCALL_GETKEY, 0, 0); + if (k == 0xFFFFFFFF) { + return false; + } else { + code.* = @truncate(k & 0xFFFF); + pressed.* = (k & 0x80000000) != 0; + return true; + } +} + +pub inline fn millis() u32 { + return syscall(uvm32.UVM32_SYSCALL_MILLIS, 0, 0); +} + +// dupeZ would be better, but want to avoid using an allocator +// this is of course, unsafe... +var termination_buf:[512]u8 = undefined; + +pub inline fn print(m: []const u8) void { + @memcpy(termination_buf[0..m.len], m); + termination_buf[m.len] = 0; + const s = termination_buf[0..m.len :0]; + _ = syscall(uvm32.UVM32_SYSCALL_PRINT, @intFromPtr(s.ptr), 0); +} + +pub inline fn println(m: []const u8) void { + @memcpy(termination_buf[0..m.len], m); + termination_buf[m.len] = 0; + const s = termination_buf[0..m.len :0]; + _ = syscall(uvm32.UVM32_SYSCALL_PRINTLN, @intFromPtr(s.ptr), 0); +} + +pub inline fn yield() void { + _ = syscall(uvm32.UVM32_SYSCALL_YIELD, 0, 0); +} + +pub inline fn halt() void { + _ = syscall(uvm32.UVM32_SYSCALL_HALT, 0, 0); +} + +pub inline fn putc(c:u8) void { + _ = syscall(uvm32.UVM32_SYSCALL_PUTC, c, 0); +} + diff --git a/hosts/host-sdl/host-sdl.c b/hosts/host-sdl/host-sdl.c index 0b1f08d..f640521 100644 --- a/hosts/host-sdl/host-sdl.c +++ b/hosts/host-sdl/host-sdl.c @@ -16,8 +16,8 @@ #include "../common/uvm32_common_custom.h" -#define WIDTH 320 -#define HEIGHT 200 +int WIDTH = 320; +int HEIGHT = 200; #define WINDOW_WIDTH WIDTH*3 #define WINDOW_HEIGHT HEIGHT*3 @@ -177,7 +177,7 @@ int main(int argc, char *argv[]) { } // parse commandline args - while ((c = getopt(argc, argv, "hi:e:")) != -1) { + while ((c = getopt(argc, argv, "hi:e:W:H:")) != -1) { switch(c) { case 'h': usage(argv[0]); @@ -191,6 +191,12 @@ int main(int argc, char *argv[]) { case 'e': extram_len = strtoll(optarg, NULL, 10); break; + case 'W': + WIDTH = strtoll(optarg, NULL, 10); + break; + case 'H': + HEIGHT = strtoll(optarg, NULL, 10); + break; } } if (optind < argc) { diff --git a/precompiled/agnes.bin b/precompiled/agnes.bin new file mode 100755 index 0000000000000000000000000000000000000000..8973d079c008aef2727b98b3ecf67c6d15b1fec0 GIT binary patch literal 88648 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Μ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 b^3q(Kg9Zf?$b%sheuDnB$(l?}^uPTJ554@} literal 0 HcmV?d00001