From cbbc922cf7e28d6cb708e48ae0df9ea6d57a2474 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Tue, 13 Jun 2017 18:02:52 -0400 Subject: [PATCH] pizza boy at c7bc6ee376028b3766de8d7a02e60ab794841f45 --- waterbox/emulibc/libemuhost.so | Bin 0 -> 12026 bytes waterbox/pizza/Makefile | 18 + waterbox/pizza/README.md | 49 + waterbox/pizza/lib/Makefile | 9 + waterbox/pizza/lib/cartridge.c | 200 ++ waterbox/pizza/lib/cartridge.h | 29 + waterbox/pizza/lib/cycles.c | 409 ++++ waterbox/pizza/lib/cycles.h | 77 + waterbox/pizza/lib/gameboy.c | 415 +++++ waterbox/pizza/lib/gameboy.h | 31 + waterbox/pizza/lib/global.c | 56 + waterbox/pizza/lib/global.h | 51 + waterbox/pizza/lib/gpu.c | 1179 ++++++++++++ waterbox/pizza/lib/gpu.h | 144 ++ waterbox/pizza/lib/input.c | 102 + waterbox/pizza/lib/input.h | 35 + waterbox/pizza/lib/interrupt.h | 35 + waterbox/pizza/lib/mmu.c | 1323 +++++++++++++ waterbox/pizza/lib/mmu.h | 152 ++ waterbox/pizza/lib/network.c | 409 ++++ waterbox/pizza/lib/network.h | 35 + waterbox/pizza/lib/serial.c | 238 +++ waterbox/pizza/lib/serial.h | 98 + waterbox/pizza/lib/sound.c | 1484 +++++++++++++++ waterbox/pizza/lib/sound.h | 334 ++++ waterbox/pizza/lib/testone.h | 8 + waterbox/pizza/lib/timer.c | 79 + waterbox/pizza/lib/timer.h | 65 + waterbox/pizza/lib/utils.c | 139 ++ waterbox/pizza/lib/utils.h | 42 + waterbox/pizza/lib/z80_gameboy.h | 2480 +++++++++++++++++++++++++ waterbox/pizza/lib/z80_gameboy_regs.h | 48 + waterbox/pizza/pizza.c | 419 +++++ 33 files changed, 10192 insertions(+) create mode 100644 waterbox/emulibc/libemuhost.so create mode 100644 waterbox/pizza/Makefile create mode 100644 waterbox/pizza/README.md create mode 100644 waterbox/pizza/lib/Makefile create mode 100644 waterbox/pizza/lib/cartridge.c create mode 100644 waterbox/pizza/lib/cartridge.h create mode 100644 waterbox/pizza/lib/cycles.c create mode 100644 waterbox/pizza/lib/cycles.h create mode 100644 waterbox/pizza/lib/gameboy.c create mode 100644 waterbox/pizza/lib/gameboy.h create mode 100644 waterbox/pizza/lib/global.c create mode 100644 waterbox/pizza/lib/global.h create mode 100644 waterbox/pizza/lib/gpu.c create mode 100644 waterbox/pizza/lib/gpu.h create mode 100644 waterbox/pizza/lib/input.c create mode 100644 waterbox/pizza/lib/input.h create mode 100644 waterbox/pizza/lib/interrupt.h create mode 100644 waterbox/pizza/lib/mmu.c create mode 100644 waterbox/pizza/lib/mmu.h create mode 100644 waterbox/pizza/lib/network.c create mode 100644 waterbox/pizza/lib/network.h create mode 100644 waterbox/pizza/lib/serial.c create mode 100644 waterbox/pizza/lib/serial.h create mode 100644 waterbox/pizza/lib/sound.c create mode 100644 waterbox/pizza/lib/sound.h create mode 100644 waterbox/pizza/lib/testone.h create mode 100644 waterbox/pizza/lib/timer.c create mode 100644 waterbox/pizza/lib/timer.h create mode 100644 waterbox/pizza/lib/utils.c create mode 100644 waterbox/pizza/lib/utils.h create mode 100644 waterbox/pizza/lib/z80_gameboy.h create mode 100644 waterbox/pizza/lib/z80_gameboy_regs.h create mode 100644 waterbox/pizza/pizza.c diff --git a/waterbox/emulibc/libemuhost.so b/waterbox/emulibc/libemuhost.so new file mode 100644 index 0000000000000000000000000000000000000000..ba581bfe47eb2d29a16132426583906785fd0c0c GIT binary patch literal 12026 zcmeHNUuYc18K2d~i5&ksC#`YoQg33r_@UZ6$&w;dQcki|N8nhBESI!|uJ-O$(#ChU z$L-2h+)@NhQ_{Yuv=mYZ+(RJ`eF?OIkQa-(guVp)5GWK{N}7jK3N-)rVyXdxXc!W$fx<&g*ct&?^4-`{nZ${)~JsLam;3L#6Y%~U2v*FIj9G# z3mB#x8s#2jZ9)Z+-*6gbFZT4XIRMJ_Co(J*#B9M?3fZj~Q-G3tm`p;@SI7-3ewo=U z67}sTlTbkv3Qk@1%Pz$N66FperHJ~R)s%g8R8lUR5<*eR6{Co#tXgBKo7TkahT|)J zat_xaLw$^$6iI!7l9O}XL?z`UlL$f%{_`s#B2CWq>sszeBEu5oCUS$yF$)AXkf`q{ zQi|Z`iQLdxiTsN5b`yzmpO#STQV`jR-0+~}Wxszwk#e7rQ0pS)Mn)w61ohoQqTFXC z)Vffv#cWgf_CKvd-{A)E^35AJ*4NhuH*PF~oI}T7lr3(@vrU?$k`%D1$#mbz8@!j_ z9}oCvM*Q;76GLbHk&)ri(%{(8$Y6eG$QwE9kCukV^0bN}W@sU+_TReB4Q^x@h9rg* z$!uYJ6x-UW9@4zolJ;ueY(4ulZ?>2Nnm1dCzGGlxQ1A)s|{iy?c7TEj1%)Se@N4wAiSxoAqoY*@4P9Q&l^Z?3vAKD{&;e3jR>LLG5Y~1;vW5`Fh0*YPuZxUd1m{ zSC4ug=y~9tc)(WAdimy9bEEm|rYPv+t7d;Q+dSH|o2Q%k<~^~b*U5eGz~|AYI zhjTa`_r1u^#npPQx)N0e1|9r{21<=EiUwB4M%~fj0ZI;-W{#5<%#|9k(u9KVMB;As zBhFvEY>!{Ehn%5dXJk-VYifl2pWAP}imQp(xqCRm+W&QgA0@)9!y^0OVe1hq(|@f0 zRR5zqr`-zsD1G$W?p_bP0@M1$w{W$re;qu4tEK-AT6b$n=C)b-Lg=+GH=ezH<|zM@ zPcG$7^jR?F$M@MdE?aG!*)@)wl$Pz@3B5zhoW%hD#nj#WKY0JYWG#AE2&BlonM}>0 zvGTZ5Iw9k+Q-GAmw3}TlucE&c)SMEfzmLS3p;s~%ONq@!Bkzx-?PSc@vyfryEoZrF z?A`NVzD{6YJY81ykW0vn>x+QYxNxw5KK3RbOtt)_{@;8mT{%^37Hw31k@Foeyz-jG~=3u4At!v zQ9F)H?_=|=zPk4@REa0i2|v%+&d-$LPVyeo?RFCGAGg~{xT4ff@)MM_!%!l1k6p!` zs#VtyGpCf~E|EvtZkM3UbLc*sw@duaOZD`PltBvl9*{a@S)q_u*a{pyrjVa?wR%e; zY0YozR$Sex)o*pHr1!rOf$9X=Cq?ZPata9JE0Gyj>STng;!GFv91wGNP?awt<0uk) zUSmb8ch@afDy|DC-Pd*1IMw^R5=oErk8OI`{sSP44Svg@6zoB_Hn_xx$AFkPR5D)x z(mp$1p|P5j%p#BlAatBUVzvUu^{$XL-71m!0gycEliq&{WC2Jb^K;!Q@$=gnq2+t>kZ0PE^Hht` zCz=<4uziBmq)dA3-_;1)s=j`t5j_~-uW2OFbG-|>31k+siG{xd!nsKF{1r&MJ^L8U zphWWtAe=pRpzIk1lIVFBoZ~OMi}b$jQl$)P{7v3rI<9{emDRpxzjy-z+F2Qm-0P?vnP^{oo9aB=8w(98`T*e%j~7VFQkF z=PqBIpN8eLm!>XH&bV_^7iP}67p5=GV-y%FKB9D^*lWa+btmU%=91c=T8BvTiUaE6 zN|chHu6j%U1P)4|3M%T6>{?4uJ@%Z^GA6SW`;8!qgHj~>iGpjs8!l2)>55wltJOdm z&|cB-BfoLgmrbhP^I^k{R*KOJQS4XUt9~O2!kVnZ6IseA#)6r?ulwE=r!zPl*M*2* ziyJSv^)RT#JNs$s;$)lA%f44n+guH5uq=$)ObX~3w+Pp$wtgw5wO%Dys#VdoM5o8+ z_2%HZ^mS+0DAe6ydOyVMs@=zR&$Uf0U1n;!oRQd~BODXo-n?zHF`1&%$8ag1SULur zDArMqMayaJa31}V1Z7)H?%)d(J92)~p_2p+LtX}T_lnlBZG6(T;$3a3n%H#oRYI@q uH{8XFx1^#QA%7)`uej9+-K9MR)tBu1rmsk-hyl+mfk-f3a0fc-B7E?15K literal 0 HcmV?d00001 diff --git a/waterbox/pizza/Makefile b/waterbox/pizza/Makefile new file mode 100644 index 0000000000..6e56488b97 --- /dev/null +++ b/waterbox/pizza/Makefile @@ -0,0 +1,18 @@ +CFLAGS=-O3 +ifeq ($(OS),Windows_NT) + LIBS=-lrt `sdl2-config --libs` + CFLAGS+=-w +else + LIBS=-lrt -lSDL2 -pthread +endif + +all: libpizza.a + gcc $(CFLAGS) pizza.c -I lib lib/libpizza.a -o emu-pizza $(LIBS) + +libpizza.a: + make -C lib + +clean: + rm -f *.o + make -C cpu clean + make -C system clean diff --git a/waterbox/pizza/README.md b/waterbox/pizza/README.md new file mode 100644 index 0000000000..edc069a750 --- /dev/null +++ b/waterbox/pizza/README.md @@ -0,0 +1,49 @@ +# Emu-pizza +A new born Gameboy Classic/Color emulator.... + +Requirements +----------- +Emu-pizza requires libSDL2 to compile and run Space Invaders and Gameboy games. To install it + +on an APT based distro: +``` +sudo apt-get install libsdl2-dev +``` + +on a YUM based distro: +``` +sudo yum install SDL2-devel +``` + +Compile +------- +``` +make +``` + +Usage +----- +``` +emu-pizza [gameboy rom] +``` + +Gameboy keys +------------------- +* Arrows -- Arrows (rly?) +* Enter -- Start +* Space -- Select +* Z/X -- A/B buttons +* Q -- Exit + +Supported ROMS +-------------- +* Almost totality of Gameboy roms + +Todo +---- +* Serial cable emulation + +Credits +------- + +Thanks to [Emulator 101](http://www.emulator101.com), the source of all my current knowledge on 8080 emulation diff --git a/waterbox/pizza/lib/Makefile b/waterbox/pizza/lib/Makefile new file mode 100644 index 0000000000..2ad536bfff --- /dev/null +++ b/waterbox/pizza/lib/Makefile @@ -0,0 +1,9 @@ +SRCS=$(wildcard *.c) +CFLAGS=-I.. -c -pthread -O3 -Wall + +all: + gcc $(CFLAGS) $(SRCS) + ar rcs libpizza.a *.o + +clean: + rm -f *.o diff --git a/waterbox/pizza/lib/cartridge.c b/waterbox/pizza/lib/cartridge.c new file mode 100644 index 0000000000..44ecf42584 --- /dev/null +++ b/waterbox/pizza/lib/cartridge.c @@ -0,0 +1,200 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include +#include +#include +#include +#include +#include + +#include "global.h" +#include "mmu.h" +#include "utils.h" + +/* buffer big enough to contain the largest possible ROM */ +uint8_t rom[2 << 24]; + +/* battery backed RAM & RTC*/ +char file_sav[1024]; +char file_rtc[1024]; + +/* internal use prototype */ +int __mkdirp (char *path, mode_t omode); + + +/* guess what */ +/* return values */ +/* 0: OK */ +/* 1: Can't open/read file */ +/* 2: Unknown cartridge */ + +char cartridge_load(char *file_gb) +{ + FILE *fp; + int i,z = 0; + + /* open ROM file */ + if ((fp = fopen(file_gb, "r")) == NULL) + return 1; + + /* read all the content into rom buffer */ + size_t sz = fread(rom, 1, (2 << 24), fp); + + /* check for errors */ + if (sz < 1) + return 1; + + /* close */ + fclose(fp); + + /* gameboy color? */ + if (rom[0x143] == 0xC0 || rom[0x143] == 0x80) + { + utils_log("Gameboy Color cartridge\n"); + global_cgb = 1; + } + else + { + utils_log("Gameboy Classic cartridge\n"); + global_cgb = 0; + } + + /* get cartridge infos */ + uint8_t mbc = rom[0x147]; + + utils_log("Cartridge code: %02x\n", mbc); + + switch (mbc) + { + case 0x00: utils_log("ROM ONLY\n"); break; + case 0x01: utils_log("MBC1\n"); break; + case 0x02: utils_log("MBC1 + RAM\n"); break; + case 0x03: utils_log("MBC1 + RAM + BATTERY\n"); break; + case 0x05: utils_log("MBC2\n"); break; + case 0x06: mmu_init_ram(512); utils_log("MBC2 + BATTERY\n"); break; + case 0x10: utils_log("MBC3 + TIMER + RAM + BATTERY\n"); break; + case 0x11: utils_log("MBC3\n"); break; + case 0x12: utils_log("MBC3 + RAM\n"); break; + case 0x13: utils_log("MBC3 + RAM + BATTERY\n"); break; + case 0x19: utils_log("MBC5\n"); break; + case 0x1A: utils_log("MBC5 + RAM\n"); break; + case 0x1B: utils_log("MBC5 + RAM + BATTERY\n"); break; + case 0x1C: global_rumble = 1; + utils_log("MBC5 + RUMBLE\n"); + break; + case 0x1D: global_rumble = 1; + utils_log("MBC5 + RUMBLE + RAM\n"); + break; + case 0x1E: global_rumble = 1; + utils_log("MBC5 + RUMBLE + RAM + BATTERY\n"); + break; + + default: utils_log("Unknown cartridge type: %02x\n", mbc); + return 2; + } + + /* title */ + for (i=0x134; i<0x143; i++) + if (rom[i] > 0x40 && rom[i] < 0x5B) + global_cart_name[z++] = rom[i]; + + global_cart_name[z] = '\0'; + + utils_log("%s\n", global_cart_name); + + /* get ROM banks */ + uint8_t byte = rom[0x148]; + + utils_log("ROM: "); + + switch (byte) + { + case 0x00: utils_log("0 banks\n"); break; + case 0x01: utils_log("4 banks\n"); break; + case 0x02: utils_log("8 banks\n"); break; + case 0x03: utils_log("16 banks\n"); break; + case 0x04: utils_log("32 banks\n"); break; + case 0x05: utils_log("64 banks\n"); break; + case 0x06: utils_log("128 banks\n"); break; + case 0x07: utils_log("256 banks\n"); break; + case 0x52: utils_log("72 banks\n"); break; + case 0x53: utils_log("80 banks\n"); break; + case 0x54: utils_log("96 banks\n"); break; + } + + /* init MMU */ + mmu_init(mbc, byte); + + /* get RAM banks */ + byte = rom[0x149]; + + utils_log("RAM: "); + + switch (byte) + { + case 0x00: utils_log("NO RAM\n"); break; + case 0x01: mmu_init_ram(1 << 11); utils_log("2 kB\n"); break; + case 0x02: + /* MBC5 got bigger values */ + if (mbc >= 0x19 && mbc <= 0x1E) + { + mmu_init_ram(1 << 16); + utils_log("64 kB\n"); + } + else + { + mmu_init_ram(1 << 13); + utils_log("8 kB\n"); + } + break; + case 0x03: mmu_init_ram(1 << 15); utils_log("32 kB\n"); break; + case 0x04: mmu_init_ram(1 << 17); utils_log("128 kB\n"); break; + case 0x05: mmu_init_ram(1 << 16); utils_log("64 kB\n"); break; + } + + /* save base name of the rom */ + strncpy(global_rom_name, basename(file_gb), 256); + + /* build file.sav */ + snprintf(file_sav, sizeof(file_sav), "%s/%s.sav", + global_save_folder, global_rom_name); + + /* build file.rtc */ + snprintf(file_rtc, sizeof(file_rtc), "%s/%s.rtc", + global_save_folder, global_rom_name); + + /* restore saved RAM if it's the case */ + mmu_restore_ram(file_sav); + + /* restore saved RTC if it's the case */ + mmu_restore_rtc(file_rtc); + + /* load FULL ROM at 0x0000 address of system memory */ + mmu_load_cartridge(rom, sz); + + return 0; +} + +void cartridge_term() +{ + /* save persistent data (battery backed RAM and RTC clock) */ + mmu_save_ram(file_sav); + mmu_save_rtc(file_rtc); +} diff --git a/waterbox/pizza/lib/cartridge.h b/waterbox/pizza/lib/cartridge.h new file mode 100644 index 0000000000..e8781c5f8c --- /dev/null +++ b/waterbox/pizza/lib/cartridge.h @@ -0,0 +1,29 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __CARTRIDGE_HDR__ +#define __CARTRIDGE_HDR__ + +#include + +/* prototypes */ +char cartridge_load(char *file_nm); +void cartridge_term(); + +#endif diff --git a/waterbox/pizza/lib/cycles.c b/waterbox/pizza/lib/cycles.c new file mode 100644 index 0000000000..559d4e4664 --- /dev/null +++ b/waterbox/pizza/lib/cycles.c @@ -0,0 +1,409 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include +#include +#include +#include + +#include "cycles.h" +#include "global.h" +#include "gpu.h" +#include "mmu.h" +#include "serial.h" +#include "sound.h" +#include "timer.h" +#include "interrupt.h" +#include "utils.h" + +/* timer stuff */ +struct itimerspec cycles_timer; +timer_t cycles_timer_id = 0; +struct sigevent cycles_te; +struct sigaction cycles_sa; + +interrupts_flags_t *cycles_if; + +/* instance of the main struct */ +cycles_t cycles = { 0, 0, 0, 0 }; + +#define CYCLES_PAUSES 256 + +/* sync timing */ +struct timespec deadline; + +/* hard sync stuff (for remote connection) */ +uint8_t cycles_hs_mode = 0; + +/* type of next */ +typedef enum +{ + CYCLES_NEXT_TYPE_CYCLES, + CYCLES_NEXT_TYPE_CYCLES_HS, + CYCLES_NEXT_TYPE_DMA, +} cycles_next_type_enum_e; + +/* closest next and its type */ +uint_fast32_t cycles_very_next; +cycles_next_type_enum_e cycles_next_type; + +/* set hard sync mode. sync is given by the remote peer + local timer */ +void cycles_start_hs() +{ + utils_log("Hard sync mode ON\n"); + + /* boolean set to on */ + cycles_hs_mode = 1; +} + +void cycles_stop_hs() +{ + utils_log("Hard sync mode OFF\n"); + + /* boolean set to on */ + cycles_hs_mode = 0; +} + +/* set double or normal speed */ +void cycles_set_speed(char dbl) +{ + /* set global */ + global_cpu_double_speed = dbl; + + /* update clock */ + if (global_cpu_double_speed) + cycles.clock = 4194304 * 2; + else + cycles.clock = 4194304; + + /* calculate the mask */ + cycles_change_emulation_speed(); +} + +/* set emulation speed */ +void cycles_change_emulation_speed() +{ + switch (global_emulation_speed) + { + case GLOBAL_EMULATION_SPEED_QUARTER: + cycles.step = ((4194304 / CYCLES_PAUSES) / 4 + << global_cpu_double_speed); + break; + case GLOBAL_EMULATION_SPEED_HALF: + cycles.step = ((4194304 / CYCLES_PAUSES) / 2 + << global_cpu_double_speed); + break; + case GLOBAL_EMULATION_SPEED_NORMAL: + cycles.step = ((4194304 / CYCLES_PAUSES) + << global_cpu_double_speed); + break; + case GLOBAL_EMULATION_SPEED_DOUBLE: + cycles.step = ((4194304 / CYCLES_PAUSES) * 2 + << global_cpu_double_speed); + break; + case GLOBAL_EMULATION_SPEED_4X: + cycles.step = ((4194304 / CYCLES_PAUSES) * 4 + << global_cpu_double_speed); + break; + } +} + +void cycles_closest_next() +{ + int_fast32_t diff = cycles.cnt - cycles.next; + + /* init */ + cycles_very_next = cycles.next; + cycles_next_type = CYCLES_NEXT_TYPE_CYCLES; + + int_fast32_t diff_new = cycles.cnt - mmu.dma_next; + + /* DMA? */ + if (diff_new < diff) + { + /* this is the new lowest */ + cycles_very_next = mmu.dma_next; + cycles_next_type = CYCLES_NEXT_TYPE_DMA; + } +} + +/* this function is gonna be called every M-cycle = 4 ticks of CPU */ +void cycles_step() +{ + cycles.cnt += 4; + +/* + while (cycles.cnt >= cycles_very_next) + { + switch (cycles_next_type) + { + case CYCLES_NEXT_TYPE_CYCLES: + + deadline.tv_nsec += 1000000000 / CYCLES_PAUSES; + + if (deadline.tv_nsec > 1000000000) + { + deadline.tv_sec += 1; + deadline.tv_nsec -= 1000000000; + } + + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, + &deadline, NULL); + + cycles.next += cycles.step; + + if (cycles.cnt % cycles.clock == 0) + cycles.seconds++; + + break; + + case CYCLES_NEXT_TYPE_DMA: + + memcpy(&mmu.memory[0xFE00], &mmu.memory[mmu.dma_address], 160); + + mmu.dma_address = 0x0000; + + mmu.dma_next = 1; + + break; + } + + cycles_closest_next(); + } +*/ + + /* 65536 == cpu clock / CYCLES_PAUSES pauses every second */ + if (cycles.cnt == cycles.next) + { + deadline.tv_nsec += 1000000000 / CYCLES_PAUSES; + + if (deadline.tv_nsec > 1000000000) + { + deadline.tv_sec += 1; + deadline.tv_nsec -= 1000000000; + } + + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, NULL); + + cycles.next += cycles.step; + + /* update current running seconds */ + if (cycles.cnt % cycles.clock == 0) + cycles.seconds++; + } + + /* hard sync next step */ + if (cycles.cnt == cycles.hs_next) + { + /* set cycles for hard sync */ + cycles.hs_next += ((4096 * 4) << global_cpu_double_speed); + + /* hard sync is on? */ + if (cycles_hs_mode) + { + /* send my status and wait for peer status back */ + serial_send_byte(); + + /* wait for reply */ + serial_wait_data(); + + /* verify if we need to trigger an interrupt */ + serial_verify_intr(); + } + } + + /* DMA */ + if (mmu.dma_next == cycles.cnt) + { + memcpy(&mmu.memory[0xFE00], &mmu.memory[mmu.dma_address], 160); + + /* reset address */ + mmu.dma_address = 0x0000; + + /* reset */ + mmu.dma_next = 1; + } + + /* update GPU state */ + if (gpu.next == cycles.cnt) + gpu_step(); + + /* fs clock */ + if (sound.fs_cycles_next == cycles.cnt) + sound_step_fs(); + + /* channel one */ + if (sound.channel_one.duty_cycles_next == cycles.cnt) + sound_step_ch1(); + + /* channel two */ + if (sound.channel_two.duty_cycles_next == cycles.cnt) + sound_step_ch2(); + + /* channel three */ + if (sound.channel_three.cycles_next <= cycles.cnt) + sound_step_ch3(); + + /* channel four */ + if (sound.channel_four.cycles_next == cycles.cnt) + sound_step_ch4(); + + /* time to generate a sample? */ + if (sound.sample_cycles_next_rounded == cycles.cnt) + sound_step_sample(); + + /* update timer state */ + if (cycles.cnt == timer.next) + { + timer.next += 256; + timer.div++; + } + + /* timer is on? */ + if (timer.sub_next == cycles.cnt) + { + timer.sub_next += timer.threshold; + timer.cnt++; + + /* cnt value > 255? trigger an interrupt */ + if (timer.cnt > 255) + { + timer.cnt = timer.mod; + + /* trigger timer interrupt */ + cycles_if->timer = 1; + } + } + + /* update serial state */ + if (serial.next == cycles.cnt) + { + /* nullize serial next */ + serial.next -= 1; + + /* reset counter */ + serial.bits_sent = 0; + + /* gotta reply with 0xff when asking for ff01 */ + serial.data = 0xFF; + + /* reset transfer_start flag to yell I'M DONE */ + serial.transfer_start = 0; + + /* if not connected, trig the fucking interrupt */ + cycles_if->serial_io = 1; + } +} + +/* things to do when vsync kicks in */ +void cycles_vblank() +{ + return; + +} + +/* stuff tied to entering into hblank state */ +void cycles_hdma() +{ + /* HDMA (only CGB) */ + if (mmu.hdma_to_transfer) + { + /* hblank transfer */ + if (mmu.hdma_transfer_mode) + { + /* transfer when line is changed and we're into HBLANK phase */ + if (mmu.memory[0xFF44] < 143 && + mmu.hdma_current_line != mmu.memory[0xFF44] && + (mmu.memory[0xFF41] & 0x03) == 0x00) + { + /* update current line */ + mmu.hdma_current_line = mmu.memory[0xFF44]; + + /* copy 0x10 bytes */ + if (mmu.vram_idx) + memcpy(mmu_addr_vram1() + mmu.hdma_dst_address - 0x8000, + &mmu.memory[mmu.hdma_src_address], 0x10); + else + memcpy(mmu_addr_vram0() + mmu.hdma_dst_address - 0x8000, + &mmu.memory[mmu.hdma_src_address], 0x10); + + /* decrease bytes to transfer */ + mmu.hdma_to_transfer -= 0x10; + + /* increase pointers */ + mmu.hdma_dst_address += 0x10; + mmu.hdma_src_address += 0x10; + } + } + } +} + +char cycles_init() +{ + /* CLOCK */ + clock_gettime(CLOCK_MONOTONIC, &deadline); + + cycles.inited = 1; + + /* interrupt registers */ + cycles_if = mmu_addr(0xFF0F); + + /* init clock and counter */ + cycles.clock = 4194304; + cycles.cnt = 0; + cycles.hs_next = 70224; + + /* mask for pauses cycles fast calc */ + cycles.step = 4194304 / CYCLES_PAUSES; + cycles.next = 4194304 / CYCLES_PAUSES; + + return 0; +} + +char cycles_start_timer() +{ + /* just pick new time reference */ + clock_gettime(CLOCK_MONOTONIC, &deadline); + + return 0; +} + +void cycles_stop_timer() +{ + if (cycles.inited == 0) + return; +} + +void cycles_term() +{ +} + +void cycles_save_stat(FILE *fp) +{ + fwrite(&cycles, 1, sizeof(cycles_t), fp); +} + +void cycles_restore_stat(FILE *fp) +{ + fread(&cycles, 1, sizeof(cycles_t), fp); + + /* recalc speed stuff */ + cycles_change_emulation_speed(); +} + diff --git a/waterbox/pizza/lib/cycles.h b/waterbox/pizza/lib/cycles.h new file mode 100644 index 0000000000..2369166b6f --- /dev/null +++ b/waterbox/pizza/lib/cycles.h @@ -0,0 +1,77 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __CYCLES_HDR__ +#define __CYCLES_HDR__ + +#include +#include + +typedef struct cycles_s +{ + /* am i init'ed? */ + uint_fast32_t inited; + + /* ticks counter */ + uint_fast32_t cnt; + + /* CPU clock */ + uint_fast32_t clock; + + /* handy for calculation */ + uint_fast32_t next; + + /* step varying on cpu and emulation speed */ + uint_fast32_t step; + + /* total running seconds */ + uint_fast32_t seconds; + + /* 2 spares */ + uint_fast32_t hs_next; + + + uint_fast32_t spare2; + +} cycles_t; + +extern cycles_t cycles; + +// extern uint8_t cycles_hs_local_cnt; +// extern uint8_t cycles_hs_peer_cnt; + +/* callback function */ +typedef void (*cycles_send_cb_t) (uint32_t v); + +/* prototypes */ +void cycles_change_emulation_speed(); +void cycles_hdma(); +char cycles_init(); +void cycles_restore_stat(FILE *fp); +void cycles_save_stat(FILE *fp); +void cycles_set_speed(char dbl); +void cycles_start_hs(); +char cycles_start_timer(); +void cycles_step(); +void cycles_stop_hs(); +void cycles_stop_timer(); +void cycles_term(); +void cycles_vblank(); + +#endif diff --git a/waterbox/pizza/lib/gameboy.c b/waterbox/pizza/lib/gameboy.c new file mode 100644 index 0000000000..f628a7ad83 --- /dev/null +++ b/waterbox/pizza/lib/gameboy.c @@ -0,0 +1,415 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include +#include +#include +#include +#include +#include +#include "cartridge.h" +#include "sound.h" +#include "mmu.h" +#include "cycles.h" +#include "gpu.h" +#include "global.h" +#include "input.h" +#include "timer.h" +#include "serial.h" +#include "utils.h" +#include "z80_gameboy_regs.h" +#include "z80_gameboy.h" + +/* semaphore for pauses */ +sem_t gameboy_sem; + +char gameboy_inited = 0; + + +void gameboy_init() +{ + /* init global values */ + // global_init(); + + /* init z80 */ + z80_init(); + + /* init cycles syncronizer */ + cycles_init(); + + /* init input */ + input_init(); + + /* init timer */ + timer_init(); + + /* init serial */ + serial_init(); + + /* init sound (this will start audio thread) */ + sound_init(); + + /* reset GPU counters */ + gpu_reset(); + + /* reset to default values */ + mmu_write_no_cyc(0xFF05, 0x00); + mmu_write_no_cyc(0xFF06, 0x00); + mmu_write_no_cyc(0xFF07, 0x00); + mmu_write_no_cyc(0xFF10, 0x80); + mmu_write_no_cyc(0xFF11, 0xBF); + mmu_write_no_cyc(0xFF12, 0xF3); + mmu_write_no_cyc(0xFF14, 0xBF); + mmu_write_no_cyc(0xFF16, 0x3F); + mmu_write_no_cyc(0xFF17, 0x00); + mmu_write_no_cyc(0xFF19, 0xBF); + mmu_write_no_cyc(0xFF1A, 0x7F); + mmu_write_no_cyc(0xFF1B, 0xFF); + mmu_write_no_cyc(0xFF1C, 0x9F); + mmu_write_no_cyc(0xFF1E, 0xBF); + mmu_write_no_cyc(0xFF20, 0xFF); + mmu_write_no_cyc(0xFF21, 0x00); + mmu_write_no_cyc(0xFF22, 0x00); + mmu_write_no_cyc(0xFF23, 0xBF); + mmu_write_no_cyc(0xFF24, 0x77); + mmu_write_no_cyc(0xFF25, 0xF3); + mmu_write_no_cyc(0xFF26, 0xF1); + mmu_write_no_cyc(0xFF40, 0x91); + mmu_write_no_cyc(0xFF41, 0x80); + mmu_write_no_cyc(0xFF42, 0x00); + mmu_write_no_cyc(0xFF43, 0x00); + mmu_write_no_cyc(0xFF44, 0x00); + mmu_write_no_cyc(0xFF45, 0x00); + mmu_write_no_cyc(0xFF47, 0xFC); + mmu_write_no_cyc(0xFF48, 0xFF); + mmu_write_no_cyc(0xFF49, 0xFF); + mmu_write_no_cyc(0xFF4A, 0x00); + mmu_write_no_cyc(0xFF4B, 0x00); + mmu_write_no_cyc(0xFF98, 0xDC); + mmu_write_no_cyc(0xFFFF, 0x00); + mmu_write_no_cyc(0xC000, 0x08); + mmu_write_no_cyc(0xFFFE, 0x69); + + if (global_cgb) + state.a = 0x11; + else + state.a = 0x00; + + state.b = 0x00; + state.c = 0x13; + state.d = 0x00; + state.e = 0xd8; + state.h = 0x01; + state.l = 0x4d; + state.pc = 0x0100; + state.sp = 0xFFFE; + *state.f = 0xB0; + + /* init semaphore for pauses */ + sem_init(&gameboy_sem, 0, 0); + + /* mark as inited */ + gameboy_inited = 1; + + return; +} + +void gameboy_set_pause(char pause) +{ + if (!gameboy_inited) + return; + + if (pause == global_pause) + return; + + global_pause = pause; + + if (pause) + { + /* wait a bit - i need the main cycle fall into global_pause check */ + usleep(100000); + + /* stop timer */ + cycles_stop_timer(); + } + else + { + /* restart timer */ + cycles_start_timer(); + + /* wake up */ + sem_post(&gameboy_sem); + } +} + +void gameboy_run() +{ + uint8_t op; + + /* reset counter */ + cycles.cnt = 0; + + /* get interrupt flags and interrupt enables */ + uint8_t *int_e; + uint8_t *int_f; + + /* pointers to memory location of interrupt enables/flags */ + int_e = mmu_addr(0xFFFF); + int_f = mmu_addr(0xFF0F); + + /* start at normal speed */ + global_cpu_double_speed = 0; + + /* run stuff! */ + /* mechanism is simple. */ + /* 1) execute instruction 2) update cycles counter 3) check interrupts */ + /* and repeat forever */ + while (!global_quit) + { + /*if (global_slow_down) + { + usleep(100000); + global_slow_down = 0; + }*/ + + /* pause? */ + while (global_pause) + sem_wait(&gameboy_sem); + + /* get op */ + op = mmu_read(state.pc); + + /* print out CPU state if enabled by debug flag */ + if (global_debug) + { + utils_log("OP: %02x F: %02x PC: %04x:%02x:%02x SP: %04x:%02x:%02x ", + op, *state.f & 0xd0, state.pc, + mmu_read_no_cyc(state.pc + 1), + mmu_read_no_cyc(state.pc + 2), state.sp, + mmu_read_no_cyc(state.sp), + mmu_read_no_cyc(state.sp + 1)); + + + utils_log("A: %02x BC: %04x DE: %04x HL: %04x FF41: %02x " + "FF44: %02x ENAB: %02x INTE: %02x INTF: %02x\n", + state.a, *state.bc, + *state.de, *state.hl, + mmu_read_no_cyc(0xFF41), + mmu_read_no_cyc(0xFF44), + state.int_enable, + *int_e, *int_f); + } + + /* execute instruction by the GB Z80 version */ + z80_execute(op); + + /* if last op was Interrupt Enable (0xFB) */ + /* we need to check for INTR on next cycle */ + if (op == 0xFB) + continue; + + /* interrupts filtered by enable flags */ + uint8_t int_r = (*int_f & *int_e); + + /* check for interrupts */ + if ((state.int_enable || op == 0x76) && (int_r != 0)) + { + /* discard useless bits */ + if ((int_r & 0x1F) == 0x00) + continue; + + /* beware of instruction that doesn't move PC! */ + /* like HALT (0x76) */ + if (op == 0x76) + { + state.pc++; + + if (state.int_enable == 0) + continue; + } + + /* reset int-enable flag, it will be restored after a RETI op */ + state.int_enable = 0; + + if ((int_r & 0x01) == 0x01) + { + /* vblank interrupt triggers RST 5 */ + + /* reset flag */ + *int_f &= 0xFE; + + /* handle the interrupt */ + z80_intr(0x0040); + } + else if ((int_r & 0x02) == 0x02) + { + /* LCD Stat interrupt */ + + /* reset flag */ + *int_f &= 0xFD; + + /* handle the interrupt! */ + z80_intr(0x0048); + } + else if ((int_r & 0x04) == 0x04) + { + /* timer interrupt */ + + /* reset flag */ + *int_f &= 0xFB; + + /* handle the interrupt! */ + z80_intr(0x0050); + } + else if ((int_r & 0x08) == 0x08) + { + /* serial interrupt */ + + /* reset flag */ + *int_f &= 0xF7; + + /* handle the interrupt! */ + z80_intr(0x0058); + } + } + } + + /* terminate all the stuff */ + cartridge_term(); + sound_term(); + mmu_term(); + + return; +} + +void gameboy_stop() +{ + global_quit = 1; + + /* wake up */ + if (global_pause) + { + global_pause = 0; + sem_post(&gameboy_sem); + } + + /* unlock threads stuck during reading */ + sound_term(); + + /* shutdown semaphore limitator */ + cycles_term(); +} + +char gameboy_restore_stat(int idx) +{ + char path[256]; + char buf[6]; + + /* ensure i'm in pause */ + gameboy_set_pause(1); + + /* build output file name */ + snprintf(path, sizeof(path), "%s/%s.%d.stat", global_save_folder, + global_rom_name, idx); + + FILE *fp = fopen(path, "r+"); + + if (fp == NULL) + { + utils_log("Cannot open stat file\n"); + return 1; + } + + /* read version */ + fread(buf, 1, 6, fp); + + if (memcmp(buf, "000001", 6)) + { + utils_log("Version of stat file doesnt match\n"); + return 1; + } + + /* restore CPU status */ + fread(&state, 1, sizeof(z80_state_t), fp); + + state.f = (uint8_t *) &state.flags; + state.bc = (uint16_t *) &state.c; + state.de = (uint16_t *) &state.e; + state.hl = (uint16_t *) &state.l; + + /* dump every module */ + cycles_restore_stat(fp); + sound_restore_stat(fp); + gpu_restore_stat(fp); + serial_restore_stat(fp); + mmu_restore_stat(fp); + + fclose(fp); + + return 0; +} + +char gameboy_save_stat(int idx) +{ + char path[256]; + + /* ensure i'm in pause */ + gameboy_set_pause(1); + + /* build output file name */ + snprintf(path, sizeof(path), "%s/%s.%d.stat", global_save_folder, + global_rom_name, idx); + + FILE *fp = fopen(path, "w+"); + + if (fp == NULL) + return 1; + + /* dump current version */ + fwrite("000001", 1, 6, fp); + + /* dump cpu status */ + fwrite(&state, 1, sizeof(z80_state_t), fp); + + /* dump every module */ + cycles_save_stat(fp); + sound_save_stat(fp); + gpu_save_stat(fp); + serial_save_stat(fp); + mmu_save_stat(fp); + + fclose(fp); + + /* now dump raw data of frame buffer */ + snprintf(path, sizeof(path), "%s/%s.%d.fb", global_save_folder, + global_rom_name, idx); + + fp = fopen(path, "w+"); + + if (fp == NULL) + return 1; + + /* dump frame buffer pixels */ + gpu_save_fb(fp); + + fclose(fp); + + return 0; +} + diff --git a/waterbox/pizza/lib/gameboy.h b/waterbox/pizza/lib/gameboy.h new file mode 100644 index 0000000000..3aedf6be03 --- /dev/null +++ b/waterbox/pizza/lib/gameboy.h @@ -0,0 +1,31 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __GAMEBOY_HDR__ +#define __GAMEBOY_HDR__ + +/* prototypes */ +void gameboy_init(); +void gameboy_run(); +char gameboy_restore_stat(int idx); +char gameboy_save_stat(int idx); +void gameboy_set_pause(char pause); +void gameboy_stop(); + +#endif diff --git a/waterbox/pizza/lib/global.c b/waterbox/pizza/lib/global.c new file mode 100644 index 0000000000..bc2cd623ae --- /dev/null +++ b/waterbox/pizza/lib/global.c @@ -0,0 +1,56 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include +#include + +#include "global.h" + +char global_cart_name[256]; +char global_cgb; +char global_cpu_double_speed; +char global_debug; +char global_emulation_speed; +char global_next_frame; +char global_pause; +char global_quit; +char global_record_audio; +char global_rom_name[256]; +char global_rumble; +char global_slow_down; +char global_save_folder[256]; +char global_window; + +void global_init() +{ + global_quit = 0; + global_pause = 0; + global_window = 1; + global_debug = 0; + global_cgb = 0; + global_cpu_double_speed = 0; + global_slow_down = 0; + global_record_audio = 0; + global_next_frame = 0; + global_rumble = 0; + global_emulation_speed = GLOBAL_EMULATION_SPEED_NORMAL; + // bzero(global_save_folder, 256); + bzero(global_rom_name, 256); + sprintf(global_cart_name, "NOCARTIRDGE"); +} diff --git a/waterbox/pizza/lib/global.h b/waterbox/pizza/lib/global.h new file mode 100644 index 0000000000..020455c8d0 --- /dev/null +++ b/waterbox/pizza/lib/global.h @@ -0,0 +1,51 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __GLOBAL__ +#define __GLOBAL__ + +/* defines */ +enum { + GLOBAL_EMULATION_SPEED_QUARTER, + GLOBAL_EMULATION_SPEED_HALF, + GLOBAL_EMULATION_SPEED_NORMAL, + GLOBAL_EMULATION_SPEED_DOUBLE, + GLOBAL_EMULATION_SPEED_4X +}; + +extern char global_quit; +extern char global_pause; +extern char global_window; +extern char global_debug; +extern char global_cgb; +extern char global_next_frame; +// extern char global_started; +extern char global_cpu_double_speed; +extern char global_slow_down; +extern char global_record_audio; +extern char global_emulation_speed; +extern char global_rumble; +extern char global_save_folder[256]; +extern char global_rom_name[256]; +extern char global_cart_name[256]; + +/* prototypes */ +void global_init(); + +#endif diff --git a/waterbox/pizza/lib/gpu.c b/waterbox/pizza/lib/gpu.c new file mode 100644 index 0000000000..dfeb2df046 --- /dev/null +++ b/waterbox/pizza/lib/gpu.c @@ -0,0 +1,1179 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include +#include +#include +#include +#include +#include + +#include "cycles.h" +#include "gameboy.h" +#include "global.h" +#include "gpu.h" +#include "interrupt.h" +#include "mmu.h" +#include "utils.h" + +/* Gameboy OAM 4 bytes data */ +typedef struct gpu_oam_s +{ + uint8_t y; + uint8_t x; + uint8_t pattern; + + uint8_t palette_cgb:3; + uint8_t vram_bank:1; + uint8_t palette:1; + uint8_t x_flip:1; + uint8_t y_flip:1; + uint8_t priority:1; + +} gpu_oam_t; + +/* Gameboy Color additional tile attributes */ +typedef struct gpu_cgb_bg_tile_s +{ + uint8_t palette:3; + uint8_t vram_bank:1; + uint8_t spare:1; + uint8_t x_flip:1; + uint8_t y_flip:1; + uint8_t priority:1; + +} gpu_cgb_bg_tile_t; + +/* ordered sprite list */ +typedef struct oam_list_s +{ + int idx; + struct oam_list_s *next; +} oam_list_t; + +/* pointer to interrupt flags (handy) */ +interrupts_flags_t *gpu_if; + +/* internal functions prototypes */ +void gpu_draw_sprite_line(gpu_oam_t *oam, + uint8_t sprites_size, + uint8_t line); +void gpu_draw_window_line(int tile_idx, uint8_t frame_x, + uint8_t frame_y, uint8_t line); + +/* 2 bit to 8 bit color lookup */ +static uint16_t gpu_color_lookup[] = { 0xFFFF, 0xAD55, 0x52AA, 0x0000 }; + +/* function to call when frame is ready */ +gpu_frame_ready_cb_t gpu_frame_ready_cb; + +/* global state of GPU */ +gpu_t gpu; + + +void gpu_dump_oam() +{ + /* make it point to the first OAM object */ + gpu_oam_t *oam = (gpu_oam_t *) mmu_addr(0xFE00); + + int i; + + for (i=0; i<40; i++) + { + if (oam[i].x != 0 && oam[i].y != 0) + printf("OAM X %d Y %d VRAM %d PATTERN %d\n", oam[i].x, oam[i].y, + oam[i].vram_bank, + oam[i].pattern); + } +} + + +/* init pointers */ +void gpu_init_pointers() +{ + /* make gpu field points to the related memory area */ + gpu.lcd_ctrl = mmu_addr(0xFF40); + gpu.lcd_status = mmu_addr(0xFF41); + gpu.scroll_y = mmu_addr(0xFF42); + gpu.scroll_x = mmu_addr(0xFF43); + gpu.window_y = mmu_addr(0xFF4A); + gpu.window_x = mmu_addr(0xFF4B); + gpu.ly = mmu_addr(0xFF44); + gpu.lyc = mmu_addr(0xFF45); + gpu_if = mmu_addr(0xFF0F); +} + +/* reset */ +void gpu_reset() +{ + /* init counters */ + gpu.next = 456 << global_cpu_double_speed; + gpu.frame_counter = 0; + +} + +/* init GPU states */ +void gpu_init(gpu_frame_ready_cb_t cb) +{ + /* reset gpu structure */ + bzero(&gpu, sizeof(gpu_t)); + + /* init memory pointers */ + gpu_init_pointers(); + + /* init counters */ + gpu.next = 456 << global_cpu_double_speed; + gpu.frame_counter = 0; + + /* step for normal CPU speed */ + gpu.step = 4; + + /* init palette */ + memcpy(gpu.bg_palette, gpu_color_lookup, sizeof(uint16_t) * 4); + memcpy(gpu.obj_palette_0, gpu_color_lookup, sizeof(uint16_t) * 4); + memcpy(gpu.obj_palette_1, gpu_color_lookup, sizeof(uint16_t) * 4); + + /* set callback */ + gpu_frame_ready_cb = cb; +} + +/* turn on/off lcd */ +void gpu_toggle(uint8_t state) +{ + /* from off to on */ + if (state & 0x80) + { + /* LCD turned on */ + gpu.next = cycles.cnt + (456 << global_cpu_double_speed); + *gpu.ly = 0; + (*gpu.lcd_status).mode = 0x00; + (*gpu.lcd_status).ly_coincidence = 0x00; + } + else + { + /* LCD turned off - reset stuff */ + gpu.next = cycles.cnt - 1; // + (80 << global_cpu_double_speed); + *gpu.ly = 0; + (*gpu.lcd_status).mode = 0x00; + } +} + +/* push frame on screen */ +void gpu_draw_frame() +{ + /* increase frame counter */ + gpu.frame_counter++; + + /* is it the case to push samples? */ + if ((global_emulation_speed == GLOBAL_EMULATION_SPEED_DOUBLE && + (gpu.frame_counter & 0x0001) != 0) || + (global_emulation_speed == GLOBAL_EMULATION_SPEED_4X && + (gpu.frame_counter & 0x0003) != 0)) + return; + + uint_fast32_t i,r,g,b,r2,g2,b2,res; + + /* simulate shitty gameboy response time of LCD */ + /* by calculating an average between current and previous frame */ + //for (i=0; i<(144*160); i++) + for (i=0; i<(144 * 160); i++) + { +/* r = gpu.frame_buffer[i] & 0x1F; + g = gpu.frame_buffer[i] >> 5 & 0x3F; + b = gpu.frame_buffer[i] >> 11 & 0x1F; + + r2 = gpu.frame_buffer_prev[i] & 0x1F; + g2 = gpu.frame_buffer_prev[i] >> 5 & 0x3F; + b2 = gpu.frame_buffer_prev[i] >> 11 & 0x1F; + + gpu.frame_buffer_prev[i] = gpu.frame_buffer[i]; + + gpu.frame_buffer[i] = (uint16_t) ((r + r2) >> 1) | + (((g + g2) >> 1) << 5) | + (((b + b2) >> 1) << 11);*/ + + r = gpu.frame_buffer[i] & 0x001F; + g = gpu.frame_buffer[i] & 0x07E0; + b = gpu.frame_buffer[i] & 0xF800; + + r2 = gpu.frame_buffer_prev[i] & 0x001F; + g2 = gpu.frame_buffer_prev[i] & 0x07E0; + b2 = gpu.frame_buffer_prev[i] & 0xF800; + + // gpu.frame_buffer_prev[i] = gpu.frame_buffer[i]; + + res = ((r + r2) >> 1) | + (((g + g2) >> 1) & 0x07E0) | + (((b + b2) >> 1) & 0xF800); + + gpu.frame_buffer_prev[i] = gpu.frame_buffer[i]; + gpu.frame_buffer[i] = res; + } + + /* call the callback */ + if (gpu_frame_ready_cb) + (*gpu_frame_ready_cb) (); + + /* reset priority matrix */ + bzero(gpu.priority, 160 * 144); + bzero(gpu.palette_idx, 160 * 144); + + if (global_next_frame) + { + global_next_frame = 0; + gameboy_set_pause(1); + } + + return; +} + +/* get pointer to frame buffer */ +uint16_t *gpu_get_frame_buffer() +{ + return gpu.frame_buffer; +} + +/* draw a single line */ +void gpu_draw_line(uint8_t line) +{ + /* avoid mess */ + if (line > 144) + return; + + /* is it the case to push samples? */ + if ((global_emulation_speed == GLOBAL_EMULATION_SPEED_DOUBLE && + (gpu.frame_counter & 0x0001) != 0) || + (global_emulation_speed == GLOBAL_EMULATION_SPEED_4X && + (gpu.frame_counter & 0x0003) != 0)) + return; + + int i, t, y, px_start, px_drawn; + uint8_t *tiles_map, tile_subline, palette_idx, x_flip, priority; + uint16_t tiles_addr, tile_n, tile_idx, tile_line; + uint16_t tile_y; + + /* gotta show BG? Answer is always YES in case of Gameboy Color */ + if ((*gpu.lcd_ctrl).bg || global_cgb) + { + gpu_cgb_bg_tile_t *tiles_map_cgb = NULL; + uint8_t *tiles = NULL; + uint16_t *palette; + + if (global_cgb) + { + /* CGB tile map into VRAM0 */ + tiles_map = mmu_addr_vram0() + ((*gpu.lcd_ctrl).bg_tiles_map ? + 0x1C00 : 0x1800); + + /* additional attribute table is into VRAM1 */ + tiles_map_cgb = mmu_addr_vram1() + ((*gpu.lcd_ctrl).bg_tiles_map ? + 0x1C00 : 0x1800); + } + else + { + /* never flip */ + x_flip = 0; + + /* get tile map offset */ + tiles_map = mmu_addr((*gpu.lcd_ctrl).bg_tiles_map ? + 0x9C00 : 0x9800); + + if ((*gpu.lcd_ctrl).bg_tiles) + tiles_addr = 0x8000; + else + tiles_addr = 0x9000; + + /* get absolute address of tiles area */ + tiles = mmu_addr(tiles_addr); + + /* monochrome GB uses a single BG palette */ + palette = gpu.bg_palette; + + /* always priority = 0 */ + priority = 0; + } + + /* calc tile y */ + tile_y = (*(gpu.scroll_y) + line) & 0xFF; + + /* calc first tile idx */ + tile_idx = ((tile_y >> 3) * 32) + (*(gpu.scroll_x) / 8); + + /* tile line because if we reach the end of the line, */ + /* we have to rewind to the first tile of the same line */ + tile_line = ((tile_y >> 3) * 32); + + /* calc first pixel of frame buffer of the current line */ + uint_fast16_t pos_fb = line * 160; + uint_fast16_t pos; + + /* calc tile subline */ + tile_subline = tile_y % 8; + + /* walk through different tiles */ + for (t=0; t<21; t++) + { + /* resolv tile data memory area */ + if ((*gpu.lcd_ctrl).bg_tiles == 0) + tile_n = (int8_t) tiles_map[tile_idx]; + else + tile_n = (tiles_map[tile_idx] & 0x00FF); + + /* if color gameboy, resolv which palette is bound */ + if (global_cgb) + { + /* extract palette index (0-31) */ + palette_idx = tiles_map_cgb[tile_idx].palette; + + /* get palette pointer to 4 (16bit) colors */ + palette = + (uint16_t *) &gpu.cgb_palette_bg_rgb565[palette_idx * 4]; + + /* get priority of the tile */ + priority = tiles_map_cgb[tile_idx].priority; + + if (tiles_map_cgb[tile_idx].vram_bank) + tiles = mmu_addr_vram1() + + ((*gpu.lcd_ctrl).bg_tiles ? 0x0000 : 0x1000); + else + tiles = mmu_addr_vram0() + + ((*gpu.lcd_ctrl).bg_tiles ? 0x0000 : 0x1000); + + /* calc subline in case of flip_y */ + if (tiles_map_cgb[tile_idx].y_flip) + tile_subline = 7 - (tile_y % 8); + else + tile_subline = tile_y % 8; + + /* save x_flip */ + x_flip = tiles_map_cgb[tile_idx].x_flip; + } + + /* calc tile data pointer */ + int16_t tile_ptr = (tile_n * 16) + (tile_subline * 2); + + /* pixels are handled in a super shitty way */ + /* bit 0 of the pixel is taken from even position tile bytes */ + /* bit 1 of the pixel is taken from odd position tile bytes */ + + uint8_t pxa[8]; + uint8_t shft; + uint8_t b1 = *(tiles + tile_ptr); + uint8_t b2 = *(tiles + tile_ptr + 1); + + for (y=0; y<8; y++) + { + if (x_flip) + shft = (1 << (7 - y)); + else + shft = (1 << y); + + pxa[y] = ((b1 & shft) ? 1 : 0) | + ((b2 & shft) ? 2 : 0); + } + + /* particular cases for first and last tile */ + /* (could be shown just a part) */ + if (t == 0) + { + px_start = (*(gpu.scroll_x) % 8); + + px_drawn = 8 - px_start; + + /* set n pixels */ + for (i=0; i= (oam[i].y - 16)) + { + /* color GB uses memory position as priority criteria */ + if (global_cgb) + { + sort[j++] = i; + continue; + } + + /* find its position on sort array */ + for (j=0; j<40; j++) + { + if (sort[j] == -1) + { + sort[j] = i; + break; + } + + if (global_cgb) + continue; + + if ((oam[i].y < oam[sort[j]].y) || + ((oam[i].y == oam[sort[j]].y) && + (oam[i].x < oam[sort[j]].x))) + { + int z; + + for (z=40; z>j; z--) + sort[z] = sort[z-1]; + + sort[j] = i; + break; + } + } + } + } + + /* draw ordered sprite list */ + for (i=0; i<40 && sort[i] != -1; i++) + gpu_draw_sprite_line(&oam[sort[i]], + (*gpu.lcd_ctrl).sprites_size, line); + + } + + /* wanna show window? */ + if (global_window && (*gpu.lcd_ctrl).window) + { + /* at least the current line is covering the window area? */ + if (line < *(gpu.window_y)) + return; + + /* TODO - reset this in a better place */ + if (line == *(gpu.window_y)) + gpu.window_skipped_lines = 0; + + int z, first_z; + uint8_t tile_pos_x, tile_pos_y; + + /* gotta draw a window? check if it is inside screen coordinates */ + if (*(gpu.window_y) >= 144 || + *(gpu.window_x) >= 160) + { + gpu.window_skipped_lines++; + return; + } + + /* calc the first interesting tile */ + first_z = ((line - *(gpu.window_y) - + gpu.window_skipped_lines) >> 3) << 5; + + for (z=first_z; z> 5) << 3) + *(gpu.window_y) + + gpu.window_skipped_lines; + + /* gone over the current line? */ + if (tile_pos_y > line) + break; + + if (tile_pos_y < (line - 7)) + continue; + + /* gone over the screen visible X? */ + /* being between last column and first one is valid */ + if (tile_pos_x >= 160 && tile_pos_x < 248) + break; + + /* gone over the screen visible section? stop it */ + if (tile_pos_y >= 144) // || (tile_pos_x >= 160)) + break; + + /* put tile on frame buffer */ + gpu_draw_window_line(z, (uint8_t) tile_pos_x, + (uint8_t) tile_pos_y, line); + } + } +} + + + +/* draw a tile in x,y coordinates */ +void gpu_draw_window_line(int tile_idx, uint8_t frame_x, + uint8_t frame_y, uint8_t line) +{ + int i, p, y, pos; + int16_t tile_n; + uint8_t *tiles_map; + gpu_cgb_bg_tile_t *tiles_map_cgb = NULL; + uint8_t *tiles, x_flip; + uint16_t *palette; + + if (global_cgb) + { + /* CGB tile map into VRAM0 */ + tiles_map = mmu_addr_vram0() + ((*gpu.lcd_ctrl).window_tiles_map ? + 0x1C00 : 0x1800); + + /* additional attribute table is into VRAM1 */ + tiles_map_cgb = mmu_addr_vram1() + ((*gpu.lcd_ctrl).window_tiles_map ? + 0x1C00 : 0x1800); + + /* get palette index */ + uint8_t palette_idx = tiles_map_cgb[tile_idx].palette; + x_flip = tiles_map_cgb[tile_idx].x_flip; + + /* get palette pointer to 4 (16bit) colors */ + palette = (uint16_t *) &gpu.cgb_palette_bg_rgb565[palette_idx * 4]; + + /* attribute table will tell us where is the tile */ + if (tiles_map_cgb[tile_idx].vram_bank) + tiles = mmu_addr_vram1() + + ((*gpu.lcd_ctrl).bg_tiles ? 0x0000 : 0x1000); + else + tiles = mmu_addr_vram0() + + ((*gpu.lcd_ctrl).bg_tiles ? 0x0000 : 0x1000); + + } + else + { + /* get tile map offset */ + tiles_map = mmu_addr((*gpu.lcd_ctrl).window_tiles_map ? + 0x9C00 : 0x9800); + + /* get tile offset */ + if ((*gpu.lcd_ctrl).bg_tiles) + tiles = mmu_addr(0x8000); + else + tiles = mmu_addr(0x9000); + + /* monochrome GB uses a single BG palette */ + palette = gpu.bg_palette; + + /* never flip */ + x_flip = 0; + } + + /* obtain tile number */ + if ((*gpu.lcd_ctrl).bg_tiles == 0) + tile_n = (int8_t) tiles_map[tile_idx]; + else + tile_n = (tiles_map[tile_idx] & 0x00ff); + + /* calc vertical offset INSIDE the tile */ + p = (line - frame_y) * 2; + + /* calc frame position buffer for 4 pixels */ + uint32_t pos_fb = (line * 160); + + /* calc tile pointer */ + int16_t tile_ptr = (tile_n * 16) + p; + + /* pixels are handled in a super shitty way */ + /* bit 0 of the pixel is taken from even position tile bytes */ + /* bit 1 of the pixel is taken from odd position tile bytes */ + + uint8_t pxa[8]; + uint8_t shft; + + for (y=0; y<8; y++) + { + //uint8_t shft = (1 << y); + + if (x_flip) + shft = (1 << (7 - y)); + else + shft = (1 << y); + + pxa[y] = ((*(tiles + tile_ptr) & shft) ? 1 : 0) | + ((*(tiles + tile_ptr + 1) & shft) ? 2 : 0); + } + + /* set 8 pixels (full tile line) */ + for (i=0; i<8; i++) + { + /* over the last column? */ + uint8_t x = frame_x + (7 - i); + + if (x > 159) + continue; + + /* calc position on frame buffer */ + pos = pos_fb + x; + + /* can overwrite sprites? depends on pixel priority */ + if (gpu.priority[pos] != 0x02) + gpu.frame_buffer[pos] = palette[pxa[i]]; + } +} + +/* draw a sprite tile in x,y coordinates */ +void gpu_draw_sprite_line(gpu_oam_t *oam, uint8_t sprites_size, uint8_t line) +{ + int_fast32_t x, y, pos, fb_x, off; + uint_fast16_t p, i, j; + uint8_t sprite_bytes; + int16_t tile_ptr; + uint16_t *palette; + uint8_t *tiles; + + /* is it the case to push samples? */ +/* if ((global_emulation_speed == GLOBAL_EMULATION_SPEED_DOUBLE && + (gpu.frame_counter & 0x01) != 0) || + (global_emulation_speed == GLOBAL_EMULATION_SPEED_4X && + (gpu.frame_counter & 0x03) != 0)) + return; */ + + /* REMEMBER! position of sprites is relative to the visible screen area */ + /* ... and y is shifted by 16 pixels, x by 8 */ + y = oam->y - 16; + x = oam->x - 8; + + if (x < -7) + return; + + /* first pixel on frame buffer position */ + uint32_t tile_pos_fb = (y * 160) + x; + + /* choose palette */ + if (global_cgb) + { + uint8_t palette_idx = oam->palette_cgb; + + /* get palette pointer to 4 (16bit) colors */ + palette = (uint16_t *) &gpu.cgb_palette_oam_rgb565[palette_idx * 4]; + + /* tiles are into vram0 */ + if (oam->vram_bank) + tiles = mmu_addr_vram1(); + else + tiles = mmu_addr_vram0(); + } + else + { + /* tiles are int fixed 0x8000 address */ + tiles = mmu_addr(0x8000); + + if (oam->palette) + palette = gpu.obj_palette_1; + else + palette = gpu.obj_palette_0; + } + + /* calc sprite in byte */ + sprite_bytes = 16 * (sprites_size + 1); + + /* walk through 8x8 pixels (2bit per pixel -> 4 pixels per byte) */ + /* 1 line is 8 pixels -> 2 bytes per line */ + for (p=0; py_flip) + tile_ptr = (oam->pattern * 16) + (sprite_bytes - p - 2); + else + tile_ptr = (oam->pattern * 16) + p; + + /* pixels are handled in a super shitty way */ + /* bit 0 of the pixel is taken from even position tile bytes */ + /* bit 1 of the pixel is taken from odd position tile bytes */ + + uint8_t pxa[8]; + + for (j=0; j<8; j++) + { + uint8_t shft = (1 << j); + + pxa[j] = ((*(tiles + tile_ptr) & shft) ? 1 : 0) | + ((*(tiles + tile_ptr + 1) & shft) ? 2 : 0); + } + + /* set 8 pixels (full tile line) */ + for (i=0; i<8; i++) + { + if (oam->x_flip) + off = i; + else + off = 7 - i; + + /* is it on screen? */ + fb_x = x + off; + + if (fb_x < 0 || fb_x > 160) + continue; + + /* set serial position on frame buffer */ + pos = pos_fb + off; + + /* is it inside the screen? */ + if (pos >= 144 * 160 || pos < 0) + continue; + + if (global_cgb) + { + /* sprite color 0 = transparent */ + if (pxa[i] != 0x00) + { + /* flag clr = sprites always on top of bg and window */ + if ((*gpu.lcd_ctrl).bg == 0) + { + gpu.frame_buffer[pos] = palette[pxa[i]]; + gpu.priority[pos] = 0x02; + } + else + { + if (((gpu.priority[pos] == 0) && + (oam->priority == 0 || + (oam->priority == 1 && + gpu.palette_idx[pos] == 0x00))) || + (gpu.priority[pos] == 1 && + gpu.palette_idx[pos] == 0x00)) + { + gpu.frame_buffer[pos] = palette[pxa[i]]; + gpu.priority[pos] = (oam->priority ? 0x00 : 0x02); + } + } + } + } + else + { + /* push on screen pixels not set to zero (transparent) */ + /* and if the priority is set to one, overwrite just */ + /* bg pixels set to zero */ + if ((pxa[i] != 0x00) && + (oam->priority == 0 || + (oam->priority == 1 && + gpu.frame_buffer[pos] == gpu.bg_palette[0x00]))) + { + gpu.frame_buffer[pos] = palette[pxa[i]]; + gpu.priority[pos] = (oam->priority ? 0x00 : 0x02); + } + } + } + } +} + +/* update GPU internal state given CPU T-states */ +void gpu_step() +{ + char ly_changed = 0; + char mode_changed = 0; + + /* take different action based on current state */ + switch((*gpu.lcd_status).mode) + { + /* + * during HBLANK (CPU can access VRAM) + */ + case 0: + /* handle HDMA stuff during hblank */ + cycles_hdma(); + + /* + * if current line == 143 (and it's about to turn 144) + * enter mode 01 (VBLANK) + */ + if (*gpu.ly == 143) + { + /* notify mode has changes */ + mode_changed = 1; + + (*gpu.lcd_status).mode = 0x01; + + /* mode one lasts 456 cycles */ + gpu.next = cycles.cnt + + (456 << global_cpu_double_speed); + + /* DRAW! TODO */ + /* CHECK INTERRUPTS! TODO */ + cycles_vblank(); + + /* set VBLANK interrupt flag */ + gpu_if->lcd_vblank = 1; + + /* apply gameshark patches */ + mmu_apply_gs(); + + /* and finally push it on screen! */ + gpu_draw_frame(); + } + else + { + /* notify mode has changed */ + mode_changed = 1; + + /* enter OAM mode */ + (*gpu.lcd_status).mode = 0x02; + + /* mode 2 needs 80 cycles */ + gpu.next = cycles.cnt + + (80 << global_cpu_double_speed); + + } + + /* notify mode has changed */ + ly_changed = 1; + + /* inc current line */ + (*gpu.ly)++; + +// cycles_hblank(*gpu.ly); + + break; + + /* + * during VBLANK (CPU can access VRAM) + */ + case 1: + /* notify ly has changed */ + ly_changed = 1; + + /* inc current line */ + (*gpu.ly)++; + + /* reached the bottom? */ + if ((*gpu.ly) > 153) + { + /* go back to line 0 */ + (*gpu.ly) = 0; + + /* switch to OAM mode */ + (*gpu.lcd_status).mode = 0x02; + + /* */ + gpu.next = + cycles.cnt + (80 << global_cpu_double_speed); + } + else + gpu.next = + cycles.cnt + (456 << global_cpu_double_speed); + + break; + + /* + * during OAM (LCD access FE00-FE90, so CPU cannot) + */ + case 2: + /* reset clock counter */ + gpu.next = + cycles.cnt + (172 << global_cpu_double_speed); + + /* notify mode has changed */ + mode_changed = 1; + + /* switch to VRAM mode */ + (*gpu.lcd_status).mode = 0x03; + + break; + + /* + * during VRAM (LCD access both OAM and VRAM, so CPU cannot) + */ + case 3: + /* reset clock counter */ + gpu.next = + cycles.cnt + (204 << global_cpu_double_speed); + + /* notify mode has changed */ + mode_changed = 1; + + /* go back to HBLANK mode */ + (*gpu.lcd_status).mode = 0x00; + + /* draw line */ + gpu_draw_line(*gpu.ly); + + /* notify cycles */ +// cycles_hblank(*gpu.ly); + + //printf("COLLA %d\n", *gpu.ly); + + break; + } + + /* ly changed? is it the case to trig an interrupt? */ + if (ly_changed) + { + /* check if we gotta trigger an interrupt */ + if ((*gpu.ly) == (*gpu.lyc)) + { + /* set lcd status flags indicating there's a concidence */ + (*gpu.lcd_status).ly_coincidence = 1; + + /* an interrupt is desiderable? */ + if ((*gpu.lcd_status).ir_ly_coincidence) + gpu_if->lcd_ctrl = 1; + } + else + { + /* set lcd status flags indicating there's NOT a concidence */ + (*gpu.lcd_status).ly_coincidence = 0; + } + } + + /* mode changed? is is the case to trig an interrupt? */ + if (mode_changed) + { + if ((*gpu.lcd_status).mode == 0x00 && + (*gpu.lcd_status).ir_mode_00) + gpu_if->lcd_ctrl = 1; + else if ((*gpu.lcd_status).mode == 0x01 && + (*gpu.lcd_status).ir_mode_01) + gpu_if->lcd_ctrl = 1; + else if ((*gpu.lcd_status).mode == 0x02 && + (*gpu.lcd_status).ir_mode_10) + gpu_if->lcd_ctrl = 1; + } +} + +uint8_t gpu_read_reg(uint16_t a) +{ + switch (a) + { + case 0xFF68: + + return (gpu.cgb_palette_bg_autoinc << 7 | gpu.cgb_palette_bg_idx); + + case 0xFF69: + + if ((gpu.cgb_palette_bg_idx & 0x01) == 0x00) + return gpu.cgb_palette_bg[gpu.cgb_palette_bg_idx / 2] & + 0x00ff; + else + return (gpu.cgb_palette_bg[gpu.cgb_palette_bg_idx / 2] & + 0xff00) >> 8; + + case 0xFF6A: + + return (gpu.cgb_palette_oam_autoinc << 7 | gpu.cgb_palette_oam_idx); + + case 0xFF6B: + + if ((gpu.cgb_palette_oam_idx & 0x01) == 0x00) + return gpu.cgb_palette_oam[gpu.cgb_palette_oam_idx / 2] & + 0x00ff; + else + return (gpu.cgb_palette_oam[gpu.cgb_palette_oam_idx / 2] & + 0xff00) >> 8; + + + } + + return 0x00; +} + +void gpu_write_reg(uint16_t a, uint8_t v) +{ + int i; + uint8_t r,g,b; + + switch (a) + { + case 0xFF47: + + gpu.bg_palette[0] = gpu_color_lookup[v & 0x03]; + gpu.bg_palette[1] = gpu_color_lookup[(v & 0x0c) >> 2]; + gpu.bg_palette[2] = gpu_color_lookup[(v & 0x30) >> 4]; + gpu.bg_palette[3] = gpu_color_lookup[(v & 0xc0) >> 6]; + + break; + + case 0xFF48: + + gpu.obj_palette_0[0] = gpu_color_lookup[v & 0x03]; + gpu.obj_palette_0[1] = gpu_color_lookup[(v & 0x0c) >> 2]; + gpu.obj_palette_0[2] = gpu_color_lookup[(v & 0x30) >> 4]; + gpu.obj_palette_0[3] = gpu_color_lookup[(v & 0xc0) >> 6]; + + break; + + case 0xFF49: + + gpu.obj_palette_1[0] = gpu_color_lookup[v & 0x03]; + gpu.obj_palette_1[1] = gpu_color_lookup[(v & 0x0c) >> 2]; + gpu.obj_palette_1[2] = gpu_color_lookup[(v & 0x30) >> 4]; + gpu.obj_palette_1[3] = gpu_color_lookup[(v & 0xc0) >> 6]; + + break; + + case 0xFF68: + + gpu.cgb_palette_bg_idx = (v & 0x3f); + gpu.cgb_palette_bg_autoinc = ((v & 0x80) == 0x80); + + break; + + case 0xFF69: + + i = gpu.cgb_palette_bg_idx / 2; + + if ((gpu.cgb_palette_bg_idx & 0x01) == 0x00) + { + gpu.cgb_palette_bg[i] &= 0xff00; + gpu.cgb_palette_bg[i] |= v; + } + else + { + gpu.cgb_palette_bg[i] &= 0x00ff; + gpu.cgb_palette_bg[i] |= (v << 8); + } + + r = gpu.cgb_palette_bg[i] & 0x1F; + g = gpu.cgb_palette_bg[i] >> 5 & 0x1F; + b = gpu.cgb_palette_bg[i] >> 10 & 0x1F; + + gpu.cgb_palette_bg_rgb565[i] = + (((r * 13 + g * 2 + b + 8) << 7) & 0xF800) | + ((g * 3 + b + 1) >> 1) << 5 | + ((r * 3 + g * 2 + b * 11 + 8) >> 4); + + if (gpu.cgb_palette_bg_autoinc) + gpu.cgb_palette_bg_idx = ((gpu.cgb_palette_bg_idx + 1) & 0x3f); + + break; + + case 0xFF6A: + + gpu.cgb_palette_oam_idx = v & 0x3f; + gpu.cgb_palette_oam_autoinc = ((v & 0x80) == 0x80); + + break; + + case 0xFF6B: + + i = gpu.cgb_palette_oam_idx / 2; + + if ((gpu.cgb_palette_oam_idx & 0x01) == 0x00) + { + gpu.cgb_palette_oam[i] &= 0xff00; + gpu.cgb_palette_oam[i] |= v; + } + else + { + gpu.cgb_palette_oam[i] &= 0x00ff; + gpu.cgb_palette_oam[i] |= (v << 8); + } + + r = gpu.cgb_palette_oam[i] & 0x1F; + g = gpu.cgb_palette_oam[i] >> 5 & 0x1F; + b = gpu.cgb_palette_oam[i] >> 10 & 0x1F; + + gpu.cgb_palette_oam_rgb565[i] = + (((r * 13 + g * 2 + b + 8) << 7) & 0xF800) | + ((g * 3 + b + 1) >> 1) << 5 | + ((r * 3 + g * 2 + b * 11 + 8) >> 4); + + if (gpu.cgb_palette_oam_autoinc) + gpu.cgb_palette_oam_idx = + ((gpu.cgb_palette_oam_idx + 1) & 0x3f); + + break; + + } +} + +void gpu_set_speed(char speed) +{ + if (speed == 1) + gpu.step = 2; + else + gpu.step = 4; +} + +void gpu_save_fb(FILE *fp) +{ + fwrite(&gpu.frame_buffer, 1, sizeof(int16_t) * 144 * 160, fp); +} + +void gpu_save_stat(FILE *fp) +{ + fwrite(&gpu, 1, sizeof(gpu_t), fp); +} + +void gpu_restore_stat(FILE *fp) +{ + fread(&gpu, 1, sizeof(gpu_t), fp); + + gpu_init_pointers(); +} + diff --git a/waterbox/pizza/lib/gpu.h b/waterbox/pizza/lib/gpu.h new file mode 100644 index 0000000000..710594ca33 --- /dev/null +++ b/waterbox/pizza/lib/gpu.h @@ -0,0 +1,144 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __GPU_HDR__ +#define __GPU_HDR__ + +#include +#include + +/* callback function */ +typedef void (*gpu_frame_ready_cb_t) (); + +/* prototypes */ +void gpu_dump_oam(); +uint16_t *gpu_get_frame_buffer(); +void gpu_init(gpu_frame_ready_cb_t cb); +void gpu_reset(); +void gpu_restore_stat(FILE *fp); +void gpu_save_stat(FILE *fp); +void gpu_save_fb(FILE *fp); +void gpu_set_speed(char speed); +void gpu_step(); +void gpu_toggle(uint8_t state); +void gpu_write_reg(uint16_t a, uint8_t v); +uint8_t gpu_read_reg(uint16_t a); + + +/* Gameboy LCD Control - R/W accessing 0xFF40 address */ +typedef struct gpu_lcd_ctrl_s +{ + uint8_t bg:1; /* 0 = BG off, 1 = BG on */ + uint8_t sprites:1; /* ??? */ + uint8_t sprites_size:1; /* 0 = 8x8, 1 = 8x16 */ + uint8_t bg_tiles_map:1; /* 0 = 9800-9BFF, 1 = 9C00-9FFF */ + uint8_t bg_tiles:1; /* 0 = 8800-97FF, 1 = 8000-8FFF */ + uint8_t window:1; /* 0 = window off, 1 = on */ + uint8_t window_tiles_map:1; /* 0 = 9800-9BFF, 1 = 9C00-9FFF */ + uint8_t display:1; /* 0 = LCD off, 1 = LCD on */ +} gpu_lcd_ctrl_t; + +/* Gameboy LCD Status - R/W accessing 0xFF41 address */ +typedef struct gpu_lcd_status_s +{ + uint8_t mode:2; + uint8_t ly_coincidence:1; + uint8_t ir_mode_00:1; + uint8_t ir_mode_01:1; + uint8_t ir_mode_10:1; + uint8_t ir_ly_coincidence:1; + uint8_t spare:1; +} gpu_lcd_status_t; + +/* RGB color */ +typedef struct rgb_s +{ + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} rgb_t; + +/* Gameboy GPU status */ +typedef struct gpu_s +{ + gpu_lcd_ctrl_t *lcd_ctrl; + gpu_lcd_status_t *lcd_status; + + /* scroll positions */ + uint8_t *scroll_x; + uint8_t *scroll_y; + + /* window position */ + uint8_t *window_x; + uint8_t *window_y; + + /* current scanline and it's compare values */ + uint8_t *ly; + uint8_t *lyc; + + /* clocks counter */ + uint_fast32_t next; + + /* gpu step span */ + uint_fast32_t step; + + /* window last drawn lines */ + uint8_t window_last_ly; + uint8_t window_skipped_lines; + uint16_t spare; + + /* frame counter */ + uint_fast16_t frame_counter; + + /* BG palette */ + uint16_t bg_palette[4]; + + /* Obj palette 0/1 */ + uint16_t obj_palette_0[4]; + uint16_t obj_palette_1[4]; + + /* CGB palette for background */ + uint16_t cgb_palette_bg_rgb565[0x20]; + uint16_t cgb_palette_bg[0x20]; + uint8_t cgb_palette_bg_idx; + uint8_t cgb_palette_bg_autoinc; + uint16_t spare2; + + /* CGB palette for sprites */ + uint16_t cgb_palette_oam_rgb565[0x20]; + uint16_t cgb_palette_oam[0x20]; + uint8_t cgb_palette_oam_idx; + uint8_t cgb_palette_oam_autoinc; + uint16_t spare3; + + /* frame buffer */ + uint16_t frame_buffer_prev[160 * 144]; + uint16_t frame_buffer[160 * 144]; + uint8_t priority[160 * 144]; + uint8_t palette_idx[160 * 144]; + + uint_fast32_t spare4; + uint_fast32_t spare5; + +} gpu_t; + +extern gpu_t gpu; + +#endif diff --git a/waterbox/pizza/lib/input.c b/waterbox/pizza/lib/input.c new file mode 100644 index 0000000000..c8680b0c30 --- /dev/null +++ b/waterbox/pizza/lib/input.c @@ -0,0 +1,102 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include "global.h" +#include "utils.h" + +#include + +/* button states */ +char input_key_left; +char input_key_right; +char input_key_up; +char input_key_down; +char input_key_a; +char input_key_b; +char input_key_select; +char input_key_start; + +uint8_t input_init() +{ + input_key_left = 0; + input_key_right = 0; + input_key_up = 0; + input_key_down = 0; + input_key_a = 0; + input_key_b = 0; + input_key_select = 0; + input_key_start = 0; + + return 0; +} + +uint8_t input_get_keys(uint8_t line) +{ + uint8_t v = line | 0x0f; + + if ((line & 0x30) == 0x20) + { + /* RIGHT pressed? */ + if (input_key_right) + v ^= 0x01; + + /* LEFT pressed? */ + if (input_key_left) + v ^= 0x02; + + /* UP pressed? */ + if (input_key_up) + v ^= 0x04; + + /* DOWN pressed? */ + if (input_key_down) + v ^= 0x08; + } + + if ((line & 0x30) == 0x10) + { + /* A pressed? */ + if (input_key_a) + v ^= 0x01; + + /* B pressed? */ + if (input_key_b) + v ^= 0x02; + + /* SELECT pressed? */ + if (input_key_select) + v ^= 0x04; + + /* START pressed? */ + if (input_key_start) + v ^= 0x08; + } + + return (v | 0xc0); +} + +void input_set_key_right(char state) { input_key_right = state; } +void input_set_key_left(char state) { input_key_left = state; } +void input_set_key_up(char state) { input_key_up = state; } +void input_set_key_down(char state) { input_key_down = state; } +void input_set_key_a(char state) { input_key_a = state; } +void input_set_key_b(char state) { input_key_b = state; } +void input_set_key_select(char state) { input_key_select = state; } +void input_set_key_start(char state) { input_key_start = state; } + diff --git a/waterbox/pizza/lib/input.h b/waterbox/pizza/lib/input.h new file mode 100644 index 0000000000..dde0428cae --- /dev/null +++ b/waterbox/pizza/lib/input.h @@ -0,0 +1,35 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __INPUT_HDR__ +#define __INPUT_HDR__ + +/* prototypes */ +uint8_t input_get_keys(uint8_t line); +uint8_t input_init(); +void input_set_key_left(char state); +void input_set_key_right(char state); +void input_set_key_up(char state); +void input_set_key_down(char state); +void input_set_key_a(char state); +void input_set_key_b(char state); +void input_set_key_select(char state); +void input_set_key_start(char state); + +#endif diff --git a/waterbox/pizza/lib/interrupt.h b/waterbox/pizza/lib/interrupt.h new file mode 100644 index 0000000000..0fbc4b0873 --- /dev/null +++ b/waterbox/pizza/lib/interrupt.h @@ -0,0 +1,35 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __INTERRUPTS_HDR__ +#define __INTERRUPTS_HDR__ + +#include + +typedef struct interrupts_flags_s +{ + uint8_t lcd_vblank:1; + uint8_t lcd_ctrl:1; + uint8_t timer:1; + uint8_t serial_io:1; + uint8_t pins1013:1; + uint8_t spare:3; +} interrupts_flags_t; + +#endif \ No newline at end of file diff --git a/waterbox/pizza/lib/mmu.c b/waterbox/pizza/lib/mmu.c new file mode 100644 index 0000000000..d330697d8c --- /dev/null +++ b/waterbox/pizza/lib/mmu.c @@ -0,0 +1,1323 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include "cycles.h" +#include "global.h" +#include "gpu.h" +#include "interrupt.h" +#include "input.h" +#include "mmu.h" +#include "sound.h" +#include "serial.h" +#include "timer.h" +#include "utils.h" + +#include +#include +#include +#include +#include + +/* GAMEBOY MEMORY AREAS + +0x0000 - 0x00FF - BIOS +0x0000 - 0x3FFF - First 16k of game ROM (permanent) +0x4000 - 0x7FFF - ROM banks (switchable) +0x8000 - 0x9FFF - Video RAM (8kb - keeps pixels data) +0xA000 - 0xBFFF - External RAM (switchable, it was on cartridge, + 8kb banks, max 32k, NON volatile) +0xC000 - 0xDFFF - Gameboy RAM +0xE000 - 0xEFFF - ???????????????? +0xFE00 - 0xFF7F - I/O +0xFF80 - 0xFFFE - Temp RAM +0xFFFF - Turn on/off interrupts + +*/ + +/* cartridge memory (max 8MB) */ +uint8_t cart_memory[1 << 22]; + +/* RAM memory area */ +uint8_t *ram; +uint32_t ram_sz; + +/* main struct */ +mmu_t mmu; + +/* function to call when rumble */ +mmu_rumble_cb_t mmu_rumble_cb = NULL; + + +/* return absolute memory address */ +void *mmu_addr(uint16_t a) +{ + return (void *) &mmu.memory[a]; +} + +/* return absolute memory address */ +void *mmu_addr_vram0() +{ + return (void *) &mmu.vram0; +} + +/* return absolute memory address */ +void *mmu_addr_vram1() +{ + return (void *) &mmu.vram1; +} + +/* modify rom in case of Gamegenie cheat */ +void mmu_apply_gg() +{ + return; + + /* a wild cheat can occour */ + if (mmu.gg_count == 0) + return; + + int i; + + for (i=0; i> 8; + } + } + else + return mmu.memory[a]; + } + + /* RAM */ + if (a < 0xE000) + return mmu.memory[a]; + + /* RAM mirror */ + if (a < 0xFE00) + return mmu.memory[a - 0x2000]; + + switch (a) + { + /* serial registers */ + case 0xFF01: + case 0xFF02: + return serial_read_reg(a); + + /* don't ask me why.... */ + case 0xFF44: + return (mmu.memory[0xFF44] == 153 ? 0 : mmu.memory[0xFF44]); + + /* sound registers */ + case 0xFF10 ... 0xFF3F: + return sound_read_reg(a, mmu.memory[a]); + + /* joypad reading */ + case 0xFF00: + return input_get_keys(mmu.memory[a]); + + /* CGB HDMA transfer */ + case 0xFF55: + + if (!global_cgb) break; + + /* HDMA result */ + if (mmu.hdma_to_transfer) + return (mmu.hdma_to_transfer / 0x10 - 0x01); + else + return 0xFF; + + /* CGB color palette registers */ + case 0xFF68: + case 0xFF69: + case 0xFF6A: + case 0xFF6B: + + if (!global_cgb) break; + + /* color palettes registers */ + return gpu_read_reg(a); + + /* timer registers */ + case 0xFF04 ... 0xFF07: + return timer_read_reg(a); + + } + + return mmu.memory[a]; +} + +/* read 16 bit data from a memory addres */ +unsigned int mmu_read_16(uint16_t a) +{ + return (mmu_read(a) | (mmu_read(a + 1) << 8)); +} + +/* read 8 bit data from a memory addres (not affecting cycles) */ +uint8_t mmu_read_no_cyc(uint16_t a) +{ + if (a >= 0xE000 && a <= 0xFDFF) + return mmu.memory[a - 0x2000]; + + return mmu.memory[a]; +} + +void mmu_restore_ram(char *fn) +{ + /* save only if cartridge got a battery */ + if (mmu.carttype == 0x03 || + mmu.carttype == 0x06 || + mmu.carttype == 0x09 || + mmu.carttype == 0x0D || + mmu.carttype == 0x0F || + mmu.carttype == 0x10 || + mmu.carttype == 0x13 || + mmu.carttype == 0x17 || + mmu.carttype == 0x1B || + mmu.carttype == 0x1E || + mmu.carttype == 0x22 || + mmu.carttype == 0xFF) + { + FILE *fp = fopen(fn, "r+"); + + /* it could be not present */ + if (fp == NULL) + return; + + if (ram_sz <= 0x2000) + { + /* no need to put togheter pieces of ram banks */ + fread(&mmu.memory[0xA000], ram_sz, 1, fp); + } + else + { + /* read entire file into ram buffer */ + fread(mmu.ram_internal, 0x2000, 1, fp); + fread(ram, ram_sz, 1, fp); + + /* copy internal RAM to 0xA000 address */ + memcpy(&mmu.memory[0xA000], mmu.ram_internal, 0x2000); + } + + fclose(fp); + } +} + +void mmu_restore_rtc(char *fn) +{ + /* save only if cartridge got a battery */ + if (mmu.carttype == 0x10 || + mmu.carttype == 0x13) + { + FILE *fp = fopen(fn, "r+"); + + /* it could be not present */ + if (fp == NULL) + { + /* just pick current time */ + time(&mmu.rtc_time); + return; + } + + /* read last saved time */ + fscanf(fp, "%ld", &mmu.rtc_time); + + fclose(fp); + } +} + +void mmu_restore_stat(FILE *fp) +{ + fread(&mmu, 1, sizeof(mmu_t), fp); + + if (ram_sz) + fread(ram, 1, ram_sz, fp); +} + +void mmu_save_ram(char *fn) +{ + /* save only if cartridge got a battery */ + if (mmu.carttype == 0x03 || + mmu.carttype == 0x06 || + mmu.carttype == 0x09 || + mmu.carttype == 0x0d || + mmu.carttype == 0x0f || + mmu.carttype == 0x10 || + mmu.carttype == 0x13 || + mmu.carttype == 0x17 || + mmu.carttype == 0x1b || + mmu.carttype == 0x1e || + mmu.carttype == 0x22 || + mmu.carttype == 0xff) + { + FILE *fp = fopen(fn, "w+"); + + if (fp == NULL) + { + printf("Error dumping RAM\n"); + return; + } + + if (ram_sz <= 0x2000) + { + /* no need to put togheter pieces of ram banks */ + fwrite(&mmu.memory[0xA000], ram_sz, 1, fp); + } + else + { + /* yes, i need to put togheter pieces */ + + /* save current used bank */ + if (mmu.ram_external_enabled) + memcpy(&ram[0x2000 * mmu.ram_current_bank], + &mmu.memory[0xA000], 0x2000); + else + memcpy(mmu.ram_internal, + &mmu.memory[0xA000], 0x2000); + + /* dump the entire internal + external RAM */ + fwrite(mmu.ram_internal, 0x2000, 1, fp); + fwrite(ram, ram_sz, 1, fp); + } + + fclose(fp); + } +} + +void mmu_save_rtc(char *fn) +{ + /* save only if cartridge got a battery */ + if (mmu.carttype == 0x10 || + mmu.carttype == 0x13) + { + FILE *fp = fopen(fn, "w+"); + + if (fp == NULL) + { + printf("Error saving RTC\n"); + return; + } + + fprintf(fp, "%ld", mmu.rtc_time); + } +} + +void mmu_save_stat(FILE *fp) +{ + fwrite(&mmu, 1, sizeof(mmu_t), fp); + + if (ram_sz) + fwrite(ram, 1, ram_sz, fp); +} + +char mmu_set_cheat(char *str) +{ + if (str == NULL) + return 1; + + size_t len = strlen(str); + + /* gamegenie is 9 char long, gameshark is 8 char long */ + if (len < 8 || len > 9) + return 1; + + unsigned int new_value, address, old_value; + + /* gamegenie branch */ + if (len == 9) + { + if (mmu.gg_count == MMU_GAMEGENIE_MAX) + { + utils_log("Max Gamegenie cheats reached"); + return 1; + } + + char tmp[5]; + + /* parse it (must be cleaned by - before) */ + if (sscanf(str, "%02x", &new_value) < 1) + return 1; + + /* build memory address */ + tmp[0] = str[5]; + tmp[1] = str[2]; + tmp[2] = str[3]; + tmp[3] = str[4]; + tmp[4] = '\0'; + + if (sscanf(tmp, "%04x", &address) < 1) + return 1; + + /* build old value */ + tmp[0] = str[6]; + tmp[1] = str[7]; + tmp[2] = str[8]; + tmp[3] = '\0'; + + if (sscanf(tmp, "%03x", &old_value) < 1) + return 1; + + /* XOR data according do GameGenie specifications */ + address ^= 0xF000; + + if ((((old_value >> 8) ^ (old_value >> 4)) & 0x0F) != 0x08) + { + utils_log("Gamegenie cloak error\n"); + return 1; + } + + old_value = ((((old_value >> 2) & 0x03) | + ((old_value >> 6) & 0x3C) | + ((old_value << 6) & 0xC0)) ^ 0xBA); + + /* save it into current array slot */ + mmu.gg_array[mmu.gg_count].address = (uint16_t) address; + mmu.gg_array[mmu.gg_count].new_value = (uint8_t) new_value; + mmu.gg_array[mmu.gg_count].old_value = (uint8_t) old_value; + + /* looks legit! activate it */ + mmu.gg_count++; + + return 0; + } + else + { + unsigned int ram_bank, mem_low, mem_high; + + /* it must be a game shark cheat */ + if (sscanf(str, "%02x%02x%02x%02x", &ram_bank, &new_value, + &mem_low, &mem_high) < 4) + { + utils_log("Wrong Gameshark format"); + return 1; + } + + if (mmu.gs_count == MMU_GAMESHARK_MAX) + { + utils_log("Max Gameshark cheats reached"); + return 1; + } + + /* save it */ + mmu.gs_array[mmu.gs_count].address = (uint16_t) + (mem_low | (mem_high << 8)); + mmu.gs_array[mmu.gs_count].ram_bank = ram_bank & 0x7F; + mmu.gs_array[mmu.gs_count].new_value = new_value; + +/* utils_log("Gameshark address %04x - bank %02x - value %02x\n", + mmu.gs_array[mmu.gs_count].address, + mmu.gs_array[mmu.gs_count].ram_bank, + mmu.gs_array[mmu.gs_count].new_value);*/ + + /* looks legit! activate it */ + mmu.gs_count++; + + return 0; + } + + utils_log("Unknown cheat format"); + + return 1; +} + +void mmu_set_rumble_cb(mmu_rumble_cb_t cb) +{ + mmu_rumble_cb = cb; +} + +void mmu_term() +{ + if (ram) + { + free(ram); + ram = NULL; + } +} + +/* write 16 bit block on a memory address */ +void mmu_write(uint16_t a, uint8_t v) +{ + /* update cycles AFTER memory set */ + cycles_step(); + + /* color gameboy stuff */ + if (global_cgb) + { + /* VRAM write? */ + if (a >= 0x8000 && a < 0xA000) + { + if (mmu.vram_idx == 0) + mmu.vram0[a - 0x8000] = v; + else + mmu.vram1[a - 0x8000] = v; + + return; + } + else + { + /* wanna access to RTC register? */ + if (a >= 0xA000 && a <= 0xBFFF && mmu.rtc_mode != 0x00) + { + time_t t,s1,s2,m1,m2,h1,h2,d1,d2,days; + + /* get current time */ + time(&t); + + /* extract parts in seconds from current and ref times */ + s1 = t % 60; + s2 = mmu.rtc_time % 60; + + m1 = (t - s1) % (60 * 60); + m2 = (mmu.rtc_time - s2) % (60 * 60); + + h1 = (t - m1 - s1) % (60 * 60 * 24); + h2 = (mmu.rtc_time - m2 - s2) % (60 * 60 * 24); + + d1 = t - h1 - m1 - s1; + d2 = mmu.rtc_time - h2 - m2 - s2; + + switch (mmu.rtc_mode) + { + case 0x08: + + /* remove seconds from current time */ + mmu.rtc_time -= s2; + + /* set new seconds */ + mmu.rtc_time += (s1 - v); + + return; + + case 0x09: + + /* remove seconds from current time */ + mmu.rtc_time -= m2; + + /* set new seconds */ + mmu.rtc_time += (m1 - (v * 60)); + + return; + + case 0x0A: + + /* remove seconds from current time */ + mmu.rtc_time -= h2; + + /* set new seconds */ + mmu.rtc_time += (h1 - (v * 60 * 24)); + + return; + + case 0x0B: + + days = (((d1 - d2) / + (60 * 60 * 24)) & 0xFF00) | v; + + /* remove seconds from current time */ + mmu.rtc_time -= d2; + + /* set new seconds */ + mmu.rtc_time += (d1 - (days * 60 * 60 * 24)); + + return; + + case 0x0C: + + days = (((d1 - d2) / + (60 * 60 * 24)) & 0xFEFF) | (v << 8); + + /* remove seconds from current time */ + mmu.rtc_time -= d2; + + /* set new seconds */ + mmu.rtc_time += (d1 - (days * 60 * 60 * 24)); + + return; + } + } + } + + /* switch WRAM */ + if (a == 0xFF70) + { + /* number goes from 1 to 7 */ + uint8_t new = (v & 0x07); + + if (new == 0) + new = 1; + + if (new == mmu.wram_current_bank) + return; + + /* save current bank */ + memcpy(&mmu.wram[0x1000 * mmu.wram_current_bank], + &mmu.memory[0xD000], 0x1000); + + mmu.wram_current_bank = new; + + /* move new ram bank */ + memcpy(&mmu.memory[0xD000], + &mmu.wram[0x1000 * mmu.wram_current_bank], + 0x1000); + + /* save current bank */ + mmu.memory[0xFF70] = new; + + return; + } + + if (a == 0xFF4F) + { + /* extract VRAM index from last bit */ + mmu.vram_idx = (v & 0x01); + + /* save current VRAM bank */ + mmu.memory[0xFF4F] = mmu.vram_idx; + + return; + } + } + + /* wanna write on ROM? */ + if (a < 0x8000) + { + /* return in case of ONLY ROM */ + if (mmu.carttype == 0x00) + return; + + /* TODO - MBC strategies */ + uint8_t b = mmu.rom_current_bank; + + switch (mmu.carttype) + { + /* MBC1 */ + case 0x01: + case 0x02: + case 0x03: + + if (a >= 0x2000 && a <= 0x3FFF) + { + /* reset 5 bits */ + b = mmu.rom_current_bank & 0xE0; + + /* set them with new value */ + b |= v & 0x1F; + + /* doesn't fit on max rom number? */ + if (b > (2 << mmu.roms)) + { + /* filter result to get a value < max rom number */ + b %= (2 << mmu.roms); + } + + /* 0x00 is not valid, switch it to 0x01 */ + if (b == 0x00) + b = 0x01; + } + else if (a >= 0x4000 && a <= 0x5FFF) + { + /* ROM banking? it's about 2 higher bits */ + if (mmu.banking == 0) + { + /* reset 5 bits */ + b = mmu.rom_current_bank & 0x1F; + + /* set them with new value */ + b |= (v << 5); + + /* doesn't fit on max rom number? */ + if (b > (2 << mmu.roms)) + { + /* filter result to get a value < max rom number */ + b %= (2 << mmu.roms); + } + } + else + { + if ((0x2000 * v) < ram_sz) + { + /* save current bank */ + memcpy(&ram[0x2000 * mmu.ram_current_bank], + &mmu.memory[0xA000], 0x2000); + + mmu.ram_current_bank = v; + + /* move new ram bank */ + memcpy(&mmu.memory[0xA000], + &ram[0x2000 * mmu.ram_current_bank], + 0x2000); + } + } + } + else if (a >= 0x6000 && a <= 0x7FFF) + mmu.banking = v; + + break; + + /* MBC2 */ + case 0x05: + case 0x06: + + if (a >= 0x2000 && a <= 0x3FFF) + { + /* use lower nibble to set current bank */ + b = v & 0x0f; + + /*if (b != rom_current_bank) + memcpy(&memory[0x4000], + &cart_memory[b * 0x4000], 0x4000); + + rom_current_bank = b;*/ + } + + break; + + /* MBC3 */ + case 0x10: + case 0x13: + + if (a >= 0x0000 && a <= 0x1FFF) + { + if (v == 0x0A) + { + /* already enabled? */ + if (mmu.ram_external_enabled) + return; + + /* save current bank */ + memcpy(mmu.ram_internal, + &mmu.memory[0xA000], 0x2000); + + /* restore external ram bank */ + memcpy(&mmu.memory[0xA000], + &ram[0x2000 * mmu.ram_current_bank], + 0x2000); + + /* set external RAM eanbled flag */ + mmu.ram_external_enabled = 1; + + return; + } + + if (v == 0x00) + { + /* already disabled? */ + if (mmu.ram_external_enabled == 0) + return; + + /* save current bank */ + memcpy(&ram[0x2000 * mmu.ram_current_bank], + &mmu.memory[0xA000], 0x2000); + + /* restore external ram bank */ + memcpy(&mmu.memory[0xA000], + mmu.ram_internal, 0x2000); + + /* clear external RAM eanbled flag */ + mmu.ram_external_enabled = 0; + } + } + else if (a >= 0x2000 && a <= 0x3FFF) + { + /* set them with new value */ + b = v & 0x7F; + + /* doesn't fit on max rom number? */ + if (b > (2 << mmu.roms)) + { + /* filter result to get a value < max rom number */ + b %= (2 << mmu.roms); + } + + /* 0x00 is not valid, switch it to 0x01 */ + if (b == 0x00) + b = 0x01; + } + else if (a >= 0x4000 && a <= 0x5FFF) + { + /* 0x00 to 0x07 is referred to RAM bank */ + if (v < 0x08) + { + /* not on RTC mode anymore */ + mmu.rtc_mode = 0x00; + + if ((0x2000 * (v & 0x0f)) < ram_sz) + { + /* save current bank */ + memcpy(&ram[0x2000 * mmu.ram_current_bank], + &mmu.memory[0xA000], 0x2000); + + mmu.ram_current_bank = v & 0x0f; + + /* move new ram bank */ + memcpy(&mmu.memory[0xA000], + &ram[0x2000 * mmu.ram_current_bank], + 0x2000); + } + } + else if (v < 0x0d) + { + /* from 0x08 to 0x0C trigger RTC mode */ + mmu.rtc_mode = v; + } + + } + else if (a >= 0x6000 && a <= 0x7FFF) + { + /* latch clock data. move clock data to RTC registers */ + time(&mmu.rtc_latch_time); + } + + + break; + + /* MBC5 */ + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + + if (a >= 0x0000 && a <= 0x1FFF) + { + if (v == 0x0A) + { + /* we got external RAM? some stupid game try */ + /* to access it despite it doesn't have it */ + if (ram_sz == 0) + return; + + /* already enabled? */ + if (mmu.ram_external_enabled) + return; + + /* save current bank */ + memcpy(mmu.ram_internal, + &mmu.memory[0xA000], 0x2000); + + /* restore external ram bank */ + memcpy(&mmu.memory[0xA000], + &ram[0x2000 * mmu.ram_current_bank], + 0x2000); + + /* set external RAM eanbled flag */ + mmu.ram_external_enabled = 1; + + return; + } + + if (v == 0x00) + { + /* we got external RAM? some stpd game try to do shit */ + if (ram_sz == 0) + return; + + /* already disabled? */ + if (mmu.ram_external_enabled == 0) + return; + + /* save current bank */ + memcpy(&ram[0x2000 * mmu.ram_current_bank], + &mmu.memory[0xA000], 0x2000); + + /* restore external ram bank */ + memcpy(&mmu.memory[0xA000], + mmu.ram_internal, 0x2000); + + /* clear external RAM eanbled flag */ + mmu.ram_external_enabled = 0; + } + } + if (a >= 0x2000 && a <= 0x2FFF) + { + /* set them with new value */ + b = (mmu.rom_current_bank & 0xFF00) | v; + + /* doesn't fit on max rom number? */ + if (b > (2 << mmu.roms)) + { + /* filter result to get a value < max rom number */ + b %= (2 << mmu.roms); + } + } + else if (a >= 0x3000 && a <= 0x3FFF) + { + /* set them with new value */ + b = (mmu.rom_current_bank & 0x00FF) | ((v & 0x01) << 8); + + /* doesn't fit on max rom number? */ + if (b > (2 << mmu.roms)) + { + /* filter result to get a value < max rom number */ + b %= (2 << mmu.roms); + } + } + else if (a >= 0x4000 && a <= 0x5FFF) + { + uint8_t mask = 0x0F; + + if (global_rumble) + { + mask = 0x07; + + if (mmu_rumble_cb) + (*mmu_rumble_cb) ((v & 0x08) ? 1 : 0); + + /* check if we want to appizz the motor */ +/* if (v & 0x08) + printf("APPIZZ MOTOR\n"); + else + printf("SPEGN MOTOR\n");*/ + } + + if ((0x2000 * (v & mask)) < ram_sz) + { + /* is externa RAM enabled? */ + if (!mmu.ram_external_enabled) + break; + + /* wanna switch on the same bank? =\ just discard it */ + if ((v & 0x0f) == mmu.ram_current_bank) + break; + + /* save current bank */ + memcpy(&ram[0x2000 * mmu.ram_current_bank], + &mmu.memory[0xA000], 0x2000); + + mmu.ram_current_bank = (v & 0x0f); + + /* move new ram bank */ + memcpy(&mmu.memory[0xA000], + &ram[0x2000 * mmu.ram_current_bank], + 0x2000); + } + } + + break; + + } + + /* need to switch? */ + if (b != mmu.rom_current_bank) + { + /* copy from cartridge rom to GB switchable bank area */ + memcpy(&mmu.memory[0x4000], &cart_memory[b * 0x4000], 0x4000); + + /* save new current bank */ + mmu.rom_current_bank = b; + + /* re-apply cheats */ +// mmu_apply_gg(); + } + + return; + } + + if (a >= 0xE000) + { + /* changes on sound registers? */ + if (a >= 0xFF10 && a <= 0xFF3F) + { + /* set memory */ + sound_write_reg(a, v); + + return; + } + + /* mirror area */ + if (a >= 0xE000 && a <= 0xFDFF) + { + mmu.memory[a - 0x2000] = v; + return; + } + + /* TODO - put them all */ + switch(a) + { + case 0xFF01: + case 0xFF02: + serial_write_reg(a, v); + return; + case 0xFF04 ... 0xFF07: + timer_write_reg(a, v); + return; + } + + /* LCD turned on/off? */ + if (a == 0xFF40) + { + if ((v ^ mmu.memory[0xFF40]) & 0x80) + gpu_toggle(v); + } + + /* only 5 high bits are writable */ + if (a == 0xFF41) + { + mmu.memory[a] = (mmu.memory[a] & 0x07) | (v & 0xf8); + return; + } + + /* palette update */ + if ((a >= 0xFF47 && a <= 0xFF49) || + (a >= 0xFF68 && a <= 0xFF6B)) + gpu_write_reg(a, v); + + /* CGB only registers */ + if (global_cgb) + { + switch (a) + { + case 0xFF4D: + + /* wanna switch speed? */ + if (v & 0x01) + { + global_cpu_double_speed ^= 0x01; + + /* update new clock */ + // cycles_clock = 4194304 << global_double_speed; + cycles_set_speed(1); + sound_set_speed(1); + gpu_set_speed(1); + + /* save into memory i'm working at double speed */ + if (global_cpu_double_speed) + mmu.memory[a] = 0x80; + else + mmu.memory[a] = 0x00; + } + + return; + + case 0xFF52: + + /* high byte of HDMA source address */ + mmu.hdma_src_address &= 0xff00; + + /* lower 4 bits are ignored */ + mmu.hdma_src_address |= (v & 0xf0); + + break; + + case 0xFF51: + + /* low byte of HDMA source address */ + mmu.hdma_src_address &= 0x00ff; + + /* highet 3 bits are ignored (always 100 binary) */ + mmu.hdma_src_address |= (v << 8); + + break; + + case 0xFF54: + + /* high byte of HDMA source address */ + mmu.hdma_dst_address &= 0xff00; + + /* lower 4 bits are ignored */ + mmu.hdma_dst_address |= (v & 0xf0); + + break; + + case 0xFF53: + + /* low byte of HDMA source address */ + mmu.hdma_dst_address &= 0x00ff; + + /* highet 3 bits are ignored (always 100 binary) */ + mmu.hdma_dst_address |= ((v & 0x1f) | 0x80) << 8; + + break; + + case 0xFF55: + + /* wanna stop HBLANK transfer? a zero on 7th bit will do */ + if ((v & 0x80) == 0 && + mmu.hdma_transfer_mode == 0x01 && + mmu.hdma_to_transfer) + { + mmu.hdma_to_transfer = 0x00; + mmu.hdma_transfer_mode = 0x00; + + return; + } + + /* general (0) or hblank (1) ? */ + mmu.hdma_transfer_mode = ((v & 0x80) ? 1 : 0); + + /* calc how many bytes gotta be transferred */ + uint16_t to_transfer = ((v & 0x7f) + 1) * 0x10; + + /* general must be done immediately */ + if (mmu.hdma_transfer_mode == 0) + { + /* copy right now */ + if (mmu.vram_idx) + memcpy(mmu_addr_vram1() + + (mmu.hdma_dst_address - 0x8000), + &mmu.memory[mmu.hdma_src_address], + to_transfer); + else + memcpy(mmu_addr_vram0() + + (mmu.hdma_dst_address - 0x8000), + &mmu.memory[mmu.hdma_src_address], + to_transfer); + + /* reset to_transfer var */ + mmu.hdma_to_transfer = 0; + + /* move forward src and dst addresses =| */ + mmu.hdma_src_address += to_transfer; + mmu.hdma_dst_address += to_transfer; + } + else + { + mmu.hdma_to_transfer = to_transfer; + + /* check if we're already into hblank phase */ + cycles_hdma(); + } + + break; + } + } + + /* finally set memory byte with data */ + mmu.memory[a] = v; + + /* DMA access */ + if (a == 0xFF46) + { + /* calc source address */ + mmu.dma_address = v * 256; + + /* initialize counter, DMA needs 672 ticks */ + mmu.dma_next = cycles.cnt + 4; // 168 / 2; + } + } + else + mmu.memory[a] = v; +} + +/* write 16 bit block on a memory address */ +void mmu_write_16(uint16_t a, uint16_t v) +{ + mmu.memory[a] = (uint8_t) (v & 0x00ff); + mmu.memory[a + 1] = (uint8_t) (v >> 8); + + /* 16 bit write = +8 cycles */ + cycles_step(); + cycles_step(); +} + + +/* write 16 bit block on a memory address (no cycles affected) */ +void mmu_write_no_cyc(uint16_t a, uint8_t v) +{ + mmu.memory[a] = v; +} + + + + + diff --git a/waterbox/pizza/lib/mmu.h b/waterbox/pizza/lib/mmu.h new file mode 100644 index 0000000000..e3e3fa2f2a --- /dev/null +++ b/waterbox/pizza/lib/mmu.h @@ -0,0 +1,152 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __MMU_HDR__ +#define __MMU_HDR__ + +#include +#include +#include + +typedef struct mmu_gamegenie_s { + + /* data necessary */ + uint16_t address; + uint8_t old_value; + uint8_t new_value; + +} mmu_gamegenie_t; + +typedef struct mmu_gameshark_s { + + /* data necessary */ + uint16_t address; + uint8_t ram_bank; + uint8_t new_value; + +} mmu_gameshark_t; + +#define MMU_GAMEGENIE_MAX 4 +#define MMU_GAMESHARK_MAX 32 + +typedef struct mmu_s { + + /* main 64K of memory */ + uint8_t memory[65536]; + + /* vram in standby */ + uint8_t vram0[0x2000]; + uint8_t vram1[0x2000]; + + /* vram current idx */ + uint8_t vram_idx; + uint8_t spare; + uint16_t spare2; + + /* internal RAM */ + uint8_t ram_internal[0x2000]; + uint8_t ram_external_enabled; + uint8_t ram_current_bank; + + /* cartridge type */ + uint8_t carttype; + + /* number of switchable roms */ + uint8_t roms; + + /* current ROM bank */ + uint8_t rom_current_bank; + + /* type of banking */ + uint8_t banking; + + /* working RAM (only CGB) */ + uint8_t wram[0x8000]; + + /* current WRAM bank (only CGB) */ + uint8_t wram_current_bank; + uint8_t spare3; + uint16_t spare4; + + /* DMA transfer stuff */ + uint_fast16_t dma_address; + uint_fast16_t dma_cycles; + + /* HDMA transfer stuff */ + uint16_t hdma_src_address; + uint16_t hdma_dst_address; + uint16_t hdma_to_transfer; + uint8_t hdma_transfer_mode; + uint8_t hdma_current_line; + + /* RTC stuff */ + uint8_t rtc_mode; + uint8_t spare5; + uint16_t spare6; + time_t rtc_time; + time_t rtc_latch_time; + + /* Gamegenie */ + uint8_t gg_count; + mmu_gamegenie_t gg_array[MMU_GAMEGENIE_MAX]; + + /* Gameshark */ + uint8_t gs_count; + mmu_gameshark_t gs_array[MMU_GAMESHARK_MAX]; + + uint_fast32_t dma_next; + uint_fast32_t spare8; + +} mmu_t; + +extern mmu_t mmu; + +/* callback function */ +typedef void (*mmu_rumble_cb_t) (uint8_t onoff); + +/* functions prototypes */ +void *mmu_addr(uint16_t a); +void *mmu_addr_vram0(); +void *mmu_addr_vram1(); +void mmu_apply_gg(); +void mmu_apply_gs(); +void mmu_dump_all(); +void mmu_init(uint8_t c, uint8_t rn); +void mmu_init_ram(uint32_t c); +void mmu_load(uint8_t *data, size_t sz, uint16_t a); +void mmu_load_cartridge(uint8_t *data, size_t sz); +void mmu_move(uint16_t d, uint16_t s); +uint8_t mmu_read_no_cyc(uint16_t a); +uint8_t mmu_read(uint16_t a); +unsigned int mmu_read_16(uint16_t a); +void mmu_restore_ram(char *fn); +void mmu_restore_rtc(char *fn); +void mmu_restore_stat(FILE *fp); +void mmu_save_ram(char *fn); +void mmu_save_rtc(char *fn); +void mmu_save_stat(FILE *fp); +char mmu_set_cheat(char *cheat); +void mmu_set_rumble_cb(mmu_rumble_cb_t cb); +void mmu_step(); +void mmu_term(); +void mmu_write_no_cyc(uint16_t a, uint8_t v); +void mmu_write(uint16_t a, uint8_t v); +void mmu_write_16(uint16_t a, uint16_t v); + +#endif diff --git a/waterbox/pizza/lib/network.c b/waterbox/pizza/lib/network.c new file mode 100644 index 0000000000..e58aeff818 --- /dev/null +++ b/waterbox/pizza/lib/network.c @@ -0,0 +1,409 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "cycles.h" +#include "global.h" +#include "network.h" +#include "serial.h" +#include "utils.h" + + +/* network special binary semaphore */ +/* typedef struct network_sem_s { + pthread_mutex_t mutex; + pthread_cond_t cvar; + int v; +} network_sem_t; */ + +/* network sockets */ +int network_sock_broad = -1; +int network_sock_bound = -1; + +/* peer addr */ +struct sockaddr_in network_peer_addr; + +/* uuid to identify myself */ +unsigned int network_uuid; + +/* uuid to identify peer */ +unsigned int network_peer_uuid; + +/* progressive number (debug purposes) */ +uint8_t network_prog_recv = 0; +uint8_t network_prog_sent = 0; + +/* track that network is running */ +unsigned char network_running = 0; + +/* broadcast address */ +char network_broadcast_addr[16]; + +/* network thread */ +pthread_t network_thread; + +/* semaphorone */ +// network_sem_t network_sem; + +/* function to call when connected to another Pizza Boy */ +network_cb_t network_connected_cb; +network_cb_t network_disconnected_cb; + +/* timeout to declare peer disconnected */ +uint8_t network_timeout = 10; + +uint8_t prot = 0, pret = 0; + +/* prototypes */ +void network_send_data(uint8_t v, uint8_t clock, uint8_t transfer_start); +void *network_start_thread(void *args); + +/* is network running? */ +char network_is_running() +{ + return network_running; +} + +/* start network thread */ +void network_start(network_cb_t connected_cb, network_cb_t disconnected_cb, + char *broadcast_addr) +{ + /* init semaphore */ + // network_sem_init(&network_sem); + + /* reset bool */ + network_running = 0; + + /* set callback */ + network_connected_cb = connected_cb; + network_disconnected_cb = disconnected_cb; + + /* save broadcast addr */ + strncpy(network_broadcast_addr, broadcast_addr, 16); + + /* start thread! */ + pthread_create(&network_thread, NULL, network_start_thread, NULL); +} + +/* stop network thread */ +void network_stop() +{ + /* already stopped? */ + if (network_running == 0) + return; + + /* tell thread to stop */ + network_running = 0; + + /* wait for it to exit */ + pthread_join(network_thread, NULL); +} + +void *network_start_thread(void *args) +{ + utils_log("Starting network thread\n"); + + /* open socket sending broadcast messages */ + network_sock_broad = socket(AF_INET, SOCK_DGRAM, 0); + + /* exit on error */ + if (network_sock_broad < 1) + { + utils_log("Error opening broadcast socket"); + return NULL; + } + + /* open socket sending/receiving serial cable data */ + network_sock_bound = socket(AF_INET, SOCK_DGRAM, 0); + + /* exit on error */ + if (network_sock_bound < 1) + { + utils_log("Error opening serial-link socket"); + close (network_sock_broad); + return NULL; + } + + /* enable to broadcast */ + int enable=1; + setsockopt(network_sock_broad, SOL_SOCKET, SO_BROADCAST, + &enable, sizeof(enable)); + + /* prepare dest stuff */ + struct sockaddr_in broadcast_addr; + struct sockaddr_in bound_addr; + struct sockaddr_in addr_from; + socklen_t addr_from_len = sizeof(addr_from); + + memset(&broadcast_addr, 0, sizeof(broadcast_addr)); + broadcast_addr.sin_family = AF_INET; +// broadcast_addr.sin_addr.s_addr = INADDR_BROADCAST; +// inet_aton("239.255.0.37", +// (struct in_addr *) &broadcast_addr.sin_addr.s_addr); +// inet_aton("192.168.100.255", + inet_aton(network_broadcast_addr, + (struct in_addr *) &broadcast_addr.sin_addr.s_addr); + broadcast_addr.sin_port = htons(64333); + + /* setup listening socket */ + memset(&bound_addr, 0, sizeof(bound_addr)); + bound_addr.sin_family = AF_INET; + bound_addr.sin_addr.s_addr = INADDR_ANY; + bound_addr.sin_port = htons(64333); + + /* bind to selected port */ + if (bind(network_sock_bound, (struct sockaddr *) &bound_addr, + sizeof(bound_addr))) + { + utils_log("Error binding to port 64333"); + + /* close sockets and exit */ + close(network_sock_broad); + close(network_sock_bound); + + return NULL; + } + + /* assign it to our multicast group */ +/* struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr=inet_addr("239.255.0.37"); + mreq.imr_interface.s_addr=htonl(INADDR_ANY); + + if (setsockopt(network_sock_bound, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)) < 0) + { + utils_log("Error joining multicast network"); + + close(network_sock_broad); + close(network_sock_bound); + + return NULL; + }*/ + + fd_set rfds; + char buf[64]; + int ret; + ssize_t recv_ret; + struct timeval tv; + int timeouts = 4; + // unsigned int v, clock, prog; + + /* message parts */ + char msg_type; + unsigned int msg_uuid; + char msg_content[64]; + + /* generate a random uuid */ + srand(time(NULL)); + network_uuid = rand() & 0xFFFFFFFF; + + /* set callback in case of data to send */ + serial_set_send_cb(&network_send_data); + + /* declare network is running */ + network_running = 1; + + utils_log("Network thread started\n"); + + /* loop forever */ + while (network_running) + { + FD_ZERO(&rfds); + FD_SET(network_sock_bound, &rfds); + + /* wait one second */ + tv.tv_sec = 1; + tv.tv_usec = 0; + + /* one second timeout OR something received */ + ret = select(network_sock_bound + 1, &rfds, NULL, NULL, &tv); + + /* error! */ + if (ret == -1) + break; + + /* ret 0 = timeout */ + if (ret == 0) + { + if (++timeouts == 3) + { + /* build output message */ + sprintf(buf, "B%08x%s", network_uuid, global_cart_name); + + /* send broadcast message */ + sendto(network_sock_broad, buf, strlen(buf), 0, + (struct sockaddr *) &broadcast_addr, + sizeof(broadcast_addr)); + + utils_log("Sending broadcast message %s\n", buf); + + timeouts = 0; + } + + if (serial.peer_connected) + { + if (--network_timeout == 0) + { + /* notify serial module */ + serial.peer_connected = 0; + + /* stop Hard Sync mode */ + cycles_stop_hs(); + + /* notify by the cb */ + if (network_disconnected_cb) + (*network_disconnected_cb) (); + } + } + } + else + { + /* reset message content */ + bzero(buf, sizeof(buf)); + bzero(msg_content, sizeof(msg_content)); + + /* exit if an error occour */ + if ((recv_ret = recvfrom(network_sock_bound, buf, 64, 0, + (struct sockaddr *) &addr_from, + (socklen_t *) &addr_from_len)) < 1) + break; + + /* extract message type (1st byte) */ + msg_type = buf[0]; + + /* is it broadcast? */ + //if (sscanf(buf, "%c%08x%s", + // &msg_type, &msg_uuid, msg_content) == 3) + // { + /* was it send by myself? */ + // if (msg_uuid != network_uuid) + // { + + /* is it a serial data message? */ + if (msg_type == 'M') + { + network_prog_recv = (uint8_t) buf[3]; + + /* buf[1] contains value - buf[2] contains serial clock */ + /* tell serial module something has arrived */ + serial_recv_byte((uint8_t) buf[1], (uint8_t) buf[2], buf[4]); + } + else if (msg_type == 'B') + { + /* extract parts from broadcast message */ + sscanf(buf, "%c%08x%s", &msg_type, &msg_uuid, msg_content); + + /* myself? */ + if (network_uuid == msg_uuid) + continue; + + /* not the same game? */ + if (strcmp(msg_content, global_cart_name) != 0) + continue; + + /* someone is claiming is playing with the same game? */ + if (serial.peer_connected == 0) + { + /* save peer uuid */ + network_peer_uuid = msg_uuid; + + /* refresh timeout */ + network_timeout = 10; + + /* save sender */ + memcpy(&network_peer_addr, &addr_from, + sizeof(struct sockaddr_in)); + + /* just change dst port */ + network_peer_addr.sin_port = htons(64333); + + /* notify the other peer by sending a b message */ + sprintf(buf, "B%08x%s", network_uuid, + global_cart_name); + + /* send broadcast message */ + sendto(network_sock_broad, buf, strlen(buf), 0, + (struct sockaddr *) &network_peer_addr, + sizeof(network_peer_addr)); + + /* log that peer is connected */ + utils_log("Peer connected: %s\n", + inet_ntoa(network_peer_addr.sin_addr)); + + /* YEAH */ + serial.peer_connected = 1; + + /* notify by the cb */ + if (network_connected_cb) + (*network_connected_cb) (); + + /* start hard sync */ + cycles_start_hs(); + } + else + { + /* refresh timeout */ + if (network_peer_uuid == msg_uuid) + network_timeout = 10; + } + } + } + } + + /* free serial */ + serial.peer_connected = 0; + + /* stop hard sync mode */ + cycles_stop_hs(); + + /* close sockets */ + close(network_sock_broad); + close(network_sock_bound); + + return NULL; +} + +void network_send_data(uint8_t v, uint8_t clock, uint8_t transfer_start) +{ + char msg[5]; + + /* format message */ + network_prog_sent = ((network_prog_sent + 1) & 0xff); + + msg[0] = 'M'; + msg[1] = v; + msg[2] = clock; + msg[3] = network_prog_sent; + msg[4] = transfer_start; + + if (network_prog_sent != network_prog_recv && + network_prog_sent != (uint8_t) (network_prog_recv + 1)) + global_quit = 1; + + /* send */ + sendto(network_sock_bound, msg, 5, 0, + (struct sockaddr *) &network_peer_addr, sizeof(network_peer_addr)); +} diff --git a/waterbox/pizza/lib/network.h b/waterbox/pizza/lib/network.h new file mode 100644 index 0000000000..05904f9d4c --- /dev/null +++ b/waterbox/pizza/lib/network.h @@ -0,0 +1,35 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __NETWORK_HDR__ +#define __NETWORK_HDR__ + +#include + +/* callback function */ +typedef void (*network_cb_t) (); + +/* prototypes */ +char network_is_running(); +void network_start(network_cb_t connected_cb, + network_cb_t disconnected_cb, + char *broadcast_addr); +void network_stop(); + +#endif diff --git a/waterbox/pizza/lib/serial.c b/waterbox/pizza/lib/serial.c new file mode 100644 index 0000000000..a8734b4f01 --- /dev/null +++ b/waterbox/pizza/lib/serial.c @@ -0,0 +1,238 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include + +#include "cycles.h" +#include "interrupt.h" +#include "mmu.h" +#include "serial.h" +#include "utils.h" + +/* main variable */ +serial_t serial; + +/* function to call when frame is ready */ +serial_data_send_cb_t serial_data_send_cb; + +interrupts_flags_t *serial_if; + +/* mutexes for serial sync */ +pthread_cond_t serial_cond; +pthread_mutex_t serial_mutex; + +/* second message before the first was handled? */ +uint8_t serial_second_set = 0; +uint8_t serial_second_data = 0; +uint8_t serial_second_clock = 0; +uint8_t serial_second_transfer_start = 0; +uint8_t serial_waiting_data = 0; + +void serial_verify_intr() +{ + if (serial.data_recv && serial.data_sent) + { + serial.data_recv = 0; + serial.data_sent = 0; + + /* valid couple of messages for a serial interrupt? */ + if ((serial.data_recv_clock != serial.data_sent_clock) && + serial.data_recv_transfer_start && + serial.data_sent_transfer_start) + { + /* put received data into 0xFF01 (serial.data) */ + /* and notify with an interrupt */ + serial.transfer_start = 0; + serial.data = serial.data_to_recv; + + serial_if->serial_io = 1; + } + + /* a message is already on queue? */ + if (serial_second_set) + { + serial_second_set = 0; + serial.data_recv = 1; + serial.data_to_recv = serial_second_data; + serial.data_recv_clock = serial_second_clock; + serial.data_recv_transfer_start = serial_second_transfer_start; + } + } +} + +void serial_init() +{ + /* pointer to interrupt flags */ + serial_if = mmu_addr(0xFF0F); + + /* init counters */ + serial.bits_sent = 0; + + /* start as not connected */ + serial.peer_connected = 0; + + /* init semaphore for sync */ + pthread_mutex_init(&serial_mutex, NULL); + pthread_cond_init(&serial_cond, NULL); +} + +void serial_save_stat(FILE *fp) +{ + fwrite(&serial, 1, sizeof(serial_t), fp); +} + +void serial_restore_stat(FILE *fp) +{ + fread(&serial, 1, sizeof(serial_t), fp); +} + +void serial_write_reg(uint16_t a, uint8_t v) +{ + /* lock the serial */ + pthread_mutex_lock(&serial_mutex); + + switch (a) + { + case 0xFF01: + serial.data = v; goto end; + case 0xFF02: + serial.clock = v & 0x01; + serial.speed = (v & 0x02) ? 0x01 : 0x00; + serial.spare = ((v >> 2) & 0x1F); + serial.transfer_start = (v & 0x80) ? 0x01 : 0x00; + + /* reset? */ + serial.data_sent = 0; + } + + if (serial.transfer_start && + !serial.peer_connected && + serial.clock) + { + if (serial.speed) + serial.next = cycles.cnt + 8 * 8; + else + serial.next = cycles.cnt + 256 * 8; + } + +end: + /* unlock the serial */ + pthread_mutex_unlock(&serial_mutex); +} + +uint8_t serial_read_reg(uint16_t a) +{ + uint8_t v = 0xFF; + + switch (a) + { + case 0xFF01: v = serial.data; break; + case 0xFF02: v = ((serial.clock) ? 0x01 : 0x00) | + ((serial.speed) ? 0x02 : 0x00) | + (serial.spare << 2) | + ((serial.transfer_start) ? 0x80 : 0x00); + } + + return v; +} + +void serial_recv_byte(uint8_t v, uint8_t clock, uint8_t transfer_start) +{ + /* lock the serial */ + pthread_mutex_lock(&serial_mutex); + + /* second message during same span time? */ + if (serial.data_recv) + { + /* store it. handle it later */ + serial_second_set = 1; + serial_second_data = v; + serial_second_clock = clock; + serial_second_transfer_start = transfer_start; + + goto end; + } + + /* received side OK */ + serial.data_recv = 1; + serial.data_recv_clock = clock; + serial.data_to_recv = v; + serial.data_recv_transfer_start = transfer_start; + + /* notify main thread in case it's waiting */ + if (serial_waiting_data) + pthread_cond_signal(&serial_cond); + +end: + + /* unlock the serial */ + pthread_mutex_unlock(&serial_mutex); +} + +void serial_send_byte() +{ + /* lock the serial */ + pthread_mutex_lock(&serial_mutex); + + serial.data_sent = 1; + serial.data_to_send = serial.data; + serial.data_sent_clock = serial.clock; + serial.data_sent_transfer_start = serial.transfer_start; + + if (serial_data_send_cb) + (*serial_data_send_cb) (serial.data, serial.clock, + serial.transfer_start); + + /* unlock the serial */ + pthread_mutex_unlock(&serial_mutex); +} + +void serial_set_send_cb(serial_data_send_cb_t cb) +{ + serial_data_send_cb = cb; +} + +void serial_wait_data() +{ + /* lock the serial */ + pthread_mutex_lock(&serial_mutex); + + if (serial.data_sent && serial.data_recv == 0) + { + /* wait max 3 seconds */ + struct timespec wait; + + wait.tv_sec = time(NULL) + 3; + + /* this is very important to avoid EINVAL return! */ + wait.tv_nsec = 0; + + /* declare i'm waiting for data */ + serial_waiting_data = 1; + + /* notify something has arrived */ + pthread_cond_timedwait(&serial_cond, &serial_mutex, &wait); + + /* not waiting anymore */ + serial_waiting_data = 0; + } + + /* unlock the serial */ + pthread_mutex_unlock(&serial_mutex); +} diff --git a/waterbox/pizza/lib/serial.h b/waterbox/pizza/lib/serial.h new file mode 100644 index 0000000000..6ce30fbddb --- /dev/null +++ b/waterbox/pizza/lib/serial.h @@ -0,0 +1,98 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __SERIAL_HDR__ +#define __SERIAL_HDR__ + +#include +#include + +typedef struct serial_ctrl_s +{ + uint8_t clock; + uint8_t speed; + uint8_t spare; + uint8_t transfer_start; +} serial_ctrl_t; + +typedef struct serial_s { + + /* pointer to serial controller register */ + // serial_ctrl_t ctrl; + + uint8_t clock; + uint8_t speed; + uint8_t spare; + uint8_t transfer_start; + + /* pointer to FF01 data */ + uint8_t data; + + /* sent bits */ + uint8_t bits_sent; + + /* data to send */ + uint8_t data_to_send; + + /* peer clock */ + uint8_t data_to_recv; + + /* counter */ + uint_fast32_t next; + + /* peer connected? */ + uint8_t peer_connected:1; + uint8_t data_sent:1; + uint8_t data_sent_clock:1; + uint8_t data_sent_transfer_start:1; + uint8_t data_recv:1; + uint8_t data_recv_clock:1; + uint8_t data_recv_transfer_start:1; + uint8_t spare10:1; + + uint8_t spare2; + uint8_t spare3; + uint8_t spare4; + + uint_fast32_t last_send_cnt; + +} serial_t; + +extern serial_t serial; + +/* callback when receive something on serial */ +typedef void (*serial_data_send_cb_t) (uint8_t v, uint8_t clock, + uint8_t transfer_start); + +/* prototypes */ +void serial_init(); +void serial_lock(); +void serial_write_reg(uint16_t a, uint8_t v); +void serial_verify_intr(); +uint8_t serial_read_reg(uint16_t a); +void serial_recv_byte(uint8_t v, uint8_t clock, uint8_t transfer_start); +void serial_recv_clock(); +void serial_save_stat(FILE *fp); +void serial_send_byte(); +void serial_set_send_cb(serial_data_send_cb_t cb); +void serial_restore_stat(FILE *fp); +void serial_unlock(); +void serial_wait_data(); + +#endif diff --git a/waterbox/pizza/lib/sound.c b/waterbox/pizza/lib/sound.c new file mode 100644 index 0000000000..324156e3eb --- /dev/null +++ b/waterbox/pizza/lib/sound.c @@ -0,0 +1,1484 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include "cycles.h" +#include "global.h" +#include "gpu.h" +#include "mmu.h" +#include "sound.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include + +/* semaphore for audio sync */ +pthread_cond_t sound_cond; +pthread_mutex_t sound_mutex; + +/* super variable for audio controller */ +sound_t sound; + +/* global for output frequency */ +int sound_output_rate = 48000; +int sound_output_rate_fifth = 48; // 48000 / 5; + +/* internal prototypes */ +size_t sound_available_samples(); +void sound_envelope_step(); +void sound_length_ctrl_step(); +void sound_push_samples(int16_t l, int16_t r); +void sound_push_sample(int16_t s); +void sound_read_samples(int len, int16_t *buf); +void sound_rebuild_wave(); +void sound_sweep_step(); +void sound_term(); +void sound_write_wave(uint16_t a, uint8_t v); + +int sound_get_samples() +{ + return SOUND_SAMPLES; // sound_output_rate / 10; +} + +void sound_init_pointers() +{ + /* point sound structures to their memory areas */ + sound.nr10 = (nr10_t *) mmu_addr(0xFF10); + sound.nr11 = (nr11_t *) mmu_addr(0xFF11); + sound.nr12 = (nr12_t *) mmu_addr(0xFF12); + sound.nr13 = (nr13_t *) mmu_addr(0xFF13); + sound.nr14 = (nr14_t *) mmu_addr(0xFF14); + + sound.nr21 = (nr21_t *) mmu_addr(0xFF16); + sound.nr22 = (nr22_t *) mmu_addr(0xFF17); + sound.nr23 = (nr23_t *) mmu_addr(0xFF18); + sound.nr24 = (nr24_t *) mmu_addr(0xFF19); + + sound.nr30 = (nr30_t *) mmu_addr(0xFF1A); + sound.nr31 = (nr31_t *) mmu_addr(0xFF1B); + sound.nr32 = (nr32_t *) mmu_addr(0xFF1C); + sound.nr33 = (nr33_t *) mmu_addr(0xFF1D); + sound.nr34 = (nr34_t *) mmu_addr(0xFF1E); + + sound.nr41 = (nr41_t *) mmu_addr(0xFF20); + sound.nr42 = (nr42_t *) mmu_addr(0xFF21); + sound.nr43 = (nr43_t *) mmu_addr(0xFF22); + sound.nr44 = (nr44_t *) mmu_addr(0xFF23); + + sound.nr50 = mmu_addr(0xFF24); + sound.nr51 = mmu_addr(0xFF25); + sound.nr52 = mmu_addr(0xFF26); + + sound.wave_table = mmu_addr(0xFF30); +} + +/* init sound states */ +void sound_init() +{ + /* reset structure */ + bzero(&sound, sizeof(sound_t)); + + /* point sound structures to their memory areas */ + sound_init_pointers(); + + /* steps length */ + sound.step_int = 4; + sound.step_int1000 = 4000; + + /* buffer stuff */ + sound.buf_wr = 0; + sound.buf_rd = 0; + sound.buf_available = 0; + + /* init semaphore for sync */ + pthread_mutex_init(&sound_mutex, NULL); + pthread_cond_init(&sound_cond, NULL); + + /* how many cpu cycles we need to emit a 512hz clock (frame sequencer) */ + sound.fs_cycles = 4194304 / 512; + + /* how many cpu cycles to generate a single frame seq clock? */ + sound.fs_cycles_next = sound.fs_cycles; + + /* how many cpu cycles to generate a sample */ + sound.sample_cycles = (uint_fast32_t) (((double) 4194304 / + (double) sound_output_rate) * 1000); + + sound.sample_cycles_next = sound.sample_cycles / 1000; + sound.sample_cycles_next_rounded = sound.sample_cycles_next & 0xFFFFFFFC; + + /* init multiplier */ + sound.frame_multiplier = 1; + + /* no, i'm not empty */ + sound.buf_empty = 0; +} + +void sound_set_speed(char dbl) +{ + return; + + if (dbl) + { + sound.step_int = 2; + sound.step_int1000 = 2000; + } + else + { + sound.step_int = 4; + sound.step_int1000 = 4000; + } +} + +void sound_change_emulation_speed() +{ + if (global_emulation_speed == GLOBAL_EMULATION_SPEED_HALF) + sound.frame_multiplier = 2; + else if (global_emulation_speed == GLOBAL_EMULATION_SPEED_QUARTER) + sound.frame_multiplier = 4; + else + sound.frame_multiplier = 1; +} + +/* update sound internal state given CPU T-states */ +void sound_step_fs() +{ + /* rotate from 0 to 7 */ + sound.fs_cycles_idx = (sound.fs_cycles_idx + 1) & 0x07; + + /* reset fs cycles counter */ + sound.fs_cycles_next = cycles.cnt + + (sound.fs_cycles << global_cpu_double_speed); + + /* length controller works at 256hz */ + if ((sound.fs_cycles_idx & 0x01) == 0) + sound_length_ctrl_step(); + + /* sweep works at 128hz */ + if (sound.fs_cycles_idx == 2 || sound.fs_cycles_idx == 6) + sound_sweep_step(); + + /* envelope works at 64hz */ + if (sound.fs_cycles_idx == 7) + sound_envelope_step(); +} + +/* update all channels */ +void sound_step_ch1() +{ + /* recalc current samples */ + if ((sound.channel_one.duty >> sound.channel_one.duty_idx) & 0x01) + sound.channel_one.sample = sound.channel_one.volume; + else + sound.channel_one.sample = -sound.channel_one.volume; + + /* step to the next duty value */ + sound.channel_one.duty_idx = + (sound.channel_one.duty_idx + 1) & 0x07; + + /* go back */ + sound.channel_one.duty_cycles_next += sound.channel_one.duty_cycles; +} + +void sound_step_ch2() +{ + /* recalc current samples */ + if ((sound.channel_two.duty >> sound.channel_two.duty_idx) & 0x01) + sound.channel_two.sample = sound.channel_two.volume; + else + sound.channel_two.sample = -sound.channel_two.volume; + + /* step to the next duty value */ + sound.channel_two.duty_idx = + (sound.channel_two.duty_idx + 1) & 0x07; + + /* go back */ + sound.channel_two.duty_cycles_next += sound.channel_two.duty_cycles; +} + +void sound_step_ch3() +{ + /* switch to the next wave sample */ + sound.channel_three.index = (sound.channel_three.index + 1) & 0x1F; + + /* set the new current sample */ + sound.channel_three.sample = + sound.channel_three.wave[sound.channel_three.index]; + + /* reload new period */ + uint_fast16_t freq = sound.nr33->frequency_lsb | + (sound.nr34->frequency_msb << 8); + + /* qty of cpu ticks needed for a wave sample change */ + sound.channel_three.cycles = ((2048 - freq) * 2) << global_cpu_double_speed; + sound.channel_three.cycles_next += sound.channel_three.cycles; +} + +void sound_step_ch4() +{ + /* update LSFR */ + if (sound.nr43->shift < 14) + { + /* shift register one bit right */ + uint16_t s = sound.channel_four.reg >> 1; + + /* xor current register and the shifted version */ + /* and extract bit zero */ + uint16_t x = (sound.channel_four.reg ^ s) & 1; + + /* update register */ + sound.channel_four.reg = s | x << 14; + + /* if width is set... */ + if (sound.nr43->width) + sound.channel_four.reg = + (sound.channel_four.reg & 0xBF) | x << 6; + } + + /* update sample */ + if (sound.channel_four.reg & 0x01) + sound.channel_four.sample = -sound.channel_four.volume; + else + sound.channel_four.sample = sound.channel_four.volume; + + /* qty of cpu ticks needed for a wave sample change */ + sound.channel_four.cycles_next += sound.channel_four.period_lfsr; +} + +void sound_step_sample() +{ + uint_fast32_t zum = sound.sample_cycles + sound.sample_cycles_remainder; + + sound.sample_cycles_next += ((zum / 1000) << global_cpu_double_speed); + sound.sample_cycles_next_rounded = + sound.sample_cycles_next & 0xFFFFFFFC; + sound.sample_cycles_remainder = zum % 1000; + + /* update output frame counter */ + sound.frame_counter++; + + /* is it the case to push samples? */ + if (((global_emulation_speed == GLOBAL_EMULATION_SPEED_DOUBLE && + (sound.frame_counter & 0x0001) != 0) || + (global_emulation_speed == GLOBAL_EMULATION_SPEED_4X && + (sound.frame_counter & 0x0003) != 0))) + return; + + /* DAC turned off? */ + if (sound.nr30->dac == 0 && + sound.channel_one.active == 0 && + sound.channel_two.active == 0 && + sound.channel_four.active == 0) + { + sound_push_samples(0, 0); + return; + } + + int16_t sample_left = 0; + int16_t sample_right = 0; + int16_t sample = 0; + + /* time to generate a sample! sum all the fields */ + if (sound.channel_one.active) // && sound.channel_one.sample) + { + /* to the right? */ + if (sound.nr51->ch1_to_so1) + sample_right += sound.channel_one.sample; + + /* to the left? */ + if (sound.nr51->ch1_to_so2) + sample_left += sound.channel_one.sample; + } + + if (sound.channel_two.active) // && sound.channel_two.sample) + { + /* to the right? */ + if (sound.nr51->ch2_to_so1) + sample_right += sound.channel_two.sample; + + /* to the left? */ + if (sound.nr51->ch2_to_so2) + sample_left += sound.channel_two.sample; + } + + if (sound.channel_three.active) + { + uint8_t shift = (sound.nr32->volume_code == 0 ? + 4 : sound.nr32->volume_code - 1); + + /* volume is zero in any case */ + if (shift == 4) + sample = 0; + else + { + /* apply volume change */ + uint8_t idx = sound.channel_three.index; + uint16_t s; + + /* extract current sample */ + if ((idx & 0x01) == 0) + s = (sound.wave_table[idx >> 1] & 0xf0) >> 4; + else + s = sound.wave_table[idx >> 1] & 0x0f; + + /* transform it into signed 16 bit sample */ + sample = ((s * 0x222) >> shift); + } + + + /* not silence? */ + if (sample != 0) + { + /* to the right? */ + if (sound.nr51->ch3_to_so1) + sample_right += sample; + + /* to the left? */ + if (sound.nr51->ch3_to_so2) + sample_left += sample; + } + } + + if (sound.channel_four.active) + { + /* to the right? */ + if (sound.nr51->ch4_to_so1) + sample_right += sound.channel_four.sample; + + /* to the left? */ + if (sound.nr51->ch4_to_so2) + sample_left += sound.channel_four.sample; + } + + int i; + + for (i=0; ilength_enable, + &sound.channel_one.length, + &sound.channel_one.active); + + sound_length_ctrl_step_ch(sound.nr24->length_enable, + &sound.channel_two.length, + &sound.channel_two.active); + + sound_length_ctrl_step_ch(sound.nr34->length_enable, + &sound.channel_three.length, + &sound.channel_three.active); + + sound_length_ctrl_step_ch(sound.nr44->length_enable, + &sound.channel_four.length, + &sound.channel_four.active); +} + +void sound_read_buffer(void *userdata, uint8_t *stream, int snd_len) +{ + /* requester snd_len is expressed in byte, */ + /* so divided by to to obtain wanted 16 bits samples */ + sound_read_samples(snd_len / 2, (int16_t *) stream); +} + +void sound_push_samples(int16_t l, int16_t r) +{ + /* store them in tmp buffer */ + sound.buf_tmp[sound.buf_tmp_wr++] = l; + sound.buf_tmp[sound.buf_tmp_wr++] = r; + + if (sound.buf_tmp_wr == SOUND_BUF_TMP_SZ) + { + unsigned int i; + + /* since we're accessing a shared buffer, lock it */ + pthread_mutex_lock(&sound_mutex); + + /* put them in circular shared buffer */ + for (i=0; i sound.buf_wr) + return sound.buf_wr + SOUND_BUF_SZ - sound.buf_rd; + + return sound.buf_wr - sound.buf_rd; +} + +/* read a block of data from circular buffer */ +void sound_read_samples(int to_read, int16_t *buf) +{ + /* lock the buffer */ + pthread_mutex_lock(&sound_mutex); + + /* am i shutting down? exit */ + if (global_quit) + { + pthread_mutex_unlock(&sound_mutex); + return; + } + + /* not enough samples? read what we got */ + if (sound.buf_available < to_read) + { + /* stop until we got enough samples */ + sound.buf_empty = 1; + + while (sound.buf_empty && !global_quit) + pthread_cond_wait(&sound_cond, &sound_mutex); + } + + if (sound.buf_rd + to_read >= SOUND_BUF_SZ) + { + /* overlaps the end of the buffer? copy in 2 phases */ + size_t first_block = SOUND_BUF_SZ - sound.buf_rd; + + memcpy(buf, &sound.buf[sound.buf_rd], first_block * 2); + + memcpy(&buf[first_block], sound.buf, (to_read - first_block) * 2); + + /* set the new read index */ + sound.buf_rd = to_read - first_block; + } + else + { + /* a single memcpy is enough */ + memcpy(buf, &sound.buf[sound.buf_rd], to_read * 2); + + /* update read index */ + sound.buf_rd += to_read; + } + + /* update avaiable samples */ + sound.buf_available -= to_read; + + /* unlock the buffer */ + pthread_mutex_unlock(&sound_mutex); +} + +/* calc the new frequency by sweep module */ +uint_fast32_t sound_sweep_calc() +{ + uint_fast32_t new_freq; + + /* time to update frequency */ + uint_fast32_t diff = + sound.channel_one.sweep_shadow_frequency >> + sound.nr10->shift; + + /* the calculated diff must be summed or subtracted to frequency */ + if (sound.nr10->negate) + { + new_freq = sound.channel_one.sweep_shadow_frequency - diff; + sound.channel_one.sweep_neg = 1; + } + else + new_freq = sound.channel_one.sweep_shadow_frequency + diff; + + /* if freq > 2047, turn off the channel */ + if (new_freq > 2047) + sound.channel_one.active = 0; + + return new_freq; +} + +/* set channel one new frequency */ +void sound_set_frequency(uint_fast32_t new_freq) +{ + /* too high? */ + if (new_freq > 2047) + { + sound.channel_one.active = 0; + return; + } + + /* update with the new frequency */ + sound.channel_one.frequency = new_freq; + + /* update them also into memory */ + sound.nr13->frequency_lsb = (uint8_t) (new_freq & 0x000000ff); + sound.nr14->frequency_msb = (uint8_t) ((new_freq >> 8) & 0x00000007); + + /* update the duty cycles */ + sound.channel_one.duty_cycles = + ((2048 - new_freq) * 4) << global_cpu_double_speed; + + /* and reset them */ + sound.channel_one.duty_cycles_next = + cycles.cnt + sound.channel_one.duty_cycles; +} + +/* step of frequency sweep at 128hz */ +void sound_sweep_step() +{ + uint_fast32_t new_freq; + + if (sound.channel_one.active && + sound.channel_one.sweep_active) + { + /* make it rotate from 0 to 8 */ + sound.channel_one.sweep_cnt++; + + /* enough cycles? */ + if (sound.channel_one.sweep_cnt == sound.channel_one.sweep_next) + { + /* reload the next step - 0 is treated as 8 */ + sound.channel_one.sweep_next = + sound.nr10->sweep_period ? + sound.nr10->sweep_period : 8; + + /* reset sweep counter */ + sound.channel_one.sweep_cnt = 0; + + /* period must be > 0 if new freq gotta be updated */ + if (sound.nr10->sweep_period == 0) + return; + + /* calc new frequency */ + new_freq = sound_sweep_calc(); + + /* set it only if < 2048 and shift != 0 */ + if (sound.nr10->shift && + new_freq < 2048) + { + /* copy new_freq into shadow register */ + sound.channel_one.sweep_shadow_frequency = new_freq; + + /* update all the stuff related to new frequency */ + sound_set_frequency(new_freq); + + /* update freq again (but only in shadow register) */ + sound_sweep_calc(); + } + } + } +} + +/* step of envelope at 64hz */ +void sound_envelope_step() +{ + if (sound.channel_one.active && sound.nr12->period) + { + /* update counter */ + sound.channel_one.envelope_cnt++; + + /* if counter reaches period, update volume */ + if (sound.channel_one.envelope_cnt == sound.nr12->period) + { + if (sound.nr12->add) + { + if (sound.channel_one.volume < (14 * 0x111)) + sound.channel_one.volume += 0x111; + } + else + { + if (sound.channel_one.volume >= 0x111) + sound.channel_one.volume -= 0x111; + } + + /* reset counter */ + sound.channel_one.envelope_cnt = 0; + } + } + + if (sound.channel_two.active && sound.nr22->period) + { + /* update counter */ + sound.channel_two.envelope_cnt++; + + /* if counter reaches period, update volume */ + if (sound.channel_two.envelope_cnt == sound.nr22->period) + { + if (sound.nr22->add) + { + if (sound.channel_two.volume < (14 * 0x111)) + sound.channel_two.volume += 0x111; + } + else + { + if (sound.channel_two.volume >= 0x111) + sound.channel_two.volume -= 0x111; + } + + /* reset counter */ + sound.channel_two.envelope_cnt = 0; + } + } + + if (sound.channel_four.active && sound.nr42->period) + { + /* update counter */ + sound.channel_four.envelope_cnt++; + + /* if counter reaches period, update volume */ + if (sound.channel_four.envelope_cnt == sound.nr42->period) + { + if (sound.nr42->add) + { + if (sound.channel_four.volume < (14 * 0x111)) + sound.channel_four.volume += 0x111; + } + else + { + if (sound.channel_four.volume > 0x111) + sound.channel_four.volume -= 0x111; + } + + /* reset counter */ + sound.channel_four.envelope_cnt = 0; + } + } +} + +uint8_t sound_read_reg(uint16_t a, uint8_t v) +{ + switch (a) + { + /* NR1X */ + case 0xFF10: return v | 0x80; + case 0xFF11: return v | 0x3F; + case 0xFF12: return v; + case 0xFF13: return v | 0xFF; + case 0xFF14: return v | 0xBF; + /* NR2X */ + case 0xFF15: return v | 0xFF; + case 0xFF16: return v | 0x3F; + case 0xFF17: return v; + case 0xFF18: return v | 0xFF; + case 0xFF19: return v | 0xBF; + /* NR3X */ + case 0xFF1A: return v | 0x7F; + case 0xFF1B: return v | 0xFF; + case 0xFF1C: return v | 0x9F; + case 0xFF1D: return v | 0xFF; + case 0xFF1E: return v | 0xBF; + /* NR4X */ + case 0xFF1F: return v | 0xFF; + case 0xFF20: return v | 0xFF; + case 0xFF21: return v; + case 0xFF22: return v; + case 0xFF23: return v | 0xBF; + /* NR5X */ + case 0xFF24: return v; + case 0xFF25: return v; + case 0xFF26: + if (sound.nr52->power) + return 0xf0 | + sound.channel_one.active | + (sound.channel_two.active << 1) | + (sound.channel_three.active << 2) | + (sound.channel_four.active << 3); + else + return 0x70; + case 0xFF27: + case 0xFF28: + case 0xFF29: + case 0xFF2A: + case 0xFF2B: + case 0xFF2C: + case 0xFF2D: + case 0xFF2E: + case 0xFF2F: return 0xFF; + case 0xFF30: + case 0xFF31: + case 0xFF32: + case 0xFF33: + case 0xFF34: + case 0xFF35: + case 0xFF36: + case 0xFF37: + case 0xFF38: + case 0xFF39: + case 0xFF3A: + case 0xFF3B: + case 0xFF3C: + case 0xFF3D: + case 0xFF3E: + case 0xFF3F: + if (sound.channel_three.active) + { +/* if (!global_cgb && sound.channel_three.ram_access != 0) + { + printf("RAM ACCESSO NON ZERO %u - CNT %d NEXT %d\n", + sound.channel_three.ram_access, cycles.cnt, sound.channel_three.ram_access_next); + return 0xFF; + }*/ + if (!global_cgb && + cycles.cnt < sound.channel_three.ram_access_next) + return 0xFF; + + return sound.wave_table[sound.channel_three.index >> 1]; + } + + default: return v; + } +} + +void sound_set_output_rate(int freq) +{ + sound_output_rate = freq; + sound_output_rate_fifth = freq / 5; + + double cpu_base_freq = 4194304; + + /* calc cycles needed to generate a sample */ + sound.sample_cycles = (uint_fast32_t) (((double) cpu_base_freq / + (double) sound_output_rate) * 1000); + + sound.sample_cycles_next = sound.sample_cycles / 1000; + sound.sample_cycles_next_rounded = sound.sample_cycles_next & 0xFFFFFFFC; +} + +void sound_write_reg(uint16_t a, uint8_t v) +{ + /* when turned off, only write to NR52 (0xFF26) is legit */ + if (!sound.nr52->power && a != 0xFF26) + { + /* CGB mode doesnt allow any write on register during power off */ + if (global_cgb) + return; + + /* in DMG mode, update length is legit while no power */ + switch (a) + { + case 0xFF11: sound.channel_one.length = 64 - (v & 0x3f); return; + case 0xFF16: sound.channel_two.length = 64 - (v & 0x3f); return; + case 0xFF1B: sound.channel_three.length = 256 - v; return; + case 0xFF20: sound.channel_four.length = 64 - (v & 0x3f); return; + default: return; + } + } + + /* wave write */ + if (a >= 0xFF30 && a <= 0xFF3F) + return sound_write_wave(a, v); + + /* save old value */ + uint8_t old = *((uint8_t *) mmu_addr(a)); + + /* confirm write on memory */ + *((uint8_t *) mmu_addr(a)) = v; + + switch (a) + { + case 0xFF10: + + if (!sound.nr10->negate && sound.channel_one.sweep_neg) + sound.channel_one.active = 0; + + break; + + case 0xFF11: + + /* set length as 64 - length_load */ + sound.channel_one.length = 64 - sound.nr11->length_load; + + /* update duty type */ + switch (sound.nr11->duty) + { + /* 12.5 % */ + case 0x00: sound.channel_one.duty = 0x80; + break; + + /* 25% */ + case 0x01: sound.channel_one.duty = 0x81; + break; + + /* 50% */ + case 0x02: sound.channel_one.duty = 0xE1; + break; + + /* 75% */ + case 0x03: sound.channel_one.duty = 0x7E; + break; + } + + break; + + case 0xFF12: + + /* volume 0 = turn off the DAC = turn off channeru */ + if (sound.nr12->volume == 0 && + sound.nr12->add == 0) + sound.channel_one.active = 0; + + break; + + case 0xFF13: + + /* update frequncy */ + sound.channel_one.frequency = sound.nr13->frequency_lsb | + (sound.nr14->frequency_msb << 8); + + /* update duty cycles */ + sound.channel_one.duty_cycles = + ((2048 - sound.channel_one.frequency) * 4) + << global_cpu_double_speed; + + break; + + case 0xFF14: + + /* length counter turned on */ + if (sound.nr14->length_enable) + { + nr14_t *old_nr14 = (nr14_t *) &old; + + /* give an extra length clock if */ + /* 1) we switched from off to on the len counter */ + /* 2) we are in the first half of len clock */ + /* 3) actual length is not zero */ + if ((old_nr14->length_enable == 0) && + ((sound.fs_cycles_idx & 0x01) == 0x00) && + (sound.channel_one.length != 0)) + sound_length_ctrl_step_ch(sound.nr14->length_enable, + &sound.channel_one.length, + &sound.channel_one.active); + } + + /* always update frequency, even if it's not a trigger */ + sound.channel_one.frequency = sound.nr13->frequency_lsb | + (sound.nr14->frequency_msb << 8); + + /* qty of cpu ticks needed for a duty change */ + /* (1/8 of wave cycle) */ + sound.channel_one.duty_cycles = + ((2048 - sound.channel_one.frequency) * 4) + << global_cpu_double_speed; + + if (v & 0x80) + { + /* if we switch from OFF to ON, reset duty idx */ + if (sound.channel_two.active == 0) + sound.channel_two.duty_idx = 0; + + /* setting internal modules data with stuff taken from memory */ + sound.channel_one.active = 1; + sound.channel_one.duty_cycles_next = + cycles.cnt + sound.channel_one.duty_cycles; + + /* set the 8 phase of a duty cycle by setting 8 bits */ + switch (sound.nr11->duty) + { + /* 12.5 % */ + case 0x00: sound.channel_one.duty = 0x80; + break; + + /* 25% */ + case 0x01: sound.channel_one.duty = 0x81; + break; + + /* 50% */ + case 0x02: sound.channel_one.duty = 0xE1; + break; + + /* 75% */ + case 0x03: sound.channel_one.duty = 0x7E; + break; + } + + /* calc length */ + if (sound.channel_one.length == 0) + sound.channel_one.length = 64; + + /* base volume */ + sound.channel_one.volume = + sound.nr12->volume * 0x111; + + /* reset envelope counter */ + sound.channel_one.envelope_cnt = 0; + + /* save current freq into sweep shadow register */ + sound.channel_one.sweep_shadow_frequency = + sound.channel_one.frequency; + + /* reset sweep timer */ + sound.channel_one.sweep_cnt = 0; + + /* reset sweep neg bool */ + sound.channel_one.sweep_neg = 0; + + /* reload the next step */ + sound.channel_one.sweep_next = sound.nr10->sweep_period ? + sound.nr10->sweep_period : 8; + + /* set sweep as active if period != 0 or shift != 0 */ + if (sound.nr10->sweep_period != 0 || + sound.nr10->shift != 0) + sound.channel_one.sweep_active = 1; + else + sound.channel_one.sweep_active = 0; + + /* if shift is != 0, calc the new frequency */ + if (sound.nr10->shift != 0) + { + uint32_t new_freq = sound_sweep_calc(); + + /* update all the stuff related to new frequency */ + sound_set_frequency(new_freq); + } + + /* if DAC is off, turn off the channel */ + if (sound.nr12->add == 0 && + sound.nr12->volume == 0) + sound.channel_one.active = 0; + + /* extra length clock if length == 64 */ + /* and FS is in the fist half */ + if ((sound.fs_cycles_idx & 0x01) == 0x00 && + sound.channel_one.length == 64) + sound_length_ctrl_step_ch(sound.nr14->length_enable, + &sound.channel_one.length, + &sound.channel_one.active); + } + + + break; + + case 0xFF16: + + sound.channel_two.length = 64 - sound.nr21->length_load; + + /* update duty type */ + switch (sound.nr21->duty) + { + /* 12.5 % */ + case 0x00: sound.channel_two.duty = 0x80; + break; + + /* 25% */ + case 0x01: sound.channel_two.duty = 0x81; + break; + + /* 50% */ + case 0x02: sound.channel_two.duty = 0xE1; + break; + + /* 75% */ + case 0x03: sound.channel_two.duty = 0x7E; + break; + } + + break; + + case 0xFF17: + + /* volume 0 = turn off the DAC = turn off channeru */ + if (sound.nr22->volume == 0 && + sound.nr22->add == 0) + sound.channel_two.active = 0; + + break; + + case 0xFF18: + + /* update frequncy */ + sound.channel_two.frequency = (sound.nr23->frequency_lsb | + (sound.nr24->frequency_msb << 8)); + + /* update duty cycles */ + sound.channel_two.duty_cycles = + ((2048 - sound.channel_two.frequency) * 4) + << global_cpu_double_speed; + + break; + + case 0xFF19: + + /* length counter turned on */ + if (sound.nr24->length_enable) + { + nr24_t *old_nr24 = (nr24_t *) &old; + + /* give an extra length clock if */ + /* 1) we switched from off to on the len counter */ + /* 2) we are in the first half of len clock */ + /* 3) actual length is not zero */ + if ((old_nr24->length_enable == 0) && + ((sound.fs_cycles_idx & 0x01) == 0x00) && + (sound.channel_two.length != 0)) + sound_length_ctrl_step_ch(sound.nr24->length_enable, + &sound.channel_two.length, + &sound.channel_two.active); + } + + /* always update frequency, even if it's not a trigger */ + sound.channel_two.frequency = sound.nr23->frequency_lsb | + (sound.nr24->frequency_msb << 8); + + /* qty of cpu ticks needed for a duty change */ + /* (1/8 of wave cycle) */ + sound.channel_two.duty_cycles = + ((2048 - sound.channel_two.frequency) * 4) + << global_cpu_double_speed; + + if (v & 0x80) + { + /* if we switch from OFF to ON, reset duty idx */ + if (sound.channel_two.active == 0) + sound.channel_two.duty_idx = 0; + + /* setting internal modules data with stuff taken from memory */ + sound.channel_two.active = 1; + sound.channel_two.duty_cycles_next = + cycles.cnt + sound.channel_two.duty_cycles; + + /* set the 8 phase of a duty cycle by setting 8 bits */ + switch (sound.nr21->duty) + { + /* 12.5 % */ + case 0x00: sound.channel_two.duty = 0x80; + break; + + /* 25% */ + case 0x01: sound.channel_two.duty = 0x81; + break; + + /* 50% */ + case 0x02: sound.channel_two.duty = 0xE1; + break; + + /* 75% */ + case 0x03: sound.channel_two.duty = 0x7E; + break; + } + + /* calc length */ + if (sound.channel_two.length == 0) + sound.channel_two.length = 64; + + /* base volume */ + sound.channel_two.volume = + sound.nr22->volume * 0x111; + + /* reset envelope counter */ + sound.channel_two.envelope_cnt = 0; + + /* if DAC is off, turn off the channel */ + if (sound.nr22->add == 0 && + sound.nr22->volume == 0) + sound.channel_two.active = 0; + + /* extra length clock if length == 64 */ + /* and FS is in the fist half */ + if ((sound.fs_cycles_idx & 0x01) == 0x00 && + sound.channel_two.length == 64) + sound_length_ctrl_step_ch(sound.nr24->length_enable, + &sound.channel_two.length, + &sound.channel_two.active); + } + + break; + + case 0xFF1A: + + /* if DAC is off, disable the channel */ + if (sound.nr30->dac == 0) + sound.channel_three.active = 0; + + break; + + case 0xFF1B: + + sound.channel_three.length = + 256 - sound.nr31->length_load; + + break; + + case 0xFF1C: + + break; + + case 0xFF1E: + + /* length counter turned on */ + if (sound.nr34->length_enable) + { + nr34_t *old_nr34 = (nr34_t *) &old; + + /* give an extra length clock if */ + /* 1) we switched from off to on the len counter */ + /* 2) we are in the first half of len clock */ + /* 3) actual length is not zero */ + if ((old_nr34->length_enable == 0) && + ((sound.fs_cycles_idx & 0x01) == 0x00) && + (sound.channel_three.length != 0)) + sound_length_ctrl_step_ch(sound.nr34->length_enable, + &sound.channel_three.length, + &sound.channel_three.active); + } + + if (v & 0x80) + { + uint16_t freq = sound.nr33->frequency_lsb | + (sound.nr34->frequency_msb << 8); + + /* setting internal modules data with stuff taken from memory */ + sound.channel_three.active = 1; + + uint_fast32_t old_cycles = sound.channel_three.cycles; + + /* qty of cpu ticks needed for a wave sample change */ + sound.channel_three.cycles = + (((2048 - freq) * 2) + 6) << global_cpu_double_speed; + + + /* treat obscure behaviours.... */ + if (!global_cgb && + cycles.cnt + 8 == sound.channel_three.cycles_next + + sound.channel_three.cycles - + old_cycles) + { + uint8_t next = + ((sound.channel_three.index + 1) & 0x1F) >> 1; + + if (next < 4) + sound.wave_table[0] = sound.wave_table[next]; + else + memcpy(sound.wave_table, + &sound.wave_table[next & 0xfc], 4); + } + + /* init wave table index */ + sound.channel_three.index = 0; + sound.channel_three.cycles_next = + cycles.cnt + sound.channel_three.cycles; + + /* calc length */ + if (sound.channel_three.length == 0) + sound.channel_three.length = 256; + + /* if DAC is off, disable the channel */ + if (sound.nr30->dac == 0) + sound.channel_three.active = 0; + + /* extra length clock if length == 256 */ + /* and FS is in the fist half */ + if ((sound.fs_cycles_idx & 0x01) == 0x00 && + sound.channel_three.length == 256) + sound_length_ctrl_step_ch(sound.nr34->length_enable, + &sound.channel_three.length, + &sound.channel_three.active); + + /* i accessed to the wave RAM... */ + sound.channel_three.ram_access = sound.channel_three.cycles; + + if (sound.channel_three.cycles % 4 == 0) + sound.channel_three.ram_access_next = + cycles.cnt + sound.channel_three.cycles; + else + sound.channel_three.ram_access_next = -1; + +/* printf("RAM ACCESS RICARICATO %u - CNT %d CYCLES %d \n", + sound.channel_three.ram_access, + cycles.cnt, sound.channel_three.cycles);*/ + } + break; + + case 0xFF20: + + sound.channel_four.length = 64 - sound.nr41->length_load; + + break; + + case 0xFF21: + + /* highest 5 bits cleared = turn off the DAC = turn off channeru */ + if (sound.nr42->volume == 0 && + sound.nr42->add == 0) + sound.channel_four.active = 0; + + break; + + case 0xFF23: + + /* length counter turned on */ + if (sound.nr44->length_enable) + { + nr44_t *old_nr44 = (nr44_t *) &old; + + /* give an extra length clock if */ + /* 1) we switched from off to on the len counter */ + /* 2) we are in the first half of len clock */ + /* 3) actual length is not zero */ + if ((old_nr44->length_enable == 0) && + ((sound.fs_cycles_idx & 0x01) == 0x00) && + (sound.channel_four.length != 0)) + sound_length_ctrl_step_ch(sound.nr44->length_enable, + &sound.channel_four.length, + &sound.channel_four.active); + } + + if (v & 0x80) + { + /* setting internal modules data with stuff taken from memory */ + sound.channel_four.active = 1; + + /* calc length */ + if (sound.channel_four.length == 0) + sound.channel_four.length = 64; + + uint16_t divisor; + + /* calc LFSR period */ + switch (sound.nr43->divisor) + { + case 0: divisor = 8; break; + case 1: divisor = 16; break; + case 2: divisor = 32; break; + case 3: divisor = 48; break; + case 4: divisor = 64; break; + case 5: divisor = 80; break; + case 6: divisor = 96; break; + case 7: divisor = 112; break; + } + + /* calc LFSR period */ + sound.channel_four.period_lfsr = divisor << sound.nr43->shift; + sound.channel_four.cycles_next = + cycles.cnt + sound.channel_four.period_lfsr; + + /* init reg to all bits to 1 */ + sound.channel_four.reg = 0x7FFF; + + /* base volume */ + sound.channel_four.volume = + sound.nr42->volume * 0x111; + + /* reset envelope counter */ + sound.channel_four.envelope_cnt = 0; + + /* if DAC is off, turn off the channel */ + if (sound.nr42->add == 0 && + sound.nr42->volume == 0) + sound.channel_four.active = 0; + + /* extra length clock if length == 64 */ + /* and FS is in the fist half */ + if ((sound.fs_cycles_idx & 0x01) == 0x00 && + sound.channel_four.length == 64) + sound_length_ctrl_step_ch(sound.nr44->length_enable, + &sound.channel_four.length, + &sound.channel_four.active); + } + + break; + + case 0xFF26: + + if (v & 0x80) + { + /* power from off to on! */ + if (!(old & 0x80)) + { + /* reset frame sequencer so the next step will be zero */ + sound.fs_cycles_idx = 7; + + /* reset wave index */ + sound.channel_three.index = 0; + + /* wave samples are resetted */ + bzero(sound.wave_table, 16); + } + } + else + { + /* power off */ + + /* clear all the sound memory */ + bzero(mmu_addr(0xFF10), 22); + + if (global_cgb) + { + sound.nr41->length_load = 0; + sound.channel_four.length = 0; + } + + /* turn off every channeru */ + sound.channel_one.active = 0; + sound.channel_two.active = 0; + sound.channel_three.active = 0; + sound.channel_four.active = 0; + } + + } +} + +void sound_write_wave(uint16_t a, uint8_t v) +{ + if (sound.channel_three.active) + { +// if (!global_cgb && sound.channel_three.ram_access != 0) +// return; + if (!global_cgb && cycles.cnt < sound.channel_three.ram_access_next) + return; + + sound.wave_table[sound.channel_three.index >> 1] = v; + + return; + } + + sound.wave_table[a - 0xFF30] = v; +} + +void sound_rebuild_wave() +{ + int sample; + uint8_t shift = (sound.nr32->volume_code == 0 ? + 4 : sound.nr32->volume_code - 1); + int i; + + int min = 0; + int max = 0; + + /* fill wave buffer */ + for (i=0; i<16; i++) + { + /* read higher nibble */ + sample = (sound.wave_table[i] & 0xf0) >> 4; + + /* apply volume change */ + sample >>= shift; + + /* convert to signed int 16 */ + sample = (sample * 0x1111) - 0x8000; + + if (sample < min) + min = sample; + if (sample > max) + max = sample; + + /* save it into rendered wave table */ + sound.channel_three.wave[i * 2] = (int16_t) sample; + + /* do the same with lowest nibble */ + sample = (sound.wave_table[i] & 0x0f); + + /* apply volume change */ + sample >>= shift; + + /* convert to signed int 16 */ + sample = (sample * 0x1111) - 0x8000; + + /* save it into rendered wave table */ + sound.channel_three.wave[(i * 2) + 1] = (int16_t) sample; + + if (sample < min) + min = sample; + if (sample > max) + max = sample; + } + + /* set the new current sample */ + sound.channel_three.sample = + sound.channel_three.wave[sound.channel_three.index]; + +} + +void sound_term() +{ + if (sound.buf_empty) + { + sound.buf_empty = 0; + pthread_cond_signal(&sound_cond); + } +} + +void sound_save_stat(FILE *fp) +{ + fwrite(&sound, 1, sizeof(sound_t), fp); +} + +void sound_restore_stat(FILE *fp) +{ + fread(&sound, 1, sizeof(sound_t), fp); + + sound_init_pointers(); +} diff --git a/waterbox/pizza/lib/sound.h b/waterbox/pizza/lib/sound.h new file mode 100644 index 0000000000..1ca9b4c0ef --- /dev/null +++ b/waterbox/pizza/lib/sound.h @@ -0,0 +1,334 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __SOUND_HDR__ +#define __SOUND_HDR__ + +#define SOUND_FREQ_MAX 48000 +#define SOUND_SAMPLES 4096 +#define SOUND_BUF_SZ (SOUND_SAMPLES * 3) +#define SOUND_BUF_TMP_SZ (SOUND_SAMPLES / 2) + +typedef struct nr10_s +{ + uint8_t shift:3; + uint8_t negate:1; + uint8_t sweep_period:3; + uint8_t spare:1; + +} nr10_t; + +typedef struct nr11_s +{ + uint8_t length_load:6; + uint8_t duty:2; + +} nr11_t; + +typedef struct nr12_s +{ + uint8_t period:3; + uint8_t add:1; + uint8_t volume:4; + +} nr12_t; + +typedef struct nr13_s +{ + uint8_t frequency_lsb; + +} nr13_t; + +typedef struct nr14_s +{ + uint8_t frequency_msb:3; + uint8_t spare:3; + uint8_t length_enable:1; + uint8_t trigger:1; + +} nr14_t; + +typedef struct nr21_s +{ + uint8_t length_load:6; + uint8_t duty:2; + +} nr21_t; + +typedef struct nr22_s +{ + uint8_t period:3; + uint8_t add:1; + uint8_t volume:4; + +} nr22_t; + +typedef struct nr23_s +{ + uint8_t frequency_lsb; + +} nr23_t; + +typedef struct nr24_s +{ + uint8_t frequency_msb:3; + uint8_t spare:3; + uint8_t length_enable:1; + uint8_t trigger:1; + +} nr24_t; + + +typedef struct nr30_s +{ + uint8_t spare:7; + uint8_t dac:1; + +} nr30_t; + +typedef struct nr31_s +{ + uint8_t length_load; + +} nr31_t; + +typedef struct nr32_s +{ + uint8_t spare:5; + uint8_t volume_code:2; + uint8_t spare2:1; + +} nr32_t; + +typedef struct nr33_s +{ + uint8_t frequency_lsb; + +} nr33_t; + +typedef struct nr34_s +{ + uint8_t frequency_msb:3; + uint8_t spare:3; + uint8_t length_enable:1; + uint8_t trigger:1; + +} nr34_t; + +typedef struct nr41_s +{ + uint8_t length_load:6; + uint8_t spare:2; + +} nr41_t; + +typedef struct nr42_s +{ + uint8_t period:3; + uint8_t add:1; + uint8_t volume:4; + +} nr42_t; + +typedef struct nr43_s +{ + uint8_t divisor:3; + uint8_t width:1; + uint8_t shift:4; + +} nr43_t; + +typedef struct nr44_s +{ + uint8_t spare:6; + uint8_t length_enable:1; + uint8_t trigger:1; + +} nr44_t; + +typedef struct nr50_s +{ + uint8_t so1_volume:3; + uint8_t vin_to_so1:1; + uint8_t so2_volume:3; + uint8_t vin_to_so2:1; +} nr50_t; + +typedef struct nr51_s +{ + uint8_t ch1_to_so1:1; + uint8_t ch2_to_so1:1; + uint8_t ch3_to_so1:1; + uint8_t ch4_to_so1:1; + uint8_t ch1_to_so2:1; + uint8_t ch2_to_so2:1; + uint8_t ch3_to_so2:1; + uint8_t ch4_to_so2:1; +} nr51_t; + +typedef struct nr52_s +{ + uint8_t spare:7; + uint8_t power:1; +} nr52_t; + +typedef struct channel_square_s +{ + uint8_t active; + uint8_t duty; + uint8_t duty_idx; + uint8_t envelope_cnt; + uint_fast16_t duty_cycles; + uint_fast16_t duty_cycles_next; + uint_fast32_t length; + uint_fast32_t frequency; + int16_t sample; + int16_t spare; + uint_fast16_t sweep_active; + uint_fast16_t sweep_cnt; + uint_fast16_t sweep_neg; + uint_fast16_t sweep_next; + int16_t volume; + int16_t spare2; + uint32_t sweep_shadow_frequency; + +} channel_square_t; + +typedef struct channel_wave_s +{ + uint8_t active; + uint8_t index; + uint16_t ram_access; + int16_t sample; + int16_t spare; + int16_t wave[32]; + uint_fast32_t cycles; + uint_fast32_t cycles_next; + uint_fast32_t ram_access_next; + uint_fast32_t length; + +} channel_wave_t; + +typedef struct channel_noise_s +{ + uint8_t active; + uint8_t envelope_cnt; + uint16_t spare; + uint_fast32_t length; + uint_fast16_t period_lfsr; + uint_fast32_t cycles_next; + int16_t volume; + int16_t sample; + uint16_t reg; + uint16_t spare2; + +} channel_noise_t; + +typedef struct sound_s +{ + nr10_t *nr10; + nr11_t *nr11; + nr12_t *nr12; + nr13_t *nr13; + nr14_t *nr14; + + nr21_t *nr21; + nr22_t *nr22; + nr23_t *nr23; + nr24_t *nr24; + + nr30_t *nr30; + nr31_t *nr31; + nr32_t *nr32; + nr33_t *nr33; + nr34_t *nr34; + + nr41_t *nr41; + nr42_t *nr42; + nr43_t *nr43; + nr44_t *nr44; + + nr50_t *nr50; + nr51_t *nr51; + nr52_t *nr52; + + uint8_t *wave_table; + + channel_square_t channel_one; + channel_square_t channel_two; + channel_wave_t channel_three; + channel_noise_t channel_four; + + /* emulation speed stuff */ + uint_fast16_t frame_counter; + uint_fast16_t frame_multiplier; + + /* circular audio buffer stuff */ + uint_fast16_t buf_rd; + uint_fast16_t buf_wr; + uint_fast16_t buf_available; + uint_fast16_t buf_empty; + uint_fast16_t buf_full; + int16_t buf[SOUND_BUF_SZ]; + int16_t buf_tmp[SOUND_BUF_TMP_SZ]; + uint_fast16_t buf_tmp_wr; + + /* output rate */ + uint_fast32_t output_rate; + + /* CPU cycles to internal cycles counters */ + uint_fast32_t fs_cycles; + uint_fast32_t fs_cycles_idx; + uint_fast32_t fs_cycles_next; + uint_fast32_t sample_cycles; + uint_fast32_t sample_cycles_remainder; + uint_fast32_t sample_cycles_next; + uint_fast32_t sample_cycles_next_rounded; + + /* steps length */ + uint_fast32_t step_int; + uint_fast32_t step_int1000; + + uint_fast32_t spare; + uint_fast32_t spare2; + +} sound_t; + +extern sound_t sound; + +/* prototypes */ +void sound_change_emulation_speed(); +int sound_get_samples(); +void sound_init(); +void sound_read_buffer(void *userdata, uint8_t *stream, int snd_len); +uint8_t sound_read_reg(uint16_t a, uint8_t v); +void sound_restore_stat(FILE *fp); +void sound_save_stat(FILE *fp); +void sound_set_speed(char dbl); +void sound_set_output_rate(int freq); +void sound_step_fs(); +void sound_step_ch1(); +void sound_step_ch2(); +void sound_step_ch3(); +void sound_step_ch4(); +void sound_step_sample(); +void sound_term(); +void sound_write_reg(uint16_t a, uint8_t v); + +#endif diff --git a/waterbox/pizza/lib/testone.h b/waterbox/pizza/lib/testone.h new file mode 100644 index 0000000000..1e9a8fc8ba --- /dev/null +++ b/waterbox/pizza/lib/testone.h @@ -0,0 +1,8 @@ +// +// Created by DVDN on 14/07/2016. +// + +#ifndef PIZZABOY_TESTONE_H +#define PIZZABOY_TESTONE_H + +#endif //PIZZABOY_TESTONE_H diff --git a/waterbox/pizza/lib/timer.c b/waterbox/pizza/lib/timer.c new file mode 100644 index 0000000000..a46c224bda --- /dev/null +++ b/waterbox/pizza/lib/timer.c @@ -0,0 +1,79 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include "cycles.h" +#include "interrupt.h" +#include "mmu.h" +#include "timer.h" + +/* pointer to interrupt flags (handy) */ +interrupts_flags_t *timer_if; + + +void timer_init() +{ + /* reset values */ + timer.next = 256; + timer.sub = 0; + + /* pointer to interrupt flags */ + timer_if = mmu_addr(0xFF0F); +} + +void timer_write_reg(uint16_t a, uint8_t v) +{ + switch (a) + { + case 0xFF04: timer.div = 0; return; + case 0xFF05: timer.cnt = v; return; + case 0xFF06: timer.mod = v; return; + case 0xFF07: timer.ctrl = v; + } + + if (timer.ctrl & 0x04) + timer.active = 1; + else + timer.active = 0; + + switch (timer.ctrl & 0x03) + { + case 0x00: timer.threshold = 1024; break; + case 0x01: timer.threshold = 16; break; + case 0x02: timer.threshold = 64; break; + case 0x03: timer.threshold = 256; break; + } + + if (timer.active) + timer.sub_next = cycles.cnt + timer.threshold; +} + +uint8_t timer_read_reg(uint16_t a) +{ + switch (a) + { + case 0xFF04: return timer.div; + case 0xFF05: return timer.cnt; + case 0xFF06: return timer.mod; + case 0xFF07: return timer.ctrl; + } + + return 0xFF; +} + + diff --git a/waterbox/pizza/lib/timer.h b/waterbox/pizza/lib/timer.h new file mode 100644 index 0000000000..acdf8561c8 --- /dev/null +++ b/waterbox/pizza/lib/timer.h @@ -0,0 +1,65 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __TIMER_HDR__ +#define __TIMER_HDR__ + +#include + +/* timer status */ +typedef struct timer_gb_s +{ + /* is it active? */ + uint8_t active; + + /* divider - 0xFF04 */ + uint8_t div; + + /* modulo - 0xFF06 */ + uint8_t mod; + + /* control - 0xFF07 */ + uint8_t ctrl; + + /* counter - 0xFF05 */ + uint_fast32_t cnt; + + /* threshold */ + uint32_t threshold; + + /* current value */ + uint_fast32_t sub; + uint_fast32_t next; + + /* spare */ + uint_fast32_t sub_next; + uint_fast32_t spare2; + +} timer_gb_t; + +/* global status of timer */ +timer_gb_t timer; + +/* prototypes */ +void timer_init(); +void timer_step(); +void timer_write_reg(uint16_t a, uint8_t v); +uint8_t timer_read_reg(uint16_t a); + +#endif diff --git a/waterbox/pizza/lib/utils.c b/waterbox/pizza/lib/utils.c new file mode 100644 index 0000000000..7bb91e5f5b --- /dev/null +++ b/waterbox/pizza/lib/utils.c @@ -0,0 +1,139 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifdef __ANDROID__ +#include +#else +#include +#endif + +#include +#include +#include + +#include "cycles.h" +#include "gpu.h" +#include "utils.h" + +uint32_t prev_cycles = 0; + +void utils_log(const char *format, ...) +{ + char buf[256]; + + va_list args; + va_start(args, format); + +#ifdef __ANDROID__ + + vsnprintf(buf, 256, format, args); + __android_log_write(ANDROID_LOG_INFO, "Pizza", buf); + +#else + + vsnprintf(buf, 256, format, args); + printf(buf); + +#endif + + va_end(args); +} + + +void utils_log_urgent(const char *format, ...) +{ + char buf[256]; + + va_list args; + va_start(args, format); + +#ifdef __ANDROID__ + + vsnprintf(buf, 256, format, args); + __android_log_write(ANDROID_LOG_INFO, "Pizza", buf); + +#else + + vsnprintf(buf, 256, format, args); + printf(buf); + +#endif + + va_end(args); +} + +void utils_ts_log(const char *format, ...) +{ + va_list args; + va_start(args, format); + + char buf[256]; + struct timeval tv; + +#ifdef __ANDROID__ + + vsnprintf(buf, 256, format, args); + __android_log_write(ANDROID_LOG_INFO, "Pizza", buf); + +#else + + vsprintf(buf, format, args); + gettimeofday(&tv, NULL); +// printf("%ld - %s\n", tv.tv_sec, buf); + printf("LINE %u - CYCLES %u - DIFF %u - %ld:%06ld - %s", + *(gpu.ly), cycles.cnt, cycles.cnt - prev_cycles, + tv.tv_sec, tv.tv_usec, buf); + + prev_cycles = cycles.cnt; + +#endif + + va_end(args); +} + +void utils_binary_sem_init(utils_binary_sem_t *p) +{ + pthread_mutex_init(&p->mutex, NULL); + pthread_cond_init(&p->cvar, NULL); + p->v = 0; +} + +void utils_binary_sem_post(utils_binary_sem_t *p) +{ + pthread_mutex_lock(&p->mutex); + p->v = 1; + pthread_cond_signal(&p->cvar); + pthread_mutex_unlock(&p->mutex); +} + +void utils_binary_sem_wait(utils_binary_sem_t *p, unsigned int nanosecs) +{ + struct timespec ts; + + ts.tv_sec = time(NULL) + nanosecs / 1000000000; + ts.tv_nsec = nanosecs % 1000000000; + + pthread_mutex_lock(&p->mutex); + while (!p->v) + if (pthread_cond_timedwait(&p->cvar, &p->mutex, &ts) == ETIMEDOUT) + break; + p->v = 0; + pthread_mutex_unlock(&p->mutex); +} + diff --git a/waterbox/pizza/lib/utils.h b/waterbox/pizza/lib/utils.h new file mode 100644 index 0000000000..a5fb171e98 --- /dev/null +++ b/waterbox/pizza/lib/utils.h @@ -0,0 +1,42 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#ifndef __UTILS_HDR__ +#define __UTILS_HDR__ + +#include + +/* binary semaphore */ +typedef struct utils_binary_sem_s +{ + pthread_mutex_t mutex; + pthread_cond_t cvar; + int v; + +} utils_binary_sem_t; + +/* prototypes */ +void utils_binary_sem_init(utils_binary_sem_t *p); +void utils_binary_sem_post(utils_binary_sem_t *p); +void utils_binary_sem_wait(utils_binary_sem_t *p, unsigned int nanosecs); +void utils_log(const char *format, ...); +void utils_log_urgent(const char *format, ...); +void utils_ts_log(const char *format, ...); + +#endif diff --git a/waterbox/pizza/lib/z80_gameboy.h b/waterbox/pizza/lib/z80_gameboy.h new file mode 100644 index 0000000000..db8c78c66d --- /dev/null +++ b/waterbox/pizza/lib/z80_gameboy.h @@ -0,0 +1,2480 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include +#include +#include +#include +#include +#include "mmu.h" +#include "z80_gameboy_regs.h" + +/* main struct describing CPU state */ + +typedef struct z80_state_s +{ + uint8_t spare; + uint8_t a; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint8_t c; + uint8_t b; +#else + uint8_t b; + uint8_t c; +#endif +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint8_t e; + uint8_t d; +#else + uint8_t d; + uint8_t e; +#endif +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint8_t l; + uint8_t h; +#else + uint8_t h; + uint8_t l; +#endif + + uint16_t sp; + uint16_t pc; + + /* shortcuts */ + uint16_t *bc; + uint16_t *de; + uint16_t *hl; + uint8_t *f; + + uint32_t spare4; + uint32_t skip_cycle; + + z80_flags_t flags; + uint8_t int_enable; + + /* latest T-state */ + uint16_t spare3; + + /* total cycles */ + uint64_t cycles; + +} z80_state_t; + +#define Z80_MAX_MEMORY 65536 + + +/* state of the Z80 CPU */ +z80_state_t state; + +/* precomputed flags masks */ +uint8_t zc[1 << 9]; +uint8_t z[1 << 9]; + +/* macro to access addresses passed as parameters */ +#define ADDR mmu_read_16(state.pc + 1) +#define NN mmu_read_16(state.pc + 2) + +/* dummy value for 0x06 regs resulution table */ +uint8_t dummy; + +/* Registers table */ +uint8_t **regs_dst; +uint8_t **regs_src; + +#define FLAG_MASK_Z (1 << FLAG_OFFSET_Z) +#define FLAG_MASK_AC (1 << FLAG_OFFSET_AC) +#define FLAG_MASK_N (1 << FLAG_OFFSET_N) +#define FLAG_MASK_CY (1 << FLAG_OFFSET_CY) + + +/********************************/ +/* */ +/* FLAGS OPS */ +/* */ +/********************************/ + +/* calc flags SZ with 16 bit param */ +void static inline z80_set_flags_z(unsigned int v) +{ + state.flags.z = (v & 0xff) == 0; +} + +/* calc flags SZC with 16 bit param */ +void static inline z80_set_flags_zc(unsigned int v) +{ + state.flags.z = (v & 0xff) == 0; + state.flags.cy = (v > 0xff); +} + +/* calc flags SZC with 32 bit result */ +void static inline z80_set_flags_zc_16(unsigned int v) +{ + state.flags.z = (v & 0xffff) == 0; + state.flags.cy = (v > 0xffff); +} + +/* calc AC for given operands */ +void static inline z80_set_flags_ac(uint8_t a, uint8_t b, + unsigned int r) +{ + /* calc xor for AC and overflow */ + unsigned int c = (a ^ b ^ r); + + /* set AC */ + state.flags.ac = ((c & 0x10) != 0); + + return; +} + +/* calc AC and overflow flag given operands (16 bit flavour) */ +void static inline z80_set_flags_ac_16(unsigned int a, + unsigned int b, + unsigned int r) +{ + /* calc xor for AC and overflow */ + unsigned int c = (a ^ b ^ r); + + /* set AC */ + state.flags.ac = ((c & 0x01000) != 0); + + return; +} + +/* calc AC flag given operands and result */ +char static inline z80_calc_ac(uint8_t a, uint8_t b, unsigned int r) +{ + /* calc xor for AC and overflow */ + unsigned int c = a ^ b ^ r; + + /* AC */ + if (c & 0x10) + return 1; + + return 0; +} + +/* calculate flags mask array */ +void static z80_calc_flags_mask_array() +{ + z80_flags_t f; + unsigned int i; + + // bzero(sz53pc, sizeof(sz53pc)); + + /* create a mask for bit 5 and 3 and its reverse */ +/* f.spare = 0= 1; f.z = 1; f.u5 = 0; f.ac = 1; f.u3 = 0; f.p = 1; f.n = 1; f.cy = 1; + u53_mask = *((uint8_t *) &f); + r53_mask = ~(u53_mask); */ + + bzero(&f, 1); + + for (i=0; i<512; i++) + { + f.z = ((i & 0xff) == 0); + f.cy = (i > 0xff); + + zc[i] = *((uint8_t *) &f); + + f.cy = 0; + + z[i] = *((uint8_t *) &f); + + /* no CY and parity */ +/* f.cy = 0; + f.p = parity[i & 0xff]; + sz53p[i] = *((uint8_t *) &f); +*/ + /* similar but with no u3 and u5 */ +/* f.u3 = 0; + f.u5 = 0; + szp[i] = *((uint8_t *) &f); +*/ + /* similar but with carry and no p */ +/* f.cy = (i > 0xff); + f.p = 0; + szc[i] = *((uint8_t *) &f); */ + } +} + + +/********************************/ +/* */ +/* INSTRUCTIONS SEGMENT */ +/* ordered by name */ +/* */ +/********************************/ + + +/* add A register, b parameter and Carry flag, then calculate flags */ +void static inline z80_adc(uint8_t b) +{ + /* calc result */ + unsigned int result = state.a + b + state.flags.cy; + + /* set flags - SZ5H3V0C */ + *state.f = zc[result & 0x1ff]; + + /* set AC and overflow flags */ + z80_set_flags_ac(state.a, b, result); + + /* save result into A register */ + state.a = (uint8_t) result; + + return; +} + +/* add a and b parameters (both 16 bits) and the carry, thencalculate flags */ +unsigned int static inline z80_adc_16(unsigned int a, unsigned int b) +{ + /* calc result */ + unsigned int result = a + b + state.flags.cy; + + /* set them - SZ5H3V0C */ + z80_set_flags_zc_16(result); + state.flags.n = 0; + + /* get only high byte */ + // unsigned int r16 = (result >> 8); + + /* set AC and overflow flags */ + z80_set_flags_ac_16(a, b, result); + + return result; +} + +/* add A register and b parameter and calculate flags */ +void static inline z80_add(uint8_t b) +{ + /* calc result */ + unsigned int result = state.a + b; + + /* set them - SZ5H3P0C */ + *state.f = zc[result & 0x1ff]; + + /* set AC and overflow flags - given AC and V set to 0 */ + z80_set_flags_ac(state.a, b, result); + + /* save result into A register */ + state.a = result; + + return; +} + +/* add a and b parameters (both 16 bits), then calculate flags */ +unsigned int static inline z80_add_16(unsigned int a, unsigned int b) +{ + /* calc result */ + unsigned int result = a + b; + + /* get only high byte */ + // uint8_t r16 = (result >> 8); + + /* not a subtraction */ + state.flags.n = 0; + + /* calc xor for AC */ + z80_set_flags_ac(a, b, result); + + /* set CY */ + state.flags.cy = (result > 0xffff); + + return result; +} + +/* b AND A register and calculate flags */ +void static inline z80_ana(uint8_t b) +{ + /* calc result */ + uint8_t result = state.a & b; + + /* set them */ + *state.f = zc[result] | FLAG_MASK_AC; + + /* save result into A register */ + state.a = result; + + return; +} + +/* BIT instruction, test pos-th bit and set flags */ +void static inline z80_bit(uint8_t *v, uint8_t pos, uint8_t muffa) +{ + uint8_t r = *v & (0x01 << pos); + + /* set flags AC,Z, N = 0 */ + state.flags.n = 0; + state.flags.ac = 1; + state.flags.z = (r == 0) ; + + return; +} + +/* push the current PC on the stack and move PC to the function addr */ +int static inline z80_call(unsigned int addr) +{ + /* move to the next instruction */ + state.pc += 3; + + /* add 4 more cycles */ + cycles_step(); + + /* save it into stack */ + mmu_write_16(state.sp - 2, state.pc); + + /* update stack pointer */ + state.sp -= 2; + + /* move PC to the called function address */ + state.pc = addr; + + return 0; +} + +/* compare b parameter against A register and calculate flags */ +void static inline z80_cmp(uint8_t b) +{ + /* calc result */ + unsigned int result = state.a - b; + + /* set flags - SZ5H3PN* */ + *state.f = zc[result & 0x1ff] | + FLAG_MASK_N; + + /* set AC and overflow flags */ + z80_set_flags_ac(state.a, b, result); + + return; +} + +/* compare b parameter against A register and calculate flags */ +void static inline z80_cpid(uint8_t b, int8_t add) +{ + /* calc result */ + unsigned int result = state.a - b; + + /* calc AC */ + state.flags.ac = z80_calc_ac(state.a, b, result); + + /* increase (add = +1) or decrease (add = -1) HL */ + *state.hl += add; + + /* decrease BC */ + *state.bc = *state.bc - 1; + + /* calc n as result - half carry flag */ + // unsigned int n = result - state.flags.ac; + + /* set flags - SZ5H3P1* */ + state.flags.z = (result & 0xff) == 0; + +// z80_set_flags_z(result); + + /* cmp = subtraction */ + state.flags.n = 1; + + /* set P if BC != 0 */ + // state.flags.p = (*state.bc != 0); + + /* flag 3 and 5 are taken from (result - ac) and not the result */ + /* and u5 is taken exceptionally from the bit 1 */ +// state.flags.u5 = (n & 0x0002) != 0; +// state.flags.u3 = (n & 0x0008) != 0; + + return; +} + +/* DAA instruction... what else? */ +void static inline z80_daa() +{ + unsigned int a = state.a; + uint8_t al = state.a & 0x0f; + + if (state.flags.n) + { + if (state.flags.ac) + a = (a - 6) & 0xFF; + + if (state.flags.cy) + a -= 0x60; + } + else + { + if (al > 9 || state.flags.ac) + { + state.flags.ac = (al > 9); + a += 6; + } + + if (state.flags.cy || ((a & 0x1f0) > 0x90)) + a += 0x60; + } + + if (a & 0x0100) state.flags.cy = 1; + + /* set computer A value */ + state.a = a & 0xff; + + state.flags.ac = 0; + state.flags.z = (state.a == 0); + + return; +} + +/* DAA instruction... what else? */ +void static inline z80_daa_ignore_n() +{ + unsigned int a = state.a; + uint8_t al = state.a & 0x0f; + + if (al > 9 || state.flags.ac) + { + state.flags.ac = (al > 9); + a += 6; + } + + if (state.flags.cy || ((a & 0x1f0) > 0x90)) + a += 0x60; + + if (a & 0x0100) state.flags.cy = 1; + + /* set computer A value */ + state.a = a & 0xff; + + /* reset H flag */ + state.flags.z = (state.a == 0x00); + state.flags.ac = 0; + + /* and its flags */ + // z80_set_flags_sz53p(state.a); + + return; +} + +/* add a and b parameters (both 16 bits) and the carry, thencalculate flags */ +unsigned int static inline dad_16(unsigned int a, unsigned int b) +{ + /* calc result */ + unsigned int result = a + b; + + /* reset n */ + state.flags.n = 0; + + /* calc xor for AC and overflow */ + unsigned int c = a ^ b ^ result; + + /* set AC */ + state.flags.ac = ((c & 0x1000) != 0); + + /* set CY */ + state.flags.cy = (result > 0xffff); + + return result; +} + +/* dec the operand and return result increased by one */ +uint8_t static inline z80_dcr(uint8_t b) +{ + unsigned int result = b - 1; + + /* set flags - SZ5H3V1* */ + z80_set_flags_z(result); + + /* it's a subtraction */ + state.flags.n = 1; + + /* set overflow and AC */ + z80_set_flags_ac(b, 1, result); + +// state.flags.ac = 0; + + return result; +} + +/* inc the operand and return result increased by one */ +uint8_t static inline z80_inr(uint8_t b) +{ + unsigned int result = b + 1; + + /* set flags - SZ5H3V1* */ + z80_set_flags_z(result); + + /* it's not a subtraction */ + state.flags.n = 0; + + /* set overflow and AC */ + z80_set_flags_ac(1, b, result); + + return result; +} + +/* same as call, but save on the stack the current PC instead of next instr */ +int static inline z80_intr(unsigned int addr) +{ + /* push the current PC into stack */ + mmu_write_16(state.sp - 2, state.pc); + + cycles_step(); + + /* update stack pointer */ + state.sp -= 2; + + /* move PC to the called function address */ + state.pc = addr; + + return 0; +} + +/* copy (HL) in (DE) and decrease HL, DE and BC */ +void static inline z80_ldd() +{ + uint8_t byte; + + /* copy! */ + mmu_move(*state.de, *state.hl); + + /* get last moved byte and sum A */ + byte = mmu_read(*state.de); + byte += state.a; + + /* decrease HL, DE and BC */ + *state.hl = *state.hl - 1; + *state.de = *state.de - 1; + *state.bc = *state.bc - 1; + + /* reset flags - preserve ZC */ + *state.f &= FLAG_MASK_Z | + FLAG_MASK_CY; + + return; +} + +/* copy (HL) in (DE) and increase HL and DE. BC is decreased */ +void static inline z80_ldi() +{ + uint8_t byte; + + /* copy! */ + mmu_move(*state.de, *state.hl); + + /* get last moved byte and sum A */ + byte = mmu_read(*state.de); + byte += state.a; + + /* u5 flag is bit 1 of last moved byte + A (WTF?) */ + // state.flags.u5 = (byte & 0x02) >> 1; + + /* u3 flag is bit 3 of last moved byte + A (WTF?) */ + // state.flags.u3 = (byte & 0x08) >> 3; + + /* decrease HL, DE and BC */ + *state.hl = *state.hl + 1; + *state.de = *state.de + 1; + *state.bc = *state.bc - 1; + + /* reset negative, half carry and parity flags */ + state.flags.n = 0; + state.flags.ac = 0; + // state.flags.p = (*state.bc != 0); + + return; +} + +/* negate register A */ +void static inline z80_neg() +{ + /* calc result */ + unsigned int result = 0 - state.a; + + /* set flags - SZ5H3V1C */ + *state.f = zc[result & 0x1ff] | FLAG_MASK_N; + + /* set AC and overflow */ + z80_set_flags_ac(0, state.a, result); + + /* save result into A register */ + state.a = (uint8_t) result; + + return; +} + +/* OR b parameter and A register and calculate flags */ +void static inline z80_ora(uint8_t b) +{ + state.a |= b; + + /* set them SZ503P0C */ + *state.f = zc[state.a]; + + return; +} + +/* RES instruction, put a 0 on pos-th bit and set flags */ +uint8_t static inline z80_res(uint8_t *v, uint8_t pos) +{ + *v &= ~(0x01 << pos); + + return *v; +} + +/* pop the return address from the stack and move PC to that address */ +int static inline z80_ret() +{ + state.pc = mmu_read_16(state.sp); + state.sp += 2; + + /* add 4 cycles */ + cycles_step(); + + return 0; +} + +/* RL (Rotate Left) instruction */ +uint8_t static inline z80_rl(uint8_t *v, char with_carry) +{ + uint8_t carry; + + /* apply RLC to the memory pointed byte */ + carry = (*v & 0x80) >> 7; + *v = *v << 1; + + if (with_carry) + *v |= carry; + else + *v |= state.flags.cy; + + /* set flags - SZ503P0C */ + *state.f = 0; // sz53p[*v]; + + state.flags.z = (*v == 0); + state.flags.cy = carry; + + return *v; +} + +/* RLA instruction */ +uint8_t static inline z80_rla(uint8_t *v, char with_carry) +{ + uint8_t carry; + + /* apply RLA to the memory pointed byte */ + carry = (*v & 0x80) >> 7; + *v = *v << 1; + + if (with_carry) + *v |= carry; + else + *v |= state.flags.cy; + + /* reset flags */ + *state.f = 0; + + /* just set carry */ + state.flags.cy = carry; + + return *v; +} + +/* RLD instruction */ +void static inline z80_rld() +{ + uint8_t hl = mmu_read(*state.hl); + + /* save lowest A 4 bits */ + uint8_t al = state.a & 0x0f; + + /* A lowest bits are overwritten by (HL) highest ones */ + state.a &= 0xf0; + state.a |= (hl >> 4); + + /* (HL) highest bits are overwritten by (HL) lowest ones */ + hl <<= 4; + + /* finally, (HL) lowest bits are overwritten by A lowest */ + hl &= 0xf0; + hl |= al; + + /* set (HL) with his new value ((HL) low | A low) */ + mmu_write(*state.hl, hl); + + /* reset flags - preserve CY */ + *state.f &= FLAG_MASK_CY; + + /* set flags - SZ503P0* */ + *state.f |= z[state.a]; + + return; +} + +/* RR instruction */ +uint8_t static inline z80_rr(uint8_t *v, char with_carry) +{ + uint8_t carry; + + /* apply RRC to the memory pointed byte */ + carry = *v & 0x01; + *v = (*v >> 1); + + /* 7th bit taken from old bit 0 or from CY */ + if (with_carry) + *v |= (carry << 7); + else + *v |= (state.flags.cy << 7); + + /* set flags - SZ503P0C */ + *state.f = z[*v]; + + state.flags.cy = carry; + + return *v; +} + +/* RRA instruction */ +uint8_t static inline z80_rra(uint8_t *v, char with_carry) +{ + uint8_t carry; + + /* apply RRC to the memory pointed byte */ + carry = *v & 0x01; + *v = (*v >> 1); + + /* 7th bit taken from old bit 0 or from CY */ + if (with_carry) + *v |= (carry << 7); + else + *v |= (state.flags.cy << 7); + + /* reset flags */ + *state.f = 0; + + state.flags.cy = carry; + +// state.flags.n = 0; +// state.flags.ac = 0; + + /* copy bit 3 and 5 of the result */ + // state.flags.u3 = ((*v & 0x08) != 0); + // state.flags.u5 = ((*v & 0x20) != 0); + + return *v; +} + +/* RRD instruction */ +void static inline z80_rrd() +{ + uint8_t hl = mmu_read(*state.hl); + + /* save lowest (HL) 4 bits */ + uint8_t hll = hl & 0x0f; + + /* (HL) lowest bits are overwritten by (HL) highest ones */ + hl >>= 4; + + /* (HL) highest bits are overwritten by A lowest ones */ + hl |= ((state.a & 0x0f) << 4); + + /* set (HL) with his new value (A low | (HL) high) */ + mmu_write(*state.hl, hl); + + /* finally, A lowest bits are overwritten by (HL) lowest */ + state.a &= 0xf0; + state.a |= hll; + + /* reset flags - preserve CY */ + *state.f &= FLAG_MASK_CY; + + /* set flags - SZ503P0* */ + *state.f |= z[state.a]; + + return; +} + +/* subtract b parameter and Carry from A register and calculate flags */ +void static inline z80_sbc(uint8_t b) +{ + /* calc result */ + unsigned int result = state.a - b - state.flags.cy; + + /* set flags - ZC and N = 1 */ + *state.f = zc[result & 0x1ff] | FLAG_MASK_N; + + /* set AC */ + z80_set_flags_ac(state.a, b, result); + + /* save result into A register */ + state.a = (uint8_t) result; + + return; +} + +/* subtract a and b parameters (both 16 bits) and the carry, then calculate flags */ +unsigned int static inline z80_sbc_16(unsigned int a, unsigned int b) +{ + /* calc result */ + unsigned int result = a - b - state.flags.cy; + + /* set flags - SZ5H3V1C */ + z80_set_flags_zc_16(result); + state.flags.n = 1; + + /* get only high byte */ + // unsigned int r16 = (result >> 8); + + /* set AC and overflow flags */ + z80_set_flags_ac_16(a, b, result); + + return result; +} + +/* SET instruction, put a 1 on pos-th bit and set flags */ +uint8_t static inline z80_set(uint8_t *v, uint8_t pos) +{ + *v |= (0x01 << pos); + + return *v; +} + +/* SL instruction (SLA = v * 2, SLL = v * 2 + 1) */ +uint8_t static inline z80_sl(uint8_t *v, char one_insertion) +{ + /* move pointed value to local (gives an huge boost in perf!) */ + uint8_t l = *v; + + /* apply SL to the memory pointed byte */ + uint8_t cy = (l & 0x80) != 0; + l = (l << 1) | one_insertion; + + /* set flags - SZ503P0C */ + *state.f = z[l]; + + state.flags.cy = cy; + + /* re-assign local value */ + *v = l; + + return l; +} + +/* SR instruction (SRA = preserve 8th bit, SRL = discard 8th bit) */ +uint8_t static inline z80_sr(uint8_t *v, char preserve) +{ + uint8_t bit = 0; + + /* save the bit 0 */ + uint8_t cy = (*v & 0x01); + + /* apply SL to the memory pointed byte */ + if (preserve) + bit = *v & 0x80; + + /* move 1 pos right and restore highest bit (in case of SRA) */ + *v = (*v >> 1) | bit; + + /* set flags - SZ503P0C */ + *state.f = z[*v]; + + state.flags.cy = cy; + + return *v; +} + +/* subtract b parameter from A register and calculate flags */ +void static inline z80_sub(uint8_t b) +{ + /* calc result */ + unsigned int result = state.a - b; + + /* set them - SZ5H3V1C */ + *state.f = zc[result & 0x1ff] | FLAG_MASK_N; + + /* set AC and overflow flags */ + z80_set_flags_ac(state.a, b, result); + + /* save result into A register */ + state.a = (uint8_t) result; + + return; +} + +/* xor b parameter and A register and calculate flags */ +void static inline z80_xra(uint8_t b) +{ + /* calc result */ + state.a ^= b; + + /* set them SZ503P00 */ + *state.f = z[state.a]; + + return; +} + + + +/********************************/ +/* */ +/* INSTRUCTIONS BRANCHES */ +/* */ +/********************************/ + + +/* Z80 extended OPs */ +int static inline z80_ext_cb_execute() +{ + uint8_t byte = 1; + int b = 2; + + /* CB family (ROT, BIT, RES, SET) */ + uint8_t cbfam; + + /* CB operation */ + uint8_t cbop; + + /* choosen register */ + uint8_t reg; + + /* get CB code */ + uint8_t code = mmu_read(state.pc + 1); + + /* extract family */ + cbfam = code >> 6; + + /* extract involved register */ + reg = code & 0x07; + + /* if reg == 0x06, refresh the pointer */ + // if (reg == 0x06 && code != 0x36) + // { + /* add 4 more cycles for reading data from memory */ + // cycles_step(); + + // regs_src[0x06] = mmu_addr(*state.hl); + // } + + switch (cbfam) + { + /* Rotate Family */ + case 0x00: cbop = code & 0xf8; + + switch(cbop) + { + /* RLC REG */ + case 0x00: if (reg == 0x06) + { + byte = mmu_read(*state.hl); + mmu_write(*state.hl, z80_rl(&byte, 1)); + } + else + z80_rl(regs_src[reg], 1); + break; + + /* RRC REG */ + case 0x08: if (reg == 0x06) + { + byte = mmu_read(*state.hl); + mmu_write(*state.hl, z80_rr(&byte, 1)); + } + else + z80_rr(regs_src[reg], 1); + + break; + + /* RL REG */ + case 0x10: if (reg == 0x06) + { + byte = mmu_read(*state.hl); + mmu_write(*state.hl, z80_rl(&byte, 0)); + } + else + z80_rl(regs_src[reg], 0); + + break; + + /* RR REG */ + case 0x18: if (reg == 0x06) + { + byte = mmu_read(*state.hl); + mmu_write(*state.hl, z80_rr(&byte, 0)); + } + else + z80_rr(regs_src[reg], 0); + + break; + + /* SLA REG */ + case 0x20: if (reg == 0x06) + { + byte = mmu_read(*state.hl); + mmu_write(*state.hl, z80_sl(&byte, 0)); + } + else + z80_sl(regs_src[reg], 0); + + break; + + /* SRA REG */ + case 0x28: if (reg == 0x06) + { + byte = mmu_read(*state.hl); + mmu_write(*state.hl, z80_sr(&byte, 1)); + } + else + z80_sr(regs_src[reg], 1); + + break; + + /* SWAP */ + case 0x30: + switch (code & 0x37) + { + /* SWAP B */ + case 0x30: byte = state.b; + state.b = ((byte & 0xf0) >> 4) | + ((byte & 0x0f) << 4); + break; + + /* SWAP C */ + case 0x31: byte = state.c; + state.c = ((byte & 0xf0) >> 4) | + ((byte & 0x0f) << 4); + break; + + /* SWAP D */ + case 0x32: byte = state.d; + state.d = ((byte & 0xf0) >> 4) | + ((byte & 0x0f) << 4); + break; + + /* SWAP E */ + case 0x33: byte = state.e; + state.e = ((byte & 0xf0) >> 4) | + ((byte & 0x0f) << 4); + break; + + /* SWAP H */ + case 0x34: byte = state.h; + state.h = ((byte & 0xf0) >> 4) | + ((byte & 0x0f) << 4); + break; + + /* SWAP L */ + case 0x35: byte = state.l; + state.l = ((byte & 0xf0) >> 4) | + ((byte & 0x0f) << 4); + break; + + /* SWAP *HL */ + case 0x36: byte = mmu_read(*state.hl); + mmu_write(*state.hl, + ((byte & 0xf0) >> 4) | + ((byte & 0x0f) << 4)); + + break; + + /* SWAP A */ + case 0x37: + byte = state.a; + state.a = ((byte & 0xf0) >> 4) | + ((byte & 0x0f) << 4); + break; + + } + + /* swap functions set Z flags */ + state.flags.z = (byte == 0x00); + + /* reset all the others */ + state.flags.ac = 0; + state.flags.cy = 0; + state.flags.n = 0; + + break; + + /* SRL REG */ + case 0x38: if (reg == 0x06) + { + byte = mmu_read(*state.hl); + mmu_write(*state.hl, z80_sr(&byte, 0)); + } + else + z80_sr(regs_src[reg], 0); + + break; + } + + /* accessing HL needs more T-cycles */ + //if (reg == 0x06 && code != 0x36) + // cycles_step(); + + /* accessing HL needs more T-cycles */ +// if (reg == 0x06) +// cycles_step(); + + break; + + /* BIT Family */ + case 0x01: if (reg == 0x06) + { + byte = mmu_read(*state.hl); + z80_bit(&byte, (code >> 3) & 0x07, + (uint8_t) *state.hl); + } + else + z80_bit(regs_src[reg], (code >> 3) & 0x07, + *regs_src[reg]); + break; + + /* RES Family */ + case 0x02: if (reg == 0x06) + { + byte = mmu_read(*state.hl); + mmu_write(*state.hl, z80_res(&byte, (code >> 3) & 0x07)); + } + else + z80_res(regs_src[reg], (code >> 3) & 0x07); + + break; + + /* SET Family */ + case 0x03: if (reg == 0x06) + { + byte = mmu_read(*state.hl); + mmu_write(*state.hl, z80_set(&byte, (code >> 3) & 0x07)); + } + else + z80_set(regs_src[reg], (code >> 3) & 0x07); + + break; + +// default: printf("Unimplemented CB family: %02x\n", +// cbfam); + + } + + return b; +} + + +/* really execute the OP. Could be ran by normal execution or * + * because an interrupt occours */ +int static inline z80_execute(unsigned char code) +{ + int b = 1; + uint8_t *p; + uint8_t byte = 1; + uint8_t byte2 = 1; + unsigned int result; + uint_fast16_t addr; + + switch (code) + { + /* NOP */ + case 0x00: break; + + /* LXI B */ + case 0x01: *state.bc = ADDR; + b = 3; + break; + + /* STAX B */ + case 0x02: mmu_write(*state.bc, state.a); + break; + + /* INX B */ + case 0x03: (*state.bc)++; + cycles_step(); + break; + + /* INR B */ + case 0x04: state.b = z80_inr(state.b); + break; + + /* DCR B */ + case 0x05: state.b = z80_dcr(state.b); + break; + + /* MVI B */ + case 0x06: state.b = mmu_read(state.pc + 1); + b = 2; + break; + + /* RLCA */ + case 0x07: z80_rla(&state.a, 1); + break; + + /* LD (NN),SP */ + case 0x08: mmu_write_16(ADDR, state.sp); + b = 3; + break; + + /* DAD B */ + case 0x09: *state.hl = dad_16(*state.hl, *state.bc); + + /* needs 4 more cycles */ + cycles_step(); + + break; + + /* LDAX B */ + case 0x0A: state.a = mmu_read(*state.bc); + break; + + /* DCX B */ + case 0x0B: (*state.bc)--; + cycles_step(); + break; + + /* INR C */ + case 0x0C: state.c = z80_inr(state.c); + break; + + /* DCR C */ + case 0x0D: state.c = z80_dcr(state.c); + break; + + /* MVI C */ + case 0x0E: state.c = mmu_read(state.pc + 1); + b = 2; + break; + + /* RRC */ + case 0x0F: z80_rra(&state.a, 1); + break; + + /* STOP */ + case 0x10: b = 2; + break; + + /* LXI D */ + case 0x11: *state.de = ADDR; + b = 3; + break; + + /* STAX D */ + case 0x12: mmu_write(*state.de, state.a); + break; + + /* INX D */ + case 0x13: (*state.de)++; + cycles_step(); + break; + + /* INR D */ + case 0x14: state.d = z80_inr(state.d); + break; + + /* DCR D */ + case 0x15: state.d = z80_dcr(state.d); + break; + + /* MVI D */ + case 0x16: state.d = mmu_read(state.pc + 1); + b = 2; + break; + + /* RLA */ + case 0x17: z80_rla(&state.a, 0); + break; + + /* JR */ + case 0x18: cycles_step(); + state.pc += (int8_t) mmu_read(state.pc + 1); + b = 2; + break; + + /* DAD D */ + case 0x19: *state.hl = dad_16(*state.hl, *state.de); + + /* needs 4 more cycles */ + cycles_step(); + + break; + + /* LDAX D */ + case 0x1A: state.a = mmu_read(*state.de); + break; + + /* DCX D */ + case 0x1B: (*state.de)--; + cycles_step(); + break; + + /* INR E */ + case 0x1C: state.e = z80_inr(state.e); + break; + + /* DCR E */ + case 0x1D: state.e = z80_dcr(state.e); + break; + + /* MVI E */ + case 0x1E: state.e = mmu_read(state.pc + 1); + b = 2; + break; + + /* RRA */ + case 0x1F: z80_rra(&state.a, 0); + break; + + /* JRNZ */ + case 0x20: cycles_step(); + + if (!state.flags.z) + state.pc += (int8_t) mmu_read(state.pc + 1); + + b = 2; + break; + + /* LXI H */ + case 0x21: *state.hl = ADDR; + b = 3; + break; + + /* LDI (HL), A */ + case 0x22: mmu_write(*state.hl, state.a); + (*state.hl)++; + break; + + /* INX H */ + case 0x23: (*state.hl)++; + cycles_step(); + break; + + /* INR H */ + case 0x24: state.h = z80_inr(state.h); + break; + + /* DCR H */ + case 0x25: state.h = z80_dcr(state.h); + break; + + /* MVI H */ + case 0x26: state.h = mmu_read(state.pc + 1); + b = 2; + break; + + /* DAA */ + case 0x27: z80_daa(); + break; + + /* JRZ */ + case 0x28: cycles_step(); + if (state.flags.z) + state.pc += (int8_t) mmu_read(state.pc + 1); + + b = 2; + break; + + /* DAD H */ + case 0x29: *state.hl = dad_16(*state.hl, *state.hl); + + /* needs 4 more cycles */ + cycles_step(); + + break; + + /* LDI A,(HL) */ + case 0x2A: state.a = mmu_read(*state.hl); + (*state.hl)++; + break; + + /* DCX H */ + case 0x2B: (*state.hl)--; + cycles_step(); + break; + + /* INR L */ + case 0x2C: state.l = z80_inr(state.l); + break; + + /* DCR L */ + case 0x2D: state.l = z80_dcr(state.l); + break; + + /* MVI L */ + case 0x2E: state.l = mmu_read(state.pc + 1); + b = 2; + break; + + /* CMA A */ + case 0x2F: state.a = ~state.a; + state.flags.ac = 1; + state.flags.n = 1; + break; + + /* JRNC */ + case 0x30: cycles_step(); + + if (!state.flags.cy) + state.pc += (int8_t) mmu_read(state.pc + 1); + + b = 2; + break; + + /* LXI SP */ + case 0x31: state.sp = ADDR; + b = 3; + break; + + /* LDD (HL), A */ + case 0x32: mmu_write(*state.hl, state.a); + (*state.hl)--; + break; + + /* INX SP */ + case 0x33: state.sp++; + cycles_step(); + break; + + /* INR M */ + case 0x34: mmu_write(*state.hl, z80_inr(mmu_read(*state.hl))); + break; + + /* DCR M */ + case 0x35: mmu_write(*state.hl, z80_dcr(mmu_read(*state.hl))); + break; + + /* MVI M */ + case 0x36: mmu_move(*state.hl, state.pc + 1); + b = 2; + break; + + /* STC */ + case 0x37: state.flags.cy = 1; + state.flags.ac = 0; + state.flags.n = 0; + break; + + /* JRC */ + case 0x38: cycles_step(); + if (state.flags.cy) + state.pc += (int8_t) mmu_read(state.pc + 1); + + b = 2; + break; + + /* DAD SP */ + case 0x39: *state.hl = dad_16(*state.hl, state.sp); + + /* needs 4 more cycles */ + cycles_step(); + + break; + + /* LDD A,(HL) */ + case 0x3A: state.a = mmu_read(*state.hl); + (*state.hl)--; + break; + + /* DCX SP */ + case 0x3B: state.sp--; + cycles_step(); + break; + + /* INR A */ + case 0x3C: state.a = z80_inr(state.a); + break; + + /* DCR A */ + case 0x3D: state.a = z80_dcr(state.a); + break; + + /* MVI A */ + case 0x3E: state.a = mmu_read(state.pc + 1); + b = 2; + break; + + /* CCF */ + case 0x3F: state.flags.ac = 0; + state.flags.cy = !state.flags.cy; + state.flags.n = 0; + + break; + + /* MOV B,B */ + case 0x40: state.b = state.b; + break; + + /* MOV B,C */ + case 0x41: state.b = state.c; + break; + + /* MOV B,D */ + case 0x42: state.b = state.d; + break; + + /* MOV B,E */ + case 0x43: state.b = state.e; + break; + + /* MOV B,H */ + case 0x44: state.b = state.h; + break; + + /* MOV B,L */ + case 0x45: state.b = state.l; + break; + + /* MOV B,M */ + case 0x46: state.b = mmu_read(*state.hl); + break; + + /* MOV B,A */ + case 0x47: state.b = state.a; + break; + + /* MOV C,B */ + case 0x48: state.c = state.b; + break; + + /* MOV C,C */ + case 0x49: state.c = state.c; + break; + + /* MOV C,D */ + case 0x4A: state.c = state.d; + break; + + /* MOV C,E */ + case 0x4B: state.c = state.e; + break; + + /* MOV C,H */ + case 0x4C: state.c = state.h; + break; + + /* MOV C,L */ + case 0x4D: state.c = state.l; + break; + + /* MOV C,M */ + case 0x4E: state.c = mmu_read(*state.hl); + break; + + /* MOV C,A */ + case 0x4F: state.c = state.a; + break; + + /* MOV D,B */ + case 0x50: state.d = state.b; + break; + + /* MOV D,C */ + case 0x51: state.d = state.c; + break; + + /* MOV D,D */ + case 0x52: state.d = state.d; + break; + + /* MOV D,E */ + case 0x53: state.d = state.e; + break; + + /* MOV D,H */ + case 0x54: state.d = state.h; + break; + + /* MOV D,L */ + case 0x55: state.d = state.l; + break; + + /* MOV D,M */ + case 0x56: state.d = mmu_read(*state.hl); + break; + + /* MOV D,A */ + case 0x57: state.d = state.a; + break; + + /* MOV E,B */ + case 0x58: state.e = state.b; + break; + + /* MOV E,C */ + case 0x59: state.e = state.c; + break; + + /* MOV E,D */ + case 0x5A: state.e = state.d; + break; + + /* MOV E,E */ + case 0x5B: state.e = state.e; + break; + + /* MOV E,H */ + case 0x5C: state.e = state.h; + break; + + /* MOV E,L */ + case 0x5D: state.e = state.l; + break; + + /* MOV E,M */ + case 0x5E: state.e = mmu_read(*state.hl); + break; + + /* MOV E,A */ + case 0x5F: state.e = state.a; + break; + + /* MOV H,B */ + case 0x60: state.h = state.b; + break; + + /* MOV H,C */ + case 0x61: state.h = state.c; + break; + + /* MOV H,D */ + case 0x62: state.h = state.d; + break; + + /* MOV H,E */ + case 0x63: state.h = state.e; + break; + + /* MOV H,H */ + case 0x64: state.h = state.h; + break; + + /* MOV H,L */ + case 0x65: state.h = state.l; + break; + + /* MOV H,M */ + case 0x66: state.h = mmu_read(*state.hl); + break; + + /* MOV H,A */ + case 0x67: state.h = state.a; + break; + + /* MOV L,B */ + case 0x68: state.l = state.b; + break; + + /* MOV L,C */ + case 0x69: state.l = state.c; + break; + + /* MOV L,D */ + case 0x6A: state.l = state.d; + break; + + /* MOV L,E */ + case 0x6B: state.l = state.e; + break; + + /* MOV L,H */ + case 0x6C: state.l = state.h; + break; + + /* MOV L,L */ + case 0x6D: state.l = state.l; + break; + + /* MOV L,M */ + case 0x6E: state.l = mmu_read(*state.hl); + break; + + /* MOV L,A */ + case 0x6F: state.l = state.a; + break; + + /* MOV M,B */ + case 0x70: mmu_write(*state.hl, state.b); + break; + + /* MOV M,C */ + case 0x71: mmu_write(*state.hl, state.c); + break; + + /* MOV M,D */ + case 0x72: mmu_write(*state.hl, state.d); + break; + + /* MOV M,E */ + case 0x73: mmu_write(*state.hl, state.e); + break; + + /* MOV M,H */ + case 0x74: mmu_write(*state.hl, state.h); + break; + + /* MOV M,L */ + case 0x75: mmu_write(*state.hl, state.l); + break; + + /* HLT */ + case 0x76: return 1; + + /* MOV M,A */ + case 0x77: mmu_write(*state.hl, state.a); + break; + + /* MOV A,B */ + case 0x78: state.a = state.b; + break; + + /* MOV A,C */ + case 0x79: state.a = state.c; + break; + + /* MOV A,D */ + case 0x7A: state.a = state.d; + break; + + /* MOV A,E */ + case 0x7B: state.a = state.e; + break; + + /* MOV A,H */ + case 0x7C: state.a = state.h; + break; + + /* MOV A,L */ + case 0x7D: state.a = state.l; + break; + + /* MOV A,M */ + case 0x7E: state.a = mmu_read(*state.hl); + break; + + /* MOV A,A */ + case 0x7F: state.a = state.a; + break; + + /* ADD B */ + case 0x80: z80_add(state.b); + break; + + /* ADD C */ + case 0x81: z80_add(state.c); + break; + + /* ADD D */ + case 0x82: z80_add(state.d); + break; + + /* ADD E */ + case 0x83: z80_add(state.e); + break; + + /* ADD H */ + case 0x84: z80_add(state.h); + break; + + /* ADD L */ + case 0x85: z80_add(state.l); + break; + + /* ADD M */ + case 0x86: z80_add(mmu_read(*state.hl)); + break; + + /* ADD A */ + case 0x87: z80_add(state.a); + break; + + /* ADC B */ + case 0x88: z80_adc(state.b); + break; + + /* ADC C */ + case 0x89: z80_adc(state.c); + break; + + /* ADC D */ + case 0x8A: z80_adc(state.d); + break; + + /* ADC E */ + case 0x8B: z80_adc(state.e); + break; + + /* ADC H */ + case 0x8C: z80_adc(state.h); + break; + + /* ADC L */ + case 0x8D: z80_adc(state.l); + break; + + /* ADC M */ + case 0x8E: z80_adc(mmu_read(*state.hl)); + break; + + /* ADC A */ + case 0x8F: z80_adc(state.a); + break; + + /* SUB B */ + case 0x90: z80_sub(state.b); + break; + + /* SUB C */ + case 0x91: z80_sub(state.c); + break; + + /* SUB D */ + case 0x92: z80_sub(state.d); + break; + + /* SUB E */ + case 0x93: z80_sub(state.e); + break; + + /* SUB H */ + case 0x94: z80_sub(state.h); + break; + + /* SUB L */ + case 0x95: z80_sub(state.l); + break; + + /* SUB M */ + case 0x96: z80_sub(mmu_read(*state.hl)); + break; + + /* SUB A */ + case 0x97: z80_sub(state.a); + break; + + /* SBC B */ + case 0x98: z80_sbc(state.b); + break; + + /* SBC C */ + case 0x99: z80_sbc(state.c); + break; + + /* SBC D */ + case 0x9a: z80_sbc(state.d); + break; + + /* SBC E */ + case 0x9b: z80_sbc(state.e); + break; + + /* SBC H */ + case 0x9c: z80_sbc(state.h); + break; + + /* SBC L */ + case 0x9d: z80_sbc(state.l); + break; + + /* SBC M */ + case 0x9E: z80_sbc(mmu_read(*state.hl)); + break; + + /* SBC A */ + case 0x9f: z80_sbc(state.a); + break; + + /* ANA B */ + case 0xA0: z80_ana(state.b); + break; + + /* ANA C */ + case 0xA1: z80_ana(state.c); + break; + + /* ANA D */ + case 0xA2: z80_ana(state.d); + break; + + /* ANA E */ + case 0xA3: z80_ana(state.e); + break; + + /* ANA H */ + case 0xA4: z80_ana(state.h); + break; + + /* ANA L */ + case 0xA5: z80_ana(state.l); + break; + + /* ANA M */ + case 0xA6: z80_ana(mmu_read(*state.hl)); + break; + + /* ANA A */ + case 0xA7: z80_ana(state.a); + break; + + /* XRA B */ + case 0xA8: z80_xra(state.b); + break; + + /* XRA C */ + case 0xA9: z80_xra(state.c); + break; + + /* XRA D */ + case 0xAA: z80_xra(state.d); + break; + + /* XRA E */ + case 0xAB: z80_xra(state.e); + break; + + /* XRA H */ + case 0xAC: z80_xra(state.h); + break; + + /* XRA L */ + case 0xAD: z80_xra(state.l); + break; + + /* XRA M */ + case 0xAE: z80_xra(mmu_read(*state.hl)); + break; + + /* XRA A */ + case 0xAF: z80_xra(state.a); + break; + + /* ORA B */ + case 0xB0: z80_ora(state.b); + break; + + /* ORA C */ + case 0xB1: z80_ora(state.c); + break; + + /* ORA D */ + case 0xB2: z80_ora(state.d); + break; + + /* ORA E */ + case 0xB3: z80_ora(state.e); + break; + + /* ORA H */ + case 0xB4: z80_ora(state.h); + break; + + /* ORA L */ + case 0xB5: z80_ora(state.l); + break; + + /* ORA M */ + case 0xB6: z80_ora(mmu_read(*state.hl)); + break; + + /* ORA A */ + case 0xB7: z80_ora(state.a); + break; + + /* CMP B */ + case 0xB8: z80_cmp(state.b); + break; + + /* CMP C */ + case 0xB9: z80_cmp(state.c); + break; + + /* CMP D */ + case 0xBA: z80_cmp(state.d); + break; + + /* CMP E */ + case 0xBB: z80_cmp(state.e); + break; + + /* CMP H */ + case 0xBC: z80_cmp(state.h); + break; + + /* CMP L */ + case 0xBD: z80_cmp(state.l); + break; + + /* CMP M */ + case 0xBE: z80_cmp(mmu_read(*state.hl)); + break; + + /* CMP A */ + case 0xBF: z80_cmp(state.a); + break; + + /* RNZ */ + case 0xC0: cycles_step(); + + if (state.flags.z == 0) + return z80_ret(); + + break; + + /* POP B */ + case 0xC1: *state.bc = mmu_read_16(state.sp); + state.sp += 2; + break; + + /* JNZ addr */ + case 0xC2: /* this will add 8 cycles */ + addr = ADDR; + + if (state.flags.z == 0) + { + /* add 4 more cycles */ + cycles_step(); + + state.pc = addr; + return 0; + } + + b = 3; + break; + + /* JMP addr */ + case 0xC3: state.pc = ADDR; + + /* add 4 cycles */ + cycles_step(); + + return 0; + + /* CNZ */ + case 0xC4: addr = ADDR; + + if (state.flags.z == 0) + return z80_call(addr); + + b = 3; + break; + + /* PUSH B */ + case 0xC5: cycles_step(); + mmu_write_16(state.sp - 2, *state.bc); + state.sp -= 2; + break; + + /* ADI */ + case 0xC6: z80_add(mmu_read(state.pc + 1)); + b = 2; + break; + + /* RST 0 */ + case 0xC7: state.pc++; + return z80_intr(0x0008 * 0); + + /* RZ */ + case 0xC8: cycles_step(); + + if (state.flags.z) + return z80_ret(); + + break; + + /* RET */ + case 0xC9: return z80_ret(); + + /* JZ */ + case 0xCA: /* add 8 cycles */ + addr = ADDR; + + if (state.flags.z) + { + /* add 4 more cycles */ + cycles_step(); + + state.pc = addr; + return 0; + } + + b = 3; + break; + + /* CB */ + case 0xCB: b = z80_ext_cb_execute(); + break; + + /* CZ */ + case 0xCC: addr = ADDR; + + if (state.flags.z) + return z80_call(addr); + + b = 3; + break; + + /* CALL addr */ + case 0xCD: return z80_call(ADDR); + + /* ACI */ + case 0xCE: z80_adc(mmu_read(state.pc + 1)); + b = 2; + break; + + /* RST 1 */ + case 0xCF: state.pc++; + return z80_intr(0x0008 * 1); + + /* RNC */ + case 0xD0: cycles_step(); + + if (state.flags.cy == 0) + return z80_ret(); + + break; + + /* POP D */ + case 0xD1: *state.de = mmu_read_16(state.sp); + state.sp += 2; + break; + + /* JNC */ + case 0xD2: /* add 8 cycles */ + addr = ADDR; + + if (state.flags.cy == 0) + { + /* add 4 more cycles */ + cycles_step(); + + state.pc = addr; + return 0; + } + + b = 3; + break; + + /* not present */ + case 0xD3: // b = 2; + break; + + /* CNC */ + case 0xD4: addr = ADDR; + + if (state.flags.cy == 0) + return z80_call(addr); + + b = 3; + break; + + /* PUSH D */ + case 0xD5: cycles_step(); + mmu_write_16(state.sp - 2, *state.de); + state.sp -= 2; + break; + + /* SUI */ + case 0xD6: z80_sub(mmu_read(state.pc + 1)); + b = 2; + break; + + /* RST 2 */ + case 0xD7: state.pc++; + return z80_intr(0x0008 * 2); + + /* RC */ + case 0xD8: cycles_step(); + + if (state.flags.cy) + return z80_ret(); + + break; + + /* RETI */ + case 0xD9: state.int_enable = 1; + return z80_ret(); + break; + + /* JC */ + case 0xDA: /* add 8 cycles */ + addr = ADDR; + + if (state.flags.cy) + { + /* add 4 more cycles */ + cycles_step(); + + state.pc = addr; + return 0; + } + + b = 3; + break; + + /* not present */ + case 0xDB: break; + + /* CC */ + case 0xDC: addr = ADDR; + + if (state.flags.cy) + return z80_call(addr); + + b = 3; + break; + + /* SBI */ + case 0xDE: z80_sbc(mmu_read(state.pc + 1)); + b = 2; + break; + + /* RST 3 */ + case 0xDF: state.pc++; + return z80_intr(0x0008 * 3); + + /* LD (FF00+N),A */ + case 0xE0: mmu_write(0xFF00 + mmu_read(state.pc + 1), state.a); + b = 2; + break; + + /* POP H */ + case 0xE1: *state.hl = mmu_read_16(state.sp); + state.sp += 2; + break; + + /* LD (FF00+C),A */ + case 0xE2: mmu_write(0xFF00 + state.c, state.a); + break; + + /* not present on Gameboy Z80 */ + case 0xE3: + case 0xE4: break; + + /* PUSH H */ + case 0xE5: cycles_step(); + mmu_write_16(state.sp - 2, *state.hl); + state.sp -= 2; + break; + + /* ANI */ + case 0xE6: z80_ana(mmu_read(state.pc + 1)); + b = 2; + break; + + /* RST 4 */ + case 0xE7: state.pc++; + return z80_intr(0x0008 * 4); + + /* ADD SP,dd */ + case 0xE8: byte = mmu_read(state.pc + 1); + byte2 = (uint8_t) (state.sp & 0x00ff); + result = byte2 + byte; + + state.flags.z = 0; + state.flags.n = 0; + + state.flags.cy = (result > 0xff); + + /* add 8 cycles */ + cycles_step(); + cycles_step(); + + /* calc xor for AC */ + z80_set_flags_ac(byte2, byte, result); + + /* set sp */ + state.sp += (int8_t) byte; // result & 0xffff; + + b = 2; + break; + + /* PCHL */ + case 0xE9: state.pc = *state.hl; + return 0; + + /* LD (NN),A */ + case 0xEA: mmu_write(ADDR, state.a); + b = 3; + break; + + /* not present on Gameboy Z80 */ + case 0xEB: + case 0xEC: + case 0xED: break; + + /* XRI */ + case 0xEE: z80_xra(mmu_read(state.pc + 1)); + b = 2; + break; + + /* RST 5 */ + case 0xEF: state.pc++; + return z80_intr(0x0008 * 5); + + /* LD A,(FF00+N) */ + case 0xF0: state.a = mmu_read(0xFF00 + mmu_read(state.pc + 1)); + b = 2; + break; + + /* POP PSW */ + case 0xF1: p = (uint8_t *) &state.flags; + *p = (mmu_read(state.sp) & 0xf0); + state.a = mmu_read(state.sp + 1); + + state.sp += 2; + break; + + /* LD A,(FF00+C) */ + case 0xF2: state.a = mmu_read(0xFF00 + state.c); + break; + + /* DI */ + case 0xF3: state.int_enable = 0; + break; + + /* not present on Gameboy Z80 */ + case 0xF4: break; + + /* PUSH PSW */ + case 0xF5: p = (uint8_t *) &state.flags; + + cycles_step(); + + mmu_write(state.sp - 1, state.a); + mmu_write(state.sp - 2, *p); + state.sp -= 2; + break; + + /* ORI */ + case 0xF6: z80_ora(mmu_read(state.pc + 1)); + b = 2; + break; + + /* RST 6 */ + case 0xF7: state.pc++; + return z80_intr(0x0008 * 6); + + /* LD HL,SP+dd */ + case 0xF8: byte = mmu_read(state.pc + 1); + byte2 = (uint8_t) (state.sp & 0x00ff); + result = byte2 + byte; + + state.flags.z = 0; + state.flags.n = 0; + + state.flags.cy = (result > 0xff); + + /* add 4 cycles */ + cycles_step(); + + /* calc xor for AC */ + z80_set_flags_ac(byte2, byte, result); + + /* set sp */ + *state.hl = state.sp + (int8_t) byte; // result & 0xffff; + + b = 2; + break; + + /* SPHL */ + case 0xF9: cycles_step(); + state.sp = *state.hl; + break; + + /* LD A, (NN) */ + case 0xFA: state.a = mmu_read(ADDR); + b = 3; + break; + + /* EI */ + case 0xFB: state.int_enable = 1; + break; + + /* not present on Gameboy Z80 */ + case 0xFC: + case 0xFD: break; + + /* CPI */ + case 0xFE: z80_cmp(mmu_read(state.pc + 1)); + b = 2; + break; + + /* RST 7 */ + case 0xFF: state.pc++; + return z80_intr(0x0008 * 7); + + default: return 1; + } + + /* make the PC points to the next instruction */ + state.pc += b; + + return 0; +} + +/* init registers, flags and state.memory of Gameboy Z80 CPU */ +z80_state_t static *z80_init() +{ + /* wipe all the structs */ + bzero(&state, sizeof(z80_state_t)); + +/* 16 bit values just point to the first reg of the pairs */ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + state.hl = (uint16_t *) &state.l; + state.bc = (uint16_t *) &state.c; + state.de = (uint16_t *) &state.e; +#else + state.hl = (uint16_t *) &state.h; + state.bc = (uint16_t *) &state.b; + state.de = (uint16_t *) &state.d; +#endif + + state.sp = 0xffff; + state.a = 0xff; + + state.b = 0x7f; + state.c = 0xbc; + state.d = 0x00; + state.e = 0x00; + state.h = 0x34; + state.l = 0xc0; + + regs_dst = malloc(8 * sizeof(uint8_t *)); + + regs_dst[0x00] = &state.b; + regs_dst[0x01] = &state.c; + regs_dst[0x02] = &state.d; + regs_dst[0x03] = &state.e; + regs_dst[0x04] = &state.h; + regs_dst[0x05] = &state.l; + regs_dst[0x06] = &dummy; + regs_dst[0x07] = &state.a; + + regs_src = malloc(8 * sizeof(uint8_t *)); + + regs_src[0x00] = &state.b; + regs_src[0x01] = &state.c; + regs_src[0x02] = &state.d; + regs_src[0x03] = &state.e; + regs_src[0x04] = &state.h; + regs_src[0x05] = &state.l; + regs_src[0x06] = mmu_addr(*state.hl); + regs_src[0x07] = &state.a; + + state.flags.cy = 1; + state.flags.n = 1; + state.flags.ac = 1; + state.flags.z = 1; + + /* flags shortcut */ + state.f = (uint8_t *) &state.flags; + + /* flags mask array */ + z80_calc_flags_mask_array(); + + return &state; +} diff --git a/waterbox/pizza/lib/z80_gameboy_regs.h b/waterbox/pizza/lib/z80_gameboy_regs.h new file mode 100644 index 0000000000..6b403005f1 --- /dev/null +++ b/waterbox/pizza/lib/z80_gameboy_regs.h @@ -0,0 +1,48 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + + +#ifndef Z80_REGS_H +#define Z80_REGS_H + +#include + +/* structs emulating z80 registers and flags */ +typedef struct z80_flags_s +{ + uint8_t spare:4; + uint8_t cy:1; + uint8_t ac:1; + uint8_t n:1; + uint8_t z:1; +} z80_flags_t; + + +/* flags offsets */ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + + #define FLAG_OFFSET_CY 4 + #define FLAG_OFFSET_AC 5 + #define FLAG_OFFSET_N 6 + #define FLAG_OFFSET_Z 7 + +#endif + + +#endif diff --git a/waterbox/pizza/pizza.c b/waterbox/pizza/pizza.c new file mode 100644 index 0000000000..83ab83b7e2 --- /dev/null +++ b/waterbox/pizza/pizza.c @@ -0,0 +1,419 @@ +/* + + This file is part of Emu-Pizza + + Emu-Pizza is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Emu-Pizza is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Emu-Pizza. If not, see . + +*/ + +#include +#include +#include +#include +#include +#include + +#include "cartridge.h" +#include "cycles.h" +#include "gameboy.h" +#include "global.h" +#include "gpu.h" +#include "input.h" +#include "network.h" +#include "sound.h" +#include "serial.h" + +/* proto */ +void cb(); +void connected_cb(); +void disconnected_cb(); +void rumble_cb(uint8_t rumble); +void network_send_data(uint8_t v); +void *start_thread(void *args); +void *start_thread_network(void *args); + +/* frame buffer pointer */ +uint16_t *fb; + +/* magnify rate */ +float magnify_rate = 1.f; + +/* emulator thread */ +pthread_t thread; + +/* SDL video stuff */ +SDL_Window *window; +SDL_Surface *screenSurface; +SDL_Surface *windowSurface; + +/* cartridge name */ +char cart_name[64]; + + +int main(int argc, char **argv) +{ + /* SDL variables */ + SDL_Event e; + SDL_AudioSpec desired; + SDL_AudioSpec obtained; + + /* init global variables */ + global_init(); + + /* set global folder */ + snprintf(global_save_folder, sizeof(global_save_folder), "/tmp/str/save/"); + __mkdirp(global_save_folder, S_IRWXU); + + /* first, load cartridge */ + char ret = cartridge_load(argv[1]); + + if (ret != 0) + return 1; + + /* apply cheat */ + + /* tetris */ +/* mmu_set_cheat("00063D6E9"); + mmu_set_cheat("3E064D5D0"); + mmu_set_cheat("04065D087"); */ + + /* samurai shodown */ + // mmu_set_cheat("11F86E3B6"); + //) + // mmu_set_cheat("3EB60D7F1"); + // + + /* gameshark aladdin */ + // mmu_set_cheat("01100ADC"); + + /* gameshark wario land */ + // mmu_set_cheat("809965A9"); + + // mmu_apply_gg(); + + /* initialize SDL video */ + if (SDL_Init(SDL_INIT_VIDEO) < 0 ) + { + printf( "SDL could not initialize! SDL_Error: %s\n", SDL_GetError() ); + return 1; + } + + window = SDL_CreateWindow("Emu Pizza - Gameboy", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + 160 * magnify_rate, 144 * magnify_rate, + SDL_WINDOW_SHOWN); + + /* get window surface */ + windowSurface = SDL_GetWindowSurface(window); + screenSurface = SDL_ConvertSurfaceFormat(windowSurface, + SDL_PIXELFORMAT_RGB565, + 0); + + gameboy_init(); + + /* initialize SDL audio */ + SDL_Init(SDL_INIT_AUDIO); + desired.freq = 44100; + desired.samples = SOUND_SAMPLES / 2; + desired.format = AUDIO_S16SYS; + desired.channels = 2; + desired.callback = sound_read_buffer; + desired.userdata = NULL; + + /* Open audio */ + if (SDL_OpenAudio(&desired, &obtained) == 0) + SDL_PauseAudio(0); + else + { + printf("Cannot open audio device!!\n"); + return 1; + } + + /* init GPU */ + gpu_init(&cb); + + /* set sound output rate */ + sound_set_output_rate(44100); + + /* set rumble cb */ + mmu_set_rumble_cb(&rumble_cb); + + /* get frame buffer reference */ + fb = gpu_get_frame_buffer(); + + /* start thread! */ + pthread_create(&thread, NULL, start_thread, NULL); + + /* start network thread! */ + network_start(&connected_cb, &disconnected_cb, "192.168.100.255"); + + /* loop forever */ + while (!global_quit) + { + /* aaaaaaaaaaaaaand finally, check for SDL events */ + + /* SDL_WaitEvent should be better but somehow, */ + /* it interfer with my cycles timer */ + if (SDL_PollEvent(&e) == 0) + { + usleep(100000); + continue; + } + + switch (e.type) + { + case SDL_QUIT: + global_quit = 1; + break; + + case SDL_KEYDOWN: + switch (e.key.keysym.sym) + { + case (SDLK_1): gameboy_set_pause(1); + gameboy_save_stat(0); + gameboy_set_pause(0); + break; + case (SDLK_2): gameboy_set_pause(1); + gameboy_restore_stat(0); + gameboy_set_pause(0); + break; + case (SDLK_9): network_start(&connected_cb, + &disconnected_cb, + "192.168.100.255"); break; + case (SDLK_0): network_stop(); break; + case (SDLK_q): global_quit = 1; break; + case (SDLK_d): global_debug ^= 0x01; break; + case (SDLK_s): global_slow_down = 1; break; + case (SDLK_w): global_window ^= 0x01; break; + case (SDLK_n): gameboy_set_pause(0); + global_next_frame = 1; break; + case (SDLK_PLUS): + if (global_emulation_speed != + GLOBAL_EMULATION_SPEED_4X) + { + global_emulation_speed++; + cycles_change_emulation_speed(); + sound_change_emulation_speed(); + } + + break; + case (SDLK_MINUS): + if (global_emulation_speed != + GLOBAL_EMULATION_SPEED_QUARTER) + { + global_emulation_speed--; + cycles_change_emulation_speed(); + sound_change_emulation_speed(); + } + + break; + case (SDLK_p): gameboy_set_pause(global_pause ^ 0x01); + break; + case (SDLK_m): mmu_dump_all(); break; + case (SDLK_SPACE): input_set_key_select(1); break; + case (SDLK_RETURN): input_set_key_start(1); break; + case (SDLK_UP): input_set_key_up(1); break; + case (SDLK_DOWN): input_set_key_down(1); break; + case (SDLK_RIGHT): input_set_key_right(1); break; + case (SDLK_LEFT): input_set_key_left(1); break; + case (SDLK_z): input_set_key_b(1); break; + case (SDLK_x): input_set_key_a(1); break; + } + break; + + case SDL_KEYUP: + switch (e.key.keysym.sym) + { + case (SDLK_SPACE): input_set_key_select(0); break; + case (SDLK_RETURN): input_set_key_start(0); break; + case (SDLK_UP): input_set_key_up(0); break; + case (SDLK_DOWN): input_set_key_down(0); break; + case (SDLK_RIGHT): input_set_key_right(0); break; + case (SDLK_LEFT): input_set_key_left(0); break; + case (SDLK_z): input_set_key_b(0); break; + case (SDLK_x): input_set_key_a(0); break; + } + break; + } + } + + /* join emulation thread */ + pthread_join(thread, NULL); + + /* stop network thread! */ + network_stop(); + + utils_log("Total cycles %d\n", cycles.cnt); + utils_log("Total running seconds %d\n", cycles.seconds); + + return 0; +} + +void *start_thread(void *args) +{ + /* run until break or global_quit is set */ + gameboy_run(); + + /* tell main thread it's over */ + global_quit = 1; +} + +void cb() +{ + uint16_t *pixel = screenSurface->pixels; + + /* magnify! */ + if (magnify_rate > 1) + { + int x,y,p; + float px, py = 0; + + uint16_t *line = malloc(sizeof(uint16_t) * 160 * magnify_rate); + + for (y=0; y<144; y++) + { + px = 0; + + for (x=0; x<160; x++) + { + for (; pxw, screenSurface->h, + screenSurface->format->format, + screenSurface->pixels, screenSurface->pitch, + SDL_PIXELFORMAT_ARGB8888, + windowSurface->pixels, windowSurface->pitch); + + /* Update the surface */ + SDL_UpdateWindowSurface(window); +} + +void connected_cb() +{ + utils_log("Connected\n"); +} + +void disconnected_cb() +{ + utils_log("Disconnected\n"); +} + +void rumble_cb(uint8_t rumble) +{ + if (rumble) + printf("RUMBLE\n"); +} + + +/* + * Returns 1 if a directory has been created, + * 2 if it already existed, and 0 on failure. + */ +int __mkdirp (char *path, mode_t omode) +{ + struct stat sb; + mode_t numask, oumask; + int first, last, retval; + char *p; + + p = path; + oumask = 0; + retval = 1; + + if (p[0] == '/') /* Skip leading '/'. */ + ++p; + + for (first = 1, last = 0; !last ; ++p) + { + if (p[0] == '\0') + last = 1; + else if (p[0] != '/') + continue; + + *p = '\0'; + + if (!last && p[1] == '\0') + last = 1; + + if (first) + { + oumask = umask(0); + numask = oumask & ~(S_IWUSR | S_IXUSR); + (void) umask(numask); + first = 0; + } + + if (last) + (void) umask(oumask); + + if (mkdir(path, last ? omode : S_IRWXU | S_IRWXG | S_IRWXO) < 0) + { + if (errno == EEXIST || errno == EISDIR) + { + if (stat(path, &sb) < 0) + { + retval = 0; + break; + } + else if (!S_ISDIR(sb.st_mode)) + { + if (last) + errno = EEXIST; + else + errno = ENOTDIR; + + retval = 0; + break; + } + + if (last) + retval = 2; + } + else + { + retval = 0; + break; + } + } + if (!last) + *p = '/'; + } + + if (!first && !last) + (void) umask(oumask); + + return retval; +}