From da7d9f2662c21c11a9d50f8425354d898d9b6e52 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Tue, 28 Dec 2010 12:53:15 +1100 Subject: [PATCH 01/12] Initial commit of bgameboy v000. The source tarball also included empty obj/ and out/ directories which git does not support. byuu says: Project started, so basically everything is new. It's basically a rough skeleton that mimics bsnes project structure. Eventually the src/gameboy folder will be copied into bsnes-official and used by the chip/supergameboy core. The middleware layer (supergameboy/interface) will be merged into a new chip/icd2 folder that will represent direct Super Game Boy emulation in the future. At least, if all goes according to plan. There is a simple GUI that can load ROMs, but do nothing after it. It's not hooked up to ruby yet. There is a basic system class and interface to expose the video/audio/input functions. There is a basic memory bus that doesn't support any MBCs yet. There is a CPU skeleton that only handles easy read/write access to the CPU registers (AF is a really fucked up register.) The core is not hooked up to libco yet, but I intend for it to be, so that I can run the CPU + LCD how I like. If it turns out the LCD+audio is easily enslavable, then I'll probably drop libco and just run it like a regular emulator, using a thread wrapper around it in bsnes only. We'll see. The CPU doesn't actually support any opcodes, and loading a ROM won't actually execute anything. --- Makefile | 78 +++ gameboy/Makefile | 10 + gameboy/cartridge/cartridge.cpp | 58 ++ gameboy/cartridge/cartridge.hpp | 19 + gameboy/cpu/cpu.cpp | 27 + gameboy/cpu/cpu.hpp | 10 + gameboy/cpu/registers.hpp | 79 +++ gameboy/gameboy.hpp | 35 + gameboy/interface/interface.hpp | 9 + gameboy/memory/memory.cpp | 140 ++++ gameboy/memory/memory.hpp | 28 + gameboy/system/system.cpp | 20 + gameboy/system/system.hpp | 15 + libco/amd64.c | 104 +++ libco/fiber.c | 51 ++ libco/libco.c | 23 + libco/libco.h | 34 + libco/ppc.c | 407 ++++++++++++ libco/sjlj.c | 102 +++ libco/ucontext.c | 67 ++ libco/x86.c | 93 +++ nall/Makefile | 109 ++++ nall/algorithm.hpp | 17 + nall/any.hpp | 74 +++ nall/array.hpp | 141 ++++ nall/base64.hpp | 90 +++ nall/bit.hpp | 51 ++ nall/concept.hpp | 34 + nall/config.hpp | 123 ++++ nall/crc32.hpp | 66 ++ nall/detect.hpp | 30 + nall/dictionary.hpp | 75 +++ nall/directory.hpp | 151 +++++ nall/dl.hpp | 115 ++++ nall/endian.hpp | 38 ++ nall/file.hpp | 261 ++++++++ nall/filemap.hpp | 200 ++++++ nall/foreach.hpp | 12 + nall/function.hpp | 60 ++ nall/input.hpp | 386 +++++++++++ nall/lzss.hpp | 81 +++ nall/moduloarray.hpp | 40 ++ nall/platform.hpp | 122 ++++ nall/priorityqueue.hpp | 109 ++++ nall/property.hpp | 91 +++ nall/random.hpp | 20 + nall/serial.hpp | 85 +++ nall/serializer.hpp | 146 +++++ nall/sha256.hpp | 143 ++++ nall/snes/cartridge.hpp | 868 +++++++++++++++++++++++++ nall/snes/cpu.hpp | 458 +++++++++++++ nall/snes/smp.hpp | 639 ++++++++++++++++++ nall/sort.hpp | 62 ++ nall/static.hpp | 20 + nall/stdint.hpp | 44 ++ nall/string.hpp | 32 + nall/string/base.hpp | 159 +++++ nall/string/bsv.hpp | 75 +++ nall/string/cast.hpp | 32 + nall/string/compare.hpp | 110 ++++ nall/string/convert.hpp | 153 +++++ nall/string/core.hpp | 139 ++++ nall/string/filename.hpp | 63 ++ nall/string/math.hpp | 164 +++++ nall/string/platform.hpp | 41 ++ nall/string/replace.hpp | 103 +++ nall/string/split.hpp | 58 ++ nall/string/strl.hpp | 52 ++ nall/string/strpos.hpp | 41 ++ nall/string/trim.hpp | 38 ++ nall/string/utility.hpp | 157 +++++ nall/string/variadic.hpp | 12 + nall/string/wrapper.hpp | 33 + nall/string/xml.hpp | 266 ++++++++ nall/ups.hpp | 223 +++++++ nall/utf8.hpp | 86 +++ nall/utility.hpp | 39 ++ nall/varint.hpp | 92 +++ nall/vector.hpp | 281 ++++++++ phoenix/gtk/button.cpp | 13 + phoenix/gtk/canvas.cpp | 59 ++ phoenix/gtk/checkbox.cpp | 23 + phoenix/gtk/combobox.cpp | 48 ++ phoenix/gtk/editbox.cpp | 58 ++ phoenix/gtk/font.cpp | 18 + phoenix/gtk/gtk.cpp | 190 ++++++ phoenix/gtk/gtk.hpp | 287 +++++++++ phoenix/gtk/hexeditor.cpp | 266 ++++++++ phoenix/gtk/horizontalslider.cpp | 25 + phoenix/gtk/label.cpp | 13 + phoenix/gtk/listbox.cpp | 195 ++++++ phoenix/gtk/menu.cpp | 129 ++++ phoenix/gtk/messagewindow.cpp | 65 ++ phoenix/gtk/object.cpp | 69 ++ phoenix/gtk/progressbar.cpp | 12 + phoenix/gtk/radiobox.cpp | 36 ++ phoenix/gtk/textbox.cpp | 33 + phoenix/gtk/verticalslider.cpp | 25 + phoenix/gtk/viewport.cpp | 20 + phoenix/gtk/widget.cpp | 47 ++ phoenix/gtk/window.cpp | 99 +++ phoenix/phoenix.cpp | 17 + phoenix/phoenix.hpp | 15 + phoenix/qt/button.cpp | 13 + phoenix/qt/canvas.cpp | 39 ++ phoenix/qt/checkbox.cpp | 21 + phoenix/qt/combobox.cpp | 38 ++ phoenix/qt/editbox.cpp | 36 ++ phoenix/qt/font.cpp | 14 + phoenix/qt/horizontalslider.cpp | 22 + phoenix/qt/label.cpp | 16 + phoenix/qt/listbox.cpp | 102 +++ phoenix/qt/menu.cpp | 169 +++++ phoenix/qt/messagewindow.cpp | 45 ++ phoenix/qt/object.cpp | 7 + phoenix/qt/progressbar.cpp | 16 + phoenix/qt/qt.cpp | 128 ++++ phoenix/qt/qt.hpp | 338 ++++++++++ phoenix/qt/qt.moc | 930 +++++++++++++++++++++++++++ phoenix/qt/qt.moc.hpp | 311 +++++++++ phoenix/qt/radiobox.cpp | 37 ++ phoenix/qt/textbox.cpp | 26 + phoenix/qt/verticalslider.cpp | 24 + phoenix/qt/viewport.cpp | 16 + phoenix/qt/widget.cpp | 35 + phoenix/qt/window.cpp | 77 +++ phoenix/windows/button.cpp | 10 + phoenix/windows/canvas.cpp | 60 ++ phoenix/windows/checkbox.cpp | 18 + phoenix/windows/combobox.cpp | 46 ++ phoenix/windows/editbox.cpp | 53 ++ phoenix/windows/font.cpp | 26 + phoenix/windows/horizontalslider.cpp | 25 + phoenix/windows/label.cpp | 51 ++ phoenix/windows/listbox.cpp | 114 ++++ phoenix/windows/menu.cpp | 144 +++++ phoenix/windows/messagewindow.cpp | 41 ++ phoenix/windows/object.cpp | 87 +++ phoenix/windows/phoenix.Manifest | 9 + phoenix/windows/phoenix.rc | 1 + phoenix/windows/progressbar.cpp | 18 + phoenix/windows/radiobox.cpp | 42 ++ phoenix/windows/textbox.cpp | 28 + phoenix/windows/verticalslider.cpp | 25 + phoenix/windows/viewport.cpp | 17 + phoenix/windows/widget.cpp | 40 ++ phoenix/windows/window.cpp | 96 +++ phoenix/windows/windows.cpp | 468 ++++++++++++++ phoenix/windows/windows.hpp | 288 +++++++++ ruby/audio.hpp | 23 + ruby/audio/alsa.cpp | 240 +++++++ ruby/audio/ao.cpp | 94 +++ ruby/audio/directsound.cpp | 212 ++++++ ruby/audio/openal.cpp | 210 ++++++ ruby/audio/oss.cpp | 113 ++++ ruby/audio/pulseaudio.cpp | 177 +++++ ruby/audio/pulseaudiosimple.cpp | 115 ++++ ruby/audio/xaudio2.cpp | 200 ++++++ ruby/audio/xaudio2.hpp | 340 ++++++++++ ruby/input.hpp | 22 + ruby/input/carbon.cpp | 157 +++++ ruby/input/directinput.cpp | 387 +++++++++++ ruby/input/rawinput.cpp | 807 +++++++++++++++++++++++ ruby/input/sdl.cpp | 230 +++++++ ruby/input/x.cpp | 50 ++ ruby/input/xlibkeys.hpp | 264 ++++++++ ruby/ruby.cpp | 383 +++++++++++ ruby/ruby.hpp | 109 ++++ ruby/ruby_audio.cpp | 133 ++++ ruby/ruby_impl.cpp | 182 ++++++ ruby/video.hpp | 29 + ruby/video/direct3d.cpp | 469 ++++++++++++++ ruby/video/directdraw.cpp | 186 ++++++ ruby/video/gdi.cpp | 100 +++ ruby/video/glx.cpp | 237 +++++++ ruby/video/opengl.hpp | 246 +++++++ ruby/video/qtopengl.cpp | 174 +++++ ruby/video/qtraster.cpp | 126 ++++ ruby/video/sdl.cpp | 140 ++++ ruby/video/wgl.cpp | 153 +++++ ruby/video/xv.cpp | 498 ++++++++++++++ sync.sh | 20 + ui/Makefile | 71 ++ ui/base.hpp | 24 + ui/general/main-window.cpp | 46 ++ ui/general/main-window.hpp | 20 + ui/interface.cpp | 18 + ui/interface.hpp | 10 + ui/main.cpp | 26 + ui/utility/utility.cpp | 15 + ui/utility/utility.hpp | 5 + 191 files changed, 21809 insertions(+) create mode 100755 Makefile create mode 100755 gameboy/Makefile create mode 100755 gameboy/cartridge/cartridge.cpp create mode 100755 gameboy/cartridge/cartridge.hpp create mode 100755 gameboy/cpu/cpu.cpp create mode 100755 gameboy/cpu/cpu.hpp create mode 100755 gameboy/cpu/registers.hpp create mode 100755 gameboy/gameboy.hpp create mode 100755 gameboy/interface/interface.hpp create mode 100755 gameboy/memory/memory.cpp create mode 100755 gameboy/memory/memory.hpp create mode 100755 gameboy/system/system.cpp create mode 100755 gameboy/system/system.hpp create mode 100755 libco/amd64.c create mode 100755 libco/fiber.c create mode 100755 libco/libco.c create mode 100755 libco/libco.h create mode 100755 libco/ppc.c create mode 100755 libco/sjlj.c create mode 100755 libco/ucontext.c create mode 100755 libco/x86.c create mode 100755 nall/Makefile create mode 100755 nall/algorithm.hpp create mode 100755 nall/any.hpp create mode 100755 nall/array.hpp create mode 100755 nall/base64.hpp create mode 100755 nall/bit.hpp create mode 100755 nall/concept.hpp create mode 100755 nall/config.hpp create mode 100755 nall/crc32.hpp create mode 100755 nall/detect.hpp create mode 100755 nall/dictionary.hpp create mode 100755 nall/directory.hpp create mode 100755 nall/dl.hpp create mode 100755 nall/endian.hpp create mode 100755 nall/file.hpp create mode 100755 nall/filemap.hpp create mode 100755 nall/foreach.hpp create mode 100755 nall/function.hpp create mode 100755 nall/input.hpp create mode 100755 nall/lzss.hpp create mode 100755 nall/moduloarray.hpp create mode 100755 nall/platform.hpp create mode 100755 nall/priorityqueue.hpp create mode 100755 nall/property.hpp create mode 100755 nall/random.hpp create mode 100755 nall/serial.hpp create mode 100755 nall/serializer.hpp create mode 100755 nall/sha256.hpp create mode 100755 nall/snes/cartridge.hpp create mode 100755 nall/snes/cpu.hpp create mode 100755 nall/snes/smp.hpp create mode 100755 nall/sort.hpp create mode 100755 nall/static.hpp create mode 100755 nall/stdint.hpp create mode 100755 nall/string.hpp create mode 100755 nall/string/base.hpp create mode 100755 nall/string/bsv.hpp create mode 100755 nall/string/cast.hpp create mode 100755 nall/string/compare.hpp create mode 100755 nall/string/convert.hpp create mode 100755 nall/string/core.hpp create mode 100755 nall/string/filename.hpp create mode 100755 nall/string/math.hpp create mode 100755 nall/string/platform.hpp create mode 100755 nall/string/replace.hpp create mode 100755 nall/string/split.hpp create mode 100755 nall/string/strl.hpp create mode 100755 nall/string/strpos.hpp create mode 100755 nall/string/trim.hpp create mode 100755 nall/string/utility.hpp create mode 100755 nall/string/variadic.hpp create mode 100755 nall/string/wrapper.hpp create mode 100755 nall/string/xml.hpp create mode 100755 nall/ups.hpp create mode 100755 nall/utf8.hpp create mode 100755 nall/utility.hpp create mode 100755 nall/varint.hpp create mode 100755 nall/vector.hpp create mode 100755 phoenix/gtk/button.cpp create mode 100755 phoenix/gtk/canvas.cpp create mode 100755 phoenix/gtk/checkbox.cpp create mode 100755 phoenix/gtk/combobox.cpp create mode 100755 phoenix/gtk/editbox.cpp create mode 100755 phoenix/gtk/font.cpp create mode 100755 phoenix/gtk/gtk.cpp create mode 100755 phoenix/gtk/gtk.hpp create mode 100755 phoenix/gtk/hexeditor.cpp create mode 100755 phoenix/gtk/horizontalslider.cpp create mode 100755 phoenix/gtk/label.cpp create mode 100755 phoenix/gtk/listbox.cpp create mode 100755 phoenix/gtk/menu.cpp create mode 100755 phoenix/gtk/messagewindow.cpp create mode 100755 phoenix/gtk/object.cpp create mode 100755 phoenix/gtk/progressbar.cpp create mode 100755 phoenix/gtk/radiobox.cpp create mode 100755 phoenix/gtk/textbox.cpp create mode 100755 phoenix/gtk/verticalslider.cpp create mode 100755 phoenix/gtk/viewport.cpp create mode 100755 phoenix/gtk/widget.cpp create mode 100755 phoenix/gtk/window.cpp create mode 100755 phoenix/phoenix.cpp create mode 100755 phoenix/phoenix.hpp create mode 100755 phoenix/qt/button.cpp create mode 100755 phoenix/qt/canvas.cpp create mode 100755 phoenix/qt/checkbox.cpp create mode 100755 phoenix/qt/combobox.cpp create mode 100755 phoenix/qt/editbox.cpp create mode 100755 phoenix/qt/font.cpp create mode 100755 phoenix/qt/horizontalslider.cpp create mode 100755 phoenix/qt/label.cpp create mode 100755 phoenix/qt/listbox.cpp create mode 100755 phoenix/qt/menu.cpp create mode 100755 phoenix/qt/messagewindow.cpp create mode 100755 phoenix/qt/object.cpp create mode 100755 phoenix/qt/progressbar.cpp create mode 100755 phoenix/qt/qt.cpp create mode 100755 phoenix/qt/qt.hpp create mode 100755 phoenix/qt/qt.moc create mode 100755 phoenix/qt/qt.moc.hpp create mode 100755 phoenix/qt/radiobox.cpp create mode 100755 phoenix/qt/textbox.cpp create mode 100755 phoenix/qt/verticalslider.cpp create mode 100755 phoenix/qt/viewport.cpp create mode 100755 phoenix/qt/widget.cpp create mode 100755 phoenix/qt/window.cpp create mode 100755 phoenix/windows/button.cpp create mode 100755 phoenix/windows/canvas.cpp create mode 100755 phoenix/windows/checkbox.cpp create mode 100755 phoenix/windows/combobox.cpp create mode 100755 phoenix/windows/editbox.cpp create mode 100755 phoenix/windows/font.cpp create mode 100755 phoenix/windows/horizontalslider.cpp create mode 100755 phoenix/windows/label.cpp create mode 100755 phoenix/windows/listbox.cpp create mode 100755 phoenix/windows/menu.cpp create mode 100755 phoenix/windows/messagewindow.cpp create mode 100755 phoenix/windows/object.cpp create mode 100755 phoenix/windows/phoenix.Manifest create mode 100755 phoenix/windows/phoenix.rc create mode 100755 phoenix/windows/progressbar.cpp create mode 100755 phoenix/windows/radiobox.cpp create mode 100755 phoenix/windows/textbox.cpp create mode 100755 phoenix/windows/verticalslider.cpp create mode 100755 phoenix/windows/viewport.cpp create mode 100755 phoenix/windows/widget.cpp create mode 100755 phoenix/windows/window.cpp create mode 100755 phoenix/windows/windows.cpp create mode 100755 phoenix/windows/windows.hpp create mode 100755 ruby/audio.hpp create mode 100755 ruby/audio/alsa.cpp create mode 100755 ruby/audio/ao.cpp create mode 100755 ruby/audio/directsound.cpp create mode 100755 ruby/audio/openal.cpp create mode 100755 ruby/audio/oss.cpp create mode 100755 ruby/audio/pulseaudio.cpp create mode 100755 ruby/audio/pulseaudiosimple.cpp create mode 100755 ruby/audio/xaudio2.cpp create mode 100755 ruby/audio/xaudio2.hpp create mode 100755 ruby/input.hpp create mode 100755 ruby/input/carbon.cpp create mode 100755 ruby/input/directinput.cpp create mode 100755 ruby/input/rawinput.cpp create mode 100755 ruby/input/sdl.cpp create mode 100755 ruby/input/x.cpp create mode 100755 ruby/input/xlibkeys.hpp create mode 100755 ruby/ruby.cpp create mode 100755 ruby/ruby.hpp create mode 100755 ruby/ruby_audio.cpp create mode 100755 ruby/ruby_impl.cpp create mode 100755 ruby/video.hpp create mode 100755 ruby/video/direct3d.cpp create mode 100755 ruby/video/directdraw.cpp create mode 100755 ruby/video/gdi.cpp create mode 100755 ruby/video/glx.cpp create mode 100755 ruby/video/opengl.hpp create mode 100755 ruby/video/qtopengl.cpp create mode 100755 ruby/video/qtraster.cpp create mode 100755 ruby/video/sdl.cpp create mode 100755 ruby/video/wgl.cpp create mode 100755 ruby/video/xv.cpp create mode 100755 sync.sh create mode 100755 ui/Makefile create mode 100755 ui/base.hpp create mode 100755 ui/general/main-window.cpp create mode 100755 ui/general/main-window.hpp create mode 100755 ui/interface.cpp create mode 100755 ui/interface.hpp create mode 100755 ui/main.cpp create mode 100755 ui/utility/utility.cpp create mode 100755 ui/utility/utility.hpp diff --git a/Makefile b/Makefile new file mode 100755 index 00000000..4788f548 --- /dev/null +++ b/Makefile @@ -0,0 +1,78 @@ +include nall/Makefile +gameboy := gameboy +ui := ui + +# compiler +c := $(compiler) -std=gnu99 +cpp := $(subst cc,++,$(compiler)) -std=gnu++0x +flags := -O3 -fomit-frame-pointer -I. -I$(gameboy) +link := +objects := + +# profile-guided instrumentation +# flags += -fprofile-generate +# link += -lgcov + +# profile-guided optimization +# flags += -fprofile-use + +# platform +ifeq ($(platform),x) + link += -s -ldl -lX11 -lXext +else ifeq ($(platform),osx) +else ifeq ($(platform),win) + link += -mwindows +# link += -mconsole + link += -mthreads -s -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32 -lole32 + link += -enable-stdcall-fixup -Wl,-enable-auto-import -Wl,-enable-runtime-pseudo-reloc +else + unknown_platform: help; +endif + +# implicit rules +compile = \ + $(strip \ + $(if $(filter %.c,$<), \ + $(c) $(flags) $1 -c $< -o $@, \ + $(if $(filter %.cpp,$<), \ + $(cpp) $(flags) $1 -c $< -o $@ \ + ) \ + ) \ + ) + +%.o: $<; $(call compile) + +all: build; + +include $(gameboy)/Makefile +include $(ui)/Makefile + +objects := $(patsubst %,obj/%.o,$(objects)) + +# targets +build: ui_build $(objects) +ifeq ($(platform),osx) + test -d ../bgameboy.app || mkdir -p ../bgameboy.app/Contents/MacOS + $(strip $(cpp) -o ../bgameboy.app/Contents/MacOS/bgameboy $(objects) $(link)) +else + $(strip $(cpp) -o out/bgameboy $(objects) $(link)) +endif + +install: +ifeq ($(platform),x) + install -D -m 755 out/bgameboy $(DESTDIR)$(prefix)/bin/bgameboy + test -d ~/.bgameboy || mkdir ~/.bgameboy +endif + +uninstall: +ifeq ($(platform),x) + rm $(DESTDIR)$(prefix)/bin/bgameboy +endif + +clean: ui_clean + -@$(call delete,obj/*) + +archive-all: + tar -cjf bgameboy.tar.bz2 gameboy libco nall obj out phoenix ruby ui Makefile sync.sh + +help:; diff --git a/gameboy/Makefile b/gameboy/Makefile new file mode 100755 index 00000000..d368bc29 --- /dev/null +++ b/gameboy/Makefile @@ -0,0 +1,10 @@ +gameboy_objects := libco +gameboy_objects += gameboy-system gameboy-cartridge gameboy-memory gameboy-cpu +objects += $(gameboy_objects) + +obj/libco.o: libco/libco.c libco/* + +obj/gameboy-system.o: $(gameboy)/system/system.cpp $(call rwildcard,$(gameboy)/system/) +obj/gameboy-cartridge.o: $(gameboy)/cartridge/cartridge.cpp $(call rwildcard,$(gameboy)/cartridge/) +obj/gameboy-memory.o: $(gameboy)/memory/memory.cpp $(call rwildcard,$(gameboy)/memory/) +obj/gameboy-cpu.o: $(gameboy)/cpu/cpu.cpp $(call rwildcard,$(gameboy)/cpu/) diff --git a/gameboy/cartridge/cartridge.cpp b/gameboy/cartridge/cartridge.cpp new file mode 100755 index 00000000..4b7bbfdf --- /dev/null +++ b/gameboy/cartridge/cartridge.cpp @@ -0,0 +1,58 @@ +#include + +#define CARTRIDGE_CPP +namespace GameBoy { + +Cartridge cartridge; + +void Cartridge::load(uint8_t *data, unsigned size) { + bus.cartrom.copy(data, size); + + char name[17]; + memcpy(name, bus.cartrom.data + 0x0134, 16); + name[16] = 0; + info.name = name; + info.name.rtrim(); + + info.cgbflag = bus.cartrom[0x0143]; + info.sgbflag = bus.cartrom[0x0146]; + info.type = bus.cartrom[0x0147]; + + switch(bus.cartrom[0x0148]) { default: + case 0x00: info.romsize = 2 * 16 * 1024; break; + case 0x01: info.romsize = 4 * 16 * 1024; break; + case 0x02: info.romsize = 8 * 16 * 1024; break; + case 0x03: info.romsize = 16 * 16 * 1024; break; + case 0x04: info.romsize = 32 * 16 * 1024; break; + case 0x05: info.romsize = 64 * 16 * 1024; break; + case 0x06: info.romsize = 128 * 16 * 1024; break; + case 0x07: info.romsize = 256 * 16 * 1024; break; + case 0x52: info.romsize = 72 * 16 * 1024; break; + case 0x53: info.romsize = 80 * 16 * 1024; break; + case 0x54: info.romsize = 96 * 16 * 1024; break; + } + + //TODO: MBC2 always stores 0x00 here; yet it has 512x4-bits RAM + switch(bus.cartrom[0x0149]) { default: + case 0x00: info.ramsize = 0 * 1024; break; + case 0x01: info.ramsize = 2 * 1024; break; + case 0x02: info.ramsize = 8 * 1024; break; + case 0x03: info.ramsize = 32 * 1024; break; + } + + loaded = true; + system.interface->message({ "Loaded:\n", info.name }); +} + +void Cartridge::unload() { + if(loaded == false) return; + + bus.cartrom.free(); + loaded = false; +} + +Cartridge::Cartridge() { + loaded = false; +} + +} diff --git a/gameboy/cartridge/cartridge.hpp b/gameboy/cartridge/cartridge.hpp new file mode 100755 index 00000000..a19b3b7d --- /dev/null +++ b/gameboy/cartridge/cartridge.hpp @@ -0,0 +1,19 @@ +class Cartridge : property { +public: + struct Information { + string name; + uint8 cgbflag; + uint8 sgbflag; + uint8 type; + unsigned romsize; + unsigned ramsize; + } info; + + readonly loaded; + + void load(uint8_t *data, unsigned size); + void unload(); + Cartridge(); +}; + +extern Cartridge cartridge; diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp new file mode 100755 index 00000000..2a986aec --- /dev/null +++ b/gameboy/cpu/cpu.cpp @@ -0,0 +1,27 @@ +//Sharp LR35902 @ 4.19MHz + +#include + +#define CPU_CPP +namespace GameBoy { + +CPU cpu; + +void CPU::main() { + while(true) { + uint8 opcode = bus.read(r.pc++); + switch(opcode) { + case 0x00: break; //NOP + } + } +} + +void CPU::power() { + reset(); +} + +void CPU::reset() { + r.pc = 0x0100; +} + +} diff --git a/gameboy/cpu/cpu.hpp b/gameboy/cpu/cpu.hpp new file mode 100755 index 00000000..073d12c9 --- /dev/null +++ b/gameboy/cpu/cpu.hpp @@ -0,0 +1,10 @@ +class CPU { +public: + #include "registers.hpp" + + void main(); + void power(); + void reset(); +}; + +extern CPU cpu; diff --git a/gameboy/cpu/registers.hpp b/gameboy/cpu/registers.hpp new file mode 100755 index 00000000..45c8cbf7 --- /dev/null +++ b/gameboy/cpu/registers.hpp @@ -0,0 +1,79 @@ +//register base class +//the idea here is to have all registers derive from a single base class. +//this allows construction of opcodes that can take any register as input or output, +//despite the fact that behind-the-scenes, special handling is done for eg: F, AF, HL, etc. +//registers can also be chained together: eg af = 0x0000 writes both a and f. +struct Register { + virtual operator unsigned() const = 0; + virtual unsigned operator=(unsigned x) = 0; + + unsigned operator++(int) { unsigned r = *this; operator=(*this + 1); return r; } + unsigned operator--(int) { unsigned r = *this; operator=(*this - 1); return r; } + unsigned operator++() { return operator=(*this + 1); } + unsigned operator--() { return operator=(*this - 1); } + + unsigned operator |=(unsigned x) { return operator=(*this | x); } + unsigned operator ^=(unsigned x) { return operator=(*this ^ x); } + unsigned operator &=(unsigned x) { return operator=(*this & x); } + + unsigned operator<<=(unsigned x) { return operator=(*this << x); } + unsigned operator>>=(unsigned x) { return operator=(*this >> x); } + + unsigned operator +=(unsigned x) { return operator=(*this + x); } + unsigned operator -=(unsigned x) { return operator=(*this - x); } + unsigned operator *=(unsigned x) { return operator=(*this * x); } + unsigned operator /=(unsigned x) { return operator=(*this / x); } + unsigned operator %=(unsigned x) { return operator=(*this % x); } +}; + +struct Register8 : Register { + uint8 data; + operator unsigned() const { return data; } + unsigned operator=(unsigned x) { return data = x; } +}; + +struct RegisterF : Register { + bool z, n, h, c; + operator unsigned() const { return (z << 7) | (n << 6) | (h << 5) | (c << 4); } + unsigned operator=(unsigned x) { z = x & 0x80; n = x & 0x40; h = x & 0x20; c = x & 0x10; return *this; } +}; + +struct Register16 : Register { + uint16 data; + operator unsigned() const { return data; } + unsigned operator=(unsigned x) { return data = x; } +}; + +struct RegisterAF : Register { + Register8 &hi; + RegisterF &lo; + operator unsigned() const { return (hi << 8) | (lo << 0); } + unsigned operator=(unsigned x) { hi = x >> 8; lo = x >> 0; return *this; } + RegisterAF(Register8 &hi, RegisterF &lo) : hi(hi), lo(lo) {} +}; + +struct RegisterW : Register { + Register8 &hi, &lo; + operator unsigned() const { return (hi << 8) | (lo << 0); } + unsigned operator=(unsigned x) { hi = x >> 8; lo = x >> 0; return *this; } + RegisterW(Register8 &hi, Register8 &lo) : hi(hi), lo(lo) {} +}; + +struct Registers { + Register8 a; + RegisterF f; + RegisterAF af; + Register8 b; + Register8 c; + RegisterW bc; + Register8 d; + Register8 e; + RegisterW de; + Register8 h; + Register8 l; + RegisterW hl; + Register16 sp; + Register16 pc; + + Registers() : af(a, f), bc(b, c), de(d, e), hl(h, l) {} +} r; diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp new file mode 100755 index 00000000..917ea17c --- /dev/null +++ b/gameboy/gameboy.hpp @@ -0,0 +1,35 @@ +//bgameboy +//author: byuu +//project started: 2010-12-27 + +namespace GameBoy { + namespace Info { + static const char Name[] = "bgameboy"; + static const char Version[] = "000"; + } +} + +#include + +#include +#include +#include +#include +using namespace nall; + +namespace GameBoy { + typedef int8_t int8; + typedef int16_t int16; + typedef int32_t int32; + typedef int64_t int64; + + typedef uint8_t uint8; + typedef uint16_t uint16; + typedef uint32_t uint32; + typedef uint64_t uint64; + + #include + #include + #include + #include +}; diff --git a/gameboy/interface/interface.hpp b/gameboy/interface/interface.hpp new file mode 100755 index 00000000..e43a4a16 --- /dev/null +++ b/gameboy/interface/interface.hpp @@ -0,0 +1,9 @@ +class Interface { +public: + virtual void video_refresh(const uint8_t *data) {} + virtual void audio_sample(signed left, signed right) {} + virtual void input_poll() {} + virtual bool input_poll(unsigned id) {} + + virtual void message(const string &text) { print(text, "\n"); } +}; diff --git a/gameboy/memory/memory.cpp b/gameboy/memory/memory.cpp new file mode 100755 index 00000000..9bc3bc98 --- /dev/null +++ b/gameboy/memory/memory.cpp @@ -0,0 +1,140 @@ +#include + +#define MEMORY_CPP +namespace GameBoy { + +Bus bus; + +uint8_t& Memory::operator[](unsigned addr) { + return data[addr]; +} + +void Memory::allocate(unsigned size_) { + free(); + size = size_; + data = new uint8_t[size](); +} + +void Memory::copy(const uint8_t *data_, unsigned size_) { + free(); + size = size_; + data = new uint8_t[size]; + memcpy(data, data_, size); +} + +void Memory::free() { + if(data) { + delete[] data; + data = 0; + } +} + +Memory::Memory() { + data = 0; + size = 0; +} + +Memory::~Memory() { + free(); +} + +// + +uint8 Bus::read(uint16 addr) { + if(/* addr >= 0x0000 && */ addr <= 0x7fff) { //Cartridge ROM + return cartrom[addr]; + } + + if(/* addr >= 0x8000 && */ addr <= 0x9fff) { //VRAM + return vram[addr & 0x1fff]; + } + + if(/* addr >= 0xa000 && */ addr <= 0xbfff) { //Cartridge RAM + if(cartram.size == 0) return 0x00; + return cartram[addr & 0x1fff]; + } + + if(/* addr >= 0xc000 && */ addr <= 0xdfff) { //WRAM + return wram[addr & 0x1fff]; + } + + if(/* addr >= 0xe000 && */ addr <= 0xfdff) { //WRAM (mirror) + return wram[addr & 0x1fff]; + } + + if(/* addr >= 0xfe00 && */ addr <= 0xfe9f) { //OAM + return oam[addr & 0xff]; + } + + if(/* addr >= 0xfea0 && */ addr <= 0xfeff) { //unmapped + return 0x00; + } + + if(/* addr >= 0xff00 && */ addr <= 0xff7f) { //MMIO + return 0x00; + } + + if(/* addr >= 0xff80 && */ addr <= 0xfffe) { //HRAM + return hram[addr & 0x7f]; + } + + //addr == 0xffff (interrupt enable register) + return 0x00; +} + +void Bus::write(uint16 addr, uint8 data) { + if(/* addr >= 0x0000 && */ addr <= 0x7fff) { //Cartridge ROM + return; + } + + if(/* addr >= 0x8000 && */ addr <= 0x9fff) { //VRAM + vram[addr & 0x1fff] = data; + return; + } + + if(/* addr >= 0xa000 && */ addr <= 0xbfff) { //Cartridge RAM + if(cartram.size == 0) return; + cartram[addr & 0x1fff] = data; + return; + } + + if(/* addr >= 0xc000 && */ addr <= 0xdfff) { //WRAM + wram[addr & 0x1fff] = data; + return; + } + + if(/* addr >= 0xe000 && */ addr <= 0xfdff) { //WRAM (mirror) + wram[addr & 0x1fff] = data; + return; + } + + if(/* addr >= 0xfe00 && */ addr <= 0xfe9f) { //OAM + oam[addr & 0xff] = data; + return; + } + + if(/* addr >= 0xfea0 && */ addr <= 0xfeff) { //unmapped + return; + } + + if(/* addr >= 0xff00 && */ addr <= 0xff7f) { //MMIO + return; + } + + if(/* addr >= 0xff80 && */ addr <= 0xfffe) { //HRAM + hram[addr & 0x7f] = data; + return; + } + + //addr == 0xffff (interrupt enable register) + return; +} + +Bus::Bus() { + vram.allocate(8192); + wram.allocate(8192); + oam.allocate ( 160); + hram.allocate( 128); +} + +} diff --git a/gameboy/memory/memory.hpp b/gameboy/memory/memory.hpp new file mode 100755 index 00000000..4c94bb8d --- /dev/null +++ b/gameboy/memory/memory.hpp @@ -0,0 +1,28 @@ +struct Memory { + uint8_t *data; + unsigned size; + + uint8_t& operator[](unsigned addr); + void allocate(unsigned size); + void copy(const uint8_t *data, unsigned size); + void free(); + Memory(); + ~Memory(); +}; + +class Bus { +public: + Memory cartrom; + Memory cartram; + Memory vram; + Memory wram; + Memory oam; + Memory hram; + + uint8 read(uint16 addr); + void write(uint16 addr, uint8 data); + + Bus(); +}; + +extern Bus bus; diff --git a/gameboy/system/system.cpp b/gameboy/system/system.cpp new file mode 100755 index 00000000..bebae611 --- /dev/null +++ b/gameboy/system/system.cpp @@ -0,0 +1,20 @@ +#include + +#define SYSTEM_CPP +namespace GameBoy { + +System system; + +void System::init(Interface *interface_) { + interface = interface_; +} + +void System::power() { + cpu.power(); +} + +void System::reset() { + cpu.reset(); +} + +} diff --git a/gameboy/system/system.hpp b/gameboy/system/system.hpp new file mode 100755 index 00000000..ccc9b9c0 --- /dev/null +++ b/gameboy/system/system.hpp @@ -0,0 +1,15 @@ +class Interface; + +class System { +public: + void init(Interface*); + void power(); + void reset(); + +//private: + Interface *interface; +}; + +#include + +extern System system; diff --git a/libco/amd64.c b/libco/amd64.c new file mode 100755 index 00000000..5f1cfca9 --- /dev/null +++ b/libco/amd64.c @@ -0,0 +1,104 @@ +/* + libco.amd64 (2009-10-12) + author: byuu + license: public domain +*/ + +#define LIBCO_C +#include "libco.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static thread_local long long co_active_buffer[64]; +static thread_local cothread_t co_active_handle = 0; +static void (*co_swap)(cothread_t, cothread_t) = 0; + +#ifdef _WIN32 + //ABI: Win64 + static unsigned char co_swap_function[] = { + 0x48, 0x89, 0x22, 0x48, 0x8B, 0x21, 0x58, 0x48, 0x89, 0x6A, 0x08, 0x48, 0x89, 0x72, 0x10, 0x48, + 0x89, 0x7A, 0x18, 0x48, 0x89, 0x5A, 0x20, 0x4C, 0x89, 0x62, 0x28, 0x4C, 0x89, 0x6A, 0x30, 0x4C, + 0x89, 0x72, 0x38, 0x4C, 0x89, 0x7A, 0x40, 0x48, 0x81, 0xC2, 0x80, 0x00, 0x00, 0x00, 0x48, 0x83, + 0xE2, 0xF0, 0x0F, 0x29, 0x32, 0x0F, 0x29, 0x7A, 0x10, 0x44, 0x0F, 0x29, 0x42, 0x20, 0x44, 0x0F, + 0x29, 0x4A, 0x30, 0x44, 0x0F, 0x29, 0x52, 0x40, 0x44, 0x0F, 0x29, 0x5A, 0x50, 0x44, 0x0F, 0x29, + 0x62, 0x60, 0x44, 0x0F, 0x29, 0x6A, 0x70, 0x44, 0x0F, 0x29, 0xB2, 0x80, 0x00, 0x00, 0x00, 0x44, + 0x0F, 0x29, 0xBA, 0x90, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x69, 0x08, 0x48, 0x8B, 0x71, 0x10, 0x48, + 0x8B, 0x79, 0x18, 0x48, 0x8B, 0x59, 0x20, 0x4C, 0x8B, 0x61, 0x28, 0x4C, 0x8B, 0x69, 0x30, 0x4C, + 0x8B, 0x71, 0x38, 0x4C, 0x8B, 0x79, 0x40, 0x48, 0x81, 0xC1, 0x80, 0x00, 0x00, 0x00, 0x48, 0x83, + 0xE1, 0xF0, 0x0F, 0x29, 0x31, 0x0F, 0x29, 0x79, 0x10, 0x44, 0x0F, 0x29, 0x41, 0x20, 0x44, 0x0F, + 0x29, 0x49, 0x30, 0x44, 0x0F, 0x29, 0x51, 0x40, 0x44, 0x0F, 0x29, 0x59, 0x50, 0x44, 0x0F, 0x29, + 0x61, 0x60, 0x44, 0x0F, 0x29, 0x69, 0x70, 0x44, 0x0F, 0x29, 0xB1, 0x80, 0x00, 0x00, 0x00, 0x44, + 0x0F, 0x29, 0xB9, 0x90, 0x00, 0x00, 0x00, 0xFF, 0xE0, + }; + + #include + + void co_init() { + DWORD old_privileges; + VirtualProtect(co_swap_function, sizeof co_swap_function, PAGE_EXECUTE_READWRITE, &old_privileges); + } +#else + //ABI: SystemV + static unsigned char co_swap_function[] = { + 0x48, 0x89, 0x26, 0x48, 0x8B, 0x27, 0x58, 0x48, 0x89, 0x6E, 0x08, 0x48, 0x89, 0x5E, 0x10, 0x4C, + 0x89, 0x66, 0x18, 0x4C, 0x89, 0x6E, 0x20, 0x4C, 0x89, 0x76, 0x28, 0x4C, 0x89, 0x7E, 0x30, 0x48, + 0x8B, 0x6F, 0x08, 0x48, 0x8B, 0x5F, 0x10, 0x4C, 0x8B, 0x67, 0x18, 0x4C, 0x8B, 0x6F, 0x20, 0x4C, + 0x8B, 0x77, 0x28, 0x4C, 0x8B, 0x7F, 0x30, 0xFF, 0xE0, + }; + + #include + #include + + void co_init() { + unsigned long long addr = (unsigned long long)co_swap_function; + unsigned long long base = addr - (addr % sysconf(_SC_PAGESIZE)); + unsigned long long size = (addr - base) + sizeof co_swap_function; + mprotect((void*)base, size, PROT_READ | PROT_WRITE | PROT_EXEC); + } +#endif + +static void crash() { + assert(0); /* called only if cothread_t entrypoint returns */ +} + +cothread_t co_active() { + if(!co_active_handle) co_active_handle = &co_active_buffer; + return co_active_handle; +} + +cothread_t co_create(unsigned int size, void (*entrypoint)(void)) { + cothread_t handle; + if(!co_swap) { + co_init(); + co_swap = (void (*)(cothread_t, cothread_t))co_swap_function; + } + if(!co_active_handle) co_active_handle = &co_active_buffer; + size += 512; /* allocate additional space for storage */ + size &= ~15; /* align stack to 16-byte boundary */ + + if(handle = (cothread_t)malloc(size)) { + long long *p = (long long*)((char*)handle + size); /* seek to top of stack */ + *--p = (long long)crash; /* crash if entrypoint returns */ + *--p = (long long)entrypoint; /* start of function */ + *(long long*)handle = (long long)p; /* stack pointer */ + } + + return handle; +} + +void co_delete(cothread_t handle) { + free(handle); +} + +void co_switch(cothread_t handle) { + register cothread_t co_previous_handle = co_active_handle; + co_swap(co_active_handle = handle, co_previous_handle); +} + +#ifdef __cplusplus +} +#endif diff --git a/libco/fiber.c b/libco/fiber.c new file mode 100755 index 00000000..02ef5bc7 --- /dev/null +++ b/libco/fiber.c @@ -0,0 +1,51 @@ +/* + libco.win (2008-01-28) + authors: Nach, byuu + license: public domain +*/ + +#define LIBCO_C +#include "libco.h" +#define WINVER 0x0400 +#define _WIN32_WINNT 0x0400 +#define WIN32_LEAN_AND_MEAN +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static thread_local cothread_t co_active_ = 0; + +static void __stdcall co_thunk(void *coentry) { + ((void (*)(void))coentry)(); +} + +cothread_t co_active() { + if(!co_active_) { + ConvertThreadToFiber(0); + co_active_ = GetCurrentFiber(); + } + return co_active_; +} + +cothread_t co_create(unsigned int heapsize, void (*coentry)(void)) { + if(!co_active_) { + ConvertThreadToFiber(0); + co_active_ = GetCurrentFiber(); + } + return (cothread_t)CreateFiber(heapsize, co_thunk, (void*)coentry); +} + +void co_delete(cothread_t cothread) { + DeleteFiber(cothread); +} + +void co_switch(cothread_t cothread) { + co_active_ = cothread; + SwitchToFiber(cothread); +} + +#ifdef __cplusplus +} +#endif diff --git a/libco/libco.c b/libco/libco.c new file mode 100755 index 00000000..55676263 --- /dev/null +++ b/libco/libco.c @@ -0,0 +1,23 @@ +/* + libco + auto-selection module + license: public domain +*/ + +#if defined(__GNUC__) && defined(__i386__) + #include "x86.c" +#elif defined(__GNUC__) && defined(__amd64__) + #include "amd64.c" +#elif defined(__GNUC__) && defined(_ARCH_PPC) + #include "ppc.c" +#elif defined(__GNUC__) + #include "sjlj.c" +#elif defined(_MSC_VER) && defined(_M_IX86) + #include "x86.c" +#elif defined(_MSC_VER) && defined(_M_AMD64) + #include "amd64.c" +#elif defined(_MSC_VER) + #include "fiber.c" +#else + #error "libco: unsupported processor, compiler or operating system" +#endif diff --git a/libco/libco.h b/libco/libco.h new file mode 100755 index 00000000..deb954fb --- /dev/null +++ b/libco/libco.h @@ -0,0 +1,34 @@ +/* + libco + version: 0.16 (2010-12-24) + license: public domain +*/ + +#ifndef LIBCO_H +#define LIBCO_H + +#ifdef LIBCO_C + #ifdef LIBCO_MP + #define thread_local __thread + #else + #define thread_local + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* cothread_t; + +cothread_t co_active(); +cothread_t co_create(unsigned int, void (*)(void)); +void co_delete(cothread_t); +void co_switch(cothread_t); + +#ifdef __cplusplus +} +#endif + +/* ifndef LIBCO_H */ +#endif diff --git a/libco/ppc.c b/libco/ppc.c new file mode 100755 index 00000000..a6028fdb --- /dev/null +++ b/libco/ppc.c @@ -0,0 +1,407 @@ +/* + libco.ppc (2010-10-17) + author: blargg + license: public domain +*/ + +/* PowerPC 32/64 using embedded or external asm, with optional +floating-point and AltiVec save/restore */ + +#define LIBCO_C +#include "libco.h" +#include +#include +#include + +#define LIBCO_MPROTECT (__unix__ && !LIBCO_PPC_ASM) + +#if LIBCO_MPROTECT + #include + #include +#endif + +/* State format (offsets in 32-bit words) + ++0 Pointer to swap code + Rest of function descriptor for entry function ++8 PC ++10 SP + Special regs + GPRs + FPRs + VRs + stack +*/ + +enum { state_size = 1024 }; +enum { above_stack = 2048 }; +enum { stack_align = 256 }; + +static thread_local cothread_t co_active_handle = 0; + +/**** Determine environment ****/ + +#define LIBCO_PPC64 (_ARCH_PPC64 || __PPC64__ || __ppc64__ || __powerpc64__) + +/* Whether function calls are indirect through a descriptor, +or are directly to function */ +#ifndef LIBCO_PPCDESC + #if !_CALL_SYSV && (_CALL_AIX || _CALL_AIXDESC || LIBCO_PPC64) + #define LIBCO_PPCDESC 1 + #endif +#endif + +#ifdef LIBCO_PPC_ASM + + #ifdef __cplusplus + extern "C" + #endif + + /* Swap code is in ppc.S */ + void co_swap_asm( cothread_t, cothread_t ); + #define CO_SWAP_ASM( x, y ) co_swap_asm( x, y ) + +#else + +/* Swap code is here in array. Please leave dieassembly comments, +as they make it easy to see what it does, and reorder instructions +if one wants to see whether that improves performance. */ +static const uint32_t libco_ppc_code [] = { +#if LIBCO_PPC64 + 0x7d000026, /* mfcr r8 */ + 0xf8240028, /* std r1,40(r4) */ + 0x7d2802a6, /* mflr r9 */ + 0xf9c40048, /* std r14,72(r4) */ + 0xf9e40050, /* std r15,80(r4) */ + 0xfa040058, /* std r16,88(r4) */ + 0xfa240060, /* std r17,96(r4) */ + 0xfa440068, /* std r18,104(r4) */ + 0xfa640070, /* std r19,112(r4) */ + 0xfa840078, /* std r20,120(r4) */ + 0xfaa40080, /* std r21,128(r4) */ + 0xfac40088, /* std r22,136(r4) */ + 0xfae40090, /* std r23,144(r4) */ + 0xfb040098, /* std r24,152(r4) */ + 0xfb2400a0, /* std r25,160(r4) */ + 0xfb4400a8, /* std r26,168(r4) */ + 0xfb6400b0, /* std r27,176(r4) */ + 0xfb8400b8, /* std r28,184(r4) */ + 0xfba400c0, /* std r29,192(r4) */ + 0xfbc400c8, /* std r30,200(r4) */ + 0xfbe400d0, /* std r31,208(r4) */ + 0xf9240020, /* std r9,32(r4) */ + 0xe8e30020, /* ld r7,32(r3) */ + 0xe8230028, /* ld r1,40(r3) */ + 0x48000009, /* bl 1 */ + 0x7fe00008, /* trap */ + 0x91040030,/*1:stw r8,48(r4) */ + 0x80c30030, /* lwz r6,48(r3) */ + 0x7ce903a6, /* mtctr r7 */ + 0xe9c30048, /* ld r14,72(r3) */ + 0xe9e30050, /* ld r15,80(r3) */ + 0xea030058, /* ld r16,88(r3) */ + 0xea230060, /* ld r17,96(r3) */ + 0xea430068, /* ld r18,104(r3) */ + 0xea630070, /* ld r19,112(r3) */ + 0xea830078, /* ld r20,120(r3) */ + 0xeaa30080, /* ld r21,128(r3) */ + 0xeac30088, /* ld r22,136(r3) */ + 0xeae30090, /* ld r23,144(r3) */ + 0xeb030098, /* ld r24,152(r3) */ + 0xeb2300a0, /* ld r25,160(r3) */ + 0xeb4300a8, /* ld r26,168(r3) */ + 0xeb6300b0, /* ld r27,176(r3) */ + 0xeb8300b8, /* ld r28,184(r3) */ + 0xeba300c0, /* ld r29,192(r3) */ + 0xebc300c8, /* ld r30,200(r3) */ + 0xebe300d0, /* ld r31,208(r3) */ + 0x7ccff120, /* mtcr r6 */ +#else + 0x7d000026, /* mfcr r8 */ + 0x90240028, /* stw r1,40(r4) */ + 0x7d2802a6, /* mflr r9 */ + 0x91a4003c, /* stw r13,60(r4) */ + 0x91c40040, /* stw r14,64(r4) */ + 0x91e40044, /* stw r15,68(r4) */ + 0x92040048, /* stw r16,72(r4) */ + 0x9224004c, /* stw r17,76(r4) */ + 0x92440050, /* stw r18,80(r4) */ + 0x92640054, /* stw r19,84(r4) */ + 0x92840058, /* stw r20,88(r4) */ + 0x92a4005c, /* stw r21,92(r4) */ + 0x92c40060, /* stw r22,96(r4) */ + 0x92e40064, /* stw r23,100(r4) */ + 0x93040068, /* stw r24,104(r4) */ + 0x9324006c, /* stw r25,108(r4) */ + 0x93440070, /* stw r26,112(r4) */ + 0x93640074, /* stw r27,116(r4) */ + 0x93840078, /* stw r28,120(r4) */ + 0x93a4007c, /* stw r29,124(r4) */ + 0x93c40080, /* stw r30,128(r4) */ + 0x93e40084, /* stw r31,132(r4) */ + 0x91240020, /* stw r9,32(r4) */ + 0x80e30020, /* lwz r7,32(r3) */ + 0x80230028, /* lwz r1,40(r3) */ + 0x48000009, /* bl 1 */ + 0x7fe00008, /* trap */ + 0x91040030,/*1:stw r8,48(r4) */ + 0x80c30030, /* lwz r6,48(r3) */ + 0x7ce903a6, /* mtctr r7 */ + 0x81a3003c, /* lwz r13,60(r3) */ + 0x81c30040, /* lwz r14,64(r3) */ + 0x81e30044, /* lwz r15,68(r3) */ + 0x82030048, /* lwz r16,72(r3) */ + 0x8223004c, /* lwz r17,76(r3) */ + 0x82430050, /* lwz r18,80(r3) */ + 0x82630054, /* lwz r19,84(r3) */ + 0x82830058, /* lwz r20,88(r3) */ + 0x82a3005c, /* lwz r21,92(r3) */ + 0x82c30060, /* lwz r22,96(r3) */ + 0x82e30064, /* lwz r23,100(r3) */ + 0x83030068, /* lwz r24,104(r3) */ + 0x8323006c, /* lwz r25,108(r3) */ + 0x83430070, /* lwz r26,112(r3) */ + 0x83630074, /* lwz r27,116(r3) */ + 0x83830078, /* lwz r28,120(r3) */ + 0x83a3007c, /* lwz r29,124(r3) */ + 0x83c30080, /* lwz r30,128(r3) */ + 0x83e30084, /* lwz r31,132(r3) */ + 0x7ccff120, /* mtcr r6 */ +#endif + +#ifndef LIBCO_PPC_NOFP + 0xd9c400e0, /* stfd f14,224(r4) */ + 0xd9e400e8, /* stfd f15,232(r4) */ + 0xda0400f0, /* stfd f16,240(r4) */ + 0xda2400f8, /* stfd f17,248(r4) */ + 0xda440100, /* stfd f18,256(r4) */ + 0xda640108, /* stfd f19,264(r4) */ + 0xda840110, /* stfd f20,272(r4) */ + 0xdaa40118, /* stfd f21,280(r4) */ + 0xdac40120, /* stfd f22,288(r4) */ + 0xdae40128, /* stfd f23,296(r4) */ + 0xdb040130, /* stfd f24,304(r4) */ + 0xdb240138, /* stfd f25,312(r4) */ + 0xdb440140, /* stfd f26,320(r4) */ + 0xdb640148, /* stfd f27,328(r4) */ + 0xdb840150, /* stfd f28,336(r4) */ + 0xdba40158, /* stfd f29,344(r4) */ + 0xdbc40160, /* stfd f30,352(r4) */ + 0xdbe40168, /* stfd f31,360(r4) */ + 0xc9c300e0, /* lfd f14,224(r3) */ + 0xc9e300e8, /* lfd f15,232(r3) */ + 0xca0300f0, /* lfd f16,240(r3) */ + 0xca2300f8, /* lfd f17,248(r3) */ + 0xca430100, /* lfd f18,256(r3) */ + 0xca630108, /* lfd f19,264(r3) */ + 0xca830110, /* lfd f20,272(r3) */ + 0xcaa30118, /* lfd f21,280(r3) */ + 0xcac30120, /* lfd f22,288(r3) */ + 0xcae30128, /* lfd f23,296(r3) */ + 0xcb030130, /* lfd f24,304(r3) */ + 0xcb230138, /* lfd f25,312(r3) */ + 0xcb430140, /* lfd f26,320(r3) */ + 0xcb630148, /* lfd f27,328(r3) */ + 0xcb830150, /* lfd f28,336(r3) */ + 0xcba30158, /* lfd f29,344(r3) */ + 0xcbc30160, /* lfd f30,352(r3) */ + 0xcbe30168, /* lfd f31,360(r3) */ +#endif + +#ifdef __ALTIVEC__ + 0x7ca042a6, /* mfvrsave r5 */ + 0x39040180, /* addi r8,r4,384 */ + 0x39240190, /* addi r9,r4,400 */ + 0x70a00fff, /* andi. r0,r5,4095 */ + 0x90a40034, /* stw r5,52(r4) */ + 0x4182005c, /* beq- 2 */ + 0x7e8041ce, /* stvx v20,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ea049ce, /* stvx v21,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7ec041ce, /* stvx v22,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ee049ce, /* stvx v23,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f0041ce, /* stvx v24,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f2049ce, /* stvx v25,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f4041ce, /* stvx v26,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f6049ce, /* stvx v27,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f8041ce, /* stvx v28,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7fa049ce, /* stvx v29,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7fc041ce, /* stvx v30,r0,r8 */ + 0x7fe049ce, /* stvx v31,r0,r9 */ + 0x80a30034,/*2:lwz r5,52(r3) */ + 0x39030180, /* addi r8,r3,384 */ + 0x39230190, /* addi r9,r3,400 */ + 0x70a00fff, /* andi. r0,r5,4095 */ + 0x7ca043a6, /* mtvrsave r5 */ + 0x4d820420, /* beqctr */ + 0x7e8040ce, /* lvx v20,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ea048ce, /* lvx v21,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7ec040ce, /* lvx v22,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ee048ce, /* lvx v23,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f0040ce, /* lvx v24,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f2048ce, /* lvx v25,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f4040ce, /* lvx v26,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f6048ce, /* lvx v27,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f8040ce, /* lvx v28,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7fa048ce, /* lvx v29,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7fc040ce, /* lvx v30,r0,r8 */ + 0x7fe048ce, /* lvx v31,r0,r9 */ +#endif + + 0x4e800420, /* bctr */ +}; + + #if LIBCO_PPCDESC + /* Function call goes through indirect descriptor */ + #define CO_SWAP_ASM( x, y ) \ + ((void (*)( cothread_t, cothread_t )) (uintptr_t) x)( x, y ) + #else + /* Function call goes directly to code */ + #define CO_SWAP_ASM( x, y ) \ + ((void (*)( cothread_t, cothread_t )) (uintptr_t) libco_ppc_code)( x, y ) + #endif + +#endif + +static uint32_t* co_create_( unsigned size, uintptr_t entry ) +{ + uint32_t* t = (uint32_t*) malloc( size ); + + (void) entry; + + #if LIBCO_PPCDESC + if ( t ) + { + /* Copy entry's descriptor */ + memcpy( t, (void*) entry, sizeof (void*) * 3 ); + + /* Set function pointer to swap routine */ + #ifdef LIBCO_PPC_ASM + *(const void**) t = *(void**) &co_swap_asm; + #else + *(const void**) t = libco_ppc_code; + #endif + } + #endif + + return t; +} + +cothread_t co_create( unsigned int size, void (*entry_)( void ) ) +{ + uintptr_t entry = (uintptr_t) entry_; + uint32_t* t = NULL; + + /* Be sure main thread was successfully allocated */ + if ( co_active() ) + { + size += state_size + above_stack + stack_align; + t = co_create_( size, entry ); + } + + if ( t ) + { + uintptr_t sp; + int shift; + + /* Save current registers into new thread, so that any special ones will + have proper values when thread is begun */ + CO_SWAP_ASM( t, t ); + + #if LIBCO_PPCDESC + /* Get real address */ + entry = (uintptr_t) *(void**) entry; + #endif + + /* Put stack near end of block, and align */ + sp = (uintptr_t) t + size - above_stack; + sp -= sp % stack_align; + + /* On PPC32, we save and restore GPRs as 32 bits. For PPC64, we + save and restore them as 64 bits, regardless of the size the ABI + uses. So, we manually write pointers at the proper size. We always + save and restore at the same address, and since PPC is big-endian, + we must put the low byte first on PPC32. */ + + /* If uintptr_t is 32 bits, >>32 is undefined behavior, so we do two shifts + and don't have to care how many bits uintptr_t is. */ + #if LIBCO_PPC64 + shift = 16; + #else + shift = 0; + #endif + + /* Set up so entry will be called on next swap */ + t [8] = (uint32_t) (entry >> shift >> shift); + t [9] = (uint32_t) entry; + + t [10] = (uint32_t) (sp >> shift >> shift); + t [11] = (uint32_t) sp; + } + + return t; +} + +void co_delete( cothread_t t ) +{ + free( t ); +} + +static void co_init_( void ) +{ + #if LIBCO_MPROTECT + /* TODO: pre- and post-pad PPC code so that this doesn't make other + data executable and writable */ + long page_size = sysconf( _SC_PAGESIZE ); + if ( page_size > 0 ) + { + uintptr_t align = page_size; + uintptr_t begin = (uintptr_t) libco_ppc_code; + uintptr_t end = begin + sizeof libco_ppc_code; + + /* Align beginning and end */ + end += align - 1; + end -= end % align; + begin -= begin % align; + + mprotect( (void*) begin, end - begin, PROT_READ | PROT_WRITE | PROT_EXEC ); + } + #endif + + co_active_handle = co_create_( state_size, (uintptr_t) &co_switch ); +} + +cothread_t co_active() +{ + if ( !co_active_handle ) + co_init_(); + + return co_active_handle; +} + +void co_switch( cothread_t t ) +{ + cothread_t old = co_active_handle; + co_active_handle = t; + + CO_SWAP_ASM( t, old ); +} diff --git a/libco/sjlj.c b/libco/sjlj.c new file mode 100755 index 00000000..8b72b614 --- /dev/null +++ b/libco/sjlj.c @@ -0,0 +1,102 @@ +/* + libco.sjlj (2008-01-28) + author: Nach + license: public domain +*/ + +/* + * Note this was designed for UNIX systems. Based on ideas expressed in a paper + * by Ralf Engelschall. + * For SJLJ on other systems, one would want to rewrite springboard() and + * co_create() and hack the jmb_buf stack pointer. + */ + +#define LIBCO_C +#include "libco.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + sigjmp_buf context; + void (*coentry)(void); + void *stack; +} cothread_struct; + +static thread_local cothread_struct co_primary; +static thread_local cothread_struct *creating, *co_running = 0; + +static void springboard(int ignored) { + if(sigsetjmp(creating->context, 0)) { + co_running->coentry(); + } +} + +cothread_t co_active() { + if(!co_running) co_running = &co_primary; + return (cothread_t)co_running; +} + +cothread_t co_create(unsigned int size, void (*coentry)(void)) { + if(!co_running) co_running = &co_primary; + + cothread_struct *thread = (cothread_struct*)malloc(sizeof(cothread_struct)); + if(thread) { + struct sigaction handler; + struct sigaction old_handler; + + stack_t stack; + stack_t old_stack; + + thread->coentry = thread->stack = 0; + + stack.ss_flags = 0; + stack.ss_size = size; + thread->stack = stack.ss_sp = malloc(size); + if(stack.ss_sp && !sigaltstack(&stack, &old_stack)) { + handler.sa_handler = springboard; + handler.sa_flags = SA_ONSTACK; + sigemptyset(&handler.sa_mask); + creating = thread; + + if(!sigaction(SIGUSR1, &handler, &old_handler)) { + if(!raise(SIGUSR1)) { + thread->coentry = coentry; + } + sigaltstack(&old_stack, 0); + sigaction(SIGUSR1, &old_handler, 0); + } + } + + if(thread->coentry != coentry) { + co_delete(thread); + thread = 0; + } + } + + return (cothread_t)thread; +} + +void co_delete(cothread_t cothread) { + if(cothread) { + if(((cothread_struct*)cothread)->stack) { + free(((cothread_struct*)cothread)->stack); + } + free(cothread); + } +} + +void co_switch(cothread_t cothread) { + if(!sigsetjmp(co_running->context, 0)) { + co_running = (cothread_struct*)cothread; + siglongjmp(co_running->context, 1); + } +} + +#ifdef __cplusplus +} +#endif diff --git a/libco/ucontext.c b/libco/ucontext.c new file mode 100755 index 00000000..17472f6b --- /dev/null +++ b/libco/ucontext.c @@ -0,0 +1,67 @@ +/* + libco.ucontext (2008-01-28) + author: Nach + license: public domain +*/ + +/* + * WARNING: the overhead of POSIX ucontext is very high, + * assembly versions of libco or libco_sjlj should be much faster + * + * This library only exists for two reasons: + * 1 - as an initial test for the viability of a ucontext implementation + * 2 - to demonstrate the power and speed of libco over existing implementations, + * such as pth (which defaults to wrapping ucontext on unix targets) + * + * Use this library only as a *last resort* + */ + +#define LIBCO_C +#include "libco.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static thread_local ucontext_t co_primary; +static thread_local ucontext_t *co_running = 0; + +cothread_t co_active() { + if(!co_running) co_running = &co_primary; + return (cothread_t)co_running; +} + +cothread_t co_create(unsigned int heapsize, void (*coentry)(void)) { + if(!co_running) co_running = &co_primary; + ucontext_t *thread = (ucontext_t*)malloc(sizeof(ucontext_t)); + if(thread) { + if((!getcontext(thread) && !(thread->uc_stack.ss_sp = 0)) && (thread->uc_stack.ss_sp = malloc(heapsize))) { + thread->uc_link = co_running; + thread->uc_stack.ss_size = heapsize; + makecontext(thread, coentry, 0); + } else { + co_delete((cothread_t)thread); + thread = 0; + } + } + return (cothread_t)thread; +} + +void co_delete(cothread_t cothread) { + if(cothread) { + if(((ucontext_t*)cothread)->uc_stack.ss_sp) { free(((ucontext_t*)cothread)->uc_stack.ss_sp); } + free(cothread); + } +} + +void co_switch(cothread_t cothread) { + ucontext_t *old_thread = co_running; + co_running = (ucontext_t*)cothread; + swapcontext(old_thread, co_running); +} + +#ifdef __cplusplus +} +#endif diff --git a/libco/x86.c b/libco/x86.c new file mode 100755 index 00000000..d8f820b0 --- /dev/null +++ b/libco/x86.c @@ -0,0 +1,93 @@ +/* + libco.x86 (2009-10-12) + author: byuu + license: public domain +*/ + +#define LIBCO_C +#include "libco.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) + #define fastcall __fastcall +#elif defined(__GNUC__) + #define fastcall __attribute__((fastcall)) +#else + #error "libco: please define fastcall macro" +#endif + +static thread_local long co_active_buffer[64]; +static thread_local cothread_t co_active_handle = 0; +static void (fastcall *co_swap)(cothread_t, cothread_t) = 0; + +//ABI: fastcall +static unsigned char co_swap_function[] = { + 0x89, 0x22, 0x8B, 0x21, 0x58, 0x89, 0x6A, 0x04, 0x89, 0x72, 0x08, 0x89, 0x7A, 0x0C, 0x89, 0x5A, + 0x10, 0x8B, 0x69, 0x04, 0x8B, 0x71, 0x08, 0x8B, 0x79, 0x0C, 0x8B, 0x59, 0x10, 0xFF, 0xE0, +}; + +#ifdef _WIN32 + #include + + void co_init() { + DWORD old_privileges; + VirtualProtect(co_swap_function, sizeof co_swap_function, PAGE_EXECUTE_READWRITE, &old_privileges); + } +#else + #include + #include + + void co_init() { + unsigned long addr = (unsigned long)co_swap_function; + unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE)); + unsigned long size = (addr - base) + sizeof co_swap_function; + mprotect((void*)base, size, PROT_READ | PROT_WRITE | PROT_EXEC); + } +#endif + +static void crash() { + assert(0); /* called only if cothread_t entrypoint returns */ +} + +cothread_t co_active() { + if(!co_active_handle) co_active_handle = &co_active_buffer; + return co_active_handle; +} + +cothread_t co_create(unsigned int size, void (*entrypoint)(void)) { + cothread_t handle; + if(!co_swap) { + co_init(); + co_swap = (void (fastcall*)(cothread_t, cothread_t))co_swap_function; + } + if(!co_active_handle) co_active_handle = &co_active_buffer; + size += 256; /* allocate additional space for storage */ + size &= ~15; /* align stack to 16-byte boundary */ + + if(handle = (cothread_t)malloc(size)) { + long *p = (long*)((char*)handle + size); /* seek to top of stack */ + *--p = (long)crash; /* crash if entrypoint returns */ + *--p = (long)entrypoint; /* start of function */ + *(long*)handle = (long)p; /* stack pointer */ + } + + return handle; +} + +void co_delete(cothread_t handle) { + free(handle); +} + +void co_switch(cothread_t handle) { + register cothread_t co_previous_handle = co_active_handle; + co_swap(co_active_handle = handle, co_previous_handle); +} + +#ifdef __cplusplus +} +#endif diff --git a/nall/Makefile b/nall/Makefile new file mode 100755 index 00000000..9a93bd23 --- /dev/null +++ b/nall/Makefile @@ -0,0 +1,109 @@ +# Makefile +# author: byuu +# license: public domain + +[A-Z] = A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +[a-z] = a b c d e f g h i j k l m n o p q r s t u v w x y z +[0-9] = 0 1 2 3 4 5 6 7 8 9 +[markup] = ` ~ ! @ \# $$ % ^ & * ( ) - _ = + [ { ] } \ | ; : ' " , < . > / ? +[all] = $([A-Z]) $([a-z]) $([0-9]) $([markup]) +[space] := +[space] += + +##### +# platform detection +##### + +ifeq ($(platform),) + uname := $(shell uname -a) + ifeq ($(uname),) + platform := win + delete = del $(subst /,\,$1) + else ifneq ($(findstring Darwin,$(uname)),) + platform := osx + delete = rm -f $1 + else + platform := x + delete = rm -f $1 + endif +endif + +ifeq ($(compiler),) + ifeq ($(platform),win) + compiler := gcc + else ifeq ($(platform),osx) + compiler := gcc-mp-4.5 + else + compiler := gcc-4.5 + endif +endif + +ifeq ($(prefix),) + prefix := /usr/local +endif + +##### +# function rwildcard(directory, pattern) +##### +rwildcard = \ + $(strip \ + $(filter $(if $2,$2,%), \ + $(foreach f, \ + $(wildcard $1*), \ + $(eval t = $(call rwildcard,$f/)) \ + $(if $t,$t,$f) \ + ) \ + ) \ + ) + +##### +# function strtr(source, from, to) +##### +strtr = \ + $(eval __temp := $1) \ + $(strip \ + $(foreach c, \ + $(join $(addsuffix :,$2),$3), \ + $(eval __temp := \ + $(subst $(word 1,$(subst :, ,$c)),$(word 2,$(subst :, ,$c)),$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) + +##### +# function strupper(source) +##### +strupper = $(call strtr,$1,$([a-z]),$([A-Z])) + +##### +# function strlower(source) +##### +strlower = $(call strtr,$1,$([A-Z]),$([a-z])) + +##### +# function strlen(source) +##### +strlen = \ + $(eval __temp := $(subst $([space]),_,$1)) \ + $(words \ + $(strip \ + $(foreach c, \ + $([all]), \ + $(eval __temp := \ + $(subst $c,$c ,$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) \ + ) + +##### +# function streq(source) +##### +streq = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),,1) + +##### +# function strne(source) +##### +strne = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),1,) diff --git a/nall/algorithm.hpp b/nall/algorithm.hpp new file mode 100755 index 00000000..037f0bb7 --- /dev/null +++ b/nall/algorithm.hpp @@ -0,0 +1,17 @@ +#ifndef NALL_ALGORITHM_HPP +#define NALL_ALGORITHM_HPP + +#undef min +#undef max + +namespace nall { + template T min(const T &t, const U &u) { + return t < u ? t : u; + } + + template T max(const T &t, const U &u) { + return t > u ? t : u; + } +} + +#endif diff --git a/nall/any.hpp b/nall/any.hpp new file mode 100755 index 00000000..b31cff3c --- /dev/null +++ b/nall/any.hpp @@ -0,0 +1,74 @@ +#ifndef NALL_ANY_HPP +#define NALL_ANY_HPP + +#include +#include +#include + +namespace nall { + class any { + public: + bool empty() const { return container; } + const std::type_info& type() const { return container ? container->type() : typeid(void); } + + template any& operator=(const T& value_) { + typedef typename static_if< + std::is_array::value, + typename std::remove_extent::type>::type*, + T + >::type auto_t; + + if(type() == typeid(auto_t)) { + static_cast*>(container)->value = (auto_t)value_; + } else { + if(container) delete container; + container = new holder((auto_t)value_); + } + + return *this; + } + + any() : container(0) {} + template any(const T& value_) : container(0) { operator=(value_); } + + private: + struct placeholder { + virtual const std::type_info& type() const = 0; + } *container; + + template struct holder : placeholder { + T value; + const std::type_info& type() const { return typeid(T); } + holder(const T& value_) : value(value_) {} + }; + + template friend T any_cast(any&); + template friend T any_cast(const any&); + template friend T* any_cast(any*); + template friend const T* any_cast(const any*); + }; + + template T any_cast(any &value) { + typedef typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T any_cast(const any &value) { + typedef const typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T* any_cast(any *value) { + if(!value || value->type() != typeid(T)) return 0; + return &static_cast*>(value->container)->value; + } + + template const T* any_cast(const any *value) { + if(!value || value->type() != typeid(T)) return 0; + return &static_cast*>(value->container)->value; + } +} + +#endif diff --git a/nall/array.hpp b/nall/array.hpp new file mode 100755 index 00000000..9cfe7758 --- /dev/null +++ b/nall/array.hpp @@ -0,0 +1,141 @@ +#ifndef NALL_ARRAY_HPP +#define NALL_ARRAY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + //dynamic vector array + //neither constructor nor destructor is ever invoked; + //thus, this should only be used for POD objects. + template class array { + protected: + T *pool; + unsigned poolsize, buffersize; + + public: + unsigned size() const { return buffersize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) free(pool); + pool = 0; + poolsize = 0; + buffersize = 0; + } + + void reserve(unsigned newsize) { + if(newsize == poolsize) return; + + pool = (T*)realloc(pool, newsize * sizeof(T)); + poolsize = newsize; + buffersize = min(buffersize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(bit::round(newsize)); //round reserve size up to power of 2 + buffersize = newsize; + } + + T* get(unsigned minsize = 0) { + if(minsize > buffersize) resize(minsize); + if(minsize > buffersize) throw "array[] out of bounds"; + return pool; + } + + void append(const T data) { + operator[](buffersize) = data; + } + + template void insert(unsigned index, const U list) { + unsigned listsize = container_size(list); + resize(buffersize + listsize); + memmove(pool + index + listsize, pool + index, (buffersize - index) * sizeof(T)); + foreach(item, list) pool[index++] = item; + } + + void insert(unsigned index, const T item) { + insert(index, array{ item }); + } + + void remove(unsigned index, unsigned count = 1) { + for(unsigned i = index; count + i < buffersize; i++) { + pool[i] = pool[count + i]; + } + if(count + index >= buffersize) resize(index); //every element >= index was removed + else resize(buffersize - count); + } + + optional find(const T data) { + for(unsigned i = 0; i < size(); i++) if(pool[i] == data) return { true, i }; + return { false, 0 }; + } + + void clear() { + memset(pool, 0, buffersize * sizeof(T)); + } + + array() : pool(0), poolsize(0), buffersize(0) { + } + + array(std::initializer_list list) : pool(0), poolsize(0), buffersize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) append(*p); + } + + ~array() { + reset(); + } + + //copy + array& operator=(const array &source) { + if(pool) free(pool); + buffersize = source.buffersize; + poolsize = source.poolsize; + pool = (T*)malloc(sizeof(T) * poolsize); //allocate entire pool size, + memcpy(pool, source.pool, sizeof(T) * buffersize); //... but only copy used pool objects + return *this; + } + + array(const array &source) : pool(0), poolsize(0), buffersize(0) { + operator=(source); + } + + //move + array& operator=(array &&source) { + if(pool) free(pool); + pool = source.pool; + poolsize = source.poolsize; + buffersize = source.buffersize; + source.pool = 0; + source.reset(); + return *this; + } + + array(array &&source) : pool(0), poolsize(0), buffersize(0) { + operator=(std::move(source)); + } + + //index + inline T& operator[](unsigned index) { + if(index >= buffersize) resize(index + 1); + if(index >= buffersize) throw "array[] out of bounds"; + return pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= buffersize) throw "array[] out of bounds"; + return pool[index]; + } + }; + + template struct has_size> { enum { value = true }; }; +} + +#endif diff --git a/nall/base64.hpp b/nall/base64.hpp new file mode 100755 index 00000000..e41c87b7 --- /dev/null +++ b/nall/base64.hpp @@ -0,0 +1,90 @@ +#ifndef NALL_BASE64_HPP +#define NALL_BASE64_HPP + +#include +#include + +namespace nall { + class base64 { + public: + static bool encode(char *&output, const uint8_t* input, unsigned inlength) { + output = new char[inlength * 8 / 6 + 6](); + + unsigned i = 0, o = 0; + while(i < inlength) { + switch(i % 3) { + case 0: { + output[o++] = enc(input[i] >> 2); + output[o] = enc((input[i] & 3) << 4); + } break; + + case 1: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 4)); + output[o] = enc((input[i] & 15) << 2); + } break; + + case 2: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 6)); + output[o++] = enc(input[i] & 63); + } break; + } + + i++; + } + + return true; + } + + static bool decode(uint8_t *&output, unsigned &outlength, const char *input) { + unsigned inlength = strlen(input), infix = 0; + output = new uint8_t[inlength](); + + unsigned i = 0, o = 0; + while(i < inlength) { + uint8_t x = dec(input[i]); + + switch(i++ & 3) { + case 0: { + output[o] = x << 2; + } break; + + case 1: { + output[o++] |= x >> 4; + output[o] = (x & 15) << 4; + } break; + + case 2: { + output[o++] |= x >> 2; + output[o] = (x & 3) << 6; + } break; + + case 3: { + output[o++] |= x; + } break; + } + } + + outlength = o; + return true; + } + + private: + static char enc(uint8_t n) { + static char lookup_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + return lookup_table[n & 63]; + } + + static uint8_t dec(char n) { + if(n >= 'A' && n <= 'Z') return n - 'A'; + if(n >= 'a' && n <= 'z') return n - 'a' + 26; + if(n >= '0' && n <= '9') return n - '0' + 52; + if(n == '-') return 62; + if(n == '_') return 63; + return 0; + } + }; +} + +#endif diff --git a/nall/bit.hpp b/nall/bit.hpp new file mode 100755 index 00000000..169fc144 --- /dev/null +++ b/nall/bit.hpp @@ -0,0 +1,51 @@ +#ifndef NALL_BIT_HPP +#define NALL_BIT_HPP + +namespace nall { + template inline unsigned uclamp(const unsigned x) { + enum { y = (1U << bits) - 1 }; + return y + ((x - y) & -(x < y)); //min(x, y); + } + + template inline unsigned uclip(const unsigned x) { + enum { m = (1U << bits) - 1 }; + return (x & m); + } + + template inline signed sclamp(const signed x) { + enum { b = 1U << (bits - 1), m = (1U << (bits - 1)) - 1 }; + return (x > m) ? m : (x < -b) ? -b : x; + } + + template inline signed sclip(const signed x) { + enum { b = 1U << (bits - 1), m = (1U << bits) - 1 }; + return ((x & m) ^ b) - b; + } + + namespace bit { + //lowest(0b1110) == 0b0010 + template inline T lowest(const T x) { + return x & -x; + } + + //clear_lowest(0b1110) == 0b1100 + template inline T clear_lowest(const T x) { + return x & (x - 1); + } + + //set_lowest(0b0101) == 0b0111 + template inline T set_lowest(const T x) { + return x | (x + 1); + } + + //round up to next highest single bit: + //round(15) == 16, round(16) == 16, round(17) == 32 + inline unsigned round(unsigned x) { + if((x & (x - 1)) == 0) return x; + while(x & (x - 1)) x &= x - 1; + return x << 1; + } + } +} + +#endif diff --git a/nall/concept.hpp b/nall/concept.hpp new file mode 100755 index 00000000..47167e21 --- /dev/null +++ b/nall/concept.hpp @@ -0,0 +1,34 @@ +#ifndef NALL_CONCEPT_HPP +#define NALL_CONCEPT_HPP + +#include +#include + +namespace nall { + //unsigned count() const; + template struct has_count { enum { value = false }; }; + + //unsigned length() const; + template struct has_length { enum { value = false }; }; + + //unsigned size() const; + template struct has_size { enum { value = false }; }; + + template unsigned container_size(const T& object, typename mp_enable_if>::type = 0) { + return object.count(); + } + + template unsigned container_size(const T& object, typename mp_enable_if>::type = 0) { + return object.length(); + } + + template unsigned container_size(const T& object, typename mp_enable_if>::type = 0) { + return object.size(); + } + + template unsigned container_size(const T& object, typename mp_enable_if>::type = 0) { + return sizeof(T) / sizeof(typename std::remove_extent::type); + } +} + +#endif diff --git a/nall/config.hpp b/nall/config.hpp new file mode 100755 index 00000000..b8381b16 --- /dev/null +++ b/nall/config.hpp @@ -0,0 +1,123 @@ +#ifndef NALL_CONFIG_HPP +#define NALL_CONFIG_HPP + +#include +#include +#include + +namespace nall { + namespace configuration_traits { + template struct is_boolean { enum { value = false }; }; + template<> struct is_boolean { enum { value = true }; }; + + template struct is_signed { enum { value = false }; }; + template<> struct is_signed { enum { value = true }; }; + + template struct is_unsigned { enum { value = false }; }; + template<> struct is_unsigned { enum { value = true }; }; + + template struct is_double { enum { value = false }; }; + template<> struct is_double { enum { value = true }; }; + + template struct is_string { enum { value = false }; }; + template<> struct is_string { enum { value = true }; }; + } + + class configuration { + public: + enum type_t { boolean_t, signed_t, unsigned_t, double_t, string_t, unknown_t }; + struct item_t { + uintptr_t data; + string name; + string desc; + type_t type; + + string get() const { + switch(type) { + case boolean_t: return string() << *(bool*)data; + case signed_t: return string() << *(signed*)data; + case unsigned_t: return string() << *(unsigned*)data; + case double_t: return string() << *(double*)data; + case string_t: return string() << "\"" << *(string*)data << "\""; + } + return "???"; + } + + void set(string s) { + switch(type) { + case boolean_t: *(bool*)data = (s == "true"); break; + case signed_t: *(signed*)data = integer(s); break; + case unsigned_t: *(unsigned*)data = decimal(s); break; + case double_t: *(double*)data = fp(s); break; + case string_t: s.trim("\""); *(string*)data = s; break; + } + } + }; + linear_vector list; + + template + void attach(T &data, const char *name, const char *desc = "") { + unsigned n = list.size(); + list[n].data = (uintptr_t)&data; + list[n].name = name; + list[n].desc = desc; + + if(configuration_traits::is_boolean::value) list[n].type = boolean_t; + else if(configuration_traits::is_signed::value) list[n].type = signed_t; + else if(configuration_traits::is_unsigned::value) list[n].type = unsigned_t; + else if(configuration_traits::is_double::value) list[n].type = double_t; + else if(configuration_traits::is_string::value) list[n].type = string_t; + else list[n].type = unknown_t; + } + + virtual bool load(const char *filename) { + string data; + if(data.readfile(filename) == true) { + data.replace("\r", ""); + lstring line; + line.split("\n", data); + + for(unsigned i = 0; i < line.size(); i++) { + if(auto position = qstrpos(line[i], "#")) line[i][position()] = 0; + if(!qstrpos(line[i], " = ")) continue; + + lstring part; + part.qsplit(" = ", line[i]); + part[0].trim(); + part[1].trim(); + + for(unsigned n = 0; n < list.size(); n++) { + if(part[0] == list[n].name) { + list[n].set(part[1]); + break; + } + } + } + + return true; + } else { + return false; + } + } + + virtual bool save(const char *filename) const { + file fp; + if(fp.open(filename, file::mode::write)) { + for(unsigned i = 0; i < list.size(); i++) { + string output; + output << list[i].name << " = " << list[i].get(); + if(list[i].desc != "") output << " # " << list[i].desc; + output << "\r\n"; + fp.print(output); + } + + fp.close(); + return true; + } else { + return false; + } + } + }; +} + +#endif diff --git a/nall/crc32.hpp b/nall/crc32.hpp new file mode 100755 index 00000000..ad36fbf6 --- /dev/null +++ b/nall/crc32.hpp @@ -0,0 +1,66 @@ +#ifndef NALL_CRC32_HPP +#define NALL_CRC32_HPP + +#include + +namespace nall { + const uint32_t crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + inline uint32_t crc32_adjust(uint32_t crc32, uint8_t input) { + return ((crc32 >> 8) & 0x00ffffff) ^ crc32_table[(crc32 ^ input) & 0xff]; + } + + inline uint32_t crc32_calculate(const uint8_t *data, unsigned length) { + uint32_t crc32 = ~0; + for(unsigned i = 0; i < length; i++) { + crc32 = crc32_adjust(crc32, data[i]); + } + return ~crc32; + } +} + +#endif diff --git a/nall/detect.hpp b/nall/detect.hpp new file mode 100755 index 00000000..b4991aaf --- /dev/null +++ b/nall/detect.hpp @@ -0,0 +1,30 @@ +#ifndef NALL_DETECT_HPP +#define NALL_DETECT_HPP + +/* Compiler detection */ + +#if defined(__GNUC__) + #define COMPILER_GCC +#elif defined(_MSC_VER) + #define COMPILER_VISUALC +#endif + +/* Platform detection */ + +#if defined(_WIN32) + #define PLATFORM_WIN +#elif defined(__APPLE__) + #define PLATFORM_OSX +#elif defined(linux) || defined(__sun__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define PLATFORM_X +#endif + +/* Endian detection */ + +#if defined(__i386__) || defined(__amd64__) || defined(_M_IX86) || defined(_M_AMD64) + #define ARCH_LSB +#elif defined(__powerpc__) || defined(_M_PPC) || defined(__BIG_ENDIAN__) + #define ARCH_MSB +#endif + +#endif diff --git a/nall/dictionary.hpp b/nall/dictionary.hpp new file mode 100755 index 00000000..dcb04151 --- /dev/null +++ b/nall/dictionary.hpp @@ -0,0 +1,75 @@ +#ifndef NALL_DICTIONARY_HPP +#define NALL_DICTIONARY_HPP + +#include +#include +#include + +namespace nall { + class dictionary { + public: + string operator[](const char *input) { + for(unsigned i = 0; i < index_input.size(); i++) { + if(index_input[i] == input) return index_output[i]; + } + + //no match, use input; remove input identifier, if one exists + if(strbegin(input, "{{")) { + if(auto pos = strpos(input, "}}")) { + string temp = substr(input, pos() + 2); + return temp; + } + } + + return input; + } + + bool import(const char *filename) { + string data; + if(data.readfile(filename) == false) return false; + data.ltrim<1>("\xef\xbb\xbf"); //remove UTF-8 marker, if it exists + data.replace("\r", ""); + + lstring line; + line.split("\n", data); + for(unsigned i = 0; i < line.size(); i++) { + lstring part; + //format: "Input" = "Output" + part.qsplit("=", line[i]); + if(part.size() != 2) continue; + + //remove whitespace + part[0].trim(); + part[1].trim(); + + //remove quotes + part[0].trim<1>("\""); + part[1].trim<1>("\""); + + unsigned n = index_input.size(); + index_input[n] = part[0]; + index_output[n] = part[1]; + } + + return true; + } + + void reset() { + index_input.reset(); + index_output.reset(); + } + + ~dictionary() { + reset(); + } + + dictionary& operator=(const dictionary&) = delete; + dictionary(const dictionary&) = delete; + + protected: + lstring index_input; + lstring index_output; + }; +} + +#endif diff --git a/nall/directory.hpp b/nall/directory.hpp new file mode 100755 index 00000000..df0bf086 --- /dev/null +++ b/nall/directory.hpp @@ -0,0 +1,151 @@ +#ifndef NALL_DIRECTORY_HPP +#define NALL_DIRECTORY_HPP + +#include +#include +#include + +#if defined(_WIN32) + #include +#else + #include + #include + #include +#endif + +namespace nall { + +struct directory { + static bool exists(const string &pathname); + static lstring folders(const string &pathname, const string &pattern = "*"); + static lstring files(const string &pathname, const string &pattern = "*"); + static lstring contents(const string &pathname, const string &pattern = "*"); +}; + +#if defined(_WIN32) + inline bool directory::exists(const string &pathname) { + DWORD result = GetFileAttributes(utf16_t(pathname)); + if(result == INVALID_FILE_ATTRIBUTES) return false; + return (result & FILE_ATTRIBUTE_DIRECTORY); + } + + inline lstring directory::folders(const string &pathname, const string &pattern) { + lstring list; + string path = pathname; + path.transform("/", "\\"); + if(!strend(path, "\\")) path.append("\\"); + path.append("*"); + HANDLE handle; + WIN32_FIND_DATA data; + handle = FindFirstFile(utf16_t(path), &data); + if(handle != INVALID_HANDLE_VALUE) { + if(wcscmp(data.cFileName, L".") && wcscmp(data.cFileName, L"..")) { + if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + string name = utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(string(name, "/")); + } + } + while(FindNextFile(handle, &data) != false) { + if(wcscmp(data.cFileName, L".") && wcscmp(data.cFileName, L"..")) { + if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + string name = utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(string(name, "/")); + } + } + } + FindClose(handle); + } + if(list.size() > 0) sort(&list[0], list.size()); + return list; + } + + inline lstring directory::files(const string &pathname, const string &pattern) { + lstring list; + string path = pathname; + path.transform("/", "\\"); + if(!strend(path, "\\")) path.append("\\"); + path.append("*"); + HANDLE handle; + WIN32_FIND_DATA data; + handle = FindFirstFile(utf16_t(path), &data); + if(handle != INVALID_HANDLE_VALUE) { + if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + string name = utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(name); + } + while(FindNextFile(handle, &data) != false) { + if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + string name = utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(name); + } + } + FindClose(handle); + } + if(list.size() > 0) sort(&list[0], list.size()); + return list; + } + + inline lstring directory::contents(const string &pathname, const string &pattern) { + lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files + lstring files = directory::files(pathname, pattern); + foreach(file, files) folders.append(file); + return folders; + } +#else + inline bool directory::exists(const string &pathname) { + DIR *dp = opendir(pathname); + if(!dp) return false; + closedir(dp); + return true; + } + + inline lstring directory::folders(const string &pathname, const string &pattern) { + lstring list; + DIR *dp; + struct dirent *ep; + dp = opendir(pathname); + if(dp) { + while(ep = readdir(dp)) { + if(!strcmp(ep->d_name, ".")) continue; + if(!strcmp(ep->d_name, "..")) continue; + if(ep->d_type & DT_DIR) { + if(wildcard(ep->d_name, pattern)) list.append(string(ep->d_name, "/")); + } + } + closedir(dp); + } + if(list.size() > 0) sort(&list[0], list.size()); + return list; + + } + + inline lstring directory::files(const string &pathname, const string &pattern) { + lstring list; + DIR *dp; + struct dirent *ep; + dp = opendir(pathname); + if(dp) { + while(ep = readdir(dp)) { + if(!strcmp(ep->d_name, ".")) continue; + if(!strcmp(ep->d_name, "..")) continue; + if((ep->d_type & DT_DIR) == 0) { + if(wildcard(ep->d_name, pattern)) list.append(ep->d_name); + } + } + closedir(dp); + } + if(list.size() > 0) sort(&list[0], list.size()); + return list; + } + + inline lstring directory::contents(const string &pathname, const string &pattern) { + lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files + lstring files = directory::files(pathname, pattern); + foreach(file, files) folders.append(file); + return folders; + } +#endif + +} + +#endif diff --git a/nall/dl.hpp b/nall/dl.hpp new file mode 100755 index 00000000..ebfa5585 --- /dev/null +++ b/nall/dl.hpp @@ -0,0 +1,115 @@ +#ifndef NALL_DL_HPP +#define NALL_DL_HPP + +//dynamic linking support + +#include +#include +#include +#include + +#if defined(PLATFORM_X) || defined(PLATFORM_OSX) + #include +#elif defined(PLATFORM_WIN) + #include + #include +#endif + +namespace nall { + struct library { + bool opened() const { return handle; } + bool open(const char*, const char* = ""); + bool open_absolute(const char*); + void* sym(const char*); + void close(); + + library() : handle(0) {} + ~library() { close(); } + + library& operator=(const library&) = delete; + library(const library&) = delete; + + private: + uintptr_t handle; + }; + + #if defined(PLATFORM_X) + inline bool library::open(const char *name, const char *path) { + if(handle) close(); + handle = (uintptr_t)dlopen(string(path, *path && !strend(path, "/") ? "/" : "", "lib", name, ".so"), RTLD_LAZY); + if(!handle) handle = (uintptr_t)dlopen(string("/usr/local/lib/lib", name, ".so"), RTLD_LAZY); + return handle; + } + + inline bool library::open_absolute(const char *name) { + if(handle) close(); + handle = (uintptr_t)dlopen(name, RTLD_LAZY); + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_OSX) + inline bool library::open(const char *name, const char *path) { + if(handle) close(); + handle = (uintptr_t)dlopen(string(path, *path && !strend(path, "/") ? "/" : "", "lib", name, ".dylib"), RTLD_LAZY); + if(!handle) handle = (uintptr_t)dlopen(string("/usr/local/lib/lib", name, ".dylib"), RTLD_LAZY); + return handle; + } + + inline bool library::open_absolute(const char *name) { + if(handle) close(); + handle = (uintptr_t)dlopen(name, RTLD_LAZY); + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_WIN) + inline bool library::open(const char *name, const char *path) { + if(handle) close(); + string filepath(path, *path && !strend(path, "/") && !strend(path, "\\") ? "\\" : "", name, ".dll"); + handle = (uintptr_t)LoadLibraryW(utf16_t(filepath)); + return handle; + } + + inline bool library::open_absolute(const char *name) { + if(handle) close(); + handle = (uintptr_t)LoadLibraryW(utf16_t(name)); + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return (void*)GetProcAddress((HMODULE)handle, name); + } + + inline void library::close() { + if(!handle) return; + FreeLibrary((HMODULE)handle); + handle = 0; + } + #else + inline bool library::open(const char*, const char*) { return false; } + inline void* library::sym(const char*) { return 0; } + inline void library::close() {} + #endif +}; + +#endif diff --git a/nall/endian.hpp b/nall/endian.hpp new file mode 100755 index 00000000..40d15633 --- /dev/null +++ b/nall/endian.hpp @@ -0,0 +1,38 @@ +#ifndef NALL_ENDIAN_HPP +#define NALL_ENDIAN_HPP + +#if !defined(ARCH_MSB) + //little-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201 + #define order_lsb2(a,b) a,b + #define order_lsb3(a,b,c) a,b,c + #define order_lsb4(a,b,c,d) a,b,c,d + #define order_lsb5(a,b,c,d,e) a,b,c,d,e + #define order_lsb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_lsb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_lsb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h + #define order_msb2(a,b) b,a + #define order_msb3(a,b,c) c,b,a + #define order_msb4(a,b,c,d) d,c,b,a + #define order_msb5(a,b,c,d,e) e,d,c,b,a + #define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a +#else + //big-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304 + #define order_lsb2(a,b) b,a + #define order_lsb3(a,b,c) c,b,a + #define order_lsb4(a,b,c,d) d,c,b,a + #define order_lsb5(a,b,c,d,e) e,d,c,b,a + #define order_lsb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_lsb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_lsb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a + #define order_msb2(a,b) a,b + #define order_msb3(a,b,c) a,b,c + #define order_msb4(a,b,c,d) a,b,c,d + #define order_msb5(a,b,c,d,e) a,b,c,d,e + #define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h +#endif + +#endif diff --git a/nall/file.hpp b/nall/file.hpp new file mode 100755 index 00000000..103c7d4a --- /dev/null +++ b/nall/file.hpp @@ -0,0 +1,261 @@ +#ifndef NALL_FILE_HPP +#define NALL_FILE_HPP + +#include +#include + +#if !defined(_WIN32) + #include +#else + #include +#endif + +#include +#include +#include +#include + +namespace nall { + inline FILE* fopen_utf8(const char *utf8_filename, const char *mode) { + #if !defined(_WIN32) + return fopen(utf8_filename, mode); + #else + return _wfopen(utf16_t(utf8_filename), utf16_t(mode)); + #endif + } + + class file { + public: + enum class mode : unsigned { read, write, readwrite, writeread }; + enum class index : unsigned { absolute, relative }; + + uint8_t read() { + if(!fp) return 0xff; //file not open + if(file_mode == mode::write) return 0xff; //reads not permitted + if(file_offset >= file_size) return 0xff; //cannot read past end of file + buffer_sync(); + return buffer[(file_offset++) & buffer_mask]; + } + + uintmax_t readl(unsigned length = 1) { + uintmax_t data = 0; + for(int i = 0; i < length; i++) { + data |= (uintmax_t)read() << (i << 3); + } + return data; + } + + uintmax_t readm(unsigned length = 1) { + uintmax_t data = 0; + while(length--) { + data <<= 8; + data |= read(); + } + return data; + } + + void read(uint8_t *buffer, unsigned length) { + while(length--) *buffer++ = read(); + } + + void write(uint8_t data) { + if(!fp) return; //file not open + if(file_mode == mode::read) return; //writes not permitted + buffer_sync(); + buffer[(file_offset++) & buffer_mask] = data; + buffer_dirty = true; + if(file_offset > file_size) file_size = file_offset; + } + + void writel(uintmax_t data, unsigned length = 1) { + while(length--) { + write(data); + data >>= 8; + } + } + + void writem(uintmax_t data, unsigned length = 1) { + for(int i = length - 1; i >= 0; i--) { + write(data >> (i << 3)); + } + } + + void write(const uint8_t *buffer, unsigned length) { + while(length--) write(*buffer++); + } + + template void print(Args... args) { + string data(args...); + const char *p = data; + while(*p) write(*p++); + } + + void flush() { + buffer_flush(); + fflush(fp); + } + + void seek(int offset, index index_ = index::absolute) { + if(!fp) return; //file not open + buffer_flush(); + + uintmax_t req_offset = file_offset; + switch(index_) { + case index::absolute: req_offset = offset; break; + case index::relative: req_offset += offset; break; + } + + if(req_offset < 0) req_offset = 0; //cannot seek before start of file + if(req_offset > file_size) { + if(file_mode == mode::read) { //cannot seek past end of file + req_offset = file_size; + } else { //pad file to requested location + file_offset = file_size; + while(file_size < req_offset) write(0x00); + } + } + + file_offset = req_offset; + } + + int offset() { + if(!fp) return -1; //file not open + return file_offset; + } + + int size() { + if(!fp) return -1; //file not open + return file_size; + } + + bool truncate(unsigned size) { + if(!fp) return false; //file not open + #if !defined(_WIN32) + return ftruncate(fileno(fp), size) == 0; + #else + return _chsize(fileno(fp), size) == 0; + #endif + } + + bool end() { + if(!fp) return true; //file not open + return file_offset >= file_size; + } + + static bool exists(const char *fn) { + #if !defined(_WIN32) + FILE *fp = fopen(fn, "rb"); + #else + FILE *fp = _wfopen(utf16_t(fn), L"rb"); + #endif + if(fp) { + fclose(fp); + return true; + } + return false; + } + + static unsigned size(const char *fn) { + #if !defined(_WIN32) + FILE *fp = fopen(fn, "rb"); + #else + FILE *fp = _wfopen(utf16_t(fn), L"rb"); + #endif + unsigned filesize = 0; + if(fp) { + fseek(fp, 0, SEEK_END); + filesize = ftell(fp); + fclose(fp); + } + return filesize; + } + + bool open() { + return fp; + } + + bool open(const char *fn, mode mode_) { + if(fp) return false; + + switch(file_mode = mode_) { + #if !defined(_WIN32) + case mode::read: fp = fopen(fn, "rb"); break; + case mode::write: fp = fopen(fn, "wb+"); break; //need read permission for buffering + case mode::readwrite: fp = fopen(fn, "rb+"); break; + case mode::writeread: fp = fopen(fn, "wb+"); break; + #else + case mode::read: fp = _wfopen(utf16_t(fn), L"rb"); break; + case mode::write: fp = _wfopen(utf16_t(fn), L"wb+"); break; + case mode::readwrite: fp = _wfopen(utf16_t(fn), L"rb+"); break; + case mode::writeread: fp = _wfopen(utf16_t(fn), L"wb+"); break; + #endif + } + if(!fp) return false; + buffer_offset = -1; //invalidate buffer + file_offset = 0; + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + return true; + } + + void close() { + if(!fp) return; + buffer_flush(); + fclose(fp); + fp = 0; + } + + file() { + memset(buffer, 0, sizeof buffer); + buffer_offset = -1; + buffer_dirty = false; + fp = 0; + file_offset = 0; + file_size = 0; + file_mode = mode::read; + } + + ~file() { + close(); + } + + file& operator=(const file&) = delete; + file(const file&) = delete; + + private: + enum { buffer_size = 1 << 12, buffer_mask = buffer_size - 1 }; + char buffer[buffer_size]; + int buffer_offset; + bool buffer_dirty; + FILE *fp; + unsigned file_offset; + unsigned file_size; + mode file_mode; + + void buffer_sync() { + if(!fp) return; //file not open + if(buffer_offset != (file_offset & ~buffer_mask)) { + buffer_flush(); + buffer_offset = file_offset & ~buffer_mask; + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fread(buffer, 1, length, fp); + } + } + + void buffer_flush() { + if(!fp) return; //file not open + if(file_mode == mode::read) return; //buffer cannot be written to + if(buffer_offset < 0) return; //buffer unused + if(buffer_dirty == false) return; //buffer unmodified since read + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fwrite(buffer, 1, length, fp); + buffer_offset = -1; //invalidate buffer + buffer_dirty = false; + } + }; +} + +#endif diff --git a/nall/filemap.hpp b/nall/filemap.hpp new file mode 100755 index 00000000..52acb2fa --- /dev/null +++ b/nall/filemap.hpp @@ -0,0 +1,200 @@ +#ifndef NALL_FILEMAP_HPP +#define NALL_FILEMAP_HPP + +#include +#include + +#include +#include +#if defined(_WIN32) + #include +#else + #include + #include + #include + #include + #include +#endif + +namespace nall { + class filemap { + public: + enum class mode : unsigned { read, write, readwrite, writeread }; + + bool opened() const { return p_opened(); } + bool open(const char *filename, mode mode_) { return p_open(filename, mode_); } + void close() { return p_close(); } + unsigned size() const { return p_size; } + uint8_t* data() { return p_handle; } + const uint8_t* data() const { return p_handle; } + filemap() : p_size(0), p_handle(0) { p_ctor(); } + filemap(const char *filename, mode mode_) : p_size(0), p_handle(0) { p_ctor(); p_open(filename, mode_); } + ~filemap() { p_dtor(); } + + private: + unsigned p_size; + uint8_t *p_handle; + + #if defined(_WIN32) + //============= + //MapViewOfFile + //============= + + HANDLE p_filehandle, p_maphandle; + + bool p_opened() const { + return p_handle; + } + + bool p_open(const char *filename, mode mode_) { + int desired_access, creation_disposition, flprotect, map_access; + + switch(mode_) { + default: return false; + case mode::read: + desired_access = GENERIC_READ; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READONLY; + map_access = FILE_MAP_READ; + break; + case mode::write: + //write access requires read access + desired_access = GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode::readwrite: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode::writeread: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = CREATE_NEW; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + } + + p_filehandle = CreateFileW(utf16_t(filename), desired_access, FILE_SHARE_READ, NULL, + creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); + if(p_filehandle == INVALID_HANDLE_VALUE) return false; + + p_size = GetFileSize(p_filehandle, NULL); + + p_maphandle = CreateFileMapping(p_filehandle, NULL, flprotect, 0, p_size, NULL); + if(p_maphandle == INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + return false; + } + + p_handle = (uint8_t*)MapViewOfFile(p_maphandle, map_access, 0, 0, p_size); + return p_handle; + } + + void p_close() { + if(p_handle) { + UnmapViewOfFile(p_handle); + p_handle = 0; + } + + if(p_maphandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_maphandle); + p_maphandle = INVALID_HANDLE_VALUE; + } + + if(p_filehandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + } + } + + void p_ctor() { + p_filehandle = INVALID_HANDLE_VALUE; + p_maphandle = INVALID_HANDLE_VALUE; + } + + void p_dtor() { + close(); + } + + #else + //==== + //mmap + //==== + + int p_fd; + + bool p_opened() const { + return p_handle; + } + + bool p_open(const char *filename, mode mode_) { + int open_flags, mmap_flags; + + switch(mode_) { + default: return false; + case mode::read: + open_flags = O_RDONLY; + mmap_flags = PROT_READ; + break; + case mode::write: + open_flags = O_RDWR | O_CREAT; //mmap() requires read access + mmap_flags = PROT_WRITE; + break; + case mode::readwrite: + open_flags = O_RDWR; + mmap_flags = PROT_READ | PROT_WRITE; + break; + case mode::writeread: + open_flags = O_RDWR | O_CREAT; + mmap_flags = PROT_READ | PROT_WRITE; + break; + } + + p_fd = ::open(filename, open_flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if(p_fd < 0) return false; + + struct stat p_stat; + fstat(p_fd, &p_stat); + p_size = p_stat.st_size; + + p_handle = (uint8_t*)mmap(0, p_size, mmap_flags, MAP_SHARED, p_fd, 0); + if(p_handle == MAP_FAILED) { + p_handle = 0; + ::close(p_fd); + p_fd = -1; + return false; + } + + return p_handle; + } + + void p_close() { + if(p_handle) { + munmap(p_handle, p_size); + p_handle = 0; + } + + if(p_fd >= 0) { + ::close(p_fd); + p_fd = -1; + } + } + + void p_ctor() { + p_fd = -1; + } + + void p_dtor() { + p_close(); + } + + #endif + }; +} + +#endif diff --git a/nall/foreach.hpp b/nall/foreach.hpp new file mode 100755 index 00000000..00a039f3 --- /dev/null +++ b/nall/foreach.hpp @@ -0,0 +1,12 @@ +#ifndef NALL_FOREACH_HPP +#define NALL_FOREACH_HPP + +#include +#include + +#undef foreach +#define foreach(iter, object) \ + for(unsigned foreach_counter = 0, foreach_limit = container_size(object), foreach_once = 0, foreach_broken = 0; foreach_counter < foreach_limit && foreach_broken == 0; foreach_counter++, foreach_once = 0) \ + for(auto &iter = object[foreach_counter]; foreach_once == 0 && (foreach_broken = 1); foreach_once++, foreach_broken = 0) + +#endif diff --git a/nall/function.hpp b/nall/function.hpp new file mode 100755 index 00000000..645991fb --- /dev/null +++ b/nall/function.hpp @@ -0,0 +1,60 @@ +#ifndef NALL_FUNCTION_HPP +#define NALL_FUNCTION_HPP + +namespace nall { + template class function; + + template class function { + struct container { + virtual R operator()(P... p) const = 0; + virtual container* copy() const = 0; + virtual ~container() {} + } *callback; + + struct global : container { + R (*function)(P...); + R operator()(P... p) const { return function(std::forward

(p)...); } + container* copy() const { return new global(function); } + global(R (*function)(P...)) : function(function) {} + }; + + template struct member : container { + R (C::*function)(P...); + C *object; + R operator()(P... p) const { return (object->*function)(std::forward

(p)...); } + container* copy() const { return new member(function, object); } + member(R (C::*function)(P...), C *object) : function(function), object(object) {} + }; + + template struct lambda : container { + L object; + R operator()(P... p) const { return object(std::forward

(p)...); } + container* copy() const { return new lambda(object); } + lambda(const L& object) : object(object) {} + }; + + public: + operator bool() const { return callback; } + R operator()(P... p) const { return (*callback)(std::forward

(p)...); } + void reset() { if(callback) { delete callback; callback = 0; } } + + function& operator=(const function &source) { + if(this != &source) { + if(callback) { delete callback; callback = 0; } + if(source.callback) callback = source.callback->copy(); + } + return *this; + } + + function(const function &source) { operator=(source); } + function() : callback(0) {} + function(void *function) : callback(0) { if(function) callback = new global((R (*)(P...))function); } + function(R (*function)(P...)) { callback = new global(function); } + template function(R (C::*function)(P...), C *object) { callback = new member(function, object); } + template function(R (C::*function)(P...) const, C *object) { callback = new member((R (C::*)(P...))function, object); } + template function(const L& object) { callback = new lambda(object); } + ~function() { if(callback) delete callback; } + }; +} + +#endif diff --git a/nall/input.hpp b/nall/input.hpp new file mode 100755 index 00000000..1fd680f4 --- /dev/null +++ b/nall/input.hpp @@ -0,0 +1,386 @@ +#ifndef NALL_INPUT_HPP +#define NALL_INPUT_HPP + +#include +#include +#include + +#include +#include + +namespace nall { + +struct Keyboard; +Keyboard& keyboard(unsigned = 0); + +static const char KeyboardScancodeName[][64] = { + "Escape", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", + "PrintScreen", "ScrollLock", "Pause", "Tilde", + "Num1", "Num2", "Num3", "Num4", "Num5", "Num6", "Num7", "Num8", "Num9", "Num0", + "Dash", "Equal", "Backspace", + "Insert", "Delete", "Home", "End", "PageUp", "PageDown", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "LeftBracket", "RightBracket", "Backslash", "Semicolon", "Apostrophe", "Comma", "Period", "Slash", + "Keypad1", "Keypad2", "Keypad3", "Keypad4", "Keypad5", "Keypad6", "Keypad7", "Keypad8", "Keypad9", "Keypad0", + "Point", "Enter", "Add", "Subtract", "Multiply", "Divide", + "NumLock", "CapsLock", + "Up", "Down", "Left", "Right", + "Tab", "Return", "Spacebar", "Menu", + "Shift", "Control", "Alt", "Super", +}; + +struct Keyboard { + const unsigned ID; + enum { Base = 1 }; + enum { Count = 8, Size = 128 }; + + enum Scancode { + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + PrintScreen, ScrollLock, Pause, Tilde, + Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Num0, + Dash, Equal, Backspace, + Insert, Delete, Home, End, PageUp, PageDown, + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + LeftBracket, RightBracket, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + Point, Enter, Add, Subtract, Multiply, Divide, + NumLock, CapsLock, + Up, Down, Left, Right, + Tab, Return, Spacebar, Menu, + Shift, Control, Alt, Super, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed keyDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return scancode - keyboard(i).key(Escape); + } + return -1; + } + + static signed modifierDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return scancode - keyboard(i).key(Shift); + } + return -1; + } + + static bool isAnyKey(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return true; + } + return false; + } + + static bool isAnyModifier(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "KB")) return 0; + s.ltrim("KB"); + unsigned id = decimal(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == KeyboardScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return string() << "KB" << ID << "::" << KeyboardScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t key(unsigned id) const { return Base + Size * ID + id; } + bool isKey(unsigned id) const { return id >= key(Escape) && id <= key(Menu); } + bool isModifier(unsigned id) const { return id >= key(Shift) && id <= key(Super); } + bool belongsTo(uint16_t scancode) const { return isKey(scancode) || isModifier(scancode); } + + Keyboard(unsigned ID_) : ID(ID_) {} +}; + +inline Keyboard& keyboard(unsigned id) { + static Keyboard kb0(0), kb1(1), kb2(2), kb3(3), kb4(4), kb5(5), kb6(6), kb7(7); + switch(id) { default: + case 0: return kb0; case 1: return kb1; case 2: return kb2; case 3: return kb3; + case 4: return kb4; case 5: return kb5; case 6: return kb6; case 7: return kb7; + } +} + +static const char MouseScancodeName[][64] = { + "Xaxis", "Yaxis", "Zaxis", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", +}; + +struct Mouse; +Mouse& mouse(unsigned = 0); + +struct Mouse { + const unsigned ID; + enum { Base = Keyboard::Base + Keyboard::Size * Keyboard::Count }; + enum { Count = 8, Size = 16 }; + enum { Axes = 3, Buttons = 8 }; + + enum Scancode { + Xaxis, Yaxis, Zaxis, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return scancode - mouse(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return scancode - mouse(i).button(0); + } + return -1; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "MS")) return 0; + s.ltrim("MS"); + unsigned id = decimal(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == MouseScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return string() << "MS" << ID << "::" << MouseScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Xaxis + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(2); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(7); } + bool belongsTo(uint16_t scancode) const { return isAxis(scancode) || isButton(scancode); } + + Mouse(unsigned ID_) : ID(ID_) {} +}; + +inline Mouse& mouse(unsigned id) { + static Mouse ms0(0), ms1(1), ms2(2), ms3(3), ms4(4), ms5(5), ms6(6), ms7(7); + switch(id) { default: + case 0: return ms0; case 1: return ms1; case 2: return ms2; case 3: return ms3; + case 4: return ms4; case 5: return ms5; case 6: return ms6; case 7: return ms7; + } +} + +static const char JoypadScancodeName[][64] = { + "Hat0", "Hat1", "Hat2", "Hat3", "Hat4", "Hat5", "Hat6", "Hat7", + "Axis0", "Axis1", "Axis2", "Axis3", "Axis4", "Axis5", "Axis6", "Axis7", + "Axis8", "Axis9", "Axis10", "Axis11", "Axis12", "Axis13", "Axis14", "Axis15", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", + "Button8", "Button9", "Button10", "Button11", "Button12", "Button13", "Button14", "Button15", + "Button16", "Button17", "Button18", "Button19", "Button20", "Button21", "Button22", "Button23", + "Button24", "Button25", "Button26", "Button27", "Button28", "Button29", "Button30", "Button31", +}; + +struct Joypad; +Joypad& joypad(unsigned = 0); + +struct Joypad { + const unsigned ID; + enum { Base = Mouse::Base + Mouse::Size * Mouse::Count }; + enum { Count = 8, Size = 64 }; + enum { Hats = 8, Axes = 16, Buttons = 32 }; + + enum Scancode { + Hat0, Hat1, Hat2, Hat3, Hat4, Hat5, Hat6, Hat7, + Axis0, Axis1, Axis2, Axis3, Axis4, Axis5, Axis6, Axis7, + Axis8, Axis9, Axis10, Axis11, Axis12, Axis13, Axis14, Axis15, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Button8, Button9, Button10, Button11, Button12, Button13, Button14, Button15, + Button16, Button17, Button18, Button19, Button20, Button21, Button22, Button23, + Button24, Button25, Button26, Button27, Button28, Button29, Button30, Button31, + Limit, + }; + + enum Hat { HatCenter = 0, HatUp = 1, HatRight = 2, HatDown = 4, HatLeft = 8 }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed hatDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return scancode - joypad(i).hat(0); + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return scancode - joypad(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return scancode - joypad(i).button(0); + } + return -1; + } + + static bool isAnyHat(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return true; + } + return false; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "JP")) return 0; + s.ltrim("JP"); + unsigned id = decimal(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == JoypadScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + } + } + return string() << "JP" << ID << "::" << JoypadScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t hat(unsigned id) const { return Base + Size * ID + Hat0 + id; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Axis0 + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isHat(unsigned id) const { return id >= hat(0) && id <= hat(7); } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(15); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(31); } + bool belongsTo(uint16_t scancode) const { return isHat(scancode) || isAxis(scancode) || isButton(scancode); } + + Joypad(unsigned ID_) : ID(ID_) {} +}; + +inline Joypad& joypad(unsigned id) { + static Joypad jp0(0), jp1(1), jp2(2), jp3(3), jp4(4), jp5(5), jp6(6), jp7(7); + switch(id) { default: + case 0: return jp0; case 1: return jp1; case 2: return jp2; case 3: return jp3; + case 4: return jp4; case 5: return jp5; case 6: return jp6; case 7: return jp7; + } +} + +struct Scancode { + enum { None = 0, Limit = Joypad::Base + Joypad::Size * Joypad::Count }; + + static uint16_t decode(const char *name) { + uint16_t code; + code = Keyboard::decode(name); + if(code) return code; + code = Mouse::decode(name); + if(code) return code; + code = Joypad::decode(name); + if(code) return code; + return None; + } + + static string encode(uint16_t code) { + for(unsigned i = 0; i < Keyboard::Count; i++) { + if(keyboard(i).belongsTo(code)) return keyboard(i).encode(code); + } + for(unsigned i = 0; i < Mouse::Count; i++) { + if(mouse(i).belongsTo(code)) return mouse(i).encode(code); + } + for(unsigned i = 0; i < Joypad::Count; i++) { + if(joypad(i).belongsTo(code)) return joypad(i).encode(code); + } + return "None"; + } +}; + +} + +#endif diff --git a/nall/lzss.hpp b/nall/lzss.hpp new file mode 100755 index 00000000..202bc814 --- /dev/null +++ b/nall/lzss.hpp @@ -0,0 +1,81 @@ +#ifndef NALL_LZSS_HPP +#define NALL_LZSS_HPP + +#include +#include +#include + +namespace nall { + class lzss { + public: + static bool encode(uint8_t *&output, unsigned &outlength, const uint8_t *input, unsigned inlength) { + output = new(zeromemory) uint8_t[inlength * 9 / 8 + 9]; + + unsigned i = 0, o = 0; + while(i < inlength) { + unsigned flagoffset = o++; + uint8_t flag = 0x00; + + for(unsigned b = 0; b < 8 && i < inlength; b++) { + unsigned longest = 0, pointer; + for(unsigned index = 1; index < 4096; index++) { + unsigned count = 0; + while(true) { + if(count >= 15 + 3) break; //verify pattern match is not longer than max length + if(i + count >= inlength) break; //verify pattern match does not read past end of input + if(i + count < index) break; //verify read is not before start of input + if(input[i + count] != input[i + count - index]) break; //verify pattern still matches + count++; + } + + if(count > longest) { + longest = count; + pointer = index; + } + } + + if(longest < 3) output[o++] = input[i++]; + else { + flag |= 1 << b; + uint16_t x = ((longest - 3) << 12) + pointer; + output[o++] = x; + output[o++] = x >> 8; + i += longest; + } + } + + output[flagoffset] = flag; + } + + outlength = o; + return true; + } + + static bool decode(uint8_t *&output, const uint8_t *input, unsigned length) { + output = new(zeromemory) uint8_t[length]; + + unsigned i = 0, o = 0; + while(o < length) { + uint8_t flag = input[i++]; + + for(unsigned b = 0; b < 8 && o < length; b++) { + if(!(flag & (1 << b))) output[o++] = input[i++]; + else { + uint16_t offset = input[i++]; + offset += input[i++] << 8; + uint16_t lookuplength = (offset >> 12) + 3; + offset &= 4095; + for(unsigned index = 0; index < lookuplength && o + index < length; index++) { + output[o + index] = output[o + index - offset]; + } + o += lookuplength; + } + } + } + + return true; + } + }; +} + +#endif diff --git a/nall/moduloarray.hpp b/nall/moduloarray.hpp new file mode 100755 index 00000000..be549ae9 --- /dev/null +++ b/nall/moduloarray.hpp @@ -0,0 +1,40 @@ +#ifndef NALL_MODULO_HPP +#define NALL_MODULO_HPP + +#include + +namespace nall { + template class modulo_array { + public: + inline T operator[](int index) const { + return buffer[size + index]; + } + + inline T read(int index) const { + return buffer[size + index]; + } + + inline void write(unsigned index, const T value) { + buffer[index] = + buffer[index + size] = + buffer[index + size + size] = value; + } + + void serialize(serializer &s) { + s.array(buffer, size * 3); + } + + modulo_array() { + buffer = new T[size * 3](); + } + + ~modulo_array() { + delete[] buffer; + } + + private: + T *buffer; + }; +} + +#endif diff --git a/nall/platform.hpp b/nall/platform.hpp new file mode 100755 index 00000000..72eeec09 --- /dev/null +++ b/nall/platform.hpp @@ -0,0 +1,122 @@ +#ifndef NALL_PLATFORM_HPP +#define NALL_PLATFORM_HPP + +#include + +//========================= +//standard platform headers +//========================= + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) + #include + #include + #include + #undef interface + #define dllexport __declspec(dllexport) +#else + #include + #include + #include + #define dllexport +#endif + +//================== +//warning supression +//================== + +//Visual C++ +#if defined(_MSC_VER) + //disable libc "deprecation" warnings + #pragma warning(disable:4996) +#endif + +//================ +//POSIX compliance +//================ + +#if defined(_MSC_VER) + #define PATH_MAX _MAX_PATH + #define va_copy(dest, src) ((dest) = (src)) +#endif + +#if defined(_WIN32) + #define getcwd _getcwd + #define ftruncate _chsize + #define putenv _putenv + #define mkdir(n, m) _wmkdir(nall::utf16_t(n)) + #define rmdir _rmdir + #define vsnprintf _vsnprintf + #define usleep(n) Sleep(n / 1000) +#endif + +//================ +//inline expansion +//================ + +#if defined(__GNUC__) + #define noinline __attribute__((noinline)) + #define inline inline + #define alwaysinline inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define noinline __declspec(noinline) + #define inline inline + #define alwaysinline inline __forceinline +#else + #define noinline + #define inline inline + #define alwaysinline inline +#endif + +//========================= +//file system functionality +//========================= + +#if defined(_WIN32) + inline char* realpath(const char *filename, char *resolvedname) { + wchar_t fn[_MAX_PATH] = L""; + _wfullpath(fn, nall::utf16_t(filename), _MAX_PATH); + strcpy(resolvedname, nall::utf8_t(fn)); + return resolvedname; + } + + inline char* userpath(char *path) { + wchar_t fp[_MAX_PATH] = L""; + SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, fp); + strcpy(path, nall::utf8_t(fp)); + return path; + } + + inline char* getcwd(char *path) { + wchar_t fp[_MAX_PATH] = L""; + _wgetcwd(fp, _MAX_PATH); + strcpy(path, nall::utf8_t(fp)); + return path; + } +#else + //realpath() already exists + + inline char* userpath(char *path) { + *path = 0; + struct passwd *userinfo = getpwuid(getuid()); + if(userinfo) strcpy(path, userinfo->pw_dir); + return path; + } + + inline char *getcwd(char *path) { + return getcwd(path, PATH_MAX); + } +#endif + +#endif + diff --git a/nall/priorityqueue.hpp b/nall/priorityqueue.hpp new file mode 100755 index 00000000..7104e791 --- /dev/null +++ b/nall/priorityqueue.hpp @@ -0,0 +1,109 @@ +#ifndef NALL_PRIORITYQUEUE_HPP +#define NALL_PRIORITYQUEUE_HPP + +#include +#include +#include +#include + +namespace nall { + template void priority_queue_nocallback(type_t) {} + + //priority queue implementation using binary min-heap array; + //does not require normalize() function. + //O(1) find (tick) + //O(log n) insert (enqueue) + //O(log n) remove (dequeue) + template class priority_queue { + public: + inline void tick(unsigned ticks) { + basecounter += ticks; + while(heapsize && gte(basecounter, heap[0].counter)) callback(dequeue()); + } + + //counter is relative to current time (eg enqueue(64, ...) fires in 64 ticks); + //counter cannot exceed std::numeric_limits::max() >> 1. + void enqueue(unsigned counter, type_t event) { + unsigned child = heapsize++; + counter += basecounter; + + while(child) { + unsigned parent = (child - 1) >> 1; + if(gte(counter, heap[parent].counter)) break; + + heap[child].counter = heap[parent].counter; + heap[child].event = heap[parent].event; + child = parent; + } + + heap[child].counter = counter; + heap[child].event = event; + } + + type_t dequeue() { + type_t event(heap[0].event); + unsigned parent = 0; + unsigned counter = heap[--heapsize].counter; + + while(true) { + unsigned child = (parent << 1) + 1; + if(child >= heapsize) break; + if(child + 1 < heapsize && gte(heap[child].counter, heap[child + 1].counter)) child++; + if(gte(heap[child].counter, counter)) break; + + heap[parent].counter = heap[child].counter; + heap[parent].event = heap[child].event; + parent = child; + } + + heap[parent].counter = counter; + heap[parent].event = heap[heapsize].event; + return event; + } + + void reset() { + basecounter = 0; + heapsize = 0; + } + + void serialize(serializer &s) { + s.integer(basecounter); + s.integer(heapsize); + for(unsigned n = 0; n < heapcapacity; n++) { + s.integer(heap[n].counter); + s.integer(heap[n].event); + } + } + + priority_queue(unsigned size, function callback_ = &priority_queue_nocallback) + : callback(callback_) { + heap = new heap_t[size]; + heapcapacity = size; + reset(); + } + + ~priority_queue() { + delete[] heap; + } + + priority_queue& operator=(const priority_queue&) = delete; + priority_queue(const priority_queue&) = delete; + + private: + function callback; + unsigned basecounter; + unsigned heapsize; + unsigned heapcapacity; + struct heap_t { + unsigned counter; + type_t event; + } *heap; + + //return true if x is greater than or equal to y + inline bool gte(unsigned x, unsigned y) { + return x - y < (std::numeric_limits::max() >> 1); + } + }; +} + +#endif diff --git a/nall/property.hpp b/nall/property.hpp new file mode 100755 index 00000000..6fd33acd --- /dev/null +++ b/nall/property.hpp @@ -0,0 +1,91 @@ +#ifndef NALL_PROPERTY_HPP +#define NALL_PROPERTY_HPP + +//nall::property implements ownership semantics into container classes +//example: property::readonly implies that only owner has full +//access to type; and all other code has readonly access. +// +//this code relies on extended friend semantics from C++0x to work, as it +//declares a friend class via a template paramter. it also exploits a bug in +//G++ 4.x to work even in C++98 mode. +// +//if compiling elsewhere, simply remove the friend class and private semantics + +//property can be used either of two ways: +//struct foo { +// property::readonly x; +// property::readwrite y; +//}; +//-or- +//struct foo : property { +// readonly x; +// readwrite y; +//}; + +//return types are const T& (byref) instead fo T (byval) to avoid major speed +//penalties for objects with expensive copy constructors + +//operator-> provides access to underlying object type: +//readonly foo; +//foo->bar(); +//... will call Object::bar(); + +//operator='s reference is constant so as to avoid leaking a reference handle +//that could bypass access restrictions + +//both constant and non-constant operators are provided, though it may be +//necessary to cast first, for instance: +//struct foo : property { readonly bar; } object; +//int main() { int value = const_cast(object); } + +//writeonly is useful for objects that have non-const reads, but const writes. +//however, to avoid leaking handles, the interface is very restricted. the only +//way to write is via operator=, which requires conversion via eg copy +//constructor. example: +//struct foo { +// foo(bool value) { ... } +//}; +//writeonly bar; +//bar = true; + +namespace nall { + template struct property { + template struct traits { typedef T type; }; + + template struct readonly { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + private: + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + friend class traits::type; + }; + + template struct writeonly { + void operator=(const T& value_) { value = value_; } + private: + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + T value; + friend class traits::type; + }; + + template struct readwrite { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + }; + }; +} + +#endif diff --git a/nall/random.hpp b/nall/random.hpp new file mode 100755 index 00000000..74ebc2d2 --- /dev/null +++ b/nall/random.hpp @@ -0,0 +1,20 @@ +#ifndef NALL_RANDOM_HPP +#define NALL_RANDOM_HPP + +namespace nall { + //pseudo-random number generator + inline unsigned prng() { + static unsigned n = 0; + return n = (n >> 1) ^ (((n & 1) - 1) & 0xedb88320); + } + + struct random_cyclic { + unsigned seed; + inline unsigned operator()() { + return seed = (seed >> 1) ^ (((seed & 1) - 1) & 0xedb88320); + } + random_cyclic() : seed(0) {} + }; +} + +#endif diff --git a/nall/serial.hpp b/nall/serial.hpp new file mode 100755 index 00000000..9ac8451a --- /dev/null +++ b/nall/serial.hpp @@ -0,0 +1,85 @@ +#ifndef NALL_SERIAL_HPP +#define NALL_SERIAL_HPP + +#include +#include +#include +#include + +#include + +namespace nall { + class serial { + public: + //-1 on error, otherwise return bytes read + int read(uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::read(port, (void*)data, length); + } + + //-1 on error, otherwise return bytes written + int write(const uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::write(port, (void*)data, length); + } + + bool open(const char *portname, unsigned rate, bool flowcontrol) { + close(); + + port = ::open(portname, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + if(port == -1) return false; + + if(ioctl(port, TIOCEXCL) == -1) { close(); return false; } + if(fcntl(port, F_SETFL, 0) == -1) { close(); return false; } + if(tcgetattr(port, &original_attr) == -1) { close(); return false; } + + termios attr = original_attr; + cfmakeraw(&attr); + cfsetspeed(&attr, rate); + + attr.c_lflag &=~ (ECHO | ECHONL | ISIG | ICANON | IEXTEN); + attr.c_iflag &=~ (BRKINT | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY); + attr.c_iflag |= (IGNBRK | IGNPAR); + attr.c_oflag &=~ (OPOST); + attr.c_cflag &=~ (CSIZE | CSTOPB | PARENB | CLOCAL); + attr.c_cflag |= (CS8 | CREAD); + if(flowcontrol == false) { + attr.c_cflag &= ~CRTSCTS; + } else { + attr.c_cflag |= CRTSCTS; + } + attr.c_cc[VTIME] = attr.c_cc[VMIN] = 0; + + if(tcsetattr(port, TCSANOW, &attr) == -1) { close(); return false; } + return port_open = true; + } + + void close() { + if(port != -1) { + tcdrain(port); + if(port_open == true) { + tcsetattr(port, TCSANOW, &original_attr); + port_open = false; + } + ::close(port); + port = -1; + } + } + + serial() { + port = -1; + port_open = false; + } + + ~serial() { + close(); + } + + private: + int port; + bool port_open; + termios original_attr; + }; +} + +#endif diff --git a/nall/serializer.hpp b/nall/serializer.hpp new file mode 100755 index 00000000..ff2337ab --- /dev/null +++ b/nall/serializer.hpp @@ -0,0 +1,146 @@ +#ifndef NALL_SERIALIZER_HPP +#define NALL_SERIALIZER_HPP + +#include +#include +#include +#include + +namespace nall { + //serializer: a class designed to save and restore the state of classes. + // + //benefits: + //- data() will be portable in size (it is not necessary to specify type sizes.) + //- data() will be portable in endianness (always stored internally as little-endian.) + //- one serialize function can both save and restore class states. + // + //caveats: + //- only plain-old-data can be stored. complex classes must provide serialize(serializer&); + //- floating-point usage is not portable across platforms + + class serializer { + public: + enum mode_t { Load, Save, Size }; + + mode_t mode() const { + return imode; + } + + const uint8_t* data() const { + return idata; + } + + unsigned size() const { + return isize; + } + + unsigned capacity() const { + return icapacity; + } + + template void floatingpoint(T &value) { + enum { size = sizeof(T) }; + //this is rather dangerous, and not cross-platform safe; + //but there is no standardized way to export FP-values + uint8_t *p = (uint8_t*)&value; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = p[n]; + } else if(imode == Load) { + for(unsigned n = 0; n < size; n++) p[n] = idata[isize++]; + } else { + isize += size; + } + } + + template void integer(T &value) { + enum { size = std::is_same::value ? 1 : sizeof(T) }; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = value >> (n << 3); + } else if(imode == Load) { + value = 0; + for(unsigned n = 0; n < size; n++) value |= idata[isize++] << (n << 3); + } else if(imode == Size) { + isize += size; + } + } + + template void array(T &array) { + enum { size = sizeof(T) / sizeof(typename std::remove_extent::type) }; + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + template void array(T array, unsigned size) { + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + //copy + serializer& operator=(const serializer &s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = new uint8_t[s.icapacity]; + isize = s.isize; + icapacity = s.icapacity; + + memcpy(idata, s.idata, s.icapacity); + return *this; + } + + serializer(const serializer &s) : idata(0) { + operator=(s); + } + + //move + serializer& operator=(serializer &&s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = s.idata; + isize = s.isize; + icapacity = s.icapacity; + + s.idata = 0; + return *this; + } + + serializer(serializer &&s) { + operator=(std::move(s)); + } + + //construction + serializer() { + imode = Size; + idata = 0; + isize = 0; + icapacity = 0; + } + + serializer(unsigned capacity) { + imode = Save; + idata = new uint8_t[capacity](); + isize = 0; + icapacity = capacity; + } + + serializer(const uint8_t *data, unsigned capacity) { + imode = Load; + idata = new uint8_t[capacity]; + isize = 0; + icapacity = capacity; + memcpy(idata, data, capacity); + } + + ~serializer() { + if(idata) delete[] idata; + } + + private: + mode_t imode; + uint8_t *idata; + unsigned isize; + unsigned icapacity; + }; + +}; + +#endif diff --git a/nall/sha256.hpp b/nall/sha256.hpp new file mode 100755 index 00000000..7f41f04e --- /dev/null +++ b/nall/sha256.hpp @@ -0,0 +1,143 @@ +#ifndef NALL_SHA256_HPP +#define NALL_SHA256_HPP + +//author: vladitx + +namespace nall { + #define PTR(t, a) ((t*)(a)) + + #define SWAP32(x) ((uint32_t)( \ + (((uint32_t)(x) & 0x000000ff) << 24) | \ + (((uint32_t)(x) & 0x0000ff00) << 8) | \ + (((uint32_t)(x) & 0x00ff0000) >> 8) | \ + (((uint32_t)(x) & 0xff000000) >> 24) \ + )) + + #define ST32(a, d) *PTR(uint32_t, a) = (d) + #define ST32BE(a, d) ST32(a, SWAP32(d)) + + #define LD32(a) *PTR(uint32_t, a) + #define LD32BE(a) SWAP32(LD32(a)) + + #define LSL32(x, n) ((uint32_t)(x) << (n)) + #define LSR32(x, n) ((uint32_t)(x) >> (n)) + #define ROR32(x, n) (LSR32(x, n) | LSL32(x, 32 - (n))) + + //first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19 + static const uint32_t T_H[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + }; + + //first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311 + static const uint32_t T_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + + struct sha256_ctx { + uint8_t in[64]; + unsigned inlen; + + uint32_t w[64]; + uint32_t h[8]; + uint64_t len; + }; + + void sha256_init(sha256_ctx *p) { + memset(p, 0, sizeof(sha256_ctx)); + memcpy(p->h, T_H, sizeof(T_H)); + } + + static void sha256_block(sha256_ctx *p) { + unsigned i; + uint32_t s0, s1; + uint32_t a, b, c, d, e, f, g, h; + uint32_t t1, t2, maj, ch; + + for(i = 0; i < 16; i++) p->w[i] = LD32BE(p->in + i * 4); + + for(i = 16; i < 64; i++) { + s0 = ROR32(p->w[i - 15], 7) ^ ROR32(p->w[i - 15], 18) ^ LSR32(p->w[i - 15], 3); + s1 = ROR32(p->w[i - 2], 17) ^ ROR32(p->w[i - 2], 19) ^ LSR32(p->w[i - 2], 10); + p->w[i] = p->w[i - 16] + s0 + p->w[i - 7] + s1; + } + + a = p->h[0]; b = p->h[1]; c = p->h[2]; d = p->h[3]; + e = p->h[4]; f = p->h[5]; g = p->h[6]; h = p->h[7]; + + for(i = 0; i < 64; i++) { + s0 = ROR32(a, 2) ^ ROR32(a, 13) ^ ROR32(a, 22); + maj = (a & b) ^ (a & c) ^ (b & c); + t2 = s0 + maj; + s1 = ROR32(e, 6) ^ ROR32(e, 11) ^ ROR32(e, 25); + ch = (e & f) ^ (~e & g); + t1 = h + s1 + ch + T_K[i] + p->w[i]; + + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + + p->h[0] += a; p->h[1] += b; p->h[2] += c; p->h[3] += d; + p->h[4] += e; p->h[5] += f; p->h[6] += g; p->h[7] += h; + + //next block + p->inlen = 0; + } + + void sha256_chunk(sha256_ctx *p, const uint8_t *s, unsigned len) { + unsigned l; + p->len += len; + + while(len) { + l = 64 - p->inlen; + l = (len < l) ? len : l; + + memcpy(p->in + p->inlen, s, l); + s += l; + p->inlen += l; + len -= l; + + if(p->inlen == 64) sha256_block(p); + } + } + + void sha256_final(sha256_ctx *p) { + uint64_t len; + p->in[p->inlen++] = 0x80; + + if(p->inlen > 56) { + memset(p->in + p->inlen, 0, 64 - p->inlen); + sha256_block(p); + } + + memset(p->in + p->inlen, 0, 56 - p->inlen); + + len = p->len << 3; + ST32BE(p->in + 56, len >> 32); + ST32BE(p->in + 60, len); + sha256_block(p); + } + + void sha256_hash(sha256_ctx *p, uint8_t *s) { + uint32_t *t = (uint32_t*)s; + for(unsigned i = 0; i < 8; i++) ST32BE(t++, p->h[i]); + } + + #undef PTR + #undef SWAP32 + #undef ST32 + #undef ST32BE + #undef LD32 + #undef LD32BE + #undef LSL32 + #undef LSR32 + #undef ROR32 +} + +#endif diff --git a/nall/snes/cartridge.hpp b/nall/snes/cartridge.hpp new file mode 100755 index 00000000..485e91eb --- /dev/null +++ b/nall/snes/cartridge.hpp @@ -0,0 +1,868 @@ +#ifndef NALL_SNES_CARTRIDGE_HPP +#define NALL_SNES_CARTRIDGE_HPP + +namespace nall { + +class SNESCartridge { +public: + string xmlMemoryMap; + inline SNESCartridge(const uint8_t *data, unsigned size); + +//private: + inline void read_header(const uint8_t *data, unsigned size); + inline unsigned find_header(const uint8_t *data, unsigned size); + inline unsigned score_header(const uint8_t *data, unsigned size, unsigned addr); + inline unsigned gameboy_ram_size(const uint8_t *data, unsigned size); + inline bool gameboy_has_rtc(const uint8_t *data, unsigned size); + + enum HeaderField { + CartName = 0x00, + Mapper = 0x15, + RomType = 0x16, + RomSize = 0x17, + RamSize = 0x18, + CartRegion = 0x19, + Company = 0x1a, + Version = 0x1b, + Complement = 0x1c, //inverse checksum + Checksum = 0x1e, + ResetVector = 0x3c, + }; + + enum Mode { + ModeNormal, + ModeBsxSlotted, + ModeBsx, + ModeSufamiTurbo, + ModeSuperGameBoy, + }; + + enum Type { + TypeNormal, + TypeBsxSlotted, + TypeBsxBios, + TypeBsx, + TypeSufamiTurboBios, + TypeSufamiTurbo, + TypeSuperGameBoy1Bios, + TypeSuperGameBoy2Bios, + TypeGameBoy, + TypeUnknown, + }; + + enum Region { + NTSC, + PAL, + }; + + enum MemoryMapper { + LoROM, + HiROM, + ExLoROM, + ExHiROM, + SuperFXROM, + SA1ROM, + SPC7110ROM, + BSCLoROM, + BSCHiROM, + BSXROM, + STROM, + }; + + enum DSP1MemoryMapper { + DSP1Unmapped, + DSP1LoROM1MB, + DSP1LoROM2MB, + DSP1HiROM, + }; + + bool loaded; //is a base cartridge inserted? + unsigned crc32; //crc32 of all cartridges (base+slot(s)) + unsigned rom_size; + unsigned ram_size; + + Mode mode; + Type type; + Region region; + MemoryMapper mapper; + DSP1MemoryMapper dsp1_mapper; + + bool has_bsx_slot; + bool has_superfx; + bool has_sa1; + bool has_srtc; + bool has_sdd1; + bool has_spc7110; + bool has_spc7110rtc; + bool has_cx4; + bool has_dsp1; + bool has_dsp2; + bool has_dsp3; + bool has_dsp4; + bool has_obc1; + bool has_st010; + bool has_st011; + bool has_st018; +}; + +SNESCartridge::SNESCartridge(const uint8_t *data, unsigned size) { + read_header(data, size); + + string xml = "\n"; + + if(type == TypeBsx) { + xml << ""; + xmlMemoryMap = xml; + return; + } + + if(type == TypeSufamiTurbo) { + xml << ""; + xmlMemoryMap = xml; + return; + } + + if(type == TypeGameBoy) { + xml << "\n"; + if(gameboy_ram_size(data, size) > 0) { + xml << " \n"; + } + xml << "\n"; + xmlMemoryMap = xml; + return; + } + + xml << "\n"; + + if(type == TypeSuperGameBoy1Bios) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(type == TypeSuperGameBoy2Bios) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(has_spc7110) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + if(has_spc7110rtc) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == LoROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + + if(ram_size > 0) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + if((rom_size > 0x200000) || (ram_size > 32 * 1024)) { + xml << " \n"; + xml << " \n"; + } else { + xml << " \n"; + xml << " \n"; + } + xml << " \n"; + } + } else if(mapper == HiROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + + if(ram_size > 0) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + if((rom_size > 0x200000) || (ram_size > 32 * 1024)) { + xml << " \n"; + } else { + xml << " \n"; + } + xml << " \n"; + } + } else if(mapper == ExLoROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + + if(ram_size > 0) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + } else if(mapper == ExHiROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + + if(ram_size > 0) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + if((rom_size > 0x200000) || (ram_size > 32 * 1024)) { + xml << " \n"; + } else { + xml << " \n"; + } + xml << " \n"; + } + } else if(mapper == SuperFXROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == SA1ROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == BSCLoROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == BSCHiROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == BSXROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == STROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_srtc) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_sdd1) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_cx4) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_dsp1) { + xml << " \n"; + if(dsp1_mapper == DSP1LoROM1MB) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(dsp1_mapper == DSP1LoROM2MB) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(dsp1_mapper == DSP1HiROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + xml << " \n"; + } + + if(has_dsp2) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_dsp3) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_dsp4) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_obc1) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_st010) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_st011) { + //ST-0011 addresses not verified; chip is unsupported + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_st018) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + xml << "\n"; + xmlMemoryMap = xml; +} + +void SNESCartridge::read_header(const uint8_t *data, unsigned size) { + type = TypeUnknown; + mapper = LoROM; + dsp1_mapper = DSP1Unmapped; + region = NTSC; + rom_size = size; + ram_size = 0; + + has_bsx_slot = false; + has_superfx = false; + has_sa1 = false; + has_srtc = false; + has_sdd1 = false; + has_spc7110 = false; + has_spc7110rtc = false; + has_cx4 = false; + has_dsp1 = false; + has_dsp2 = false; + has_dsp3 = false; + has_dsp4 = false; + has_obc1 = false; + has_st010 = false; + has_st011 = false; + has_st018 = false; + + //===================== + //detect Game Boy carts + //===================== + + if(size >= 0x0140) { + if(data[0x0104] == 0xce && data[0x0105] == 0xed && data[0x0106] == 0x66 && data[0x0107] == 0x66 + && data[0x0108] == 0xcc && data[0x0109] == 0x0d && data[0x010a] == 0x00 && data[0x010b] == 0x0b) { + type = TypeGameBoy; + return; + } + } + + if(size < 32768) { + type = TypeUnknown; + return; + } + + const unsigned index = find_header(data, size); + const uint8_t mapperid = data[index + Mapper]; + const uint8_t rom_type = data[index + RomType]; + const uint8_t rom_size = data[index + RomSize]; + const uint8_t company = data[index + Company]; + const uint8_t regionid = data[index + CartRegion] & 0x7f; + + ram_size = 1024 << (data[index + RamSize] & 7); + if(ram_size == 1024) ram_size = 0; //no RAM present + + //0, 1, 13 = NTSC; 2 - 12 = PAL + region = (regionid <= 1 || regionid >= 13) ? NTSC : PAL; + + //======================= + //detect BS-X flash carts + //======================= + + if(data[index + 0x13] == 0x00 || data[index + 0x13] == 0xff) { + if(data[index + 0x14] == 0x00) { + const uint8_t n15 = data[index + 0x15]; + if(n15 == 0x00 || n15 == 0x80 || n15 == 0x84 || n15 == 0x9c || n15 == 0xbc || n15 == 0xfc) { + if(data[index + 0x1a] == 0x33 || data[index + 0x1a] == 0xff) { + type = TypeBsx; + mapper = BSXROM; + region = NTSC; //BS-X only released in Japan + return; + } + } + } + } + + //========================= + //detect Sufami Turbo carts + //========================= + + if(!memcmp(data, "BANDAI SFC-ADX", 14)) { + if(!memcmp(data + 16, "SFC-ADX BACKUP", 14)) { + type = TypeSufamiTurboBios; + } else { + type = TypeSufamiTurbo; + } + mapper = STROM; + region = NTSC; //Sufami Turbo only released in Japan + return; //RAM size handled outside this routine + } + + //========================== + //detect Super Game Boy BIOS + //========================== + + if(!memcmp(data + index, "Super GAMEBOY2", 14)) { + type = TypeSuperGameBoy2Bios; + return; + } + + if(!memcmp(data + index, "Super GAMEBOY", 13)) { + type = TypeSuperGameBoy1Bios; + return; + } + + //===================== + //detect standard carts + //===================== + + //detect presence of BS-X flash cartridge connector (reads extended header information) + if(data[index - 14] == 'Z') { + if(data[index - 11] == 'J') { + uint8_t n13 = data[index - 13]; + if((n13 >= 'A' && n13 <= 'Z') || (n13 >= '0' && n13 <= '9')) { + if(company == 0x33 || (data[index - 10] == 0x00 && data[index - 4] == 0x00)) { + has_bsx_slot = true; + } + } + } + } + + if(has_bsx_slot) { + if(!memcmp(data + index, "Satellaview BS-X ", 21)) { + //BS-X base cart + type = TypeBsxBios; + mapper = BSXROM; + region = NTSC; //BS-X only released in Japan + return; //RAM size handled internally by load_cart_bsx() -> BSXCart class + } else { + type = TypeBsxSlotted; + mapper = (index == 0x7fc0 ? BSCLoROM : BSCHiROM); + region = NTSC; //BS-X slotted cartridges only released in Japan + } + } else { + //standard cart + type = TypeNormal; + + if(index == 0x7fc0 && size >= 0x401000) { + mapper = ExLoROM; + } else if(index == 0x7fc0 && mapperid == 0x32) { + mapper = ExLoROM; + } else if(index == 0x7fc0) { + mapper = LoROM; + } else if(index == 0xffc0) { + mapper = HiROM; + } else { //index == 0x40ffc0 + mapper = ExHiROM; + } + } + + if(mapperid == 0x20 && (rom_type == 0x13 || rom_type == 0x14 || rom_type == 0x15 || rom_type == 0x1a)) { + has_superfx = true; + mapper = SuperFXROM; + ram_size = 1024 << (data[index - 3] & 7); + if(ram_size == 1024) ram_size = 0; + } + + if(mapperid == 0x23 && (rom_type == 0x32 || rom_type == 0x34 || rom_type == 0x35)) { + has_sa1 = true; + mapper = SA1ROM; + } + + if(mapperid == 0x35 && rom_type == 0x55) { + has_srtc = true; + } + + if(mapperid == 0x32 && (rom_type == 0x43 || rom_type == 0x45)) { + has_sdd1 = true; + } + + if(mapperid == 0x3a && (rom_type == 0xf5 || rom_type == 0xf9)) { + has_spc7110 = true; + has_spc7110rtc = (rom_type == 0xf9); + mapper = SPC7110ROM; + } + + if(mapperid == 0x20 && rom_type == 0xf3) { + has_cx4 = true; + } + + if((mapperid == 0x20 || mapperid == 0x21) && rom_type == 0x03) { + has_dsp1 = true; + } + + if(mapperid == 0x30 && rom_type == 0x05 && company != 0xb2) { + has_dsp1 = true; + } + + if(mapperid == 0x31 && (rom_type == 0x03 || rom_type == 0x05)) { + has_dsp1 = true; + } + + if(has_dsp1 == true) { + if((mapperid & 0x2f) == 0x20 && size <= 0x100000) { + dsp1_mapper = DSP1LoROM1MB; + } else if((mapperid & 0x2f) == 0x20) { + dsp1_mapper = DSP1LoROM2MB; + } else if((mapperid & 0x2f) == 0x21) { + dsp1_mapper = DSP1HiROM; + } + } + + if(mapperid == 0x20 && rom_type == 0x05) { + has_dsp2 = true; + } + + if(mapperid == 0x30 && rom_type == 0x05 && company == 0xb2) { + has_dsp3 = true; + } + + if(mapperid == 0x30 && rom_type == 0x03) { + has_dsp4 = true; + } + + if(mapperid == 0x30 && rom_type == 0x25) { + has_obc1 = true; + } + + if(mapperid == 0x30 && rom_type == 0xf6 && rom_size >= 10) { + has_st010 = true; + } + + if(mapperid == 0x30 && rom_type == 0xf6 && rom_size < 10) { + has_st011 = true; + } + + if(mapperid == 0x30 && rom_type == 0xf5) { + has_st018 = true; + } +} + +unsigned SNESCartridge::find_header(const uint8_t *data, unsigned size) { + unsigned score_lo = score_header(data, size, 0x007fc0); + unsigned score_hi = score_header(data, size, 0x00ffc0); + unsigned score_ex = score_header(data, size, 0x40ffc0); + if(score_ex) score_ex += 4; //favor ExHiROM on images > 32mbits + + if(score_lo >= score_hi && score_lo >= score_ex) { + return 0x007fc0; + } else if(score_hi >= score_ex) { + return 0x00ffc0; + } else { + return 0x40ffc0; + } +} + +unsigned SNESCartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) { + if(size < addr + 64) return 0; //image too small to contain header at this location? + int score = 0; + + uint16_t resetvector = data[addr + ResetVector] | (data[addr + ResetVector + 1] << 8); + uint16_t checksum = data[addr + Checksum ] | (data[addr + Checksum + 1] << 8); + uint16_t complement = data[addr + Complement ] | (data[addr + Complement + 1] << 8); + + uint8_t resetop = data[(addr & ~0x7fff) | (resetvector & 0x7fff)]; //first opcode executed upon reset + uint8_t mapper = data[addr + Mapper] & ~0x10; //mask off irrelevent FastROM-capable bit + + //$00:[000-7fff] contains uninitialized RAM and MMIO. + //reset vector must point to ROM at $00:[8000-ffff] to be considered valid. + if(resetvector < 0x8000) return 0; + + //some images duplicate the header in multiple locations, and others have completely + //invalid header information that cannot be relied upon. + //below code will analyze the first opcode executed at the specified reset vector to + //determine the probability that this is the correct header. + + //most likely opcodes + if(resetop == 0x78 //sei + || resetop == 0x18 //clc (clc; xce) + || resetop == 0x38 //sec (sec; xce) + || resetop == 0x9c //stz $nnnn (stz $4200) + || resetop == 0x4c //jmp $nnnn + || resetop == 0x5c //jml $nnnnnn + ) score += 8; + + //plausible opcodes + if(resetop == 0xc2 //rep #$nn + || resetop == 0xe2 //sep #$nn + || resetop == 0xad //lda $nnnn + || resetop == 0xae //ldx $nnnn + || resetop == 0xac //ldy $nnnn + || resetop == 0xaf //lda $nnnnnn + || resetop == 0xa9 //lda #$nn + || resetop == 0xa2 //ldx #$nn + || resetop == 0xa0 //ldy #$nn + || resetop == 0x20 //jsr $nnnn + || resetop == 0x22 //jsl $nnnnnn + ) score += 4; + + //implausible opcodes + if(resetop == 0x40 //rti + || resetop == 0x60 //rts + || resetop == 0x6b //rtl + || resetop == 0xcd //cmp $nnnn + || resetop == 0xec //cpx $nnnn + || resetop == 0xcc //cpy $nnnn + ) score -= 4; + + //least likely opcodes + if(resetop == 0x00 //brk #$nn + || resetop == 0x02 //cop #$nn + || resetop == 0xdb //stp + || resetop == 0x42 //wdm + || resetop == 0xff //sbc $nnnnnn,x + ) score -= 8; + + //at times, both the header and reset vector's first opcode will match ... + //fallback and rely on info validity in these cases to determine more likely header. + + //a valid checksum is the biggest indicator of a valid header. + if((checksum + complement) == 0xffff && (checksum != 0) && (complement != 0)) score += 4; + + if(addr == 0x007fc0 && mapper == 0x20) score += 2; //0x20 is usually LoROM + if(addr == 0x00ffc0 && mapper == 0x21) score += 2; //0x21 is usually HiROM + if(addr == 0x007fc0 && mapper == 0x22) score += 2; //0x22 is usually ExLoROM + if(addr == 0x40ffc0 && mapper == 0x25) score += 2; //0x25 is usually ExHiROM + + if(data[addr + Company] == 0x33) score += 2; //0x33 indicates extended header + if(data[addr + RomType] < 0x08) score++; + if(data[addr + RomSize] < 0x10) score++; + if(data[addr + RamSize] < 0x08) score++; + if(data[addr + CartRegion] < 14) score++; + + if(score < 0) score = 0; + return score; +} + +unsigned SNESCartridge::gameboy_ram_size(const uint8_t *data, unsigned size) { + if(size < 512) return 0; + switch(data[0x0149]) { + case 0x00: return 0 * 1024; + case 0x01: return 8 * 1024; + case 0x02: return 8 * 1024; + case 0x03: return 32 * 1024; + case 0x04: return 128 * 1024; + case 0x05: return 128 * 1024; + default: return 128 * 1024; + } +} + +bool SNESCartridge::gameboy_has_rtc(const uint8_t *data, unsigned size) { + if(size < 512) return false; + if(data[0x0147] == 0x0f ||data[0x0147] == 0x10) return true; + return false; +} + +} + +#endif diff --git a/nall/snes/cpu.hpp b/nall/snes/cpu.hpp new file mode 100755 index 00000000..28f5ddb5 --- /dev/null +++ b/nall/snes/cpu.hpp @@ -0,0 +1,458 @@ +#ifndef NALL_SNES_CPU_HPP +#define NALL_SNES_CPU_HPP + +namespace nall { + +struct SNESCPU { + enum : unsigned { + Implied, // + Constant, //#$00 + AccumConstant, //#$00 + IndexConstant, //#$00 + Direct, //$00 + DirectX, //$00,x + DirectY, //$00,y + IDirect, //($00) + IDirectX, //($00,x) + IDirectY, //($00),y + ILDirect, //[$00] + ILDirectY, //[$00],y + Address, //$0000 + AddressX, //$0000,x + AddressY, //$0000,y + IAddressX, //($0000,x) + ILAddress, //[$0000] + PAddress, //PBR:$0000 + PIAddress, //PBR:($0000) + Long, //$000000 + LongX, //$000000,x + Stack, //$00,s + IStackY, //($00,s),y + BlockMove, //$00,$00 + RelativeShort, //+/- $00 + RelativeLong, //+/- $0000 + }; + + struct OpcodeInfo { + char name[4]; + unsigned mode; + }; + + static const OpcodeInfo opcodeInfo[256]; + + static unsigned getOpcodeLength(bool accum, bool index, uint8_t opcode); + static string disassemble(unsigned pc, bool accum, bool index, uint8_t opcode, uint8_t pl, uint8_t ph, uint8_t pb); +}; + +const SNESCPU::OpcodeInfo SNESCPU::opcodeInfo[256] = { + //0x00 - 0x0f + { "brk", Constant }, + { "ora", IDirectX }, + { "cop", Constant }, + { "ora", Stack }, + + { "tsb", Direct }, + { "ora", Direct }, + { "asl", Direct }, + { "ora", ILDirect }, + + { "php", Implied }, + { "ora", AccumConstant }, + { "asl", Implied }, + { "phd", Implied }, + + { "tsb", Address }, + { "ora", Address }, + { "asl", Address }, + { "ora", Long }, + + //0x10 - 0x1f + { "bpl", RelativeShort }, + { "ora", IDirectY }, + { "ora", IDirect }, + { "ora", IStackY }, + + { "trb", Direct }, + { "ora", DirectX }, + { "asl", DirectX }, + { "ora", ILDirectY }, + + { "clc", Implied }, + { "ora", AddressY }, + { "inc", Implied }, + { "tcs", Implied }, + + { "trb", Address }, + { "ora", AddressX }, + { "asl", AddressX }, + { "ora", LongX }, + + //0x20 - 0x2f + { "jsr", Address }, + { "and", IDirectX }, + { "jsl", Long }, + { "and", Stack }, + + { "bit", Direct }, + { "and", Direct }, + { "rol", Direct }, + { "and", ILDirect }, + + { "plp", Implied }, + { "and", AccumConstant }, + { "rol", Implied }, + { "pld", Implied }, + + { "bit", Address }, + { "and", Address }, + { "rol", Address }, + { "and", Long }, + + //0x30 - 0x3f + { "bmi", RelativeShort }, + { "and", IDirectY }, + { "and", IDirect }, + { "and", IStackY }, + + { "bit", DirectX }, + { "and", DirectX }, + { "rol", DirectX }, + { "and", ILDirectY }, + + { "sec", Implied }, + { "and", AddressY }, + { "dec", Implied }, + { "tsc", Implied }, + + { "bit", AddressX }, + { "and", AddressX }, + { "rol", AddressX }, + { "and", LongX }, + + //0x40 - 0x4f + { "rti", Implied }, + { "eor", IDirectX }, + { "wdm", Constant }, + { "eor", Stack }, + + { "mvp", BlockMove }, + { "eor", Direct }, + { "lsr", Direct }, + { "eor", ILDirect }, + + { "pha", Implied }, + { "eor", AccumConstant }, + { "lsr", Implied }, + { "phk", Implied }, + + { "jmp", PAddress }, + { "eor", Address }, + { "lsr", Address }, + { "eor", Long }, + + //0x50 - 0x5f + { "bvc", RelativeShort }, + { "eor", IDirectY }, + { "eor", IDirect }, + { "eor", IStackY }, + + { "mvn", BlockMove }, + { "eor", DirectX }, + { "lsr", DirectX }, + { "eor", ILDirectY }, + + { "cli", Implied }, + { "eor", AddressY }, + { "phy", Implied }, + { "tcd", Implied }, + + { "jml", Long }, + { "eor", AddressX }, + { "lsr", AddressX }, + { "eor", LongX }, + + //0x60 - 0x6f + { "rts", Implied }, + { "adc", IDirectX }, + { "per", Address }, + { "adc", Stack }, + + { "stz", Direct }, + { "adc", Direct }, + { "ror", Direct }, + { "adc", ILDirect }, + + { "pla", Implied }, + { "adc", AccumConstant }, + { "ror", Implied }, + { "rtl", Implied }, + + { "jmp", PIAddress }, + { "adc", Address }, + { "ror", Address }, + { "adc", Long }, + + //0x70 - 0x7f + { "bvs", RelativeShort }, + { "adc", IDirectY }, + { "adc", IDirect }, + { "adc", IStackY }, + + { "stz", DirectX }, + { "adc", DirectX }, + { "ror", DirectX }, + { "adc", ILDirectY }, + + { "sei", Implied }, + { "adc", AddressY }, + { "ply", Implied }, + { "tdc", Implied }, + + { "jmp", IAddressX }, + { "adc", AddressX }, + { "ror", AddressX }, + { "adc", LongX }, + + //0x80 - 0x8f + { "bra", RelativeShort }, + { "sta", IDirectX }, + { "brl", RelativeLong }, + { "sta", Stack }, + + { "sty", Direct }, + { "sta", Direct }, + { "stx", Direct }, + { "sta", ILDirect }, + + { "dey", Implied }, + { "bit", AccumConstant }, + { "txa", Implied }, + { "phb", Implied }, + + { "sty", Address }, + { "sta", Address }, + { "stx", Address }, + { "sta", Long }, + + //0x90 - 0x9f + { "bcc", RelativeShort }, + { "sta", IDirectY }, + { "sta", IDirect }, + { "sta", IStackY }, + + { "sty", DirectX }, + { "sta", DirectX }, + { "stx", DirectY }, + { "sta", ILDirectY }, + + { "tya", Implied }, + { "sta", AddressY }, + { "txs", Implied }, + { "txy", Implied }, + + { "stz", Address }, + { "sta", AddressX }, + { "stz", AddressX }, + { "sta", LongX }, + + //0xa0 - 0xaf + { "ldy", IndexConstant }, + { "lda", IDirectX }, + { "ldx", IndexConstant }, + { "lda", Stack }, + + { "ldy", Direct }, + { "lda", Direct }, + { "ldx", Direct }, + { "lda", ILDirect }, + + { "tay", Implied }, + { "lda", AccumConstant }, + { "tax", Implied }, + { "plb", Implied }, + + { "ldy", Address }, + { "lda", Address }, + { "ldx", Address }, + { "lda", Long }, + + //0xb0 - 0xbf + { "bcs", RelativeShort }, + { "lda", IDirectY }, + { "lda", IDirect }, + { "lda", IStackY }, + + { "ldy", DirectX }, + { "lda", DirectX }, + { "ldx", DirectY }, + { "lda", ILDirectY }, + + { "clv", Implied }, + { "lda", AddressY }, + { "tsx", Implied }, + { "tyx", Implied }, + + { "ldy", AddressX }, + { "lda", AddressX }, + { "ldx", AddressY }, + { "lda", LongX }, + + //0xc0 - 0xcf + { "cpy", IndexConstant }, + { "cmp", IDirectX }, + { "rep", Constant }, + { "cmp", Stack }, + + { "cpy", Direct }, + { "cmp", Direct }, + { "dec", Direct }, + { "cmp", ILDirect }, + + { "iny", Implied }, + { "cmp", AccumConstant }, + { "dex", Implied }, + { "wai", Implied }, + + { "cpy", Address }, + { "cmp", Address }, + { "dec", Address }, + { "cmp", Long }, + + //0xd0 - 0xdf + { "bne", RelativeShort }, + { "cmp", IDirectY }, + { "cmp", IDirect }, + { "cmp", IStackY }, + + { "pei", IDirect }, + { "cmp", DirectX }, + { "dec", DirectX }, + { "cmp", ILDirectY }, + + { "cld", Implied }, + { "cmp", AddressY }, + { "phx", Implied }, + { "stp", Implied }, + + { "jmp", ILAddress }, + { "cmp", AddressX }, + { "dec", AddressX }, + { "cmp", LongX }, + + //0xe0 - 0xef + { "cpx", IndexConstant }, + { "sbc", IDirectX }, + { "sep", Constant }, + { "sbc", Stack }, + + { "cpx", Direct }, + { "sbc", Direct }, + { "inc", Direct }, + { "sbc", ILDirect }, + + { "inx", Implied }, + { "sbc", AccumConstant }, + { "nop", Implied }, + { "xba", Implied }, + + { "cpx", Address }, + { "sbc", Address }, + { "inc", Address }, + { "sbc", Long }, + + //0xf0 - 0xff + { "beq", RelativeShort }, + { "sbc", IDirectY }, + { "sbc", IDirect }, + { "sbc", IStackY }, + + { "pea", Address }, + { "sbc", DirectX }, + { "inc", DirectX }, + { "sbc", ILDirectY }, + + { "sed", Implied }, + { "sbc", AddressY }, + { "plx", Implied }, + { "xce", Implied }, + + { "jsr", IAddressX }, + { "sbc", AddressX }, + { "inc", AddressX }, + { "sbc", LongX }, +}; + +inline unsigned SNESCPU::getOpcodeLength(bool accum, bool index, uint8_t opcode) { + switch(opcodeInfo[opcode].mode) { default: + case Implied: return 1; + case Constant: return 2; + case AccumConstant: return 3 - accum; + case IndexConstant: return 3 - index; + case Direct: return 2; + case DirectX: return 2; + case DirectY: return 2; + case IDirect: return 2; + case IDirectX: return 2; + case IDirectY: return 2; + case ILDirect: return 2; + case ILDirectY: return 2; + case Address: return 3; + case AddressX: return 3; + case AddressY: return 3; + case IAddressX: return 3; + case ILAddress: return 3; + case PAddress: return 3; + case PIAddress: return 3; + case Long: return 4; + case LongX: return 4; + case Stack: return 2; + case IStackY: return 2; + case BlockMove: return 3; + case RelativeShort: return 2; + case RelativeLong: return 3; + } +} + +inline string SNESCPU::disassemble(unsigned pc, bool accum, bool index, uint8_t opcode, uint8_t pl, uint8_t ph, uint8_t pb) { + string name = opcodeInfo[opcode].name; + unsigned mode = opcodeInfo[opcode].mode; + + if(mode == Implied) return name; + if(mode == Constant) return { name, " #$", hex<2>(pl) }; + if(mode == AccumConstant) return { name, " #$", accum ? "" : hex<2>(ph), hex<2>(pl) }; + if(mode == IndexConstant) return { name, " #$", index ? "" : hex<2>(ph), hex<2>(pl) }; + if(mode == Direct) return { name, " $", hex<2>(pl) }; + if(mode == DirectX) return { name, " $", hex<2>(pl), ",x" }; + if(mode == DirectY) return { name, " $", hex<2>(pl), ",y" }; + if(mode == IDirect) return { name, " ($", hex<2>(pl), ")" }; + if(mode == IDirectX) return { name, " ($", hex<2>(pl), ",x)" }; + if(mode == IDirectY) return { name, " ($", hex<2>(pl), "),y" }; + if(mode == ILDirect) return { name, " [$", hex<2>(pl), "]" }; + if(mode == ILDirectY) return { name, " [$", hex<2>(pl), "],y" }; + if(mode == Address) return { name, " $", hex<2>(ph), hex<2>(pl) }; + if(mode == AddressX) return { name, " $", hex<2>(ph), hex<2>(pl), ",x" }; + if(mode == AddressY) return { name, " $", hex<2>(ph), hex<2>(pl), ",y" }; + if(mode == IAddressX) return { name, " ($", hex<2>(ph), hex<2>(pl), ",x)" }; + if(mode == ILAddress) return { name, " [$", hex<2>(ph), hex<2>(pl), "]" }; + if(mode == PAddress) return { name, " $", hex<2>(ph), hex<2>(pl) }; + if(mode == PIAddress) return { name, " ($", hex<2>(ph), hex<2>(pl), ")" }; + if(mode == Long) return { name, " $", hex<2>(pb), hex<2>(ph), hex<2>(pl) }; + if(mode == LongX) return { name, " $", hex<2>(pb), hex<2>(ph), hex<2>(pl), ",x" }; + if(mode == Stack) return { name, " $", hex<2>(pl), ",s" }; + if(mode == IStackY) return { name, " ($", hex<2>(pl), ",s),y" }; + if(mode == BlockMove) return { name, " $", hex<2>(ph), ",$", hex<2>(pl) }; + if(mode == RelativeShort) { + unsigned addr = (pc + 2) + (int8_t)(pl << 0); + return { name, " $", hex<4>(addr) }; + } + if(mode == RelativeLong) { + unsigned addr = (pc + 3) + (int16_t)((ph << 8) + (pl << 0)); + return { name, " $", hex<4>(addr) }; + } + + return ""; +} + +} + +#endif diff --git a/nall/snes/smp.hpp b/nall/snes/smp.hpp new file mode 100755 index 00000000..7a1ac47b --- /dev/null +++ b/nall/snes/smp.hpp @@ -0,0 +1,639 @@ +#ifndef NALL_SNES_SMP_HPP +#define NALL_SNES_SMP_HPP + +namespace nall { + +struct SNESSMP { + enum : unsigned { + Implied, // + TVector, //0 + Direct, //$00 + DirectRelative, //$00,+/-$00 + ADirect, //a,$00 + AAbsolute, //a,$0000 + AIX, //a,(x) + AIDirectX, //a,($00+x) + AConstant, //a,#$00 + DirectDirect, //$00,$00 + CAbsoluteBit, //c,$0000:0 + Absolute, //$0000 + P, //p + AbsoluteA, //$0000,a + Relative, //+/-$00 + ADirectX, //a,$00+x + AAbsoluteX, //a,$0000+x + AAbsoluteY, //a,$0000+y + AIDirectY, //a,($00)+y + DirectConstant, //$00,#$00 + IXIY, //(x),(y) + DirectX, //$00+x + A, //a + X, //x + XAbsolute, //x,$0000 + IAbsoluteX, //($0000+x) + CNAbsoluteBit, //c,!$0000:0 + XDirect, //x,$00 + PVector, //$ff00 + YaDirect, //ya,$00 + XA, //x,a + YAbsolute, //y,$0000 + Y, //y + AX, //a,x + YDirect, //y,$00 + YConstant, //y,#$00 + XSp, //x,sp + YaX, //ya,x + IXPA, //(x)+,a + SpX, //sp,x + AIXP, //a,(x)+ + DirectA, //$00,a + IXA, //(x),a + IDirectXA, //($00+x),a + XConstant, //x,#$00 + AbsoluteX, //$0000,x + AbsoluteBitC, //$0000:0,c + DirectY, //$00,y + AbsoluteY, //$0000,y + Ya, //ya + DirectXA, //$00+x,a + AbsoluteXA, //$0000+x,a + AbsoluteYA, //$0000+y,a + IDirectYA, //($00)+y,a + DirectYX, //$00+y,x + DirectYa, //$00,ya + DirectXY, //$00+x,y + AY, //a,y + DirectXRelative, //$00+x,+/-$00 + XDirectY, //x,$00+y + YDirectX, //y,$00+x + YA, //y,a + YRelative, //y,+/-$00 + }; + + struct OpcodeInfo { + char name[6]; + unsigned mode; + }; + + static const OpcodeInfo opcodeInfo[256]; + + static unsigned getOpcodeLength(uint8_t opcode); + static string disassemble(uint16_t pc, uint8_t opcode, uint8_t pl, uint8_t ph); + static string disassemble(uint16_t pc, bool p, uint8_t opcode, uint8_t pl, uint8_t ph); +}; + +const SNESSMP::OpcodeInfo SNESSMP::opcodeInfo[256] = { + //0x00 - 0x0f + { "nop ", Implied }, + { "tcall", TVector }, + { "set0 ", Direct }, + { "bbs0 ", DirectRelative }, + + { "or ", ADirect }, + { "or ", AAbsolute }, + { "or ", AIX }, + { "or ", AIDirectX }, + + { "or ", AConstant }, + { "or ", DirectDirect }, + { "or1 ", CAbsoluteBit }, + { "asl ", Direct }, + + { "asl ", Absolute }, + { "push ", P }, + { "tset ", AbsoluteA }, + { "brk ", Implied }, + + //0x10 - 0x1f + { "bpl ", Relative }, + { "tcall", TVector }, + { "clr0 ", Direct }, + { "bbc0 ", DirectRelative }, + + { "or ", ADirectX }, + { "or ", AAbsoluteX }, + { "or ", AAbsoluteY }, + { "or ", AIDirectY }, + + { "or ", DirectConstant }, + { "or ", IXIY }, + { "decw ", Direct }, + { "asl ", DirectX }, + + { "asl ", A }, + { "dec ", X }, + { "cmp ", XAbsolute }, + { "jmp ", IAbsoluteX }, + + //0x20 - 0x2f + { "clrp ", Implied }, + { "tcall", TVector }, + { "set1 ", Direct }, + { "bbs1 ", DirectRelative }, + + { "and ", ADirect }, + { "and ", AAbsolute }, + { "and ", AIX }, + { "and ", AIDirectX }, + + { "and ", AConstant }, + { "and ", DirectDirect }, + { "or1 ", CNAbsoluteBit }, + { "rol ", Direct }, + + { "rol ", Absolute }, + { "push ", A }, + { "cbne ", DirectRelative }, + { "bra ", Relative }, + + //0x30 - 0x3f + { "bmi ", Relative }, + { "tcall", TVector }, + { "clr1 ", Direct }, + { "bbc1 ", DirectRelative }, + + { "and ", ADirectX }, + { "and ", AAbsoluteX }, + { "and ", AAbsoluteY }, + { "and ", AIDirectY }, + + { "and ", DirectConstant }, + { "and ", IXIY }, + { "incw ", Direct }, + { "rol ", DirectX }, + + { "rol ", A }, + { "inc ", X }, + { "cmp ", XDirect }, + { "call ", Absolute }, + + //0x40 - 0x4f + { "setp ", Implied }, + { "tcall", TVector }, + { "set2 ", Direct }, + { "bbs2 ", DirectRelative }, + + { "eor ", ADirect }, + { "eor ", AAbsolute }, + { "eor ", AIX }, + { "eor ", AIDirectX }, + + { "eor ", AConstant }, + { "eor ", DirectDirect }, + { "and1 ", CAbsoluteBit }, + { "lsr ", Direct }, + + { "lsr ", Absolute }, + { "push ", X }, + { "tclr ", AbsoluteA }, + { "pcall", PVector }, + + //0x50 - 0x5f + { "bvc ", Relative }, + { "tcall", TVector }, + { "clr2 ", Direct }, + { "bbc2 ", DirectRelative }, + + { "eor ", ADirectX }, + { "eor ", AAbsoluteX }, + { "eor ", AAbsoluteY }, + { "eor ", AIDirectY }, + + { "eor ", DirectConstant }, + { "eor ", IXIY }, + { "cmpw ", YaDirect }, + { "lsr ", DirectX }, + + { "lsr ", A }, + { "mov ", XA }, + { "cmp ", YAbsolute }, + { "jmp ", Absolute }, + + //0x60 - 0x6f + { "clrc ", Implied }, + { "tcall", TVector }, + { "set3 ", Direct }, + { "bbs3 ", DirectRelative }, + + { "cmp ", ADirect }, + { "cmp ", AAbsolute }, + { "cmp ", AIX }, + { "cmp ", AIDirectX }, + + { "cmp ", AConstant }, + { "cmp ", DirectDirect }, + { "and1 ", CNAbsoluteBit }, + { "ror ", Direct }, + + { "ror ", Absolute }, + { "push ", Y }, + { "dbnz ", DirectRelative }, + { "ret ", Implied }, + + //0x70 - 0x7f + { "bvs ", Relative }, + { "tcall", TVector }, + { "clr3 ", Direct }, + { "bbc3 ", DirectRelative }, + + { "cmp ", ADirectX }, + { "cmp ", AAbsoluteX }, + { "cmp ", AAbsoluteY }, + { "cmp ", AIDirectY }, + + { "cmp ", DirectConstant }, + { "cmp ", IXIY }, + { "addw ", YaDirect }, + { "ror ", DirectX }, + + { "ror ", A }, + { "mov ", AX }, + { "cmp ", YDirect }, + { "reti ", Implied }, + + //0x80 - 0x8f + { "setc ", Implied }, + { "tcall", TVector }, + { "set4 ", Direct }, + { "bbs4 ", DirectRelative }, + + { "adc ", ADirect }, + { "adc ", AAbsolute }, + { "adc ", AIX }, + { "adc ", AIDirectX }, + + { "adc ", AConstant }, + { "adc ", DirectDirect }, + { "eor1 ", CAbsoluteBit }, + { "dec ", Direct }, + + { "dec ", Absolute }, + { "mov ", YConstant }, + { "pop ", P }, + { "mov ", DirectConstant }, + + //0x90 - 0x9f + { "bcc ", Relative }, + { "tcall", TVector }, + { "clr4 ", Direct }, + { "bbc4 ", DirectRelative }, + + { "adc ", ADirectX }, + { "adc ", AAbsoluteX }, + { "adc ", AAbsoluteY }, + { "adc ", AIDirectY }, + + { "adc ", DirectRelative }, + { "adc ", IXIY }, + { "subw ", YaDirect }, + { "dec ", DirectX }, + + { "dec ", A }, + { "mov ", XSp }, + { "div ", YaX }, + { "xcn ", A }, + + //0xa0 - 0xaf + { "ei ", Implied }, + { "tcall", TVector }, + { "set5 ", Direct }, + { "bbs5 ", DirectRelative }, + + { "sbc ", ADirect }, + { "sbc ", AAbsolute }, + { "sbc ", AIX }, + { "sbc ", AIDirectX }, + + { "sbc ", AConstant }, + { "sbc ", DirectDirect }, + { "mov1 ", CAbsoluteBit }, + { "inc ", Direct }, + + { "inc ", Absolute }, + { "cmp ", YConstant }, + { "pop ", A }, + { "mov ", IXPA }, + + //0xb0 - 0xbf + { "bcs ", Relative }, + { "tcall", TVector }, + { "clr5 ", Direct }, + { "bbc5 ", DirectRelative }, + + { "sbc ", ADirectX }, + { "sbc ", AAbsoluteX }, + { "sbc ", AAbsoluteY }, + { "sbc ", AIDirectY }, + + { "sbc ", DirectConstant }, + { "sbc ", IXIY }, + { "movw ", YaDirect }, + { "inc ", DirectX }, + + { "inc ", A }, + { "mov ", SpX }, + { "das ", A }, + { "mov ", AIXP }, + + //0xc0 - 0xcf + { "di ", Implied }, + { "tcall", TVector }, + { "set6 ", Direct }, + { "bbs6 ", DirectRelative }, + + { "mov ", DirectA }, + { "mov ", AbsoluteA }, + { "mov ", IXA }, + { "mov ", IDirectXA }, + + { "cmp ", XConstant }, + { "mov ", AbsoluteX }, + { "mov1 ", AbsoluteBitC }, + { "mov ", DirectY }, + + { "mov ", AbsoluteY }, + { "mov ", XConstant }, + { "pop ", X }, + { "mul ", Ya }, + + //0xd0 - 0xdf + { "bne ", Relative }, + { "tcall", TVector }, + { "clr6 ", Relative }, + { "bbc6 ", DirectRelative }, + + { "mov ", DirectXA }, + { "mov ", AbsoluteXA }, + { "mov ", AbsoluteYA }, + { "mov ", IDirectYA }, + + { "mov ", DirectX }, + { "mov ", DirectYX }, + { "movw ", DirectYa }, + { "mov ", DirectXY }, + + { "dec ", Y }, + { "mov ", AY }, + { "cbne ", DirectXRelative }, + { "daa ", A }, + + //0xe0 - 0xef + { "clrv ", Implied }, + { "tcall", TVector }, + { "set7 ", Direct }, + { "bbs7 ", DirectRelative }, + + { "mov ", ADirect }, + { "mov ", AAbsolute }, + { "mov ", AIX }, + { "mov ", AIDirectX }, + + { "mov ", AConstant }, + { "mov ", XAbsolute }, + { "not1 ", CAbsoluteBit }, + { "mov ", YDirect }, + + { "mov ", YAbsolute }, + { "notc ", Implied }, + { "pop ", Y }, + { "sleep", Implied }, + + //0xf0 - 0xff + { "beq ", Relative }, + { "tcall", TVector }, + { "clr7 ", Direct }, + { "bbc7 ", DirectRelative }, + + { "mov ", ADirectX }, + { "mov ", AAbsoluteX }, + { "mov ", AAbsoluteY }, + { "mov ", AIDirectY }, + + { "mov ", XDirect }, + { "mov ", XDirectY }, + { "mov ", DirectDirect }, + { "mov ", YDirectX }, + + { "inc ", Y }, + { "mov ", YA }, + { "dbz ", YRelative }, + { "stop ", Implied }, +}; + +inline unsigned SNESSMP::getOpcodeLength(uint8_t opcode) { + switch(opcodeInfo[opcode].mode) { default: + case Implied: return 1; // + case TVector: return 1; //0 + case Direct: return 2; //$00 + case DirectRelative: return 3; //$00,+/-$00 + case ADirect: return 2; //a,$00 + case AAbsolute: return 3; //a,$0000 + case AIX: return 1; //a,(x) + case AIDirectX: return 2; //a,($00+x) + case AConstant: return 2; //a,#$00 + case DirectDirect: return 3; //$00,$00 + case CAbsoluteBit: return 3; //c,$0000:0 + case Absolute: return 3; //$0000 + case P: return 1; //p + case AbsoluteA: return 3; //$0000,a + case Relative: return 2; //+/-$00 + case ADirectX: return 2; //a,$00+x + case AAbsoluteX: return 3; //a,$0000+x + case AAbsoluteY: return 3; //a,$0000+y + case AIDirectY: return 2; //a,($00)+y + case DirectConstant: return 3; //$00,#$00 + case IXIY: return 1; //(x),(y) + case DirectX: return 2; //$00+x + case A: return 1; //a + case X: return 1; //x + case XAbsolute: return 3; //x,$0000 + case IAbsoluteX: return 3; //($0000+x) + case CNAbsoluteBit: return 3; //c,!$0000:0 + case XDirect: return 2; //x,$00 + case PVector: return 2; //$ff00 + case YaDirect: return 2; //ya,$00 + case XA: return 1; //x,a + case YAbsolute: return 3; //y,$0000 + case Y: return 1; //y + case AX: return 1; //a,x + case YDirect: return 2; //y,$00 + case YConstant: return 2; //y,#$00 + case XSp: return 1; //x,sp + case YaX: return 1; //ya,x + case IXPA: return 1; //(x)+,a + case SpX: return 1; //sp,x + case AIXP: return 1; //a,(x)+ + case DirectA: return 2; //$00,a + case IXA: return 1; //(x),a + case IDirectXA: return 2; //($00+x),a + case XConstant: return 2; //x,#$00 + case AbsoluteX: return 3; //$0000,x + case AbsoluteBitC: return 3; //$0000:0,c + case DirectY: return 2; //$00,y + case AbsoluteY: return 3; //$0000,y + case Ya: return 1; //ya + case DirectXA: return 2; //$00+x,a + case AbsoluteXA: return 3; //$0000+x,a + case AbsoluteYA: return 3; //$0000+y,a + case IDirectYA: return 2; //($00)+y,a + case DirectYX: return 2; //$00+y,x + case DirectYa: return 2; //$00,ya + case DirectXY: return 2; //$00+x,y + case AY: return 1; //a,y + case DirectXRelative: return 3; //$00+x,+/-$00 + case XDirectY: return 2; //x,$00+y + case YDirectX: return 2; //y,$00+x + case YA: return 1; //y,a + case YRelative: return 2; //y,+/-$00 + } +} + +inline string SNESSMP::disassemble(uint16_t pc, uint8_t opcode, uint8_t pl, uint8_t ph) { + string name = opcodeInfo[opcode].name; + unsigned mode = opcodeInfo[opcode].mode; + unsigned pa = (ph << 8) + pl; + + if(mode == Implied) return name; + if(mode == TVector) return { name, " ", opcode >> 4 }; + if(mode == Direct) return { name, " $", hex<2>(pl) }; + if(mode == DirectRelative) return { name, " $", hex<2>(pl), ",$", hex<4>(pc + 3 + (int8_t)ph) }; + if(mode == ADirect) return { name, " a,$", hex<2>(pl) }; + if(mode == AAbsolute) return { name, " a,$", hex<4>(pa) }; + if(mode == AIX) return { name, "a,(x)" }; + if(mode == AIDirectX) return { name, " a,($", hex<2>(pl), "+x)" }; + if(mode == AConstant) return { name, " a,#$", hex<2>(pl) }; + if(mode == DirectDirect) return { name, " $", hex<2>(ph), ",$", hex<2>(pl) }; + if(mode == CAbsoluteBit) return { name, " c,$", hex<4>(pa & 0x1fff), ":", pa >> 13 }; + if(mode == Absolute) return { name, " $", hex<4>(pa) }; + if(mode == P) return { name, " p" }; + if(mode == AbsoluteA) return { name, " $", hex<4>(pa), ",a" }; + if(mode == Relative) return { name, " $", hex<4>(pc + 2 + (int8_t)pl) }; + if(mode == ADirectX) return { name, " a,$", hex<2>(pl), "+x" }; + if(mode == AAbsoluteX) return { name, " a,$", hex<4>(pa), "+x" }; + if(mode == AAbsoluteY) return { name, " a,$", hex<4>(pa), "+y" }; + if(mode == AIDirectY) return { name, " a,($", hex<2>(pl), ")+y" }; + if(mode == DirectConstant) return { name, " $", hex<2>(ph), ",#$", hex<2>(pl) }; + if(mode == IXIY) return { name, " (x),(y)" }; + if(mode == DirectX) return { name, " $", hex<2>(pl), "+x" }; + if(mode == A) return { name, " a" }; + if(mode == X) return { name, " x" }; + if(mode == XAbsolute) return { name, " x,$", hex<4>(pa) }; + if(mode == IAbsoluteX) return { name, " ($", hex<4>(pa), "+x)" }; + if(mode == CNAbsoluteBit) return { name, " c,!$", hex<4>(pa & 0x1fff), ":", pa >> 13 }; + if(mode == XDirect) return { name, " x,$", hex<2>(pl) }; + if(mode == PVector) return { name, " $ff", hex<2>(pl) }; + if(mode == YaDirect) return { name, " ya,$", hex<2>(pl) }; + if(mode == XA) return { name, " x,a" }; + if(mode == YAbsolute) return { name, " y,$", hex<4>(pa) }; + if(mode == Y) return { name, " y" }; + if(mode == AX) return { name, " a,x" }; + if(mode == YDirect) return { name, " y,$", hex<2>(pl) }; + if(mode == YConstant) return { name, " y,#$", hex<2>(pl) }; + if(mode == XSp) return { name, " x,sp" }; + if(mode == YaX) return { name, " ya,x" }; + if(mode == IXPA) return { name, " (x)+,a" }; + if(mode == SpX) return { name, " sp,x" }; + if(mode == AIXP) return { name, " a,(x)+" }; + if(mode == DirectA) return { name, " $", hex<2>(pl), ",a" }; + if(mode == IXA) return { name, " (x),a" }; + if(mode == IDirectXA) return { name, " ($", hex<2>(pl), "+x),a" }; + if(mode == XConstant) return { name, " x,#$", hex<2>(pl) }; + if(mode == AbsoluteX) return { name, " $", hex<4>(pa), ",x" }; + if(mode == AbsoluteBitC) return { name, " $", hex<4>(pa & 0x1fff), ":", pa >> 13, ",c" }; + if(mode == DirectY) return { name, " $", hex<2>(pl), ",y" }; + if(mode == AbsoluteY) return { name, " $", hex<4>(pa), ",y" }; + if(mode == Ya) return { name, " ya" }; + if(mode == DirectXA) return { name, " $", hex<2>(pl), "+x,a" }; + if(mode == AbsoluteXA) return { name, " $", hex<4>(pa), "+x,a" }; + if(mode == AbsoluteYA) return { name, " $", hex<4>(pa), "+y,a" }; + if(mode == IDirectYA) return { name, " ($", hex<2>(pl), ")+y,a" }; + if(mode == DirectYX) return { name, " $", hex<2>(pl), "+y,x" }; + if(mode == DirectYa) return { name, " $", hex<2>(pl), ",ya" }; + if(mode == DirectXY) return { name, " $", hex<2>(pl), "+x,y" }; + if(mode == AY) return { name, " a,y" }; + if(mode == DirectXRelative) return { name, " $", hex<2>(pl), ",$", hex<4>(pc + 3 + (int8_t)ph) }; + if(mode == XDirectY) return { name, " x,$", hex<2>(pl), "+y" }; + if(mode == YDirectX) return { name, " y,$", hex<2>(pl), "+x" }; + if(mode == YA) return { name, " y,a" }; + if(mode == YRelative) return { name, " y,$", hex<4>(pc + 2 + (int8_t)pl) }; + + return ""; +} + +inline string SNESSMP::disassemble(uint16_t pc, bool p, uint8_t opcode, uint8_t pl, uint8_t ph) { + string name = opcodeInfo[opcode].name; + unsigned mode = opcodeInfo[opcode].mode; + unsigned pdl = (p << 8) + pl; + unsigned pdh = (p << 8) + ph; + unsigned pa = (ph << 8) + pl; + + if(mode == Implied) return name; + if(mode == TVector) return { name, " ", opcode >> 4 }; + if(mode == Direct) return { name, " $", hex<3>(pdl) }; + if(mode == DirectRelative) return { name, " $", hex<3>(pdl), ",$", hex<4>(pc + 3 + (int8_t)ph) }; + if(mode == ADirect) return { name, " a,$", hex<3>(pdl) }; + if(mode == AAbsolute) return { name, " a,$", hex<4>(pa) }; + if(mode == AIX) return { name, "a,(x)" }; + if(mode == AIDirectX) return { name, " a,($", hex<3>(pdl), "+x)" }; + if(mode == AConstant) return { name, " a,#$", hex<2>(pl) }; + if(mode == DirectDirect) return { name, " $", hex<3>(pdh), ",$", hex<3>(pdl) }; + if(mode == CAbsoluteBit) return { name, " c,$", hex<4>(pa & 0x1fff), ":", pa >> 13 }; + if(mode == Absolute) return { name, " $", hex<4>(pa) }; + if(mode == P) return { name, " p" }; + if(mode == AbsoluteA) return { name, " $", hex<4>(pa), ",a" }; + if(mode == Relative) return { name, " $", hex<4>(pc + 2 + (int8_t)pl) }; + if(mode == ADirectX) return { name, " a,$", hex<3>(pdl), "+x" }; + if(mode == AAbsoluteX) return { name, " a,$", hex<4>(pa), "+x" }; + if(mode == AAbsoluteY) return { name, " a,$", hex<4>(pa), "+y" }; + if(mode == AIDirectY) return { name, " a,($", hex<3>(pdl), ")+y" }; + if(mode == DirectConstant) return { name, " $", hex<3>(pdh), ",#$", hex<2>(pl) }; + if(mode == IXIY) return { name, " (x),(y)" }; + if(mode == DirectX) return { name, " $", hex<3>(pdl), "+x" }; + if(mode == A) return { name, " a" }; + if(mode == X) return { name, " x" }; + if(mode == XAbsolute) return { name, " x,$", hex<4>(pa) }; + if(mode == IAbsoluteX) return { name, " ($", hex<4>(pa), "+x)" }; + if(mode == CNAbsoluteBit) return { name, " c,!$", hex<4>(pa & 0x1fff), ":", pa >> 13 }; + if(mode == XDirect) return { name, " x,$", hex<3>(pdl) }; + if(mode == PVector) return { name, " $ff", hex<2>(pl) }; + if(mode == YaDirect) return { name, " ya,$", hex<3>(pdl) }; + if(mode == XA) return { name, " x,a" }; + if(mode == YAbsolute) return { name, " y,$", hex<4>(pa) }; + if(mode == Y) return { name, " y" }; + if(mode == AX) return { name, " a,x" }; + if(mode == YDirect) return { name, " y,$", hex<3>(pdl) }; + if(mode == YConstant) return { name, " y,#$", hex<2>(pl) }; + if(mode == XSp) return { name, " x,sp" }; + if(mode == YaX) return { name, " ya,x" }; + if(mode == IXPA) return { name, " (x)+,a" }; + if(mode == SpX) return { name, " sp,x" }; + if(mode == AIXP) return { name, " a,(x)+" }; + if(mode == DirectA) return { name, " $", hex<3>(pdl), ",a" }; + if(mode == IXA) return { name, " (x),a" }; + if(mode == IDirectXA) return { name, " ($", hex<3>(pdl), "+x),a" }; + if(mode == XConstant) return { name, " x,#$", hex<2>(pl) }; + if(mode == AbsoluteX) return { name, " $", hex<4>(pa), ",x" }; + if(mode == AbsoluteBitC) return { name, " $", hex<4>(pa & 0x1fff), ":", pa >> 13, ",c" }; + if(mode == DirectY) return { name, " $", hex<3>(pdl), ",y" }; + if(mode == AbsoluteY) return { name, " $", hex<4>(pa), ",y" }; + if(mode == Ya) return { name, " ya" }; + if(mode == DirectXA) return { name, " $", hex<3>(pdl), "+x,a" }; + if(mode == AbsoluteXA) return { name, " $", hex<4>(pa), "+x,a" }; + if(mode == AbsoluteYA) return { name, " $", hex<4>(pa), "+y,a" }; + if(mode == IDirectYA) return { name, " ($", hex<3>(pdl), ")+y,a" }; + if(mode == DirectYX) return { name, " $", hex<3>(pdl), "+y,x" }; + if(mode == DirectYa) return { name, " $", hex<3>(pdl), ",ya" }; + if(mode == DirectXY) return { name, " $", hex<3>(pdl), "+x,y" }; + if(mode == AY) return { name, " a,y" }; + if(mode == DirectXRelative) return { name, " $", hex<3>(pdl), ",$", hex<4>(pc + 3 + (int8_t)ph) }; + if(mode == XDirectY) return { name, " x,$", hex<3>(pdl), "+y" }; + if(mode == YDirectX) return { name, " y,$", hex<3>(pdl), "+x" }; + if(mode == YA) return { name, " y,a" }; + if(mode == YRelative) return { name, " y,$", hex<4>(pc + 2 + (int8_t)pl) }; + + return ""; +} + +} + +#endif diff --git a/nall/sort.hpp b/nall/sort.hpp new file mode 100755 index 00000000..23c317a5 --- /dev/null +++ b/nall/sort.hpp @@ -0,0 +1,62 @@ +#ifndef NALL_SORT_HPP +#define NALL_SORT_HPP + +#include + +//class: merge sort +//average: O(n log n) +//worst: O(n log n) +//memory: O(n) +//stack: O(log n) +//stable?: yes + +//notes: +//there are two primary reasons for choosing merge sort +//over the (usually) faster quick sort*: +//1: it is a stable sort. +//2: it lacks O(n^2) worst-case overhead. +//(* which is also O(n log n) in the average case.) + +namespace nall { + template + void sort(T list[], unsigned length) { + if(length <= 1) return; //nothing to sort + + //use insertion sort to quickly sort smaller blocks + if(length < 64) { + for(unsigned i = 0; i < length; i++) { + unsigned min = i; + for(unsigned j = i + 1; j < length; j++) { + if(list[j] < list[min]) min = j; + } + if(min != i) swap(list[i], list[min]); + } + return; + } + + //split list in half and recursively sort both + unsigned middle = length / 2; + sort(list, middle); + sort(list + middle, length - middle); + + //left and right are sorted here; perform merge sort + T *buffer = new T[length]; + unsigned offset = 0; + unsigned left = 0; + unsigned right = middle; + while(left < middle && right < length) { + if(list[left] < list[right]) { + buffer[offset++] = list[left++]; + } else { + buffer[offset++] = list[right++]; + } + } + while(left < middle) buffer[offset++] = list[left++]; + while(right < length) buffer[offset++] = list[right++]; + + for(unsigned i = 0; i < length; i++) list[i] = buffer[i]; + delete[] buffer; + } +} + +#endif diff --git a/nall/static.hpp b/nall/static.hpp new file mode 100755 index 00000000..4acb9fd0 --- /dev/null +++ b/nall/static.hpp @@ -0,0 +1,20 @@ +#ifndef NALL_STATIC_HPP +#define NALL_STATIC_HPP + +namespace nall { + template struct static_if { typedef T type; }; + template struct static_if { typedef F type; }; + template struct mp_static_if { typedef typename static_if::type type; }; + + template struct static_and { enum { value = false }; }; + template<> struct static_and { enum { value = true }; }; + template struct mp_static_and { enum { value = static_and::value }; }; + + template struct static_or { enum { value = false }; }; + template<> struct static_or { enum { value = true }; }; + template<> struct static_or { enum { value = true }; }; + template<> struct static_or { enum { value = true }; }; + template struct mp_static_or { enum { value = static_or::value }; }; +} + +#endif diff --git a/nall/stdint.hpp b/nall/stdint.hpp new file mode 100755 index 00000000..d8b6c788 --- /dev/null +++ b/nall/stdint.hpp @@ -0,0 +1,44 @@ +#ifndef NALL_STDINT_HPP +#define NALL_STDINT_HPP + +#include + +#if defined(_MSC_VER) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef signed long long int64_t; + typedef int64_t intmax_t; + #if defined(_WIN64) + typedef int64_t intptr_t; + #else + typedef int32_t intptr_t; + #endif + + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + typedef unsigned long long uint64_t; + typedef uint64_t uintmax_t; + #if defined(_WIN64) + typedef uint64_t uintptr_t; + #else + typedef uint32_t uintptr_t; + #endif +#else + #include +#endif + +namespace nall { + static_assert(sizeof(int8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(int16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(int32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(int64_t) == 8, "int64_t is not of the correct size"); + + static_assert(sizeof(uint8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(uint16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(uint32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(uint64_t) == 8, "int64_t is not of the correct size"); +} + +#endif diff --git a/nall/string.hpp b/nall/string.hpp new file mode 100755 index 00000000..9acc2e9d --- /dev/null +++ b/nall/string.hpp @@ -0,0 +1,32 @@ +#ifndef NALL_STRING_HPP +#define NALL_STRING_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + template<> struct has_length { enum { value = true }; }; + template<> struct has_size { enum { value = true }; }; +} + +#endif diff --git a/nall/string/base.hpp b/nall/string/base.hpp new file mode 100755 index 00000000..f2e307c0 --- /dev/null +++ b/nall/string/base.hpp @@ -0,0 +1,159 @@ +#ifndef NALL_STRING_BASE_HPP +#define NALL_STRING_BASE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + class string; + template inline string to_string(T); + + class string { + public: + inline void reserve(unsigned); + + inline string& assign(const char*); + inline string& append(const char*); + inline string& append(bool); + inline string& append(signed int value); + inline string& append(unsigned int value); + inline string& append(double value); + + inline bool readfile(const char*); + + inline string& replace (const char*, const char*); + inline string& qreplace(const char*, const char*); + + inline unsigned length() const; + + inline bool equals(const char*) const; + inline bool iequals(const char*) const; + + inline bool wildcard(const char*) const; + inline bool iwildcard(const char*) const; + + inline bool beginswith(const char*) const; + inline bool ibeginswith(const char*) const; + inline bool endswith(const char*) const; + inline bool iendswith(const char*) const; + + inline string& lower(); + inline string& upper(); + inline string& transform(const char *before, const char *after); + + template inline string& ltrim(const char *key = " "); + template inline string& rtrim(const char *key = " "); + template inline string& trim (const char *key = " "); + + inline optional position(const char *key) const; + inline optional qposition(const char *key) const; + + template inline string& operator= (T value); + template inline string& operator<<(T value); + + inline operator const char*() const; + inline char* operator()(); + inline char& operator[](int); + + inline bool operator==(const char*) const; + inline bool operator!=(const char*) const; + inline bool operator< (const char*) const; + inline bool operator<=(const char*) const; + inline bool operator> (const char*) const; + inline bool operator>=(const char*) const; + + inline string& operator=(const string&); + inline string& operator=(string&&); + + template inline string(Args&&... args); + inline string(const string&); + inline string(string&&); + inline ~string(); + + protected: + char *data; + unsigned size; + + #if defined(QSTRING_H) + public: + inline operator QString() const; + #endif + }; + + class lstring : public linear_vector { + public: + template inline lstring& operator<<(T value); + + inline optional find(const char*) const; + template inline void split (const char*, const char*); + template inline void qsplit(const char*, const char*); + + lstring(); + lstring(std::initializer_list); + }; + + //compare.hpp + inline char chrlower(char c); + inline char chrupper(char c); + inline int stricmp(const char *str1, const char *str2); + inline bool wildcard(const char *str, const char *pattern); + inline bool iwildcard(const char *str, const char *pattern); + inline bool strbegin (const char *str, const char *key); + inline bool stribegin(const char *str, const char *key); + inline bool strend (const char *str, const char *key); + inline bool striend(const char *str, const char *key); + + //convert.hpp + inline char* strlower(char *str); + inline char* strupper(char *str); + inline char* strtr(char *dest, const char *before, const char *after); + inline uintmax_t hex (const char *str); + inline intmax_t integer(const char *str); + inline uintmax_t decimal(const char *str); + inline uintmax_t binary (const char *str); + inline double fp (const char *str); + + //math.hpp + inline bool strint (const char *str, int &result); + inline bool strmath(const char *str, int &result); + + //platform.hpp + inline string realpath(const char *name); + inline string userpath(); + inline string currentpath(); + + //strl.hpp + inline unsigned strlcpy(char *dest, const char *src, unsigned length); + inline unsigned strlcat(char *dest, const char *src, unsigned length); + + //strpos.hpp + inline optional strpos(const char *str, const char *key); + inline optional qstrpos(const char *str, const char *key); + + //trim.hpp + template inline char* ltrim(char *str, const char *key = " "); + template inline char* rtrim(char *str, const char *key = " "); + template inline char* trim (char *str, const char *key = " "); + + //utility.hpp + inline unsigned strlcpy(string &dest, const char *src, unsigned length); + inline unsigned strlcat(string &dest, const char *src, unsigned length); + inline string substr(const char *src, unsigned start = 0, unsigned length = 0); + template inline string hex(uintmax_t value); + template inline string integer(intmax_t value); + template inline string decimal(uintmax_t value); + template inline string binary(uintmax_t value); + inline unsigned fp(char *str, double value); + inline string fp(double value); + + //variadic.hpp + template inline void print(Args&&... args); +}; + +#endif diff --git a/nall/string/bsv.hpp b/nall/string/bsv.hpp new file mode 100755 index 00000000..d4b919e0 --- /dev/null +++ b/nall/string/bsv.hpp @@ -0,0 +1,75 @@ +#ifndef NALL_STRING_BSV_HPP +#define NALL_STRING_BSV_HPP + +//BSV parser +//version 0.01 + +namespace nall { + +inline string bsv_decode(const char *input) { + string output; + unsigned offset = 0; + while(*input) { + //illegal characters + if(*input == '}' ) return ""; + if(*input == '\r') return ""; + if(*input == '\n') return ""; + + //normal characters + if(*input != '{') { output[offset++] = *input++; continue; } + + //entities + if(strbegin(input, "{lf}")) { output[offset++] = '\n'; input += 4; continue; } + if(strbegin(input, "{lb}")) { output[offset++] = '{'; input += 4; continue; } + if(strbegin(input, "{rb}")) { output[offset++] = '}'; input += 4; continue; } + + //illegal entities + return ""; + } + output[offset] = 0; + return output; +} + +inline string bsv_encode(const char *input) { + string output; + unsigned offset = 0; + while(*input) { + //illegal characters + if(*input == '\r') return ""; + + if(*input == '\n') { + output[offset++] = '{'; + output[offset++] = 'l'; + output[offset++] = 'f'; + output[offset++] = '}'; + input++; + continue; + } + + if(*input == '{') { + output[offset++] = '{'; + output[offset++] = 'l'; + output[offset++] = 'b'; + output[offset++] = '}'; + input++; + continue; + } + + if(*input == '}') { + output[offset++] = '{'; + output[offset++] = 'r'; + output[offset++] = 'b'; + output[offset++] = '}'; + input++; + continue; + } + + output[offset++] = *input++; + } + output[offset] = 0; + return output; +} + +} + +#endif diff --git a/nall/string/cast.hpp b/nall/string/cast.hpp new file mode 100755 index 00000000..d8503106 --- /dev/null +++ b/nall/string/cast.hpp @@ -0,0 +1,32 @@ +#ifndef NALL_STRING_CAST_HPP +#define NALL_STRING_CAST_HPP + +namespace nall { + +//this is needed, as C++0x does not support explicit template specialization inside classes +template<> inline string to_string (bool v) { return v ? "true" : "false"; } +template<> inline string to_string (signed int v) { return integer(v); } +template<> inline string to_string (unsigned int v) { return decimal(v); } +template<> inline string to_string (double v) { return fp(v); } +template<> inline string to_string (char *v) { return v; } +template<> inline string to_string (const char *v) { return v; } +template<> inline string to_string (string v) { return v; } +template<> inline string to_string(const string &v) { return v; } + +template string& string::operator= (T value) { return assign(to_string(value)); } +template string& string::operator<<(T value) { return append(to_string(value)); } + +template lstring& lstring::operator<<(T value) { + operator[](size()).assign(to_string(value)); + return *this; +} + +#if defined(QSTRING_H) +template<> inline string to_string(QString v) { return v.toUtf8().constData(); } +template<> inline string to_string(const QString &v) { return v.toUtf8().constData(); } +string::operator QString() const { return QString::fromUtf8(*this); } +#endif + +} + +#endif diff --git a/nall/string/compare.hpp b/nall/string/compare.hpp new file mode 100755 index 00000000..bce0895b --- /dev/null +++ b/nall/string/compare.hpp @@ -0,0 +1,110 @@ +#ifndef NALL_STRING_COMPARE_HPP +#define NALL_STRING_COMPARE_HPP + +namespace nall { + +char chrlower(char c) { + return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; +} + +char chrupper(char c) { + return (c >= 'a' && c <= 'z') ? c - ('a' - 'A') : c; +} + +int stricmp(const char *str1, const char *str2) { + while(*str1) { + if(chrlower(*str1) != chrlower(*str2)) break; + str1++, str2++; + } + return (int)chrlower(*str1) - (int)chrlower(*str2); +} + +bool wildcard(const char *s, const char *p) { + const char *cp = 0, *mp = 0; + while(*s && *p != '*') { + if(*p != '?' && *s != *p) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || *p == *s) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +bool iwildcard(const char *s, const char *p) { + const char *cp = 0, *mp = 0; + while(*s && *p != '*') { + if(*p != '?' && chrlower(*s) != chrlower(*p)) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || chrlower(*p) == chrlower(*s)) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +bool strbegin(const char *str, const char *key) { + int i, ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str, key, ksl)); +} + +bool stribegin(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = 0; i < ksl; i++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[i] && str[i]+0x20 != key[i])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[i] && str[i]-0x20 != key[i])return false; + } else { + if(str[i] != key[i])return false; + } + } + return true; +} + +bool strend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str + ssl - ksl, key, ksl)); +} + +bool striend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = ssl - ksl, z = 0; i < ssl; i++, z++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[z] && str[i]+0x20 != key[z])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[z] && str[i]-0x20 != key[z])return false; + } else { + if(str[i] != key[z])return false; + } + } + return true; +} + +} + +#endif diff --git a/nall/string/convert.hpp b/nall/string/convert.hpp new file mode 100755 index 00000000..9040cb83 --- /dev/null +++ b/nall/string/convert.hpp @@ -0,0 +1,153 @@ +#ifndef NALL_STRING_CONVERT_HPP +#define NALL_STRING_CONVERT_HPP + +namespace nall { + +char* strlower(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrlower(str[i]); + i++; + } + return str; +} + +char* strupper(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrupper(str[i]); + i++; + } + return str; +} + +char* strtr(char *dest, const char *before, const char *after) { + if(!dest || !before || !after) return dest; + int sl = strlen(dest), bsl = strlen(before), asl = strlen(after); + + if(bsl != asl || bsl == 0) return dest; //patterns must be the same length for 1:1 replace + for(unsigned i = 0; i < sl; i++) { + for(unsigned l = 0; l < bsl; l++) { + if(dest[i] == before[l]) { + dest[i] = after[l]; + break; + } + } + } + + return dest; +} + +uintmax_t hex(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + //skip hex identifiers 0x and $, if present + if(*str == '0' && (*(str + 1) == 'X' || *(str + 1) == 'x')) str += 2; + else if(*str == '$') str++; + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else if(x >= 'A' && x <= 'F') x -= 'A' - 10; + else if(x >= 'a' && x <= 'f') x -= 'a' - 10; + else break; //stop at first invalid character + result = result * 16 + x; + } + + return result; +} + +intmax_t integer(const char *str) { + if(!str) return 0; + intmax_t result = 0; + bool negate = false; + + //check for negation + if(*str == '-') { + negate = true; + str++; + } + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result = result * 10 + x; + } + + return !negate ? result : -result; +} + +uintmax_t decimal(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result = result * 10 + x; + } + + return result; +} + +uintmax_t binary(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + //skip bin identifiers 0b and %, if present + if(*str == '0' && (*(str + 1) == 'B' || *(str + 1) == 'b')) str += 2; + else if(*str == '%') str++; + + while(*str) { + uint8_t x = *str++; + if(x == '0' || x == '1') x -= '0'; + else break; //stop at first invalid character + result = result * 2 + x; + } + + return result; +} + +double fp(const char *str) { + if(!str) return 0.0; + bool negate = false; + + //check for negation + if(*str == '-') { + negate = true; + str++; + } + + intmax_t result_integral = 0; + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else if(x == '.' || x == ',') break; //break loop and read fractional part + else return (double)result_integral; //invalid value, assume no fractional part + result_integral = result_integral * 10 + x; + } + + intmax_t result_fractional = 0; + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result_fractional = result_fractional * 10 + x; + } + + //calculate fractional portion + double result = (double)result_fractional; + while((uintmax_t)result > 0) result /= 10.0; + result += (double)result_integral; + + return !negate ? result : -result; +} + +} + +#endif diff --git a/nall/string/core.hpp b/nall/string/core.hpp new file mode 100755 index 00000000..976ae9b3 --- /dev/null +++ b/nall/string/core.hpp @@ -0,0 +1,139 @@ +#ifndef NALL_STRING_CORE_HPP +#define NALL_STRING_CORE_HPP + +namespace nall { + +void string::reserve(unsigned size_) { + if(size_ > size) { + size = size_; + data = (char*)realloc(data, size + 1); + data[size] = 0; + } +} + +string& string::assign(const char *s) { + unsigned length = strlen(s); + reserve(length); + strcpy(data, s); + return *this; +} + +string& string::append(const char *s) { + unsigned length = strlen(data) + strlen(s); + reserve(length); + strcat(data, s); + return *this; +} + +string& string::append(bool value) { append(value ? "true" : "false"); return *this; } +string& string::append(signed int value) { append(integer(value)); return *this; } +string& string::append(unsigned int value) { append(decimal(value)); return *this; } +string& string::append(double value) { append(fp(value)); return *this; } + +string::operator const char*() const { + return data; +} + +char* string::operator()() { + return data; +} + +char& string::operator[](int index) { + reserve(index); + return data[index]; +} + +bool string::operator==(const char *str) const { return strcmp(data, str) == 0; } +bool string::operator!=(const char *str) const { return strcmp(data, str) != 0; } +bool string::operator< (const char *str) const { return strcmp(data, str) < 0; } +bool string::operator<=(const char *str) const { return strcmp(data, str) <= 0; } +bool string::operator> (const char *str) const { return strcmp(data, str) > 0; } +bool string::operator>=(const char *str) const { return strcmp(data, str) >= 0; } + +string& string::operator=(const string &value) { + assign(value); + return *this; +} + +string& string::operator=(string &&source) { + if(data) free(data); + size = source.size; + data = source.data; + source.data = 0; + source.size = 0; + return *this; +} + +static void istring(string &output) { +} + +template +static void istring(string &output, const T &value, Args&&... args) { + output.append(value); + istring(output, std::forward(args)...); +} + +template string::string(Args&&... args) { + size = 64; + data = (char*)malloc(size + 1); + *data = 0; + istring(*this, std::forward(args)...); +} + +string::string(const string &value) { + size = strlen(value); + data = strdup(value); +} + +string::string(string &&source) { + size = source.size; + data = source.data; + source.data = 0; +} + +string::~string() { + if(data) free(data); +} + +bool string::readfile(const char *filename) { + assign(""); + + #if !defined(_WIN32) + FILE *fp = fopen(filename, "rb"); + #else + FILE *fp = _wfopen(utf16_t(filename), L"rb"); + #endif + if(!fp) return false; + + fseek(fp, 0, SEEK_END); + unsigned size = ftell(fp); + rewind(fp); + char *fdata = new char[size + 1]; + unsigned unused = fread(fdata, 1, size, fp); + fclose(fp); + fdata[size] = 0; + assign(fdata); + delete[] fdata; + + return true; +} + +optional lstring::find(const char *key) const { + for(unsigned i = 0; i < size(); i++) { + if(operator[](i) == key) return { true, i }; + } + return { false, 0 }; +} + +inline lstring::lstring() { +} + +inline lstring::lstring(std::initializer_list list) { + for(const string *s = list.begin(); s != list.end(); ++s) { + operator<<(*s); + } +} + +} + +#endif diff --git a/nall/string/filename.hpp b/nall/string/filename.hpp new file mode 100755 index 00000000..93d605ae --- /dev/null +++ b/nall/string/filename.hpp @@ -0,0 +1,63 @@ +#ifndef NALL_FILENAME_HPP +#define NALL_FILENAME_HPP + +namespace nall { + +// "foo/bar.c" -> "foo/" +// "foo/" -> "foo/" +// "bar.c" -> "./" +inline string dir(char const *name) { + string result = name; + for(signed i = strlen(result); i >= 0; i--) { + if(result[i] == '/' || result[i] == '\\') { + result[i + 1] = 0; + break; + } + if(i == 0) result = "./"; + } + return result; +} + +// "foo/bar.c" -> "bar.c" +inline string notdir(char const *name) { + for(signed i = strlen(name); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') { + name += i + 1; + break; + } + } + string result = name; + return result; +} + +// "foo/bar.c" -> "foo/bar" +inline string basename(char const *name) { + string result = name; + for(signed i = strlen(result); i >= 0; i--) { + if(result[i] == '/' || result[i] == '\\') { + //file has no extension + break; + } + if(result[i] == '.') { + result[i] = 0; + break; + } + } + return result; +} + +// "foo/bar.c" -> "c" +inline string extension(char const *name) { + for(signed i = strlen(name); i >= 0; i--) { + if(name[i] == '.') { + name += i + 1; + break; + } + } + string result = name; + return result; +} + +} + +#endif diff --git a/nall/string/math.hpp b/nall/string/math.hpp new file mode 100755 index 00000000..ea8b99c8 --- /dev/null +++ b/nall/string/math.hpp @@ -0,0 +1,164 @@ +#ifndef NALL_STRING_MATH_HPP +#define NALL_STRING_MATH_HPP + +namespace nall { + +static int eval_integer(const char *&s) { + if(!*s) throw "unrecognized_integer"; + int value = 0, x = *s, y = *(s + 1); + + //hexadecimal + if(x == '0' && (y == 'X' || y == 'x')) { + s += 2; + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } + if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } + if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } + return value; + } + } + + //binary + if(x == '0' && (y == 'B' || y == 'b')) { + s += 2; + while(true) { + if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } + return value; + } + } + + //octal (or decimal '0') + if(x == '0') { + s += 1; + while(true) { + if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } + return value; + } + } + + //decimal + if(x >= '0' && x <= '9') { + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } + return value; + } + } + + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched_char"; + } + } + + throw "unrecognized_integer"; +} + +static int eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized_token"; + int value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched_group"; + } + + else if(x == '!') value = !eval(++s, 13); + else if(x == '~') value = ~eval(++s, 13); + else if(x == '+') value = +eval(++s, 13); + else if(x == '-') value = -eval(++s, 13); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else throw "unrecognized_token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 13) break; + if(x == '*') { value *= eval(++s, 13); continue; } + if(x == '/') { value /= eval(++s, 13); continue; } + if(x == '%') { value %= eval(++s, 13); continue; } + + if(depth >= 12) break; + if(x == '+') { value += eval(++s, 12); continue; } + if(x == '-') { value -= eval(++s, 12); continue; } + + if(depth >= 11) break; + if(x == '<' && y == '<') { value <<= eval(++++s, 11); continue; } + if(x == '>' && y == '>') { value >>= eval(++++s, 11); continue; } + + if(depth >= 10) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 10); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 10); continue; } + if(x == '<') { value = value < eval(++s, 10); continue; } + if(x == '>') { value = value > eval(++s, 10); continue; } + + if(depth >= 9) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 9); continue; } + if(x == '!' && y == '=') { value = value != eval(++++s, 9); continue; } + + if(depth >= 8) break; + if(x == '&' && y != '&') { value = value & eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '^' && y != '^') { value = value ^ eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '|' && y != '|') { value = value | eval(++s, 6); continue; } + + if(depth >= 5) break; + if(x == '&' && y == '&') { value = eval(++++s, 5) && value; continue; } + + if(depth >= 4) break; + if(x == '^' && y == '^') { value = (!eval(++++s, 4) != !value); continue; } + + if(depth >= 3) break; + if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } + + if(x == '?') { + int lhs = eval(++s, 2); + if(*s != ':') throw "mismatched_ternary"; + int rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized_token"; + } + + return value; +} + +bool strint(const char *s, int &result) { + try { + result = eval_integer(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +bool strmath(const char *s, int &result) { + try { + result = eval(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +} + +#endif diff --git a/nall/string/platform.hpp b/nall/string/platform.hpp new file mode 100755 index 00000000..42c1a756 --- /dev/null +++ b/nall/string/platform.hpp @@ -0,0 +1,41 @@ +#ifndef NALL_STRING_PLATFORM_HPP +#define NALL_STRING_PLATFORM_HPP + +namespace nall { + +string realpath(const char *name) { + char path[PATH_MAX]; + if(::realpath(name, path)) { + string result(path); + result.transform("\\", "/"); + if(result.endswith("/") == false) result.append("/"); + return result; + } + return ""; +} + +string userpath() { + char path[PATH_MAX]; + if(::userpath(path)) { + string result(path); + result.transform("\\", "/"); + if(result.endswith("/") == false) result.append("/"); + return result; + } + return ""; +} + +string currentpath() { + char path[PATH_MAX]; + if(::getcwd(path)) { + string result(path); + result.transform("\\", "/"); + if(result.endswith("/") == false) result.append("/"); + return result; + } + return ""; +} + +} + +#endif diff --git a/nall/string/replace.hpp b/nall/string/replace.hpp new file mode 100755 index 00000000..db405a9b --- /dev/null +++ b/nall/string/replace.hpp @@ -0,0 +1,103 @@ +#ifndef NALL_STRING_REPLACE_HPP +#define NALL_STRING_REPLACE_HPP + +namespace nall { + +string& string::replace(const char *key, const char *token) { + int i, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); + unsigned int replace_count = 0, size = ssl; + char *buffer; + + if(ksl <= ssl) { + if(tsl > ksl) { //the new string may be longer than the old string... + for(i = 0; i <= ssl - ksl;) { //so let's find out how big of a string we'll need... + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; + } + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); + } + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; + } + buffer[z] = 0; + + assign(buffer); + delete[] buffer; + } + + return *this; +} + +string& string::qreplace(const char *key, const char *token) { + int i, l, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); + unsigned int replace_count = 0, size = ssl; + uint8_t x; + char *buffer; + + if(ksl <= ssl) { + if(tsl > ksl) { + for(i = 0; i <= ssl - ksl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i; + i++; + while(data[i++] != x) { + if(i == ssl) { + i = l; + break; + } + } + } + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; + } + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); + } + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i++; + while(data[i] != x && i < ssl)i++; + if(i >= ssl)i = l; + else { + memcpy(buffer + z, data + l, i - l); + z += i - l; + } + } + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + replace_count++; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; + } + buffer[z] = 0; + + assign(buffer); + delete[] buffer; + } + + return *this; +} + +}; + +#endif diff --git a/nall/string/split.hpp b/nall/string/split.hpp new file mode 100755 index 00000000..8d3ca877 --- /dev/null +++ b/nall/string/split.hpp @@ -0,0 +1,58 @@ +#ifndef NALL_STRING_SPLIT_HPP +#define NALL_STRING_SPLIT_HPP + +namespace nall { + +template void lstring::split(const char *key, const char *src) { + unsigned limit = Limit; + reset(); + + int ssl = strlen(src), ksl = strlen(key); + int lp = 0, split_count = 0; + + for(int i = 0; i <= ssl - ksl;) { + if(!memcmp(src + i, key, ksl)) { + strlcpy(operator[](split_count++), src + lp, i - lp + 1); + i += ksl; + lp = i; + if(!--limit) break; + } else i++; + } + + operator[](split_count++) = src + lp; +} + +template void lstring::qsplit(const char *key, const char *src) { + unsigned limit = Limit; + reset(); + + int ssl = strlen(src), ksl = strlen(key); + int lp = 0, split_count = 0; + + for(int i = 0; i <= ssl - ksl;) { + uint8_t x = src[i]; + + if(x == '\"' || x == '\'') { + int z = i++; //skip opening quote + while(i < ssl && src[i] != x) i++; + if(i >= ssl) i = z; //failed match, rewind i + else { + i++; //skip closing quote + continue; //restart in case next char is also a quote + } + } + + if(!memcmp(src + i, key, ksl)) { + strlcpy(operator[](split_count++), src + lp, i - lp + 1); + i += ksl; + lp = i; + if(!--limit) break; + } else i++; + } + + operator[](split_count++) = src + lp; +} + +}; + +#endif diff --git a/nall/string/strl.hpp b/nall/string/strl.hpp new file mode 100755 index 00000000..84c841fa --- /dev/null +++ b/nall/string/strl.hpp @@ -0,0 +1,52 @@ +#ifndef NALL_STRING_STRL_HPP +#define NALL_STRING_STRL_HPP + +namespace nall { + +//strlcpy, strlcat based on OpenBSD implementation by Todd C. Miller + +//return = strlen(src) +unsigned strlcpy(char *dest, const char *src, unsigned length) { + char *d = dest; + const char *s = src; + unsigned n = length; + + if(n) { + while(--n && (*d++ = *s++)); //copy as many bytes as possible, or until null terminator reached + } + + if(!n) { + if(length) *d = 0; + while(*s++); //traverse rest of s, so that s - src == strlen(src) + } + + return (s - src - 1); //return length of copied string, sans null terminator +} + +//return = strlen(src) + min(length, strlen(dest)) +unsigned strlcat(char *dest, const char *src, unsigned length) { + char *d = dest; + const char *s = src; + unsigned n = length; + + while(n-- && *d) d++; //find end of dest + unsigned dlength = d - dest; + n = length - dlength; //subtract length of dest from maximum string length + + if(!n) return dlength + strlen(s); + + while(*s) { + if(n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = 0; + + return dlength + (s - src); //return length of resulting string, sans null terminator +} + +} + +#endif diff --git a/nall/string/strpos.hpp b/nall/string/strpos.hpp new file mode 100755 index 00000000..1907a2f3 --- /dev/null +++ b/nall/string/strpos.hpp @@ -0,0 +1,41 @@ +#ifndef NALL_STRING_STRPOS_HPP +#define NALL_STRING_STRPOS_HPP + +//usage example: +//if(auto pos = strpos(str, key)) print(pos(), "\n"); +//prints position of key within str, only if it is found + +namespace nall { + +optional strpos(const char *str, const char *key) { + unsigned ssl = strlen(str), ksl = strlen(key); + if(ksl > ssl) return { false, 0 }; + + for(unsigned i = 0; i <= ssl - ksl; i++) { + if(!memcmp(str + i, key, ksl)) return { true, i }; + } + + return { false, 0 }; +} + +optional qstrpos(const char *str, const char *key) { + unsigned ssl = strlen(str), ksl = strlen(key); + if(ksl > ssl) return { false, 0 }; + + for(unsigned i = 0; i <= ssl - ksl;) { + uint8_t x = str[i]; + if(x == '\"' || x == '\'') { + uint8_t z = i++; + while(str[i] != x && i < ssl) i++; + if(i >= ssl) i = z; + } + if(!memcmp(str + i, key, ksl)) return { true, i }; + i++; + } + + return { false, 0 }; +} + +} + +#endif diff --git a/nall/string/trim.hpp b/nall/string/trim.hpp new file mode 100755 index 00000000..f5355d7d --- /dev/null +++ b/nall/string/trim.hpp @@ -0,0 +1,38 @@ +#ifndef NALL_STRING_TRIM_HPP +#define NALL_STRING_TRIM_HPP + +namespace nall { + +//limit defaults to zero, which will underflow on first compare; equivalent to no limit +template char* ltrim(char *str, const char *key) { + unsigned limit = Limit; + if(!key || !*key) return str; + while(strbegin(str, key)) { + char *dest = str, *src = str + strlen(key); + while(true) { + *dest = *src++; + if(!*dest) break; + dest++; + } + if(--limit == 0) break; + } + return str; +} + +template char* rtrim(char *str, const char *key) { + unsigned limit = Limit; + if(!key || !*key) return str; + while(strend(str, key)) { + str[strlen(str) - strlen(key)] = 0; + if(--limit == 0) break; + } + return str; +} + +template char* trim(char *str, const char *key) { + return ltrim(rtrim(str, key), key); +} + +} + +#endif diff --git a/nall/string/utility.hpp b/nall/string/utility.hpp new file mode 100755 index 00000000..779e6c7d --- /dev/null +++ b/nall/string/utility.hpp @@ -0,0 +1,157 @@ +#ifndef NALL_STRING_UTILITY_HPP +#define NALL_STRING_UTILITY_HPP + +namespace nall { + +unsigned strlcpy(string &dest, const char *src, unsigned length) { + dest.reserve(length); + return strlcpy(dest(), src, length); +} + +unsigned strlcat(string &dest, const char *src, unsigned length) { + dest.reserve(length); + return strlcat(dest(), src, length); +} + +string substr(const char *src, unsigned start, unsigned length) { + string dest; + if(length == 0) { + //copy entire string + dest = src + start; + } else { + //copy partial string + strlcpy(dest, src + start, length + 1); + } + return dest; +} + +/* arithmetic <> string */ + +template string hex(uintmax_t value) { + string output; + unsigned offset = 0; + + //render string backwards, as we do not know its length yet + do { + unsigned n = value & 15; + output[offset++] = n < 10 ? '0' + n : 'a' + n - 10; + value >>= 4; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + //reverse the string in-place + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string integer(intmax_t value) { + string output; + unsigned offset = 0; + + bool negative = value < 0; + if(negative) value = abs(value); + + do { + unsigned n = value % 10; + output[offset++] = '0' + n; + value /= 10; + } while(value); + + while(offset < length) output[offset++] = padding; + if(negative) output[offset++] = '-'; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string decimal(uintmax_t value) { + string output; + unsigned offset = 0; + + do { + unsigned n = value % 10; + output[offset++] = '0' + n; + value /= 10; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string binary(uintmax_t value) { + string output; + unsigned offset = 0; + + do { + unsigned n = value & 1; + output[offset++] = '0' + n; + value >>= 1; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +//using sprintf is certainly not the most ideal method to convert +//a double to a string ... but attempting to parse a double by +//hand, digit-by-digit, results in subtle rounding errors. +unsigned fp(char *str, double value) { + char buffer[256]; + sprintf(buffer, "%f", value); + + //remove excess 0's in fraction (2.500000 -> 2.5) + for(char *p = buffer; *p; p++) { + if(*p == '.') { + char *p = buffer + strlen(buffer) - 1; + while(*p == '0') { + if(*(p - 1) != '.') *p = 0; //... but not for eg 1.0 -> 1. + p--; + } + break; + } + } + + unsigned length = strlen(buffer); + if(str) strcpy(str, buffer); + return length + 1; +} + +string fp(double value) { + string temp; + temp.reserve(fp(0, value)); + fp(temp(), value); + return temp; +} + +} + +#endif diff --git a/nall/string/variadic.hpp b/nall/string/variadic.hpp new file mode 100755 index 00000000..6c027fc8 --- /dev/null +++ b/nall/string/variadic.hpp @@ -0,0 +1,12 @@ +#ifndef NALL_STRING_VARIADIC_HPP +#define NALL_STRING_VARIADIC_HPP + +namespace nall { + +template inline void print(Args&&... args) { + printf("%s", (const char*)string(std::forward(args)...)); +} + +} + +#endif diff --git a/nall/string/wrapper.hpp b/nall/string/wrapper.hpp new file mode 100755 index 00000000..eadf0a10 --- /dev/null +++ b/nall/string/wrapper.hpp @@ -0,0 +1,33 @@ +#ifndef NALL_STRING_WRAPPER_HPP +#define NALL_STRING_WRAPPER_HPP + +namespace nall { + +unsigned string::length() const { return strlen(data); } + +bool string::equals(const char *str) const { return !strcmp(data, str); } +bool string::iequals(const char *str) const { return !stricmp(data, str); } + +bool string::wildcard(const char *str) const { return nall::wildcard(data, str); } +bool string::iwildcard(const char *str) const { return nall::iwildcard(data, str); } + +bool string::beginswith(const char *str) const { return strbegin(data, str); } +bool string::ibeginswith(const char *str) const { return stribegin(data, str); } + +bool string::endswith(const char *str) const { return strend(data, str); } +bool string::iendswith(const char *str) const { return striend(data, str); } + +string& string::lower() { nall::strlower(data); return *this; } +string& string::upper() { nall::strupper(data); return *this; } +string& string::transform(const char *before, const char *after) { nall::strtr(data, before, after); return *this; } + +template string& string::ltrim(const char *key) { nall::ltrim(data, key); return *this; } +template string& string::rtrim(const char *key) { nall::rtrim(data, key); return *this; } +template string& string::trim (const char *key) { nall::trim (data, key); return *this; } + +optional string::position(const char *key) const { return strpos(data, key); } +optional string::qposition(const char *key) const { return qstrpos(data, key); } + +} + +#endif diff --git a/nall/string/xml.hpp b/nall/string/xml.hpp new file mode 100755 index 00000000..185a89f9 --- /dev/null +++ b/nall/string/xml.hpp @@ -0,0 +1,266 @@ +#ifndef NALL_STRING_XML_HPP +#define NALL_STRING_XML_HPP + +//XML subset parser +//version 0.05 + +namespace nall { + +struct xml_attribute { + string name; + string content; + virtual string parse() const; +}; + +struct xml_element : xml_attribute { + string parse() const; + linear_vector attribute; + linear_vector element; + +protected: + void parse_doctype(const char *&data); + bool parse_head(string data); + bool parse_body(const char *&data); + friend xml_element xml_parse(const char *data); +}; + +inline string xml_attribute::parse() const { + string data; + unsigned offset = 0; + + const char *source = content; + while(*source) { + if(*source == '&') { + if(strbegin(source, "<")) { data[offset++] = '<'; source += 4; continue; } + if(strbegin(source, ">")) { data[offset++] = '>'; source += 4; continue; } + if(strbegin(source, "&")) { data[offset++] = '&'; source += 5; continue; } + if(strbegin(source, "'")) { data[offset++] = '\''; source += 6; continue; } + if(strbegin(source, """)) { data[offset++] = '"'; source += 6; continue; } + } + + //reject illegal characters + if(*source == '&') return ""; + if(*source == '<') return ""; + if(*source == '>') return ""; + + data[offset++] = *source++; + } + + data[offset] = 0; + return data; +} + +inline string xml_element::parse() const { + string data; + unsigned offset = 0; + + const char *source = content; + while(*source) { + if(*source == '&') { + if(strbegin(source, "<")) { data[offset++] = '<'; source += 4; continue; } + if(strbegin(source, ">")) { data[offset++] = '>'; source += 4; continue; } + if(strbegin(source, "&")) { data[offset++] = '&'; source += 5; continue; } + if(strbegin(source, "'")) { data[offset++] = '\''; source += 6; continue; } + if(strbegin(source, """)) { data[offset++] = '"'; source += 6; continue; } + } + + if(strbegin(source, "")) { + source += pos() + 3; + continue; + } else { + return ""; + } + } + + if(strbegin(source, "")) { + if(pos() - 9 > 0) { + string cdata = substr(source, 9, pos() - 9); + data << cdata; + offset += strlen(cdata); + } + source += 9 + offset + 3; + continue; + } else { + return ""; + } + } + + //reject illegal characters + if(*source == '&') return ""; + if(*source == '<') return ""; + if(*source == '>') return ""; + + data[offset++] = *source++; + } + + data[offset] = 0; + return data; +} + +inline void xml_element::parse_doctype(const char *&data) { + name = "!DOCTYPE"; + const char *content_begin = data; + + signed counter = 0; + while(*data) { + char value = *data++; + if(value == '<') counter++; + if(value == '>') counter--; + if(counter < 0) { + content = substr(content_begin, 0, data - content_begin - 1); + return; + } + } + throw "..."; +} + +inline bool xml_element::parse_head(string data) { + data.qreplace("\t", " "); + data.qreplace("\r", " "); + data.qreplace("\n", " "); + while(qstrpos(data, " ")) data.qreplace(" ", " "); + data.qreplace(" =", "="); + data.qreplace("= ", "="); + data.rtrim(); + + lstring part; + part.qsplit(" ", data); + + name = part[0]; + if(name == "") throw "..."; + + for(unsigned i = 1; i < part.size(); i++) { + lstring side; + side.qsplit("=", part[i]); + if(side.size() != 2) throw "..."; + + xml_attribute attr; + attr.name = side[0]; + attr.content = side[1]; + if(strbegin(attr.content, "\"") && strend(attr.content, "\"")) attr.content.trim<1>("\""); + else if(strbegin(attr.content, "'") && strend(attr.content, "'")) attr.content.trim<1>("'"); + else throw "..."; + attribute.append(attr); + } +} + +inline bool xml_element::parse_body(const char *&data) { + while(true) { + if(!*data) return false; + if(*data++ != '<') continue; + if(*data == '/') return false; + + if(strbegin(data, "!DOCTYPE") == true) { + parse_doctype(data); + return true; + } + + if(strbegin(data, "!--")) { + if(auto offset = strpos(data, "-->")) { + data += offset() + 3; + continue; + } else { + throw "..."; + } + } + + if(strbegin(data, "![CDATA[")) { + if(auto offset = strpos(data, "]]>")) { + data += offset() + 3; + continue; + } else { + throw "..."; + } + } + + auto offset = strpos(data, ">"); + if(!offset) throw "..."; + + string tag = substr(data, 0, offset()); + data += offset() + 1; + const char *content_begin = data; + + bool self_terminating = false; + + if(strend(tag, "?") == true) { + self_terminating = true; + tag.rtrim<1>("?"); + } else if(strend(tag, "/") == true) { + self_terminating = true; + tag.rtrim<1>("/"); + } + + parse_head(tag); + if(self_terminating) return true; + + while(*data) { + unsigned index = element.size(); + xml_element node; + if(node.parse_body(data) == false) { + if(*data == '/') { + signed length = data - content_begin - 1; + if(length > 0) content = substr(content_begin, 0, length); + + data++; + auto offset = strpos(data, ">"); + if(!offset) throw "..."; + + tag = substr(data, 0, offset()); + data += offset() + 1; + + tag.replace("\t", " "); + tag.replace("\r", " "); + tag.replace("\n", " "); + while(strpos(tag, " ")) tag.replace(" ", " "); + tag.rtrim(); + + if(name != tag) throw "..."; + return true; + } + } else { + element.append(node); + } + } + } +} + +//ensure there is only one root element +inline bool xml_validate(xml_element &document) { + unsigned root_counter = 0; + + for(unsigned i = 0; i < document.element.size(); i++) { + string &name = document.element[i].name; + if(strbegin(name, "?")) continue; + if(strbegin(name, "!")) continue; + if(++root_counter > 1) return false; + } + + return true; +} + +inline xml_element xml_parse(const char *data) { + xml_element self; + + try { + while(*data) { + xml_element node; + if(node.parse_body(data) == false) { + break; + } else { + self.element.append(node); + } + } + + if(xml_validate(self) == false) throw "..."; + return self; + } catch(const char*) { + xml_element empty; + return empty; + } +} + +} + +#endif diff --git a/nall/ups.hpp b/nall/ups.hpp new file mode 100755 index 00000000..ffcdb2d7 --- /dev/null +++ b/nall/ups.hpp @@ -0,0 +1,223 @@ +#ifndef NALL_UPS_HPP +#define NALL_UPS_HPP + +#include +#include +#include +#include + +namespace nall { + +struct ups { + enum class result : unsigned { + unknown, + success, + patch_unwritable, + patch_invalid, + source_invalid, + target_invalid, + target_too_small, + patch_checksum_invalid, + source_checksum_invalid, + target_checksum_invalid, + }; + + function progress; + + result create( + const uint8_t *sourcedata, unsigned sourcelength, + const uint8_t *targetdata, unsigned targetlength, + const char *patchfilename + ) { + source_data = (uint8_t*)sourcedata, target_data = (uint8_t*)targetdata; + source_length = sourcelength, target_length = targetlength; + source_offset = target_offset = 0; + source_checksum = target_checksum = patch_checksum = ~0; + + if(patch_file.open(patchfilename, file::mode::write) == false) return result::patch_unwritable; + + patch_write('U'); + patch_write('P'); + patch_write('S'); + patch_write('1'); + encode(source_length); + encode(target_length); + + unsigned output_length = source_length > target_length ? source_length : target_length; + unsigned relative = 0; + for(unsigned offset = 0; offset < output_length;) { + uint8_t x = source_read(); + uint8_t y = target_read(); + + if(x == y) { + offset++; + continue; + } + + encode(offset++ - relative); + patch_write(x ^ y); + + while(true) { + if(offset >= output_length) { + patch_write(0x00); + break; + } + + x = source_read(); + y = target_read(); + offset++; + patch_write(x ^ y); + if(x == y) break; + } + + relative = offset; + } + + source_checksum = ~source_checksum; + target_checksum = ~target_checksum; + for(unsigned i = 0; i < 4; i++) patch_write(source_checksum >> (i * 8)); + for(unsigned i = 0; i < 4; i++) patch_write(target_checksum >> (i * 8)); + uint32_t patch_result_checksum = ~patch_checksum; + for(unsigned i = 0; i < 4; i++) patch_write(patch_result_checksum >> (i * 8)); + + patch_file.close(); + return result::success; + } + + result apply( + const uint8_t *patchdata, unsigned patchlength, + const uint8_t *sourcedata, unsigned sourcelength, + uint8_t *targetdata, unsigned &targetlength + ) { + patch_data = (uint8_t*)patchdata, source_data = (uint8_t*)sourcedata, target_data = targetdata; + patch_length = patchlength, source_length = sourcelength, target_length = targetlength; + patch_offset = source_offset = target_offset = 0; + patch_checksum = source_checksum = target_checksum = ~0; + + if(patch_length < 18) return result::patch_invalid; + if(patch_read() != 'U') return result::patch_invalid; + if(patch_read() != 'P') return result::patch_invalid; + if(patch_read() != 'S') return result::patch_invalid; + if(patch_read() != '1') return result::patch_invalid; + + unsigned source_read_length = decode(); + unsigned target_read_length = decode(); + + if(source_length != source_read_length && source_length != target_read_length) return result::source_invalid; + targetlength = (source_length == source_read_length ? target_read_length : source_read_length); + if(target_length < targetlength) return result::target_too_small; + target_length = targetlength; + + while(patch_offset < patch_length - 12) { + unsigned length = decode(); + while(length--) target_write(source_read()); + while(true) { + uint8_t patch_xor = patch_read(); + target_write(patch_xor ^ source_read()); + if(patch_xor == 0) break; + } + } + while(source_offset < source_length) target_write(source_read()); + while(target_offset < target_length) target_write(source_read()); + + uint32_t patch_read_checksum = 0, source_read_checksum = 0, target_read_checksum = 0; + for(unsigned i = 0; i < 4; i++) source_read_checksum |= patch_read() << (i * 8); + for(unsigned i = 0; i < 4; i++) target_read_checksum |= patch_read() << (i * 8); + uint32_t patch_result_checksum = ~patch_checksum; + source_checksum = ~source_checksum; + target_checksum = ~target_checksum; + for(unsigned i = 0; i < 4; i++) patch_read_checksum |= patch_read() << (i * 8); + + if(patch_result_checksum != patch_read_checksum) return result::patch_invalid; + if(source_checksum == source_read_checksum && source_length == source_read_length) { + if(target_checksum == target_read_checksum && target_length == target_read_length) return result::success; + return result::target_invalid; + } else if(source_checksum == target_read_checksum && source_length == target_read_length) { + if(target_checksum == source_read_checksum && target_length == source_read_length) return result::success; + return result::target_invalid; + } else { + return result::source_invalid; + } + } + +private: + uint8_t *patch_data, *source_data, *target_data; + unsigned patch_length, source_length, target_length; + unsigned patch_offset, source_offset, target_offset; + unsigned patch_checksum, source_checksum, target_checksum; + file patch_file; + + uint8_t patch_read() { + if(patch_offset < patch_length) { + uint8_t n = patch_data[patch_offset++]; + patch_checksum = crc32_adjust(patch_checksum, n); + return n; + } + return 0x00; + } + + uint8_t source_read() { + if(source_offset < source_length) { + uint8_t n = source_data[source_offset++]; + source_checksum = crc32_adjust(source_checksum, n); + return n; + } + return 0x00; + } + + uint8_t target_read() { + uint8_t result = 0x00; + if(target_offset < target_length) { + result = target_data[target_offset]; + target_checksum = crc32_adjust(target_checksum, result); + } + if(((target_offset++ & 255) == 0) && progress) { + progress(target_offset, source_length > target_length ? source_length : target_length); + } + return result; + } + + void patch_write(uint8_t n) { + patch_file.write(n); + patch_checksum = crc32_adjust(patch_checksum, n); + } + + void target_write(uint8_t n) { + if(target_offset < target_length) { + target_data[target_offset] = n; + target_checksum = crc32_adjust(target_checksum, n); + } + if(((target_offset++ & 255) == 0) && progress) { + progress(target_offset, source_length > target_length ? source_length : target_length); + } + } + + void encode(uint64_t offset) { + while(true) { + uint64_t x = offset & 0x7f; + offset >>= 7; + if(offset == 0) { + patch_write(0x80 | x); + break; + } + patch_write(x); + offset--; + } + } + + uint64_t decode() { + uint64_t offset = 0, shift = 1; + while(true) { + uint8_t x = patch_read(); + offset += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + offset += shift; + } + return offset; + } +}; + +} + +#endif diff --git a/nall/utf8.hpp b/nall/utf8.hpp new file mode 100755 index 00000000..f5597b85 --- /dev/null +++ b/nall/utf8.hpp @@ -0,0 +1,86 @@ +#ifndef NALL_UTF8_HPP +#define NALL_UTF8_HPP + +//UTF-8 <> UTF-16 conversion +//used only for Win32; Linux, etc use UTF-8 internally + +#if defined(_WIN32) + +#undef UNICODE +#undef _WIN32_WINNT +#undef NOMINMAX +#define UNICODE +#define _WIN32_WINNT 0x0501 +#define NOMINMAX +#include +#undef interface + +namespace nall { + //UTF-8 to UTF-16 + class utf16_t { + public: + operator wchar_t*() { + return buffer; + } + + operator const wchar_t*() const { + return buffer; + } + + utf16_t(const char *s = "") { + if(!s) s = ""; + unsigned length = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0); + buffer = new wchar_t[length + 1](); + MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, length); + } + + ~utf16_t() { + delete[] buffer; + } + + private: + wchar_t *buffer; + }; + + //UTF-16 to UTF-8 + class utf8_t { + public: + operator char*() { + return buffer; + } + + operator const char*() const { + return buffer; + } + + utf8_t(const wchar_t *s = L"") { + if(!s) s = L""; + unsigned length = WideCharToMultiByte(CP_UTF8, 0, s, -1, 0, 0, (const char*)0, (BOOL*)0); + buffer = new char[length + 1](); + WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, (const char*)0, (BOOL*)0); + } + + ~utf8_t() { + delete[] buffer; + } + + utf8_t(const utf8_t&) = delete; + utf8_t& operator=(const utf8_t&) = delete; + + private: + char *buffer; + }; + + inline void utf8_args(int &argc, char **&argv) { + wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + argv = new char*[argc]; + for(unsigned i = 0; i < argc; i++) { + argv[i] = new char[_MAX_PATH]; + strcpy(argv[i], nall::utf8_t(wargv[i])); + } + } +} + +#endif //if defined(_WIN32) + +#endif diff --git a/nall/utility.hpp b/nall/utility.hpp new file mode 100755 index 00000000..60bda562 --- /dev/null +++ b/nall/utility.hpp @@ -0,0 +1,39 @@ +#ifndef NALL_UTILITY_HPP +#define NALL_UTILITY_HPP + +#include +#include + +namespace nall { + template struct enable_if { typedef T type; }; + template struct enable_if {}; + template struct mp_enable_if : enable_if {}; + + template inline void swap(T &x, T &y) { + T temp(std::move(x)); + x = std::move(y); + y = std::move(temp); + } + + template struct base_from_member { + T value; + base_from_member(T value_) : value(value_) {} + }; + + template class optional { + bool valid; + T value; + public: + inline operator bool() const { return valid; } + inline const T& operator()() const { if(!valid) throw; return value; } + inline optional(bool valid, const T &value) : valid(valid), value(value) {} + }; + + template inline T* allocate(unsigned size, const T &value) { + T *array = new T[size]; + for(unsigned i = 0; i < size; i++) array[i] = value; + return array; + } +} + +#endif diff --git a/nall/varint.hpp b/nall/varint.hpp new file mode 100755 index 00000000..fe4732b1 --- /dev/null +++ b/nall/varint.hpp @@ -0,0 +1,92 @@ +#ifndef NALL_VARINT_HPP +#define NALL_VARINT_HPP + +#include +#include +#include + +namespace nall { + template class uint_t { + private: + enum { bytes = (bits + 7) >> 3 }; //minimum number of bytes needed to store value + typedef typename static_if< + sizeof(int) >= bytes, + unsigned int, + typename static_if< + sizeof(long) >= bytes, + unsigned long, + typename static_if< + sizeof(long long) >= bytes, + unsigned long long, + void + >::type + >::type + >::type T; + static_assert(!std::is_same::value, ""); + T data; + + public: + inline operator T() const { return data; } + inline T operator ++(int) { T r = data; data = uclip(data + 1); return r; } + inline T operator --(int) { T r = data; data = uclip(data - 1); return r; } + inline T operator ++() { return data = uclip(data + 1); } + inline T operator --() { return data = uclip(data - 1); } + inline T operator =(const T i) { return data = uclip(i); } + inline T operator |=(const T i) { return data = uclip(data | i); } + inline T operator ^=(const T i) { return data = uclip(data ^ i); } + inline T operator &=(const T i) { return data = uclip(data & i); } + inline T operator<<=(const T i) { return data = uclip(data << i); } + inline T operator>>=(const T i) { return data = uclip(data >> i); } + inline T operator +=(const T i) { return data = uclip(data + i); } + inline T operator -=(const T i) { return data = uclip(data - i); } + inline T operator *=(const T i) { return data = uclip(data * i); } + inline T operator /=(const T i) { return data = uclip(data / i); } + inline T operator %=(const T i) { return data = uclip(data % i); } + + inline uint_t() : data(0) {} + inline uint_t(const T i) : data(uclip(i)) {} + }; + + template class int_t { + private: + enum { bytes = (bits + 7) >> 3 }; //minimum number of bytes needed to store value + typedef typename static_if< + sizeof(int) >= bytes, + signed int, + typename static_if< + sizeof(long) >= bytes, + signed long, + typename static_if< + sizeof(long long) >= bytes, + signed long long, + void + >::type + >::type + >::type T; + static_assert(!std::is_same::value, ""); + T data; + + public: + inline operator T() const { return data; } + inline T operator ++(int) { T r = data; data = sclip(data + 1); return r; } + inline T operator --(int) { T r = data; data = sclip(data - 1); return r; } + inline T operator ++() { return data = sclip(data + 1); } + inline T operator --() { return data = sclip(data - 1); } + inline T operator =(const T i) { return data = sclip(i); } + inline T operator |=(const T i) { return data = sclip(data | i); } + inline T operator ^=(const T i) { return data = sclip(data ^ i); } + inline T operator &=(const T i) { return data = sclip(data & i); } + inline T operator<<=(const T i) { return data = sclip(data << i); } + inline T operator>>=(const T i) { return data = sclip(data >> i); } + inline T operator +=(const T i) { return data = sclip(data + i); } + inline T operator -=(const T i) { return data = sclip(data - i); } + inline T operator *=(const T i) { return data = sclip(data * i); } + inline T operator /=(const T i) { return data = sclip(data / i); } + inline T operator %=(const T i) { return data = sclip(data % i); } + + inline int_t() : data(0) {} + inline int_t(const T i) : data(sclip(i)) {} + }; +} + +#endif diff --git a/nall/vector.hpp b/nall/vector.hpp new file mode 100755 index 00000000..543c7b69 --- /dev/null +++ b/nall/vector.hpp @@ -0,0 +1,281 @@ +#ifndef NALL_VECTOR_HPP +#define NALL_VECTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + //linear_vector + //memory: O(capacity * 2) + // + //linear_vector uses placement new + manual destructor calls to create a + //contiguous block of memory for all objects. accessing individual elements + //is fast, though resizing the array incurs significant overhead. + //reserve() overhead is reduced from quadratic time to amortized constant time + //by resizing twice as much as requested. + // + //if objects hold memory address references to themselves (introspection), a + //valid copy constructor will be needed to keep pointers valid. + + template class linear_vector { + protected: + T *pool; + unsigned poolsize, objectsize; + + public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) { + for(unsigned i = 0; i < objectsize; i++) pool[i].~T(); + free(pool); + } + pool = 0; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned newsize) { + newsize = bit::round(newsize); //round to nearest power of two (for amortized growth) + + T *poolcopy = (T*)malloc(newsize * sizeof(T)); + for(unsigned i = 0; i < min(objectsize, newsize); i++) new(poolcopy + i) T(pool[i]); + for(unsigned i = 0; i < objectsize; i++) pool[i].~T(); + free(pool); + pool = poolcopy; + poolsize = newsize; + objectsize = min(objectsize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(newsize); + + if(newsize < objectsize) { + //vector is shrinking; destroy excess objects + for(unsigned i = newsize; i < objectsize; i++) pool[i].~T(); + } else if(newsize > objectsize) { + //vector is expanding; allocate new objects + for(unsigned i = objectsize; i < newsize; i++) new(pool + i) T; + } + + objectsize = newsize; + } + + void append(const T data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + new(pool + objectsize++) T(data); + } + + template void insert(unsigned index, const U list) { + linear_vector merged; + for(unsigned i = 0; i < index; i++) merged.append(pool[i]); + foreach(item, list) merged.append(item); + for(unsigned i = index; i < objectsize; i++) merged.append(pool[i]); + operator=(merged); + } + + void insert(unsigned index, const T item) { + insert(index, linear_vector{ item }); + } + + void remove(unsigned index, unsigned count = 1) { + for(unsigned i = index; count + i < objectsize; i++) { + pool[i] = pool[count + i]; + } + if(count + index >= objectsize) resize(index); //every element >= index was removed + else resize(objectsize - count); + } + + inline T& operator[](unsigned index) { + if(index >= objectsize) resize(index + 1); + return pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= objectsize) throw "vector[] out of bounds"; + return pool[index]; + } + + //copy + inline linear_vector& operator=(const linear_vector &source) { + reset(); + reserve(source.capacity()); + resize(source.size()); + for(unsigned i = 0; i < source.size(); i++) operator[](i) = source.operator[](i); + return *this; + } + + linear_vector(const linear_vector &source) : pool(0), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline linear_vector& operator=(linear_vector &&source) { + reset(); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = 0; + source.reset(); + return *this; + } + + linear_vector(linear_vector &&source) : pool(0), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + linear_vector() : pool(0), poolsize(0), objectsize(0) { + } + + linear_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) append(*p); + } + + ~linear_vector() { + reset(); + } + }; + + //pointer_vector + //memory: O(1) + // + //pointer_vector keeps an array of pointers to each vector object. this adds + //significant overhead to individual accesses, but allows for optimal memory + //utilization. + // + //by guaranteeing that the base memory address of each objects never changes, + //this avoids the need for an object to have a valid copy constructor. + + template class pointer_vector { + protected: + T **pool; + unsigned poolsize, objectsize; + + public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) { + for(unsigned i = 0; i < objectsize; i++) { if(pool[i]) delete pool[i]; } + free(pool); + } + pool = 0; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned newsize) { + newsize = bit::round(newsize); //round to nearest power of two (for amortized growth) + + for(unsigned i = newsize; i < objectsize; i++) { + if(pool[i]) { delete pool[i]; pool[i] = 0; } + } + + pool = (T**)realloc(pool, newsize * sizeof(T*)); + for(unsigned i = poolsize; i < newsize; i++) pool[i] = 0; + poolsize = newsize; + objectsize = min(objectsize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(newsize); + + for(unsigned i = newsize; i < objectsize; i++) { + if(pool[i]) { delete pool[i]; pool[i] = 0; } + } + + objectsize = newsize; + } + + void append(const T data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + pool[objectsize++] = new T(data); + } + + template void insert(unsigned index, const U list) { + pointer_vector merged; + for(unsigned i = 0; i < index; i++) merged.append(*pool[i]); + foreach(item, list) merged.append(item); + for(unsigned i = index; i < objectsize; i++) merged.append(*pool[i]); + operator=(merged); + } + + void insert(unsigned index, const T item) { + insert(index, pointer_vector{ item }); + } + + void remove(unsigned index, unsigned count = 1) { + for(unsigned i = index; count + i < objectsize; i++) { + *pool[i] = *pool[count + i]; + } + if(count + index >= objectsize) resize(index); //every element >= index was removed + else resize(objectsize - count); + } + + inline T& operator[](unsigned index) { + if(index >= objectsize) resize(index + 1); + if(!pool[index]) pool[index] = new T; + return *pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= objectsize || !pool[index]) throw "vector[] out of bounds"; + return *pool[index]; + } + + //copy + inline pointer_vector& operator=(const pointer_vector &source) { + reset(); + reserve(source.capacity()); + resize(source.size()); + for(unsigned i = 0; i < source.size(); i++) operator[](i) = source.operator[](i); + return *this; + } + + pointer_vector(const pointer_vector &source) : pool(0), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline pointer_vector& operator=(pointer_vector &&source) { + reset(); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = 0; + source.reset(); + return *this; + } + + pointer_vector(pointer_vector &&source) : pool(0), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + pointer_vector() : pool(0), poolsize(0), objectsize(0) { + } + + pointer_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) append(*p); + } + + ~pointer_vector() { + reset(); + } + }; + + template struct has_size> { enum { value = true }; }; + template struct has_size> { enum { value = true }; }; +} + +#endif diff --git a/phoenix/gtk/button.cpp b/phoenix/gtk/button.cpp new file mode 100755 index 00000000..af2a8a61 --- /dev/null +++ b/phoenix/gtk/button.cpp @@ -0,0 +1,13 @@ +static void Button_tick(Button *self) { + if(self->onTick) self->onTick(); +} + +void Button::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_button_new_with_label(text); + widget->parent = &parent; + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "clicked", G_CALLBACK(Button_tick), (gpointer)this); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} diff --git a/phoenix/gtk/canvas.cpp b/phoenix/gtk/canvas.cpp new file mode 100755 index 00000000..43913871 --- /dev/null +++ b/phoenix/gtk/canvas.cpp @@ -0,0 +1,59 @@ +static void Canvas_expose(Canvas *self) { + uint32_t *rgb = self->canvas->bufferRGB; + uint32_t *bgr = self->canvas->bufferBGR; + for(unsigned y = self->object->widget->allocation.height; y; y--) { + for(unsigned x = self->object->widget->allocation.width; x; x--) { + uint32_t pixel = *rgb++; + *bgr++ = ((pixel << 16) & 0xff0000) | (pixel & 0x00ff00) | ((pixel >> 16) & 0x0000ff); + } + } + + gdk_draw_rgb_32_image( + self->object->widget->window, + self->object->widget->style->fg_gc[GTK_WIDGET_STATE(self->object->widget)], + 0, 0, self->object->widget->allocation.width, self->object->widget->allocation.height, + GDK_RGB_DITHER_NONE, (guchar*)self->canvas->bufferBGR, self->canvas->pitch + ); +} + +void Canvas::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + canvas->bufferRGB = new uint32_t[width * height](); + canvas->bufferBGR = new uint32_t[width * height](); + canvas->pitch = width * sizeof(uint32_t); + + object->widget = gtk_drawing_area_new(); + widget->parent = &parent; + GdkColor color; + color.pixel = color.red = color.green = color.blue = 0; + gtk_widget_modify_bg(object->widget, GTK_STATE_NORMAL, &color); + gtk_widget_set_double_buffered(object->widget, false); + gtk_widget_add_events(object->widget, GDK_EXPOSURE_MASK); + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "expose_event", G_CALLBACK(Canvas_expose), (gpointer)this); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +uint32_t* Canvas::buffer() { + return canvas->bufferRGB; +} + +void Canvas::redraw() { + GdkRectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = object->widget->allocation.width; + rect.height = object->widget->allocation.height; + gdk_window_invalidate_rect(object->widget->window, &rect, true); +} + +Canvas::Canvas() { + canvas = new Canvas::Data; + canvas->bufferRGB = 0; + canvas->bufferBGR = 0; +} + +Canvas::~Canvas() { + if(canvas->bufferRGB) delete[] canvas->bufferRGB; + if(canvas->bufferBGR) delete[] canvas->bufferBGR; +} diff --git a/phoenix/gtk/checkbox.cpp b/phoenix/gtk/checkbox.cpp new file mode 100755 index 00000000..f569775c --- /dev/null +++ b/phoenix/gtk/checkbox.cpp @@ -0,0 +1,23 @@ +static void CheckBox_tick(CheckBox *self) { + if(self->onTick && self->object->locked == false) self->onTick(); +} + +void CheckBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_check_button_new_with_label(text); + widget->parent = &parent; + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(CheckBox_tick), (gpointer)this); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +bool CheckBox::checked() { + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(object->widget)); +} + +void CheckBox::setChecked(bool checked) { + object->locked = true; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object->widget), checked); + object->locked = false; +} diff --git a/phoenix/gtk/combobox.cpp b/phoenix/gtk/combobox.cpp new file mode 100755 index 00000000..dbabf247 --- /dev/null +++ b/phoenix/gtk/combobox.cpp @@ -0,0 +1,48 @@ +void ComboBox_change(ComboBox *self) { + if(self->object->locked == false && self->onChange) self->onChange(); +} + +void ComboBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_combo_box_new_text(); + widget->parent = &parent; + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "changed", G_CALLBACK(ComboBox_change), (gpointer)this); + + if(*text) { + lstring list; + list.split("\n", text); + foreach(item, list) addItem(item); + } + + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +void ComboBox::reset() { + object->locked = true; + for(signed i = counter - 1; i >= 0; i--) { + gtk_combo_box_remove_text(GTK_COMBO_BOX(object->widget), i); + } + object->locked = false; + counter = 0; +} + +void ComboBox::addItem(const string &text) { + gtk_combo_box_append_text(GTK_COMBO_BOX(object->widget), text); + if(counter++ == 0) setSelection(0); +} + +unsigned ComboBox::selection() { + return gtk_combo_box_get_active(GTK_COMBO_BOX(object->widget)); +} + +void ComboBox::setSelection(unsigned item) { + object->locked = true; + gtk_combo_box_set_active(GTK_COMBO_BOX(object->widget), item); + object->locked = false; +} + +ComboBox::ComboBox() { + counter = 0; +} diff --git a/phoenix/gtk/editbox.cpp b/phoenix/gtk/editbox.cpp new file mode 100755 index 00000000..90cddfe9 --- /dev/null +++ b/phoenix/gtk/editbox.cpp @@ -0,0 +1,58 @@ +static void EditBox_change(EditBox *self) { + if(self->object->locked == false && self->onChange) self->onChange(); +} + +void EditBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_scrolled_window_new(0, 0); + widget->parent = &parent; + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(object->widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(object->widget), GTK_SHADOW_ETCHED_IN); + gtk_widget_set_size_request(object->widget, width, height); + object->subWidget = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(object->subWidget), GTK_WRAP_WORD_CHAR); + gtk_container_add(GTK_CONTAINER(object->widget), object->subWidget); + object->textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(object->subWidget)); + gtk_text_buffer_set_text(object->textBuffer, text, -1); + g_signal_connect_swapped(G_OBJECT(object->textBuffer), "changed", G_CALLBACK(EditBox_change), (gpointer)this); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->subWidget); + gtk_widget_show(object->widget); +} + +void EditBox::setFocused() { + gtk_widget_grab_focus(object->subWidget); +} + +void EditBox::setEditable(bool editable) { + gtk_text_view_set_editable(GTK_TEXT_VIEW(object->subWidget), editable); +} + +void EditBox::setWordWrap(bool wordWrap) { + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(object->subWidget), wordWrap ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE); +} + +string EditBox::text() { + GtkTextIter start, end; + gtk_text_buffer_get_start_iter(object->textBuffer, &start); + gtk_text_buffer_get_end_iter(object->textBuffer, &end); + char *temp = gtk_text_buffer_get_text(object->textBuffer, &start, &end, true); + string text = temp; + g_free(temp); + return text; +} + +void EditBox::setText(const string &text) { + object->locked = true; + gtk_text_buffer_set_text(object->textBuffer, text, -1); + object->locked = false; +} + +void EditBox::setCursorPosition(unsigned position) { + GtkTextMark *mark = gtk_text_buffer_get_mark(object->textBuffer, "insert"); + GtkTextIter iter; + gtk_text_buffer_get_end_iter(object->textBuffer, &iter); + gtk_text_iter_set_offset(&iter, min(position, gtk_text_iter_get_offset(&iter))); + gtk_text_buffer_place_cursor(object->textBuffer, &iter); + gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(object->subWidget), mark); +} diff --git a/phoenix/gtk/font.cpp b/phoenix/gtk/font.cpp new file mode 100755 index 00000000..459151e0 --- /dev/null +++ b/phoenix/gtk/font.cpp @@ -0,0 +1,18 @@ +bool Font::create(const string &name, unsigned size, Font::Style style) { + font->font = pango_font_description_new(); + pango_font_description_set_family(font->font, name); + pango_font_description_set_size(font->font, size * PANGO_SCALE); + pango_font_description_set_style(font->font, (style & Style::Italic) == Style::Italic ? PANGO_STYLE_OBLIQUE : PANGO_STYLE_NORMAL); + pango_font_description_set_weight(font->font, (style & Style::Bold) == Style::Bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL); + return true; +} + +Font::Font() { + font = new Font::Data; + font->font = 0; +} + +Font::~Font() { + if(font->font) pango_font_description_free(font->font); + delete font; +} diff --git a/phoenix/gtk/gtk.cpp b/phoenix/gtk/gtk.cpp new file mode 100755 index 00000000..3c7cbb60 --- /dev/null +++ b/phoenix/gtk/gtk.cpp @@ -0,0 +1,190 @@ +#include +#include +#include + +#define None X11None +#define Window X11Window + +#include +#include +#include +#include + +#undef None +#undef Window + +using namespace nall; + +namespace phoenix { + +#include "object.cpp" +#include "font.cpp" +#include "menu.cpp" +#include "widget.cpp" +#include "window.cpp" +#include "button.cpp" +#include "canvas.cpp" +#include "checkbox.cpp" +#include "combobox.cpp" +#include "editbox.cpp" +#include "hexeditor.cpp" +#include "horizontalslider.cpp" +#include "label.cpp" +#include "listbox.cpp" +#include "progressbar.cpp" +#include "radiobox.cpp" +#include "textbox.cpp" +#include "verticalslider.cpp" +#include "viewport.cpp" +#include "messagewindow.cpp" + +Window Window::None; + +void OS::initialize() { + static bool initialized = false; + if(initialized == true) return; + initialized = true; + + int argc = 1; + char *argv[2]; + argv[0] = new char[8]; + argv[1] = 0; + strcpy(argv[0], "phoenix"); + char **argvp = argv; + gtk_init(&argc, &argvp); + + gtk_rc_parse_string( + "style \"phoenix-gtk\"\n" + "{\n" + " GtkComboBox::appears-as-list = 1\n" + " GtkTreeView::vertical-separator = 0\n" + "}\n" + "class \"GtkComboBox\" style \"phoenix-gtk\"\n" + "class \"GtkTreeView\" style \"phoenix-gtk\"\n" + ); +} + +bool OS::pending() { + return gtk_events_pending(); +} + +void OS::run() { + while(pending()) gtk_main_iteration_do(false); +} + +void OS::main() { + gtk_main(); +} + +void OS::quit() { + gtk_main_quit(); +} + +unsigned OS::desktopWidth() { + return gdk_screen_get_width(gdk_screen_get_default()); +} + +unsigned OS::desktopHeight() { + return gdk_screen_get_height(gdk_screen_get_default()); +} + +string OS::folderSelect(Window &parent, const string &path) { + string name; + + GtkWidget *dialog = gtk_file_chooser_dialog_new( + "Select Folder", + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + (const gchar*)0 + ); + + if(path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path); + + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + name = temp; + g_free(temp); + } + + gtk_widget_destroy(dialog); + if(name.endswith("/") == false) name.append("/"); + return name; +} + +string OS::fileOpen(Window &parent, const string &filter, const string &path) { + string name; + + GtkWidget *dialog = gtk_file_chooser_dialog_new( + "Open File", + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + (const gchar*)0 + ); + + if(path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path); + + lstring list; + list.split("\n", filter); + foreach(item, list) { + lstring part; + part.split("\t", item); + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, string(part[0], " (", part[1], ")")); + lstring patterns; + patterns.split(",", part[1]); + foreach(pattern, patterns) gtk_file_filter_add_pattern(filter, pattern); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); + } + + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + name = temp; + g_free(temp); + } + + gtk_widget_destroy(dialog); + return name; +} + +string OS::fileSave(Window &parent, const string &filter, const string &path) { + string name; + + GtkWidget *dialog = gtk_file_chooser_dialog_new( + "Save File", + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + (const gchar*)0 + ); + + if(path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path); + + lstring list; + list.split("\n", filter); + foreach(item, list) { + lstring part; + part.split("\t", item); + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, string(part[0], " (", part[1], ")")); + lstring patterns; + patterns.split(",", part[1]); + foreach(pattern, patterns) gtk_file_filter_add_pattern(filter, pattern); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); + } + + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + name = temp; + g_free(temp); + } + + gtk_widget_destroy(dialog); + return name; +} + +} diff --git a/phoenix/gtk/gtk.hpp b/phoenix/gtk/gtk.hpp new file mode 100755 index 00000000..5b14e34c --- /dev/null +++ b/phoenix/gtk/gtk.hpp @@ -0,0 +1,287 @@ +namespace phoenix { + +struct Window; + +struct Object { + Object(); + Object& operator=(const Object&) = delete; + Object(const Object&) = delete; +//private: + virtual void unused(); + struct Data; + Data *object; +}; + +struct Geometry { + unsigned x, y; + unsigned width, height; + inline Geometry() : x(0), y(0), width(0), height(0) {} + inline Geometry(unsigned x, unsigned y, unsigned width, unsigned height) : x(x), y(y), width(width), height(height) {} +}; + +struct Font : Object { + enum class Style : unsigned { + None = 0, + Bold = 1, + Italic = 2, + }; + bool create(const nall::string &name, unsigned size, Font::Style style = Style::None); + Font(); + ~Font(); +//private: + struct Data; + Data *font; +}; + +inline Font::Style operator|(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a | (unsigned)b); } +inline Font::Style operator&(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a & (unsigned)b); } + +struct Action : Object { + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + Action(); +//private: + struct Data; + Data *action; +}; + +struct Menu : Action { + void create(Window &parent, const nall::string &text); + void create(Menu &parent, const nall::string &text); +}; + +struct MenuSeparator : Action { + void create(Menu &parent); +}; + +struct MenuItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); +}; + +struct MenuCheckItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + bool checked(); + void setChecked(bool checked = true); +}; + +struct MenuRadioItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + void create(MenuRadioItem &parent, const nall::string &text); + bool checked(); + void setChecked(); +private: + MenuRadioItem *first; +}; + +struct Widget : Object { + virtual void setFont(Font &font); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + virtual bool focused(); + virtual void setFocused(); + virtual void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + Widget(); +//private: + struct Data; + Data *widget; +}; + +struct Window : Widget { + nall::function onClose; + void create(unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool focused(); + void setFocused(); + Geometry geometry(); + void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + void setDefaultFont(Font &font); + void setFont(Font &font); + void setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue); + void setTitle(const nall::string &text); + void setStatusText(const nall::string &text); + void setMenuVisible(bool visible = true); + void setStatusVisible(bool visible = true); + Window(); +//private: + struct Data; + Data *window; + static Window None; +}; + +struct Button : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); +}; + +struct Canvas : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + uint32_t* buffer(); + void redraw(); + Canvas(); + ~Canvas(); +//private: + struct Data; + Data *canvas; +}; + +struct CheckBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(bool checked = true); +}; + +struct ComboBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void reset(); + void addItem(const nall::string &text); + unsigned selection(); + void setSelection(unsigned item); + ComboBox(); +private: + unsigned counter; +}; + +struct EditBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setFocused(); + void setEditable(bool editable = true); + void setWordWrap(bool wordWrap = true); + nall::string text(); + void setText(const nall::string &text); + void setCursorPosition(unsigned position); +}; + +struct HexEditor : Widget { + nall::function onRead; + nall::function onWrite; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + void setSize(unsigned size); + void setOffset(unsigned offset); + void setColumns(unsigned columns); + void setRows(unsigned rows); + void update(); + HexEditor(); +//private: + struct Data; + Data *hexEditor; + bool keyPress(unsigned scancode); + void scroll(unsigned position); + void setScroll(); + void updateScroll(); + unsigned cursorPosition(); + void setCursorPosition(unsigned position); +}; + +struct HorizontalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); +}; + +struct Label : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setText(const nall::string &text); +}; + +struct ListBox : Widget { + nall::function onActivate; + nall::function onChange; + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setFocused(); + void setHeaderVisible(bool headerVisible = true); + void setCheckable(bool checkable = true); + void setFont(Font &font); + void reset(); + void resizeColumnsToContent(); + void addItem(const nall::string &text); + void setItem(unsigned row, const nall::string &text); + bool checked(unsigned row); + void setChecked(unsigned row, bool checked = true); + nall::optional selection(); + void setSelection(unsigned row); + ListBox(); +//private: + struct Data; + Data *listBox; +}; + +struct ProgressBar : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + void setPosition(unsigned position); +}; + +struct RadioBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(); +private: + RadioBox *first; +}; + +struct TextBox : Widget { + nall::function onActivate; + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setEditable(bool editable = true); + nall::string text(); + void setText(const nall::string &text); +}; + +struct VerticalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); +}; + +struct Viewport : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + uintptr_t handle(); +}; + +struct MessageWindow : Object { + enum class Buttons : unsigned { + Ok, + OkCancel, + YesNo, + }; + enum class Response : unsigned { + Ok, + Cancel, + Yes, + No, + }; + static Response information(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response question(Window &parent, const nall::string &text, Buttons = Buttons::YesNo); + static Response warning(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response critical(Window &parent, const nall::string &text, Buttons = Buttons::Ok); +}; + +struct OS : Object { + static bool pending(); + static void run(); + static void main(); + static void quit(); + static unsigned desktopWidth(); + static unsigned desktopHeight(); + static nall::string folderSelect(Window &parent, const nall::string &path = ""); + static nall::string fileOpen(Window &parent, const nall::string &filter, const nall::string &path = ""); + static nall::string fileSave(Window &parent, const nall::string &filter, const nall::string &path = ""); +//private: + static void initialize(); +}; + +} diff --git a/phoenix/gtk/hexeditor.cpp b/phoenix/gtk/hexeditor.cpp new file mode 100755 index 00000000..3da8f3e1 --- /dev/null +++ b/phoenix/gtk/hexeditor.cpp @@ -0,0 +1,266 @@ +static bool HexEditor_keyPress(GtkWidget *widget, GdkEventKey *event, HexEditor *self) { + return self->keyPress(event->keyval); +} + +static bool HexEditor_scroll(GtkRange *range, GtkScrollType scroll, gdouble value, HexEditor *self) { + self->scroll((unsigned)value); + return false; +} + +void HexEditor::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + widget->parent = &parent; + + hexEditor->size = 0; + hexEditor->offset = 0; + hexEditor->columns = 16; + hexEditor->rows = 16; + + object->widget = gtk_hbox_new(false, 0); + gtk_widget_set_size_request(object->widget, width, height); + + hexEditor->container = gtk_scrolled_window_new(0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(hexEditor->container), GTK_POLICY_NEVER, GTK_POLICY_NEVER); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(hexEditor->container), GTK_SHADOW_ETCHED_IN); + + hexEditor->widget = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(hexEditor->widget), GTK_WRAP_NONE); + gtk_container_add(GTK_CONTAINER(hexEditor->container), hexEditor->widget); + g_signal_connect(G_OBJECT(hexEditor->widget), "key-press-event", G_CALLBACK(HexEditor_keyPress), (gpointer)this); + + hexEditor->scroll = gtk_vscrollbar_new((GtkAdjustment*)0); + gtk_range_set_range(GTK_RANGE(hexEditor->scroll), 0, 256); + gtk_range_set_increments(GTK_RANGE(hexEditor->scroll), 1, 16); + gtk_widget_set_sensitive(hexEditor->scroll, false); + g_signal_connect(G_OBJECT(hexEditor->scroll), "change-value", G_CALLBACK(HexEditor_scroll), (gpointer)this); + + gtk_box_pack_start(GTK_BOX(object->widget), hexEditor->container, true, true, 0); + gtk_box_pack_start(GTK_BOX(object->widget), hexEditor->scroll, false, false, 1); + + object->textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(hexEditor->widget)); + hexEditor->cursor = gtk_text_buffer_get_mark(object->textBuffer, "insert"); + + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + + gtk_widget_show(hexEditor->scroll); + gtk_widget_show(hexEditor->widget); + gtk_widget_show(hexEditor->container); + gtk_widget_show(object->widget); +} + +void HexEditor::setSize(unsigned size) { + hexEditor->size = size; + setScroll(); +} + +void HexEditor::setOffset(unsigned offset) { + hexEditor->offset = offset; + setScroll(); + updateScroll(); +} + +void HexEditor::setColumns(unsigned columns) { + hexEditor->columns = columns; + setScroll(); +} + +void HexEditor::setRows(unsigned rows) { + hexEditor->rows = rows; + setScroll(); +} + +void HexEditor::update() { + if(!onRead) { + gtk_text_buffer_set_text(object->textBuffer, "", -1); + return; + } + + unsigned position = cursorPosition(); + + string output; + unsigned offset = hexEditor->offset; + for(unsigned row = 0; row < hexEditor->rows; row++) { + output.append(hex<8>(offset)); + output.append(" "); + + string hexdata; + string ansidata = " "; + for(unsigned column = 0; column < hexEditor->columns; column++) { + if(offset < hexEditor->size) { + uint8_t data = onRead(offset++); + hexdata.append(hex<2>(data)); + hexdata.append(" "); + char buffer[2] = { data >= 0x20 && data <= 0x7e ? (char)data : '.', 0 }; + ansidata.append(buffer); + } else { + hexdata.append(" "); + ansidata.append(" "); + } + } + + output.append(hexdata); + output.append(ansidata); + if(offset >= hexEditor->size) break; + if(row != hexEditor->rows - 1) output.append("\n"); + } + + gtk_text_buffer_set_text(object->textBuffer, output, -1); + if(position == 0) position = 10; //start at first position where hex values can be entered + setCursorPosition(position); +} + +HexEditor::HexEditor() { + hexEditor = new HexEditor::Data; +} + +//internal + +bool HexEditor::keyPress(unsigned scancode) { + if(!onRead && !onWrite) return false; + + unsigned position = cursorPosition(); + unsigned lineWidth = 10 + (hexEditor->columns * 3) + 1 + (hexEditor->columns) + 1; + unsigned cursorY = position / lineWidth; + unsigned cursorX = position % lineWidth; + + if(scancode == GDK_Home) { + setCursorPosition(cursorY * lineWidth + 10); + return true; + } + + if(scancode == GDK_End) { + setCursorPosition(cursorY * lineWidth + 10 + (hexEditor->columns * 3 - 1)); + return true; + } + + if(scancode == GDK_Up) { + if(cursorY != 0) return false; + + signed newOffset = hexEditor->offset - hexEditor->columns; + if(newOffset >= 0) { + setOffset(newOffset); + update(); + } + return true; + } + + if(scancode == GDK_Down) { + if(cursorY != hexEditor->rows - 1) return false; + + signed newOffset = hexEditor->offset + hexEditor->columns; + if(newOffset + hexEditor->columns * hexEditor->rows - (hexEditor->columns - 1) <= hexEditor->size) { + setOffset(newOffset); + update(); + } + return true; + } + + if(scancode == GDK_Page_Up) { + signed newOffset = hexEditor->offset - hexEditor->columns * hexEditor->rows; + if(newOffset >= 0) { + setOffset(newOffset); + update(); + } else { + setOffset(0); + update(); + } + return true; + } + + if(scancode == GDK_Page_Down) { + signed newOffset = hexEditor->offset + hexEditor->columns * hexEditor->rows; + for(unsigned n = 0; n < hexEditor->rows; n++) { + if(newOffset + hexEditor->columns * hexEditor->rows - (hexEditor->columns - 1) <= hexEditor->size) { + setOffset(newOffset); + update(); + break; + } + newOffset -= hexEditor->columns; + } + return true; + } + + //convert scancode to hex nibble + if(scancode >= '0' && scancode <= '9') scancode = scancode - '0'; + else if(scancode >= 'A' && scancode <= 'F') scancode = scancode - 'A' + 10; + else if(scancode >= 'a' && scancode <= 'f') scancode = scancode - 'a' + 10; + else return false; //not a valid hex value + + if(cursorX >= 10) { + //not on an offset + cursorX -= 10; + if((cursorX % 3) != 2) { + //not on a space + bool cursorNibble = (cursorX % 3) == 1; //0 = high, 1 = low + cursorX /= 3; + if(cursorX < hexEditor->columns) { + //not in ANSI region + unsigned offset = hexEditor->offset + (cursorY * hexEditor->columns + cursorX); + + if(offset >= hexEditor->size) return false; //do not edit past end of file + uint8_t data = onRead(offset); + + //write modified value + if(cursorNibble == 1) { + data = (data & 0xf0) | (scancode << 0); + } else { + data = (data & 0x0f) | (scancode << 4); + } + onWrite(offset, data); + + //auto-advance cursor to next nibble/byte + position++; + if(cursorNibble && cursorX != hexEditor->columns - 1) position++; + setCursorPosition(position); + + //refresh output to reflect modified data + update(); + } + } + } + + return true; +} + +void HexEditor::scroll(unsigned position) { + unsigned rows = hexEditor->size / hexEditor->columns; + if(position >= rows) position = rows - 1; + setOffset(position * hexEditor->columns); + update(); +} + +void HexEditor::setScroll() { + unsigned rows = hexEditor->size / hexEditor->columns; + if(rows) rows--; + if(rows) { + gtk_range_set_range(GTK_RANGE(hexEditor->scroll), 0, rows); + gtk_widget_set_sensitive(hexEditor->scroll, true); + } else { + gtk_widget_set_sensitive(hexEditor->scroll, false); + } +} + +void HexEditor::updateScroll() { + unsigned row = hexEditor->offset / hexEditor->columns; + gtk_range_set_value(GTK_RANGE(hexEditor->scroll), row); +} + +unsigned HexEditor::cursorPosition() { + GtkTextIter iter; + gtk_text_buffer_get_iter_at_mark(object->textBuffer, &iter, hexEditor->cursor); + return gtk_text_iter_get_offset(&iter); +} + +void HexEditor::setCursorPosition(unsigned position) { + GtkTextIter iter; + gtk_text_buffer_get_iter_at_mark(object->textBuffer, &iter, hexEditor->cursor); + + //GTK+ will throw a hundred errors on the terminal + //if you set an iterator past the end of the text buffer + GtkTextIter endIter; + gtk_text_buffer_get_end_iter(object->textBuffer, &iter); + unsigned endPosition = gtk_text_iter_get_offset(&iter); + + gtk_text_iter_set_offset(&iter, min(position, endPosition)); + gtk_text_buffer_place_cursor(object->textBuffer, &iter); +} diff --git a/phoenix/gtk/horizontalslider.cpp b/phoenix/gtk/horizontalslider.cpp new file mode 100755 index 00000000..9e6e76c7 --- /dev/null +++ b/phoenix/gtk/horizontalslider.cpp @@ -0,0 +1,25 @@ +static void HorizontalSlider_change(HorizontalSlider *self) { + if(self->object->position == self->position()) return; + self->object->position = self->position(); + if(self->onChange) self->onChange(); +} + +void HorizontalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + object->position = 0; + length += (length == 0); + object->widget = gtk_hscale_new_with_range(0, length - 1, 1); + widget->parent = &parent; + gtk_scale_set_draw_value(GTK_SCALE(object->widget), false); + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "value-changed", G_CALLBACK(HorizontalSlider_change), (gpointer)this); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +unsigned HorizontalSlider::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(object->widget)); +} + +void HorizontalSlider::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(object->widget), position); +} diff --git a/phoenix/gtk/label.cpp b/phoenix/gtk/label.cpp new file mode 100755 index 00000000..8321523f --- /dev/null +++ b/phoenix/gtk/label.cpp @@ -0,0 +1,13 @@ +void Label::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_label_new(text); + widget->parent = &parent; + gtk_misc_set_alignment(GTK_MISC(object->widget), 0.0, 0.5); + gtk_widget_set_size_request(object->widget, width, height); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +void Label::setText(const string &text) { + gtk_label_set_text(GTK_LABEL(object->widget), text); +} diff --git a/phoenix/gtk/listbox.cpp b/phoenix/gtk/listbox.cpp new file mode 100755 index 00000000..3d390338 --- /dev/null +++ b/phoenix/gtk/listbox.cpp @@ -0,0 +1,195 @@ +static void ListBox_activate(ListBox *self) { + signed selection = -1; + if(auto position = self->selection()) selection = position(); + self->listBox->selection = selection; + if(self->onActivate) self->onActivate(); +} + +static void ListBox_change(ListBox *self) { + signed selection = -1; + if(auto position = self->selection()) selection = position(); + if(selection == self->listBox->selection) return; + self->listBox->selection = selection; + if(self->onChange) self->onChange(); +} + +static void ListBox_tick(GtkCellRendererToggle *cell, gchar *path_string, ListBox *self) { + unsigned index = decimal(path_string); + self->setChecked(index, !self->checked(index)); + if(self->onTick) self->onTick(index); +} + +void ListBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + listBox->selection = -1; + object->widget = gtk_scrolled_window_new(0, 0); + widget->parent = &parent; + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(object->widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(object->widget), GTK_SHADOW_ETCHED_IN); + gtk_widget_set_size_request(object->widget, width, height); + + lstring list; + list.split("\t", string("\t", text)); + + GType *v = (GType*)malloc(list.size() * sizeof(GType)); + for(unsigned i = 0; i < list.size(); i++) v[i] = (i == 0 ? G_TYPE_BOOLEAN : G_TYPE_STRING); + listBox->store = gtk_list_store_newv(list.size(), v); + free(v); + + object->subWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(listBox->store)); + gtk_container_add(GTK_CONTAINER(object->widget), object->subWidget); + g_object_unref(G_OBJECT(listBox->store)); + + for(unsigned i = 0; i < list.size(); i++) { + if(i == 0) { + listBox->column[i].renderer = gtk_cell_renderer_toggle_new(); + listBox->column[i].column = gtk_tree_view_column_new_with_attributes( + "", listBox->column[i].renderer, "active", i, (void*)0 + ); + gtk_tree_view_column_set_resizable(listBox->column[i].column, false); + gtk_tree_view_column_set_visible(listBox->column[i].column, listBox->checkable); + g_signal_connect(listBox->column[i].renderer, "toggled", G_CALLBACK(ListBox_tick), (gpointer)this); + } else { + listBox->column[i].renderer = gtk_cell_renderer_text_new(); + listBox->column[i].column = gtk_tree_view_column_new_with_attributes( + "", listBox->column[i].renderer, "text", i, (void*)0 + ); + gtk_tree_view_column_set_resizable(listBox->column[i].column, true); + } + listBox->column[i].label = gtk_label_new(list[i]); + gtk_tree_view_column_set_widget(GTK_TREE_VIEW_COLUMN(listBox->column[i].column), listBox->column[i].label); + gtk_tree_view_append_column(GTK_TREE_VIEW(object->subWidget), listBox->column[i].column); + gtk_widget_show(listBox->column[i].label); + } + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(object->subWidget), false); + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(object->subWidget), list.size() >= 3); //>= 2 + one for the checkbox column + gtk_tree_view_set_search_column(GTK_TREE_VIEW(object->subWidget), 1); + + g_signal_connect_swapped(G_OBJECT(object->subWidget), "cursor-changed", G_CALLBACK(ListBox_change), (gpointer)this); + g_signal_connect_swapped(G_OBJECT(object->subWidget), "row-activated", G_CALLBACK(ListBox_activate), (gpointer)this); + + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->subWidget); + gtk_widget_show(object->widget); +} + +void ListBox::setFocused() { + gtk_widget_grab_focus(object->subWidget); +} + +void ListBox::setHeaderVisible(bool visible) { + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(object->subWidget), visible); +} + +void ListBox::setCheckable(bool checkable) { + listBox->checkable = checkable; + if(object->subWidget) gtk_tree_view_column_set_visible(listBox->column[0].column, checkable); +} + +void ListBox::setFont(Font &font) { + Widget::setFont(font); + unsigned columns = 1; + while(true) { + if(gtk_tree_view_get_column(GTK_TREE_VIEW(object->subWidget), columns) == 0) break; + columns++; + } + for(unsigned i = 0; i < columns; i++) { + gtk_widget_modify_font(listBox->column[i].label, font.font->font); + } +} + +void ListBox::reset() { + listBox->selection = -1; + gtk_list_store_clear(GTK_LIST_STORE(listBox->store)); + gtk_tree_view_set_model(GTK_TREE_VIEW(object->subWidget), GTK_TREE_MODEL(listBox->store)); + //reset gtk_scrolled_window scrollbar position to 0,0 (top-left), as ListBox is now empty + gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(object->widget), 0); + gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(object->widget), 0); +} + +void ListBox::resizeColumnsToContent() { + gtk_tree_view_columns_autosize(GTK_TREE_VIEW(object->subWidget)); +} + +void ListBox::addItem(const string &text) { + lstring list; + list.split("\t", text); + GtkTreeIter iter; + gtk_list_store_append(listBox->store, &iter); + unsigned index = 1; + foreach(item, list) gtk_list_store_set(listBox->store, &iter, index++, (const char*)item, -1); +} + +void ListBox::setItem(unsigned row, const string &text) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(object->subWidget)); + GtkTreeIter iter; + for(unsigned i = 0; i <= row; i++) { + if(i == 0) gtk_tree_model_get_iter_first(model, &iter); + else gtk_tree_model_iter_next(model, &iter); + } + + lstring list; + list.split("\t", text); + unsigned index = 1; + foreach(item, list) gtk_list_store_set(listBox->store, &iter, index++, (const char*)item, -1); +} + +bool ListBox::checked(unsigned row) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(object->subWidget)); + GtkTreePath *path = gtk_tree_path_new_from_string(string(row)); + GtkTreeIter iter; + bool state; + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_model_get(model, &iter, 0, &state, -1); + gtk_tree_path_free(path); + return state; +} + +void ListBox::setChecked(unsigned row, bool checked) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(object->subWidget)); + GtkTreePath *path = gtk_tree_path_new_from_string(string(row)); + GtkTreeIter iter; + gtk_tree_model_get_iter(model, &iter, path); + gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, checked, -1); + gtk_tree_path_free(path); +} + +optional ListBox::selection() { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(object->subWidget)); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(object->subWidget)); + GtkTreeIter iter; + if(gtk_tree_model_get_iter_first(model, &iter) == false) return { false, 0 }; + if(gtk_tree_selection_iter_is_selected(selection, &iter) == true) return { true, 0 }; + for(unsigned i = 1;; i++) { + if(gtk_tree_model_iter_next(model, &iter) == false) return { false, 0 }; + if(gtk_tree_selection_iter_is_selected(selection, &iter) == true) return { true, i }; + } + return { false, 0 }; +} + +void ListBox::setSelection(unsigned row) { + signed current = -1; + if(auto position = selection()) current = position(); + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(object->subWidget)); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(object->subWidget)); + gtk_tree_selection_unselect_all(selection); + + GtkTreeIter iter; + if(gtk_tree_model_get_iter_first(model, &iter) == false) return; + if(row == 0) { + gtk_tree_selection_select_iter(selection, &iter); + return; + } + for(unsigned i = 1;; i++) { + if(gtk_tree_model_iter_next(model, &iter) == false) return; + if(row == i) { + gtk_tree_selection_select_iter(selection, &iter); + return; + } + } +} + +ListBox::ListBox() { + listBox = new ListBox::Data; + listBox->checkable = false; +} diff --git a/phoenix/gtk/menu.cpp b/phoenix/gtk/menu.cpp new file mode 100755 index 00000000..fbfc67bc --- /dev/null +++ b/phoenix/gtk/menu.cpp @@ -0,0 +1,129 @@ +static void Action_setFont(GtkWidget *widget, gpointer font) { + if(font) { + gtk_widget_modify_font(widget, (PangoFontDescription*)font); + if(GTK_IS_CONTAINER(widget)) { + gtk_container_foreach(GTK_CONTAINER(widget), (GtkCallback)Action_setFont, (PangoFontDescription*)font); + } + } +} + +bool Action::visible() { + return gtk_widget_get_visible(object->widget); +} + +void Action::setVisible(bool visible) { + gtk_widget_set_visible(object->widget, visible); +} + +bool Action::enabled() { + return gtk_widget_get_sensitive(object->widget); +} + +void Action::setEnabled(bool enabled) { + gtk_widget_set_sensitive(object->widget, enabled); +} + +Action::Action() { + action = new Action::Data; + action->font = 0; +} + +void Menu::create(Window &parent, const string &text) { + action->font = parent.window->defaultFont; + object->menu = gtk_menu_new(); + object->widget = gtk_menu_item_new_with_label(text); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(object->widget), object->menu); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_bar_append(parent.object->menu, object->widget); + gtk_widget_show(object->widget); +} + +void Menu::create(Menu &parent, const string &text) { + action->font = parent.action->font; + object->menu = gtk_menu_new(); + object->widget = gtk_menu_item_new_with_label(text); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(object->widget), object->menu); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(parent.object->menu), object->widget); + gtk_widget_show(object->widget); +} + +void MenuSeparator::create(Menu &parent) { + action->font = parent.action->font; + object->widget = gtk_separator_menu_item_new(); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(parent.object->menu), object->widget); + gtk_widget_show(object->widget); +} + +static void MenuItem_tick(MenuItem *self) { + if(self->onTick) self->onTick(); +} + +void MenuItem::create(Menu &parent, const string &text) { + action->font = parent.action->font; + object->widget = gtk_menu_item_new_with_label(text); + g_signal_connect_swapped(G_OBJECT(object->widget), "activate", G_CALLBACK(MenuItem_tick), (gpointer)this); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(parent.object->menu), object->widget); + gtk_widget_show(object->widget); +} + +static void MenuCheckItem_tick(MenuCheckItem *self) { + if(self->onTick && self->object->locked == false) self->onTick(); +} + +void MenuCheckItem::create(Menu &parent, const string &text) { + action->font = parent.action->font; + object->widget = gtk_check_menu_item_new_with_label(text); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(MenuCheckItem_tick), (gpointer)this); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(parent.object->menu), object->widget); + gtk_widget_show(object->widget); +} + +bool MenuCheckItem::checked() { + return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(object->widget)); +} + +void MenuCheckItem::setChecked(bool state) { + object->locked = true; + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(object->widget), state); + object->locked = false; +} + +static void MenuRadioItem_tick(MenuRadioItem *self) { + if(self->onTick && self->checked() && self->object->locked == false) self->onTick(); +} + +void MenuRadioItem::create(Menu &parent, const string &text) { + first = this; + action->font = parent.action->font; + object->parentMenu = &parent; + object->widget = gtk_radio_menu_item_new_with_label(0, text); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(MenuRadioItem_tick), (gpointer)this); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(parent.object->menu), object->widget); + gtk_widget_show(object->widget); +} + +void MenuRadioItem::create(MenuRadioItem &parent, const string &text) { + first = parent.first; + action->font = parent.action->font; + object->parentMenu = parent.object->parentMenu; + object->widget = gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM(first->object->widget), text); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(MenuRadioItem_tick), (gpointer)this); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(object->parentMenu->object->menu), object->widget); + gtk_widget_show(object->widget); +} + +bool MenuRadioItem::checked() { + return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(object->widget)); +} + +void MenuRadioItem::setChecked() { + object->locked = true; + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(object->widget), true); + object->locked = false; +} diff --git a/phoenix/gtk/messagewindow.cpp b/phoenix/gtk/messagewindow.cpp new file mode 100755 index 00000000..6c41f022 --- /dev/null +++ b/phoenix/gtk/messagewindow.cpp @@ -0,0 +1,65 @@ +static MessageWindow::Response MessageWindow_response(MessageWindow::Buttons buttons, gint response) { + if(response == GTK_RESPONSE_OK) return MessageWindow::Response::Ok; + if(response == GTK_RESPONSE_CANCEL) return MessageWindow::Response::Cancel; + if(response == GTK_RESPONSE_YES) return MessageWindow::Response::Yes; + if(response == GTK_RESPONSE_NO) return MessageWindow::Response::No; + if(buttons == MessageWindow::Buttons::OkCancel) return MessageWindow::Response::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) return MessageWindow::Response::No; + return MessageWindow::Response::Ok; +} + +MessageWindow::Response MessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} + +MessageWindow::Response MessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} + +MessageWindow::Response MessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} + +MessageWindow::Response MessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} diff --git a/phoenix/gtk/object.cpp b/phoenix/gtk/object.cpp new file mode 100755 index 00000000..5b5e04f6 --- /dev/null +++ b/phoenix/gtk/object.cpp @@ -0,0 +1,69 @@ +struct Object::Data { + bool locked; + GtkWidget *widget; + GtkWidget *subWidget; + GtkWidget *menuContainer; + GtkWidget *formContainer; + GtkWidget *statusContainer; + GtkWidget *menu; + GtkWidget *status; + Menu *parentMenu; + Window *parentWindow; + GtkTextBuffer *textBuffer; + unsigned position; +}; + +struct Font::Data { + PangoFontDescription *font; +}; + +struct Action::Data { + Font *font; +}; + +struct Widget::Data { + Window *parent; +}; + +struct Window::Data { + Font *defaultFont; +}; + +struct Canvas::Data { + uint32_t *bufferRGB; + uint32_t *bufferBGR; + unsigned pitch; +}; + +struct HexEditor::Data { + GtkWidget *container; + GtkWidget *widget; + GtkWidget *scroll; + + GtkTextMark *cursor; + unsigned size; + unsigned offset; + unsigned columns; + unsigned rows; +}; + +struct ListBox::Data { + GtkListStore *store; + struct GtkColumn { + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkWidget *label; + }; + linear_vector column; + bool checkable; + signed selection; +}; + +void Object::unused() { +} + +Object::Object() { + OS::initialize(); + object = new Object::Data; + object->locked = false; +} diff --git a/phoenix/gtk/progressbar.cpp b/phoenix/gtk/progressbar.cpp new file mode 100755 index 00000000..193e924d --- /dev/null +++ b/phoenix/gtk/progressbar.cpp @@ -0,0 +1,12 @@ +void ProgressBar::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + object->widget = gtk_progress_bar_new(); + widget->parent = &parent; + gtk_widget_set_size_request(object->widget, width, height); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +void ProgressBar::setPosition(unsigned position) { + position = position <= 100 ? position : 0; + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(object->widget), (double)position / 100.0); +} diff --git a/phoenix/gtk/radiobox.cpp b/phoenix/gtk/radiobox.cpp new file mode 100755 index 00000000..603e199f --- /dev/null +++ b/phoenix/gtk/radiobox.cpp @@ -0,0 +1,36 @@ +static void RadioBox_tick(RadioBox *self) { + if(self->onTick && self->checked() && self->object->locked == false) self->onTick(); +} + +void RadioBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + first = this; + object->parentWindow = &parent; + object->widget = gtk_radio_button_new_with_label(0, text); + widget->parent = &parent; + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(RadioBox_tick), (gpointer)this); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +void RadioBox::create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + first = parent.first; + object->parentWindow = parent.object->parentWindow; + object->widget = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(parent.object->widget), text); + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(RadioBox_tick), (gpointer)this); + if(object->parentWindow->window->defaultFont) setFont(*object->parentWindow->window->defaultFont); + gtk_fixed_put(GTK_FIXED(object->parentWindow->object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +bool RadioBox::checked() { + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(object->widget)); +} + +void RadioBox::setChecked() { + object->locked = true; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object->widget), true); + object->locked = false; +} diff --git a/phoenix/gtk/textbox.cpp b/phoenix/gtk/textbox.cpp new file mode 100755 index 00000000..66455f03 --- /dev/null +++ b/phoenix/gtk/textbox.cpp @@ -0,0 +1,33 @@ +static void TextBox_activate(TextBox *self) { + if(self->onActivate) self->onActivate(); +} + +static void TextBox_change(TextBox *self) { + if(self->object->locked == false && self->onChange) self->onChange(); +} + +void TextBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_entry_new(); + widget->parent = &parent; + gtk_entry_set_text(GTK_ENTRY(object->widget), text); + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "activate", G_CALLBACK(TextBox_activate), (gpointer)this); + g_signal_connect_swapped(G_OBJECT(object->widget), "changed", G_CALLBACK(TextBox_change), (gpointer)this); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +void TextBox::setEditable(bool editable) { + gtk_entry_set_editable(GTK_ENTRY(object->widget), editable); +} + +string TextBox::text() { + return gtk_entry_get_text(GTK_ENTRY(object->widget)); +} + +void TextBox::setText(const string &text) { + object->locked = true; + gtk_entry_set_text(GTK_ENTRY(object->widget), text); + object->locked = false; +} diff --git a/phoenix/gtk/verticalslider.cpp b/phoenix/gtk/verticalslider.cpp new file mode 100755 index 00000000..1cca9e4c --- /dev/null +++ b/phoenix/gtk/verticalslider.cpp @@ -0,0 +1,25 @@ +static void VerticalSlider_change(VerticalSlider *self) { + if(self->object->position == self->position()) return; + self->object->position = self->position(); + if(self->onChange) self->onChange(); +} + +void VerticalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + object->position = 0; + length += (length == 0); + object->widget = gtk_vscale_new_with_range(0, length - 1, 1); + widget->parent = &parent; + gtk_scale_set_draw_value(GTK_SCALE(object->widget), false); + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "value-changed", G_CALLBACK(VerticalSlider_change), (gpointer)this); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +unsigned VerticalSlider::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(object->widget)); +} + +void VerticalSlider::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(object->widget), position); +} diff --git a/phoenix/gtk/viewport.cpp b/phoenix/gtk/viewport.cpp new file mode 100755 index 00000000..3b407727 --- /dev/null +++ b/phoenix/gtk/viewport.cpp @@ -0,0 +1,20 @@ +void Viewport::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + object->widget = gtk_drawing_area_new(); + widget->parent = &parent; +//gtk_widget_set_double_buffered(object->widget, false); + gtk_widget_set_size_request(object->widget, width, height); + + GdkColor color; + color.pixel = 0; + color.red = 0; + color.green = 0; + color.blue = 0; + gtk_widget_modify_bg(object->widget, GTK_STATE_NORMAL, &color); + + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +uintptr_t Viewport::handle() { + return GDK_WINDOW_XID(object->widget->window); +} diff --git a/phoenix/gtk/widget.cpp b/phoenix/gtk/widget.cpp new file mode 100755 index 00000000..4f0bf079 --- /dev/null +++ b/phoenix/gtk/widget.cpp @@ -0,0 +1,47 @@ +static void Widget_setFont(GtkWidget *widget, gpointer font) { + gtk_widget_modify_font(widget, (PangoFontDescription*)font); + if(GTK_IS_CONTAINER(widget)) { + gtk_container_foreach(GTK_CONTAINER(widget), (GtkCallback)Widget_setFont, font); + } +} + +void Widget::setFont(Font &font) { + Widget_setFont(object->widget, font.font->font); +} + +bool Widget::visible() { + return gtk_widget_get_visible(object->widget); +} + +void Widget::setVisible(bool visible) { + if(visible) gtk_widget_show(object->widget); + else gtk_widget_hide(object->widget); +} + +bool Widget::enabled() { + return gtk_widget_get_sensitive(object->widget); +} + +void Widget::setEnabled(bool enabled) { + gtk_widget_set_sensitive(object->widget, enabled); +} + +bool Widget::focused() { + return gtk_widget_is_focus(object->widget); +} + +void Widget::setFocused() { + if(visible() == false) setVisible(true); + gtk_widget_grab_focus(object->widget); +} + +void Widget::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + if(widget->parent == 0) return; + gtk_fixed_move(GTK_FIXED(widget->parent->object->formContainer), object->widget, x, y); + gtk_widget_set_size_request(object->widget, width, height); +} + +Widget::Widget() { + widget = new Widget::Data; + widget->parent = 0; +} diff --git a/phoenix/gtk/window.cpp b/phoenix/gtk/window.cpp new file mode 100755 index 00000000..5b289296 --- /dev/null +++ b/phoenix/gtk/window.cpp @@ -0,0 +1,99 @@ +static gint Window_close(Window *window) { + if(window->onClose) { + if(window->onClose()) window->setVisible(false); + return true; + } + window->setVisible(false); + return true; +} + +void Window::create(unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_move(GTK_WINDOW(object->widget), x, y); + + gtk_window_set_title(GTK_WINDOW(object->widget), text); + gtk_window_set_resizable(GTK_WINDOW(object->widget), false); + gtk_widget_set_app_paintable(object->widget, true); + + g_signal_connect_swapped(G_OBJECT(object->widget), "delete_event", G_CALLBACK(Window_close), (gpointer)this); + + object->menuContainer = gtk_vbox_new(false, 0); + gtk_container_add(GTK_CONTAINER(object->widget), object->menuContainer); + gtk_widget_show(object->menuContainer); + + object->menu = gtk_menu_bar_new(); + gtk_box_pack_start(GTK_BOX(object->menuContainer), object->menu, false, false, 0); + + object->formContainer = gtk_fixed_new(); + gtk_widget_set_size_request(object->formContainer, width, height); + gtk_box_pack_start(GTK_BOX(object->menuContainer), object->formContainer, true, true, 0); + gtk_widget_show(object->formContainer); + + object->statusContainer = gtk_event_box_new(); + object->status = gtk_statusbar_new(); + gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(object->status), false); + gtk_container_add(GTK_CONTAINER(object->statusContainer), object->status); + gtk_box_pack_start(GTK_BOX(object->menuContainer), object->statusContainer, false, false, 0); + gtk_widget_show(object->statusContainer); + + gtk_widget_realize(object->widget); +} + +bool Window::focused() { + return gtk_window_is_active(GTK_WINDOW(object->widget)); +} + +void Window::setFocused() { + gtk_window_present(GTK_WINDOW(object->widget)); +} + +Geometry Window::geometry() { + gint x, y, width, height; + gtk_window_get_position(GTK_WINDOW(object->widget), &x, &y); + gtk_widget_get_size_request(object->formContainer, &width, &height); + return Geometry(x, y, width, height); +} + +void Window::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + gtk_window_move(GTK_WINDOW(object->widget), x, y); + gtk_widget_set_size_request(object->formContainer, width, height); +} + +void Window::setDefaultFont(Font &font) { + window->defaultFont = &font; +} + +void Window::setFont(Font &font) { + Widget_setFont(object->status, font.font->font); +} + +void Window::setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue) { + GdkColor color; + color.pixel = (red << 16) | (green << 8) | (blue << 0); + color.red = (red << 8) | (red << 0); + color.green = (green << 8) | (green << 0); + color.blue = (blue << 8) | (blue << 0); + gtk_widget_modify_bg(object->widget, GTK_STATE_NORMAL, &color); +} + +void Window::setTitle(const string &text) { + gtk_window_set_title(GTK_WINDOW(object->widget), text); +} + +void Window::setStatusText(const string &text) { + gtk_statusbar_pop(GTK_STATUSBAR(object->status), 1); + gtk_statusbar_push(GTK_STATUSBAR(object->status), 1, text); +} + +void Window::setMenuVisible(bool visible) { + gtk_widget_set_visible(object->menu, visible); +} + +void Window::setStatusVisible(bool visible) { + gtk_widget_set_visible(object->status, visible); +} + +Window::Window() { + window = new Window::Data; + window->defaultFont = 0; +} diff --git a/phoenix/phoenix.cpp b/phoenix/phoenix.cpp new file mode 100755 index 00000000..bf51451b --- /dev/null +++ b/phoenix/phoenix.cpp @@ -0,0 +1,17 @@ +#if defined(PHOENIX_WINDOWS) + #define UNICODE + #define WINVER 0x0501 + #define _WIN32_WINNT 0x0501 + #define _WIN32_IE 0x0600 + #define NOMINMAX +#endif + +#include "phoenix.hpp" + +#if defined(PHOENIX_WINDOWS) + #include "windows/windows.cpp" +#elif defined(PHOENIX_GTK) + #include "gtk/gtk.cpp" +#elif defined(PHOENIX_QT) + #include "qt/qt.cpp" +#endif diff --git a/phoenix/phoenix.hpp b/phoenix/phoenix.hpp new file mode 100755 index 00000000..99c82d15 --- /dev/null +++ b/phoenix/phoenix.hpp @@ -0,0 +1,15 @@ +#include +#include +#include +#include +#include +#include +#include + +#if defined(PHOENIX_WINDOWS) + #include "windows/windows.hpp" +#elif defined(PHOENIX_GTK) + #include "gtk/gtk.hpp" +#elif defined(PHOENIX_QT) + #include "qt/qt.hpp" +#endif diff --git a/phoenix/qt/button.cpp b/phoenix/qt/button.cpp new file mode 100755 index 00000000..e11a14b5 --- /dev/null +++ b/phoenix/qt/button.cpp @@ -0,0 +1,13 @@ +void Button::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + button->setParent(parent.window->container); + button->setGeometry(x, y, width, height); + button->setText(QString::fromUtf8(text)); + if(parent.window->defaultFont) button->setFont(*parent.window->defaultFont); + button->show(); + button->connect(button, SIGNAL(released()), SLOT(onTick())); +} + +Button::Button() { + button = new Button::Data(*this); + widget->widget = button; +} diff --git a/phoenix/qt/canvas.cpp b/phoenix/qt/canvas.cpp new file mode 100755 index 00000000..a0887604 --- /dev/null +++ b/phoenix/qt/canvas.cpp @@ -0,0 +1,39 @@ +void Canvas::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + canvas->image = new QImage(width, height, QImage::Format_RGB32); + canvas->image->fill(0); + canvas->setParent(parent.window->container); + canvas->setGeometry(x, y, width, height); + canvas->show(); +} + +void Canvas::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + delete canvas->image; + canvas->image = new QImage(width, height, QImage::Format_RGB32); + canvas->image->fill(0); + canvas->setGeometry(x, y, width, height); + canvas->update(); +} + +uint32_t* Canvas::buffer() { + return (uint32_t*)canvas->image->bits(); +} + +void Canvas::redraw() { + canvas->update(); +} + +Canvas::Canvas() { + canvas = new Canvas::Data(*this); + canvas->image = 0; + widget->widget = canvas; +} + +Canvas::~Canvas() { + if(canvas->image) delete canvas->image; + delete canvas; +} + +void Canvas::Data::paintEvent(QPaintEvent *event) { + QPainter painter(this); + painter.drawImage(0, 0, *image); +} diff --git a/phoenix/qt/checkbox.cpp b/phoenix/qt/checkbox.cpp new file mode 100755 index 00000000..a0ab121b --- /dev/null +++ b/phoenix/qt/checkbox.cpp @@ -0,0 +1,21 @@ +void CheckBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + checkBox->setParent(parent.window->container); + checkBox->setGeometry(x, y, width, height); + checkBox->setText(QString::fromUtf8(text)); + if(parent.window->defaultFont) checkBox->setFont(*parent.window->defaultFont); + checkBox->show(); + checkBox->connect(checkBox, SIGNAL(stateChanged(int)), SLOT(onTick())); +} + +bool CheckBox::checked() { + return checkBox->isChecked(); +} + +void CheckBox::setChecked(bool checked) { + checkBox->setChecked(checked); +} + +CheckBox::CheckBox() { + checkBox = new CheckBox::Data(*this); + widget->widget = checkBox; +} diff --git a/phoenix/qt/combobox.cpp b/phoenix/qt/combobox.cpp new file mode 100755 index 00000000..41bd7d8f --- /dev/null +++ b/phoenix/qt/combobox.cpp @@ -0,0 +1,38 @@ +void ComboBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + comboBox->setParent(parent.window->container); + comboBox->setGeometry(x, y, width, height); + + if(*text) { + lstring list; + list.split("\n", text); + foreach(item, list) addItem(item); + } + + comboBox->connect(comboBox, SIGNAL(currentIndexChanged(int)), SLOT(onChange())); + if(parent.window->defaultFont) comboBox->setFont(*parent.window->defaultFont); + comboBox->show(); +} + +void ComboBox::reset() { + while(comboBox->count()) comboBox->removeItem(0); +} + +void ComboBox::addItem(const string &text) { + comboBox->addItem(QString::fromUtf8(text)); +} + +unsigned ComboBox::selection() { + signed index = comboBox->currentIndex(); + return (index >= 0 ? index : 0); +} + +void ComboBox::setSelection(unsigned row) { + object->locked = true; + comboBox->setCurrentIndex(row); + object->locked = false; +} + +ComboBox::ComboBox() { + comboBox = new ComboBox::Data(*this); + widget->widget = comboBox; +} diff --git a/phoenix/qt/editbox.cpp b/phoenix/qt/editbox.cpp new file mode 100755 index 00000000..7f7b744c --- /dev/null +++ b/phoenix/qt/editbox.cpp @@ -0,0 +1,36 @@ +void EditBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + editBox->setParent(parent.window->container); + editBox->setGeometry(x, y, width, height); + editBox->setPlainText(QString::fromUtf8(text)); + if(parent.window->defaultFont) editBox->setFont(*parent.window->defaultFont); + editBox->show(); + editBox->connect(editBox, SIGNAL(textChanged()), SLOT(onChange())); +} + +void EditBox::setEditable(bool editable) { + editBox->setReadOnly(editable == false); +} + +void EditBox::setWordWrap(bool wordWrap) { + editBox->setWordWrapMode(wordWrap ? QTextOption::WordWrap : QTextOption::NoWrap); +} + +string EditBox::text() { + return editBox->toPlainText().toUtf8().constData(); +} + +void EditBox::setText(const string &text) { + editBox->setPlainText(QString::fromUtf8(text)); +} + +void EditBox::setCursorPosition(unsigned position) { + QTextCursor cursor = editBox->textCursor(); + unsigned lastchar = strlen(editBox->toPlainText().toUtf8().constData()); + cursor.setPosition(min(position, lastchar)); + editBox->setTextCursor(cursor); +} + +EditBox::EditBox() { + editBox = new EditBox::Data(*this); + widget->widget = editBox; +} diff --git a/phoenix/qt/font.cpp b/phoenix/qt/font.cpp new file mode 100755 index 00000000..01d6bab6 --- /dev/null +++ b/phoenix/qt/font.cpp @@ -0,0 +1,14 @@ +bool Font::create(const string &name, unsigned size, Font::Style style) { + font->setFamily(QString::fromUtf8(name)); + font->setPointSize(size); + font->setBold((style & Style::Bold) == Style::Bold); + font->setItalic((style & Style::Italic) == Style::Italic); +} + +Font::Font() { + font = new Font::Data(*this); +} + +Font::~Font() { + delete font; +} diff --git a/phoenix/qt/horizontalslider.cpp b/phoenix/qt/horizontalslider.cpp new file mode 100755 index 00000000..48f9e18c --- /dev/null +++ b/phoenix/qt/horizontalslider.cpp @@ -0,0 +1,22 @@ +void HorizontalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + length += (length == 0); + horizontalSlider->setParent(parent.window->container); + horizontalSlider->setGeometry(x, y, width, height); + horizontalSlider->setRange(0, length - 1); + horizontalSlider->setPageStep(length >> 3); + horizontalSlider->connect(horizontalSlider, SIGNAL(valueChanged(int)), SLOT(onChange())); + horizontalSlider->show(); +} + +unsigned HorizontalSlider::position() { + return horizontalSlider->value(); +} + +void HorizontalSlider::setPosition(unsigned position) { + horizontalSlider->setValue(position); +} + +HorizontalSlider::HorizontalSlider() { + horizontalSlider = new HorizontalSlider::Data(*this); + widget->widget = horizontalSlider; +} diff --git a/phoenix/qt/label.cpp b/phoenix/qt/label.cpp new file mode 100755 index 00000000..cee55452 --- /dev/null +++ b/phoenix/qt/label.cpp @@ -0,0 +1,16 @@ +void Label::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + label->setParent(parent.window->container); + label->setGeometry(x, y, width, height); + label->setText(QString::fromUtf8(text)); + if(parent.window->defaultFont) label->setFont(*parent.window->defaultFont); + label->show(); +} + +void Label::setText(const string &text) { + label->setText(QString::fromUtf8(text)); +} + +Label::Label() { + label = new Label::Data(*this); + widget->widget = label; +} diff --git a/phoenix/qt/listbox.cpp b/phoenix/qt/listbox.cpp new file mode 100755 index 00000000..ac5e97ee --- /dev/null +++ b/phoenix/qt/listbox.cpp @@ -0,0 +1,102 @@ +void ListBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + listBox->setParent(parent.window->container); + listBox->setGeometry(x, y, width, height); + listBox->setAllColumnsShowFocus(true); + listBox->setRootIsDecorated(false); + + lstring list; + list.split("\t", text); + QStringList labels; + foreach(item, list) labels << QString::fromUtf8(item); + listBox->setColumnCount(list.size()); + listBox->setHeaderLabels(labels); + for(unsigned i = 0; i < list.size(); i++) listBox->resizeColumnToContents(i); + + listBox->setHeaderHidden(true); + listBox->setAlternatingRowColors(list.size() >= 2); + listBox->connect(listBox, SIGNAL(itemActivated(QTreeWidgetItem*, int)), SLOT(onActivate())); + listBox->connect(listBox, SIGNAL(itemSelectionChanged()), SLOT(onChange())); + listBox->connect(listBox, SIGNAL(itemChanged(QTreeWidgetItem*, int)), SLOT(onTick(QTreeWidgetItem*))); + if(parent.window->defaultFont) listBox->setFont(*parent.window->defaultFont); + listBox->show(); +} + +void ListBox::setHeaderVisible(bool headerVisible) { + listBox->setHeaderHidden(headerVisible == false); +} + +void ListBox::setCheckable(bool checkable) { + listBox->checkable = checkable; + if(listBox->checkable) { + auto items = listBox->findItems("", Qt::MatchContains); + for(unsigned i = 0; i < items.size(); i++) items[i]->setCheckState(0, Qt::Unchecked); + } +} + +void ListBox::reset() { + listBox->clear(); +} + +void ListBox::resizeColumnsToContent() { + for(unsigned i = 0; i < listBox->columnCount(); i++) listBox->resizeColumnToContents(i); +} + +void ListBox::addItem(const string &text) { + object->locked = true; + auto items = listBox->findItems("", Qt::MatchContains); + QTreeWidgetItem *item = new QTreeWidgetItem(listBox); + if(listBox->checkable) item->setCheckState(0, Qt::Unchecked); + item->setData(0, Qt::UserRole, (unsigned)items.size()); + lstring list; + list.split("\t", text); + for(unsigned i = 0; i < list.size(); i++) item->setText(i, QString::fromUtf8(list[i])); + object->locked = false; +} + +void ListBox::setItem(unsigned row, const string &text) { + object->locked = true; + QTreeWidgetItem *item = listBox->topLevelItem(row); + lstring list; + list.split("\t", text); + for(unsigned i = 0; i < list.size(); i++) item->setText(i, QString::fromUtf8(list[i])); + object->locked = false; +} + +bool ListBox::checked(unsigned row) { + QTreeWidgetItem *item = listBox->topLevelItem(row); + return (item ? item->checkState(0) == Qt::Checked : false); +} + +void ListBox::setChecked(unsigned row, bool checked) { + object->locked = true; + QTreeWidgetItem *item = listBox->topLevelItem(row); + if(item) item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked); + object->locked = false; +} + +optional ListBox::selection() { + QTreeWidgetItem *item = listBox->currentItem(); + if(item == 0) return { false, 0 }; + if(item->isSelected() == false) return { false, 0 }; + unsigned row = item->data(0, Qt::UserRole).toUInt(); + return { true, row }; +} + +void ListBox::setSelection(unsigned row) { + object->locked = true; + QTreeWidgetItem *item = listBox->currentItem(); + if(item) item->setSelected(false); + auto items = listBox->findItems("", Qt::MatchContains); + for(unsigned i = 0; i < items.size(); i++) { + if(items[i]->data(0, Qt::UserRole).toUInt() == row) { + listBox->setCurrentItem(items[i]); + break; + } + } + object->locked = false; +} + +ListBox::ListBox() { + listBox = new ListBox::Data(*this); + widget->widget = listBox; +} diff --git a/phoenix/qt/menu.cpp b/phoenix/qt/menu.cpp new file mode 100755 index 00000000..8e6fa819 --- /dev/null +++ b/phoenix/qt/menu.cpp @@ -0,0 +1,169 @@ +void Menu::create(Window &parent, const string &text) { + menu->parent = &parent; + if(menu->parent->window->defaultFont) menu->setFont(*menu->parent->window->defaultFont); + menu->setTitle(QString::fromUtf8(text)); + parent.window->menuBar->addMenu(menu); +} + +void Menu::create(Menu &parent, const string &text) { + menu->parent = parent.menu->parent; + if(menu->parent->window->defaultFont) menu->setFont(*menu->parent->window->defaultFont); + menu->setTitle(QString::fromUtf8(text)); + parent.menu->addMenu(menu); +} + +bool Menu::visible() { + return menu->isVisible(); +} + +void Menu::setVisible(bool visible) { + menu->setVisible(visible); +} + +bool Menu::enabled() { + return menu->isEnabled(); +} + +void Menu::setEnabled(bool enabled) { + menu->setEnabled(enabled); +} + +Menu::Menu() { + menu = new Menu::Data(*this); +} + +void MenuSeparator::create(Menu &parent) { + menuSeparator->action = parent.menu->addSeparator(); +} + +bool MenuSeparator::visible() { + return menuSeparator->action->isVisible(); +} + +void MenuSeparator::setVisible(bool visible) { + menuSeparator->action->setVisible(visible); +} + +bool MenuSeparator::enabled() { + return menuSeparator->action->isEnabled(); +} + +void MenuSeparator::setEnabled(bool enabled) { + menuSeparator->action->setEnabled(enabled); +} + +MenuSeparator::MenuSeparator() { + menuSeparator = new MenuSeparator::Data(*this); +} + +void MenuItem::create(Menu &parent, const string &text) { + menuItem->setText(QString::fromUtf8(text)); + menuItem->connect(menuItem, SIGNAL(triggered()), SLOT(onTick())); + parent.menu->addAction(menuItem); +} + +bool MenuItem::visible() { + return menuItem->isVisible(); +} + +void MenuItem::setVisible(bool visible) { + menuItem->setVisible(visible); +} + +bool MenuItem::enabled() { + return menuItem->isEnabled(); +} + +void MenuItem::setEnabled(bool enabled) { + menuItem->setEnabled(enabled); +} + +MenuItem::MenuItem() { + menuItem = new MenuItem::Data(*this); +} + +void MenuCheckItem::create(Menu &parent, const string &text) { + menuCheckItem->setText(QString::fromUtf8(text)); + menuCheckItem->setCheckable(true); + menuCheckItem->connect(menuCheckItem, SIGNAL(triggered()), SLOT(onTick())); + parent.menu->addAction(menuCheckItem); +} + +bool MenuCheckItem::visible() { + return menuCheckItem->isVisible(); +} + +void MenuCheckItem::setVisible(bool visible) { + menuCheckItem->setVisible(visible); +} + +bool MenuCheckItem::enabled() { + return menuCheckItem->isEnabled(); +} + +void MenuCheckItem::setEnabled(bool enabled) { + menuCheckItem->setEnabled(enabled); +} + +bool MenuCheckItem::checked() { + return menuCheckItem->isChecked(); +} + +void MenuCheckItem::setChecked(bool checked) { + menuCheckItem->setChecked(checked); +} + +MenuCheckItem::MenuCheckItem() { + menuCheckItem = new MenuCheckItem::Data(*this); +} + +void MenuRadioItem::create(Menu &parent, const string &text) { + menuRadioItem->parent = &parent; + menuRadioItem->actionGroup = new QActionGroup(0); + menuRadioItem->actionGroup->addAction(menuRadioItem); + menuRadioItem->setText(QString::fromUtf8(text)); + menuRadioItem->setCheckable(true); + menuRadioItem->setChecked(true); + menuRadioItem->connect(menuRadioItem, SIGNAL(changed()), SLOT(onTick())); + menuRadioItem->parent->menu->addAction(menuRadioItem); +} + +void MenuRadioItem::create(MenuRadioItem &parent, const string &text) { + menuRadioItem->parent = parent.menuRadioItem->parent; + menuRadioItem->actionGroup = parent.menuRadioItem->actionGroup; + menuRadioItem->actionGroup->addAction(menuRadioItem); + menuRadioItem->setText(QString::fromUtf8(text)); + menuRadioItem->setCheckable(true); + menuRadioItem->connect(menuRadioItem, SIGNAL(changed()), SLOT(onTick())); + menuRadioItem->parent->menu->addAction(menuRadioItem); +} + +bool MenuRadioItem::visible() { + return menuRadioItem->isVisible(); +} + +void MenuRadioItem::setVisible(bool visible) { + menuRadioItem->setVisible(visible); +} + +bool MenuRadioItem::enabled() { + return menuRadioItem->isEnabled(); +} + +void MenuRadioItem::setEnabled(bool enabled) { + menuRadioItem->setEnabled(enabled); +} + +bool MenuRadioItem::checked() { + return menuRadioItem->isChecked(); +} + +void MenuRadioItem::setChecked() { + object->locked = true; + menuRadioItem->setChecked(true); + object->locked = false; +} + +MenuRadioItem::MenuRadioItem() { + menuRadioItem = new MenuRadioItem::Data(*this); +} diff --git a/phoenix/qt/messagewindow.cpp b/phoenix/qt/messagewindow.cpp new file mode 100755 index 00000000..5d60ea01 --- /dev/null +++ b/phoenix/qt/messagewindow.cpp @@ -0,0 +1,45 @@ +static QMessageBox::StandardButtons MessageWindow_buttons(MessageWindow::Buttons buttons) { + QMessageBox::StandardButtons standardButtons = QMessageBox::NoButton; + if(buttons == MessageWindow::Buttons::Ok) standardButtons = QMessageBox::Ok; + if(buttons == MessageWindow::Buttons::OkCancel) standardButtons = QMessageBox::Ok | QMessageBox::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) standardButtons = QMessageBox::Yes | QMessageBox::No; + return standardButtons; +} + +static MessageWindow::Response MessageWindow_response(MessageWindow::Buttons buttons, QMessageBox::StandardButton response) { + if(response == QMessageBox::Ok) return MessageWindow::Response::Ok; + if(response == QMessageBox::Cancel) return MessageWindow::Response::Cancel; + if(response == QMessageBox::Yes) return MessageWindow::Response::Yes; + if(response == QMessageBox::No) return MessageWindow::Response::No; + if(buttons == MessageWindow::Buttons::OkCancel) return MessageWindow::Response::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) return MessageWindow::Response::No; + return MessageWindow::Response::Ok; +} + +MessageWindow::Response MessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::information(&parent != &Window::None ? parent.window : 0, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response MessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::question(&parent != &Window::None ? parent.window : 0, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response MessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::warning(&parent != &Window::None ? parent.window : 0, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response MessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::critical(&parent != &Window::None ? parent.window : 0, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} diff --git a/phoenix/qt/object.cpp b/phoenix/qt/object.cpp new file mode 100755 index 00000000..ae6cc6d9 --- /dev/null +++ b/phoenix/qt/object.cpp @@ -0,0 +1,7 @@ +void Object::unused() { +} + +Object::Object() { + OS::initialize(); + object = new Object::Data(*this); +} diff --git a/phoenix/qt/progressbar.cpp b/phoenix/qt/progressbar.cpp new file mode 100755 index 00000000..ab21f882 --- /dev/null +++ b/phoenix/qt/progressbar.cpp @@ -0,0 +1,16 @@ +void ProgressBar::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + progressBar->setParent(parent.window->container); + progressBar->setGeometry(x, y, width, height); + progressBar->setRange(0, 100); + progressBar->setTextVisible(false); + progressBar->show(); +} + +void ProgressBar::setPosition(unsigned position) { + progressBar->setValue(position); +} + +ProgressBar::ProgressBar() { + progressBar = new ProgressBar::Data(*this); + widget->widget = progressBar; +} diff --git a/phoenix/qt/qt.cpp b/phoenix/qt/qt.cpp new file mode 100755 index 00000000..203b4b6f --- /dev/null +++ b/phoenix/qt/qt.cpp @@ -0,0 +1,128 @@ +#include +#include +using namespace nall; + +namespace phoenix { + +#include "qt.moc.hpp" +#include "qt.moc" + +#include "object.cpp" +#include "font.cpp" +#include "menu.cpp" +#include "widget.cpp" +#include "window.cpp" +#include "button.cpp" +#include "canvas.cpp" +#include "checkbox.cpp" +#include "combobox.cpp" +#include "editbox.cpp" +#include "horizontalslider.cpp" +#include "label.cpp" +#include "listbox.cpp" +#include "progressbar.cpp" +#include "radiobox.cpp" +#include "textbox.cpp" +#include "verticalslider.cpp" +#include "viewport.cpp" +#include "messagewindow.cpp" + +OS::Data *OS::os = 0; +Window Window::None; + +void OS::initialize() { + static bool initialized = false; + if(initialized == true) return; + initialized = true; + + os = new OS::Data; + static int argc = 1; + static char *argv[2]; + argv[0] = new char[8]; + argv[1] = 0; + strcpy(argv[0], "phoenix"); + char **argvp = argv; + os->application = new QApplication(argc, argvp); +} + +bool OS::pending() { + return QApplication::hasPendingEvents(); +} + +void OS::run() { + QApplication::processEvents(); +} + +void OS::main() { + QApplication::exec(); +} + +void OS::quit() { + QApplication::quit(); +} + +unsigned OS::desktopWidth() { + return QApplication::desktop()->screenGeometry().width(); +} + +unsigned OS::desktopHeight() { + return QApplication::desktop()->screenGeometry().height(); +} + +string OS::folderSelect(Window &parent, const string &path) { + QString directory = QFileDialog::getExistingDirectory( + &parent != &Window::None ? parent.window : 0, "Select Directory", + QString::fromUtf8(path), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks + ); + string name = directory.toUtf8().constData(); + if(name.endswith("/") == false) name.append("/"); + return name; +} + +string OS::fileOpen(Window &parent, const string &filter, const string &path) { + string filters; + lstring list; + list.split("\n", filter); + foreach(item, list) { + lstring part; + part.split("\t", item); + if(part.size() != 2) continue; + part[1].replace(",", " "); + filters.append(part[0]); + filters.append(" ("); + filters.append(part[1]); + filters.append(");;"); + } + filters.rtrim(";;"); + + QString filename = QFileDialog::getOpenFileName( + &parent != &Window::None ? parent.window : 0, "Open File", + QString::fromUtf8(path), QString::fromUtf8(filters) + ); + return filename.toUtf8().constData(); +} + +string OS::fileSave(Window &parent, const string &filter, const string &path) { + string filters; + lstring list; + list.split("\n", filter); + foreach(item, list) { + lstring part; + part.split("\t", item); + if(part.size() != 2) continue; + part[1].replace(",", " "); + filters.append(part[0]); + filters.append(" ("); + filters.append(part[1]); + filters.append(");;"); + } + filters.rtrim(";;"); + + QString filename = QFileDialog::getSaveFileName( + &parent != &Window::None ? parent.window : 0, "Save File", + QString::fromUtf8(path), QString::fromUtf8(filters) + ); + return filename.toUtf8().constData(); +} + +} diff --git a/phoenix/qt/qt.hpp b/phoenix/qt/qt.hpp new file mode 100755 index 00000000..9c6abdcc --- /dev/null +++ b/phoenix/qt/qt.hpp @@ -0,0 +1,338 @@ +namespace phoenix { + +struct Window; + +struct Object { + Object(); + Object& operator=(const Object&) = delete; + Object(const Object&) = delete; +//private: + virtual void unused(); + struct Data; + Data *object; +}; + +struct Geometry { + unsigned x, y; + unsigned width, height; + inline Geometry() : x(0), y(0), width(0), height(0) {} + inline Geometry(unsigned x, unsigned y, unsigned width, unsigned height) : x(x), y(y), width(width), height(height) {} +}; + +struct Font : Object { + enum class Style : unsigned { + None = 0, + Bold = 1, + Italic = 2, + }; + bool create(const nall::string &name, unsigned size, Font::Style style = Style::None); + Font(); + ~Font(); +//private: + struct Data; + Data *font; +}; + +inline Font::Style operator|(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a | (unsigned)b); } +inline Font::Style operator&(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a & (unsigned)b); } + +struct Action : Object { + virtual bool visible() = 0; + virtual void setVisible(bool visible = true) = 0; + virtual bool enabled() = 0; + virtual void setEnabled(bool enabled = true) = 0; +}; + +struct Menu : Action { + void create(Window &parent, const nall::string &text); + void create(Menu &parent, const nall::string &text); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + Menu(); +//private: + struct Data; + Data *menu; +}; + +struct MenuSeparator : Action { + void create(Menu &parent); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + MenuSeparator(); +//private: + struct Data; + Data *menuSeparator; +}; + +struct MenuItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + MenuItem(); +//private: + struct Data; + Data *menuItem; +}; + +struct MenuCheckItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + bool checked(); + void setChecked(bool checked = true); + MenuCheckItem(); +//private: + struct Data; + Data *menuCheckItem; +}; + +struct MenuRadioItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + void create(MenuRadioItem &parent, const nall::string &text); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + bool checked(); + void setChecked(); + MenuRadioItem(); +//private: + struct Data; + Data *menuRadioItem; +}; + +struct Widget : Object { + virtual void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + virtual void setFont(Font &font); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + virtual bool focused(); + virtual void setFocused(); + Widget(); +//private: + struct Data; + Data *widget; +}; + +struct Window : Widget { + nall::function onClose; + void create(unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + Geometry geometry(); + void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + void setDefaultFont(Font &font); + void setFont(Font &font); + void setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue); + void setTitle(const nall::string &text); + void setStatusText(const nall::string &text); + void setMenuVisible(bool visible = true); + void setStatusVisible(bool visible = true); + bool focused(); + Window(); +//private: + struct Data; + Data *window; + static Window None; +}; + +struct Button : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + Button(); +//private: + struct Data; + Data *button; +}; + +struct Canvas : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + uint32_t* buffer(); + void redraw(); + Canvas(); + ~Canvas(); +//private: + struct Data; + Data *canvas; +}; + +struct CheckBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(bool checked = true); + CheckBox(); +//private: + struct Data; + Data *checkBox; +}; + +struct ComboBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void reset(); + void addItem(const nall::string &text); + unsigned selection(); + void setSelection(unsigned row); + ComboBox(); +//private: + struct Data; + Data *comboBox; +}; + +struct EditBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setEditable(bool editable = true); + void setWordWrap(bool wordWrap = true); + nall::string text(); + void setText(const nall::string &text); + void setCursorPosition(unsigned position); + EditBox(); +//private: + struct Data; + Data *editBox; +}; + +struct HorizontalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); + HorizontalSlider(); +//private: + struct Data; + Data *horizontalSlider; +}; + +struct Label : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setText(const nall::string &text); + Label(); +//private: + struct Data; + Data *label; +}; + +struct ListBox : Widget { + nall::function onActivate; + nall::function onChange; + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setHeaderVisible(bool headerVisible = true); + void setCheckable(bool checkable = true); + void reset(); + void resizeColumnsToContent(); + void addItem(const nall::string &text); + void setItem(unsigned row, const nall::string &text); + bool checked(unsigned row); + void setChecked(unsigned row, bool checked = true); + nall::optional selection(); + void setSelection(unsigned row); + ListBox(); +//private: + struct Data; + Data *listBox; +}; + +struct ProgressBar : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + void setPosition(unsigned position); + ProgressBar(); +//private: + struct Data; + Data *progressBar; +}; + +struct RadioBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(); + RadioBox(); +//private: + struct Data; + Data *radioBox; +}; + +struct TextBox : Widget { + nall::function onActivate; + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setEditable(bool editable = true); + nall::string text(); + void setText(const nall::string &text); + TextBox(); +//private: + struct Data; + Data *textBox; +}; + +struct VerticalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); + VerticalSlider(); +//private: + struct Data; + Data *verticalSlider; +}; + +struct Viewport : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + uintptr_t handle(); + Viewport(); +//private: + struct Data; + Data *viewport; +}; + +struct MessageWindow : Object { + enum class Buttons : unsigned { + Ok, + OkCancel, + YesNo, + }; + enum class Response : unsigned { + Ok, + Cancel, + Yes, + No, + }; + static Response information(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response question(Window &parent, const nall::string &text, Buttons = Buttons::YesNo); + static Response warning(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response critical(Window &parent, const nall::string &text, Buttons = Buttons::Ok); +}; + +struct OS : Object { + static bool pending(); + static void run(); + static void main(); + static void quit(); + static unsigned desktopWidth(); + static unsigned desktopHeight(); + static nall::string folderSelect(Window &parent, const nall::string &path = ""); + static nall::string fileOpen(Window &parent, const nall::string &filter, const nall::string &path = ""); + static nall::string fileSave(Window &parent, const nall::string &filter, const nall::string &path = ""); +//private: + struct Data; + static Data *os; + static void initialize(); +}; + +} diff --git a/phoenix/qt/qt.moc b/phoenix/qt/qt.moc new file mode 100755 index 00000000..0d6e7f71 --- /dev/null +++ b/phoenix/qt/qt.moc @@ -0,0 +1,930 @@ +/**************************************************************************** +** Meta object code from reading C++ file 'qt.moc.hpp' +** +** Created: Mon Nov 1 06:26:59 2010 +** by: The Qt Meta Object Compiler version 62 (Qt 4.6.2) +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +#if !defined(Q_MOC_OUTPUT_REVISION) +#error "The header file 'qt.moc.hpp' doesn't include ." +#elif Q_MOC_OUTPUT_REVISION != 62 +#error "This file was generated using the moc from 4.6.2. It" +#error "cannot be used with the include files from this version of Qt." +#error "(The moc has changed too much.)" +#endif + +QT_BEGIN_MOC_NAMESPACE +static const uint qt_meta_data_MenuItem__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 16, 15, 15, 15, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_MenuItem__Data[] = { + "MenuItem::Data\0\0onTick()\0" +}; + +const QMetaObject MenuItem::Data::staticMetaObject = { + { &QAction::staticMetaObject, qt_meta_stringdata_MenuItem__Data, + qt_meta_data_MenuItem__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &MenuItem::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *MenuItem::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *MenuItem::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_MenuItem__Data)) + return static_cast(const_cast< Data*>(this)); + return QAction::qt_metacast(_clname); +} + +int MenuItem::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QAction::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_MenuCheckItem__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 21, 20, 20, 20, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_MenuCheckItem__Data[] = { + "MenuCheckItem::Data\0\0onTick()\0" +}; + +const QMetaObject MenuCheckItem::Data::staticMetaObject = { + { &QAction::staticMetaObject, qt_meta_stringdata_MenuCheckItem__Data, + qt_meta_data_MenuCheckItem__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &MenuCheckItem::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *MenuCheckItem::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *MenuCheckItem::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_MenuCheckItem__Data)) + return static_cast(const_cast< Data*>(this)); + return QAction::qt_metacast(_clname); +} + +int MenuCheckItem::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QAction::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_MenuRadioItem__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 21, 20, 20, 20, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_MenuRadioItem__Data[] = { + "MenuRadioItem::Data\0\0onTick()\0" +}; + +const QMetaObject MenuRadioItem::Data::staticMetaObject = { + { &QAction::staticMetaObject, qt_meta_stringdata_MenuRadioItem__Data, + qt_meta_data_MenuRadioItem__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &MenuRadioItem::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *MenuRadioItem::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *MenuRadioItem::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_MenuRadioItem__Data)) + return static_cast(const_cast< Data*>(this)); + return QAction::qt_metacast(_clname); +} + +int MenuRadioItem::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QAction::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_Window__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +static const char qt_meta_stringdata_Window__Data[] = { + "Window::Data\0" +}; + +const QMetaObject Window::Data::staticMetaObject = { + { &QWidget::staticMetaObject, qt_meta_stringdata_Window__Data, + qt_meta_data_Window__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &Window::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *Window::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *Window::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_Window__Data)) + return static_cast(const_cast< Data*>(this)); + return QWidget::qt_metacast(_clname); +} + +int Window::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QWidget::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +static const uint qt_meta_data_Button__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 14, 13, 13, 13, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_Button__Data[] = { + "Button::Data\0\0onTick()\0" +}; + +const QMetaObject Button::Data::staticMetaObject = { + { &QPushButton::staticMetaObject, qt_meta_stringdata_Button__Data, + qt_meta_data_Button__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &Button::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *Button::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *Button::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_Button__Data)) + return static_cast(const_cast< Data*>(this)); + return QPushButton::qt_metacast(_clname); +} + +int Button::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QPushButton::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_Canvas__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +static const char qt_meta_stringdata_Canvas__Data[] = { + "Canvas::Data\0" +}; + +const QMetaObject Canvas::Data::staticMetaObject = { + { &QWidget::staticMetaObject, qt_meta_stringdata_Canvas__Data, + qt_meta_data_Canvas__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &Canvas::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *Canvas::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *Canvas::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_Canvas__Data)) + return static_cast(const_cast< Data*>(this)); + return QWidget::qt_metacast(_clname); +} + +int Canvas::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QWidget::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +static const uint qt_meta_data_CheckBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 16, 15, 15, 15, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_CheckBox__Data[] = { + "CheckBox::Data\0\0onTick()\0" +}; + +const QMetaObject CheckBox::Data::staticMetaObject = { + { &QCheckBox::staticMetaObject, qt_meta_stringdata_CheckBox__Data, + qt_meta_data_CheckBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &CheckBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *CheckBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *CheckBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_CheckBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QCheckBox::qt_metacast(_clname); +} + +int CheckBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QCheckBox::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_ComboBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 16, 15, 15, 15, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_ComboBox__Data[] = { + "ComboBox::Data\0\0onChange()\0" +}; + +const QMetaObject ComboBox::Data::staticMetaObject = { + { &QComboBox::staticMetaObject, qt_meta_stringdata_ComboBox__Data, + qt_meta_data_ComboBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &ComboBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *ComboBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *ComboBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_ComboBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QComboBox::qt_metacast(_clname); +} + +int ComboBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QComboBox::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_EditBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 15, 14, 14, 14, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_EditBox__Data[] = { + "EditBox::Data\0\0onChange()\0" +}; + +const QMetaObject EditBox::Data::staticMetaObject = { + { &QTextEdit::staticMetaObject, qt_meta_stringdata_EditBox__Data, + qt_meta_data_EditBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &EditBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *EditBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *EditBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_EditBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QTextEdit::qt_metacast(_clname); +} + +int EditBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QTextEdit::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_HorizontalSlider__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 24, 23, 23, 23, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_HorizontalSlider__Data[] = { + "HorizontalSlider::Data\0\0onChange()\0" +}; + +const QMetaObject HorizontalSlider::Data::staticMetaObject = { + { &QSlider::staticMetaObject, qt_meta_stringdata_HorizontalSlider__Data, + qt_meta_data_HorizontalSlider__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &HorizontalSlider::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *HorizontalSlider::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *HorizontalSlider::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_HorizontalSlider__Data)) + return static_cast(const_cast< Data*>(this)); + return QSlider::qt_metacast(_clname); +} + +int HorizontalSlider::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QSlider::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_Label__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +static const char qt_meta_stringdata_Label__Data[] = { + "Label::Data\0" +}; + +const QMetaObject Label::Data::staticMetaObject = { + { &QLabel::staticMetaObject, qt_meta_stringdata_Label__Data, + qt_meta_data_Label__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &Label::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *Label::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *Label::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_Label__Data)) + return static_cast(const_cast< Data*>(this)); + return QLabel::qt_metacast(_clname); +} + +int Label::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QLabel::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +static const uint qt_meta_data_ListBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 3, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 15, 14, 14, 14, 0x0a, + 28, 14, 14, 14, 0x0a, + 44, 39, 14, 14, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_ListBox__Data[] = { + "ListBox::Data\0\0onActivate()\0onChange()\0" + "item\0onTick(QTreeWidgetItem*)\0" +}; + +const QMetaObject ListBox::Data::staticMetaObject = { + { &QTreeWidget::staticMetaObject, qt_meta_stringdata_ListBox__Data, + qt_meta_data_ListBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &ListBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *ListBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *ListBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_ListBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QTreeWidget::qt_metacast(_clname); +} + +int ListBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QTreeWidget::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + case 1: onChange(); break; + case 2: onTick((*reinterpret_cast< QTreeWidgetItem*(*)>(_a[1]))); break; + default: ; + } + _id -= 3; + } + return _id; +} +static const uint qt_meta_data_RadioBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 16, 15, 15, 15, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_RadioBox__Data[] = { + "RadioBox::Data\0\0onTick()\0" +}; + +const QMetaObject RadioBox::Data::staticMetaObject = { + { &QRadioButton::staticMetaObject, qt_meta_stringdata_RadioBox__Data, + qt_meta_data_RadioBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &RadioBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *RadioBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *RadioBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_RadioBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QRadioButton::qt_metacast(_clname); +} + +int RadioBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QRadioButton::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_TextBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 2, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 15, 14, 14, 14, 0x0a, + 28, 14, 14, 14, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_TextBox__Data[] = { + "TextBox::Data\0\0onActivate()\0onChange()\0" +}; + +const QMetaObject TextBox::Data::staticMetaObject = { + { &QLineEdit::staticMetaObject, qt_meta_stringdata_TextBox__Data, + qt_meta_data_TextBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &TextBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *TextBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *TextBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_TextBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QLineEdit::qt_metacast(_clname); +} + +int TextBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QLineEdit::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + case 1: onChange(); break; + default: ; + } + _id -= 2; + } + return _id; +} +static const uint qt_meta_data_VerticalSlider__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 22, 21, 21, 21, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_VerticalSlider__Data[] = { + "VerticalSlider::Data\0\0onChange()\0" +}; + +const QMetaObject VerticalSlider::Data::staticMetaObject = { + { &QSlider::staticMetaObject, qt_meta_stringdata_VerticalSlider__Data, + qt_meta_data_VerticalSlider__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &VerticalSlider::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *VerticalSlider::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *VerticalSlider::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_VerticalSlider__Data)) + return static_cast(const_cast< Data*>(this)); + return QSlider::qt_metacast(_clname); +} + +int VerticalSlider::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QSlider::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_OS__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +static const char qt_meta_stringdata_OS__Data[] = { + "OS::Data\0" +}; + +const QMetaObject OS::Data::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_OS__Data, + qt_meta_data_OS__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &OS::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *OS::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *OS::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_OS__Data)) + return static_cast(const_cast< Data*>(this)); + return QObject::qt_metacast(_clname); +} + +int OS::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +QT_END_MOC_NAMESPACE diff --git a/phoenix/qt/qt.moc.hpp b/phoenix/qt/qt.moc.hpp new file mode 100755 index 00000000..8beaed5e --- /dev/null +++ b/phoenix/qt/qt.moc.hpp @@ -0,0 +1,311 @@ +struct Object::Data { +public: + Object &self; + bool locked; + + Data(Object &self) : self(self) { + locked = false; + } +}; + +struct Font::Data : public QFont { +public: + Font &self; + + Data(Font &self) : self(self) { + } +}; + +struct Menu::Data : public QMenu { +public: + Menu &self; + Window *parent; + + Data(Menu &self) : self(self), parent(0) { + } +}; + +struct MenuSeparator::Data { +public: + MenuSeparator &self; + QAction *action; + + Data(MenuSeparator &self) : self(self) { + } +}; + +struct MenuItem::Data : public QAction { + Q_OBJECT + +public: + MenuItem &self; + + Data(MenuItem &self) : self(self), QAction(0) { + } + +public slots: + void onTick() { + if(self.onTick) self.onTick(); + } +}; + +struct MenuCheckItem::Data : public QAction { + Q_OBJECT + +public: + MenuCheckItem &self; + + Data(MenuCheckItem &self) : self(self), QAction(0) { + } + +public slots: + void onTick() { + if(self.onTick) self.onTick(); + } +}; + +struct MenuRadioItem::Data : public QAction { + Q_OBJECT + +public: + MenuRadioItem &self; + Menu *parent; + QActionGroup *actionGroup; + + Data(MenuRadioItem &self) : self(self), QAction(0) { + } + +public slots: + void onTick() { + if(self.object->locked == false && self.onTick && self.checked()) self.onTick(); + } +}; + +struct Widget::Data { +public: + Widget &self; + QWidget *widget; + + Data(Widget &self) : self(self) { + } +}; + +struct Window::Data : public QWidget { + Q_OBJECT + +public: + Window &self; + QFont *defaultFont; + QVBoxLayout *layout; + QMenuBar *menuBar; + QWidget *container; + QStatusBar *statusBar; + + void closeEvent(QCloseEvent *event) { + if(self.onClose) { + bool result = self.onClose(); + if(result == false) event->ignore(); + } + } + + Data(Window &self) : self(self) { + } +}; + +struct Button::Data : public QPushButton { + Q_OBJECT + +public: + Button &self; + + Data(Button &self) : self(self) { + } + +public slots: + void onTick() { + if(self.onTick) self.onTick(); + } +}; + +struct Canvas::Data : public QWidget { + Q_OBJECT + +public: + Canvas &self; + QImage *image; + void paintEvent(QPaintEvent*); + + Data(Canvas &self) : self(self) { + } +}; + +struct CheckBox::Data : public QCheckBox { + Q_OBJECT + +public: + CheckBox &self; + + Data(CheckBox &self) : self(self) { + } + +public slots: + void onTick() { + if(self.onTick) self.onTick(); + } +}; + +struct ComboBox::Data : public QComboBox { + Q_OBJECT + +public: + ComboBox &self; + + Data(ComboBox &self) : self(self) { + } + +public slots: + void onChange() { + if(self.object->locked == false && self.onChange) self.onChange(); + } +}; + +struct EditBox::Data : public QTextEdit { + Q_OBJECT + +public: + EditBox &self; + + Data(EditBox &self) : self(self) { + } + +public slots: + void onChange() { + if(self.onChange) self.onChange(); + } +}; + +struct HorizontalSlider::Data : public QSlider { + Q_OBJECT + +public: + HorizontalSlider &self; + + Data(HorizontalSlider &self) : self(self), QSlider(Qt::Horizontal) { + } + +public slots: + void onChange() { + if(self.onChange) self.onChange(); + } +}; + +struct Label::Data : public QLabel { + Q_OBJECT + +public: + Label &self; + + Data(Label &self) : self(self) { + } +}; + +struct ListBox::Data : public QTreeWidget { + Q_OBJECT + +public: + ListBox &self; + bool checkable; + + Data(ListBox &self) : self(self) { + checkable = false; + } + +public slots: + void onActivate() { + if(self.object->locked == false && self.onActivate) self.onActivate(); + } + + void onChange() { + if(self.object->locked == false && self.onChange) self.onChange(); + } + + void onTick(QTreeWidgetItem *item) { + if(self.object->locked == false && self.onTick) self.onTick(item->data(0, Qt::UserRole).toUInt()); + } +}; + +struct ProgressBar::Data : public QProgressBar { +public: + ProgressBar &self; + + Data(ProgressBar &self) : self(self) { + } +}; + +struct RadioBox::Data : public QRadioButton { + Q_OBJECT + +public: + RadioBox &self; + Window *parent; + QButtonGroup *buttonGroup; + + Data(RadioBox &self) : self(self) { + } + +public slots: + void onTick() { + if(self.onTick && self.checked()) self.onTick(); + } +}; + +struct TextBox::Data : public QLineEdit { + Q_OBJECT + +public: + TextBox &self; + + Data(TextBox &self) : self(self) { + } + +public slots: + void onActivate() { + if(self.onActivate) self.onActivate(); + } + + void onChange() { + if(self.onChange) self.onChange(); + } +}; + +struct VerticalSlider::Data : public QSlider { + Q_OBJECT + +public: + VerticalSlider &self; + + Data(VerticalSlider &self) : self(self), QSlider(Qt::Vertical) { + } + +public slots: + void onChange() { + if(self.onChange) self.onChange(); + } +}; + +struct Viewport::Data : public QWidget { +public: + Viewport &self; + + Data(Viewport &self) : self(self) { + } +}; + +struct OS::Data : public QObject { + Q_OBJECT + +public: + QApplication *application; + +public slots: +}; diff --git a/phoenix/qt/radiobox.cpp b/phoenix/qt/radiobox.cpp new file mode 100755 index 00000000..b6f3a846 --- /dev/null +++ b/phoenix/qt/radiobox.cpp @@ -0,0 +1,37 @@ +void RadioBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + radioBox->parent = &parent; + radioBox->buttonGroup = new QButtonGroup; + radioBox->buttonGroup->addButton(radioBox); + radioBox->setParent(radioBox->parent->window->container); + radioBox->setGeometry(x, y, width, height); + radioBox->setText(QString::fromUtf8(text)); + radioBox->setChecked(true); + if(parent.window->defaultFont) radioBox->setFont(*parent.window->defaultFont); + radioBox->show(); + radioBox->connect(radioBox, SIGNAL(toggled(bool)), SLOT(onTick())); +} + +void RadioBox::create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + radioBox->parent = parent.radioBox->parent; + radioBox->buttonGroup = parent.radioBox->buttonGroup; + radioBox->buttonGroup->addButton(radioBox); + radioBox->setParent(radioBox->parent->window->container); + radioBox->setGeometry(x, y, width, height); + radioBox->setText(QString::fromUtf8(text)); + if(radioBox->parent->window->defaultFont) radioBox->setFont(*radioBox->parent->window->defaultFont); + radioBox->show(); + radioBox->connect(radioBox, SIGNAL(toggled(bool)), SLOT(onTick())); +} + +bool RadioBox::checked() { + return radioBox->isChecked(); +} + +void RadioBox::setChecked() { + radioBox->setChecked(true); +} + +RadioBox::RadioBox() { + radioBox = new RadioBox::Data(*this); + widget->widget = radioBox; +} diff --git a/phoenix/qt/textbox.cpp b/phoenix/qt/textbox.cpp new file mode 100755 index 00000000..ffe2c82f --- /dev/null +++ b/phoenix/qt/textbox.cpp @@ -0,0 +1,26 @@ +void TextBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + textBox->setParent(parent.window->container); + textBox->setGeometry(x, y, width, height); + textBox->setText(QString::fromUtf8(text)); + if(parent.window->defaultFont) textBox->setFont(*parent.window->defaultFont); + textBox->show(); + textBox->connect(textBox, SIGNAL(returnPressed()), SLOT(onActivate())); + textBox->connect(textBox, SIGNAL(textEdited(const QString&)), SLOT(onChange())); +} + +void TextBox::setEditable(bool editable) { + textBox->setReadOnly(editable == false); +} + +string TextBox::text() { + return textBox->text().toUtf8().constData(); +} + +void TextBox::setText(const string &text) { + textBox->setText(QString::fromUtf8(text)); +} + +TextBox::TextBox() { + textBox = new TextBox::Data(*this); + widget->widget = textBox; +} diff --git a/phoenix/qt/verticalslider.cpp b/phoenix/qt/verticalslider.cpp new file mode 100755 index 00000000..98d9f22a --- /dev/null +++ b/phoenix/qt/verticalslider.cpp @@ -0,0 +1,24 @@ +void VerticalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + length += (length == 0); + verticalSlider->setParent(parent.window->container); + verticalSlider->setGeometry(x, y, width, height); + verticalSlider->setInvertedAppearance(true); + verticalSlider->setInvertedControls(true); + verticalSlider->setRange(0, length - 1); + verticalSlider->setPageStep(length >> 3); + verticalSlider->connect(verticalSlider, SIGNAL(valueChanged(int)), SLOT(onChange())); + verticalSlider->show(); +} + +unsigned VerticalSlider::position() { + return verticalSlider->value(); +} + +void VerticalSlider::setPosition(unsigned position) { + verticalSlider->setValue(position); +} + +VerticalSlider::VerticalSlider() { + verticalSlider = new VerticalSlider::Data(*this); + widget->widget = verticalSlider; +} diff --git a/phoenix/qt/viewport.cpp b/phoenix/qt/viewport.cpp new file mode 100755 index 00000000..7c4b0f19 --- /dev/null +++ b/phoenix/qt/viewport.cpp @@ -0,0 +1,16 @@ +void Viewport::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + viewport->setParent(parent.window->container); + viewport->setGeometry(x, y, width, height); + viewport->setAttribute(Qt::WA_PaintOnScreen, true); + viewport->setStyleSheet("background: #000000"); + viewport->show(); +} + +uintptr_t Viewport::handle() { + return (uintptr_t)viewport->winId(); +} + +Viewport::Viewport() { + viewport = new Viewport::Data(*this); + widget->widget = viewport; +} diff --git a/phoenix/qt/widget.cpp b/phoenix/qt/widget.cpp new file mode 100755 index 00000000..9510607a --- /dev/null +++ b/phoenix/qt/widget.cpp @@ -0,0 +1,35 @@ +void Widget::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + widget->widget->setGeometry(x, y, width, height); +} + +void Widget::setFont(Font &font) { + widget->widget->setFont(*font.font); +} + +bool Widget::visible() { + return widget->widget->isVisible(); +} + +void Widget::setVisible(bool visible) { + widget->widget->setVisible(visible); +} + +bool Widget::enabled() { + return widget->widget->isEnabled(); +} + +void Widget::setEnabled(bool enabled) { + widget->widget->setEnabled(enabled); +} + +bool Widget::focused() { + return widget->widget->hasFocus(); +} + +void Widget::setFocused() { + widget->widget->setFocus(Qt::OtherFocusReason); +} + +Widget::Widget() { + widget = new Widget::Data(*this); +} diff --git a/phoenix/qt/window.cpp b/phoenix/qt/window.cpp new file mode 100755 index 00000000..ef209ab0 --- /dev/null +++ b/phoenix/qt/window.cpp @@ -0,0 +1,77 @@ +void Window::create(unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + window->setWindowTitle(QString::fromUtf8(text)); + window->move(x, y); + + window->layout = new QVBoxLayout(window); + window->layout->setMargin(0); + window->layout->setSpacing(0); + window->layout->setSizeConstraint(QLayout::SetFixedSize); + window->setLayout(window->layout); + + window->menuBar = new QMenuBar(window); + window->menuBar->setVisible(false); + window->layout->addWidget(window->menuBar); + + window->container = new QWidget(window); + window->container->setFixedSize(width, height); + window->container->setVisible(true); + window->layout->addWidget(window->container); + + window->statusBar = new QStatusBar(window); + window->statusBar->setSizeGripEnabled(false); + window->statusBar->setVisible(false); + window->layout->addWidget(window->statusBar); +} + +Geometry Window::geometry() { + return Geometry(window->x(), window->y(), window->container->width(), window->container->height()); +} + +void Window::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + window->container->setFixedSize(width, height); + window->move(x, y); +} + +void Window::setDefaultFont(Font &font) { + window->defaultFont = font.font; + window->menuBar->setFont(*font.font); +} + +void Window::setFont(Font &font) { + window->statusBar->setFont(*font.font); +} + +void Window::setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue) { + QPalette palette; + palette.setColor(QPalette::Window, QColor(red, green, blue)); + window->setPalette(palette); + window->setAutoFillBackground(true); +} + +void Window::setTitle(const string &text) { + window->setWindowTitle(QString::fromUtf8(text)); +} + +void Window::setStatusText(const string &text) { + window->statusBar->showMessage(QString::fromUtf8(text), 0); +} + +void Window::setMenuVisible(bool visible) { + if(visible) window->menuBar->show(); + else window->menuBar->hide(); +} + +void Window::setStatusVisible(bool visible) { + if(visible) window->statusBar->show(); + else window->statusBar->hide(); +} + +bool Window::focused() { + return window->isActiveWindow() && !window->isMinimized(); +} + +Window::Window() { + window = new Window::Data(*this); + window->defaultFont = 0; + widget->widget = window; +} diff --git a/phoenix/windows/button.cpp b/phoenix/windows/button.cpp new file mode 100755 index 00000000..267b446e --- /dev/null +++ b/phoenix/windows/button.cpp @@ -0,0 +1,10 @@ +void Button::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindow( + L"BUTTON", utf16_t(text), + WS_CHILD | WS_TABSTOP | WS_VISIBLE, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); +} diff --git a/phoenix/windows/canvas.cpp b/phoenix/windows/canvas.cpp new file mode 100755 index 00000000..783d7042 --- /dev/null +++ b/phoenix/windows/canvas.cpp @@ -0,0 +1,60 @@ +void Canvas::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + canvas->buffer = new uint32_t[width * height](); + canvas->pitch = width * sizeof(uint32_t); + canvas->width = width; + canvas->height = height; + memset(&canvas->bmi, 0, sizeof(BITMAPINFO)); + canvas->bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + canvas->bmi.bmiHeader.biPlanes = 1; + canvas->bmi.bmiHeader.biBitCount = 32; + canvas->bmi.bmiHeader.biCompression = BI_RGB; + canvas->bmi.bmiHeader.biWidth = width; + canvas->bmi.bmiHeader.biHeight = -height; //GDI stores bitmaps upside down; negative height flips bitmap + canvas->bmi.bmiHeader.biSizeImage = canvas->pitch * canvas->height; + + widget->window = CreateWindow( + L"phoenix_canvas", L"", + WS_CHILD | WS_VISIBLE, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); +} + +uint32_t* Canvas::buffer() { + return canvas->buffer; +} + +void Canvas::redraw() { + PAINTSTRUCT ps; + BeginPaint(widget->window, &ps); + SetDIBitsToDevice(ps.hdc, 0, 0, canvas->width, canvas->height, 0, 0, 0, canvas->height, (void*)canvas->buffer, &canvas->bmi, DIB_RGB_COLORS); + EndPaint(widget->window, &ps); + InvalidateRect(widget->window, 0, false); +} + +Canvas::Canvas() { + canvas = new Canvas::Data; + canvas->buffer = 0; +} + +Canvas::~Canvas() { + delete[] canvas->buffer; + delete canvas; +} + +static LRESULT CALLBACK Canvas_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch(msg) { + case WM_PAINT: { + Object *object_ptr = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + Canvas &canvas = (Canvas&)*object_ptr; + canvas.redraw(); + } + } + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} diff --git a/phoenix/windows/checkbox.cpp b/phoenix/windows/checkbox.cpp new file mode 100755 index 00000000..035deb56 --- /dev/null +++ b/phoenix/windows/checkbox.cpp @@ -0,0 +1,18 @@ +void CheckBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindow( + L"BUTTON", utf16_t(text), + WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_CHECKBOX, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); +} + +bool CheckBox::checked() { + return SendMessage(widget->window, BM_GETCHECK, 0, 0); +} + +void CheckBox::setChecked(bool checked) { + SendMessage(widget->window, BM_SETCHECK, (WPARAM)checked, 0); +} diff --git a/phoenix/windows/combobox.cpp b/phoenix/windows/combobox.cpp new file mode 100755 index 00000000..4965a529 --- /dev/null +++ b/phoenix/windows/combobox.cpp @@ -0,0 +1,46 @@ +void ComboBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindowEx( + 0, L"COMBOBOX", L"", + WS_CHILD | WS_TABSTOP | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_HASSTRINGS, + x, y, width, 200, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); + + //CreateWindow height parameter is the height of the expanded list box; + //need additional code to override default ComboBox control height + RECT rc; + GetWindowRect(widget->window, &rc); + unsigned adjustedHeight = height - ((rc.bottom - rc.top) - SendMessage(widget->window, CB_GETITEMHEIGHT, (WPARAM)-1, 0)); + SendMessage(widget->window, CB_SETITEMHEIGHT, (WPARAM)-1, adjustedHeight); + + if(*text) { + lstring list; + list.split("\n", text); + foreach(item, list) addItem(item); + } +} + +void ComboBox::reset() { + SendMessage(widget->window, CB_RESETCONTENT, 0, 0); +} + +void ComboBox::addItem(const string &text) { + SendMessage(widget->window, CB_ADDSTRING, 0, (LPARAM)(wchar_t*)utf16_t(text)); + if(SendMessage(widget->window, CB_GETCOUNT, 0, 0) == 1) setSelection(0); +} + +unsigned ComboBox::selection() { + return SendMessage(widget->window, CB_GETCURSEL, 0, 0); +} + +void ComboBox::setSelection(unsigned row) { + SendMessage(widget->window, CB_SETCURSEL, comboBox->selection = row, 0); +} + +ComboBox::ComboBox() { + comboBox = new ComboBox::Data; + comboBox->selection = 0; +} diff --git a/phoenix/windows/editbox.cpp b/phoenix/windows/editbox.cpp new file mode 100755 index 00000000..2d165b3a --- /dev/null +++ b/phoenix/windows/editbox.cpp @@ -0,0 +1,53 @@ +void EditBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_VISIBLE | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN | + (editBox->wordWrap == false ? ES_AUTOHSCROLL : 0), + editBox->x = x, editBox->y = y, editBox->width = width, editBox->height = height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + setText(text); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); +} + +string EditBox::getText() { + unsigned length = GetWindowTextLength(widget->window); + wchar_t buffer[length + 1]; + GetWindowText(widget->window, buffer, length + 1); + buffer[length] = 0; + string text = utf8_t(buffer); + text.replace("\r", ""); + return text; +} + +void EditBox::setText(const string &text) { + string output = text; + output.replace("\r", ""); + output.replace("\n", "\r\n"); + object->locked = true; + SetWindowText(widget->window, utf16_t(output)); + object->locked = false; +} + +void EditBox::setEditable(bool editable) { + SendMessage(widget->window, EM_SETREADONLY, editable == false, (LPARAM)0); +} + +void EditBox::setWordWrap(bool wordWrap) { + editBox->wordWrap = wordWrap; + if(widget->window == 0) return; + + //ES_AUTOSCROLL options cannot be changed after control has been created; + //so destroy the control and recreate it with desired options + HWND hparent = GetParent(widget->window); + Window *parent = (Window*)GetWindowLongPtr(hparent, GWLP_USERDATA); + string text = getText(); + DestroyWindow(widget->window); + create(*parent, editBox->x, editBox->y, editBox->width, editBox->height, text); +} + +EditBox::EditBox() { + editBox = new EditBox::Data; + editBox->wordWrap = true; +} diff --git a/phoenix/windows/font.cpp b/phoenix/windows/font.cpp new file mode 100755 index 00000000..712b928f --- /dev/null +++ b/phoenix/windows/font.cpp @@ -0,0 +1,26 @@ +static HFONT Font_createFont(const string &name, unsigned size, bool bold, bool italic) { + return CreateFont( + -(size * 96.0 / 72.0 + 0.5), + 0, 0, 0, bold == false ? FW_NORMAL : FW_BOLD, italic, 0, 0, 0, 0, 0, 0, 0, + utf16_t(name) + ); +} + +bool Font::create(const string &name, unsigned size, Font::Style style) { + font->font = Font_createFont( + name, size, + (style & Font::Style::Bold) == Font::Style::Bold, + (style & Font::Style::Italic) == Font::Style::Italic + ); + return font->font; +} + +Font::Font() { + font = new Font::Data; + font->font = 0; +} + +Font::~Font() { + if(font->font) DeleteObject(font->font); + delete font; +} diff --git a/phoenix/windows/horizontalslider.cpp b/phoenix/windows/horizontalslider.cpp new file mode 100755 index 00000000..9f5939bd --- /dev/null +++ b/phoenix/windows/horizontalslider.cpp @@ -0,0 +1,25 @@ +void HorizontalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + length += (length == 0); + widget->window = CreateWindow( + TRACKBAR_CLASS, L"", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | TBS_NOTICKS | TBS_BOTH | TBS_HORZ, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); + SendMessage(widget->window, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); + setPosition(0); +} + +unsigned HorizontalSlider::position() { + return SendMessage(widget->window, TBM_GETPOS, 0, 0); +} + +void HorizontalSlider::setPosition(unsigned position) { + SendMessage(widget->window, TBM_SETPOS, (WPARAM)true, (LPARAM)(horizontalSlider->position = position)); +} + +HorizontalSlider::HorizontalSlider() { + horizontalSlider = new HorizontalSlider::Data; +} diff --git a/phoenix/windows/label.cpp b/phoenix/windows/label.cpp new file mode 100755 index 00000000..e20c7cf0 --- /dev/null +++ b/phoenix/windows/label.cpp @@ -0,0 +1,51 @@ +void Label::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindow( + L"phoenix_label", L"", + WS_CHILD | WS_VISIBLE, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); + setText(text); +} + +void Label::setText(const string &text) { + SetWindowText(widget->window, utf16_t(text)); + InvalidateRect(widget->window, 0, false); +} + +//all of this for want of a STATIC SS_VCENTER flag ... +LRESULT CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Window *window_ptr = (Window*)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA); + if(!window_ptr) return DefWindowProc(hwnd, msg, wparam, lparam); + Label *label_ptr = (Label*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!label_ptr) return DefWindowProc(hwnd, msg, wparam, lparam); + Window &window = *window_ptr; + Label &label = *label_ptr; + + switch(msg) { + case WM_PAINT: { + PAINTSTRUCT ps; + RECT rc; + BeginPaint(hwnd, &ps); + GetClientRect(hwnd, &rc); + FillRect(ps.hdc, &rc, window.window->brush ? window.window->brush : GetSysColorBrush(COLOR_3DFACE)); + SetBkColor(ps.hdc, window.window->brush ? window.window->brushColor : GetSysColor(COLOR_3DFACE)); + SelectObject(ps.hdc, label.widget->font); + unsigned length = GetWindowTextLength(hwnd); + wchar_t text[length + 1]; + GetWindowText(hwnd, text, length + 1); + text[length] = 0; + DrawText(ps.hdc, text, -1, &rc, DT_CALCRECT | DT_END_ELLIPSIS); + unsigned height = rc.bottom; + GetClientRect(hwnd, &rc); + rc.top = (rc.bottom - height) / 2; + rc.bottom = rc.top + height; + DrawText(ps.hdc, text, -1, &rc, DT_LEFT | DT_END_ELLIPSIS); + EndPaint(hwnd, &ps); + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} diff --git a/phoenix/windows/listbox.cpp b/phoenix/windows/listbox.cpp new file mode 100755 index 00000000..86d82255 --- /dev/null +++ b/phoenix/windows/listbox.cpp @@ -0,0 +1,114 @@ +void ListBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindowEx( + WS_EX_CLIENTEDGE, WC_LISTVIEW, L"", + WS_CHILD | WS_TABSTOP | WS_VISIBLE | + LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_NOCOLUMNHEADER, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); + ListView_SetExtendedListViewStyle(widget->window, LVS_EX_FULLROWSELECT); + + lstring list; + list.split("\t", text); + listBox->columns = list.size(); + for(unsigned i = 0; i < list.size(); i++) { + LVCOLUMN column; + column.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM; + column.fmt = LVCFMT_LEFT; + column.iSubItem = list.size(); + utf16_t text(list[i]); + column.pszText = text; + ListView_InsertColumn(widget->window, i, &column); + } + resizeColumnsToContent(); +} + +void ListBox::setHeaderVisible(bool headerVisible) { + SetWindowLong( + widget->window, + GWL_STYLE, + (GetWindowLong(widget->window, GWL_STYLE) & ~LVS_NOCOLUMNHEADER) | + (headerVisible == false ? LVS_NOCOLUMNHEADER : 0) + ); +} + +void ListBox::setCheckable(bool checkable) { + ListView_SetExtendedListViewStyle(widget->window, LVS_EX_FULLROWSELECT | (checkable ? LVS_EX_CHECKBOXES : 0)); +} + +void ListBox::reset() { + ListView_DeleteAllItems(widget->window); +} + +void ListBox::resizeColumnsToContent() { + for(unsigned i = 0; i < listBox->columns; i++) { + ListView_SetColumnWidth(widget->window, i, LVSCW_AUTOSIZE_USEHEADER); + } +} + +void ListBox::addItem(const string &text) { + lstring list; + list.split("\t", text); + LVITEM item; + unsigned row = ListView_GetItemCount(widget->window); + item.mask = LVIF_TEXT; + item.iItem = row; + item.iSubItem = 0; + utf16_t wtext(list[0]); + item.pszText = wtext; + object->locked = true; + ListView_InsertItem(widget->window, &item); + object->locked = false; + for(unsigned i = 1; i < list.size(); i++) { + utf16_t wtext(list[i]); + ListView_SetItemText(widget->window, row, i, wtext); + } + + //workaround: when there is only one column, the horizontal scrollbar will always appear without this + if(listBox->columns == 1) ListView_SetColumnWidth(widget->window, 0, LVSCW_AUTOSIZE_USEHEADER); +} + +void ListBox::setItem(unsigned row, const string &text) { + lstring list; + list.split("\t", text); + for(unsigned i = 0; i < list.size(); i++) { + utf16_t wtext(list[i]); + ListView_SetItemText(widget->window, row, i, wtext); + } + + //workaround: when there is only one column, the horizontal scrollbar will always appear without this + if(listBox->columns == 1) ListView_SetColumnWidth(widget->window, 0, LVSCW_AUTOSIZE_USEHEADER); +} + +optional ListBox::selection() { + unsigned count = ListView_GetItemCount(widget->window); + for(unsigned i = 0; i < count; i++) { + if(ListView_GetItemState(widget->window, i, LVIS_SELECTED)) return { true, i }; + } + return { false, 0 }; +} + +void ListBox::setSelection(unsigned row) { + unsigned count = ListView_GetItemCount(widget->window); + for(unsigned i = 0; i < count; i++) { + ListView_SetItemState(widget->window, i, LVIS_FOCUSED, (i == row ? LVIS_FOCUSED : 0)); + ListView_SetItemState(widget->window, i, LVIS_SELECTED, (i == row ? LVIS_SELECTED : 0)); + } +} + +bool ListBox::checked(unsigned row) { + return ListView_GetCheckState(widget->window, row); +} + +void ListBox::setChecked(unsigned row, bool checked) { + object->locked = true; + ListView_SetCheckState(widget->window, row, checked); + object->locked = false; +} + +ListBox::ListBox() { + listBox = new ListBox::Data; + listBox->lostFocus = false; +} diff --git a/phoenix/windows/menu.cpp b/phoenix/windows/menu.cpp new file mode 100755 index 00000000..789ed382 --- /dev/null +++ b/phoenix/windows/menu.cpp @@ -0,0 +1,144 @@ +Action::Action() { + OS::os->objects.append(this); + action = new Action::Data; +} + +void Menu::create(Window &parent, const string &text) { + action->parentMenu = parent.window->menu; + action->menu = CreatePopupMenu(); + AppendMenu(parent.window->menu, MF_STRING | MF_POPUP, (UINT_PTR)action->menu, utf16_t(text)); +} + +void Menu::create(Menu &parent, const string &text) { + action->parentMenu = parent.action->menu; + action->menu = CreatePopupMenu(); + AppendMenu(parent.action->menu, MF_STRING | MF_POPUP, (UINT_PTR)action->menu, utf16_t(text)); +} + +bool Menu::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parentMenu, (UINT_PTR)action->menu, false, &info); + return (info.fState & MFS_GRAYED) == 0; +} + +void Menu::setEnabled(bool enabled) { + EnableMenuItem(action->parentMenu, (UINT_PTR)action->menu, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); +} + +void MenuSeparator::create(Menu &parent) { + action->parent = &parent; + AppendMenu(parent.action->menu, MF_SEPARATOR, object->id, L""); +} + +bool MenuSeparator::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return (info.fState & MFS_GRAYED) == 0; +} + +void MenuSeparator::setEnabled(bool enabled) { + EnableMenuItem(action->parent->action->menu, object->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); +} + +void MenuItem::create(Menu &parent, const string &text) { + action->parent = &parent; + AppendMenu(parent.action->menu, MF_STRING, object->id, utf16_t(text)); +} + +bool MenuItem::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return (info.fState & MFS_GRAYED) == 0; +} + +void MenuItem::setEnabled(bool enabled) { + EnableMenuItem(action->parent->action->menu, object->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); +} + +void MenuCheckItem::create(Menu &parent, const string &text) { + action->parent = &parent; + AppendMenu(parent.action->menu, MF_STRING, object->id, utf16_t(text)); +} + +bool MenuCheckItem::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return (info.fState & MFS_GRAYED) == 0; +} + +void MenuCheckItem::setEnabled(bool enabled) { + EnableMenuItem(action->parent->action->menu, object->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); +} + +bool MenuCheckItem::checked() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return info.fState & MFS_CHECKED; +} + +void MenuCheckItem::setChecked(bool checked) { + CheckMenuItem(action->parent->action->menu, object->id, checked ? MF_CHECKED : MF_UNCHECKED); +} + +void MenuRadioItem::create(Menu &parent, const string &text) { + action->parent = &parent; + action->radioParent = this; + action->items.append(this); + AppendMenu(parent.action->menu, MF_STRING, object->id, utf16_t(text)); + setChecked(); +} + +void MenuRadioItem::create(MenuRadioItem &parent, const string &text) { + action->parent = parent.action->parent; + action->radioParent = parent.action->radioParent; + action->radioParent->action->items.append(this); + AppendMenu(action->parent->action->menu, MF_STRING, object->id, utf16_t(text)); +} + +bool MenuRadioItem::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return (info.fState & MFS_GRAYED) == 0; +} + +void MenuRadioItem::setEnabled(bool enabled) { + EnableMenuItem(action->parent->action->menu, object->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); +} + +bool MenuRadioItem::checked() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return info.fState & MFS_CHECKED; +} + +void MenuRadioItem::setChecked() { + MenuRadioItem *parent = action->radioParent; + foreach(item, parent->action->items) { + CheckMenuRadioItem( + action->parent->action->menu, + item->object->id, item->object->id, item->object->id + (item != this), + MF_BYCOMMAND + ); + } +} diff --git a/phoenix/windows/messagewindow.cpp b/phoenix/windows/messagewindow.cpp new file mode 100755 index 00000000..b2439dc3 --- /dev/null +++ b/phoenix/windows/messagewindow.cpp @@ -0,0 +1,41 @@ +static MessageWindow::Response MessageWindow_response(MessageWindow::Buttons buttons, UINT response) { + if(response == IDOK) return MessageWindow::Response::Ok; + if(response == IDCANCEL) return MessageWindow::Response::Cancel; + if(response == IDYES) return MessageWindow::Response::Yes; + if(response == IDNO) return MessageWindow::Response::No; + if(buttons == MessageWindow::Buttons::OkCancel) return MessageWindow::Response::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) return MessageWindow::Response::No; + return MessageWindow::Response::Ok; +} + +MessageWindow::Response MessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONINFORMATION; + if(buttons == Buttons::Ok) flags |= MB_OK; + if(buttons == Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::None ? parent.widget->window : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response MessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONQUESTION; + if(buttons == Buttons::Ok) flags |= MB_OK; + if(buttons == Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::None ? parent.widget->window : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response MessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONWARNING; + if(buttons == Buttons::Ok) flags |= MB_OK; + if(buttons == Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::None ? parent.widget->window : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response MessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONERROR; + if(buttons == Buttons::Ok) flags |= MB_OK; + if(buttons == Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::None ? parent.widget->window : 0, utf16_t(text), L"", flags)); +} diff --git a/phoenix/windows/object.cpp b/phoenix/windows/object.cpp new file mode 100755 index 00000000..2a5b8785 --- /dev/null +++ b/phoenix/windows/object.cpp @@ -0,0 +1,87 @@ +struct Object::Data { + unsigned id; + bool locked; +}; + +struct Font::Data { + HFONT font; +}; + +struct Action::Data { + Menu *parent; + HMENU parentMenu; + HMENU menu; + MenuRadioItem *radioParent; + array items; +}; + +struct Widget::Data { + HWND window; + HFONT font; +}; + +struct Window::Data { + HFONT defaultFont; + HBRUSH brush; + COLORREF brushColor; + HMENU menu; + HWND status; + unsigned width; + unsigned height; +}; + +struct Canvas::Data { + uint32_t *buffer; + BITMAPINFO bmi; + unsigned pitch; + unsigned width; + unsigned height; +}; + +struct ComboBox::Data { + unsigned selection; +}; + +struct EditBox::Data { + bool wordWrap; + unsigned x; + unsigned y; + unsigned width; + unsigned height; +}; + +struct HorizontalSlider::Data { + unsigned position; +}; + +struct ListBox::Data { + unsigned columns; + bool lostFocus; +}; + +struct RadioBox::Data { + Window *parentWindow; + RadioBox *parent; + array items; +}; + +struct VerticalSlider::Data { + unsigned position; +}; + +struct OS::Data { + nall::array objects; + HFONT proportionalFont; + HFONT monospaceFont; +}; + +void Object::unused() { +} + +Object::Object() { + OS::initialize(); + static unsigned guid = 100; + object = new Object::Data; + object->id = guid++; + object->locked = false; +} diff --git a/phoenix/windows/phoenix.Manifest b/phoenix/windows/phoenix.Manifest new file mode 100755 index 00000000..71013ffe --- /dev/null +++ b/phoenix/windows/phoenix.Manifest @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/phoenix/windows/phoenix.rc b/phoenix/windows/phoenix.rc new file mode 100755 index 00000000..89fb8dc2 --- /dev/null +++ b/phoenix/windows/phoenix.rc @@ -0,0 +1 @@ +1 24 "phoenix.Manifest" diff --git a/phoenix/windows/progressbar.cpp b/phoenix/windows/progressbar.cpp new file mode 100755 index 00000000..230f12b4 --- /dev/null +++ b/phoenix/windows/progressbar.cpp @@ -0,0 +1,18 @@ +void ProgressBar::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + widget->window = CreateWindow( + PROGRESS_CLASS, L"", + WS_CHILD | WS_VISIBLE | PBS_SMOOTH, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SendMessage(widget->window, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); + SendMessage(widget->window, PBM_SETSTEP, MAKEWPARAM(1, 0), 0); +} + +unsigned ProgressBar::position() { + return SendMessage(widget->window, PBM_GETPOS, 0, 0); +} + +void ProgressBar::setPosition(unsigned position) { + SendMessage(widget->window, PBM_SETPOS, (WPARAM)position, 0); +} diff --git a/phoenix/windows/radiobox.cpp b/phoenix/windows/radiobox.cpp new file mode 100755 index 00000000..9c49297b --- /dev/null +++ b/phoenix/windows/radiobox.cpp @@ -0,0 +1,42 @@ +void RadioBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + radioBox->parentWindow = &parent; + radioBox->parent = this; + radioBox->parent->radioBox->items.append(this); + widget->window = CreateWindow( + L"BUTTON", utf16_t(text), + WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_RADIOBUTTON, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); + setChecked(); +} + +void RadioBox::create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + radioBox->parentWindow = parent.radioBox->parentWindow; + radioBox->parent = parent.radioBox->parent; + radioBox->parent->radioBox->items.append(this); + widget->window = CreateWindow( + L"BUTTON", utf16_t(text), + WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_RADIOBUTTON, + x, y, width, height, + GetParent(radioBox->parent->widget->window), (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(radioBox->parentWindow->window->defaultFont ? radioBox->parentWindow->window->defaultFont : OS::os->proportionalFont), 0); +} + +bool RadioBox::checked() { + return SendMessage(widget->window, BM_GETCHECK, 0, 0); +} + +void RadioBox::setChecked() { + foreach(item, radioBox->parent->radioBox->items) { + SendMessage(item->widget->window, BM_SETCHECK, (WPARAM)(item == this), 0); + } +} + +RadioBox::RadioBox() { + radioBox = new RadioBox::Data; +} diff --git a/phoenix/windows/textbox.cpp b/phoenix/windows/textbox.cpp new file mode 100755 index 00000000..e63f2dac --- /dev/null +++ b/phoenix/windows/textbox.cpp @@ -0,0 +1,28 @@ +void TextBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", utf16_t(text), + WS_CHILD | WS_TABSTOP | WS_VISIBLE | ES_AUTOHSCROLL | ES_AUTOVSCROLL, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); +} + +string TextBox::text() { + unsigned length = GetWindowTextLength(widget->window); + wchar_t text[length + 1]; + GetWindowText(widget->window, text, length + 1); + text[length] = 0; + return utf8_t(text); +} + +void TextBox::setText(const string &text) { + object->locked = true; + SetWindowText(widget->window, utf16_t(text)); + object->locked = false; +} + +void TextBox::setEditable(bool editable) { + SendMessage(widget->window, EM_SETREADONLY, editable == false, 0); +} diff --git a/phoenix/windows/verticalslider.cpp b/phoenix/windows/verticalslider.cpp new file mode 100755 index 00000000..43024432 --- /dev/null +++ b/phoenix/windows/verticalslider.cpp @@ -0,0 +1,25 @@ +void VerticalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + length += (length == 0); + widget->window = CreateWindow( + TRACKBAR_CLASS, L"", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | TBS_NOTICKS | TBS_BOTH | TBS_VERT, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); + SendMessage(widget->window, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); + setPosition(0); +} + +unsigned VerticalSlider::position() { + return SendMessage(widget->window, TBM_GETPOS, 0, 0); +} + +void VerticalSlider::setPosition(unsigned position) { + SendMessage(widget->window, TBM_SETPOS, (WPARAM)true, (LPARAM)(verticalSlider->position = position)); +} + +VerticalSlider::VerticalSlider() { + verticalSlider = new VerticalSlider::Data; +} diff --git a/phoenix/windows/viewport.cpp b/phoenix/windows/viewport.cpp new file mode 100755 index 00000000..21e38d79 --- /dev/null +++ b/phoenix/windows/viewport.cpp @@ -0,0 +1,17 @@ +void Viewport::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + widget->window = CreateWindow( + L"phoenix_viewport", L"", + WS_CHILD | WS_VISIBLE | WS_DISABLED, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); +} + +uintptr_t Viewport::handle() { + return (uintptr_t)widget->window; +} + +static LRESULT CALLBACK Viewport_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + return DefWindowProc(hwnd, msg, wparam, lparam); +} diff --git a/phoenix/windows/widget.cpp b/phoenix/windows/widget.cpp new file mode 100755 index 00000000..70ac5301 --- /dev/null +++ b/phoenix/windows/widget.cpp @@ -0,0 +1,40 @@ +void Widget::setFont(Font &font) { + widget->font = font.font->font; + SendMessage(widget->window, WM_SETFONT, (WPARAM)font.font->font, 0); +} + +bool Widget::visible() { + return GetWindowLong(widget->window, GWL_STYLE) & WS_VISIBLE; +} + +void Widget::setVisible(bool visible) { + ShowWindow(widget->window, visible ? SW_SHOWNORMAL : SW_HIDE); +} + +bool Widget::enabled() { + return IsWindowEnabled(widget->window); +} + +void Widget::setEnabled(bool enabled) { + EnableWindow(widget->window, enabled); +} + +bool Widget::focused() { + return (GetForegroundWindow() == widget->window); +} + +void Widget::setFocused() { + if(visible() == false) setVisible(true); + SetFocus(widget->window); +} + +void Widget::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + SetWindowPos(widget->window, NULL, x, y, width, height, SWP_NOZORDER); +} + +Widget::Widget() { + OS::os->objects.append(this); + widget = new Widget::Data; + widget->window = 0; + widget->font = OS::os->proportionalFont; +} diff --git a/phoenix/windows/window.cpp b/phoenix/windows/window.cpp new file mode 100755 index 00000000..5f86d547 --- /dev/null +++ b/phoenix/windows/window.cpp @@ -0,0 +1,96 @@ +void Window::create(unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindowEx( + 0, L"phoenix_window", utf16_t(text), + WS_POPUP | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX, + x, y, width, height, + 0, 0, GetModuleHandle(0), 0 + ); + window->menu = CreateMenu(); + window->status = CreateWindowEx( + 0, STATUSCLASSNAME, L"", + WS_CHILD, + 0, 0, 0, 0, + widget->window, 0, GetModuleHandle(0), 0 + ); + //StatusBar will be capable of receiving tab focus if it is not disabled + SetWindowLongPtr(window->status, GWL_STYLE, GetWindowLong(window->status, GWL_STYLE) | WS_DISABLED); + resize(width, height); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); +} + +void Window::setDefaultFont(Font &font) { + window->defaultFont = font.font->font; +} + +void Window::setFont(Font &font) { + SendMessage(window->status, WM_SETFONT, (WPARAM)font.font->font, 0); +} + +Geometry Window::geometry() { + RECT position, size; + GetWindowRect(widget->window, &position); + GetClientRect(widget->window, &size); + if(GetWindowLongPtr(window->status, GWL_STYLE) & WS_VISIBLE) { + RECT status; + GetClientRect(window->status, &status); + size.bottom -= status.bottom - status.top; + } + return Geometry(position.left, position.top, size.right, size.bottom); +} + +void Window::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + bool isVisible = visible(); + if(isVisible) setVisible(false); + SetWindowPos(widget->window, NULL, x, y, width, height, SWP_NOZORDER | SWP_FRAMECHANGED); + resize(width, height); + if(isVisible) setVisible(true); +} + +void Window::setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue) { + if(window->brush) DeleteObject(window->brush); + window->brushColor = RGB(red, green, blue); + window->brush = CreateSolidBrush(window->brushColor); +} + +void Window::setTitle(const string &text) { + SetWindowText(widget->window, utf16_t(text)); +} + +void Window::setStatusText(const string &text) { + SendMessage(window->status, SB_SETTEXT, 0, (LPARAM)(wchar_t*)utf16_t(text)); +} + +void Window::setMenuVisible(bool visible) { + SetMenu(widget->window, visible ? window->menu : 0); + resize(window->width, window->height); +} + +void Window::setStatusVisible(bool visible) { + ShowWindow(window->status, visible ? SW_SHOWNORMAL : SW_HIDE); + resize(window->width, window->height); +} + +Window::Window() { + window = new Window::Data; + window->defaultFont = 0; + window->brush = 0; +} + +void Window::resize(unsigned width, unsigned height) { + window->width = width; + window->height = height; + + SetWindowPos(widget->window, NULL, 0, 0, width, height, SWP_NOZORDER | SWP_NOMOVE | SWP_FRAMECHANGED); + RECT rc; + GetClientRect(widget->window, &rc); + width += width - (rc.right - rc.left); + height += height - (rc.bottom - rc.top); + + if(GetWindowLongPtr(window->status, GWL_STYLE) & WS_VISIBLE) { + GetClientRect(window->status, &rc); + height += rc.bottom - rc.top; + } + + SetWindowPos(widget->window, NULL, 0, 0, width, height, SWP_NOZORDER | SWP_NOMOVE | SWP_FRAMECHANGED); + SetWindowPos(window->status, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_FRAMECHANGED); +} \ No newline at end of file diff --git a/phoenix/windows/windows.cpp b/phoenix/windows/windows.cpp new file mode 100755 index 00000000..697f8eec --- /dev/null +++ b/phoenix/windows/windows.cpp @@ -0,0 +1,468 @@ +#include +#include +#include +#include +#include +#include +#include +using namespace nall; + +namespace phoenix { + +#include "object.cpp" +#include "font.cpp" +#include "menu.cpp" +#include "widget.cpp" +#include "window.cpp" +#include "button.cpp" +#include "canvas.cpp" +#include "checkbox.cpp" +#include "combobox.cpp" +#include "editbox.cpp" +#include "horizontalslider.cpp" +#include "label.cpp" +#include "listbox.cpp" +#include "progressbar.cpp" +#include "radiobox.cpp" +#include "textbox.cpp" +#include "verticalslider.cpp" +#include "viewport.cpp" +#include "messagewindow.cpp" + +OS::Data *OS::os = 0; +Window Window::None; + +static void OS_keyboardProc(HWND, UINT, WPARAM, LPARAM); +static LRESULT CALLBACK OS_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + +void OS::initialize() { + static bool initialized = false; + if(initialized == true) return; + initialized = true; + + InitCommonControls(); + CoInitialize(0); + + os = new OS::Data; + os->proportionalFont = Font_createFont("Tahoma", 8, false, false); + os->monospaceFont = Font_createFont("Courier New", 8, false, false); + + WNDCLASS wc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(GetModuleHandle(0), MAKEINTRESOURCE(2)); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = OS_windowProc; + wc.lpszClassName = L"phoenix_window"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); + + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = CreateSolidBrush(RGB(0, 0, 0)); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = Canvas_windowProc; + wc.lpszClassName = L"phoenix_canvas"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); + + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = Label_windowProc; + wc.lpszClassName = L"phoenix_label"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); + + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = CreateSolidBrush(RGB(0, 0, 0)); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = Viewport_windowProc; + wc.lpszClassName = L"phoenix_viewport"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); +} + +bool OS::pending() { + MSG msg; + return PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE); +} + +void OS::run() { + while(pending()) { + MSG msg; + if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { + if(msg.message == WM_KEYDOWN || msg.message == WM_KEYUP) { + OS_keyboardProc(msg.hwnd, msg.message, msg.wParam, msg.lParam); + } + if(!IsDialogMessage(GetParent(msg.hwnd) ? GetParent(msg.hwnd) : msg.hwnd, &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } +} + +void OS::main() { + MSG msg; + while(GetMessage(&msg, 0, 0, 0)) { + if(msg.message == WM_KEYDOWN || msg.message == WM_KEYUP) { + OS_keyboardProc(msg.hwnd, msg.message, msg.wParam, msg.lParam); + } + if(!IsDialogMessage(GetParent(msg.hwnd) ? GetParent(msg.hwnd) : msg.hwnd, &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +} + +void OS::quit() { + PostQuitMessage(0); +} + +unsigned OS::desktopWidth() { + return GetSystemMetrics(SM_CXSCREEN); +} + +unsigned OS::desktopHeight() { + return GetSystemMetrics(SM_CYSCREEN); +} + +string OS::folderSelect(Window &parent, const string &path) { + wchar_t wfilename[PATH_MAX + 1] = L""; + BROWSEINFO bi; + bi.hwndOwner = &parent != &Window::None ? parent.widget->window : 0; + bi.pidlRoot = NULL; + bi.pszDisplayName = wfilename; + bi.lpszTitle = L""; + bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS; + bi.lpfn = NULL; + bi.lParam = 0; + bi.iImage = 0; + bool result = false; + LPITEMIDLIST pidl = SHBrowseForFolder(&bi); + if(pidl) { + if(SHGetPathFromIDList(pidl, wfilename)) { + result = true; + IMalloc *imalloc = 0; + if(SUCCEEDED(SHGetMalloc(&imalloc))) { + imalloc->Free(pidl); + imalloc->Release(); + } + } + } + if(result == false) return ""; + string name = utf8_t(wfilename); + name.transform("\\", "/"); + if(name.endswith("/") == false) name.append("/"); + return name; +} + +string OS::fileOpen(Window &parent, const string &filter, const string &path) { + string dir = path; + dir.replace("/", "\\"); + + string filterInfo; + lstring type; + type.split("\n", filter); + for(unsigned i = 0; i < type.size(); i++) { + lstring part; + part.split("\t", type[i]); + if(part.size() != 2) continue; + filterInfo.append(part[0]); + filterInfo.append(" ("); + filterInfo.append(part[1]); + filterInfo.append(")\t"); + part[1].replace(",", ";"); + filterInfo.append(part[1]); + filterInfo.append("\t"); + } + + utf16_t wfilter(filterInfo); + utf16_t wdir(dir); + wchar_t wfilename[PATH_MAX] = L""; + + wchar_t *p = wfilter; + while(*p != L'\0') { + if(*p == L'\t') *p = L'\0'; + p++; + } + + OPENFILENAME ofn; + memset(&ofn, 0, sizeof(OPENFILENAME)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = &parent != &Window::None ? parent.widget->window : 0; + ofn.lpstrFilter = wfilter; + ofn.lpstrInitialDir = wdir; + ofn.lpstrFile = wfilename; + ofn.nMaxFile = PATH_MAX; + ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + ofn.lpstrDefExt = L""; + + bool result = GetOpenFileName(&ofn); + if(result == false) return ""; + string name = utf8_t(wfilename); + name.transform("\\", "/"); + return name; +} + +string OS::fileSave(Window &parent, const string &filter, const string &path) { + string dir = path; + dir.replace("/", "\\"); + + string filterInfo; + lstring type; + type.split("\n", filter); + for(unsigned i = 0; i < type.size(); i++) { + lstring part; + part.split("\t", type[i]); + if(part.size() != 2) continue; + filterInfo.append(part[0]); + filterInfo.append(" ("); + filterInfo.append(part[1]); + filterInfo.append(")\t"); + part[1].replace(",", ";"); + filterInfo.append(part[1]); + filterInfo.append("\t"); + } + + utf16_t wfilter(filterInfo); + utf16_t wdir(dir); + wchar_t wfilename[PATH_MAX] = L""; + + wchar_t *p = wfilter; + while(*p != L'\0') { + if(*p == L'\t') *p = L'\0'; + p++; + } + + OPENFILENAME ofn; + memset(&ofn, 0, sizeof(OPENFILENAME)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = &parent != &Window::None ? parent.widget->window : 0; + ofn.lpstrFilter = wfilter; + ofn.lpstrInitialDir = wdir; + ofn.lpstrFile = wfilename; + ofn.nMaxFile = PATH_MAX; + ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + ofn.lpstrDefExt = L""; + + bool result = GetSaveFileName(&ofn); + if(result == false) return ""; + string name = utf8_t(wfilename); + name.transform("\\", "/"); + return name; +} + +static void OS_keyboardProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch(msg) { + case WM_KEYDOWN: { + GUITHREADINFO info; + memset(&info, 0, sizeof(GUITHREADINFO)); + info.cbSize = sizeof(GUITHREADINFO); + GetGUIThreadInfo(GetCurrentThreadId(), &info); + Object *object_ptr = (Object*)GetWindowLongPtr(info.hwndFocus, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + ListBox &listBox = (ListBox&)*object_ptr; + if(wparam == VK_RETURN) { + if(listBox.onActivate) listBox.onActivate(); + } + } else if(dynamic_cast(object_ptr)) { + TextBox &textBox = (TextBox&)*object_ptr; + if(wparam == VK_RETURN) { + if(textBox.onActivate) textBox.onActivate(); + } + } + } + } + } +} + +static LRESULT CALLBACK OS_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Object *object_ptr = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!object_ptr || !dynamic_cast(object_ptr)) return DefWindowProc(hwnd, msg, wparam, lparam); + Window &window = (Window&)*object_ptr; + + switch(msg) { + case WM_CLOSE: { + if(window.onClose) { + if(window.onClose()) window.setVisible(false); + } else { + window.setVisible(false); + } + return TRUE; + } + + case WM_ERASEBKGND: { + if(window.window->brush == 0) break; + RECT rc; + GetClientRect(window.widget->window, &rc); + PAINTSTRUCT ps; + BeginPaint(window.widget->window, &ps); + FillRect(ps.hdc, &rc, window.window->brush); + EndPaint(window.widget->window, &ps); + return TRUE; + } + + case WM_CTLCOLORBTN: + case WM_CTLCOLORSTATIC: { + Object *object_ptr = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); + if(object_ptr && window.window->brush) { + HDC hdc = (HDC)wparam; + SetBkColor((HDC)wparam, window.window->brushColor); + return (INT_PTR)window.window->brush; + } + } + + case WM_COMMAND: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.widget->window, id); + if(control == 0) { + Object *object_ptr = (Object*)OS::findObject(id); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + MenuItem &menuItem = (MenuItem&)*object_ptr; + if(menuItem.onTick) menuItem.onTick(); + } else if(dynamic_cast(object_ptr)) { + MenuCheckItem &menuCheckItem = (MenuCheckItem&)*object_ptr; + menuCheckItem.setChecked(!menuCheckItem.checked()); + if(menuCheckItem.onTick) menuCheckItem.onTick(); + } else if(dynamic_cast(object_ptr)) { + MenuRadioItem &menuRadioItem = (MenuRadioItem&)*object_ptr; + if(menuRadioItem.checked() == false) { + menuRadioItem.setChecked(); + if(menuRadioItem.onTick) menuRadioItem.onTick(); + } + } + } + } else { + Object *object_ptr = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + Button &button = (Button&)*object_ptr; + if(button.onTick) button.onTick(); + } else if(dynamic_cast(object_ptr)) { + CheckBox &checkBox = (CheckBox&)*object_ptr; + checkBox.setChecked(!checkBox.checked()); + if(checkBox.onTick) checkBox.onTick(); + } else if(dynamic_cast(object_ptr)) { + ComboBox &comboBox = (ComboBox&)*object_ptr; + if(HIWORD(wparam) == CBN_SELCHANGE) { + if(comboBox.comboBox->selection != comboBox.selection()) { + comboBox.comboBox->selection = comboBox.selection(); + if(comboBox.onChange) comboBox.onChange(); + } + } + } else if(dynamic_cast(object_ptr)) { + EditBox &editBox = (EditBox&)*object_ptr; + if(HIWORD(wparam) == EN_CHANGE) { + if(editBox.object->locked == false && editBox.onChange) editBox.onChange(); + } + } else if(dynamic_cast(object_ptr)) { + RadioBox &radioBox = (RadioBox&)*object_ptr; + if(radioBox.checked() == false) { + radioBox.setChecked(); + if(radioBox.onTick) radioBox.onTick(); + } + } else if(dynamic_cast(object_ptr)) { + TextBox &textBox = (TextBox&)*object_ptr; + if(HIWORD(wparam) == EN_CHANGE) { + if(textBox.object->locked == false && textBox.onChange) textBox.onChange(); + } + } + } + } + } + + case WM_NOTIFY: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.widget->window, id); + if(control) { + Object *object_ptr = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + ListBox &listBox = (ListBox&)*object_ptr; + LPNMHDR nmhdr = (LPNMHDR)lparam; + LPNMLISTVIEW nmlistview = (LPNMLISTVIEW)lparam; + + if(nmhdr->code == LVN_ITEMCHANGED && (nmlistview->uChanged & LVIF_STATE)) { + unsigned imagemask = ((nmlistview->uNewState & LVIS_STATEIMAGEMASK) >> 12) - 1; + if(imagemask == 0 || imagemask == 1) { + if(listBox.object->locked == false && listBox.onTick) listBox.onTick(nmlistview->iItem); + } else if((nmlistview->uOldState & LVIS_FOCUSED) && !(nmlistview->uNewState & LVIS_FOCUSED)) { + listBox.listBox->lostFocus = true; + } else { + if(!(nmlistview->uOldState & LVIS_SELECTED) && (nmlistview->uNewState & LVIS_SELECTED)) { + if(listBox.onChange) listBox.onChange(); + } else if(listBox.listBox->lostFocus == false && listBox.selection() == false) { + if(listBox.onChange) listBox.onChange(); + } + listBox.listBox->lostFocus = false; + } + } else if(nmhdr->code == LVN_ITEMACTIVATE) { + if(listBox.onActivate) listBox.onActivate(); + } + } + } + } + } + + case WM_HSCROLL: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.widget->window, id); + if(control) { + Object *object_ptr = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + HorizontalSlider &horizontalSlider = (HorizontalSlider&)*object_ptr; + if(horizontalSlider.horizontalSlider->position != horizontalSlider.position()) { + horizontalSlider.horizontalSlider->position = horizontalSlider.position(); + if(horizontalSlider.onChange) horizontalSlider.onChange(); + } + } + } + } + } + + case WM_VSCROLL: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.widget->window, id); + if(control) { + Object *object_ptr = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + VerticalSlider &verticalSlider = (VerticalSlider&)*object_ptr; + if(verticalSlider.verticalSlider->position != verticalSlider.position()) { + verticalSlider.verticalSlider->position = verticalSlider.position(); + if(verticalSlider.onChange) verticalSlider.onChange(); + } + } + } + } + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +Object* OS::findObject(unsigned id) { + foreach(object, os->objects) { if(object->object->id == id) return object; } + return 0; +} + +} diff --git a/phoenix/windows/windows.hpp b/phoenix/windows/windows.hpp new file mode 100755 index 00000000..41807a94 --- /dev/null +++ b/phoenix/windows/windows.hpp @@ -0,0 +1,288 @@ +namespace phoenix { + +struct Window; + +struct Object { + Object(); + Object& operator=(const Object&) = delete; + Object(const Object&) = delete; +//private: + struct Data; + Data *object; +private: + virtual void unused(); +}; + +struct Geometry { + unsigned x, y; + unsigned width, height; + inline Geometry() : x(0), y(0), width(0), height(0) {} + inline Geometry(unsigned x, unsigned y, unsigned width, unsigned height) : x(x), y(y), width(width), height(height) {} +}; + +struct Font : Object { + enum class Style : unsigned { + None = 0, + Bold = 1, + Italic = 2, + }; + bool create(const nall::string &name, unsigned size, Font::Style style = Style::None); + Font(); + ~Font(); +//private: + struct Data; + Data *font; +}; + +inline Font::Style operator|(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a | (unsigned)b); } +inline Font::Style operator&(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a & (unsigned)b); } + +struct Action : Object { + virtual bool enabled() = 0; + virtual void setEnabled(bool enabled = true) = 0; + Action(); +//private: + struct Data; + Data *action; +}; + +struct Menu : Action { + void create(Window &parent, const nall::string &text); + void create(Menu &parent, const nall::string &text); + bool enabled(); + void setEnabled(bool enabled = true); +}; + +struct MenuSeparator : Action { + void create(Menu &parent); + bool enabled(); + void setEnabled(bool enabled = true); +}; + +struct MenuItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + bool enabled(); + void setEnabled(bool enabled = true); +}; + +struct MenuCheckItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + bool enabled(); + void setEnabled(bool enabled = true); + bool checked(); + void setChecked(bool checked = true); +}; + +struct MenuRadioItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + void create(MenuRadioItem &parent, const nall::string &text); + bool enabled(); + void setEnabled(bool enabled = true); + bool checked(); + void setChecked(); +}; + +struct Widget : Object { + virtual void setFont(Font &font); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + bool focused(); + void setFocused(); + virtual void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + Widget(); +//private: + struct Data; + Data *widget; +}; + +struct Window : Widget { + nall::function onClose; + void create(unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setDefaultFont(Font &font); + void setFont(Font &font); + Geometry geometry(); + void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + void setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue); + void setTitle(const nall::string &text); + void setStatusText(const nall::string &text); + void setMenuVisible(bool visible = true); + void setStatusVisible(bool visible = true); + Window(); +//private: + struct Data; + Data *window; + static Window None; + void resize(unsigned width, unsigned height); +}; + +struct Button : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); +}; + +struct Canvas : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + uint32_t* buffer(); + void redraw(); + Canvas(); + ~Canvas(); +//private: + struct Data; + Data *canvas; +}; + +struct CheckBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(bool checked = true); +}; + +struct ComboBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void reset(); + void addItem(const nall::string &text); + unsigned selection(); + void setSelection(unsigned item); + ComboBox(); +//private: + struct Data; + Data *comboBox; +}; + +struct EditBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + nall::string getText(); + void setText(const nall::string &text); + void setEditable(bool editable = true); + void setWordWrap(bool wordWrap = true); + EditBox(); +//private: + struct Data; + Data *editBox; +}; + +struct HorizontalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); + HorizontalSlider(); +//private: + struct Data; + Data *horizontalSlider; +}; + +struct Label : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setText(const nall::string &text); +}; + +struct ListBox : Widget { + nall::function onActivate; + nall::function onChange; + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setHeaderVisible(bool headerVisible = true); + void setCheckable(bool checkable = true); + void reset(); + void resizeColumnsToContent(); + void addItem(const nall::string &text); + void setItem(unsigned row, const nall::string &text); + bool checked(unsigned row); + void setChecked(unsigned row, bool checked = true); + nall::optional selection(); + void setSelection(unsigned row); + ListBox(); +//private: + struct Data; + Data *listBox; +}; + +struct ProgressBar : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + unsigned position(); + void setPosition(unsigned position); +}; + +struct RadioBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(); + RadioBox(); +//private: + struct Data; + Data *radioBox; +}; + +struct TextBox : Widget { + nall::function onActivate; + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + nall::string text(); + void setText(const nall::string &text); + void setEditable(bool editable = true); +}; + +struct VerticalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); + VerticalSlider(); +//private: + struct Data; + Data *verticalSlider; +}; + +struct Viewport : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + uintptr_t handle(); +}; + +struct MessageWindow : Object { + enum class Buttons : unsigned { + Ok, + OkCancel, + YesNo, + }; + enum class Response : unsigned { + Ok, + Cancel, + Yes, + No, + }; + static Response information(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response question(Window &parent, const nall::string &text, Buttons = Buttons::YesNo); + static Response warning(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response critical(Window &parent, const nall::string &text, Buttons = Buttons::Ok); +}; + +struct OS : Object { + static bool pending(); + static void run(); + static void main(); + static void quit(); + static unsigned desktopWidth(); + static unsigned desktopHeight(); + static nall::string folderSelect(Window &parent, const nall::string &path = ""); + static nall::string fileOpen(Window &parent, const nall::string &filter, const nall::string &path = ""); + static nall::string fileSave(Window &parent, const nall::string &filter, const nall::string &path = ""); +//private: + static void initialize(); + struct Data; + static Data *os; + static Object* findObject(unsigned id); + friend class Object; +}; + +}; diff --git a/ruby/audio.hpp b/ruby/audio.hpp new file mode 100755 index 00000000..aaf43ff0 --- /dev/null +++ b/ruby/audio.hpp @@ -0,0 +1,23 @@ +class Audio { +public: + static const char *Volume; + static const char *Resample; + static const char *ResampleRatio; + + static const char *Handle; + static const char *Synchronize; + static const char *Frequency; + static const char *Latency; + + virtual bool cap(const nall::string& name) { return false; } + virtual nall::any get(const nall::string& name) { return false; } + virtual bool set(const nall::string& name, const nall::any& value) { return false; } + + virtual void sample(uint16_t left, uint16_t right) {} + virtual void clear() {} + virtual bool init() { return true; } + virtual void term() {} + + Audio() {} + virtual ~Audio() {} +}; diff --git a/ruby/audio/alsa.cpp b/ruby/audio/alsa.cpp new file mode 100755 index 00000000..45b4a8bd --- /dev/null +++ b/ruby/audio/alsa.cpp @@ -0,0 +1,240 @@ +//audio.alsa (2009-11-30) +//authors: BearOso, byuu, Nach, RedDwarf + +#include + +namespace ruby { + +class pAudioALSA { +public: + struct { + snd_pcm_t *handle; + snd_pcm_format_t format; + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; + int channels; + const char *name; + } device; + + struct { + uint32_t *data; + unsigned length; + } buffer; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + if(settings.synchronize != any_cast(value)) { + settings.synchronize = any_cast(value); + if(device.handle) init(); + } + return true; + } + + if(name == Audio::Frequency) { + if(settings.frequency != any_cast(value)) { + settings.frequency = any_cast(value); + if(device.handle) init(); + } + return true; + } + + if(name == Audio::Latency) { + if(settings.latency != any_cast(value)) { + settings.latency = any_cast(value); + if(device.handle) init(); + } + return true; + } + + return false; + } + + void sample(uint16_t left, uint16_t right) { + if(!device.handle) return; + + buffer.data[buffer.length++] = left + (right << 16); + if(buffer.length < device.period_size) return; + + snd_pcm_sframes_t avail; + do { + avail = snd_pcm_avail_update(device.handle); + if(avail < 0) snd_pcm_recover(device.handle, avail, 1); + if(avail < buffer.length) { + if(settings.synchronize == false) { + buffer.length = 0; + return; + } + int error = snd_pcm_wait(device.handle, -1); + if(error < 0) snd_pcm_recover(device.handle, error, 1); + } + } while(avail < buffer.length); + + //below code has issues with PulseAudio sound server + #if 0 + if(settings.synchronize == false) { + snd_pcm_sframes_t avail = snd_pcm_avail_update(device.handle); + if(avail < device.period_size) { + buffer.length = 0; + return; + } + } + #endif + + uint32_t *buffer_ptr = buffer.data; + int i = 4; + + while((buffer.length > 0) && i--) { + snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer_ptr, buffer.length); + if(written < 0) { + //no samples written + snd_pcm_recover(device.handle, written, 1); + } else if(written <= buffer.length) { + buffer.length -= written; + buffer_ptr += written; + } + } + + if(i < 0) { + if(buffer.data == buffer_ptr) { + buffer.length--; + buffer_ptr++; + } + memmove(buffer.data, buffer_ptr, buffer.length * sizeof(uint32_t)); + } + } + + void clear() { + } + + bool init() { + term(); + + if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) { + term(); + return false; + } + + //below code will not work with 24khz frequency rate (ALSA library bug) + #if 0 + if(snd_pcm_set_params(device.handle, device.format, SND_PCM_ACCESS_RW_INTERLEAVED, + device.channels, settings.frequency, 1, settings.latency * 1000) < 0) { + //failed to set device parameters + term(); + return false; + } + + if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) { + device.period_size = settings.latency * 1000 * 1e-6 * settings.frequency / 4; + } + #endif + + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + unsigned rate = settings.frequency; + unsigned buffer_time = settings.latency * 1000; + unsigned period_time = settings.latency * 1000 / 4; + + snd_pcm_hw_params_alloca(&hwparams); + if(snd_pcm_hw_params_any(device.handle, hwparams) < 0) { + term(); + return false; + } + + if(snd_pcm_hw_params_set_access(device.handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0 + || snd_pcm_hw_params_set_format(device.handle, hwparams, device.format) < 0 + || snd_pcm_hw_params_set_channels(device.handle, hwparams, device.channels) < 0 + || snd_pcm_hw_params_set_rate_near(device.handle, hwparams, &rate, 0) < 0 + || snd_pcm_hw_params_set_period_time_near(device.handle, hwparams, &period_time, 0) < 0 + || snd_pcm_hw_params_set_buffer_time_near(device.handle, hwparams, &buffer_time, 0) < 0 + ) { + term(); + return false; + } + + if(snd_pcm_hw_params(device.handle, hwparams) < 0) { + term(); + return false; + } + + if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) { + term(); + return false; + } + + snd_pcm_sw_params_alloca(&swparams); + if(snd_pcm_sw_params_current(device.handle, swparams) < 0) { + term(); + return false; + } + + if(snd_pcm_sw_params_set_start_threshold(device.handle, swparams, + (device.buffer_size / device.period_size) * device.period_size) < 0 + ) { + term(); + return false; + } + + if(snd_pcm_sw_params(device.handle, swparams) < 0) { + term(); + return false; + } + + buffer.data = new uint32_t[device.period_size]; + return true; + } + + void term() { + if(device.handle) { + snd_pcm_drain(device.handle); + snd_pcm_close(device.handle); + device.handle = 0; + } + + if(buffer.data) { + delete[] buffer.data; + buffer.data = 0; + } + } + + pAudioALSA() { + device.handle = 0; + device.format = SND_PCM_FORMAT_S16_LE; + device.channels = 2; + device.name = "default"; + + buffer.data = 0; + buffer.length = 0; + + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 60; + } + + ~pAudioALSA() { + term(); + } +}; + +DeclareAudio(ALSA) + +}; diff --git a/ruby/audio/ao.cpp b/ruby/audio/ao.cpp new file mode 100755 index 00000000..0cfe670d --- /dev/null +++ b/ruby/audio/ao.cpp @@ -0,0 +1,94 @@ +/* + audio.ao (2008-06-01) + authors: Nach, RedDwarf +*/ + +#include + +namespace ruby { + +class pAudioAO { +public: + int driver_id; + ao_sample_format driver_format; + ao_device *audio_device; + + struct { + unsigned frequency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Frequency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Frequency) return settings.frequency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(audio_device) init(); + return true; + } + + return false; + } + + void sample(uint16_t l_sample, uint16_t r_sample) { + uint32_t samp = (l_sample << 0) + (r_sample << 16); + ao_play(audio_device, (char*)&samp, 4); //This may need to be byte swapped for Big Endian + } + + void clear() { + } + + bool init() { + term(); + + driver_id = ao_default_driver_id(); //ao_driver_id((const char*)driver) + if(driver_id < 0) return false; + + driver_format.bits = 16; + driver_format.channels = 2; + driver_format.rate = settings.frequency; + driver_format.byte_format = AO_FMT_LITTLE; + + ao_option *options = 0; + ao_info *di = ao_driver_info(driver_id); + if(!di) return false; + if(!strcmp(di->short_name, "alsa")) { + ao_append_option(&options, "buffer_time", "100000"); //100ms latency (default was 500ms) + } + + audio_device = ao_open_live(driver_id, &driver_format, options); + if(!audio_device) return false; + + return true; + } + + void term() { + if(audio_device) { + ao_close(audio_device); + audio_device = 0; + } + } + + pAudioAO() { + audio_device = 0; + ao_initialize(); + + settings.frequency = 22050; + } + + ~pAudioAO() { + term(); + //ao_shutdown(); //FIXME: this is causing a segfault for some reason when called ... + } +}; + +DeclareAudio(AO) + +}; diff --git a/ruby/audio/directsound.cpp b/ruby/audio/directsound.cpp new file mode 100755 index 00000000..17d09e23 --- /dev/null +++ b/ruby/audio/directsound.cpp @@ -0,0 +1,212 @@ +/* + audio.directsound (2007-12-26) + author: byuu +*/ + +#include + +namespace ruby { + +class pAudioDS { +public: + LPDIRECTSOUND ds; + LPDIRECTSOUNDBUFFER dsb_p, dsb_b; + DSBUFFERDESC dsbd; + WAVEFORMATEX wfx; + + struct { + unsigned rings; + unsigned latency; + + uint32_t *buffer; + unsigned bufferoffset; + + unsigned readring; + unsigned writering; + int distance; + } device; + + struct { + HWND handle; + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Handle) return true; + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Handle) return (uintptr_t)settings.handle; + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + if(ds) clear(); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(ds) init(); + return true; + } + + if(name == Audio::Latency) { + settings.latency = any_cast(value); + if(ds) init(); + return true; + } + + return false; + } + + void sample(uint16_t left, uint16_t right) { + device.buffer[device.bufferoffset++] = left + (right << 16); + if(device.bufferoffset < device.latency) return; + device.bufferoffset = 0; + + DWORD pos, size; + void *output; + + if(settings.synchronize == true) { + //wait until playback buffer has an empty ring to write new audio data to + while(device.distance >= device.rings - 1) { + dsb_b->GetCurrentPosition(&pos, 0); + unsigned activering = pos / (device.latency * 4); + if(activering == device.readring) { + if(settings.synchronize == false) Sleep(1); + continue; + } + + //subtract number of played rings from ring distance counter + device.distance -= (device.rings + activering - device.readring) % device.rings; + device.readring = activering; + + if(device.distance < 2) { + //buffer underflow; set max distance to recover quickly + device.distance = device.rings - 1; + device.writering = (device.rings + device.readring - 1) % device.rings; + break; + } + } + } + + device.writering = (device.writering + 1) % device.rings; + device.distance = (device.distance + 1) % device.rings; + + if(dsb_b->Lock(device.writering * device.latency * 4, device.latency * 4, &output, &size, 0, 0, 0) == DS_OK) { + memcpy(output, device.buffer, device.latency * 4); + dsb_b->Unlock(output, size, 0, 0); + } + } + + void clear() { + device.readring = 0; + device.writering = device.rings - 1; + device.distance = device.rings - 1; + + device.bufferoffset = 0; + if(device.buffer) memset(device.buffer, 0, device.latency * device.rings * 4); + + if(!dsb_b) return; + dsb_b->Stop(); + dsb_b->SetCurrentPosition(0); + + DWORD size; + void *output; + dsb_b->Lock(0, device.latency * device.rings * 4, &output, &size, 0, 0, 0); + memset(output, 0, size); + dsb_b->Unlock(output, size, 0, 0); + + dsb_b->Play(0, 0, DSBPLAY_LOOPING); + } + + bool init() { + term(); + + device.rings = 8; + device.latency = settings.frequency * settings.latency / device.rings / 1000.0 + 0.5; + device.buffer = new uint32_t[device.latency * device.rings]; + device.bufferoffset = 0; + + DirectSoundCreate(0, &ds, 0); + ds->SetCooperativeLevel((HWND)settings.handle, DSSCL_PRIORITY); + + memset(&dsbd, 0, sizeof(dsbd)); + dsbd.dwSize = sizeof(dsbd); + dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbd.dwBufferBytes = 0; + dsbd.lpwfxFormat = 0; + ds->CreateSoundBuffer(&dsbd, &dsb_p, 0); + + memset(&wfx, 0, sizeof(wfx)); + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 2; + wfx.nSamplesPerSec = settings.frequency; + wfx.wBitsPerSample = 16; + wfx.nBlockAlign = wfx.wBitsPerSample / 8 * wfx.nChannels; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + dsb_p->SetFormat(&wfx); + + memset(&dsbd, 0, sizeof(dsbd)); + dsbd.dwSize = sizeof(dsbd); + dsbd.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLFREQUENCY | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE; + dsbd.dwBufferBytes = device.latency * device.rings * sizeof(uint32_t); + dsbd.guid3DAlgorithm = GUID_NULL; + dsbd.lpwfxFormat = &wfx; + ds->CreateSoundBuffer(&dsbd, &dsb_b, 0); + dsb_b->SetFrequency(settings.frequency); + dsb_b->SetCurrentPosition(0); + + clear(); + return true; + } + + void term() { + if(device.buffer) { + delete[] device.buffer; + device.buffer = 0; + } + + if(dsb_b) { dsb_b->Stop(); dsb_b->Release(); dsb_b = 0; } + if(dsb_p) { dsb_p->Stop(); dsb_p->Release(); dsb_p = 0; } + if(ds) { ds->Release(); ds = 0; } + } + + pAudioDS() { + ds = 0; + dsb_p = 0; + dsb_b = 0; + + device.buffer = 0; + device.bufferoffset = 0; + device.readring = 0; + device.writering = 0; + device.distance = 0; + + settings.handle = GetDesktopWindow(); + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 120; + } +}; + +DeclareAudio(DS) + +}; diff --git a/ruby/audio/openal.cpp b/ruby/audio/openal.cpp new file mode 100755 index 00000000..a5be2aac --- /dev/null +++ b/ruby/audio/openal.cpp @@ -0,0 +1,210 @@ +/* + audio.openal (2007-12-26) + author: Nach + contributors: byuu, wertigon, _willow_ +*/ + +#if defined(PLATFORM_OSX) + #include + #include +#else + #include + #include +#endif + +namespace ruby { + +class pAudioOpenAL { +public: + struct { + ALCdevice *handle; + ALCcontext *context; + ALuint source; + ALenum format; + unsigned latency; + unsigned queue_length; + } device; + + struct { + uint32_t *data; + unsigned length; + unsigned size; + } buffer; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + return true; + } + + if(name == Audio::Latency) { + if(settings.latency != any_cast(value)) { + settings.latency = any_cast(value); + update_latency(); + } + return true; + } + + return false; + } + + void sample(uint16_t sl, uint16_t sr) { + buffer.data[buffer.length++] = sl + (sr << 16); + if(buffer.length < buffer.size) return; + + ALuint albuffer = 0; + int processed = 0; + while(true) { + alGetSourcei(device.source, AL_BUFFERS_PROCESSED, &processed); + while(processed--) { + alSourceUnqueueBuffers(device.source, 1, &albuffer); + alDeleteBuffers(1, &albuffer); + device.queue_length--; + } + //wait for buffer playback to catch up to sample generation if not synchronizing + if(settings.synchronize == false || device.queue_length < 3) break; + } + + if(device.queue_length < 3) { + alGenBuffers(1, &albuffer); + alBufferData(albuffer, device.format, buffer.data, buffer.size * 4, settings.frequency); + alSourceQueueBuffers(device.source, 1, &albuffer); + device.queue_length++; + } + + ALint playing; + alGetSourcei(device.source, AL_SOURCE_STATE, &playing); + if(playing != AL_PLAYING) alSourcePlay(device.source); + buffer.length = 0; + } + + void clear() { + } + + void update_latency() { + if(buffer.data) delete[] buffer.data; + buffer.size = settings.frequency * settings.latency / 1000.0 + 0.5; + buffer.data = new uint32_t[buffer.size]; + } + + bool init() { + update_latency(); + device.queue_length = 0; + + bool success = false; + if(device.handle = alcOpenDevice(NULL)) { + if(device.context = alcCreateContext(device.handle, NULL)) { + alcMakeContextCurrent(device.context); + alGenSources(1, &device.source); + + //alSourcef (device.source, AL_PITCH, 1.0); + //alSourcef (device.source, AL_GAIN, 1.0); + //alSource3f(device.source, AL_POSITION, 0.0, 0.0, 0.0); + //alSource3f(device.source, AL_VELOCITY, 0.0, 0.0, 0.0); + //alSource3f(device.source, AL_DIRECTION, 0.0, 0.0, 0.0); + //alSourcef (device.source, AL_ROLLOFF_FACTOR, 0.0); + //alSourcei (device.source, AL_SOURCE_RELATIVE, AL_TRUE); + + alListener3f(AL_POSITION, 0.0, 0.0, 0.0); + alListener3f(AL_VELOCITY, 0.0, 0.0, 0.0); + ALfloat listener_orientation[] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + alListenerfv(AL_ORIENTATION, listener_orientation); + + success = true; + } + } + + if(success == false) { + term(); + return false; + } + + return true; + } + + void term() { + if(alIsSource(device.source) == AL_TRUE) { + int playing = 0; + alGetSourcei(device.source, AL_SOURCE_STATE, &playing); + if(playing == AL_PLAYING) { + alSourceStop(device.source); + int queued = 0; + alGetSourcei(device.source, AL_BUFFERS_QUEUED, &queued); + while(queued--) { + ALuint albuffer = 0; + alSourceUnqueueBuffers(device.source, 1, &albuffer); + alDeleteBuffers(1, &albuffer); + device.queue_length--; + } + } + + alDeleteSources(1, &device.source); + device.source = 0; + } + + if(device.context) { + alcMakeContextCurrent(NULL); + alcDestroyContext(device.context); + device.context = 0; + } + + if(device.handle) { + alcCloseDevice(device.handle); + device.handle = 0; + } + + if(buffer.data) { + delete[] buffer.data; + buffer.data = 0; + } + } + + pAudioOpenAL() { + device.source = 0; + device.handle = 0; + device.context = 0; + device.format = AL_FORMAT_STEREO16; + device.queue_length = 0; + + buffer.data = 0; + buffer.length = 0; + buffer.size = 0; + + settings.synchronize = true; + settings.frequency = 22050; + settings.latency = 40; + } + + ~pAudioOpenAL() { + term(); + } +}; + +DeclareAudio(OpenAL) + +}; diff --git a/ruby/audio/oss.cpp b/ruby/audio/oss.cpp new file mode 100755 index 00000000..dcb8115c --- /dev/null +++ b/ruby/audio/oss.cpp @@ -0,0 +1,113 @@ +/* + audio.oss (2007-12-26) + author: Nach +*/ + +#include +#include +#include +#include + +//OSS4 soundcard.h includes below SNDCTL defines, but OSS3 does not +//However, OSS4 soundcard.h does not reside in +//Therefore, attempt to manually define SNDCTL values if using OSS3 header +//Note that if the defines below fail to work on any specific platform, one can point soundcard.h +//above to the correct location for OSS4 (usually /usr/lib/oss/include/sys/soundcard.h) +//Failing that, one can disable OSS4 ioctl calls inside init() and remove the below defines + +#ifndef SNDCTL_DSP_COOKEDMODE + #define SNDCTL_DSP_COOKEDMODE _IOW('P', 30, int) +#endif + +#ifndef SNDCTL_DSP_POLICY + #define SNDCTL_DSP_POLICY _IOW('P', 45, int) +#endif + +namespace ruby { + +class pAudioOSS { +public: + struct { + int fd; + int format; + int channels; + const char *name; + } device; + + struct { + unsigned frequency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Frequency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Frequency) return settings.frequency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(device.fd > 0) init(); + return true; + } + + return false; + } + + void sample(uint16_t sl, uint16_t sr) { + uint32_t sample = sl + (sr << 16); + unsigned unused = write(device.fd, &sample, 4); + } + + void clear() { + } + + bool init() { + term(); + + device.fd = open(device.name, O_WRONLY, O_NONBLOCK); + if(device.fd < 0) return false; + + #if 1 //SOUND_VERSION >= 0x040000 + //attempt to enable OSS4-specific features regardless of version + //OSS3 ioctl calls will silently fail, but sound will still work + int cooked = 1, policy = 4; //policy should be 0 - 10, lower = less latency, more CPU usage + ioctl(device.fd, SNDCTL_DSP_COOKEDMODE, &cooked); + ioctl(device.fd, SNDCTL_DSP_POLICY, &policy); + #endif + int freq = settings.frequency; + ioctl(device.fd, SNDCTL_DSP_CHANNELS, &device.channels); + ioctl(device.fd, SNDCTL_DSP_SETFMT, &device.format); + ioctl(device.fd, SNDCTL_DSP_SPEED, &freq); + + return true; + } + + void term() { + if(device.fd > 0) { + close(device.fd); + device.fd = -1; + } + } + + pAudioOSS() { + device.fd = -1; + device.format = AFMT_S16_LE; + device.channels = 2; + device.name = "/dev/dsp"; + + settings.frequency = 22050; + } + + ~pAudioOSS() { + term(); + } +}; + +DeclareAudio(OSS) + +}; diff --git a/ruby/audio/pulseaudio.cpp b/ruby/audio/pulseaudio.cpp new file mode 100755 index 00000000..bdd5f682 --- /dev/null +++ b/ruby/audio/pulseaudio.cpp @@ -0,0 +1,177 @@ +//audio.pulseaudio (2010-01-05) +//author: RedDwarf + +#include + +namespace ruby { + +class pAudioPulseAudio { +public: + struct { + pa_mainloop *mainloop; + pa_context *context; + pa_stream *stream; + pa_sample_spec spec; + pa_buffer_attr buffer_attr; + bool first; + } device; + + struct { + uint32_t *data; + size_t size; + unsigned offset; + } buffer; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(device.stream) { + pa_operation_unref(pa_stream_update_sample_rate(device.stream, settings.frequency, NULL, NULL)); + } + return true; + } + + if(name == Audio::Latency) { + settings.latency = any_cast(value); + if(device.stream) { + device.buffer_attr.tlength = pa_usec_to_bytes(settings.latency * PA_USEC_PER_MSEC, &device.spec); + pa_stream_set_buffer_attr(device.stream, &device.buffer_attr, NULL, NULL); + } + return true; + } + } + + void sample(uint16_t left, uint16_t right) { + pa_stream_begin_write(device.stream, (void**)&buffer.data, &buffer.size); + buffer.data[buffer.offset++] = left + (right << 16); + if((buffer.offset + 1) * pa_frame_size(&device.spec) <= buffer.size) return; + + while(true) { + if(device.first) { + device.first = false; + pa_mainloop_iterate(device.mainloop, 0, NULL); + } else { + pa_mainloop_iterate(device.mainloop, 1, NULL); + } + unsigned length = pa_stream_writable_size(device.stream); + if(length >= buffer.offset * pa_frame_size(&device.spec)) break; + if(settings.synchronize == false) { + buffer.offset = 0; + return; + } + } + + pa_stream_write(device.stream, (const void*)buffer.data, buffer.offset * pa_frame_size(&device.spec), NULL, 0LL, PA_SEEK_RELATIVE); + buffer.data = 0; + buffer.offset = 0; + } + + void clear() { + } + + bool init() { + device.mainloop = pa_mainloop_new(); + + device.context = pa_context_new(pa_mainloop_get_api(device.mainloop), "ruby::pulseaudio"); + pa_context_connect(device.context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_context_state_t cstate; + do { + pa_mainloop_iterate(device.mainloop, 1, NULL); + cstate = pa_context_get_state(device.context); + if(!PA_CONTEXT_IS_GOOD(cstate)) return false; + } while(cstate != PA_CONTEXT_READY); + + device.spec.format = PA_SAMPLE_S16LE; + device.spec.channels = 2; + device.spec.rate = settings.frequency; + device.stream = pa_stream_new(device.context, "audio", &device.spec, NULL); + + device.buffer_attr.maxlength = -1; + device.buffer_attr.tlength = pa_usec_to_bytes(settings.latency * PA_USEC_PER_MSEC, &device.spec); + device.buffer_attr.prebuf = -1; + device.buffer_attr.minreq = -1; + device.buffer_attr.fragsize = -1; + + pa_stream_flags_t flags = (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY | PA_STREAM_VARIABLE_RATE); + pa_stream_connect_playback(device.stream, NULL, &device.buffer_attr, flags, NULL, NULL); + + pa_stream_state_t sstate; + do { + pa_mainloop_iterate(device.mainloop, 1, NULL); + sstate = pa_stream_get_state(device.stream); + if(!PA_STREAM_IS_GOOD(sstate)) return false; + } while(sstate != PA_STREAM_READY); + + buffer.size = 960; + buffer.offset = 0; + device.first = true; + + return true; + } + + void term() { + if(buffer.data) { + pa_stream_cancel_write(device.stream); + buffer.data = 0; + } + + if(device.stream) { + pa_stream_disconnect(device.stream); + pa_stream_unref(device.stream); + device.stream = 0; + } + + if(device.context) { + pa_context_disconnect(device.context); + pa_context_unref(device.context); + device.context = 0; + } + + if(device.mainloop) { + pa_mainloop_free(device.mainloop); + device.mainloop = 0; + } + } + + pAudioPulseAudio() { + device.mainloop = 0; + device.context = 0; + device.stream = 0; + buffer.data = 0; + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 60; + } + + ~pAudioPulseAudio() { + term(); + } +}; + +DeclareAudio(PulseAudio) + +} diff --git a/ruby/audio/pulseaudiosimple.cpp b/ruby/audio/pulseaudiosimple.cpp new file mode 100755 index 00000000..cdd6e438 --- /dev/null +++ b/ruby/audio/pulseaudiosimple.cpp @@ -0,0 +1,115 @@ +//audio.pulseaudiosimple (2010-01-05) +//author: byuu + +#include +#include + +namespace ruby { + +class pAudioPulseAudioSimple { +public: + struct { + pa_simple *handle; + pa_sample_spec spec; + } device; + + struct { + uint32_t *data; + unsigned offset; + } buffer; + + struct { + unsigned frequency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Frequency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Frequency) return settings.frequency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(device.handle) init(); + return true; + } + + return false; + } + + void sample(uint16_t left, uint16_t right) { + if(!device.handle) return; + + buffer.data[buffer.offset++] = left + (right << 16); + if(buffer.offset >= 64) { + int error; + pa_simple_write(device.handle, (const void*)buffer.data, buffer.offset * sizeof(uint32_t), &error); + buffer.offset = 0; + } + } + + void clear() { + } + + bool init() { + term(); + + device.spec.format = PA_SAMPLE_S16LE; + device.spec.channels = 2; + device.spec.rate = settings.frequency; + + int error = 0; + device.handle = pa_simple_new( + 0, //default server + "ruby::pulseaudiosimple", //application name + PA_STREAM_PLAYBACK, //direction + 0, //default device + "audio", //stream description + &device.spec, //sample format + 0, //default channel map + 0, //default buffering attributes + &error //error code + ); + if(!device.handle) { + fprintf(stderr, "ruby::pulseaudiosimple failed to initialize - %s\n", pa_strerror(error)); + return false; + } + + buffer.data = new uint32_t[64]; + buffer.offset = 0; + return true; + } + + void term() { + if(device.handle) { + int error; + pa_simple_flush(device.handle, &error); + pa_simple_free(device.handle); + device.handle = 0; + } + + if(buffer.data) { + delete[] buffer.data; + buffer.data = 0; + } + } + + pAudioPulseAudioSimple() { + device.handle = 0; + buffer.data = 0; + settings.frequency = 22050; + } + + ~pAudioPulseAudioSimple() { + term(); + } +}; + +DeclareAudio(PulseAudioSimple) + +}; diff --git a/ruby/audio/xaudio2.cpp b/ruby/audio/xaudio2.cpp new file mode 100755 index 00000000..d6298593 --- /dev/null +++ b/ruby/audio/xaudio2.cpp @@ -0,0 +1,200 @@ +/* + audio.xaudio2 (2010-08-14) + author: OV2 +*/ + +#include "xaudio2.hpp" +#include + +namespace ruby { + +class pAudioXAudio2: public IXAudio2VoiceCallback { +public: + IXAudio2 *pXAudio2; + IXAudio2MasteringVoice* pMasterVoice; + IXAudio2SourceVoice *pSourceVoice; + + // inherited from IXAudio2VoiceCallback + STDMETHODIMP_(void) OnBufferStart(void *pBufferContext){} + STDMETHODIMP_(void) OnLoopEnd(void *pBufferContext){} + STDMETHODIMP_(void) OnStreamEnd() {} + STDMETHODIMP_(void) OnVoiceError(void *pBufferContext, HRESULT Error) {} + STDMETHODIMP_(void) OnVoiceProcessingPassEnd() {} + STDMETHODIMP_(void) OnVoiceProcessingPassStart(UINT32 BytesRequired) {} + + struct { + unsigned buffers; + unsigned latency; + + uint32_t *buffer; + unsigned bufferoffset; + + volatile long submitbuffers; + unsigned writebuffer; + } device; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + if(pXAudio2) clear(); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(pXAudio2) init(); + return true; + } + + if(name == Audio::Latency) { + settings.latency = any_cast(value); + if(pXAudio2) init(); + return true; + } + + return false; + } + + void pushbuffer(unsigned bytes,uint32_t *pAudioData) { + XAUDIO2_BUFFER xa2buffer={0}; + xa2buffer.AudioBytes=bytes; + xa2buffer.pAudioData=reinterpret_cast(pAudioData); + xa2buffer.pContext=0; + InterlockedIncrement(&device.submitbuffers); + pSourceVoice->SubmitSourceBuffer(&xa2buffer); + } + + void sample(uint16_t left, uint16_t right) { + device.buffer[device.writebuffer * device.latency + device.bufferoffset++] = left + (right << 16); + if(device.bufferoffset < device.latency) return; + device.bufferoffset = 0; + + if(device.submitbuffers == device.buffers - 1) { + if(settings.synchronize == true) { + //wait until there is at least one other free buffer for the next sample + while(device.submitbuffers == device.buffers - 1) { + //Sleep(0); + } + } else { //we need one free buffer for the next sample, so ignore the current contents + return; + } + } + + pushbuffer(device.latency * 4,device.buffer + device.writebuffer * device.latency); + + device.writebuffer = (device.writebuffer + 1) % device.buffers; + } + + void clear() { + if(!pSourceVoice) return; + pSourceVoice->Stop(0); + pSourceVoice->FlushSourceBuffers(); //calls OnBufferEnd for all currently submitted buffers + + device.writebuffer = 0; + + device.bufferoffset = 0; + if(device.buffer) memset(device.buffer, 0, device.latency * device.buffers * 4); + + pSourceVoice->Start(0); + } + + bool init() { + term(); + + device.buffers = 8; + device.latency = settings.frequency * settings.latency / device.buffers / 1000.0 + 0.5; + device.buffer = new uint32_t[device.latency * device.buffers]; + device.bufferoffset = 0; + device.submitbuffers = 0; + + HRESULT hr; + if(FAILED(hr = XAudio2Create(&pXAudio2, 0 , XAUDIO2_DEFAULT_PROCESSOR))) { + return false; + } + + if(FAILED(hr = pXAudio2->CreateMasteringVoice( &pMasterVoice, 2, + settings.frequency, 0, 0 , NULL))) { + return false; + } + + WAVEFORMATEX wfx; + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 2; + wfx.nSamplesPerSec = settings.frequency; + wfx.nBlockAlign = 4; + wfx.wBitsPerSample = 16; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + wfx.cbSize = 0; + + if(FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, (WAVEFORMATEX*)&wfx, + XAUDIO2_VOICE_NOSRC , XAUDIO2_DEFAULT_FREQ_RATIO, this, NULL, NULL))) { + return false; + } + + clear(); + return true; + } + + void term() { + if(pSourceVoice) { + pSourceVoice->Stop(0); + pSourceVoice->DestroyVoice(); + pSourceVoice = 0; + } + if(pMasterVoice) { + pMasterVoice->DestroyVoice(); + pMasterVoice = 0; + } + if(pXAudio2) { + pXAudio2->Release(); + pXAudio2 = NULL; + } + if(device.buffer) { + delete[] device.buffer; + device.buffer = 0; + } + } + + STDMETHODIMP_(void) OnBufferEnd(void *pBufferContext) { + InterlockedDecrement(&device.submitbuffers); + } + + pAudioXAudio2() { + pXAudio2 = 0; + pMasterVoice = 0; + pSourceVoice = 0; + + device.buffer = 0; + device.bufferoffset = 0; + device.submitbuffers = 0; + device.writebuffer = 0; + + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 120; + } +}; + +DeclareAudio(XAudio2) + +}; diff --git a/ruby/audio/xaudio2.hpp b/ruby/audio/xaudio2.hpp new file mode 100755 index 00000000..e283f503 --- /dev/null +++ b/ruby/audio/xaudio2.hpp @@ -0,0 +1,340 @@ +/* + xaudio2.hpp (2010-08-14) + author: OV2 + + ruby-specific header to provide mingw-friendly xaudio2 interfaces +*/ + +#ifndef XAUDIO2_RUBY_H +#define XAUDIO2_RUBY_H + +//64-bit GCC fix +#define GUID_EXT EXTERN_C +#define GUID_SECT + +#include + +#define DEFINE_GUID_X(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) GUID_EXT const GUID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} +#define DEFINE_CLSID_X(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + DEFINE_GUID_X(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) +#define DEFINE_IID_X(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + DEFINE_GUID_X(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) +#define X2DEFAULT(x) =x + +DEFINE_CLSID_X(XAudio2, e21a7345, eb21, 468e, be, 50, 80, 4d, b9, 7c, f7, 08); +DEFINE_CLSID_X(XAudio2_Debug, f7a76c21, 53d4, 46bb, ac, 53, 8b, 45, 9c, ae, 46, bd); +DEFINE_IID_X(IXAudio2, 8bcf1f58, 9fe7, 4583, 8a, c6, e2, ad, c4, 65, c8, bb); + +DECLARE_INTERFACE(IXAudio2Voice); + +#define XAUDIO2_COMMIT_NOW 0 +#define XAUDIO2_DEFAULT_CHANNELS 0 +#define XAUDIO2_DEFAULT_SAMPLERATE 0 +#define XAUDIO2_DEFAULT_FREQ_RATIO 4.0f +#define XAUDIO2_DEBUG_ENGINE 0x0001 +#define XAUDIO2_VOICE_NOSRC 0x0004 + +typedef struct +{ + WAVEFORMATEX Format; + union + { + WORD wValidBitsPerSample; + WORD wSamplesPerBlock; + WORD wReserved; + } Samples; + DWORD dwChannelMask; + GUID SubFormat; +} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE, *LPPWAVEFORMATEXTENSIBLE; +typedef const WAVEFORMATEXTENSIBLE* LPCWAVEFORMATEXTENSIBLE; + +typedef enum XAUDIO2_DEVICE_ROLE +{ + NotDefaultDevice = 0x0, + DefaultConsoleDevice = 0x1, + DefaultMultimediaDevice = 0x2, + DefaultCommunicationsDevice = 0x4, + DefaultGameDevice = 0x8, + GlobalDefaultDevice = 0xf, + InvalidDeviceRole = ~GlobalDefaultDevice +} XAUDIO2_DEVICE_ROLE; + +typedef struct XAUDIO2_DEVICE_DETAILS +{ + WCHAR DeviceID[256]; + WCHAR DisplayName[256]; + XAUDIO2_DEVICE_ROLE Role; + WAVEFORMATEXTENSIBLE OutputFormat; +} XAUDIO2_DEVICE_DETAILS; + +typedef struct XAUDIO2_VOICE_DETAILS +{ + UINT32 CreationFlags; + UINT32 InputChannels; + UINT32 InputSampleRate; +} XAUDIO2_VOICE_DETAILS; + +typedef enum XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER +{ + Processor1 = 0x00000001, + Processor2 = 0x00000002, + Processor3 = 0x00000004, + Processor4 = 0x00000008, + Processor5 = 0x00000010, + Processor6 = 0x00000020, + Processor7 = 0x00000040, + Processor8 = 0x00000080, + Processor9 = 0x00000100, + Processor10 = 0x00000200, + Processor11 = 0x00000400, + Processor12 = 0x00000800, + Processor13 = 0x00001000, + Processor14 = 0x00002000, + Processor15 = 0x00004000, + Processor16 = 0x00008000, + Processor17 = 0x00010000, + Processor18 = 0x00020000, + Processor19 = 0x00040000, + Processor20 = 0x00080000, + Processor21 = 0x00100000, + Processor22 = 0x00200000, + Processor23 = 0x00400000, + Processor24 = 0x00800000, + Processor25 = 0x01000000, + Processor26 = 0x02000000, + Processor27 = 0x04000000, + Processor28 = 0x08000000, + Processor29 = 0x10000000, + Processor30 = 0x20000000, + Processor31 = 0x40000000, + Processor32 = 0x80000000, + XAUDIO2_ANY_PROCESSOR = 0xffffffff, + XAUDIO2_DEFAULT_PROCESSOR = XAUDIO2_ANY_PROCESSOR +} XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER, XAUDIO2_PROCESSOR; + +typedef struct XAUDIO2_VOICE_SENDS +{ + UINT32 OutputCount; + IXAudio2Voice** pOutputVoices; +} XAUDIO2_VOICE_SENDS; + +typedef struct XAUDIO2_EFFECT_DESCRIPTOR +{ + IUnknown* pEffect; + BOOL InitialState; + UINT32 OutputChannels; +} XAUDIO2_EFFECT_DESCRIPTOR; + +typedef struct XAUDIO2_EFFECT_CHAIN +{ + UINT32 EffectCount; + const XAUDIO2_EFFECT_DESCRIPTOR* pEffectDescriptors; +} XAUDIO2_EFFECT_CHAIN; + +typedef enum XAUDIO2_FILTER_TYPE +{ + LowPassFilter, + BandPassFilter, + HighPassFilter +} XAUDIO2_FILTER_TYPE; + +typedef struct XAUDIO2_FILTER_PARAMETERS +{ + XAUDIO2_FILTER_TYPE Type; + float Frequency; + float OneOverQ; + +} XAUDIO2_FILTER_PARAMETERS; + +typedef struct XAUDIO2_BUFFER +{ + UINT32 Flags; + UINT32 AudioBytes; + const BYTE* pAudioData; + UINT32 PlayBegin; + UINT32 PlayLength; + UINT32 LoopBegin; + UINT32 LoopLength; + UINT32 LoopCount; + void* pContext; +} XAUDIO2_BUFFER; + +typedef struct XAUDIO2_BUFFER_WMA +{ + const UINT32* pDecodedPacketCumulativeBytes; + UINT32 PacketCount; +} XAUDIO2_BUFFER_WMA; + +typedef struct XAUDIO2_VOICE_STATE +{ + void* pCurrentBufferContext; + UINT32 BuffersQueued; + UINT64 SamplesPlayed; +} XAUDIO2_VOICE_STATE; + +typedef struct XAUDIO2_PERFORMANCE_DATA +{ + UINT64 AudioCyclesSinceLastQuery; + UINT64 TotalCyclesSinceLastQuery; + UINT32 MinimumCyclesPerQuantum; + UINT32 MaximumCyclesPerQuantum; + UINT32 MemoryUsageInBytes; + UINT32 CurrentLatencyInSamples; + UINT32 GlitchesSinceEngineStarted; + UINT32 ActiveSourceVoiceCount; + UINT32 TotalSourceVoiceCount; + UINT32 ActiveSubmixVoiceCount; + UINT32 TotalSubmixVoiceCount; + UINT32 ActiveXmaSourceVoices; + UINT32 ActiveXmaStreams; +} XAUDIO2_PERFORMANCE_DATA; + +typedef struct XAUDIO2_DEBUG_CONFIGURATION +{ + UINT32 TraceMask; + UINT32 BreakMask; + BOOL LogThreadID; + BOOL LogFileline; + BOOL LogFunctionName; + BOOL LogTiming; +} XAUDIO2_DEBUG_CONFIGURATION; + +DECLARE_INTERFACE(IXAudio2EngineCallback) +{ + STDMETHOD_(void, OnProcessingPassStart) (THIS) PURE; + STDMETHOD_(void, OnProcessingPassEnd) (THIS) PURE; + STDMETHOD_(void, OnCriticalError) (THIS_ HRESULT Error) PURE; +}; + +DECLARE_INTERFACE(IXAudio2VoiceCallback) +{ + STDMETHOD_(void, OnVoiceProcessingPassStart) (THIS_ UINT32 BytesRequired) PURE; + STDMETHOD_(void, OnVoiceProcessingPassEnd) (THIS) PURE; + STDMETHOD_(void, OnStreamEnd) (THIS) PURE; + STDMETHOD_(void, OnBufferStart) (THIS_ void* pBufferContext) PURE; + STDMETHOD_(void, OnBufferEnd) (THIS_ void* pBufferContext) PURE; + STDMETHOD_(void, OnLoopEnd) (THIS_ void* pBufferContext) PURE; + STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) PURE; +}; + +DECLARE_INTERFACE(IXAudio2Voice) +{ + #define Declare_IXAudio2Voice_Methods() \ + STDMETHOD_(void, GetVoiceDetails) (THIS_ XAUDIO2_VOICE_DETAILS* pVoiceDetails) PURE; \ + STDMETHOD(SetOutputVoices) (THIS_ const XAUDIO2_VOICE_SENDS* pSendList) PURE; \ + STDMETHOD(SetEffectChain) (THIS_ const XAUDIO2_EFFECT_CHAIN* pEffectChain) PURE; \ + STDMETHOD(EnableEffect) (THIS_ UINT32 EffectIndex, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD(DisableEffect) (THIS_ UINT32 EffectIndex, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetEffectState) (THIS_ UINT32 EffectIndex, BOOL* pEnabled) PURE; \ + STDMETHOD(SetEffectParameters) (THIS_ UINT32 EffectIndex, \ + const void* pParameters, \ + UINT32 ParametersByteSize, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD(GetEffectParameters) (THIS_ UINT32 EffectIndex, void* pParameters, \ + UINT32 ParametersByteSize) PURE; \ + STDMETHOD(SetFilterParameters) (THIS_ const XAUDIO2_FILTER_PARAMETERS* pParameters, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetFilterParameters) (THIS_ XAUDIO2_FILTER_PARAMETERS* pParameters) PURE; \ + STDMETHOD(SetVolume) (THIS_ float Volume, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetVolume) (THIS_ float* pVolume) PURE; \ + STDMETHOD(SetChannelVolumes) (THIS_ UINT32 Channels, const float* pVolumes, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetChannelVolumes) (THIS_ UINT32 Channels, float* pVolumes) PURE; \ + STDMETHOD(SetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \ + UINT32 SourceChannels, UINT32 DestinationChannels, \ + const float* pLevelMatrix, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \ + UINT32 SourceChannels, UINT32 DestinationChannels, \ + float* pLevelMatrix) PURE; \ + STDMETHOD_(void, DestroyVoice) (THIS) PURE + + Declare_IXAudio2Voice_Methods(); +}; + + +DECLARE_INTERFACE_(IXAudio2MasteringVoice, IXAudio2Voice) +{ + Declare_IXAudio2Voice_Methods(); +}; + +DECLARE_INTERFACE_(IXAudio2SubmixVoice, IXAudio2Voice) +{ + Declare_IXAudio2Voice_Methods(); +}; + +DECLARE_INTERFACE_(IXAudio2SourceVoice, IXAudio2Voice) +{ + Declare_IXAudio2Voice_Methods(); + STDMETHOD(Start) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD(Stop) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD(SubmitSourceBuffer) (THIS_ const XAUDIO2_BUFFER* pBuffer, const XAUDIO2_BUFFER_WMA* pBufferWMA X2DEFAULT(NULL)) PURE; + STDMETHOD(FlushSourceBuffers) (THIS) PURE; + STDMETHOD(Discontinuity) (THIS) PURE; + STDMETHOD(ExitLoop) (THIS_ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD_(void, GetState) (THIS_ XAUDIO2_VOICE_STATE* pVoiceState) PURE; + STDMETHOD(SetFrequencyRatio) (THIS_ float Ratio, + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD_(void, GetFrequencyRatio) (THIS_ float* pRatio) PURE; +}; + +DECLARE_INTERFACE_(IXAudio2, IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvInterface) PURE; + STDMETHOD_(ULONG, AddRef) (THIS) PURE; + STDMETHOD_(ULONG, Release) (THIS) PURE; + STDMETHOD(GetDeviceCount) (THIS_ UINT32* pCount) PURE; + STDMETHOD(GetDeviceDetails) (THIS_ UINT32 Index, XAUDIO2_DEVICE_DETAILS* pDeviceDetails) PURE; + STDMETHOD(Initialize) (THIS_ UINT32 Flags X2DEFAULT(0), + XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) PURE; + STDMETHOD(RegisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE; + STDMETHOD_(void, UnregisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE; + STDMETHOD(CreateSourceVoice) (THIS_ IXAudio2SourceVoice** ppSourceVoice, + const WAVEFORMATEX* pSourceFormat, + UINT32 Flags X2DEFAULT(0), + float MaxFrequencyRatio X2DEFAULT(XAUDIO2_DEFAULT_FREQ_RATIO), + IXAudio2VoiceCallback* pCallback X2DEFAULT(NULL), + const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL), + const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + STDMETHOD(CreateSubmixVoice) (THIS_ IXAudio2SubmixVoice** ppSubmixVoice, + UINT32 InputChannels, UINT32 InputSampleRate, + UINT32 Flags X2DEFAULT(0), UINT32 ProcessingStage X2DEFAULT(0), + const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL), + const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + STDMETHOD(CreateMasteringVoice) (THIS_ IXAudio2MasteringVoice** ppMasteringVoice, + UINT32 InputChannels X2DEFAULT(XAUDIO2_DEFAULT_CHANNELS), + UINT32 InputSampleRate X2DEFAULT(XAUDIO2_DEFAULT_SAMPLERATE), + UINT32 Flags X2DEFAULT(0), UINT32 DeviceIndex X2DEFAULT(0), + const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + STDMETHOD(StartEngine) (THIS) PURE; + STDMETHOD_(void, StopEngine) (THIS) PURE; + STDMETHOD(CommitChanges) (THIS_ UINT32 OperationSet) PURE; + STDMETHOD_(void, GetPerformanceData) (THIS_ XAUDIO2_PERFORMANCE_DATA* pPerfData) PURE; + STDMETHOD_(void, SetDebugConfiguration) (THIS_ const XAUDIO2_DEBUG_CONFIGURATION* pDebugConfiguration, + void* pReserved X2DEFAULT(NULL)) PURE; +}; + +__inline HRESULT XAudio2Create(IXAudio2** ppXAudio2, UINT32 Flags X2DEFAULT(0), + XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) +{ + IXAudio2* pXAudio2; + HRESULT hr = CoCreateInstance((Flags & XAUDIO2_DEBUG_ENGINE) ? CLSID_XAudio2_Debug : CLSID_XAudio2, + NULL, CLSCTX_INPROC_SERVER, IID_IXAudio2, (void**)&pXAudio2); + if (SUCCEEDED(hr)) + { + hr = pXAudio2->Initialize(Flags, XAudio2Processor); + if (SUCCEEDED(hr)) + { + *ppXAudio2 = pXAudio2; + } + else + { + pXAudio2->Release(); + } + } + return hr; +} +#endif diff --git a/ruby/input.hpp b/ruby/input.hpp new file mode 100755 index 00000000..5334c4da --- /dev/null +++ b/ruby/input.hpp @@ -0,0 +1,22 @@ +class Input { +public: + static const char *Handle; + static const char *KeyboardSupport; + static const char *MouseSupport; + static const char *JoypadSupport; + + virtual bool cap(const nall::string& name) { return false; } + virtual nall::any get(const nall::string& name) { return false; } + virtual bool set(const nall::string& name, const nall::any& value) { return false; } + + virtual bool acquire() { return false; } + virtual bool unacquire() { return false; } + virtual bool acquired() { return false; } + + virtual bool poll(int16_t *table) { return false; } + virtual bool init() { return true; } + virtual void term() {} + + Input() {} + virtual ~Input() {} +}; diff --git a/ruby/input/carbon.cpp b/ruby/input/carbon.cpp new file mode 100755 index 00000000..c5191f4b --- /dev/null +++ b/ruby/input/carbon.cpp @@ -0,0 +1,157 @@ +namespace ruby { + +class pInputCarbon { +public: + bool cap(const string& name) { + return false; + } + + any get(const string& name) { + return false; + } + + bool set(const string& name, const any& value) { + return false; + } + + bool acquire() { return false; } + bool unacquire() { return false; } + bool acquired() { return false; } + + bool poll(int16_t *table) { + memset(table, 0, Scancode::Limit * sizeof(int16_t)); + + KeyMap keys; + GetKeys(keys); + uint8_t *keymap = (uint8_t*)keys; + + #define map(id, name) table[keyboard(0)[name]] = (bool)(keymap[id >> 3] & (1 << (id & 7))) + map(0x35, Keyboard::Escape); + + map(0x7a, Keyboard::F1); + map(0x78, Keyboard::F2); + map(0x63, Keyboard::F3); + map(0x76, Keyboard::F4); + map(0x60, Keyboard::F5); + map(0x61, Keyboard::F6); + map(0x62, Keyboard::F7); + map(0x64, Keyboard::F8); + map(0x65, Keyboard::F9); + map(0x6d, Keyboard::F10); + map(0x67, Keyboard::F11); + //map(0x??, Keyboard::F12); + + map(0x69, Keyboard::PrintScreen); + //map(0x??, Keyboard::ScrollLock); + map(0x71, Keyboard::Pause); + + map(0x32, Keyboard::Tilde); + map(0x12, Keyboard::Num1); + map(0x13, Keyboard::Num2); + map(0x14, Keyboard::Num3); + map(0x15, Keyboard::Num4); + map(0x17, Keyboard::Num5); + map(0x16, Keyboard::Num6); + map(0x1a, Keyboard::Num7); + map(0x1c, Keyboard::Num8); + map(0x19, Keyboard::Num9); + map(0x1d, Keyboard::Num0); + + map(0x1b, Keyboard::Dash); + map(0x18, Keyboard::Equal); + map(0x33, Keyboard::Backspace); + + map(0x72, Keyboard::Insert); + map(0x75, Keyboard::Delete); + map(0x73, Keyboard::Home); + map(0x77, Keyboard::End); + map(0x74, Keyboard::PageUp); + map(0x79, Keyboard::PageDown); + + map(0x00, Keyboard::A); + map(0x0b, Keyboard::B); + map(0x08, Keyboard::C); + map(0x02, Keyboard::D); + map(0x0e, Keyboard::E); + map(0x03, Keyboard::F); + map(0x05, Keyboard::G); + map(0x04, Keyboard::H); + map(0x22, Keyboard::I); + map(0x26, Keyboard::J); + map(0x28, Keyboard::K); + map(0x25, Keyboard::L); + map(0x2e, Keyboard::M); + map(0x2d, Keyboard::N); + map(0x1f, Keyboard::O); + map(0x23, Keyboard::P); + map(0x0c, Keyboard::Q); + map(0x0f, Keyboard::R); + map(0x01, Keyboard::S); + map(0x11, Keyboard::T); + map(0x20, Keyboard::U); + map(0x09, Keyboard::V); + map(0x0d, Keyboard::W); + map(0x07, Keyboard::X); + map(0x10, Keyboard::Y); + map(0x06, Keyboard::Z); + + map(0x21, Keyboard::LeftBracket); + map(0x1e, Keyboard::RightBracket); + map(0x2a, Keyboard::Backslash); + map(0x29, Keyboard::Semicolon); + map(0x27, Keyboard::Apostrophe); + map(0x2b, Keyboard::Comma); + map(0x2f, Keyboard::Period); + map(0x2c, Keyboard::Slash); + + map(0x53, Keyboard::Keypad1); + map(0x54, Keyboard::Keypad2); + map(0x55, Keyboard::Keypad3); + map(0x56, Keyboard::Keypad4); + map(0x57, Keyboard::Keypad5); + map(0x58, Keyboard::Keypad6); + map(0x59, Keyboard::Keypad7); + map(0x5b, Keyboard::Keypad8); + map(0x5c, Keyboard::Keypad9); + map(0x52, Keyboard::Keypad0); + + //map(0x??, Keyboard::Point); + map(0x4c, Keyboard::Enter); + map(0x45, Keyboard::Add); + map(0x4e, Keyboard::Subtract); + map(0x43, Keyboard::Multiply); + map(0x4b, Keyboard::Divide); + + map(0x47, Keyboard::NumLock); + //map(0x39, Keyboard::CapsLock); + + map(0x7e, Keyboard::Up); + map(0x7d, Keyboard::Down); + map(0x7b, Keyboard::Left); + map(0x7c, Keyboard::Right); + + map(0x30, Keyboard::Tab); + map(0x24, Keyboard::Return); + map(0x31, Keyboard::Spacebar); + //map(0x??, Keyboard::Menu); + + map(0x38, Keyboard::Shift); + map(0x3b, Keyboard::Control); + map(0x3a, Keyboard::Alt); + map(0x37, Keyboard::Super); + #undef map + + return true; + } + + bool init() { + return true; + } + + void term() { + } +}; + +DeclareInput(Carbon) + +}; diff --git a/ruby/input/directinput.cpp b/ruby/input/directinput.cpp new file mode 100755 index 00000000..6db107b2 --- /dev/null +++ b/ruby/input/directinput.cpp @@ -0,0 +1,387 @@ +#define DIRECTINPUT_VERSION 0x0800 +#include + +namespace ruby { + +static BOOL CALLBACK DI_EnumJoypadsCallback(const DIDEVICEINSTANCE*, void*); +static BOOL CALLBACK DI_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE*, void*); + +using namespace nall; + +class pInputDI { +public: + struct { + LPDIRECTINPUT8 context; + LPDIRECTINPUTDEVICE8 keyboard; + LPDIRECTINPUTDEVICE8 mouse; + LPDIRECTINPUTDEVICE8 gamepad[Joypad::Count]; + bool mouseacquired; + } device; + + struct { + HWND handle; + } settings; + + bool cap(const string& name) { + if(name == Input::Handle) return true; + if(name == Input::KeyboardSupport) return true; + if(name == Input::MouseSupport) return true; + if(name == Input::JoypadSupport) return true; + return false; + } + + any get(const string& name) { + if(name == Input::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Input::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + return false; + } + + bool poll(int16_t *table) { + memset(table, 0, Scancode::Limit * sizeof(int16_t)); + + //======== + //Keyboard + //======== + + if(device.keyboard) { + uint8_t state[256]; + if(FAILED(device.keyboard->GetDeviceState(sizeof state, state))) { + device.keyboard->Acquire(); + if(FAILED(device.keyboard->GetDeviceState(sizeof state, state))) { + memset(state, 0, sizeof state); + } + } + + #define key(id) table[keyboard(0)[id]] + + key(Keyboard::Escape) = (bool)(state[0x01] & 0x80); + key(Keyboard::F1 ) = (bool)(state[0x3b] & 0x80); + key(Keyboard::F2 ) = (bool)(state[0x3c] & 0x80); + key(Keyboard::F3 ) = (bool)(state[0x3d] & 0x80); + key(Keyboard::F4 ) = (bool)(state[0x3e] & 0x80); + key(Keyboard::F5 ) = (bool)(state[0x3f] & 0x80); + key(Keyboard::F6 ) = (bool)(state[0x40] & 0x80); + key(Keyboard::F7 ) = (bool)(state[0x41] & 0x80); + key(Keyboard::F8 ) = (bool)(state[0x42] & 0x80); + key(Keyboard::F9 ) = (bool)(state[0x43] & 0x80); + key(Keyboard::F10 ) = (bool)(state[0x44] & 0x80); + key(Keyboard::F11 ) = (bool)(state[0x57] & 0x80); + key(Keyboard::F12 ) = (bool)(state[0x58] & 0x80); + + key(Keyboard::PrintScreen) = (bool)(state[0xb7] & 0x80); + key(Keyboard::ScrollLock ) = (bool)(state[0x46] & 0x80); + key(Keyboard::Pause ) = (bool)(state[0xc5] & 0x80); + key(Keyboard::Tilde ) = (bool)(state[0x29] & 0x80); + + key(Keyboard::Num1) = (bool)(state[0x02] & 0x80); + key(Keyboard::Num2) = (bool)(state[0x03] & 0x80); + key(Keyboard::Num3) = (bool)(state[0x04] & 0x80); + key(Keyboard::Num4) = (bool)(state[0x05] & 0x80); + key(Keyboard::Num5) = (bool)(state[0x06] & 0x80); + key(Keyboard::Num6) = (bool)(state[0x07] & 0x80); + key(Keyboard::Num7) = (bool)(state[0x08] & 0x80); + key(Keyboard::Num8) = (bool)(state[0x09] & 0x80); + key(Keyboard::Num9) = (bool)(state[0x0a] & 0x80); + key(Keyboard::Num0) = (bool)(state[0x0b] & 0x80); + + key(Keyboard::Dash ) = (bool)(state[0x0c] & 0x80); + key(Keyboard::Equal ) = (bool)(state[0x0d] & 0x80); + key(Keyboard::Backspace) = (bool)(state[0x0e] & 0x80); + + key(Keyboard::Insert ) = (bool)(state[0xd2] & 0x80); + key(Keyboard::Delete ) = (bool)(state[0xd3] & 0x80); + key(Keyboard::Home ) = (bool)(state[0xc7] & 0x80); + key(Keyboard::End ) = (bool)(state[0xcf] & 0x80); + key(Keyboard::PageUp ) = (bool)(state[0xc9] & 0x80); + key(Keyboard::PageDown) = (bool)(state[0xd1] & 0x80); + + key(Keyboard::A) = (bool)(state[0x1e] & 0x80); + key(Keyboard::B) = (bool)(state[0x30] & 0x80); + key(Keyboard::C) = (bool)(state[0x2e] & 0x80); + key(Keyboard::D) = (bool)(state[0x20] & 0x80); + key(Keyboard::E) = (bool)(state[0x12] & 0x80); + key(Keyboard::F) = (bool)(state[0x21] & 0x80); + key(Keyboard::G) = (bool)(state[0x22] & 0x80); + key(Keyboard::H) = (bool)(state[0x23] & 0x80); + key(Keyboard::I) = (bool)(state[0x17] & 0x80); + key(Keyboard::J) = (bool)(state[0x24] & 0x80); + key(Keyboard::K) = (bool)(state[0x25] & 0x80); + key(Keyboard::L) = (bool)(state[0x26] & 0x80); + key(Keyboard::M) = (bool)(state[0x32] & 0x80); + key(Keyboard::N) = (bool)(state[0x31] & 0x80); + key(Keyboard::O) = (bool)(state[0x18] & 0x80); + key(Keyboard::P) = (bool)(state[0x19] & 0x80); + key(Keyboard::Q) = (bool)(state[0x10] & 0x80); + key(Keyboard::R) = (bool)(state[0x13] & 0x80); + key(Keyboard::S) = (bool)(state[0x1f] & 0x80); + key(Keyboard::T) = (bool)(state[0x14] & 0x80); + key(Keyboard::U) = (bool)(state[0x16] & 0x80); + key(Keyboard::V) = (bool)(state[0x2f] & 0x80); + key(Keyboard::W) = (bool)(state[0x11] & 0x80); + key(Keyboard::X) = (bool)(state[0x2d] & 0x80); + key(Keyboard::Y) = (bool)(state[0x15] & 0x80); + key(Keyboard::Z) = (bool)(state[0x2c] & 0x80); + + key(Keyboard::LeftBracket ) = (bool)(state[0x1a] & 0x80); + key(Keyboard::RightBracket) = (bool)(state[0x1b] & 0x80); + key(Keyboard::Backslash ) = (bool)(state[0x2b] & 0x80); + key(Keyboard::Semicolon ) = (bool)(state[0x27] & 0x80); + key(Keyboard::Apostrophe ) = (bool)(state[0x28] & 0x80); + key(Keyboard::Comma ) = (bool)(state[0x33] & 0x80); + key(Keyboard::Period ) = (bool)(state[0x34] & 0x80); + key(Keyboard::Slash ) = (bool)(state[0x35] & 0x80); + + key(Keyboard::Keypad1) = (bool)(state[0x4f] & 0x80); + key(Keyboard::Keypad2) = (bool)(state[0x50] & 0x80); + key(Keyboard::Keypad3) = (bool)(state[0x51] & 0x80); + key(Keyboard::Keypad4) = (bool)(state[0x4b] & 0x80); + key(Keyboard::Keypad5) = (bool)(state[0x4c] & 0x80); + key(Keyboard::Keypad6) = (bool)(state[0x4d] & 0x80); + key(Keyboard::Keypad7) = (bool)(state[0x47] & 0x80); + key(Keyboard::Keypad8) = (bool)(state[0x48] & 0x80); + key(Keyboard::Keypad9) = (bool)(state[0x49] & 0x80); + key(Keyboard::Keypad0) = (bool)(state[0x52] & 0x80); + key(Keyboard::Point ) = (bool)(state[0x53] & 0x80); + + key(Keyboard::Add ) = (bool)(state[0x4e] & 0x80); + key(Keyboard::Subtract) = (bool)(state[0x4a] & 0x80); + key(Keyboard::Multiply) = (bool)(state[0x37] & 0x80); + key(Keyboard::Divide ) = (bool)(state[0xb5] & 0x80); + key(Keyboard::Enter ) = (bool)(state[0x9c] & 0x80); + + key(Keyboard::NumLock ) = (bool)(state[0x45] & 0x80); + key(Keyboard::CapsLock) = (bool)(state[0x3a] & 0x80); + + key(Keyboard::Up ) = (bool)(state[0xc8] & 0x80); + key(Keyboard::Down ) = (bool)(state[0xd0] & 0x80); + key(Keyboard::Left ) = (bool)(state[0xcb] & 0x80); + key(Keyboard::Right) = (bool)(state[0xcd] & 0x80); + + key(Keyboard::Tab ) = (bool)(state[0x0f] & 0x80); + key(Keyboard::Return ) = (bool)(state[0x1c] & 0x80); + key(Keyboard::Spacebar) = (bool)(state[0x39] & 0x80); + key(Keyboard::Menu ) = (bool)(state[0xdd] & 0x80); + + key(Keyboard::Shift ) = (bool)(state[0x2a] & 0x80) || (bool)(state[0x36] & 0x80); + key(Keyboard::Control) = (bool)(state[0x1d] & 0x80) || (bool)(state[0x9d] & 0x80); + key(Keyboard::Alt ) = (bool)(state[0x38] & 0x80) || (bool)(state[0xb8] & 0x80); + key(Keyboard::Super ) = (bool)(state[0xdb] & 0x80) || (bool)(state[0xdc] & 0x80); + + #undef key + } + + //===== + //Mouse + //===== + + if(device.mouse) { + DIMOUSESTATE2 state; + if(FAILED(device.mouse->GetDeviceState(sizeof(DIMOUSESTATE2), (void*)&state))) { + device.mouse->Acquire(); + if(FAILED(device.mouse->GetDeviceState(sizeof(DIMOUSESTATE2), (void*)&state))) { + memset(&state, 0, sizeof(DIMOUSESTATE2)); + } + } + + table[mouse(0).axis(0)] = state.lX; + table[mouse(0).axis(1)] = state.lY; + table[mouse(0).axis(2)] = state.lZ / WHEEL_DELTA; + for(unsigned n = 0; n < Mouse::Buttons; n++) { + table[mouse(0).button(n)] = (bool)state.rgbButtons[n]; + } + + //on Windows, 0 = left, 1 = right, 2 = middle + //swap middle and right buttons for consistency with Linux + int16_t temp = table[mouse(0).button(1)]; + table[mouse(0).button(1)] = table[mouse(0).button(2)]; + table[mouse(0).button(2)] = temp; + } + + //========= + //Joypad(s) + //========= + + for(unsigned i = 0; i < Joypad::Count; i++) { + if(!device.gamepad[i]) continue; + + if(FAILED(device.gamepad[i]->Poll())) { + device.gamepad[i]->Acquire(); + continue; + } + + DIJOYSTATE2 state; + device.gamepad[i]->GetDeviceState(sizeof(DIJOYSTATE2), &state); + + //POV hats + for(unsigned n = 0; n < min((unsigned)Joypad::Hats, 4); n++) { + //POV value is in clockwise-hundredth degree units. + unsigned pov = state.rgdwPOV[n]; + //some drivers report a centered POV hat as -1U, others as 65535U. + //>= 36000 will match both, as well as invalid ranges. + if(pov < 36000) { + if(pov >= 31500 || pov <= 4500) table[joypad(i).hat(n)] |= Joypad::HatUp; + if(pov >= 4500 && pov <= 13500) table[joypad(i).hat(n)] |= Joypad::HatRight; + if(pov >= 13500 && pov <= 22500) table[joypad(i).hat(n)] |= Joypad::HatDown; + if(pov >= 22500 && pov <= 31500) table[joypad(i).hat(n)] |= Joypad::HatLeft; + } + } + + //axes + table[joypad(i).axis(0)] = state.lX; + table[joypad(i).axis(1)] = state.lY; + table[joypad(i).axis(2)] = state.lZ; + table[joypad(i).axis(3)] = state.lRx; + table[joypad(i).axis(4)] = state.lRy; + table[joypad(i).axis(5)] = state.lRz; + + //buttons + for(unsigned n = 0; n < min((unsigned)Joypad::Buttons, 128); n++) { + table[joypad(i).button(n)] = (bool)state.rgbButtons[n]; + } + } + + return true; + } + + bool init_joypad(const DIDEVICEINSTANCE *instance) { + unsigned n; + for(n = 0; n < Joypad::Count; n++) { if(!device.gamepad[n]) break; } + if(n >= Joypad::Count) return DIENUM_STOP; + + if(FAILED(device.context->CreateDevice(instance->guidInstance, &device.gamepad[n], 0))) { + return DIENUM_CONTINUE; //continue and try next gamepad + } + + device.gamepad[n]->SetDataFormat(&c_dfDIJoystick2); + device.gamepad[n]->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + device.gamepad[n]->EnumObjects(DI_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS); + + return DIENUM_CONTINUE; + } + + bool init_axis(const DIDEVICEOBJECTINSTANCE *instance) { + signed n; + for(n = Joypad::Count - 1; n >= 0; n--) { if(device.gamepad[n]) break; } + if(n < 0) return DIENUM_STOP; + + DIPROPRANGE range; + range.diph.dwSize = sizeof(DIPROPRANGE); + range.diph.dwHeaderSize = sizeof(DIPROPHEADER); + range.diph.dwHow = DIPH_BYID; + range.diph.dwObj = instance->dwType; + range.lMin = -32768; + range.lMax = +32767; + device.gamepad[n]->SetProperty(DIPROP_RANGE, &range.diph); + + return DIENUM_CONTINUE; + } + + bool init() { + device.context = 0; + device.keyboard = 0; + device.mouse = 0; + for(unsigned i = 0; i < Joypad::Count; i++) device.gamepad[i] = 0; + device.mouseacquired = false; + + DirectInput8Create(GetModuleHandle(0), 0x0800, IID_IDirectInput8, (void**)&device.context, 0); + + device.context->CreateDevice(GUID_SysKeyboard, &device.keyboard, 0); + device.keyboard->SetDataFormat(&c_dfDIKeyboard); + device.keyboard->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + device.keyboard->Acquire(); + + device.context->CreateDevice(GUID_SysMouse, &device.mouse, 0); + device.mouse->SetDataFormat(&c_dfDIMouse2); + HRESULT hr = device.mouse->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + device.mouse->Acquire(); + + device.context->EnumDevices(DI8DEVCLASS_GAMECTRL, DI_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY); + + return true; + } + + void term() { + if(device.keyboard) { + device.keyboard->Unacquire(); + device.keyboard->Release(); + device.keyboard = 0; + } + + if(device.mouse) { + device.mouse->Unacquire(); + device.mouse->Release(); + device.mouse = 0; + } + + for(unsigned i = 0; i < Joypad::Count; i++) { + if(device.gamepad[i]) { + device.gamepad[i]->Unacquire(); + device.gamepad[i]->Release(); + device.gamepad[i] = 0; + } + } + + if(device.context) { + device.context->Release(); + device.context = 0; + } + } + + bool acquire() { + if(!device.mouse) return false; + if(acquired() == false) { + device.mouse->Unacquire(); + device.mouse->SetCooperativeLevel(settings.handle, DISCL_EXCLUSIVE | DISCL_FOREGROUND); + device.mouse->Acquire(); + device.mouseacquired = true; + } + return true; + } + + bool unacquire() { + if(!device.mouse) return false; + if(acquired() == true) { + device.mouse->Unacquire(); + device.mouse->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + device.mouse->Acquire(); + device.mouseacquired = false; + } + return true; + } + + bool acquired() { + return device.mouseacquired; + } + + pInputDI() { + device.context = 0; + device.keyboard = 0; + device.mouse = 0; + for(unsigned i = 0; i < Joypad::Count; i++) device.gamepad[i] = 0; + device.mouseacquired = false; + + settings.handle = 0; + } + + ~pInputDI() { term(); } +}; + +BOOL CALLBACK DI_EnumJoypadsCallback(const DIDEVICEINSTANCE *instance, void *p) { + return ((pInputDI*)p)->init_joypad(instance); +} + +BOOL CALLBACK DI_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE *instance, void *p) { + return ((pInputDI*)p)->init_axis(instance); +} + +DeclareInput(DI) + +}; diff --git a/ruby/input/rawinput.cpp b/ruby/input/rawinput.cpp new file mode 100755 index 00000000..bba81286 --- /dev/null +++ b/ruby/input/rawinput.cpp @@ -0,0 +1,807 @@ +//RawInput driver +//author: byuu + +//this driver utilizes RawInput (WM_INPUT) to capture keyboard and mouse input. +//although this requires WinXP or newer, it is the only way to uniquely identify +//and independently map multiple keyboards and mice. DirectInput merges all +//keyboards and mice into one device per. +// +//as WM_INPUT lacks specific RAWINPUT structures for gamepads, giving only raw +//data, and because DirectInput supports up to 16 joypads, DirectInput is used +//for joypad mapping. +// +//further, Xbox 360 controllers are explicitly detected and supported through +//XInput. this is because under DirectInput, the LT / RT (trigger) buttons are +//merged into a single Z-axis -- making it impossible to detect both buttons +//being pressed at the same time. with XInput, the state of both trigger +//buttons can be read independently. +// +//so in essence, this is actually more of a hybrid driver. + +#define DIRECTINPUT_VERSION 0x0800 +#include +#include + +namespace ruby { + +static DWORD WINAPI RawInputThreadProc(void*); +static LRESULT CALLBACK RawInputWindowProc(HWND, UINT, WPARAM, LPARAM); + +class RawInput { +public: + HANDLE mutex; + HWND hwnd; + bool initialized; + bool ready; + + struct Device { + HANDLE handle; + }; + + struct Keyboard : Device { + bool state[nall::Keyboard::Size]; + + void update(RAWINPUT *input) { + unsigned code = input->data.keyboard.MakeCode; + unsigned flags = input->data.keyboard.Flags; + + #define map(id, flag, name) if(code == id) state[name] = (bool)(flags == flag); + map(0x0001, 0, nall::Keyboard::Escape) + map(0x003b, 0, nall::Keyboard::F1) + map(0x003c, 0, nall::Keyboard::F2) + map(0x003d, 0, nall::Keyboard::F3) + map(0x003e, 0, nall::Keyboard::F4) + map(0x003f, 0, nall::Keyboard::F5) + map(0x0040, 0, nall::Keyboard::F6) + map(0x0041, 0, nall::Keyboard::F7) + map(0x0042, 0, nall::Keyboard::F8) + map(0x0043, 0, nall::Keyboard::F9) + map(0x0044, 0, nall::Keyboard::F10) + map(0x0057, 0, nall::Keyboard::F11) + map(0x0058, 0, nall::Keyboard::F12) + + map(0x0037, 2, nall::Keyboard::PrintScreen) + map(0x0046, 0, nall::Keyboard::ScrollLock) + map(0x001d, 4, nall::Keyboard::Pause) + map(0x0029, 0, nall::Keyboard::Tilde) + + map(0x0002, 0, nall::Keyboard::Num1) + map(0x0003, 0, nall::Keyboard::Num2) + map(0x0004, 0, nall::Keyboard::Num3) + map(0x0005, 0, nall::Keyboard::Num4) + map(0x0006, 0, nall::Keyboard::Num5) + map(0x0007, 0, nall::Keyboard::Num6) + map(0x0008, 0, nall::Keyboard::Num7) + map(0x0009, 0, nall::Keyboard::Num8) + map(0x000a, 0, nall::Keyboard::Num9) + map(0x000b, 0, nall::Keyboard::Num0) + + map(0x000c, 0, nall::Keyboard::Dash) + map(0x000d, 0, nall::Keyboard::Equal) + map(0x000e, 0, nall::Keyboard::Backspace) + + map(0x0052, 2, nall::Keyboard::Insert) + map(0x0053, 2, nall::Keyboard::Delete) + map(0x0047, 2, nall::Keyboard::Home) + map(0x004f, 2, nall::Keyboard::End) + map(0x0049, 2, nall::Keyboard::PageUp) + map(0x0051, 2, nall::Keyboard::PageDown) + + map(0x001e, 0, nall::Keyboard::A) + map(0x0030, 0, nall::Keyboard::B) + map(0x002e, 0, nall::Keyboard::C) + map(0x0020, 0, nall::Keyboard::D) + map(0x0012, 0, nall::Keyboard::E) + map(0x0021, 0, nall::Keyboard::F) + map(0x0022, 0, nall::Keyboard::G) + map(0x0023, 0, nall::Keyboard::H) + map(0x0017, 0, nall::Keyboard::I) + map(0x0024, 0, nall::Keyboard::J) + map(0x0025, 0, nall::Keyboard::K) + map(0x0026, 0, nall::Keyboard::L) + map(0x0032, 0, nall::Keyboard::M) + map(0x0031, 0, nall::Keyboard::N) + map(0x0018, 0, nall::Keyboard::O) + map(0x0019, 0, nall::Keyboard::P) + map(0x0010, 0, nall::Keyboard::Q) + map(0x0013, 0, nall::Keyboard::R) + map(0x001f, 0, nall::Keyboard::S) + map(0x0014, 0, nall::Keyboard::T) + map(0x0016, 0, nall::Keyboard::U) + map(0x002f, 0, nall::Keyboard::V) + map(0x0011, 0, nall::Keyboard::W) + map(0x002d, 0, nall::Keyboard::X) + map(0x0015, 0, nall::Keyboard::Y) + map(0x002c, 0, nall::Keyboard::Z) + + map(0x001a, 0, nall::Keyboard::LeftBracket) + map(0x001b, 0, nall::Keyboard::RightBracket) + map(0x002b, 0, nall::Keyboard::Backslash) + map(0x0027, 0, nall::Keyboard::Semicolon) + map(0x0028, 0, nall::Keyboard::Apostrophe) + map(0x0033, 0, nall::Keyboard::Comma) + map(0x0034, 0, nall::Keyboard::Period) + map(0x0035, 0, nall::Keyboard::Slash) + + map(0x004f, 0, nall::Keyboard::Keypad1) + map(0x0050, 0, nall::Keyboard::Keypad2) + map(0x0051, 0, nall::Keyboard::Keypad3) + map(0x004b, 0, nall::Keyboard::Keypad4) + map(0x004c, 0, nall::Keyboard::Keypad5) + map(0x004d, 0, nall::Keyboard::Keypad6) + map(0x0047, 0, nall::Keyboard::Keypad7) + map(0x0048, 0, nall::Keyboard::Keypad8) + map(0x0049, 0, nall::Keyboard::Keypad9) + map(0x0052, 0, nall::Keyboard::Keypad0) + + map(0x0053, 0, nall::Keyboard::Point) + map(0x001c, 2, nall::Keyboard::Enter) + map(0x004e, 0, nall::Keyboard::Add) + map(0x004a, 0, nall::Keyboard::Subtract) + map(0x0037, 0, nall::Keyboard::Multiply) + map(0x0035, 2, nall::Keyboard::Divide) + + map(0x0045, 0, nall::Keyboard::NumLock) + map(0x003a, 0, nall::Keyboard::CapsLock) + + //Pause signals 0x1d:4 + 0x45:0, whereas NumLock signals only 0x45:0. + //this makes it impractical to detect both Pause+NumLock independently. + //workaround: always detect Pause; detect NumLock only when Pause is released. + if(state[nall::Keyboard::Pause]) state[nall::Keyboard::NumLock] = false; + + map(0x0048, 2, nall::Keyboard::Up) + map(0x0050, 2, nall::Keyboard::Down) + map(0x004b, 2, nall::Keyboard::Left) + map(0x004d, 2, nall::Keyboard::Right) + + map(0x000f, 0, nall::Keyboard::Tab) + map(0x001c, 0, nall::Keyboard::Return) + map(0x0039, 0, nall::Keyboard::Spacebar) + map(0x005d, 2, nall::Keyboard::Menu) + + //merge left and right modifiers to one ID + if(code == 0x002a && flags == 0) state[nall::Keyboard::Shift] = 1; //left shift + if(code == 0x002a && flags == 1) state[nall::Keyboard::Shift] = 0; + if(code == 0x0036 && flags == 0) state[nall::Keyboard::Shift] = 1; //right shift + if(code == 0x0036 && flags == 1) state[nall::Keyboard::Shift] = 0; + + if(code == 0x001d && flags == 0) state[nall::Keyboard::Control] = 1; //left control + if(code == 0x001d && flags == 1) state[nall::Keyboard::Control] = 0; + if(code == 0x001d && flags == 2) state[nall::Keyboard::Control] = 1; //right control + if(code == 0x001d && flags == 3) state[nall::Keyboard::Control] = 0; + + if(code == 0x0038 && flags == 0) state[nall::Keyboard::Alt] = 1; //left alt + if(code == 0x0038 && flags == 1) state[nall::Keyboard::Alt] = 0; + if(code == 0x0038 && flags == 2) state[nall::Keyboard::Alt] = 1; //right alt + if(code == 0x0038 && flags == 3) state[nall::Keyboard::Alt] = 0; + + if(code == 0x005b && flags == 2) state[nall::Keyboard::Super] = 1; //left super + if(code == 0x005b && flags == 3) state[nall::Keyboard::Super] = 0; + if(code == 0x005c && flags == 2) state[nall::Keyboard::Super] = 1; //right super + if(code == 0x005c && flags == 3) state[nall::Keyboard::Super] = 0; + #undef map + } + + Keyboard() { + for(unsigned i = 0; i < nall::Keyboard::Size; i++) state[i] = false; + } + }; + + struct Mouse : Device { + signed xDistance; + signed yDistance; + signed zDistance; + unsigned buttonState; + + void sync() { + xDistance = 0; + yDistance = 0; + zDistance = 0; + } + + void update(RAWINPUT *input) { + if((input->data.mouse.usFlags & 1) == MOUSE_MOVE_RELATIVE) { + xDistance += input->data.mouse.lLastX; + yDistance += input->data.mouse.lLastY; + } + + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) buttonState |= 1 << 0; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP ) buttonState &=~ 1 << 0; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) buttonState |= 1 << 2; //swap middle and right buttons, + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP ) buttonState &=~ 1 << 2; //for consistency with Linux: + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) buttonState |= 1 << 1; //left = 0, middle = 1, right = 2 + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP ) buttonState &=~ 1 << 1; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) buttonState |= 1 << 3; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP ) buttonState &=~ 1 << 3; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) buttonState |= 1 << 4; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP ) buttonState &=~ 1 << 4; + + if(input->data.mouse.usButtonFlags & RI_MOUSE_WHEEL) { + zDistance += (int16_t)input->data.mouse.usButtonData; + } + } + + Mouse() { + xDistance = yDistance = zDistance = 0; + buttonState = 0; + } + }; + + //keep track of gamepads for the sole purpose of distinguishing XInput devices + //from all other devices. this is necessary, as DirectInput does not provide + //a way to retrieve the necessary RIDI_DEVICENAME string. + struct Gamepad : Device { + bool isXInputDevice; + uint16_t vendorId; + uint16_t productId; + }; + + linear_vector lkeyboard; + linear_vector lmouse; + linear_vector lgamepad; + + LRESULT window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + if(msg == WM_INPUT) { + unsigned size = 0; + GetRawInputData((HRAWINPUT)lparam, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER)); + RAWINPUT *input = new RAWINPUT[size]; + GetRawInputData((HRAWINPUT)lparam, RID_INPUT, input, &size, sizeof(RAWINPUTHEADER)); + WaitForSingleObject(mutex, INFINITE); + + if(input->header.dwType == RIM_TYPEKEYBOARD) { + for(unsigned i = 0; i < lkeyboard.size(); i++) { + if(input->header.hDevice == lkeyboard[i].handle) { + lkeyboard[i].update(input); + break; + } + } + } else if(input->header.dwType == RIM_TYPEMOUSE) { + for(unsigned i = 0; i < lmouse.size(); i++) { + if(input->header.hDevice == lmouse[i].handle) { + lmouse[i].update(input); + break; + } + } + } + + ReleaseMutex(mutex); + //allow propogation of WM_INPUT message + LRESULT result = DefRawInputProc(&input, size, sizeof(RAWINPUTHEADER)); + delete[] input; + return result; + } + + return DefWindowProc(hwnd, msg, wparam, lparam); + } + + //this is used to sort device IDs + struct DevicePool { + HANDLE handle; + wchar_t name[4096]; + bool operator<(const DevicePool &pool) const { return wcscmp(name, pool.name) < 0; } + }; + + int main() { + //create an invisible window to act as a sink, capturing all WM_INPUT messages + WNDCLASS wc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = RawInputWindowProc; + wc.lpszClassName = L"RawInputClass"; + wc.lpszMenuName = 0; + wc.style = CS_VREDRAW | CS_HREDRAW; + RegisterClass(&wc); + + hwnd = CreateWindow(L"RawInputClass", L"RawInputClass", WS_POPUP, + 0, 0, 64, 64, 0, 0, GetModuleHandle(0), 0); + + //enumerate all HID devices + unsigned devices = 0; + GetRawInputDeviceList(NULL, &devices, sizeof(RAWINPUTDEVICELIST)); + RAWINPUTDEVICELIST *list = new RAWINPUTDEVICELIST[devices]; + GetRawInputDeviceList(list, &devices, sizeof(RAWINPUTDEVICELIST)); + + //sort all devices by name. this has two important properties: + //1) it consistently orders peripherals, so mapped IDs remain constant + //2) it sorts the virtual keyboard and mouse to the bottom of the list + // (real devices start with \\?\HID#, virtual with \\?\Root#) + DevicePool pool[devices]; + for(unsigned i = 0; i < devices; i++) { + pool[i].handle = list[i].hDevice; + unsigned size = sizeof(pool[i].name) - 1; + GetRawInputDeviceInfo(list[i].hDevice, RIDI_DEVICENAME, &pool[i].name, &size); + } + nall::sort(pool, devices); + delete[] list; + + for(unsigned i = 0; i < devices; i++) { + RID_DEVICE_INFO info; + info.cbSize = sizeof(RID_DEVICE_INFO); + + unsigned size = info.cbSize; + GetRawInputDeviceInfo(pool[i].handle, RIDI_DEVICEINFO, &info, &size); + + if(info.dwType == RIM_TYPEKEYBOARD) { + unsigned n = lkeyboard.size(); + lkeyboard[n].handle = pool[i].handle; + } else if(info.dwType == RIM_TYPEMOUSE) { + unsigned n = lmouse.size(); + lmouse[n].handle = pool[i].handle; + } else if(info.dwType == RIM_TYPEHID) { + //if this is a gamepad or joystick device ... + if(info.hid.usUsagePage == 1 && (info.hid.usUsage == 4 || info.hid.usUsage == 5)) { + //... then cache device information for later use + unsigned n = lgamepad.size(); + lgamepad[n].handle = pool[i].handle; + lgamepad[n].vendorId = (uint16_t)info.hid.dwVendorId; + lgamepad[n].productId = (uint16_t)info.hid.dwProductId; + + //per MSDN: XInput devices have "IG_" in their device strings, + //which is how they should be identified. + string p = utf8_t(pool[i].name); + if(auto position = strpos(p, "IG_")) { + lgamepad[n].isXInputDevice = true; + } else { + lgamepad[n].isXInputDevice = false; + } + } + } + } + + RAWINPUTDEVICE device[2]; + //capture all keyboard input + device[0].usUsagePage = 1; + device[0].usUsage = 6; + device[0].dwFlags = RIDEV_INPUTSINK; + device[0].hwndTarget = hwnd; + //capture all mouse input + device[1].usUsagePage = 1; + device[1].usUsage = 2; + device[1].dwFlags = RIDEV_INPUTSINK; + device[1].hwndTarget = hwnd; + RegisterRawInputDevices(device, 2, sizeof(RAWINPUTDEVICE)); + + WaitForSingleObject(mutex, INFINITE); + ready = true; + ReleaseMutex(mutex); + + while(true) { + MSG msg; + GetMessage(&msg, hwnd, 0, 0); + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return 0; + } + + RawInput() : initialized(false), ready(false) { + } +}; + +static RawInput rawinput; + +DWORD WINAPI RawInputThreadProc(void*) { + return rawinput.main(); +} + +LRESULT CALLBACK RawInputWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + return rawinput.window_proc(hwnd, msg, wparam, lparam); +} + +class XInput { +public: + HMODULE libxinput; + DWORD WINAPI (*pXInputGetState)(DWORD, XINPUT_STATE*); + + struct Gamepad { + unsigned id; + + int16_t hat; + int16_t axis[6]; + bool button[10]; + + void poll(XINPUT_STATE &state) { + hat = Joypad::HatCenter; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ) hat |= Joypad::HatUp; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) hat |= Joypad::HatRight; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) hat |= Joypad::HatDown; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) hat |= Joypad::HatLeft; + + axis[0] = (int16_t)state.Gamepad.sThumbLX; + axis[1] = (int16_t)state.Gamepad.sThumbLY; + axis[2] = (int16_t)state.Gamepad.sThumbRX; + axis[3] = (int16_t)state.Gamepad.sThumbRY; + + //transform left and right trigger ranges: + //from: 0 (low, eg released) to 255 (high, eg pressed all the way down) + //to: +32767 (low) to -32768 (high) + uint16_t triggerX = state.Gamepad.bLeftTrigger; + uint16_t triggerY = state.Gamepad.bRightTrigger; + + triggerX = (triggerX << 8) | triggerX; + triggerY = (triggerY << 8) | triggerY; + + axis[4] = (~triggerX) - 32768; + axis[5] = (~triggerY) - 32768; + + button[0] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_A); + button[1] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_B); + button[2] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_X); + button[3] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y); + button[4] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK); + button[5] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_START); + button[6] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER); + button[7] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER); + button[8] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB); + button[9] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB); + } + + Gamepad() { + hat = Joypad::HatCenter; + for(unsigned n = 0; n < 6; n++) axis[n] = 0; + for(unsigned n = 0; n < 10; n++) button[n] = false; + } + }; + + linear_vector lgamepad; + + void poll() { + if(!pXInputGetState) return; + + for(unsigned i = 0; i < lgamepad.size(); i++) { + XINPUT_STATE state; + DWORD result = pXInputGetState(lgamepad[i].id, &state); + if(result == ERROR_SUCCESS) lgamepad[i].poll(state); + } + } + + void init() { + if(!pXInputGetState) return; + + //XInput only supports up to four controllers + for(unsigned i = 0; i <= 3; i++) { + XINPUT_STATE state; + DWORD result = pXInputGetState(i, &state); + if(result == ERROR_SUCCESS) { + //valid controller detected, add to gamepad list + unsigned n = lgamepad.size(); + lgamepad[n].id = i; + } + } + } + + XInput() : pXInputGetState(0) { + //bind xinput1 dynamically, as it does not ship with Windows Vista or below + libxinput = LoadLibraryA("xinput1_3.dll"); + if(!libxinput) libxinput = LoadLibraryA("xinput1_2.dll"); + if(!libxinput) libxinput = LoadLibraryA("xinput1_1.dll"); + if(!libxinput) return; + pXInputGetState = (DWORD WINAPI (*)(DWORD, XINPUT_STATE*))GetProcAddress(libxinput, "XInputGetState"); + } + + ~XInput() { + if(libxinput) FreeLibrary(libxinput); + } +}; + +static BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE*, void*); +static BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE*, void*); + +class DirectInput { +public: + HWND handle; + LPDIRECTINPUT8 context; + struct Gamepad { + LPDIRECTINPUTDEVICE8 handle; + + int16_t hat[4]; + int16_t axis[6]; + bool button[128]; + + void poll(DIJOYSTATE2 &state) { + //POV hats + for(unsigned n = 0; n < 4; n++) { + hat[n] = Joypad::HatCenter; + + //POV value is in clockwise-hundredth degree units + unsigned pov = state.rgdwPOV[n]; + + //some drivers report a centered POV hat as -1U, others as 65535U. + //>= 36000 will match both, as well as invalid ranges. + if(pov >= 36000) continue; + + if(pov >= 31500 || pov <= 4500) hat[n] |= Joypad::HatUp; + if(pov >= 4500 && pov <= 13500) hat[n] |= Joypad::HatRight; + if(pov >= 13500 && pov <= 22500) hat[n] |= Joypad::HatDown; + if(pov >= 22500 && pov <= 31500) hat[n] |= Joypad::HatLeft; + } + + //axes + axis[0] = state.lX; + axis[1] = state.lY; + axis[2] = state.lZ; + axis[3] = state.lRx; + axis[4] = state.lRy; + axis[5] = state.lRz; + + //buttons + for(unsigned n = 0; n < 128; n++) { + button[n] = (bool)state.rgbButtons[n]; + } + } + + Gamepad() { + handle = 0; + for(unsigned n = 0; n < 4; n++) hat[n] = Joypad::HatCenter; + for(unsigned n = 0; n < 6; n++) axis[n] = 0; + for(unsigned n = 0; n < 128; n++) button[n] = false; + } + }; + linear_vector lgamepad; + + void poll() { + for(unsigned i = 0; i < lgamepad.size(); i++) { + if(FAILED(lgamepad[i].handle->Poll())) { + lgamepad[i].handle->Acquire(); + continue; + } + + DIJOYSTATE2 state; + lgamepad[i].handle->GetDeviceState(sizeof(DIJOYSTATE2), &state); + lgamepad[i].poll(state); + } + } + + bool init_joypad(const DIDEVICEINSTANCE *instance) { + //if this is an XInput device, do not acquire it via DirectInput ... + //the XInput driver above will handle said device. + for(unsigned i = 0; i < rawinput.lgamepad.size(); i++) { + uint32_t guid = MAKELONG(rawinput.lgamepad[i].vendorId, rawinput.lgamepad[i].productId); + if(guid == instance->guidProduct.Data1) { + if(rawinput.lgamepad[i].isXInputDevice == true) { + return DIENUM_CONTINUE; + } + } + } + + if(FAILED(context->CreateDevice(instance->guidInstance, &device, 0))) { + return DIENUM_CONTINUE; + } + + device->SetDataFormat(&c_dfDIJoystick2); + device->SetCooperativeLevel(handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + device->EnumObjects(DirectInput_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS); + unsigned n = lgamepad.size(); + lgamepad[n].handle = device; + return DIENUM_CONTINUE; + } + + bool init_axis(const DIDEVICEOBJECTINSTANCE *instance) { + DIPROPRANGE range; + range.diph.dwSize = sizeof(DIPROPRANGE); + range.diph.dwHeaderSize = sizeof(DIPROPHEADER); + range.diph.dwHow = DIPH_BYID; + range.diph.dwObj = instance->dwType; + range.lMin = -32768; + range.lMax = +32767; + device->SetProperty(DIPROP_RANGE, &range.diph); + return DIENUM_CONTINUE; + } + + void init(HWND handle_) { + handle = handle_; + DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&context, 0); + context->EnumDevices(DI8DEVCLASS_GAMECTRL, DirectInput_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY); + } + + void term() { + for(unsigned i = 0; i < lgamepad.size(); i++) { + lgamepad[i].handle->Unacquire(); + lgamepad[i].handle->Release(); + } + lgamepad.reset(); + + if(context) { + context->Release(); + context = 0; + } + } + +private: + LPDIRECTINPUTDEVICE8 device; +}; + +BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE *instance, void *p) { + return ((DirectInput*)p)->init_joypad(instance); +} + +BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE *instance, void *p) { + return ((DirectInput*)p)->init_axis(instance); +} + +class pInputRaw { +public: + XInput xinput; + DirectInput dinput; + + bool acquire_mouse; + bool cursor_visible; + + struct { + HWND handle; + } settings; + + bool cap(const string& name) { + if(name == Input::Handle) return true; + if(name == Input::KeyboardSupport) return true; + if(name == Input::MouseSupport) return true; + if(name == Input::JoypadSupport) return true; + return false; + } + + any get(const string& name) { + if(name == Input::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Input::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + return false; + } + + bool acquire() { + acquire_mouse = true; + if(cursor_visible == true) { + ShowCursor(cursor_visible = false); + } + return acquired(); + } + + bool unacquire() { + acquire_mouse = false; + ReleaseCapture(); + ClipCursor(NULL); + if(cursor_visible == false) { + ShowCursor(cursor_visible = true); + } + return true; + } + + bool acquired() { + if(acquire_mouse == true) { + SetFocus(settings.handle); + SetCapture(settings.handle); + RECT rc; + GetWindowRect(settings.handle, &rc); + ClipCursor(&rc); + } + return GetCapture() == settings.handle; + } + + bool poll(int16_t *table) { + memset(table, 0, Scancode::Limit * sizeof(int16_t)); + + WaitForSingleObject(rawinput.mutex, INFINITE); + + //========= + //Keyboards + //========= + for(unsigned i = 0; i < min(rawinput.lkeyboard.size(), (unsigned)Keyboard::Count); i++) { + for(unsigned n = 0; n < nall::Keyboard::Size; n++) { + //using keyboard(0)|= instead of keyboard(i)= merges all keyboards to KB0 + //this is done to favor ease of mapping over flexibility (eg share laptop+USB keyboard mapping) + table[keyboard(0).key(n)] |= rawinput.lkeyboard[i].state[n]; + } + } + + //==== + //Mice + //==== + for(unsigned i = 0; i < min(rawinput.lmouse.size(), (unsigned)Mouse::Count); i++) { + table[mouse(i).axis(0)] = rawinput.lmouse[i].xDistance; + table[mouse(i).axis(1)] = rawinput.lmouse[i].yDistance; + table[mouse(i).axis(2)] = rawinput.lmouse[i].zDistance; + + for(unsigned n = 0; n < min(5U, (unsigned)Mouse::Buttons); n++) { + table[mouse(i).button(n)] = (bool)(rawinput.lmouse[i].buttonState & (1 << n)); + } + + rawinput.lmouse[i].sync(); + } + + ReleaseMutex(rawinput.mutex); + + unsigned joy = 0; + + //================== + //XInput controllers + //================== + xinput.poll(); + for(unsigned i = 0; i < xinput.lgamepad.size(); i++) { + if(joy >= Joypad::Count) break; + + table[joypad(joy).hat(0)] = xinput.lgamepad[i].hat; + + for(unsigned axis = 0; axis < min(6U, (unsigned)Joypad::Axes); axis++) { + table[joypad(joy).axis(axis)] = xinput.lgamepad[i].axis[axis]; + } + + for(unsigned button = 0; button < min(10U, (unsigned)Joypad::Buttons); button++) { + table[joypad(joy).button(button)] = xinput.lgamepad[i].button[button]; + } + + joy++; + } + + //======================= + //DirectInput controllers + //======================= + dinput.poll(); + for(unsigned i = 0; i < dinput.lgamepad.size(); i++) { + if(joy >= Joypad::Count) break; + + for(unsigned hat = 0; hat < min(4U, (unsigned)Joypad::Hats); hat++) { + table[joypad(joy).hat(hat)] = dinput.lgamepad[i].hat[hat]; + } + + for(unsigned axis = 0; axis < min(6U, (unsigned)Joypad::Axes); axis++) { + table[joypad(joy).axis(axis)] = dinput.lgamepad[i].axis[axis]; + } + + for(unsigned button = 0; button < min(128U, (unsigned)Joypad::Buttons); button++) { + table[joypad(joy).button(button)] = dinput.lgamepad[i].button[button]; + } + + joy++; + } + + return true; + } + + bool init() { + //only spawn RawInput processing thread one time + if(rawinput.initialized == false) { + rawinput.initialized = true; + rawinput.mutex = CreateMutex(NULL, FALSE, NULL); + CreateThread(NULL, 0, RawInputThreadProc, 0, 0, NULL); + + //RawInput device calibration needs to finish before initializing DirectInput; + //as it needs device GUIDs to distinguish XInput devices from ordinary joypads. + bool ready = false; + do { + Sleep(10); + WaitForSingleObject(rawinput.mutex, INFINITE); + ready = rawinput.ready; + ReleaseMutex(rawinput.mutex); + } while(ready == false); + } + + xinput.init(); + dinput.init(settings.handle); + + acquire_mouse = false; + cursor_visible = true; + return true; + } + + void term() { + unacquire(); + dinput.term(); + } + + pInputRaw() { + } +}; + +DeclareInput(Raw) + +}; diff --git a/ruby/input/sdl.cpp b/ruby/input/sdl.cpp new file mode 100755 index 00000000..9986a007 --- /dev/null +++ b/ruby/input/sdl.cpp @@ -0,0 +1,230 @@ +//================ +//SDL input driver +//================ +//Keyboard and mouse are controlled directly via Xlib, +//as SDL cannot capture input from windows it does not create itself. +//SDL is used only to handle joysticks / gamepads. + +#include +#include +#include + +namespace ruby { + +struct pInputSDL { + #include "xlibkeys.hpp" + + struct { + Display *display; + Window rootwindow; + Cursor InvisibleCursor; + SDL_Joystick *gamepad[Joypad::Count]; + + unsigned screenwidth, screenheight; + unsigned relativex, relativey; + bool mouseacquired; + + //mouse device settings + int accel_numerator; + int accel_denominator; + int threshold; + } device; + + struct { + uintptr_t handle; + } settings; + + bool cap(const string& name) { + if(name == Input::Handle) return true; + if(name == Input::KeyboardSupport) return true; + if(name == Input::MouseSupport) return true; + if(name == Input::JoypadSupport) return true; + return false; + } + + any get(const string& name) { + if(name == Input::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any &value) { + if(name == Input::Handle) { + settings.handle = any_cast(value); + return true; + } + + return false; + } + + bool acquire() { + if(acquired()) return true; + + if(XGrabPointer(device.display, settings.handle, True, 0, GrabModeAsync, GrabModeAsync, + device.rootwindow, device.InvisibleCursor, CurrentTime) == GrabSuccess) { + //backup existing cursor acceleration settings + XGetPointerControl(device.display, &device.accel_numerator, &device.accel_denominator, &device.threshold); + + //disable cursor acceleration + XChangePointerControl(device.display, True, False, 1, 1, 0); + + //center cursor (so that first relative poll returns 0, 0 if mouse has not moved) + XWarpPointer(device.display, None, device.rootwindow, 0, 0, 0, 0, device.screenwidth / 2, device.screenheight / 2); + + return device.mouseacquired = true; + } else { + return device.mouseacquired = false; + } + } + + bool unacquire() { + if(acquired()) { + //restore cursor acceleration and release cursor + XChangePointerControl(device.display, True, True, device.accel_numerator, device.accel_denominator, device.threshold); + XUngrabPointer(device.display, CurrentTime); + device.mouseacquired = false; + } + return true; + } + + bool acquired() { + return device.mouseacquired; + } + + bool poll(int16_t *table) { + memset(table, 0, Scancode::Limit * sizeof(int16_t)); + + //======== + //Keyboard + //======== + + x_poll(table); + + //===== + //Mouse + //===== + + Window root_return, child_return; + int root_x_return = 0, root_y_return = 0; + int win_x_return = 0, win_y_return = 0; + unsigned int mask_return = 0; + XQueryPointer(device.display, settings.handle, + &root_return, &child_return, &root_x_return, &root_y_return, + &win_x_return, &win_y_return, &mask_return); + + if(acquired()) { + XWindowAttributes attributes; + XGetWindowAttributes(device.display, settings.handle, &attributes); + + //absolute -> relative conversion + table[mouse(0).axis(0)] = (int16_t)(root_x_return - device.screenwidth / 2); + table[mouse(0).axis(1)] = (int16_t)(root_y_return - device.screenheight / 2); + + if(table[mouse(0).axis(0)] != 0 || table[mouse(0).axis(1)] != 0) { + //if mouse movement occurred, re-center mouse for next poll + XWarpPointer(device.display, None, device.rootwindow, 0, 0, 0, 0, device.screenwidth / 2, device.screenheight / 2); + } + } else { + table[mouse(0).axis(0)] = (int16_t)(root_x_return - device.relativex); + table[mouse(0).axis(1)] = (int16_t)(root_y_return - device.relativey); + + device.relativex = root_x_return; + device.relativey = root_y_return; + } + + //manual device polling is limited to only five buttons ... + table[mouse(0).button(0)] = (bool)(mask_return & Button1Mask); + table[mouse(0).button(1)] = (bool)(mask_return & Button2Mask); + table[mouse(0).button(2)] = (bool)(mask_return & Button3Mask); + table[mouse(0).button(3)] = (bool)(mask_return & Button4Mask); + table[mouse(0).button(4)] = (bool)(mask_return & Button5Mask); + + //========= + //Joypad(s) + //========= + + SDL_JoystickUpdate(); + for(unsigned i = 0; i < Joypad::Count; i++) { + if(!device.gamepad[i]) continue; + + //POV hats + unsigned hats = min((unsigned)Joypad::Hats, SDL_JoystickNumHats(device.gamepad[i])); + for(unsigned hat = 0; hat < hats; hat++) { + uint8_t state = SDL_JoystickGetHat(device.gamepad[i], hat); + if(state & SDL_HAT_UP ) table[joypad(i).hat(hat)] |= Joypad::HatUp; + if(state & SDL_HAT_RIGHT) table[joypad(i).hat(hat)] |= Joypad::HatRight; + if(state & SDL_HAT_DOWN ) table[joypad(i).hat(hat)] |= Joypad::HatDown; + if(state & SDL_HAT_LEFT ) table[joypad(i).hat(hat)] |= Joypad::HatLeft; + } + + //axes + unsigned axes = min((unsigned)Joypad::Axes, SDL_JoystickNumAxes(device.gamepad[i])); + for(unsigned axis = 0; axis < axes; axis++) { + table[joypad(i).axis(axis)] = (int16_t)SDL_JoystickGetAxis(device.gamepad[i], axis); + } + + //buttons + for(unsigned button = 0; button < Joypad::Buttons; button++) { + table[joypad(i).button(button)] = (bool)SDL_JoystickGetButton(device.gamepad[i], button); + } + } + + return true; + } + + bool init() { + x_init(); + SDL_InitSubSystem(SDL_INIT_JOYSTICK); + SDL_JoystickEventState(SDL_IGNORE); + + device.display = XOpenDisplay(0); + device.rootwindow = DefaultRootWindow(device.display); + XWindowAttributes attributes; + XGetWindowAttributes(device.display, device.rootwindow, &attributes); + device.screenwidth = attributes.width; + device.screenheight = attributes.height; + + //Xlib: "because XShowCursor(false) would be too easy." + //create a fully transparent cursor named InvisibleCursor, + //for use while acquire() / XGrabPointer() is active. + Pixmap pixmap; + XColor black, unused; + static char invisible_data[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + Colormap colormap = DefaultColormap(device.display, DefaultScreen(device.display)); + XAllocNamedColor(device.display, colormap, "black", &black, &unused); + pixmap = XCreateBitmapFromData(device.display, settings.handle, invisible_data, 8, 8); + device.InvisibleCursor = XCreatePixmapCursor(device.display, pixmap, pixmap, &black, &black, 0, 0); + XFreePixmap(device.display, pixmap); + XFreeColors(device.display, colormap, &black.pixel, 1, 0); + + device.mouseacquired = false; + device.relativex = 0; + device.relativey = 0; + + unsigned joypads = min((unsigned)Joypad::Count, SDL_NumJoysticks()); + for(unsigned i = 0; i < joypads; i++) device.gamepad[i] = SDL_JoystickOpen(i); + + return true; + } + + void term() { + unacquire(); + XFreeCursor(device.display, device.InvisibleCursor); + + for(unsigned i = 0; i < Joypad::Count; i++) { + if(device.gamepad[i]) SDL_JoystickClose(device.gamepad[i]); + device.gamepad[i] = 0; + } + + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + XCloseDisplay(device.display); + } + + pInputSDL() { + for(unsigned i = 0; i < Joypad::Count; i++) device.gamepad[i] = 0; + settings.handle = 0; + } +}; + +DeclareInput(SDL) + +}; diff --git a/ruby/input/x.cpp b/ruby/input/x.cpp new file mode 100755 index 00000000..fe1440a8 --- /dev/null +++ b/ruby/input/x.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include + +namespace ruby { + +class pInputX { +public: + Display *display; + #include "xlibkeys.hpp" + + bool cap(const string& name) { + if(name == Input::KeyboardSupport) return true; + return false; + } + + any get(const string& name) { + return false; + } + + bool set(const string& name, const any &value) { + return false; + } + + bool acquire() { return false; } + bool unacquire() { return false; } + bool acquired() { return false; } + + bool poll(int16_t *table) { + memset(table, 0, Scancode::Limit * sizeof(int16_t)); + x_poll(table); + return true; + } + + bool init() { + x_init(); + display = XOpenDisplay(0); + return true; + } + + void term() { + XCloseDisplay(display); + } +}; + +DeclareInput(X) + +}; diff --git a/ruby/input/xlibkeys.hpp b/ruby/input/xlibkeys.hpp new file mode 100755 index 00000000..770de16d --- /dev/null +++ b/ruby/input/xlibkeys.hpp @@ -0,0 +1,264 @@ +uint8_t scancode[256]; + +enum XScancode { + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + ScrollLock, Pause, Tilde, + Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Num0, + Dash, Equal, Backspace, + Insert, Delete, Home, End, PageUp, PageDown, + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + LeftBracket, RightBracket, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + Point, Enter, Add, Subtract, Multiply, Divide, + Up, Down, Left, Right, + Tab, Return, Spacebar, Menu, + LeftShift, RightShift, LeftControl, RightControl, LeftAlt, RightAlt, LeftSuper, RightSuper, +}; + +void x_poll(int16_t *table) { + char state[32]; + Display *display = XOpenDisplay(0); + XQueryKeymap(display, state); + XCloseDisplay(display); + + #define key(id) table[keyboard(0)[id]] + #define pressed(id) (bool)(state[scancode[id] >> 3] & (1 << (scancode[id] & 7))) + + key(Keyboard::Escape) = pressed(Escape); + + key(Keyboard::F1) = pressed(F1); + key(Keyboard::F2) = pressed(F2); + key(Keyboard::F3) = pressed(F3); + key(Keyboard::F4) = pressed(F4); + key(Keyboard::F5) = pressed(F5); + key(Keyboard::F6) = pressed(F6); + key(Keyboard::F7) = pressed(F7); + key(Keyboard::F8) = pressed(F8); + key(Keyboard::F9) = pressed(F9); + key(Keyboard::F10) = pressed(F10); + key(Keyboard::F11) = pressed(F11); + key(Keyboard::F12) = pressed(F12); + + key(Keyboard::ScrollLock) = pressed(ScrollLock); + key(Keyboard::Pause) = pressed(Pause); + key(Keyboard::Tilde) = pressed(Tilde); + + key(Keyboard::Num1) = pressed(Num1); + key(Keyboard::Num2) = pressed(Num2); + key(Keyboard::Num3) = pressed(Num3); + key(Keyboard::Num4) = pressed(Num4); + key(Keyboard::Num5) = pressed(Num5); + key(Keyboard::Num6) = pressed(Num6); + key(Keyboard::Num7) = pressed(Num7); + key(Keyboard::Num8) = pressed(Num8); + key(Keyboard::Num9) = pressed(Num9); + key(Keyboard::Num0) = pressed(Num0); + + key(Keyboard::Dash) = pressed(Dash); + key(Keyboard::Equal) = pressed(Equal); + key(Keyboard::Backspace) = pressed(Backspace); + + key(Keyboard::Insert) = pressed(Insert); + key(Keyboard::Delete) = pressed(Delete); + key(Keyboard::Home) = pressed(Home); + key(Keyboard::End) = pressed(End); + key(Keyboard::PageUp) = pressed(PageUp); + key(Keyboard::PageDown) = pressed(PageDown); + + key(Keyboard::A) = pressed(A); + key(Keyboard::B) = pressed(B); + key(Keyboard::C) = pressed(C); + key(Keyboard::D) = pressed(D); + key(Keyboard::E) = pressed(E); + key(Keyboard::F) = pressed(F); + key(Keyboard::G) = pressed(G); + key(Keyboard::H) = pressed(H); + key(Keyboard::I) = pressed(I); + key(Keyboard::J) = pressed(J); + key(Keyboard::K) = pressed(K); + key(Keyboard::L) = pressed(L); + key(Keyboard::M) = pressed(M); + key(Keyboard::N) = pressed(N); + key(Keyboard::O) = pressed(O); + key(Keyboard::P) = pressed(P); + key(Keyboard::Q) = pressed(Q); + key(Keyboard::R) = pressed(R); + key(Keyboard::S) = pressed(S); + key(Keyboard::T) = pressed(T); + key(Keyboard::U) = pressed(U); + key(Keyboard::V) = pressed(V); + key(Keyboard::W) = pressed(W); + key(Keyboard::X) = pressed(X); + key(Keyboard::Y) = pressed(Y); + key(Keyboard::Z) = pressed(Z); + + key(Keyboard::LeftBracket) = pressed(LeftBracket); + key(Keyboard::RightBracket) = pressed(RightBracket); + key(Keyboard::Backslash) = pressed(Backslash); + key(Keyboard::Semicolon) = pressed(Semicolon); + key(Keyboard::Apostrophe) = pressed(Apostrophe); + key(Keyboard::Comma) = pressed(Comma); + key(Keyboard::Period) = pressed(Period); + key(Keyboard::Slash) = pressed(Slash); + + key(Keyboard::Keypad1) = pressed(Keypad1); + key(Keyboard::Keypad2) = pressed(Keypad2); + key(Keyboard::Keypad3) = pressed(Keypad3); + key(Keyboard::Keypad4) = pressed(Keypad4); + key(Keyboard::Keypad5) = pressed(Keypad5); + key(Keyboard::Keypad6) = pressed(Keypad6); + key(Keyboard::Keypad7) = pressed(Keypad7); + key(Keyboard::Keypad8) = pressed(Keypad8); + key(Keyboard::Keypad9) = pressed(Keypad9); + key(Keyboard::Keypad0) = pressed(Keypad0); + + key(Keyboard::Point) = pressed(Point); + key(Keyboard::Enter) = pressed(Enter); + key(Keyboard::Add) = pressed(Add); + key(Keyboard::Subtract) = pressed(Subtract); + key(Keyboard::Multiply) = pressed(Multiply); + key(Keyboard::Divide) = pressed(Divide); + + key(Keyboard::Up) = pressed(Up); + key(Keyboard::Down) = pressed(Down); + key(Keyboard::Left) = pressed(Left); + key(Keyboard::Right) = pressed(Right); + + key(Keyboard::Tab) = pressed(Tab); + key(Keyboard::Return) = pressed(Return); + key(Keyboard::Spacebar) = pressed(Spacebar); + key(Keyboard::Menu) = pressed(Menu); + + key(Keyboard::Shift) = pressed(LeftShift) || pressed(RightShift); + key(Keyboard::Control) = pressed(LeftControl) || pressed(RightControl); + key(Keyboard::Alt) = pressed(LeftAlt) || pressed(RightAlt); + key(Keyboard::Super) = pressed(LeftSuper) || pressed(RightSuper); + + #undef key + #undef pressed +} + +void x_init() { + Display *display = XOpenDisplay(0); + memset(&scancode, 0, sizeof scancode); + + #define assign(x, y) scancode[x] = XKeysymToKeycode(display, y) + assign(Escape, XK_Escape); + + assign(F1, XK_F1); + assign(F2, XK_F2); + assign(F3, XK_F3); + assign(F4, XK_F4); + assign(F5, XK_F5); + assign(F6, XK_F6); + assign(F7, XK_F7); + assign(F8, XK_F8); + assign(F9, XK_F9); + assign(F10, XK_F10); + assign(F11, XK_F11); + assign(F12, XK_F12); + + assign(ScrollLock, XK_Scroll_Lock); + assign(Pause, XK_Pause); + + assign(Tilde, XK_asciitilde); + + assign(Num0, XK_0); + assign(Num1, XK_1); + assign(Num2, XK_2); + assign(Num3, XK_3); + assign(Num4, XK_4); + assign(Num5, XK_5); + assign(Num6, XK_6); + assign(Num7, XK_7); + assign(Num8, XK_8); + assign(Num9, XK_9); + + assign(Dash, XK_minus); + assign(Equal, XK_equal); + assign(Backspace, XK_BackSpace); + + assign(Insert, XK_Insert); + assign(Delete, XK_Delete); + assign(Home, XK_Home); + assign(End, XK_End); + assign(PageUp, XK_Prior); + assign(PageDown, XK_Next); + + assign(A, XK_A); + assign(B, XK_B); + assign(C, XK_C); + assign(D, XK_D); + assign(E, XK_E); + assign(F, XK_F); + assign(G, XK_G); + assign(H, XK_H); + assign(I, XK_I); + assign(J, XK_J); + assign(K, XK_K); + assign(L, XK_L); + assign(M, XK_M); + assign(N, XK_N); + assign(O, XK_O); + assign(P, XK_P); + assign(Q, XK_Q); + assign(R, XK_R); + assign(S, XK_S); + assign(T, XK_T); + assign(U, XK_U); + assign(V, XK_V); + assign(W, XK_W); + assign(X, XK_X); + assign(Y, XK_Y); + assign(Z, XK_Z); + + assign(LeftBracket, XK_bracketleft); + assign(RightBracket, XK_bracketright); + assign(Backslash, XK_backslash); + assign(Semicolon, XK_semicolon); + assign(Apostrophe, XK_apostrophe); + assign(Comma, XK_comma); + assign(Period, XK_period); + assign(Slash, XK_slash); + + assign(Keypad0, XK_KP_0); + assign(Keypad1, XK_KP_1); + assign(Keypad2, XK_KP_2); + assign(Keypad3, XK_KP_3); + assign(Keypad4, XK_KP_4); + assign(Keypad5, XK_KP_5); + assign(Keypad6, XK_KP_6); + assign(Keypad7, XK_KP_7); + assign(Keypad8, XK_KP_8); + assign(Keypad9, XK_KP_9); + + assign(Add, XK_KP_Add); + assign(Subtract, XK_KP_Subtract); + assign(Multiply, XK_KP_Multiply); + assign(Divide, XK_KP_Divide); + assign(Enter, XK_KP_Enter); + + assign(Up, XK_Up); + assign(Down, XK_Down); + assign(Left, XK_Left); + assign(Right, XK_Right); + + assign(Tab, XK_Tab); + assign(Return, XK_Return); + assign(Spacebar, XK_space); + + assign(LeftControl, XK_Control_L); + assign(RightControl, XK_Control_R); + assign(LeftAlt, XK_Alt_L); + assign(RightAlt, XK_Alt_R); + assign(LeftShift, XK_Shift_L); + assign(RightShift, XK_Shift_R); + assign(LeftSuper, XK_Super_L); + assign(RightSuper, XK_Super_R); + assign(Menu, XK_Menu); + + #undef assign + + XCloseDisplay(display); +} diff --git a/ruby/ruby.cpp b/ruby/ruby.cpp new file mode 100755 index 00000000..8b84c4f4 --- /dev/null +++ b/ruby/ruby.cpp @@ -0,0 +1,383 @@ +#include +using namespace nall; + +#undef mkdir +#undef usleep +#include + +namespace ruby { + +VideoInterface video; +AudioInterface audio; +InputInterface input; + +/* VideoInterface */ + +const char *Video::Handle = "Handle"; +const char *Video::Synchronize = "Synchronize"; +const char *Video::Filter = "Filter"; +const char *Video::Shader = "Shader"; +const char *Video::FragmentShader = "FragmentShader"; +const char *Video::VertexShader = "VertexShader"; + +void VideoInterface::driver(const char *driver) { + if(p) term(); + + if(!driver || !*driver) driver = default_driver(); + + if(0); + + #ifdef VIDEO_DIRECT3D + else if(!strcmp(driver, "Direct3D")) p = new VideoD3D(); + #endif + + #ifdef VIDEO_DIRECTDRAW + else if(!strcmp(driver, "DirectDraw")) p = new VideoDD(); + #endif + + #ifdef VIDEO_GDI + else if(!strcmp(driver, "GDI")) p = new VideoGDI(); + #endif + + #ifdef VIDEO_GLX + else if(!strcmp(driver, "OpenGL")) p = new VideoGLX(); + #endif + + #ifdef VIDEO_QTOPENGL + else if(!strcmp(driver, "Qt-OpenGL")) p = new VideoQtOpenGL(); + #endif + + #ifdef VIDEO_QTRASTER + else if(!strcmp(driver, "Qt-Raster")) p = new VideoQtRaster(); + #endif + + #ifdef VIDEO_SDL + else if(!strcmp(driver, "SDL")) p = new VideoSDL(); + #endif + + #ifdef VIDEO_WGL + else if(!strcmp(driver, "OpenGL")) p = new VideoWGL(); + #endif + + #ifdef VIDEO_XV + else if(!strcmp(driver, "X-Video")) p = new VideoXv(); + #endif + + else p = new Video(); +} + +//select the *safest* available driver, not the fastest +const char* VideoInterface::default_driver() { + #if defined(VIDEO_DIRECT3D) + return "Direct3D"; + #elif defined(VIDEO_WGL) + return "OpenGL"; + #elif defined(VIDEO_DIRECTDRAW) + return "DirectDraw"; + #elif defined(VIDEO_GDI) + return "GDI"; + #elif defined(VIDEO_QTOPENGL) + return "Qt-OpenGL"; + #elif defined(VIDEO_QTRASTER) + return "Qt-Raster"; + #elif defined(VIDEO_SDL) + return "SDL"; + #elif defined(VIDEO_XV) + return "X-Video"; + #elif defined(VIDEO_GLX) + return "OpenGL"; + #else + return "None"; + #endif +} + +//returns list of available drivers, sorted from most to least optimal +const char* VideoInterface::driver_list() { + return + + //Windows + + #if defined(VIDEO_DIRECT3D) + "Direct3D;" + #endif + + #if defined(VIDEO_WGL) + "OpenGL;" + #endif + + #if defined(VIDEO_DIRECTDRAW) + "DirectDraw;" + #endif + + #if defined(VIDEO_GDI) + "GDI;" + #endif + + //Linux + + #if defined(VIDEO_GLX) + "OpenGL;" + #endif + + #if defined(VIDEO_QTOPENGL) + "Qt-OpenGL;" + #endif + + #if defined(VIDEO_XV) + "X-Video;" + #endif + + #if defined(VIDEO_QTRASTER) + "Qt-Raster;" + #endif + + #if defined(VIDEO_SDL) + "SDL;" + #endif + + "None"; +} + +bool VideoInterface::init() { + if(!p) driver(); + return p->init(); +} + +void VideoInterface::term() { + if(p) { + delete p; + p = 0; + } +} + +bool VideoInterface::cap(const string& name) { return p ? p->cap(name) : false; } +any VideoInterface::get(const string& name) { return p ? p->get(name) : false; } +bool VideoInterface::set(const string& name, const any& value) { return p ? p->set(name, value) : false; } +bool VideoInterface::lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { return p ? p->lock(data, pitch, width, height) : false; } +void VideoInterface::unlock() { if(p) p->unlock(); } +void VideoInterface::clear() { if(p) p->clear(); } +void VideoInterface::refresh() { if(p) p->refresh(); } +VideoInterface::VideoInterface() : p(0) {} +VideoInterface::~VideoInterface() { term(); } + +/* AudioInterface */ + +void AudioInterface::driver(const char *driver) { + if(p) term(); + + if(!driver || !*driver) driver = default_driver(); + + if(0); + + #ifdef AUDIO_ALSA + else if(!strcmp(driver, "ALSA")) p = new AudioALSA(); + #endif + + #ifdef AUDIO_AO + else if(!strcmp(driver, "libao")) p = new AudioAO(); + #endif + + #ifdef AUDIO_DIRECTSOUND + else if(!strcmp(driver, "DirectSound")) p = new AudioDS(); + #endif + + #ifdef AUDIO_OPENAL + else if(!strcmp(driver, "OpenAL")) p = new AudioOpenAL(); + #endif + + #ifdef AUDIO_OSS + else if(!strcmp(driver, "OSS")) p = new AudioOSS(); + #endif + + #ifdef AUDIO_PULSEAUDIO + else if(!strcmp(driver, "PulseAudio")) p = new AudioPulseAudio(); + #endif + + #ifdef AUDIO_PULSEAUDIOSIMPLE + else if(!strcmp(driver, "PulseAudioSimple")) p = new AudioPulseAudioSimple(); + #endif + + #ifdef AUDIO_XAUDIO2 + else if(!strcmp(driver, "XAudio2")) p = new AudioXAudio2(); + #endif + + else p = new Audio(); +} + +//select the *safest* available driver, not the fastest +const char* AudioInterface::default_driver() { + #if defined(AUDIO_DIRECTSOUND) + return "DirectSound"; + #elif defined(AUDIO_XAUDIO2) + return "XAudio2"; + #elif defined(AUDIO_ALSA) + return "ALSA"; + #elif defined(AUDIO_OPENAL) + return "OpenAL"; + #elif defined(AUDIO_PULSEAUDIO) + return "PulseAudio"; + #elif defined(AUDIO_PULSEAUDIOSIMPLE) + return "PulseAudioSimple"; + #elif defined(AUDIO_AO) + return "libao"; + #elif defined(AUDIO_OSS) + return "OSS"; + #else + return "None"; + #endif +} + +//returns list of available drivers, sorted from most to least optimal +const char* AudioInterface::driver_list() { + return + + //Windows + + #if defined(AUDIO_DIRECTSOUND) + "DirectSound;" + #endif + + #if defined(AUDIO_XAUDIO2) + "XAudio2;" + #endif + + //Linux + + #if defined(AUDIO_ALSA) + "ALSA;" + #endif + + #if defined(AUDIO_OPENAL) + "OpenAL;" + #endif + + #if defined(AUDIO_OSS) + "OSS;" + #endif + + #if defined(AUDIO_PULSEAUDIO) + "PulseAudio;" + #endif + + #if defined(AUDIO_PULSEAUDIOSIMPLE) + "PulseAudioSimple;" + #endif + + #if defined(AUDIO_AO) + "libao;" + #endif + + "None"; +} + +#include "ruby_audio.cpp" + +/* InputInterface */ + +const char *Input::Handle = "Handle"; +const char *Input::KeyboardSupport = "KeyboardSupport"; +const char *Input::MouseSupport = "MouseSupport"; +const char *Input::JoypadSupport = "JoypadSupport"; + +void InputInterface::driver(const char *driver) { + if(p) term(); + + if(!driver || !*driver) driver = default_driver(); + + if(0); + + #ifdef INPUT_DIRECTINPUT + else if(!strcmp(driver, "DirectInput")) p = new InputDI(); + #endif + + #ifdef INPUT_RAWINPUT + else if(!strcmp(driver, "RawInput")) p = new InputRaw(); + #endif + + #ifdef INPUT_SDL + else if(!strcmp(driver, "SDL")) p = new InputSDL(); + #endif + + #ifdef INPUT_X + else if(!strcmp(driver, "X-Windows")) p = new InputX(); + #endif + + #ifdef INPUT_CARBON + else if(!strcmp(driver, "Carbon")) p = new InputCarbon(); + #endif + + else p = new Input(); +} + +//select the *safest* available driver, not the fastest +const char* InputInterface::default_driver() { + #if defined(INPUT_RAWINPUT) + return "RawInput"; + #elif defined(INPUT_DIRECTINPUT) + return "DirectInput"; + #elif defined(INPUT_SDL) + return "SDL"; + #elif defined(INPUT_X) + return "X-Windows"; + #elif defined(INPUT_CARBON) + return "Carbon"; + #else + return "none"; + #endif +} + +const char* InputInterface::driver_list() { + return + + //Windows + + #if defined(INPUT_RAWINPUT) + "RawInput;" + #endif + + #if defined(INPUT_DIRECTINPUT) + "DirectInput;" + #endif + + //Linux + + #if defined(INPUT_SDL) + "SDL;" + #endif + + #if defined(INPUT_X) + "X-Windows;" + #endif + + //OS X + + #if defined(INPUT_CARBON) + "Carbon;" + #endif + + "None"; +} + +bool InputInterface::init() { + if(!p) driver(); + return p->init(); +} + +void InputInterface::term() { + if(p) { + delete p; + p = 0; + } +} + +bool InputInterface::cap(const string& name) { return p ? p->cap(name) : false; } +any InputInterface::get(const string& name) { return p ? p->get(name) : false; } +bool InputInterface::set(const string& name, const any& value) { return p ? p->set(name, value) : false; } +bool InputInterface::acquire() { return p ? p->acquire() : false; } +bool InputInterface::unacquire() { return p ? p->unacquire() : false; } +bool InputInterface::acquired() { return p ? p->acquired() : false; } +bool InputInterface::poll(int16_t *table) { return p ? p->poll(table) : false; } +InputInterface::InputInterface() : p(0) {} +InputInterface::~InputInterface() { term(); } + +}; diff --git a/ruby/ruby.hpp b/ruby/ruby.hpp new file mode 100755 index 00000000..4d1b1a35 --- /dev/null +++ b/ruby/ruby.hpp @@ -0,0 +1,109 @@ +/* + ruby + version: 0.06 (2009-05-22) + license: public domain +*/ + +#ifndef RUBY_H +#define RUBY_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ruby { + +#include +#include +#include + +class VideoInterface { +public: + void driver(const char *driver = ""); + const char* default_driver(); + const char* driver_list(); + bool init(); + void term(); + + bool cap(const nall::string& name); + nall::any get(const nall::string& name); + bool set(const nall::string& name, const nall::any& value); + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height); + void unlock(); + void clear(); + void refresh(); + VideoInterface(); + ~VideoInterface(); + +private: + Video *p; +}; + +class AudioInterface { +public: + void driver(const char *driver = ""); + const char* default_driver(); + const char* driver_list(); + bool init(); + void term(); + + bool cap(const nall::string& name); + nall::any get(const nall::string& name); + bool set(const nall::string& name, const nall::any& value); + + void sample(uint16_t left, uint16_t right); + void clear(); + AudioInterface(); + ~AudioInterface(); + +private: + Audio *p; + + unsigned volume; + + //resample unit + double hermite(double mu, double a, double b, double c, double d); + bool resample_enabled; + double r_step, r_frac; + int r_left[4], r_right[4]; +}; + +class InputInterface { +public: + void driver(const char *driver = ""); + const char* default_driver(); + const char* driver_list(); + bool init(); + void term(); + + bool cap(const nall::string& name); + nall::any get(const nall::string& name); + bool set(const nall::string& name, const nall::any& value); + + bool acquire(); + bool unacquire(); + bool acquired(); + + bool poll(int16_t *table); + InputInterface(); + ~InputInterface(); + +private: + Input *p; +}; + +extern VideoInterface video; +extern AudioInterface audio; +extern InputInterface input; + +}; + +#endif diff --git a/ruby/ruby_audio.cpp b/ruby/ruby_audio.cpp new file mode 100755 index 00000000..4afe1f56 --- /dev/null +++ b/ruby/ruby_audio.cpp @@ -0,0 +1,133 @@ +const char *Audio::Volume = "Volume"; +const char *Audio::Resample = "Resample"; +const char *Audio::ResampleRatio = "ResampleRatio"; + +const char *Audio::Handle = "Handle"; +const char *Audio::Synchronize = "Synchronize"; +const char *Audio::Frequency = "Frequency"; +const char *Audio::Latency = "Latency"; + +bool AudioInterface::init() { + if(!p) driver(); + return p->init(); +} + +void AudioInterface::term() { + if(p) { + delete p; + p = 0; + } +} + +bool AudioInterface::cap(const string& name) { + if(name == Audio::Volume) return true; + if(name == Audio::Resample) return true; + if(name == Audio::ResampleRatio) return true; + + return p ? p->cap(name) : false; +} + +any AudioInterface::get(const string& name) { + if(name == Audio::Volume) return volume; + if(name == Audio::Resample) return resample_enabled; + if(name == Audio::ResampleRatio); + + return p ? p->get(name) : false; +} + +bool AudioInterface::set(const string& name, const any& value) { + if(name == Audio::Volume) { + volume = any_cast(value); + return true; + } + + if(name == Audio::Resample) { + resample_enabled = any_cast(value); + return true; + } + + if(name == Audio::ResampleRatio) { + r_step = any_cast(value); + r_frac = 0; + return true; + } + + return p ? p->set(name, value) : false; +} + +//4-tap hermite interpolation +double AudioInterface::hermite(double mu1, double a, double b, double c, double d) { + const double tension = 0.0; //-1 = low, 0 = normal, 1 = high + const double bias = 0.0; //-1 = left, 0 = even, 1 = right + + double mu2, mu3, m0, m1, a0, a1, a2, a3; + + mu2 = mu1 * mu1; + mu3 = mu2 * mu1; + + m0 = (b - a) * (1 + bias) * (1 - tension) / 2; + m0 += (c - b) * (1 - bias) * (1 - tension) / 2; + m1 = (c - b) * (1 + bias) * (1 - tension) / 2; + m1 += (d - c) * (1 - bias) * (1 - tension) / 2; + + a0 = +2 * mu3 - 3 * mu2 + 1; + a1 = mu3 - 2 * mu2 + mu1; + a2 = mu3 - mu2; + a3 = -2 * mu3 + 3 * mu2; + + return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c); +} + +void AudioInterface::sample(uint16_t left, uint16_t right) { + int s_left = (int16_t)left; + int s_right = (int16_t)right; + + if(volume != 100) { + s_left = sclamp<16>((double)s_left * (double)volume / 100.0); + s_right = sclamp<16>((double)s_right * (double)volume / 100.0); + } + + r_left [0] = r_left [1]; + r_left [1] = r_left [2]; + r_left [2] = r_left [3]; + r_left [3] = s_left; + + r_right[0] = r_right[1]; + r_right[1] = r_right[2]; + r_right[2] = r_right[3]; + r_right[3] = s_right; + + if(resample_enabled == false) { + if(p) p->sample(left, right); + return; + } + + while(r_frac <= 1.0) { + int output_left = sclamp<16>(hermite(r_frac, r_left [0], r_left [1], r_left [2], r_left [3])); + int output_right = sclamp<16>(hermite(r_frac, r_right[0], r_right[1], r_right[2], r_right[3])); + r_frac += r_step; + if(p) p->sample(output_left, output_right); + } + + r_frac -= 1.0; +} + +void AudioInterface::clear() { + r_frac = 0; + r_left [0] = r_left [1] = r_left [2] = r_left [3] = 0; + r_right[0] = r_right[1] = r_right[2] = r_right[3] = 0; + if(p) p->clear(); +} + +AudioInterface::AudioInterface() { + p = 0; + volume = 100; + resample_enabled = false; + r_step = r_frac = 0; + r_left [0] = r_left [1] = r_left [2] = r_left [3] = 0; + r_right[0] = r_right[1] = r_right[2] = r_right[3] = 0; +} + +AudioInterface::~AudioInterface() { + term(); +} diff --git a/ruby/ruby_impl.cpp b/ruby/ruby_impl.cpp new file mode 100755 index 00000000..4b7049e2 --- /dev/null +++ b/ruby/ruby_impl.cpp @@ -0,0 +1,182 @@ +/* Global Headers */ + +#if defined(VIDEO_QTOPENGL) || defined(VIDEO_QTRASTER) + #include + #include +#endif + +#if defined(VIDEO_QTOPENGL) + #include + #if defined(PLATFORM_WIN) + #include + #endif +#endif + +#if defined(PLATFORM_X) + #include + #include + #include +#elif defined(PLATFORM_OSX) + #define __INTEL_COMPILER + #include +#elif defined(PLATFORM_WIN) + #define _WIN32_WINNT 0x0501 + #include +#endif + +/* Video */ + +#define DeclareVideo(Name) \ + class Video##Name : public Video { \ + public: \ + bool cap(const string& name) { return p.cap(name); } \ + any get(const string& name) { return p.get(name); } \ + bool set(const string& name, const any& value) { return p.set(name, value); } \ + \ + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { return p.lock(data, pitch, width, height); } \ + void unlock() { p.unlock(); } \ + \ + void clear() { p.clear(); } \ + void refresh() { p.refresh(); } \ + bool init() { return p.init(); } \ + void term() { p.term(); } \ + \ + Video##Name() : p(*new pVideo##Name) {} \ + ~Video##Name() { delete &p; } \ + \ + private: \ + pVideo##Name &p; \ + }; + +#ifdef VIDEO_DIRECT3D + #include +#endif + +#ifdef VIDEO_DIRECTDRAW + #include +#endif + +#ifdef VIDEO_GDI + #include +#endif + +#ifdef VIDEO_GLX + #include +#endif + +#ifdef VIDEO_QTOPENGL + #include +#endif + +#ifdef VIDEO_QTRASTER + #include +#endif + +#ifdef VIDEO_SDL + #include +#endif + +#ifdef VIDEO_WGL + #include +#endif + +#ifdef VIDEO_XV + #include +#endif + +/* Audio */ + +#define DeclareAudio(Name) \ + class Audio##Name : public Audio { \ + public: \ + bool cap(const string& name) { return p.cap(name); } \ + any get(const string& name) { return p.get(name); } \ + bool set(const string& name, const any& value) { return p.set(name, value); } \ + \ + void sample(uint16_t left, uint16_t right) { p.sample(left, right); } \ + void clear() { p.clear(); } \ + bool init() { return p.init(); } \ + void term() { p.term(); } \ + \ + Audio##Name() : p(*new pAudio##Name) {} \ + ~Audio##Name() { delete &p; } \ + \ + private: \ + pAudio##Name &p; \ + }; + +#ifdef AUDIO_ALSA + #include +#endif + +#ifdef AUDIO_AO + #include +#endif + +#ifdef AUDIO_DIRECTSOUND + #include +#endif + +#ifdef AUDIO_OPENAL + #include +#endif + +#ifdef AUDIO_OSS + #include +#endif + +#ifdef AUDIO_PULSEAUDIO + #include +#endif + +#ifdef AUDIO_PULSEAUDIOSIMPLE + #include +#endif + +#ifdef AUDIO_XAUDIO2 + #include +#endif + +/* Input */ + +#define DeclareInput(Name) \ + class Input##Name : public Input { \ + public: \ + bool cap(const string& name) { return p.cap(name); } \ + any get(const string& name) { return p.get(name); } \ + bool set(const string& name, const any& value) { return p.set(name, value); } \ + \ + bool acquire() { return p.acquire(); } \ + bool unacquire() { return p.unacquire(); } \ + bool acquired() { return p.acquired(); } \ + \ + bool poll(int16_t *table) { return p.poll(table); } \ + bool init() { return p.init(); } \ + void term() { p.term(); } \ + \ + Input##Name() : p(*new pInput##Name) {} \ + ~Input##Name() { delete &p; } \ + \ + private: \ + pInput##Name &p; \ + }; + +#ifdef INPUT_DIRECTINPUT + #include +#endif + +#ifdef INPUT_RAWINPUT + #include +#endif + +#ifdef INPUT_SDL + #include +#endif + +#ifdef INPUT_X + #include +#endif + +#ifdef INPUT_CARBON + #include +#endif diff --git a/ruby/video.hpp b/ruby/video.hpp new file mode 100755 index 00000000..c79c5531 --- /dev/null +++ b/ruby/video.hpp @@ -0,0 +1,29 @@ +class Video { +public: + static const char *Handle; + static const char *Synchronize; + static const char *Filter; + static const char *Shader; + static const char *FragmentShader; + static const char *VertexShader; + + enum Filter { + FilterPoint, + FilterLinear, + }; + + virtual bool cap(const nall::string& name) { return false; } + virtual nall::any get(const nall::string& name) { return false; } + virtual bool set(const nall::string& name, const nall::any& value) { return false; } + + virtual bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { return false; } + virtual void unlock() {} + + virtual void clear() {} + virtual void refresh() {} + virtual bool init() { return true; } + virtual void term() {} + + Video() {} + virtual ~Video() {} +}; diff --git a/ruby/video/direct3d.cpp b/ruby/video/direct3d.cpp new file mode 100755 index 00000000..a9404d8e --- /dev/null +++ b/ruby/video/direct3d.cpp @@ -0,0 +1,469 @@ +#undef interface +#define interface struct +#include +#include +#undef interface + +#define D3DVERTEX (D3DFVF_XYZRHW | D3DFVF_TEX1) + +typedef HRESULT (__stdcall *EffectProc)(LPDIRECT3DDEVICE9, LPCVOID, UINT, D3DXMACRO const*, LPD3DXINCLUDE, DWORD, LPD3DXEFFECTPOOL, LPD3DXEFFECT*, LPD3DXBUFFER*); +typedef HRESULT (__stdcall *TextureProc)(LPDIRECT3DDEVICE9, LPCTSTR, LPDIRECT3DTEXTURE9*); + +namespace ruby { + +class pVideoD3D { +public: + LPDIRECT3D9 lpd3d; + LPDIRECT3DDEVICE9 device; + LPDIRECT3DVERTEXBUFFER9 vertex_buffer, *vertex_ptr; + D3DPRESENT_PARAMETERS presentation; + D3DSURFACE_DESC d3dsd; + D3DLOCKED_RECT d3dlr; + D3DRASTER_STATUS d3drs; + D3DCAPS9 d3dcaps; + LPDIRECT3DTEXTURE9 texture; + LPDIRECT3DSURFACE9 surface; + LPD3DXEFFECT effect; + string shader_source_xml; + + bool lost; + unsigned iwidth, iheight; + + struct d3dvertex { + float x, y, z, rhw; //screen coords + float u, v; //texture coords + }; + + struct { + uint32_t t_usage, v_usage; + uint32_t t_pool, v_pool; + uint32_t lock; + uint32_t filter; + } flags; + + struct { + bool dynamic; //device supports dynamic textures + bool shader; //device supports pixel shaders + } caps; + + struct { + HWND handle; + bool synchronize; + unsigned filter; + + unsigned width; + unsigned height; + } settings; + + struct { + unsigned width; + unsigned height; + } state; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + if(name == Video::Filter) return true; + if(name == Video::Shader) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + if(name == Video::Filter) return settings.filter; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + if(name == Video::Filter) { + settings.filter = any_cast(value); + if(lpd3d) update_filter(); + return true; + } + + if(name == Video::Shader) { + set_shader(any_cast(value)); + return true; + } + + return false; + } + + bool recover() { + if(!device) return false; + + if(lost) { + release_resources(); + if(device->Reset(&presentation) != D3D_OK) return false; + } + + lost = false; + + device->SetDialogBoxMode(false); + + device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + + device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + + device->SetRenderState(D3DRS_LIGHTING, false); + device->SetRenderState(D3DRS_ZENABLE, false); + device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + + device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + device->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + + device->SetVertexShader(NULL); + device->SetFVF(D3DVERTEX); + + device->CreateVertexBuffer(sizeof(d3dvertex) * 4, flags.v_usage, D3DVERTEX, (D3DPOOL)flags.v_pool, &vertex_buffer, NULL); + iwidth = 0; + iheight = 0; + resize(settings.width = 256, settings.height = 256); + update_filter(); + clear(); + return true; + } + + unsigned rounded_power_of_two(unsigned n) { + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + return n + 1; + } + + void resize(unsigned width, unsigned height) { + if(iwidth >= width && iheight >= height) return; + + iwidth = rounded_power_of_two(max(width, iwidth )); + iheight = rounded_power_of_two(max(height, iheight)); + + if(d3dcaps.MaxTextureWidth < iwidth || d3dcaps.MaxTextureWidth < iheight) { + //TODO: attempt to handle this more gracefully + return; + } + + if(texture) texture->Release(); + device->CreateTexture(iwidth, iheight, 1, flags.t_usage, D3DFMT_X8R8G8B8, (D3DPOOL)flags.t_pool, &texture, NULL); + } + + void update_filter() { + if(!device) return; + if(lost && !recover()) return; + + switch(settings.filter) { default: + case Video::FilterPoint: flags.filter = D3DTEXF_POINT; break; + case Video::FilterLinear: flags.filter = D3DTEXF_LINEAR; break; + } + + device->SetSamplerState(0, D3DSAMP_MINFILTER, flags.filter); + device->SetSamplerState(0, D3DSAMP_MAGFILTER, flags.filter); + } + + // Vertex format: + // + // 0----------1 + // | /| + // | / | + // | / | + // | / | + // | / | + // 2----------3 + // + // (x,y) screen coords, in pixels + // (u,v) texture coords, betweeen 0.0 (top, left) to 1.0 (bottom, right) + void set_vertex( + uint32_t px, uint32_t py, uint32_t pw, uint32_t ph, + uint32_t tw, uint32_t th, + uint32_t x, uint32_t y, uint32_t w, uint32_t h + ) { + d3dvertex vertex[4]; + vertex[0].x = vertex[2].x = (double)(x - 0.5); + vertex[1].x = vertex[3].x = (double)(x + w - 0.5); + vertex[0].y = vertex[1].y = (double)(y - 0.5); + vertex[2].y = vertex[3].y = (double)(y + h - 0.5); + + //Z-buffer and RHW are unused for 2D blit, set to normal values + vertex[0].z = vertex[1].z = vertex[2].z = vertex[3].z = 0.0; + vertex[0].rhw = vertex[1].rhw = vertex[2].rhw = vertex[3].rhw = 1.0; + + double rw = (double)w / (double)pw * (double)tw; + double rh = (double)h / (double)ph * (double)th; + vertex[0].u = vertex[2].u = (double)(px ) / rw; + vertex[1].u = vertex[3].u = (double)(px + w) / rw; + vertex[0].v = vertex[1].v = (double)(py ) / rh; + vertex[2].v = vertex[3].v = (double)(py + h) / rh; + + vertex_buffer->Lock(0, sizeof(d3dvertex) * 4, (void**)&vertex_ptr, 0); + memcpy(vertex_ptr, vertex, sizeof(d3dvertex) * 4); + vertex_buffer->Unlock(); + + device->SetStreamSource(0, vertex_buffer, 0, sizeof(d3dvertex)); + } + + void clear() { + if(lost && !recover()) return; + + texture->GetLevelDesc(0, &d3dsd); + texture->GetSurfaceLevel(0, &surface); + + if(surface) { + device->ColorFill(surface, 0, D3DCOLOR_XRGB(0x00, 0x00, 0x00)); + surface->Release(); + } + + //clear primary display and all backbuffers + for(unsigned i = 0; i < 3; i++) { + device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0x00, 0x00, 0x00), 1.0f, 0); + device->Present(0, 0, 0, 0); + } + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + if(lost && !recover()) return false; + + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + texture->GetLevelDesc(0, &d3dsd); + texture->GetSurfaceLevel(0, &surface); + + surface->LockRect(&d3dlr, 0, flags.lock); + pitch = d3dlr.Pitch; + return data = (uint32_t*)d3dlr.pBits; + } + + void unlock() { + surface->UnlockRect(); + surface->Release(); + } + + void refresh() { + if(lost && !recover()) return; + + RECT rd, rs; //dest, source rectangles + GetClientRect(settings.handle, &rd); + SetRect(&rs, 0, 0, settings.width, settings.height); + + //if output size changed, driver must be re-initialized. + //failure to do so causes scaling issues on some video drivers. + if(state.width != rd.right || state.height != rd.bottom) { + init(); + set_shader(shader_source_xml); + return; + } + + if(caps.shader && effect) { + device->BeginScene(); + set_vertex(0, 0, settings.width, settings.height, iwidth, iheight, 0, 0, rd.right, rd.bottom); + + D3DXVECTOR4 rubyTextureSize; + rubyTextureSize.x = iwidth; + rubyTextureSize.y = iheight; + rubyTextureSize.z = 1.0 / iheight; + rubyTextureSize.w = 1.0 / iwidth; + effect->SetVector("rubyTextureSize", &rubyTextureSize); + + D3DXVECTOR4 rubyInputSize; + rubyInputSize.x = settings.width; + rubyInputSize.y = settings.height; + rubyInputSize.z = 1.0 / settings.height; + rubyInputSize.w = 1.0 / settings.width; + effect->SetVector("rubyInputSize", &rubyInputSize); + + D3DXVECTOR4 rubyOutputSize; + rubyOutputSize.x = rd.right; + rubyOutputSize.y = rd.bottom; + rubyOutputSize.z = 1.0 / rd.bottom; + rubyOutputSize.w = 1.0 / rd.right; + effect->SetVector("rubyOutputSize", &rubyOutputSize); + + UINT passes; + effect->Begin(&passes, 0); + effect->SetTexture("rubyTexture", texture); + device->SetTexture(0, texture); + for(unsigned pass = 0; pass < passes; pass++) { + effect->BeginPass(pass); + device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + effect->EndPass(); + } + effect->End(); + device->EndScene(); + } else { + device->BeginScene(); + set_vertex(0, 0, settings.width, settings.height, iwidth, iheight, 0, 0, rd.right, rd.bottom); + device->SetTexture(0, texture); + device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + device->EndScene(); + } + + if(settings.synchronize) { + while(true) { + D3DRASTER_STATUS status; + device->GetRasterStatus(0, &status); + if(status.InVBlank == true) break; + } + } + + if(device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) lost = true; + } + + void set_shader(const char *source) { + if(!caps.shader) return; + + if(effect) { + effect->Release(); + effect = NULL; + } + + if(!source || !*source) { + shader_source_xml = ""; + return; + } + shader_source_xml = source; + + bool is_hlsl = false; + string shader_source; + xml_element document = xml_parse(shader_source_xml); + foreach(head, document.element) { + if(head.name == "shader") { + foreach(attribute, head.attribute) { + if(attribute.name == "language" && attribute.content == "HLSL") is_hlsl = true; + } + foreach(element, head.element) { + if(element.name == "source") { + if(is_hlsl) shader_source = element.parse(); + } + } + } + } + if(shader_source == "") return; + + HMODULE d3dx; + for(unsigned i = 0; i < 256; i++) { + char t[256]; + sprintf(t, "d3dx9_%u.dll", i); + d3dx = LoadLibraryW(utf16_t(t)); + if(d3dx) break; + } + if(!d3dx) d3dx = LoadLibraryW(L"d3dx9.dll"); + if(!d3dx) return; + + EffectProc effectProc = (EffectProc)GetProcAddress(d3dx, "D3DXCreateEffect"); + TextureProc textureProc = (TextureProc)GetProcAddress(d3dx, "D3DXCreateTextureFromFileA"); + + LPD3DXBUFFER pBufferErrors = NULL; + effectProc(device, shader_source, lstrlenA(shader_source), NULL, NULL, 0, NULL, &effect, &pBufferErrors); + + D3DXHANDLE hTech; + effect->FindNextValidTechnique(NULL, &hTech); + effect->SetTechnique(hTech); + } + + bool init() { + term(); + + RECT rd; + GetClientRect(settings.handle, &rd); + state.width = rd.right; + state.height = rd.bottom; + + lpd3d = Direct3DCreate9(D3D_SDK_VERSION); + if(!lpd3d) return false; + + memset(&presentation, 0, sizeof(presentation)); + presentation.Flags = D3DPRESENTFLAG_VIDEO; + presentation.SwapEffect = D3DSWAPEFFECT_FLIP; + presentation.hDeviceWindow = settings.handle; + presentation.BackBufferCount = 1; + presentation.MultiSampleType = D3DMULTISAMPLE_NONE; + presentation.MultiSampleQuality = 0; + presentation.EnableAutoDepthStencil = false; + presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN; + presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + presentation.Windowed = true; + presentation.BackBufferFormat = D3DFMT_UNKNOWN; + presentation.BackBufferWidth = 0; + presentation.BackBufferHeight = 0; + + if(lpd3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, settings.handle, + D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) { + return false; + } + + device->GetDeviceCaps(&d3dcaps); + + caps.dynamic = bool(d3dcaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES); + caps.shader = d3dcaps.PixelShaderVersion > D3DPS_VERSION(1, 4); + + if(caps.dynamic == true) { + flags.t_usage = D3DUSAGE_DYNAMIC; + flags.v_usage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC; + flags.t_pool = D3DPOOL_DEFAULT; + flags.v_pool = D3DPOOL_DEFAULT; + flags.lock = D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD; + } else { + flags.t_usage = 0; + flags.v_usage = D3DUSAGE_WRITEONLY; + flags.t_pool = D3DPOOL_MANAGED; + flags.v_pool = D3DPOOL_MANAGED; + flags.lock = D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD; + } + + lost = false; + recover(); + return true; + } + + void release_resources() { + if(effect) { effect->Release(); effect = 0; } + if(vertex_buffer) { vertex_buffer->Release(); vertex_buffer = 0; } + if(surface) { surface->Release(); surface = 0; } + if(texture) { texture->Release(); texture = 0; } + } + + void term() { + release_resources(); + if(device) { device->Release(); device = 0; } + if(lpd3d) { lpd3d->Release(); lpd3d = 0; } + } + + pVideoD3D() { + effect = 0; + vertex_buffer = 0; + surface = 0; + texture = 0; + device = 0; + lpd3d = 0; + lost = true; + + settings.handle = 0; + settings.synchronize = false; + settings.filter = Video::FilterLinear; + } +}; + +DeclareVideo(D3D) + +}; + +#undef D3DVERTEX diff --git a/ruby/video/directdraw.cpp b/ruby/video/directdraw.cpp new file mode 100755 index 00000000..5a77ec44 --- /dev/null +++ b/ruby/video/directdraw.cpp @@ -0,0 +1,186 @@ +#include + +namespace ruby { + +class pVideoDD { +public: + LPDIRECTDRAW lpdd; + LPDIRECTDRAW7 lpdd7; + LPDIRECTDRAWSURFACE7 screen, raster; + LPDIRECTDRAWCLIPPER clipper; + DDSURFACEDESC2 ddsd; + DDSCAPS2 ddscaps; + unsigned iwidth, iheight; + + struct { + HWND handle; + bool synchronize; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + return false; + } + + void resize(unsigned width, unsigned height) { + if(iwidth >= width && iheight >= height) return; + + iwidth = max(width, iwidth); + iheight = max(height, iheight); + + if(raster) raster->Release(); + + screen->GetSurfaceDesc(&ddsd); + int depth = ddsd.ddpfPixelFormat.dwRGBBitCount; + if(depth == 32) goto try_native_surface; + + memset(&ddsd, 0, sizeof(DDSURFACEDESC2)); + ddsd.dwSize = sizeof(DDSURFACEDESC2); + ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; //DDSCAPS_SYSTEMMEMORY + ddsd.dwWidth = iwidth; + ddsd.dwHeight = iheight; + + ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT); + ddsd.ddpfPixelFormat.dwFlags = DDPF_RGB; + ddsd.ddpfPixelFormat.dwRGBBitCount = 32; + ddsd.ddpfPixelFormat.dwRBitMask = 0xff0000; + ddsd.ddpfPixelFormat.dwGBitMask = 0x00ff00; + ddsd.ddpfPixelFormat.dwBBitMask = 0x0000ff; + + if(lpdd7->CreateSurface(&ddsd, &raster, 0) == DD_OK) return clear(); + + try_native_surface: + memset(&ddsd, 0, sizeof(DDSURFACEDESC2)); + ddsd.dwSize = sizeof(DDSURFACEDESC2); + ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; //DDSCAPS_SYSTEMMEMORY + ddsd.dwWidth = iwidth; + ddsd.dwHeight = iheight; + + if(lpdd7->CreateSurface(&ddsd, &raster, 0) == DD_OK) return clear(); + } + + void clear() { + DDBLTFX fx; + fx.dwSize = sizeof(DDBLTFX); + fx.dwFillColor = 0x00000000; + screen->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx); + raster->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx); + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + if(raster->Lock(0, &ddsd, DDLOCK_WAIT, 0) != DD_OK) { + raster->Restore(); + if(raster->Lock(0, &ddsd, DDLOCK_WAIT, 0) != DD_OK) return false; + } + pitch = ddsd.lPitch; + return data = (uint32_t*)ddsd.lpSurface; + } + + void unlock() { + raster->Unlock(0); + } + + void refresh() { + if(settings.synchronize) { + while(true) { + BOOL in_vblank; + lpdd7->GetVerticalBlankStatus(&in_vblank); + if(in_vblank == true) break; + } + } + + HRESULT hr; + RECT rd, rs; + SetRect(&rs, 0, 0, settings.width, settings.height); + + POINT p = { 0, 0 }; + ClientToScreen(settings.handle, &p); + GetClientRect(settings.handle, &rd); + OffsetRect(&rd, p.x, p.y); + + if(screen->Blt(&rd, raster, &rs, DDBLT_WAIT, 0) == DDERR_SURFACELOST) { + screen->Restore(); + raster->Restore(); + } + } + + bool init() { + term(); + + DirectDrawCreate(0, &lpdd, 0); + lpdd->QueryInterface(IID_IDirectDraw7, (void**)&lpdd7); + if(lpdd) { lpdd->Release(); lpdd = 0; } + + lpdd7->SetCooperativeLevel(settings.handle, DDSCL_NORMAL); + + memset(&ddsd, 0, sizeof(DDSURFACEDESC2)); + ddsd.dwSize = sizeof(DDSURFACEDESC2); + + ddsd.dwFlags = DDSD_CAPS; + ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + lpdd7->CreateSurface(&ddsd, &screen, 0); + + lpdd7->CreateClipper(0, &clipper, 0); + clipper->SetHWnd(0, settings.handle); + screen->SetClipper(clipper); + + raster = 0; + iwidth = 0; + iheight = 0; + resize(settings.width = 256, settings.height = 256); + + return true; + } + + void term() { + if(clipper) { clipper->Release(); clipper = 0; } + if(raster) { raster->Release(); raster = 0; } + if(screen) { screen->Release(); screen = 0; } + if(lpdd7) { lpdd7->Release(); lpdd7 = 0; } + if(lpdd) { lpdd->Release(); lpdd = 0; } + } + + pVideoDD() { + lpdd = 0; + lpdd7 = 0; + screen = 0; + raster = 0; + clipper = 0; + + settings.handle = 0; + } +}; + +DeclareVideo(DD) + +}; diff --git a/ruby/video/gdi.cpp b/ruby/video/gdi.cpp new file mode 100755 index 00000000..c79f6d65 --- /dev/null +++ b/ruby/video/gdi.cpp @@ -0,0 +1,100 @@ +#include + +namespace ruby { + +class pVideoGDI { +public: + uint32_t *buffer; + HBITMAP bitmap; + HDC bitmapdc; + BITMAPINFO bmi; + + struct { + HWND handle; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + return false; + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + settings.width = width; + settings.height = height; + + pitch = 1024 * 4; + return data = buffer; + } + + void unlock() {} + + void clear() {} + + void refresh() { + RECT rc; + GetClientRect(settings.handle, &rc); + + SetDIBits(bitmapdc, bitmap, 0, settings.height, (void*)buffer, &bmi, DIB_RGB_COLORS); + HDC hdc = GetDC(settings.handle); + StretchBlt(hdc, rc.left, rc.top, rc.right, rc.bottom, bitmapdc, 0, 1024 - settings.height, settings.width, settings.height, SRCCOPY); + ReleaseDC(settings.handle, hdc); + } + + bool init() { + HDC hdc = GetDC(settings.handle); + bitmapdc = CreateCompatibleDC(hdc); + assert(bitmapdc); + bitmap = CreateCompatibleBitmap(hdc, 1024, 1024); + assert(bitmap); + SelectObject(bitmapdc, bitmap); + ReleaseDC(settings.handle, hdc); + + memset(&bmi, 0, sizeof(BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = 1024; + bmi.bmiHeader.biHeight = -1024; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; //biBitCount of 15 is invalid, biBitCount of 16 is really RGB555 + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = 1024 * 1024 * sizeof(uint32_t); + + settings.width = 256; + settings.height = 256; + return true; + } + + void term() { + DeleteObject(bitmap); + DeleteDC(bitmapdc); + } + + pVideoGDI() { + buffer = (uint32_t*)malloc(1024 * 1024 * sizeof(uint32_t)); + settings.handle = 0; + } + + ~pVideoGDI() { + if(buffer) free(buffer); + } +}; + +DeclareVideo(GDI) + +}; diff --git a/ruby/video/glx.cpp b/ruby/video/glx.cpp new file mode 100755 index 00000000..1f6f9a3b --- /dev/null +++ b/ruby/video/glx.cpp @@ -0,0 +1,237 @@ +/* + video.glx + author: byuu + license: public domain + last updated: 2010-09-28 + + Design notes: + SGI's GLX is the X11/Xlib interface to OpenGL. + At the time of this writing, there are three relevant versions of the API: versions 1.2, 1.3 and 1.4. + + Version 1.2 was released on March 4th, 1997. + Version 1.3 was released on October 19th, 1998. + Version 1.4 was released on December 16th, 2005. + + Despite version 1.3 being roughly ten years old at this time, there are still many modern X11 GLX drivers + that lack full support for the specification. Most notable would be the official video drivers from ATI. + Given this, 1.4 support is pretty much hopeless to target. + + Luckily, each version has been designed to be backwards compatible with the previous version. As well, + version 1.2 is wholly sufficient, albeit less convenient, to implement this video module. + + Therefore, for the purpose of compatibility, this driver only uses GLX 1.2 or earlier API commands. + As well, it only uses raw Xlib API commands, so that it is compatible with any toolkit. +*/ + +#include "opengl.hpp" + +namespace ruby { + +//returns true once window is mapped (created and displayed onscreen) +static Bool glx_wait_for_map_notify(Display *d, XEvent *e, char *arg) { + return (e->type == MapNotify) && (e->xmap.window == (Window)arg); +} + +class pVideoGLX : public OpenGL { +public: + int (*glSwapInterval)(int); + + Display *display; + int screen; + Window xwindow; + Colormap colormap; + GLXContext glxcontext; + GLXWindow glxwindow; + + struct { + int version_major, version_minor; + bool double_buffer; + bool is_direct; + } glx; + + struct { + Window handle; + bool synchronize; + unsigned filter; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + if(name == Video::Filter) return true; + if(name == Video::Shader) return true; + if(name == Video::FragmentShader) return true; + if(name == Video::VertexShader) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + if(name == Video::Filter) return settings.filter; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + if(settings.synchronize != any_cast(value)) { + settings.synchronize = any_cast(value); + if(glSwapInterval) glSwapInterval(settings.synchronize); + return true; + } + } + + if(name == Video::Filter) { + settings.filter = any_cast(value); + return true; + } + + if(name == Video::Shader) { + OpenGL::set_shader(any_cast(value)); + return true; + } + + if(name == Video::FragmentShader) { + OpenGL::set_fragment_shader(any_cast(value)); + return true; + } + + if(name == Video::VertexShader) { + OpenGL::set_vertex_shader(any_cast(value)); + return true; + } + + return false; + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + resize(width, height); + settings.width = width; + settings.height = height; + return OpenGL::lock(data, pitch); + } + + void unlock() { + } + + void clear() { + OpenGL::clear(); + if(glx.double_buffer) glXSwapBuffers(display, glxwindow); + } + + void refresh() { + //we must ensure that the child window is the same size as the parent window. + //unfortunately, we cannot hook the parent window resize event notification, + //as we did not create the parent window, nor have any knowledge of the toolkit used. + //therefore, inelegant as it may be, we query each window size and resize as needed. + XWindowAttributes parent, child; + XGetWindowAttributes(display, settings.handle, &parent); + XGetWindowAttributes(display, xwindow, &child); + if(child.width != parent.width || child.height != parent.height) { + XResizeWindow(display, xwindow, parent.width, parent.height); + } + + OpenGL::refresh(settings.filter == Video::FilterLinear, + settings.width, settings.height, parent.width, parent.height); + if(glx.double_buffer) glXSwapBuffers(display, glxwindow); + } + + bool init() { + term(); + + display = XOpenDisplay(0); + screen = DefaultScreen(display); + glXQueryVersion(display, &glx.version_major, &glx.version_minor); + //require GLX 1.2+ API + if(glx.version_major < 1 || (glx.version_major == 1 && glx.version_minor < 2)) return false; + + XWindowAttributes window_attributes; + XGetWindowAttributes(display, settings.handle, &window_attributes); + + //let GLX determine the best Visual to use for GL output; provide a few hints + //note: some video drivers will override double buffering attribute + int attributelist[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None }; + XVisualInfo *vi = glXChooseVisual(display, screen, attributelist); + + //Window settings.handle has already been realized, most likely with DefaultVisual. + //GLX requires that the GL output window has the same Visual as the GLX context. + //it is not possible to change the Visual of an already realized (created) window. + //therefore a new child window, using the same GLX Visual, must be created and binded to settings.handle. + colormap = XCreateColormap(display, RootWindow(display, vi->screen), vi->visual, AllocNone); + XSetWindowAttributes attributes; + attributes.colormap = colormap; + attributes.border_pixel = 0; + attributes.event_mask = StructureNotifyMask; + xwindow = XCreateWindow(display, /* parent = */ settings.handle, + /* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height, + /* border_width = */ 0, vi->depth, InputOutput, vi->visual, + CWColormap | CWBorderPixel | CWEventMask, &attributes); + XSetWindowBackground(display, xwindow, /* color = */ 0); + XMapWindow(display, xwindow); + XEvent event; + //window must be realized (appear onscreen) before we make the context current + XIfEvent(display, &event, glx_wait_for_map_notify, (char*)xwindow); + + glxcontext = glXCreateContext(display, vi, /* sharelist = */ 0, /* direct = */ GL_TRUE); + glXMakeCurrent(display, glxwindow = xwindow, glxcontext); + + //read attributes of frame buffer for later use, as requested attributes from above are not always granted + int value = 0; + glXGetConfig(display, vi, GLX_DOUBLEBUFFER, &value); + glx.double_buffer = value; + glx.is_direct = glXIsDirect(display, glxcontext); + + OpenGL::init(); + settings.width = 256; + settings.height = 256; + + //vertical synchronization + if(!glSwapInterval) glSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalSGI"); + if(!glSwapInterval) glSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalMESA"); + if( glSwapInterval) glSwapInterval(settings.synchronize); + + return true; + } + + void term() { + OpenGL::term(); + + if(glxcontext) { + glXDestroyContext(display, glxcontext); + glxcontext = 0; + } + + if(xwindow) { + XUnmapWindow(display, xwindow); + xwindow = 0; + } + + if(colormap) { + XFreeColormap(display, colormap); + colormap = 0; + } + } + + pVideoGLX() : glSwapInterval(0) { + settings.handle = 0; + settings.synchronize = false; + xwindow = 0; + colormap = 0; + glxcontext = 0; + glxwindow = 0; + } + + ~pVideoGLX() { term(); } +}; + +DeclareVideo(GLX) + +}; diff --git a/ruby/video/opengl.hpp b/ruby/video/opengl.hpp new file mode 100755 index 00000000..add60abb --- /dev/null +++ b/ruby/video/opengl.hpp @@ -0,0 +1,246 @@ +#include + +#if defined(PLATFORM_X) + #include + #define glGetProcAddress(name) (*glXGetProcAddress)((const GLubyte*)(name)) +#elif defined(PLATFORM_WIN) + #include + #define glGetProcAddress(name) wglGetProcAddress(name) +#else + #error "ruby::OpenGL: unsupported platform" +#endif + +PFNGLCREATEPROGRAMPROC glCreateProgram = 0; +PFNGLUSEPROGRAMPROC glUseProgram = 0; +PFNGLCREATESHADERPROC glCreateShader = 0; +PFNGLDELETESHADERPROC glDeleteShader = 0; +PFNGLSHADERSOURCEPROC glShaderSource = 0; +PFNGLCOMPILESHADERPROC glCompileShader = 0; +PFNGLATTACHSHADERPROC glAttachShader = 0; +PFNGLDETACHSHADERPROC glDetachShader = 0; +PFNGLLINKPROGRAMPROC glLinkProgram = 0; +PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = 0; +PFNGLUNIFORM1IPROC glUniform1i = 0; +PFNGLUNIFORM2FVPROC glUniform2fv = 0; +PFNGLUNIFORM4FVPROC glUniform4fv = 0; + +class OpenGL { +public: + GLuint gltexture; + GLuint glprogram; + GLuint fragmentshader; + GLuint vertexshader; + bool shader_support; + + uint32_t *buffer; + unsigned iwidth, iheight; + + void resize(unsigned width, unsigned height) { + if(iwidth >= width && iheight >= height) return; + + if(gltexture) glDeleteTextures(1, &gltexture); + iwidth = max(width, iwidth ); + iheight = max(height, iheight); + if(buffer) delete[] buffer; + buffer = new uint32_t[iwidth * iheight]; + + glGenTextures(1, &gltexture); + glBindTexture(GL_TEXTURE_2D, gltexture); + glPixelStorei(GL_UNPACK_ROW_LENGTH, iwidth); + glTexImage2D(GL_TEXTURE_2D, + /* mip-map level = */ 0, /* internal format = */ GL_RGB, + iwidth, iheight, /* border = */ 0, /* format = */ GL_BGRA, + GL_UNSIGNED_INT_8_8_8_8_REV, buffer); + } + + bool lock(uint32_t *&data, unsigned &pitch) { + pitch = iwidth * sizeof(uint32_t); + return data = buffer; + } + + void clear() { + memset(buffer, 0, iwidth * iheight * sizeof(uint32_t)); + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + glFlush(); + } + + void refresh(bool smooth, unsigned inwidth, unsigned inheight, unsigned outwidth, unsigned outheight) { + if(shader_support) { + glUseProgram(glprogram); + GLint location; + + float inputSize[2] = { (float)inwidth, (float)inheight }; + location = glGetUniformLocation(glprogram, "rubyInputSize"); + glUniform2fv(location, 1, inputSize); + + float outputSize[2] = { (float)outwidth, (float)outheight }; + location = glGetUniformLocation(glprogram, "rubyOutputSize"); + glUniform2fv(location, 1, outputSize); + + float textureSize[2] = { (float)iwidth, (float)iheight }; + location = glGetUniformLocation(glprogram, "rubyTextureSize"); + glUniform2fv(location, 1, textureSize); + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, smooth ? GL_LINEAR : GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, smooth ? GL_LINEAR : GL_NEAREST); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, outwidth, 0, outheight, -1.0, 1.0); + glViewport(0, 0, outwidth, outheight); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glPixelStorei(GL_UNPACK_ROW_LENGTH, iwidth); + glTexSubImage2D(GL_TEXTURE_2D, + /* mip-map level = */ 0, /* x = */ 0, /* y = */ 0, + inwidth, inheight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer); + + //OpenGL projection sets 0,0 as *bottom-left* of screen. + //therefore, below vertices flip image to support top-left source. + //texture range = x1:0.0, y1:0.0, x2:1.0, y2:1.0 + //vertex range = x1:0, y1:0, x2:width, y2:height + double w = double(inwidth) / double(iwidth); + double h = double(inheight) / double(iheight); + int u = outwidth; + int v = outheight; + glBegin(GL_TRIANGLE_STRIP); + glTexCoord2f(0, 0); glVertex3i(0, v, 0); + glTexCoord2f(w, 0); glVertex3i(u, v, 0); + glTexCoord2f(0, h); glVertex3i(0, 0, 0); + glTexCoord2f(w, h); glVertex3i(u, 0, 0); + glEnd(); + + glFlush(); + + if(shader_support) { + glUseProgram(0); + } + } + + void set_shader(const char *source) { + if(!shader_support) return; + + if(fragmentshader) { + glDetachShader(glprogram, fragmentshader); + glDeleteShader(fragmentshader); + fragmentshader = 0; + } + + if(vertexshader) { + glDetachShader(glprogram, vertexshader); + glDeleteShader(vertexshader); + vertexshader = 0; + } + + if(source) { + bool is_glsl = false; + string fragment_source; + string vertex_source; + + xml_element document = xml_parse(source); + foreach(head, document.element) { + if(head.name == "shader") { + foreach(attribute, head.attribute) { + if(attribute.name == "language" && attribute.content == "GLSL") is_glsl = true; + } + foreach(element, head.element) { + if(element.name == "fragment") { + fragment_source = element.parse(); + } else if(element.name == "vertex") { + vertex_source = element.parse(); + } + } + } + } + + if(is_glsl) { + if(fragment_source != "") set_fragment_shader(fragment_source); + if(vertex_source != "") set_vertex_shader(vertex_source); + } + } + + glLinkProgram(glprogram); + } + + void set_fragment_shader(const char *source) { + fragmentshader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragmentshader, 1, &source, 0); + glCompileShader(fragmentshader); + glAttachShader(glprogram, fragmentshader); + } + + void set_vertex_shader(const char *source) { + vertexshader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertexshader, 1, &source, 0); + glCompileShader(vertexshader); + glAttachShader(glprogram, vertexshader); + } + + void init() { + //disable unused features + glDisable(GL_ALPHA_TEST); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_POLYGON_SMOOTH); + glDisable(GL_STENCIL_TEST); + + //enable useful and required features + glEnable(GL_DITHER); + glEnable(GL_TEXTURE_2D); + + //bind shader functions + glCreateProgram = (PFNGLCREATEPROGRAMPROC)glGetProcAddress("glCreateProgram"); + glUseProgram = (PFNGLUSEPROGRAMPROC)glGetProcAddress("glUseProgram"); + glCreateShader = (PFNGLCREATESHADERPROC)glGetProcAddress("glCreateShader"); + glDeleteShader = (PFNGLDELETESHADERPROC)glGetProcAddress("glDeleteShader"); + glShaderSource = (PFNGLSHADERSOURCEPROC)glGetProcAddress("glShaderSource"); + glCompileShader = (PFNGLCOMPILESHADERPROC)glGetProcAddress("glCompileShader"); + glAttachShader = (PFNGLATTACHSHADERPROC)glGetProcAddress("glAttachShader"); + glDetachShader = (PFNGLDETACHSHADERPROC)glGetProcAddress("glDetachShader"); + glLinkProgram = (PFNGLLINKPROGRAMPROC)glGetProcAddress("glLinkProgram"); + glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)glGetProcAddress("glGetUniformLocation"); + glUniform1i = (PFNGLUNIFORM1IPROC)glGetProcAddress("glUniform1i"); + glUniform2fv = (PFNGLUNIFORM2FVPROC)glGetProcAddress("glUniform2fv"); + glUniform4fv = (PFNGLUNIFORM4FVPROC)glGetProcAddress("glUniform4fv"); + + shader_support = glCreateProgram && glUseProgram && glCreateShader + && glDeleteShader && glShaderSource && glCompileShader && glAttachShader + && glDetachShader && glLinkProgram && glGetUniformLocation + && glUniform1i && glUniform2fv && glUniform4fv; + + if(shader_support) glprogram = glCreateProgram(); + + //create surface texture + resize(256, 256); + } + + void term() { + if(gltexture) { + glDeleteTextures(1, &gltexture); + gltexture = 0; + } + + if(buffer) { + delete[] buffer; + buffer = 0; + iwidth = 0; + iheight = 0; + } + } + + OpenGL() { + gltexture = 0; + glprogram = 0; + fragmentshader = 0; + vertexshader = 0; + + buffer = 0; + iwidth = 0; + iheight = 0; + } +}; diff --git a/ruby/video/qtopengl.cpp b/ruby/video/qtopengl.cpp new file mode 100755 index 00000000..c357c403 --- /dev/null +++ b/ruby/video/qtopengl.cpp @@ -0,0 +1,174 @@ +#ifdef __APPLE__ + #include +#endif + +namespace ruby { + +class pVideoQtOpenGL { +public: + QWidget *parent; + QVBoxLayout *layout; + + class RubyGLWidget : public QGLWidget { + public: + GLuint texture; + unsigned textureWidth, textureHeight; + + uint32_t *buffer; + unsigned rasterWidth, rasterHeight; + + bool synchronize; + unsigned filter; + + void resize(unsigned width, unsigned height) { + if(width > textureWidth || height > textureHeight) { + textureWidth = max(width, textureWidth); + textureHeight = max(height, textureHeight); + + if(buffer) { + delete[] buffer; + glDeleteTextures(1, &texture); + } + + buffer = new uint32_t[textureWidth * textureHeight]; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glPixelStorei(GL_UNPACK_ROW_LENGTH, textureWidth); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, textureHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer); + } + } + + void updateSynchronization() { + #ifdef __APPLE__ + makeCurrent(); + CGLContextObj context = CGLGetCurrentContext(); + GLint value = synchronize; //0 = draw immediately (no vsync), 1 = draw once per frame (vsync) + CGLSetParameter(context, kCGLCPSwapInterval, &value); + #endif + } + + void paintGL() { + unsigned outputWidth = width(); + unsigned outputHeight = height(); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, outputWidth, 0, outputHeight, -1.0, 1.0); + glViewport(0, 0, outputWidth, outputHeight); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter == Video::FilterPoint) ? GL_NEAREST : GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter == Video::FilterPoint) ? GL_NEAREST : GL_LINEAR); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rasterWidth, rasterHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer); + + double w = (double)rasterWidth / (double)textureWidth; + double h = (double)rasterHeight / (double)textureHeight; + unsigned u = outputWidth; + unsigned v = outputHeight; + + glBegin(GL_TRIANGLE_STRIP); + glTexCoord2f(0, 0); glVertex3i(0, v, 0); + glTexCoord2f(w, 0); glVertex3i(u, v, 0); + glTexCoord2f(0, h); glVertex3i(0, 0, 0); + glTexCoord2f(w, h); glVertex3i(u, 0, 0); + glEnd(); + } + + void initializeGL() { + format().setDoubleBuffer(true); + + texture = 0; + textureWidth = 0; + textureHeight = 0; + buffer = 0; + resize(rasterWidth = 256, rasterHeight = 256); + + glDisable(GL_ALPHA_TEST); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_POLYGON_SMOOTH); + glDisable(GL_STENCIL_TEST); + glEnable(GL_DITHER); + glEnable(GL_TEXTURE_2D); + glClearColor(0.0, 0.0, 0.0, 0.0); + } + } *widget; + + bool cap(const string& name) { + if(name == Video::Synchronize) return true; + if(name == Video::Filter) return true; + if(name == "QWidget") return true; + return false; + } + + any get(const string& name) { + if(name == Video::Synchronize) return widget->synchronize; + if(name == Video::Filter) return widget->filter; + if(name == "QWidget") return parent; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Synchronize) { + widget->synchronize = any_cast(value); + widget->updateSynchronization(); + return true; + } + + if(name == Video::Filter) { + widget->filter = any_cast(value); + return true; + } + + if(name == "QWidget") { + parent = any_cast(value); + return true; + } + + return false; + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + widget->resize(width, height); + widget->rasterWidth = width; + widget->rasterHeight = height; + + pitch = widget->textureWidth * sizeof(uint32_t); + return data = widget->buffer; + } + + void unlock() { + } + + void clear() { + memset(widget->buffer, 0, widget->textureWidth * widget->textureHeight * sizeof(uint32_t)); + widget->updateGL(); + } + + void refresh() { + widget->updateGL(); + } + + bool init() { + layout = new QVBoxLayout; + layout->setMargin(0); + + widget = new RubyGLWidget; + widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(widget); + parent->setLayout(layout); + + return true; + } + + void term() { + } +}; + +DeclareVideo(QtOpenGL) + +}; diff --git a/ruby/video/qtraster.cpp b/ruby/video/qtraster.cpp new file mode 100755 index 00000000..414689b9 --- /dev/null +++ b/ruby/video/qtraster.cpp @@ -0,0 +1,126 @@ +namespace ruby { + +struct VideoQtRasterContext { + QImage *image; + unsigned width, height; + unsigned filter; +} context; + +class pVideoQtRaster { +public: + QWidget *parent; + QVBoxLayout *layout; + + struct QtImage : public QWidget { + VideoQtRasterContext &context; + + void paintEvent(QPaintEvent*) { + if(context.image == 0) return; + QPainter painter(this); + + if(size().width() == context.width && size().height() == context.height) { + painter.drawImage(0, 0, *context.image); + } else { + Qt::TransformationMode mode = Qt::FastTransformation; + if(context.filter == Video::FilterLinear) mode = Qt::SmoothTransformation; + painter.drawImage(0, 0, context.image->scaled(size(), Qt::IgnoreAspectRatio, mode)); + } + } + + QtImage(QWidget *parent, VideoQtRasterContext &context_) : QWidget(parent), context(context_) {} + } *widget; + + bool cap(const string& name) { + if(name == Video::Filter) return true; + if(name == "QWidget") return true; + return false; + } + + any get(const string& name) { + if(name == Video::Filter) return context.filter; + if(name == "QWidget") return parent; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Filter) { + context.filter = any_cast(value); + return true; + } + + if(name == "QWidget") { + parent = any_cast(value); + return true; + } + + return false; + } + + void resize(unsigned width, unsigned height) { + if(context.width != width || context.height != height) { + if(context.image) delete context.image; + context.image = new QImage(context.width = width, context.height = height, QImage::Format_RGB32); + } + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + //if image size has changed since last lock(), re-allocate buffer to match new size + if(width != context.width || height != context.height) resize(width, height); + + pitch = width * sizeof(uint32_t); + return data = (uint32_t*)context.image->bits(); + } + + void unlock() { + } + + void clear() { + context.image->fill(0); + widget->update(); + } + + void refresh() { + widget->update(); + } + + bool init() { + term(); + + layout = new QVBoxLayout; + layout->setMargin(0); + + context.image = 0; + context.width = 0; + context.height = 0; + context.filter = Video::FilterPoint; + resize(256, 256); + + widget = new QtImage(parent, context); + widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(widget); + parent->setLayout(layout); + clear(); + + return true; + } + + void term() { + if(context.image) delete context.image; + if(widget) delete widget; + if(layout) delete layout; + + context.image = 0; + widget = 0; + layout = 0; + } + + pVideoQtRaster() { + context.image = 0; + widget = 0; + layout = 0; + } +}; + +DeclareVideo(QtRaster) + +}; diff --git a/ruby/video/sdl.cpp b/ruby/video/sdl.cpp new file mode 100755 index 00000000..8f70342e --- /dev/null +++ b/ruby/video/sdl.cpp @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include + +namespace ruby { + +class pVideoSDL { +public: + Display *display; + SDL_Surface *screen, *buffer; + unsigned iwidth, iheight; + + struct { + uintptr_t handle; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = any_cast(value); + return true; + } + + return false; + } + + void resize(unsigned width, unsigned height) { + if(iwidth >= width && iheight >= height) return; + + iwidth = max(width, iwidth); + iheight = max(height, iheight); + + if(buffer) SDL_FreeSurface(buffer); + buffer = SDL_CreateRGBSurface( + SDL_SWSURFACE, iwidth, iheight, 32, + 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 + ); + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); + pitch = buffer->pitch; + return data = (uint32_t*)buffer->pixels; + } + + void unlock() { + if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); + } + + void clear() { + if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); + for(unsigned y = 0; y < iheight; y++) { + uint32_t *data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2); + for(unsigned x = 0; x < iwidth; x++) *data++ = 0xff000000; + } + if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); + refresh(); + } + + void refresh() { + //ruby input is X8R8G8B8, top 8-bits are ignored. + //as SDL forces us to use a 32-bit buffer, we must set alpha to 255 (full opacity) + //to prevent blending against the window beneath when X window visual is 32-bits. + if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); + for(unsigned y = 0; y < settings.height; y++) { + uint32_t *data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2); + for(unsigned x = 0; x < settings.width; x++) *data++ |= 0xff000000; + } + if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); + + XWindowAttributes attributes; + XGetWindowAttributes(display, settings.handle, &attributes); + + SDL_Rect src, dest; + + src.x = 0; + src.y = 0; + src.w = settings.width; + src.h = settings.height; + + dest.x = 0; + dest.y = 0; + dest.w = attributes.width; + dest.h = attributes.height; + + SDL_SoftStretch(buffer, &src, screen, &dest); + SDL_UpdateRect(screen, dest.x, dest.y, dest.w, dest.h); + } + + bool init() { + display = XOpenDisplay(0); + + char env[512]; + sprintf(env, "SDL_WINDOWID=%ld", (long int)settings.handle); + putenv(env); + + SDL_InitSubSystem(SDL_INIT_VIDEO); + screen = SDL_SetVideoMode(2560, 1600, 32, SDL_HWSURFACE); + + buffer = 0; + iwidth = 0; + iheight = 0; + resize(settings.width = 256, settings.height = 256); + + return true; + } + + void term() { + XCloseDisplay(display); + SDL_FreeSurface(buffer); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + } + + pVideoSDL() { + settings.handle = 0; + } +}; + +DeclareVideo(SDL) + +}; diff --git a/ruby/video/wgl.cpp b/ruby/video/wgl.cpp new file mode 100755 index 00000000..9a2e82ac --- /dev/null +++ b/ruby/video/wgl.cpp @@ -0,0 +1,153 @@ +/* + video.wgl + authors: byuu, krom +*/ + +#include "opengl.hpp" + +namespace ruby { + +class pVideoWGL : public OpenGL { +public: + BOOL (APIENTRY *glSwapInterval)(int); + + HDC display; + HGLRC wglcontext; + HWND window; + HINSTANCE glwindow; + + struct { + HWND handle; + bool synchronize; + unsigned filter; + string shader; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + if(name == Video::Filter) return true; + if(name == Video::Shader) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + if(name == Video::Filter) return settings.filter; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + if(settings.synchronize != any_cast(value)) { + settings.synchronize = any_cast(value); + if(wglcontext) { + init(); + OpenGL::set_shader(settings.shader); + } + } + } + + if(name == Video::Filter) { + settings.filter = any_cast(value); + return true; + } + + if(name == Video::Shader) { + settings.shader = any_cast(value); + OpenGL::set_shader(settings.shader); + return true; + } + + return false; + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + resize(width, height); + settings.width = width; + settings.height = height; + return OpenGL::lock(data, pitch); + } + + void unlock() { + } + + void clear() { + OpenGL::clear(); + SwapBuffers(display); + } + + void refresh() { + RECT rc; + GetClientRect(settings.handle, &rc); + + OpenGL::refresh(settings.filter == Video::FilterLinear, + settings.width, settings.height, + rc.right - rc.left, rc.bottom - rc.top); + + SwapBuffers(display); + } + + bool init() { + term(); + + GLuint pixel_format; + PIXELFORMATDESCRIPTOR pfd; + memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + + display = GetDC(settings.handle); + pixel_format = ChoosePixelFormat(display, &pfd); + SetPixelFormat(display, pixel_format, &pfd); + + wglcontext = wglCreateContext(display); + wglMakeCurrent(display, wglcontext); + + OpenGL::init(); + settings.width = 256; + settings.height = 256; + + //vertical synchronization + if(!glSwapInterval) glSwapInterval = (BOOL (APIENTRY*)(int))glGetProcAddress("wglSwapIntervalEXT"); + if( glSwapInterval) glSwapInterval(settings.synchronize); + + return true; + } + + void term() { + OpenGL::term(); + + if(wglcontext) { + wglDeleteContext(wglcontext); + wglcontext = 0; + } + } + + pVideoWGL() : glSwapInterval(0) { + settings.handle = 0; + settings.synchronize = false; + settings.filter = 0; + + window = 0; + wglcontext = 0; + glwindow = 0; + } + + ~pVideoWGL() { term(); } +}; + +DeclareVideo(WGL) + +}; diff --git a/ruby/video/xv.cpp b/ruby/video/xv.cpp new file mode 100755 index 00000000..10d51647 --- /dev/null +++ b/ruby/video/xv.cpp @@ -0,0 +1,498 @@ +#include +#include +#include +#include +#include + +extern "C" XvImage* XvShmCreateImage(Display*, XvPortID, int, char*, int, int, XShmSegmentInfo*); + +namespace ruby { + +class pVideoXv { +public: + uint32_t *buffer; + uint8_t *ytable, *utable, *vtable; + + enum XvFormat { + XvFormatRGB32, + XvFormatRGB24, + XvFormatRGB16, + XvFormatRGB15, + XvFormatYUY2, + XvFormatUYVY, + XvFormatUnknown + }; + + struct { + Display *display; + GC gc; + Window window; + Colormap colormap; + XShmSegmentInfo shminfo; + + int port; + int depth; + int visualid; + + XvImage *image; + XvFormat format; + uint32_t fourcc; + + unsigned width; + unsigned height; + } device; + + struct { + Window handle; + bool synchronize; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) { + return XInternAtom(XOpenDisplay(0), "XV_SYNC_TO_VBLANK", true) != None; + } + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + Display *display = XOpenDisplay(0); + Atom atom = XInternAtom(display, "XV_SYNC_TO_VBLANK", true); + if(atom != None && device.port >= 0) { + settings.synchronize = any_cast(value); + XvSetPortAttribute(display, device.port, atom, settings.synchronize); + return true; + } + return false; + } + + return false; + } + + void resize(unsigned width, unsigned height) { + if(device.width >= width && device.height >= height) return; + device.width = max(width, device.width); + device.height = max(height, device.height); + + XShmDetach(device.display, &device.shminfo); + shmdt(device.shminfo.shmaddr); + shmctl(device.shminfo.shmid, IPC_RMID, NULL); + XFree(device.image); + delete[] buffer; + + device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, device.width, device.height, &device.shminfo); + + device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777); + device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0); + device.shminfo.readOnly = false; + XShmAttach(device.display, &device.shminfo); + + buffer = new uint32_t[device.width * device.height]; + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + pitch = device.width * 4; + return data = buffer; + } + + void unlock() { + } + + void clear() { + memset(buffer, 0, device.width * device.height * sizeof(uint32_t)); + //clear twice in case video is double buffered ... + refresh(); + refresh(); + } + + void refresh() { + unsigned width = settings.width; + unsigned height = settings.height; + + XWindowAttributes target; + XGetWindowAttributes(device.display, device.window, &target); + + //we must ensure that the child window is the same size as the parent window. + //unfortunately, we cannot hook the parent window resize event notification, + //as we did not create the parent window, nor have any knowledge of the toolkit used. + //therefore, query each window size and resize as needed. + XWindowAttributes parent; + XGetWindowAttributes(device.display, settings.handle, &parent); + if(target.width != parent.width || target.height != parent.height) { + XResizeWindow(device.display, device.window, parent.width, parent.height); + } + + //update target width and height attributes + XGetWindowAttributes(device.display, device.window, &target); + + switch(device.format) { + case XvFormatRGB32: render_rgb32(width, height); break; + case XvFormatRGB24: render_rgb24(width, height); break; + case XvFormatRGB16: render_rgb16(width, height); break; + case XvFormatRGB15: render_rgb15(width, height); break; + case XvFormatYUY2: render_yuy2 (width, height); break; + case XvFormatUYVY: render_uyvy (width, height); break; + } + + XvShmPutImage(device.display, device.port, device.window, device.gc, device.image, + 0, 0, width, height, + 0, 0, target.width, target.height, + true); + } + + bool init() { + device.display = XOpenDisplay(0); + + if(!XShmQueryExtension(device.display)) { + fprintf(stderr, "VideoXv: XShm extension not found.\n"); + return false; + } + + //find an appropriate Xv port + device.port = -1; + XvAdaptorInfo *adaptor_info; + unsigned adaptor_count; + XvQueryAdaptors(device.display, DefaultRootWindow(device.display), &adaptor_count, &adaptor_info); + for(unsigned i = 0; i < adaptor_count; i++) { + //find adaptor that supports both input (memory->drawable) and image (drawable->screen) masks + if(adaptor_info[i].num_formats < 1) continue; + if(!(adaptor_info[i].type & XvInputMask)) continue; + if(!(adaptor_info[i].type & XvImageMask)) continue; + + device.port = adaptor_info[i].base_id; + device.depth = adaptor_info[i].formats->depth; + device.visualid = adaptor_info[i].formats->visual_id; + break; + } + XvFreeAdaptorInfo(adaptor_info); + if(device.port < 0) { + fprintf(stderr, "VideoXv: failed to find valid XvPort.\n"); + return false; + } + + //create child window to attach to parent window. + //this is so that even if parent window visual depth doesn't match Xv visual + //(common with composited windows), Xv can still render to child window. + XWindowAttributes window_attributes; + XGetWindowAttributes(device.display, settings.handle, &window_attributes); + + XVisualInfo visualtemplate; + visualtemplate.visualid = device.visualid; + visualtemplate.screen = DefaultScreen(device.display); + visualtemplate.depth = device.depth; + visualtemplate.visual = 0; + int visualmatches = 0; + XVisualInfo *visualinfo = XGetVisualInfo(device.display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualtemplate, &visualmatches); + if(visualmatches < 1 || !visualinfo->visual) { + if(visualinfo) XFree(visualinfo); + fprintf(stderr, "VideoXv: unable to find Xv-compatible visual.\n"); + return false; + } + + device.colormap = XCreateColormap(device.display, settings.handle, visualinfo->visual, AllocNone); + XSetWindowAttributes attributes; + attributes.colormap = device.colormap; + attributes.border_pixel = 0; + attributes.event_mask = StructureNotifyMask; + device.window = XCreateWindow(device.display, /* parent = */ settings.handle, + /* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height, + /* border_width = */ 0, device.depth, InputOutput, visualinfo->visual, + CWColormap | CWBorderPixel | CWEventMask, &attributes); + XFree(visualinfo); + XSetWindowBackground(device.display, device.window, /* color = */ 0); + XMapWindow(device.display, device.window); + + device.gc = XCreateGC(device.display, device.window, 0, 0); + + //set colorkey to auto paint, so that Xv video output is always visible + Atom atom = XInternAtom(device.display, "XV_AUTOPAINT_COLORKEY", true); + if(atom != None) XvSetPortAttribute(device.display, device.port, atom, 1); + + //find optimal rendering format + device.format = XvFormatUnknown; + signed format_count; + XvImageFormatValues *format = XvListImageFormats(device.display, device.port, &format_count); + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel == 32) { + device.format = XvFormatRGB32; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel == 24) { + device.format = XvFormatRGB24; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel <= 16 && format[i].red_mask == 0xf800) { + device.format = XvFormatRGB16; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel <= 16 && format[i].red_mask == 0x7c00) { + device.format = XvFormatRGB15; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) { + if(format[i].component_order[0] == 'Y' && format[i].component_order[1] == 'U' + && format[i].component_order[2] == 'Y' && format[i].component_order[3] == 'V' + ) { + device.format = XvFormatYUY2; + device.fourcc = format[i].id; + break; + } + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) { + if(format[i].component_order[0] == 'U' && format[i].component_order[1] == 'Y' + && format[i].component_order[2] == 'V' && format[i].component_order[3] == 'Y' + ) { + device.format = XvFormatUYVY; + device.fourcc = format[i].id; + break; + } + } + } + + free(format); + if(device.format == XvFormatUnknown) { + fprintf(stderr, "VideoXv: unable to find a supported image format.\n"); + return false; + } + + device.width = 256; + device.height = 256; + + device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, device.width, device.height, &device.shminfo); + if(!device.image) { + fprintf(stderr, "VideoXv: XShmCreateImage failed.\n"); + return false; + } + + device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777); + device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0); + device.shminfo.readOnly = false; + if(!XShmAttach(device.display, &device.shminfo)) { + fprintf(stderr, "VideoXv: XShmAttach failed.\n"); + return false; + } + + buffer = new uint32_t[device.width * device.height]; + settings.width = 256; + settings.height = 256; + init_yuv_tables(); + clear(); + return true; + } + + void term() { + XShmDetach(device.display, &device.shminfo); + shmdt(device.shminfo.shmaddr); + shmctl(device.shminfo.shmid, IPC_RMID, NULL); + XFree(device.image); + + if(device.window) { + XUnmapWindow(device.display, device.window); + device.window = 0; + } + + if(device.colormap) { + XFreeColormap(device.display, device.colormap); + device.colormap = 0; + } + + if(buffer) { delete[] buffer; buffer = 0; } + if(ytable) { delete[] ytable; ytable = 0; } + if(utable) { delete[] utable; utable = 0; } + if(vtable) { delete[] vtable; vtable = 0; } + } + + void render_rgb32(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint32_t *output = (uint32_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + memcpy(output, input, width * 4); + input += device.width; + output += device.width; + } + } + + void render_rgb24(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint8_t *output = (uint8_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = p; + *output++ = p >> 8; + *output++ = p >> 16; + } + + input += (device.width - width); + output += (device.width - width) * 3; + } + } + + void render_rgb16(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = ((p >> 8) & 0xf800) | ((p >> 5) & 0x07e0) | ((p >> 3) & 0x001f); //RGB32->RGB16 + } + + input += device.width - width; + output += device.width - width; + } + } + + void render_rgb15(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x001f); //RGB32->RGB15 + } + + input += device.width - width; + output += device.width - width; + } + } + + void render_yuy2(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width >> 1; x++) { + uint32_t p0 = *input++; + uint32_t p1 = *input++; + p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); //RGB32->RGB16 + p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); //RGB32->RGB16 + + uint8_t u = (utable[p0] + utable[p1]) >> 1; + uint8_t v = (vtable[p0] + vtable[p1]) >> 1; + + *output++ = (u << 8) | ytable[p0]; + *output++ = (v << 8) | ytable[p1]; + } + + input += device.width - width; + output += device.width - width; + } + } + + void render_uyvy(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width >> 1; x++) { + uint32_t p0 = *input++; + uint32_t p1 = *input++; + p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); + p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); + + uint8_t u = (utable[p0] + utable[p1]) >> 1; + uint8_t v = (vtable[p0] + vtable[p1]) >> 1; + + *output++ = (ytable[p0] << 8) | u; + *output++ = (ytable[p1] << 8) | v; + } + + input += device.width - width; + output += device.width - width; + } + } + + void init_yuv_tables() { + ytable = new uint8_t[65536]; + utable = new uint8_t[65536]; + vtable = new uint8_t[65536]; + + for(unsigned i = 0; i < 65536; i++) { + //extract RGB565 color data from i + uint8_t r = (i >> 11) & 31, g = (i >> 5) & 63, b = (i) & 31; + r = (r << 3) | (r >> 2); //R5->R8 + g = (g << 2) | (g >> 4); //G6->G8 + b = (b << 3) | (b >> 2); //B5->B8 + + //ITU-R Recommendation BT.601 + //double lr = 0.299, lg = 0.587, lb = 0.114; + int y = int( +(double(r) * 0.257) + (double(g) * 0.504) + (double(b) * 0.098) + 16.0 ); + int u = int( -(double(r) * 0.148) - (double(g) * 0.291) + (double(b) * 0.439) + 128.0 ); + int v = int( +(double(r) * 0.439) - (double(g) * 0.368) - (double(b) * 0.071) + 128.0 ); + + //ITU-R Recommendation BT.709 + //double lr = 0.2126, lg = 0.7152, lb = 0.0722; + //int y = int( double(r) * lr + double(g) * lg + double(b) * lb ); + //int u = int( (double(b) - y) / (2.0 - 2.0 * lb) + 128.0 ); + //int v = int( (double(r) - y) / (2.0 - 2.0 * lr) + 128.0 ); + + ytable[i] = y < 0 ? 0 : y > 255 ? 255 : y; + utable[i] = u < 0 ? 0 : u > 255 ? 255 : u; + vtable[i] = v < 0 ? 0 : v > 255 ? 255 : v; + } + } + + pVideoXv() { + device.window = 0; + device.colormap = 0; + device.port = -1; + + ytable = 0; + utable = 0; + vtable = 0; + + settings.handle = 0; + settings.synchronize = false; + } + + ~pVideoXv() { + term(); + } +}; + +DeclareVideo(Xv) + +}; diff --git a/sync.sh b/sync.sh new file mode 100755 index 00000000..765c0b7a --- /dev/null +++ b/sync.sh @@ -0,0 +1,20 @@ +synchronize() { + if [ -d ../"$1" ]; then + test -d "$1" && rm -r "$1" + cp -r ../"$1" ./"$1" + fi +} + +synchronize "libco" +synchronize "nall" +synchronize "ruby" +synchronize "phoenix" + +test -d libco/doc && rm -r libco/doc +test -d libco/test && rm -r libco/test +test -d ruby/_test && rm -r ruby/_test +test -d phoenix/nall && rm -r phoenix/nall +rm -r phoenix/test* +rm -r phoenix/*.sh +rm -r phoenix/*.bat + diff --git a/ui/Makefile b/ui/Makefile new file mode 100755 index 00000000..c7c9db80 --- /dev/null +++ b/ui/Makefile @@ -0,0 +1,71 @@ +ui_objects := ui-main ui-utility +ui_objects += ruby phoenix + +# platform +ifeq ($(platform),x) + phoenix_compile = $(call compile,-DPHOENIX_GTK `pkg-config --cflags gtk+-2.0`) + link += `pkg-config --libs gtk+-2.0` +# phoenix_compile = $(call compile,-DPHOENIX_QT `pkg-config --cflags QtCore QtGui`) +# link += `pkg-config --libs QtCore QtGui` + + ruby := video.glx video.xv video.sdl + ruby += audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao + ruby += input.sdl input.x + + link += $(if $(findstring audio.openal,$(ruby)),-lopenal) +else ifeq ($(platform),osx) + phoenix_compile = $(call compile,-DPHOENIX_QT) + link += + + ruby := + ruby += audio.openal + ruby += input.carbon + + link += $(if $(findstring audio.openal,$(ruby)),-framework OpenAL) +else ifeq ($(platform),win) + phoenix_compile = $(call compile,-DPHOENIX_WINDOWS) + link += + + ruby := video.direct3d video.wgl video.directdraw video.gdi + ruby += audio.directsound audio.xaudio2 + ruby += input.rawinput input.directinput + + link += $(if $(findstring audio.openal,$(ruby)),-lopenal32) +endif + +# ruby +rubyflags := $(if $(finstring .sdl,$(ruby)),`sdl-config --cflags`) + +link += $(if $(findstring .sdl,$(ruby)),`sdl-config --libs`) +link += $(if $(findstring video.direct3d,$(ruby)),-ld3d9) +link += $(if $(findstring video.directdraw,$(ruby)),-lddraw) +link += $(if $(findstring video.glx,$(ruby)),-lGL) +link += $(if $(findstring video.wgl,$(ruby)),-lopengl32) +link += $(if $(findstring video.xv,$(ruby)),-lXv) +link += $(if $(findstring audio.alsa,$(ruby)),-lasound) +link += $(if $(findstring audio.ao,$(ruby)),-lao) +link += $(if $(findstring audio.directsound,$(ruby)),-ldsound) +link += $(if $(findstring audio.pulseaudio,$(ruby)),-lpulse) +link += $(if $(findstring audio.pulseaudiosimple,$(ruby)),-lpulse-simple) +link += $(if $(findstring audio.xaudio2,$(ruby)),-lole32) +link += $(if $(findstring input.directinput,$(ruby)),-ldinput8 -ldxguid) +link += $(if $(findstring input.rawinput,$(ruby)),-ldinput8 -ldxguid) + +rubydef := $(foreach c,$(subst .,_,$(call strupper,$(ruby))),-D$c) + +# rules +objects := $(ui_objects) $(objects) + +obj/ui-main.o: $(ui)/main.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/*); $(phoenix_compile) +obj/ui-utility.o: $(ui)/utility/utility.cpp $(call rwildcard,$(ui)/utility/*); $(phoenix_compile) + +obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*) + $(call compile,$(rubydef) $(rubyflags)) + +obj/phoenix.o: phoenix/phoenix.cpp $(call rwildcard,phoenix/*) + $(phoenix_compile) + +# targets +ui_build:; + +ui_clean:; diff --git a/ui/base.hpp b/ui/base.hpp new file mode 100755 index 00000000..323331fb --- /dev/null +++ b/ui/base.hpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include +using namespace nall; + +#include +using namespace phoenix; + +#include + +#include "interface.hpp" + +#include "general/main-window.hpp" + +#include "utility/utility.hpp" + +struct Application { + bool quit; + + void main(int argc, char **argv); +}; + +extern Application application; diff --git a/ui/general/main-window.cpp b/ui/general/main-window.cpp new file mode 100755 index 00000000..fb08eac2 --- /dev/null +++ b/ui/general/main-window.cpp @@ -0,0 +1,46 @@ +MainWindow mainWindow; + +void MainWindow::create() { + Window::create(128, 128, 160 * 3, 144 * 3, { GameBoy::Info::Name, " v", GameBoy::Info::Version }); + + system.create(*this, "System"); + systemLoadCartridge.create(system, "Load Cartridge ..."); + systemSeparator1.create(system); + systemPower.create(system, "Power Cycle"); + systemPower.setEnabled(false); + systemReset.create(system, "Reset"); + systemReset.setEnabled(false); + + settings.create(*this, "Settings"); + settings.setEnabled(false); + + tools.create(*this, "Tools"); + tools.setEnabled(false); + + help.create(*this, "Help"); + helpAbout.create(help, "About ..."); + + viewport.create(*this, 0, 0, 160 * 3, 144 * 3); + + setMenuVisible(true); + setStatusVisible(true); + + onClose = []() { + application.quit = true; + return false; + }; + + systemLoadCartridge.onTick = []() { + string filename = OS::fileOpen(mainWindow, "Game Boy cartridges\t*.gb", "/media/sdb1/root/gameboy_images/"); + if(filename != "") utility.loadCartridge(filename); + }; + + helpAbout.onTick = []() { + MessageWindow::information(mainWindow, { + "bgameboy\n\n", + "Version: ", GameBoy::Info::Version, "\n", + "Author: byuu\n", + "Homepage: http://byuu.org/" + }); + }; +} diff --git a/ui/general/main-window.hpp b/ui/general/main-window.hpp new file mode 100755 index 00000000..b9d965ad --- /dev/null +++ b/ui/general/main-window.hpp @@ -0,0 +1,20 @@ +struct MainWindow : Window { + Menu system; + MenuItem systemLoadCartridge; + MenuSeparator systemSeparator1; + MenuItem systemPower; + MenuItem systemReset; + + Menu settings; + + Menu tools; + + Menu help; + MenuItem helpAbout; + + Viewport viewport; + + void create(); +}; + +extern MainWindow mainWindow; diff --git a/ui/interface.cpp b/ui/interface.cpp new file mode 100755 index 00000000..dd28990d --- /dev/null +++ b/ui/interface.cpp @@ -0,0 +1,18 @@ +Interface interface; + +void Interface::video_refresh(const uint8_t *data) { +} + +void Interface::audio_sample(signed left, signed right) { +} + +void Interface::input_poll() { +} + +bool Interface::input_poll(unsigned id) { + return false; +} + +void Interface::message(const string &text) { + MessageWindow::information(mainWindow, text); +} diff --git a/ui/interface.hpp b/ui/interface.hpp new file mode 100755 index 00000000..e33902d5 --- /dev/null +++ b/ui/interface.hpp @@ -0,0 +1,10 @@ +struct Interface : public GameBoy::Interface { + void video_refresh(const uint8_t *data); + void audio_sample(signed left, signed right); + void input_poll(); + bool input_poll(unsigned id); + + void message(const string &text); +}; + +extern Interface interface; diff --git a/ui/main.cpp b/ui/main.cpp new file mode 100755 index 00000000..04a697c8 --- /dev/null +++ b/ui/main.cpp @@ -0,0 +1,26 @@ +#include "base.hpp" +Application application; + +#include "interface.cpp" + +#include "general/main-window.cpp" + +void Application::main(int argc, char **argv) { + quit = false; + + mainWindow.create(); + + mainWindow.setVisible(); + + GameBoy::system.init(&interface); + + while(quit == false) { + OS::run(); + } +} + +int main(int argc, char **argv) { + application.main(argc, argv); + + return 0; +} diff --git a/ui/utility/utility.cpp b/ui/utility/utility.cpp new file mode 100755 index 00000000..864a1393 --- /dev/null +++ b/ui/utility/utility.cpp @@ -0,0 +1,15 @@ +#include "../base.hpp" +Utility utility; + +void Utility::loadCartridge(const char *filename) { + file fp; + if(fp.open(filename, file::mode::read)) { + unsigned size = fp.size(); + uint8_t *data = new uint8_t[size]; + fp.read(data, size); + fp.close(); + GameBoy::cartridge.load(data, size); + delete[] data; + GameBoy::system.power(); + } +} diff --git a/ui/utility/utility.hpp b/ui/utility/utility.hpp new file mode 100755 index 00000000..d6e1afaa --- /dev/null +++ b/ui/utility/utility.hpp @@ -0,0 +1,5 @@ +struct Utility { + void loadCartridge(const char *filename); +}; + +extern Utility utility; From 246d6aaf08730daef64837229f055e0c6c47a9d0 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Tue, 28 Dec 2010 12:57:58 +1100 Subject: [PATCH 02/12] Add .gitignore files to populate obj/ and out/ --- obj/.gitignore | 1 + out/.gitignore | 1 + 2 files changed, 2 insertions(+) create mode 100644 obj/.gitignore create mode 100644 out/.gitignore diff --git a/obj/.gitignore b/obj/.gitignore new file mode 100644 index 00000000..72e8ffc0 --- /dev/null +++ b/obj/.gitignore @@ -0,0 +1 @@ +* diff --git a/out/.gitignore b/out/.gitignore new file mode 100644 index 00000000..72e8ffc0 --- /dev/null +++ b/out/.gitignore @@ -0,0 +1 @@ +* From e0a9f1cf2c50d7da2e11deca9336ebcd4540cc60 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Tue, 28 Dec 2010 17:03:02 +1100 Subject: [PATCH 03/12] Update to release v000r01. byuu says: Hooked up a scheduler to enter/exit the CPU core wherever I want. Added basic 4*1024*1024hz clock, and about eleven or so opcodes. Creating the disassembler as I encounter each new opcode, not skipping ahead to do all 'like other' opcodes, eg if I add 'dec b', I don't then add 'dec c' until I encounter it. --- gameboy/Makefile | 5 +- gameboy/cartridge/cartridge.cpp | 1 - gameboy/cpu/core/core.cpp | 91 ++++++++++++++++++++++++++++ gameboy/cpu/core/core.hpp | 27 +++++++++ gameboy/cpu/core/disassembler.cpp | 30 +++++++++ gameboy/cpu/{ => core}/registers.hpp | 21 +++++++ gameboy/cpu/cpu.cpp | 33 +++++++--- gameboy/cpu/cpu.hpp | 12 +++- gameboy/cpu/timing/opcode.cpp | 18 ++++++ gameboy/cpu/timing/timing.cpp | 28 +++++++++ gameboy/cpu/timing/timing.hpp | 8 +++ gameboy/gameboy.hpp | 18 +++++- gameboy/scheduler/scheduler.cpp | 33 ++++++++++ gameboy/scheduler/scheduler.hpp | 13 ++++ gameboy/system/system.cpp | 6 ++ gameboy/system/system.hpp | 1 + ui/main.cpp | 4 ++ 17 files changed, 336 insertions(+), 13 deletions(-) create mode 100755 gameboy/cpu/core/core.cpp create mode 100755 gameboy/cpu/core/core.hpp create mode 100755 gameboy/cpu/core/disassembler.cpp rename gameboy/cpu/{ => core}/registers.hpp (88%) create mode 100755 gameboy/cpu/timing/opcode.cpp create mode 100755 gameboy/cpu/timing/timing.cpp create mode 100755 gameboy/cpu/timing/timing.hpp create mode 100755 gameboy/scheduler/scheduler.cpp create mode 100755 gameboy/scheduler/scheduler.hpp diff --git a/gameboy/Makefile b/gameboy/Makefile index d368bc29..ecb87ae3 100755 --- a/gameboy/Makefile +++ b/gameboy/Makefile @@ -1,10 +1,13 @@ gameboy_objects := libco -gameboy_objects += gameboy-system gameboy-cartridge gameboy-memory gameboy-cpu +gameboy_objects += gameboy-system gameboy-scheduler +gameboy_objects += gameboy-cartridge gameboy-memory +gameboy_objects += gameboy-cpu objects += $(gameboy_objects) obj/libco.o: libco/libco.c libco/* obj/gameboy-system.o: $(gameboy)/system/system.cpp $(call rwildcard,$(gameboy)/system/) +obj/gameboy-scheduler.o: $(gameboy)/scheduler/scheduler.cpp $(call rwildcard,$(gameboy)/scheduler/) obj/gameboy-cartridge.o: $(gameboy)/cartridge/cartridge.cpp $(call rwildcard,$(gameboy)/cartridge/) obj/gameboy-memory.o: $(gameboy)/memory/memory.cpp $(call rwildcard,$(gameboy)/memory/) obj/gameboy-cpu.o: $(gameboy)/cpu/cpu.cpp $(call rwildcard,$(gameboy)/cpu/) diff --git a/gameboy/cartridge/cartridge.cpp b/gameboy/cartridge/cartridge.cpp index 4b7bbfdf..b900cbbc 100755 --- a/gameboy/cartridge/cartridge.cpp +++ b/gameboy/cartridge/cartridge.cpp @@ -41,7 +41,6 @@ void Cartridge::load(uint8_t *data, unsigned size) { } loaded = true; - system.interface->message({ "Loaded:\n", info.name }); } void Cartridge::unload() { diff --git a/gameboy/cpu/core/core.cpp b/gameboy/cpu/core/core.cpp new file mode 100755 index 00000000..d81d9f71 --- /dev/null +++ b/gameboy/cpu/core/core.cpp @@ -0,0 +1,91 @@ +#ifdef CPU_CPP + +#include "disassembler.cpp" + +unsigned opcode_counter = 0; + +void CPU::op_unknown() { + uint8 opcode = bus.read(--r[PC]); + print( + "CPU: unknown opcode [", hex<2>(opcode), "]\n", + "af:", hex<4>(r[AF]), " bc:", hex<4>(r[BC]), " de:", hex<4>(r[DE]), " hl:", hex<4>(r[HL]), " ", + "sp:", hex<4>(r[SP]), " pc:", hex<4>(r[PC]), "\n", + "ly:", decimal<3, ' '>(status.lycounter), " exec:", opcode_counter, "\n" + ); + while(true) scheduler.exit(); +} + +//8-bit load commands + +template void CPU::op_ld_r_n() { + r[x] = op_read(r[PC]++); +} + +void CPU::op_ldd_hl_a() { + op_write(r[HL], r[A]); + r[HL]--; +} + +//16-bit load commands + +template void CPU::op_ld_rr_nn() { + r[y] = op_read(r[PC]++); + r[x] = op_read(r[PC]++); +} + +//8-bit arithmetic commands + +template void CPU::op_xor_r() { + r[A] ^= r[x]; + r.f.z = r[A] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = 0; +} + +template void CPU::op_dec_r() { + r[x]--; + r.f.z = r[x] == 0; + r.f.n = 0; //??? + r.f.h = 0; //??? +} + +//control commands + +void CPU::op_nop() { +} + +//jump commands + +void CPU::op_jp_nn() { + uint8 lo = op_read(r[PC]++); + uint8 hi = op_read(r[PC]++); + r[PC] = (hi << 8) | (lo << 0); + op_io(); +} + +template void CPU::op_jr_f_n() { + int8 n = op_read(r[PC]++); + if(r.f[x] == y) { + r[PC] += n; + op_io(); + } +} + +void CPU::initialize_opcode_table() { + for(unsigned n = 0; n < 256; n++) opcode_table[n] = &CPU::op_unknown; + + opcode_table[0x00] = &CPU::op_nop; + opcode_table[0x05] = &CPU::op_dec_r; + opcode_table[0x06] = &CPU::op_ld_r_n; + opcode_table[0x0d] = &CPU::op_dec_r; + opcode_table[0x0e] = &CPU::op_ld_r_n; + opcode_table[0x20] = &CPU::op_jr_f_n; + opcode_table[0x21] = &CPU::op_ld_rr_nn; + opcode_table[0x32] = &CPU::op_ldd_hl_a; + opcode_table[0x3e] = &CPU::op_ld_r_n; + opcode_table[0xaf] = &CPU::op_xor_r; + opcode_table[0xc3] = &CPU::op_jp_nn; +} + +#endif diff --git a/gameboy/cpu/core/core.hpp b/gameboy/cpu/core/core.hpp new file mode 100755 index 00000000..86c9ee04 --- /dev/null +++ b/gameboy/cpu/core/core.hpp @@ -0,0 +1,27 @@ +#include "registers.hpp" +void (CPU::*opcode_table[256])(); +void initialize_opcode_table(); + +void op_unknown(); + +//8-bit load commands +template void op_ld_r_n(); +void op_ldd_hl_a(); + +//16-bit load commands +template void op_ld_rr_nn(); + +//8-bit arithmetic commands +template void op_xor_r(); +template void op_dec_r(); + +//control commands +void op_nop(); + +//jump commands +void op_jp_nn(); +template void op_jr_f_n(); + +//disassembler.cpp +string disassemble(uint16 pc); +string disassemble_opcode(uint16 pc); diff --git a/gameboy/cpu/core/disassembler.cpp b/gameboy/cpu/core/disassembler.cpp new file mode 100755 index 00000000..85e8e551 --- /dev/null +++ b/gameboy/cpu/core/disassembler.cpp @@ -0,0 +1,30 @@ +#ifdef CPU_CPP + +string CPU::disassemble(uint16 pc) { + return { hex<4>(pc), " ", disassemble_opcode(pc) }; +} + +string CPU::disassemble_opcode(uint16 pc) { + uint8 opcode = bus.read(pc); + uint8 p0 = bus.read(pc + 1); + uint8 p1 = bus.read(pc + 2); + uint8 p2 = bus.read(pc + 3); + + switch(opcode) { + case 0x00: return { "nop" }; + case 0x05: return { "dec b" }; + case 0x06: return { "ld b,$", hex<2>(p0) }; + case 0x0d: return { "dec c" }; + case 0x0e: return { "ld c,$", hex<2>(p0) }; + case 0x20: return { "jp nz,$", hex<2>(p0) }; + case 0x21: return { "ld hl,$", hex<2>(p1), hex<2>(p0) }; + case 0x32: return { "ldd (hl),a" }; + case 0x3e: return { "ld a,$", hex<2>(p0) }; + case 0xaf: return { "xor a" }; + case 0xc3: return { "jp $", hex<2>(p1), hex<2>(p0) }; + } + + return { "???? [", hex<2>(opcode), ",", hex<2>(p1), ",", hex<2>(p0), "]" }; +} + +#endif diff --git a/gameboy/cpu/registers.hpp b/gameboy/cpu/core/registers.hpp similarity index 88% rename from gameboy/cpu/registers.hpp rename to gameboy/cpu/core/registers.hpp index 45c8cbf7..db4cb602 100755 --- a/gameboy/cpu/registers.hpp +++ b/gameboy/cpu/core/registers.hpp @@ -1,3 +1,15 @@ +enum { + A, F, AF, + B, C, BC, + D, E, DE, + H, L, HL, + SP, PC, +}; + +enum { + ZF, NF, HF, CF, +}; + //register base class //the idea here is to have all registers derive from a single base class. //this allows construction of opcodes that can take any register as input or output, @@ -36,6 +48,10 @@ struct RegisterF : Register { bool z, n, h, c; operator unsigned() const { return (z << 7) | (n << 6) | (h << 5) | (c << 4); } unsigned operator=(unsigned x) { z = x & 0x80; n = x & 0x40; h = x & 0x20; c = x & 0x10; return *this; } + bool& operator[](unsigned r) { + static bool* table[] = { &z, &n, &h, &c }; + return *table[r]; + } }; struct Register16 : Register { @@ -75,5 +91,10 @@ struct Registers { Register16 sp; Register16 pc; + Register& operator[](unsigned r) { + static Register* table[] = { &a, &f, &af, &b, &c, &bc, &d, &e, &de, &h, &l, &hl, &sp, &pc }; + return *table[r]; + } + Registers() : af(a, f), bc(b, c), de(d, e), hl(h, l) {} } r; diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp index 2a986aec..b0646f32 100755 --- a/gameboy/cpu/cpu.cpp +++ b/gameboy/cpu/cpu.cpp @@ -1,18 +1,24 @@ -//Sharp LR35902 @ 4.19MHz - #include #define CPU_CPP namespace GameBoy { +#include "core/core.cpp" +#include "timing/timing.cpp" CPU cpu; +void CPU::Main() { + cpu.main(); +} + void CPU::main() { while(true) { - uint8 opcode = bus.read(r.pc++); - switch(opcode) { - case 0x00: break; //NOP - } + print(disassemble(r[PC]), "\n"); + + uint8 opcode = op_read(r[PC]++); + (this->*opcode_table[opcode])(); + + opcode_counter++; } } @@ -21,7 +27,20 @@ void CPU::power() { } void CPU::reset() { - r.pc = 0x0100; + create(Main, 4 * 1024 * 1024); + + r[PC] = 0x0100; + r[SP] = 0xfffe; + r[AF] = 0x0000; + r[BC] = 0x0000; + r[DE] = 0x0000; + r[HL] = 0x0000; + + status.lycounter = 0; +} + +CPU::CPU() { + initialize_opcode_table(); } } diff --git a/gameboy/cpu/cpu.hpp b/gameboy/cpu/cpu.hpp index 073d12c9..fa28f0c3 100755 --- a/gameboy/cpu/cpu.hpp +++ b/gameboy/cpu/cpu.hpp @@ -1,10 +1,16 @@ -class CPU { -public: - #include "registers.hpp" +struct CPU : Processor { + #include "core/core.hpp" + #include "timing/timing.hpp" + struct Status { + unsigned lycounter; + } status; + + static void Main(); void main(); void power(); void reset(); + CPU(); }; extern CPU cpu; diff --git a/gameboy/cpu/timing/opcode.cpp b/gameboy/cpu/timing/opcode.cpp new file mode 100755 index 00000000..b952e3d6 --- /dev/null +++ b/gameboy/cpu/timing/opcode.cpp @@ -0,0 +1,18 @@ +#ifdef CPU_CPP + +void CPU::op_io() { + add_clocks(4); +} + +uint8 CPU::op_read(uint16 addr) { + uint8 r = bus.read(addr); + add_clocks(4); + return r; +} + +void CPU::op_write(uint16 addr, uint8 data) { + bus.write(addr, data); + add_clocks(4); +} + +#endif diff --git a/gameboy/cpu/timing/timing.cpp b/gameboy/cpu/timing/timing.cpp new file mode 100755 index 00000000..becf6feb --- /dev/null +++ b/gameboy/cpu/timing/timing.cpp @@ -0,0 +1,28 @@ +//4194304hz (4 * 1024 * 1024) +//70224 clocks/frame +// 456 clocks/scanline +// 154 scanlines/frame + +#ifdef CPU_CPP + +#include "opcode.cpp" + +void CPU::add_clocks(unsigned clocks) { + clock += clocks; + + if(clock >= 456) scanline(); +} + +void CPU::scanline() { + clock -= 456; + + status.lycounter++; + if(status.lycounter >= 154) frame(); +} + +void CPU::frame() { + status.lycounter -= 154; + scheduler.exit(); +} + +#endif diff --git a/gameboy/cpu/timing/timing.hpp b/gameboy/cpu/timing/timing.hpp new file mode 100755 index 00000000..89dbf7d8 --- /dev/null +++ b/gameboy/cpu/timing/timing.hpp @@ -0,0 +1,8 @@ +void add_clocks(unsigned clocks); +void scanline(); +void frame(); + +//opcode.cpp +void op_io(); +uint8 op_read(uint16 addr); +void op_write(uint16 addr, uint8 data); diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp index 917ea17c..c9c6948b 100755 --- a/gameboy/gameboy.hpp +++ b/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000"; + static const char Version[] = "000.01"; } } @@ -28,7 +28,23 @@ namespace GameBoy { typedef uint32_t uint32; typedef uint64_t uint64; + struct Processor { + cothread_t thread; + unsigned frequency; + int64 clock; + + inline void create(void (*entrypoint_)(), unsigned frequency_) { + if(thread) co_delete(thread); + thread = co_create(65536 * sizeof(void*), entrypoint_); + frequency = frequency_; + clock = 0; + } + + inline Processor() : thread(0) {} + }; + #include + #include #include #include #include diff --git a/gameboy/scheduler/scheduler.cpp b/gameboy/scheduler/scheduler.cpp new file mode 100755 index 00000000..e8a98068 --- /dev/null +++ b/gameboy/scheduler/scheduler.cpp @@ -0,0 +1,33 @@ +#include + +#define SCHEDULER_CPP +namespace GameBoy { + +Scheduler scheduler; + +void Scheduler::enter() { + host_thread = co_active(); + co_switch(active_thread); +} + +void Scheduler::exit() { + active_thread = co_active(); + co_switch(host_thread); +} + +void Scheduler::swapto(Processor &p) { + active_thread = p.thread; + co_switch(active_thread); +} + +void Scheduler::init() { + host_thread = co_active(); + active_thread = cpu.thread; +} + +Scheduler::Scheduler() { + host_thread = 0; + active_thread = 0; +} + +} diff --git a/gameboy/scheduler/scheduler.hpp b/gameboy/scheduler/scheduler.hpp new file mode 100755 index 00000000..69337acf --- /dev/null +++ b/gameboy/scheduler/scheduler.hpp @@ -0,0 +1,13 @@ +struct Scheduler { + cothread_t host_thread; + cothread_t active_thread; + + void enter(); + void exit(); + void swapto(Processor&); + + void init(); + Scheduler(); +}; + +extern Scheduler scheduler; diff --git a/gameboy/system/system.cpp b/gameboy/system/system.cpp index bebae611..2a8b0aa1 100755 --- a/gameboy/system/system.cpp +++ b/gameboy/system/system.cpp @@ -11,10 +11,16 @@ void System::init(Interface *interface_) { void System::power() { cpu.power(); + scheduler.init(); } void System::reset() { cpu.reset(); + scheduler.init(); +} + +void System::run() { + scheduler.enter(); } } diff --git a/gameboy/system/system.hpp b/gameboy/system/system.hpp index ccc9b9c0..ccc6c889 100755 --- a/gameboy/system/system.hpp +++ b/gameboy/system/system.hpp @@ -5,6 +5,7 @@ public: void init(Interface*); void power(); void reset(); + void run(); //private: Interface *interface; diff --git a/ui/main.cpp b/ui/main.cpp index 04a697c8..8aace5a4 100755 --- a/ui/main.cpp +++ b/ui/main.cpp @@ -16,6 +16,10 @@ void Application::main(int argc, char **argv) { while(quit == false) { OS::run(); + + if(GameBoy::cartridge.loaded()) { + GameBoy::system.run(); + } } } From 1c3c7fe0a7b2dacc7db0c7bfd1d950c781a2e9e5 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 29 Dec 2010 22:03:42 +1100 Subject: [PATCH 04/12] Update to release v000r02. byuu says: 314 of 512 opcodes implemented, can execute the first 67,450 instructions of Tetris. I also added an MMIO bus, ala bsnes, so that I can map and access individual registers with a single indirection. --- gameboy/Makefile | 5 +- gameboy/cartridge/cartridge.cpp | 34 +- gameboy/cartridge/cartridge.hpp | 16 +- gameboy/cartridge/mmio/mmio.cpp | 10 + gameboy/cartridge/mmio/mmio.hpp | 2 + gameboy/cpu/core/core.cpp | 518 +++++++++++++++++++++++++++++- gameboy/cpu/core/core.hpp | 42 ++- gameboy/cpu/core/disassembler.cpp | 338 ++++++++++++++++++- gameboy/cpu/core/registers.hpp | 1 + gameboy/cpu/cpu.cpp | 7 +- gameboy/cpu/cpu.hpp | 8 +- gameboy/cpu/mmio/mmio.cpp | 15 + gameboy/cpu/mmio/mmio.hpp | 2 + gameboy/cpu/timing/timing.cpp | 6 +- gameboy/gameboy.hpp | 5 +- gameboy/lcd/lcd.cpp | 21 ++ gameboy/lcd/lcd.hpp | 15 + gameboy/lcd/mmio/mmio.cpp | 26 ++ gameboy/lcd/mmio/mmio.hpp | 2 + gameboy/memory/memory.cpp | 98 +----- gameboy/memory/memory.hpp | 22 +- gameboy/system/system.cpp | 6 + 22 files changed, 1072 insertions(+), 127 deletions(-) create mode 100755 gameboy/cartridge/mmio/mmio.cpp create mode 100755 gameboy/cartridge/mmio/mmio.hpp create mode 100755 gameboy/cpu/mmio/mmio.cpp create mode 100755 gameboy/cpu/mmio/mmio.hpp create mode 100755 gameboy/lcd/lcd.cpp create mode 100755 gameboy/lcd/lcd.hpp create mode 100755 gameboy/lcd/mmio/mmio.cpp create mode 100755 gameboy/lcd/mmio/mmio.hpp diff --git a/gameboy/Makefile b/gameboy/Makefile index ecb87ae3..b50d3ad4 100755 --- a/gameboy/Makefile +++ b/gameboy/Makefile @@ -1,7 +1,7 @@ gameboy_objects := libco gameboy_objects += gameboy-system gameboy-scheduler -gameboy_objects += gameboy-cartridge gameboy-memory -gameboy_objects += gameboy-cpu +gameboy_objects += gameboy-memory gameboy-cartridge +gameboy_objects += gameboy-cpu gameboy-lcd objects += $(gameboy_objects) obj/libco.o: libco/libco.c libco/* @@ -11,3 +11,4 @@ obj/gameboy-scheduler.o: $(gameboy)/scheduler/scheduler.cpp $(call rwildcard,$(g obj/gameboy-cartridge.o: $(gameboy)/cartridge/cartridge.cpp $(call rwildcard,$(gameboy)/cartridge/) obj/gameboy-memory.o: $(gameboy)/memory/memory.cpp $(call rwildcard,$(gameboy)/memory/) obj/gameboy-cpu.o: $(gameboy)/cpu/cpu.cpp $(call rwildcard,$(gameboy)/cpu/) +obj/gameboy-lcd.o: $(gameboy)/lcd/lcd.cpp $(call rwildcard,$(gameboy)/lcd/) diff --git a/gameboy/cartridge/cartridge.cpp b/gameboy/cartridge/cartridge.cpp index b900cbbc..611b35f5 100755 --- a/gameboy/cartridge/cartridge.cpp +++ b/gameboy/cartridge/cartridge.cpp @@ -3,22 +3,24 @@ #define CARTRIDGE_CPP namespace GameBoy { +#include "mmio/mmio.cpp" Cartridge cartridge; void Cartridge::load(uint8_t *data, unsigned size) { - bus.cartrom.copy(data, size); + romdata = new uint8[romsize = size]; + memcpy(romdata, data, size); char name[17]; - memcpy(name, bus.cartrom.data + 0x0134, 16); + memcpy(name, romdata + 0x0134, 16); name[16] = 0; info.name = name; info.name.rtrim(); - info.cgbflag = bus.cartrom[0x0143]; - info.sgbflag = bus.cartrom[0x0146]; - info.type = bus.cartrom[0x0147]; + info.cgbflag = romdata[0x0143]; + info.sgbflag = romdata[0x0146]; + info.type = romdata[0x0147]; - switch(bus.cartrom[0x0148]) { default: + switch(romdata[0x0148]) { default: case 0x00: info.romsize = 2 * 16 * 1024; break; case 0x01: info.romsize = 4 * 16 * 1024; break; case 0x02: info.romsize = 8 * 16 * 1024; break; @@ -33,7 +35,7 @@ void Cartridge::load(uint8_t *data, unsigned size) { } //TODO: MBC2 always stores 0x00 here; yet it has 512x4-bits RAM - switch(bus.cartrom[0x0149]) { default: + switch(romdata[0x0149]) { default: case 0x00: info.ramsize = 0 * 1024; break; case 0x01: info.ramsize = 2 * 1024; break; case 0x02: info.ramsize = 8 * 1024; break; @@ -46,12 +48,28 @@ void Cartridge::load(uint8_t *data, unsigned size) { void Cartridge::unload() { if(loaded == false) return; - bus.cartrom.free(); + if(romdata) { delete[] romdata; romdata = 0; } + if(ramdata) { delete[] ramdata; ramdata = 0; } loaded = false; } +void Cartridge::power() { + for(unsigned n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = this; + + reset(); +} + +void Cartridge::reset() { +} + Cartridge::Cartridge() { loaded = false; + romdata = 0; + ramdata = 0; +} + +Cartridge::~Cartridge() { + unload(); } } diff --git a/gameboy/cartridge/cartridge.hpp b/gameboy/cartridge/cartridge.hpp index a19b3b7d..4868fc56 100755 --- a/gameboy/cartridge/cartridge.hpp +++ b/gameboy/cartridge/cartridge.hpp @@ -1,5 +1,6 @@ -class Cartridge : property { -public: +struct Cartridge : MMIO, property { + #include "mmio/mmio.hpp" + struct Information { string name; uint8 cgbflag; @@ -11,9 +12,20 @@ public: readonly loaded; + uint8_t *romdata; + unsigned romsize; + + uint8_t *ramdata; + unsigned ramsize; + void load(uint8_t *data, unsigned size); void unload(); + + void power(); + void reset(); + Cartridge(); + ~Cartridge(); }; extern Cartridge cartridge; diff --git a/gameboy/cartridge/mmio/mmio.cpp b/gameboy/cartridge/mmio/mmio.cpp new file mode 100755 index 00000000..749b5895 --- /dev/null +++ b/gameboy/cartridge/mmio/mmio.cpp @@ -0,0 +1,10 @@ +#ifdef CARTRIDGE_CPP + +uint8 Cartridge::mmio_read(uint16 addr) { + if(addr >= 0x0000 && addr <= 0x7fff) return romdata[addr]; +} + +void Cartridge::mmio_write(uint16 addr, uint8 data) { +} + +#endif diff --git a/gameboy/cartridge/mmio/mmio.hpp b/gameboy/cartridge/mmio/mmio.hpp new file mode 100755 index 00000000..92b8f614 --- /dev/null +++ b/gameboy/cartridge/mmio/mmio.hpp @@ -0,0 +1,2 @@ +uint8 mmio_read(uint16 addr); +void mmio_write(uint16 addr, uint8 data); diff --git a/gameboy/cpu/core/core.cpp b/gameboy/cpu/core/core.cpp index d81d9f71..296c3391 100755 --- a/gameboy/cpu/core/core.cpp +++ b/gameboy/cpu/core/core.cpp @@ -10,17 +10,57 @@ void CPU::op_unknown() { "CPU: unknown opcode [", hex<2>(opcode), "]\n", "af:", hex<4>(r[AF]), " bc:", hex<4>(r[BC]), " de:", hex<4>(r[DE]), " hl:", hex<4>(r[HL]), " ", "sp:", hex<4>(r[SP]), " pc:", hex<4>(r[PC]), "\n", - "ly:", decimal<3, ' '>(status.lycounter), " exec:", opcode_counter, "\n" + "ly:", decimal<3, ' '>(lcd.status.ly), " exec:", opcode_counter, "\n" ); while(true) scheduler.exit(); } +void CPU::op_cb() { + uint8 opcode = op_read(r[PC]++); + (this->*opcode_table_cb[opcode])(); +} + //8-bit load commands +template void CPU::op_ld_r_r() { + r[x] = r[y]; +} + template void CPU::op_ld_r_n() { r[x] = op_read(r[PC]++); } +template void CPU::op_ld_r_hl() { + r[x] = op_read(r[HL]); +} + +void CPU::op_ld_hl_n() { + op_write(r[HL], op_read(r[PC]++)); +} + +void CPU::op_ld_nn_a() { + uint8 lo = op_read(r[PC]++); + uint8 hi = op_read(r[PC]++); + op_write((hi << 8) | (lo << 0), r[A]); +} + +void CPU::op_ld_a_ffn() { + r[A] = op_read(0xff00 + op_read(r[PC]++)); +} + +void CPU::op_ld_ffn_a() { + op_write(0xff00 + op_read(r[PC]++), r[A]); +} + +void CPU::op_ld_ffc_a() { + op_write(0xff00 + r[C], r[A]); +} + +void CPU::op_ldi_a_hl() { + r[A] = op_read(r[HL]); + r[HL]++; +} + void CPU::op_ldd_hl_a() { op_write(r[HL], r[A]); r[HL]--; @@ -28,13 +68,50 @@ void CPU::op_ldd_hl_a() { //16-bit load commands -template void CPU::op_ld_rr_nn() { - r[y] = op_read(r[PC]++); - r[x] = op_read(r[PC]++); +template void CPU::op_ld_rr_nn() { + r[x] = op_read(r[PC]++) << 0; + r[x] |= op_read(r[PC]++) << 8; +} + +template void CPU::op_push_rr() { + op_write(--r[SP], r[x] >> 8); + op_write(--r[SP], r[x] >> 0); + op_io(); +} + +template void CPU::op_pop_rr() { + r[x] = op_read(r[SP]++) << 0; + r[x] |= op_read(r[SP]++) << 8; } //8-bit arithmetic commands +template void CPU::op_add_a_r() { + uint16 rb = (r[A] + r[x]); + uint16 rn = (r[A] & 0x0f) + (r[x] & 0x0f); + r[A] = rb; + r.f.z = (uint8)rb == 0; + r.f.n = 0; + r.f.h = rn > 0x0f; + r.f.c = rb > 0xff; +} + +template void CPU::op_and_r() { + r[A] &= r[x]; + r.f.z = r[A] == 0; + r.f.n = 0; + r.f.h = 1; + r.f.c = 0; +} + +void CPU::op_and_n() { + r[A] &= op_read(r[PC]++); + r.f.z = r[A] == 0; + r.f.n = 0; + r.f.h = 1; + r.f.c = 0; +} + template void CPU::op_xor_r() { r[A] ^= r[x]; r.f.z = r[A] == 0; @@ -43,11 +120,87 @@ template void CPU::op_xor_r() { r.f.c = 0; } +template void CPU::op_or_r() { + r[A] |= r[x]; + r.f.z = r[A] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = 0; +} + +void CPU::op_cp_n() { + uint8 data = op_read(r[PC]++); + uint16 rb = (r[A] - data); + uint16 rn = (r[A] & 0x0f) - (data & 0x0f); + r.f.z = (uint8)rb == 0; + r.f.n = 1; + r.f.h = rn > 0x0f; + r.f.c = rb > 0xff; +} + +template void CPU::op_inc_r() { + r[x]++; + r.f.z = r[x] == 0; + r.f.n = 0; + r.f.h = (r[x] & 0x0f) == 0x00; +} + template void CPU::op_dec_r() { r[x]--; r.f.z = r[x] == 0; - r.f.n = 0; //??? - r.f.h = 0; //??? + r.f.n = 1; + r.f.h = (r[x] & 0x0f) == 0x0f; +} + +void CPU::op_cpl() { + r[A] ^= 0xff; + r.f.n = 1; + r.f.h = 1; +} + +//16-bit arithmetic commands + +template void CPU::op_add_hl_rr() { + uint32 rb = (r[HL] + r[x]); + uint32 rn = (r[HL] & 0xfff) + (r[x] & 0xfff); + r[HL] = rb; + r.f.n = 0; + r.f.h = rn > 0x0fff; + r.f.c = rb > 0xffff; +} + +template void CPU::op_inc_rr() { + r[x]++; +} + +template void CPU::op_dec_rr() { + r[x]--; +} + +//rotate/shift commands + +template void CPU::op_swap_r() { + r[x] = (r[x] << 4) | (r[x] >> 4); + r.f.z = r[x] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = 0; +} + +//single-bit commands + +template void CPU::op_bit_n_r() { + r.f.z = (r[x] & (1 << b)) == 0; + r.f.n = 0; + r.f.h = 1; +} + +template void CPU::op_set_n_r() { + r[x] |= 1 << b; +} + +template void CPU::op_res_n_r() { + r[x] &= ~(1 << b); } //control commands @@ -55,6 +208,14 @@ template void CPU::op_dec_r() { void CPU::op_nop() { } +void CPU::op_di() { + status.ime = 0; +} + +void CPU::op_ei() { + status.ime = 1; +} + //jump commands void CPU::op_jp_nn() { @@ -64,6 +225,10 @@ void CPU::op_jp_nn() { op_io(); } +void CPU::op_jp_hl() { + r[PC] = r[HL]; +} + template void CPU::op_jr_f_n() { int8 n = op_read(r[PC]++); if(r.f[x] == y) { @@ -72,20 +237,359 @@ template void CPU::op_jr_f_n() { } } +void CPU::op_call_nn() { + uint16 dest = r[PC] + 2; + op_write(--r[SP], dest >> 8); + op_write(--r[SP], dest >> 0); + uint8 lo = op_read(r[PC]++); + uint8 hi = op_read(r[PC]++); + r[PC] = (hi << 8) | (lo << 0); + op_io(); +} + +void CPU::op_ret() { + uint8 lo = op_read(r[SP]++); + uint8 hi = op_read(r[SP]++); + r[PC] = (hi << 8) | (lo << 0); + op_io(); +} + +template void CPU::op_rst_n() { + op_write(--r[SP], r[PC] >> 8); + op_write(--r[SP], r[PC] >> 0); + r[PC] = n; + op_io(); +} + +//opcode tables + void CPU::initialize_opcode_table() { for(unsigned n = 0; n < 256; n++) opcode_table[n] = &CPU::op_unknown; + for(unsigned n = 0; n < 256; n++) opcode_table_cb[n] = &CPU::op_unknown; opcode_table[0x00] = &CPU::op_nop; + opcode_table[0x01] = &CPU::op_ld_rr_nn; + opcode_table[0x03] = &CPU::op_inc_rr; opcode_table[0x05] = &CPU::op_dec_r; opcode_table[0x06] = &CPU::op_ld_r_n; + opcode_table[0x09] = &CPU::op_add_hl_rr; + opcode_table[0x0b] = &CPU::op_dec_rr; + opcode_table[0x0c] = &CPU::op_inc_r; opcode_table[0x0d] = &CPU::op_dec_r; opcode_table[0x0e] = &CPU::op_ld_r_n; + opcode_table[0x11] = &CPU::op_ld_rr_nn; + opcode_table[0x13] = &CPU::op_inc_rr; + opcode_table[0x16] = &CPU::op_ld_r_n; + opcode_table[0x19] = &CPU::op_add_hl_rr; + opcode_table[0x1b] = &CPU::op_dec_rr; + opcode_table[0x1e] = &CPU::op_ld_r_n; opcode_table[0x20] = &CPU::op_jr_f_n; - opcode_table[0x21] = &CPU::op_ld_rr_nn; + opcode_table[0x21] = &CPU::op_ld_rr_nn; + opcode_table[0x23] = &CPU::op_inc_rr; + opcode_table[0x26] = &CPU::op_ld_r_n; + opcode_table[0x29] = &CPU::op_add_hl_rr; + opcode_table[0x2a] = &CPU::op_ldi_a_hl; + opcode_table[0x2b] = &CPU::op_dec_rr; + opcode_table[0x2e] = &CPU::op_ld_r_n; + opcode_table[0x2f] = &CPU::op_cpl; + opcode_table[0x31] = &CPU::op_ld_rr_nn; opcode_table[0x32] = &CPU::op_ldd_hl_a; + opcode_table[0x33] = &CPU::op_inc_rr; + opcode_table[0x36] = &CPU::op_ld_hl_n; + opcode_table[0x39] = &CPU::op_add_hl_rr; + opcode_table[0x3b] = &CPU::op_dec_rr; opcode_table[0x3e] = &CPU::op_ld_r_n; + opcode_table[0x40] = &CPU::op_ld_r_r; + opcode_table[0x41] = &CPU::op_ld_r_r; + opcode_table[0x42] = &CPU::op_ld_r_r; + opcode_table[0x43] = &CPU::op_ld_r_r; + opcode_table[0x44] = &CPU::op_ld_r_r; + opcode_table[0x45] = &CPU::op_ld_r_r; + opcode_table[0x46] = &CPU::op_ld_r_hl; + opcode_table[0x47] = &CPU::op_ld_r_r; + opcode_table[0x48] = &CPU::op_ld_r_r; + opcode_table[0x49] = &CPU::op_ld_r_r; + opcode_table[0x4a] = &CPU::op_ld_r_r; + opcode_table[0x4b] = &CPU::op_ld_r_r; + opcode_table[0x4c] = &CPU::op_ld_r_r; + opcode_table[0x4d] = &CPU::op_ld_r_r; + opcode_table[0x4e] = &CPU::op_ld_r_hl; + opcode_table[0x4f] = &CPU::op_ld_r_r; + opcode_table[0x50] = &CPU::op_ld_r_r; + opcode_table[0x51] = &CPU::op_ld_r_r; + opcode_table[0x52] = &CPU::op_ld_r_r; + opcode_table[0x53] = &CPU::op_ld_r_r; + opcode_table[0x54] = &CPU::op_ld_r_r; + opcode_table[0x55] = &CPU::op_ld_r_r; + opcode_table[0x56] = &CPU::op_ld_r_hl; + opcode_table[0x57] = &CPU::op_ld_r_r; + opcode_table[0x58] = &CPU::op_ld_r_r; + opcode_table[0x59] = &CPU::op_ld_r_r; + opcode_table[0x5a] = &CPU::op_ld_r_r; + opcode_table[0x5b] = &CPU::op_ld_r_r; + opcode_table[0x5c] = &CPU::op_ld_r_r; + opcode_table[0x5d] = &CPU::op_ld_r_r; + opcode_table[0x5e] = &CPU::op_ld_r_hl; + opcode_table[0x5f] = &CPU::op_ld_r_r; + opcode_table[0x60] = &CPU::op_ld_r_r; + opcode_table[0x61] = &CPU::op_ld_r_r; + opcode_table[0x62] = &CPU::op_ld_r_r; + opcode_table[0x63] = &CPU::op_ld_r_r; + opcode_table[0x64] = &CPU::op_ld_r_r; + opcode_table[0x65] = &CPU::op_ld_r_r; + opcode_table[0x66] = &CPU::op_ld_r_hl; + opcode_table[0x67] = &CPU::op_ld_r_r; + opcode_table[0x68] = &CPU::op_ld_r_r; + opcode_table[0x69] = &CPU::op_ld_r_r; + opcode_table[0x6a] = &CPU::op_ld_r_r; + opcode_table[0x6b] = &CPU::op_ld_r_r; + opcode_table[0x6c] = &CPU::op_ld_r_r; + opcode_table[0x6d] = &CPU::op_ld_r_r; + opcode_table[0x6e] = &CPU::op_ld_r_hl; + opcode_table[0x6f] = &CPU::op_ld_r_r; + opcode_table[0x78] = &CPU::op_ld_r_r; + opcode_table[0x79] = &CPU::op_ld_r_r; + opcode_table[0x7a] = &CPU::op_ld_r_r; + opcode_table[0x7b] = &CPU::op_ld_r_r; + opcode_table[0x7c] = &CPU::op_ld_r_r; + opcode_table[0x7d] = &CPU::op_ld_r_r; + opcode_table[0x7e] = &CPU::op_ld_r_hl; + opcode_table[0x7f] = &CPU::op_ld_r_r; + opcode_table[0x80] = &CPU::op_add_a_r; + opcode_table[0x81] = &CPU::op_add_a_r; + opcode_table[0x82] = &CPU::op_add_a_r; + opcode_table[0x83] = &CPU::op_add_a_r; + opcode_table[0x84] = &CPU::op_add_a_r; + opcode_table[0x85] = &CPU::op_add_a_r; + opcode_table[0x87] = &CPU::op_add_a_r; + opcode_table[0xa0] = &CPU::op_and_r; + opcode_table[0xa1] = &CPU::op_and_r; + opcode_table[0xa2] = &CPU::op_and_r; + opcode_table[0xa3] = &CPU::op_and_r; + opcode_table[0xa4] = &CPU::op_and_r; + opcode_table[0xa5] = &CPU::op_and_r; + opcode_table[0xa7] = &CPU::op_and_r; + opcode_table[0xa8] = &CPU::op_xor_r; + opcode_table[0xa9] = &CPU::op_xor_r; + opcode_table[0xaa] = &CPU::op_xor_r; + opcode_table[0xab] = &CPU::op_xor_r; + opcode_table[0xac] = &CPU::op_xor_r; + opcode_table[0xad] = &CPU::op_xor_r; opcode_table[0xaf] = &CPU::op_xor_r; + opcode_table[0xb0] = &CPU::op_or_r; + opcode_table[0xb1] = &CPU::op_or_r; + opcode_table[0xb2] = &CPU::op_or_r; + opcode_table[0xb3] = &CPU::op_or_r; + opcode_table[0xb4] = &CPU::op_or_r; + opcode_table[0xb5] = &CPU::op_or_r; + opcode_table[0xb7] = &CPU::op_or_r; + opcode_table[0xc1] = &CPU::op_pop_rr; opcode_table[0xc3] = &CPU::op_jp_nn; + opcode_table[0xc5] = &CPU::op_push_rr; + opcode_table[0xc7] = &CPU::op_rst_n<0x00>; + opcode_table[0xc9] = &CPU::op_ret; + opcode_table[0xcb] = &CPU::op_cb; + opcode_table[0xcd] = &CPU::op_call_nn; + opcode_table[0xcf] = &CPU::op_rst_n<0x08>; + opcode_table[0xd1] = &CPU::op_pop_rr; + opcode_table[0xd5] = &CPU::op_push_rr; + opcode_table[0xd7] = &CPU::op_rst_n<0x10>; + opcode_table[0xdf] = &CPU::op_rst_n<0x18>; + opcode_table[0xe0] = &CPU::op_ld_ffn_a; + opcode_table[0xe1] = &CPU::op_pop_rr; + opcode_table[0xe2] = &CPU::op_ld_ffc_a; + opcode_table[0xe5] = &CPU::op_push_rr; + opcode_table[0xe6] = &CPU::op_and_n; + opcode_table[0xe7] = &CPU::op_rst_n<0x20>; + opcode_table[0xe9] = &CPU::op_jp_hl; + opcode_table[0xea] = &CPU::op_ld_nn_a; + opcode_table[0xef] = &CPU::op_rst_n<0x28>; + opcode_table[0xf0] = &CPU::op_ld_a_ffn; + opcode_table[0xf1] = &CPU::op_pop_rr; + opcode_table[0xf3] = &CPU::op_di; + opcode_table[0xf5] = &CPU::op_push_rr; + opcode_table[0xf7] = &CPU::op_rst_n<0x30>; + opcode_table[0xfb] = &CPU::op_ei; + opcode_table[0xfe] = &CPU::op_cp_n; + opcode_table[0xff] = &CPU::op_rst_n<0x38>; + + opcode_table_cb[0x37] = &CPU::op_swap_r; + opcode_table_cb[0x40] = &CPU::op_bit_n_r<0, B>; + opcode_table_cb[0x41] = &CPU::op_bit_n_r<0, C>; + opcode_table_cb[0x42] = &CPU::op_bit_n_r<0, D>; + opcode_table_cb[0x43] = &CPU::op_bit_n_r<0, E>; + opcode_table_cb[0x44] = &CPU::op_bit_n_r<0, H>; + opcode_table_cb[0x45] = &CPU::op_bit_n_r<0, L>; + opcode_table_cb[0x47] = &CPU::op_bit_n_r<0, A>; + opcode_table_cb[0x48] = &CPU::op_bit_n_r<1, B>; + opcode_table_cb[0x49] = &CPU::op_bit_n_r<1, C>; + opcode_table_cb[0x4a] = &CPU::op_bit_n_r<1, D>; + opcode_table_cb[0x4b] = &CPU::op_bit_n_r<1, E>; + opcode_table_cb[0x4c] = &CPU::op_bit_n_r<1, H>; + opcode_table_cb[0x4d] = &CPU::op_bit_n_r<1, L>; + opcode_table_cb[0x4f] = &CPU::op_bit_n_r<1, A>; + opcode_table_cb[0x50] = &CPU::op_bit_n_r<2, B>; + opcode_table_cb[0x51] = &CPU::op_bit_n_r<2, C>; + opcode_table_cb[0x52] = &CPU::op_bit_n_r<2, D>; + opcode_table_cb[0x53] = &CPU::op_bit_n_r<2, E>; + opcode_table_cb[0x54] = &CPU::op_bit_n_r<2, H>; + opcode_table_cb[0x55] = &CPU::op_bit_n_r<2, L>; + opcode_table_cb[0x57] = &CPU::op_bit_n_r<2, A>; + opcode_table_cb[0x58] = &CPU::op_bit_n_r<3, B>; + opcode_table_cb[0x59] = &CPU::op_bit_n_r<3, C>; + opcode_table_cb[0x5a] = &CPU::op_bit_n_r<3, D>; + opcode_table_cb[0x5b] = &CPU::op_bit_n_r<3, E>; + opcode_table_cb[0x5c] = &CPU::op_bit_n_r<3, H>; + opcode_table_cb[0x5d] = &CPU::op_bit_n_r<3, L>; + opcode_table_cb[0x5f] = &CPU::op_bit_n_r<3, A>; + opcode_table_cb[0x60] = &CPU::op_bit_n_r<4, B>; + opcode_table_cb[0x61] = &CPU::op_bit_n_r<4, C>; + opcode_table_cb[0x62] = &CPU::op_bit_n_r<4, D>; + opcode_table_cb[0x63] = &CPU::op_bit_n_r<4, E>; + opcode_table_cb[0x64] = &CPU::op_bit_n_r<4, H>; + opcode_table_cb[0x65] = &CPU::op_bit_n_r<4, L>; + opcode_table_cb[0x67] = &CPU::op_bit_n_r<4, A>; + opcode_table_cb[0x68] = &CPU::op_bit_n_r<5, B>; + opcode_table_cb[0x69] = &CPU::op_bit_n_r<5, C>; + opcode_table_cb[0x6a] = &CPU::op_bit_n_r<5, D>; + opcode_table_cb[0x6b] = &CPU::op_bit_n_r<5, E>; + opcode_table_cb[0x6c] = &CPU::op_bit_n_r<5, H>; + opcode_table_cb[0x6d] = &CPU::op_bit_n_r<5, L>; + opcode_table_cb[0x6f] = &CPU::op_bit_n_r<5, A>; + opcode_table_cb[0x70] = &CPU::op_bit_n_r<6, B>; + opcode_table_cb[0x71] = &CPU::op_bit_n_r<6, C>; + opcode_table_cb[0x72] = &CPU::op_bit_n_r<6, D>; + opcode_table_cb[0x73] = &CPU::op_bit_n_r<6, E>; + opcode_table_cb[0x74] = &CPU::op_bit_n_r<6, H>; + opcode_table_cb[0x75] = &CPU::op_bit_n_r<6, L>; + opcode_table_cb[0x77] = &CPU::op_bit_n_r<6, A>; + opcode_table_cb[0x78] = &CPU::op_bit_n_r<7, B>; + opcode_table_cb[0x79] = &CPU::op_bit_n_r<7, C>; + opcode_table_cb[0x7a] = &CPU::op_bit_n_r<7, D>; + opcode_table_cb[0x7b] = &CPU::op_bit_n_r<7, E>; + opcode_table_cb[0x7c] = &CPU::op_bit_n_r<7, H>; + opcode_table_cb[0x7d] = &CPU::op_bit_n_r<7, L>; + opcode_table_cb[0x7f] = &CPU::op_bit_n_r<7, A>; + opcode_table_cb[0x80] = &CPU::op_res_n_r<0, B>; + opcode_table_cb[0x81] = &CPU::op_res_n_r<0, C>; + opcode_table_cb[0x82] = &CPU::op_res_n_r<0, D>; + opcode_table_cb[0x83] = &CPU::op_res_n_r<0, E>; + opcode_table_cb[0x84] = &CPU::op_res_n_r<0, H>; + opcode_table_cb[0x85] = &CPU::op_res_n_r<0, L>; + opcode_table_cb[0x87] = &CPU::op_res_n_r<0, A>; + opcode_table_cb[0x88] = &CPU::op_res_n_r<1, B>; + opcode_table_cb[0x89] = &CPU::op_res_n_r<1, C>; + opcode_table_cb[0x8a] = &CPU::op_res_n_r<1, D>; + opcode_table_cb[0x8b] = &CPU::op_res_n_r<1, E>; + opcode_table_cb[0x8c] = &CPU::op_res_n_r<1, H>; + opcode_table_cb[0x8d] = &CPU::op_res_n_r<1, L>; + opcode_table_cb[0x8f] = &CPU::op_res_n_r<1, A>; + opcode_table_cb[0x90] = &CPU::op_res_n_r<2, B>; + opcode_table_cb[0x91] = &CPU::op_res_n_r<2, C>; + opcode_table_cb[0x92] = &CPU::op_res_n_r<2, D>; + opcode_table_cb[0x93] = &CPU::op_res_n_r<2, E>; + opcode_table_cb[0x94] = &CPU::op_res_n_r<2, H>; + opcode_table_cb[0x95] = &CPU::op_res_n_r<2, L>; + opcode_table_cb[0x97] = &CPU::op_res_n_r<2, A>; + opcode_table_cb[0x98] = &CPU::op_res_n_r<3, B>; + opcode_table_cb[0x99] = &CPU::op_res_n_r<3, C>; + opcode_table_cb[0x9a] = &CPU::op_res_n_r<3, D>; + opcode_table_cb[0x9b] = &CPU::op_res_n_r<3, E>; + opcode_table_cb[0x9c] = &CPU::op_res_n_r<3, H>; + opcode_table_cb[0x9d] = &CPU::op_res_n_r<3, L>; + opcode_table_cb[0x9f] = &CPU::op_res_n_r<3, A>; + opcode_table_cb[0xa0] = &CPU::op_res_n_r<4, B>; + opcode_table_cb[0xa1] = &CPU::op_res_n_r<4, C>; + opcode_table_cb[0xa2] = &CPU::op_res_n_r<4, D>; + opcode_table_cb[0xa3] = &CPU::op_res_n_r<4, E>; + opcode_table_cb[0xa4] = &CPU::op_res_n_r<4, H>; + opcode_table_cb[0xa5] = &CPU::op_res_n_r<4, L>; + opcode_table_cb[0xa7] = &CPU::op_res_n_r<4, A>; + opcode_table_cb[0xa8] = &CPU::op_res_n_r<5, B>; + opcode_table_cb[0xa9] = &CPU::op_res_n_r<5, C>; + opcode_table_cb[0xaa] = &CPU::op_res_n_r<5, D>; + opcode_table_cb[0xab] = &CPU::op_res_n_r<5, E>; + opcode_table_cb[0xac] = &CPU::op_res_n_r<5, H>; + opcode_table_cb[0xad] = &CPU::op_res_n_r<5, L>; + opcode_table_cb[0xaf] = &CPU::op_res_n_r<5, A>; + opcode_table_cb[0xb0] = &CPU::op_res_n_r<6, B>; + opcode_table_cb[0xb1] = &CPU::op_res_n_r<6, C>; + opcode_table_cb[0xb2] = &CPU::op_res_n_r<6, D>; + opcode_table_cb[0xb3] = &CPU::op_res_n_r<6, E>; + opcode_table_cb[0xb4] = &CPU::op_res_n_r<6, H>; + opcode_table_cb[0xb5] = &CPU::op_res_n_r<6, L>; + opcode_table_cb[0xb7] = &CPU::op_res_n_r<6, A>; + opcode_table_cb[0xb8] = &CPU::op_res_n_r<7, B>; + opcode_table_cb[0xb9] = &CPU::op_res_n_r<7, C>; + opcode_table_cb[0xba] = &CPU::op_res_n_r<7, D>; + opcode_table_cb[0xbb] = &CPU::op_res_n_r<7, E>; + opcode_table_cb[0xbc] = &CPU::op_res_n_r<7, H>; + opcode_table_cb[0xbd] = &CPU::op_res_n_r<7, L>; + opcode_table_cb[0xbf] = &CPU::op_res_n_r<7, A>; + opcode_table_cb[0xc0] = &CPU::op_set_n_r<0, B>; + opcode_table_cb[0xc1] = &CPU::op_set_n_r<0, C>; + opcode_table_cb[0xc2] = &CPU::op_set_n_r<0, D>; + opcode_table_cb[0xc3] = &CPU::op_set_n_r<0, E>; + opcode_table_cb[0xc4] = &CPU::op_set_n_r<0, H>; + opcode_table_cb[0xc5] = &CPU::op_set_n_r<0, L>; + opcode_table_cb[0xc7] = &CPU::op_set_n_r<0, A>; + opcode_table_cb[0xc8] = &CPU::op_set_n_r<1, B>; + opcode_table_cb[0xc9] = &CPU::op_set_n_r<1, C>; + opcode_table_cb[0xca] = &CPU::op_set_n_r<1, D>; + opcode_table_cb[0xcb] = &CPU::op_set_n_r<1, E>; + opcode_table_cb[0xcc] = &CPU::op_set_n_r<1, H>; + opcode_table_cb[0xcd] = &CPU::op_set_n_r<1, L>; + opcode_table_cb[0xcf] = &CPU::op_set_n_r<1, A>; + opcode_table_cb[0xd0] = &CPU::op_set_n_r<2, B>; + opcode_table_cb[0xd1] = &CPU::op_set_n_r<2, C>; + opcode_table_cb[0xd2] = &CPU::op_set_n_r<2, D>; + opcode_table_cb[0xd3] = &CPU::op_set_n_r<2, E>; + opcode_table_cb[0xd4] = &CPU::op_set_n_r<2, H>; + opcode_table_cb[0xd5] = &CPU::op_set_n_r<2, L>; + opcode_table_cb[0xd7] = &CPU::op_set_n_r<2, A>; + opcode_table_cb[0xd8] = &CPU::op_set_n_r<3, B>; + opcode_table_cb[0xd9] = &CPU::op_set_n_r<3, C>; + opcode_table_cb[0xda] = &CPU::op_set_n_r<3, D>; + opcode_table_cb[0xdb] = &CPU::op_set_n_r<3, E>; + opcode_table_cb[0xdc] = &CPU::op_set_n_r<3, H>; + opcode_table_cb[0xdd] = &CPU::op_set_n_r<3, L>; + opcode_table_cb[0xdf] = &CPU::op_set_n_r<3, A>; + opcode_table_cb[0xe0] = &CPU::op_set_n_r<4, B>; + opcode_table_cb[0xe1] = &CPU::op_set_n_r<4, C>; + opcode_table_cb[0xe2] = &CPU::op_set_n_r<4, D>; + opcode_table_cb[0xe3] = &CPU::op_set_n_r<4, E>; + opcode_table_cb[0xe4] = &CPU::op_set_n_r<4, H>; + opcode_table_cb[0xe5] = &CPU::op_set_n_r<4, L>; + opcode_table_cb[0xe7] = &CPU::op_set_n_r<4, A>; + opcode_table_cb[0xe8] = &CPU::op_set_n_r<5, B>; + opcode_table_cb[0xe9] = &CPU::op_set_n_r<5, C>; + opcode_table_cb[0xea] = &CPU::op_set_n_r<5, D>; + opcode_table_cb[0xeb] = &CPU::op_set_n_r<5, E>; + opcode_table_cb[0xec] = &CPU::op_set_n_r<5, H>; + opcode_table_cb[0xed] = &CPU::op_set_n_r<5, L>; + opcode_table_cb[0xef] = &CPU::op_set_n_r<5, A>; + opcode_table_cb[0xf0] = &CPU::op_set_n_r<6, B>; + opcode_table_cb[0xf1] = &CPU::op_set_n_r<6, C>; + opcode_table_cb[0xf2] = &CPU::op_set_n_r<6, D>; + opcode_table_cb[0xf3] = &CPU::op_set_n_r<6, E>; + opcode_table_cb[0xf4] = &CPU::op_set_n_r<6, H>; + opcode_table_cb[0xf5] = &CPU::op_set_n_r<6, L>; + opcode_table_cb[0xf7] = &CPU::op_set_n_r<6, A>; + opcode_table_cb[0xf8] = &CPU::op_set_n_r<7, B>; + opcode_table_cb[0xf9] = &CPU::op_set_n_r<7, C>; + opcode_table_cb[0xfa] = &CPU::op_set_n_r<7, D>; + opcode_table_cb[0xfb] = &CPU::op_set_n_r<7, E>; + opcode_table_cb[0xfc] = &CPU::op_set_n_r<7, H>; + opcode_table_cb[0xfd] = &CPU::op_set_n_r<7, L>; + opcode_table_cb[0xff] = &CPU::op_set_n_r<7, A>; + + unsigned missing = 0; + for(unsigned n = 0; n < 256; n++) { + if(opcode_table[n] == &CPU::op_unknown) missing++; + if(opcode_table_cb[n] == &CPU::op_unknown) missing++; + } + + print("CPU opcodes: ", 512 - missing, " implemented, ", missing, " remaining.\n"); } #endif diff --git a/gameboy/cpu/core/core.hpp b/gameboy/cpu/core/core.hpp index 86c9ee04..82a0ed03 100755 --- a/gameboy/cpu/core/core.hpp +++ b/gameboy/cpu/core/core.hpp @@ -1,27 +1,67 @@ #include "registers.hpp" void (CPU::*opcode_table[256])(); +void (CPU::*opcode_table_cb[256])(); void initialize_opcode_table(); void op_unknown(); +void op_cb(); + //8-bit load commands +template void op_ld_r_r(); template void op_ld_r_n(); +template void op_ld_r_hl(); +void op_ld_hl_n(); +void op_ld_nn_a(); +void op_ld_a_ffn(); +void op_ld_ffn_a(); +void op_ld_ffc_a(); +void op_ldi_a_hl(); void op_ldd_hl_a(); //16-bit load commands -template void op_ld_rr_nn(); +template void op_ld_rr_nn(); +template void op_push_rr(); +template void op_pop_rr(); //8-bit arithmetic commands +template void op_add_a_r(); +template void op_and_r(); +void op_and_n(); template void op_xor_r(); +template void op_or_r(); +void op_cp_n(); +template void op_inc_r(); template void op_dec_r(); +void op_cpl(); + +//16-bit arithmetic commands +template void op_add_hl_rr(); +template void op_inc_rr(); +template void op_dec_rr(); + +//rotate/shift commands +template void op_swap_r(); + +//single-bit commands +template void op_bit_n_r(); +template void op_set_n_r(); +template void op_res_n_r(); //control commands void op_nop(); +void op_di(); +void op_ei(); //jump commands void op_jp_nn(); +void op_jp_hl(); template void op_jr_f_n(); +void op_call_nn(); +void op_ret(); +template void op_rst_n(); //disassembler.cpp string disassemble(uint16 pc); string disassemble_opcode(uint16 pc); +string disassemble_opcode_cb(uint16 pc); diff --git a/gameboy/cpu/core/disassembler.cpp b/gameboy/cpu/core/disassembler.cpp index 85e8e551..c8c69fa9 100755 --- a/gameboy/cpu/core/disassembler.cpp +++ b/gameboy/cpu/core/disassembler.cpp @@ -1,7 +1,24 @@ #ifdef CPU_CPP string CPU::disassemble(uint16 pc) { - return { hex<4>(pc), " ", disassemble_opcode(pc) }; + char output[80]; + memset(output, ' ', sizeof output); + output[79] = 0; + + string opcode = disassemble_opcode(pc); + string registers = { + " AF:", hex<4>(r[AF]), + " BC:", hex<4>(r[BC]), + " DE:", hex<4>(r[DE]), + " HL:", hex<4>(r[HL]), + " SP:", hex<4>(r[SP]) + }; + + memcpy(output + 0, hex<4>(pc), 4); + memcpy(output + 6, opcode, opcode.length()); + memcpy(output + 23, registers, registers.length()); + output[63] = 0; + return output; } string CPU::disassemble_opcode(uint16 pc) { @@ -12,19 +29,334 @@ string CPU::disassemble_opcode(uint16 pc) { switch(opcode) { case 0x00: return { "nop" }; + case 0x01: return { "ld bc,$", hex<2>(p1), hex<2>(p0) }; + case 0x03: return { "inc bc" }; case 0x05: return { "dec b" }; case 0x06: return { "ld b,$", hex<2>(p0) }; + case 0x09: return { "add hl,bc" }; + case 0x0b: return { "dec bc" }; + case 0x0c: return { "inc c" }; case 0x0d: return { "dec c" }; case 0x0e: return { "ld c,$", hex<2>(p0) }; - case 0x20: return { "jp nz,$", hex<2>(p0) }; + case 0x11: return { "ld de,$", hex<2>(p1), hex<2>(p0) }; + case 0x13: return { "inc de" }; + case 0x16: return { "ld d,$", hex<2>(p0) }; + case 0x19: return { "add hl,de" }; + case 0x1b: return { "dec de" }; + case 0x1e: return { "ld e,$", hex<2>(p0) }; + case 0x20: return { "jp nz,$", hex<4>(r[PC] + 2 + (int8)p0) }; case 0x21: return { "ld hl,$", hex<2>(p1), hex<2>(p0) }; + case 0x23: return { "inc hl" }; + case 0x26: return { "ld h,$", hex<2>(p0) }; + case 0x29: return { "add hl,hl" }; + case 0x2a: return { "ldi a,(hl)" }; + case 0x2b: return { "dec hl" }; + case 0x2e: return { "ld l,$", hex<2>(p0) }; + case 0x2f: return { "cpl" }; + case 0x31: return { "ld sp,$", hex<2>(p1), hex<2>(p0) }; case 0x32: return { "ldd (hl),a" }; + case 0x33: return { "inc sp" }; + case 0x36: return { "ld (hl),$", hex<2>(p0) }; + case 0x39: return { "add hl,sp" }; + case 0x3b: return { "dec sp" }; case 0x3e: return { "ld a,$", hex<2>(p0) }; + case 0x40: return { "ld b,b" }; + case 0x41: return { "ld b,c" }; + case 0x42: return { "ld b,d" }; + case 0x43: return { "ld b,e" }; + case 0x44: return { "ld b,h" }; + case 0x45: return { "ld b,l" }; + case 0x46: return { "ld b,(hl)" }; + case 0x47: return { "ld b,a" }; + case 0x48: return { "ld c,b" }; + case 0x49: return { "ld c,c" }; + case 0x4a: return { "ld c,d" }; + case 0x4b: return { "ld c,e" }; + case 0x4c: return { "ld c,h" }; + case 0x4d: return { "ld c,l" }; + case 0x4e: return { "ld c,(hl)" }; + case 0x4f: return { "ld c,a" }; + case 0x50: return { "ld d,b" }; + case 0x51: return { "ld d,c" }; + case 0x52: return { "ld d,d" }; + case 0x53: return { "ld d,e" }; + case 0x54: return { "ld d,h" }; + case 0x55: return { "ld d,l" }; + case 0x56: return { "ld d,(hl)" }; + case 0x57: return { "ld d,a" }; + case 0x58: return { "ld e,b" }; + case 0x59: return { "ld e,c" }; + case 0x5a: return { "ld e,d" }; + case 0x5b: return { "ld e,e" }; + case 0x5c: return { "ld e,h" }; + case 0x5d: return { "ld e,l" }; + case 0x5e: return { "ld e,(hl)" }; + case 0x5f: return { "ld e,a" }; + case 0x60: return { "ld h,b" }; + case 0x61: return { "ld h,c" }; + case 0x62: return { "ld h,d" }; + case 0x63: return { "ld h,e" }; + case 0x64: return { "ld h,h" }; + case 0x65: return { "ld h,l" }; + case 0x66: return { "ld h,(hl)" }; + case 0x67: return { "ld h,a" }; + case 0x68: return { "ld l,b" }; + case 0x69: return { "ld l,c" }; + case 0x6a: return { "ld l,d" }; + case 0x6b: return { "ld l,e" }; + case 0x6c: return { "ld l,h" }; + case 0x6d: return { "ld l,l" }; + case 0x6e: return { "ld l,(hl)" }; + case 0x6f: return { "ld l,a" }; + case 0x78: return { "ld a,b" }; + case 0x79: return { "ld a,c" }; + case 0x7a: return { "ld a,d" }; + case 0x7b: return { "ld a,e" }; + case 0x7c: return { "ld a,h" }; + case 0x7d: return { "ld a,l" }; + case 0x7e: return { "ld a,(hl)" }; + case 0x7f: return { "ld a,a" }; + case 0x80: return { "add a,b" }; + case 0x81: return { "add a,c" }; + case 0x82: return { "add a,d" }; + case 0x83: return { "add a,e" }; + case 0x84: return { "add a,h" }; + case 0x85: return { "add a,l" }; + case 0x87: return { "add a,a" }; + case 0xa0: return { "and b" }; + case 0xa1: return { "and c" }; + case 0xa2: return { "and d" }; + case 0xa3: return { "and e" }; + case 0xa4: return { "and h" }; + case 0xa5: return { "and l" }; + case 0xa7: return { "and a" }; + case 0xa8: return { "xor b" }; + case 0xa9: return { "xor c" }; + case 0xaa: return { "xor d" }; + case 0xab: return { "xor e" }; + case 0xac: return { "xor h" }; + case 0xad: return { "xor l" }; case 0xaf: return { "xor a" }; + case 0xb0: return { "or b" }; + case 0xb1: return { "or c" }; + case 0xb2: return { "or d" }; + case 0xb3: return { "or e" }; + case 0xb4: return { "or h" }; + case 0xb5: return { "or l" }; + case 0xb7: return { "or a" }; + case 0xc1: return { "pop bc" }; case 0xc3: return { "jp $", hex<2>(p1), hex<2>(p0) }; + case 0xc5: return { "push bc" }; + case 0xc7: return { "rst $0000" }; + case 0xc9: return { "ret" }; + case 0xcb: return disassemble_opcode_cb(pc + 1); + case 0xcd: return { "call $", hex<2>(p1), hex<2>(p0) }; + case 0xcf: return { "rst $0008" }; + case 0xd1: return { "pop de" }; + case 0xd5: return { "push de" }; + case 0xd7: return { "rst $0010" }; + case 0xdf: return { "rst $0018" }; + case 0xe0: return { "ld ($ff", hex<2>(p0), "),a" }; + case 0xe1: return { "pop hl" }; + case 0xe2: return { "ld ($ff00+c),a" }; + case 0xe5: return { "push hl" }; + case 0xe6: return { "and $", hex<2>(p0) }; + case 0xe7: return { "rst $0020" }; + case 0xe9: return { "jp hl" }; + case 0xea: return { "ld ($", hex<2>(p1), hex<2>(p0), "),a" }; + case 0xef: return { "rst $0028" }; + case 0xf0: return { "ld a,($ff", hex<2>(p0), ")" }; + case 0xf1: return { "pop af" }; + case 0xf3: return { "di" }; + case 0xf5: return { "push af" }; + case 0xf7: return { "rst $0030" }; + case 0xfb: return { "ei" }; + case 0xfe: return { "cp $", hex<2>(p0) }; + case 0xff: return { "rst $0038" }; } - return { "???? [", hex<2>(opcode), ",", hex<2>(p1), ",", hex<2>(p0), "]" }; + return { "??? [", hex<2>(opcode), ",", hex<2>(p1), ",", hex<2>(p0), "]" }; +} + +string CPU::disassemble_opcode_cb(uint16 pc) { + uint8 opcode = bus.read(pc); + uint8 p0 = bus.read(pc + 1); + uint8 p1 = bus.read(pc + 2); + uint8 p2 = bus.read(pc + 3); + + switch(opcode) { + case 0x37: return { "swap a" }; + case 0x40: return { "bit 0,b" }; + case 0x41: return { "bit 0,c" }; + case 0x42: return { "bit 0,d" }; + case 0x43: return { "bit 0,e" }; + case 0x44: return { "bit 0,h" }; + case 0x45: return { "bit 0,l" }; + case 0x47: return { "bit 0,a" }; + case 0x48: return { "bit 1,b" }; + case 0x49: return { "bit 1,c" }; + case 0x4a: return { "bit 1,d" }; + case 0x4b: return { "bit 1,e" }; + case 0x4c: return { "bit 1,h" }; + case 0x4d: return { "bit 1,l" }; + case 0x4f: return { "bit 1,a" }; + case 0x50: return { "bit 2,b" }; + case 0x51: return { "bit 2,c" }; + case 0x52: return { "bit 2,d" }; + case 0x53: return { "bit 2,e" }; + case 0x54: return { "bit 2,h" }; + case 0x55: return { "bit 2,l" }; + case 0x57: return { "bit 2,a" }; + case 0x58: return { "bit 3,b" }; + case 0x59: return { "bit 3,c" }; + case 0x5a: return { "bit 3,d" }; + case 0x5b: return { "bit 3,e" }; + case 0x5c: return { "bit 3,h" }; + case 0x5d: return { "bit 3,l" }; + case 0x5f: return { "bit 3,a" }; + case 0x60: return { "bit 4,b" }; + case 0x61: return { "bit 4,c" }; + case 0x62: return { "bit 4,d" }; + case 0x63: return { "bit 4,e" }; + case 0x64: return { "bit 4,h" }; + case 0x65: return { "bit 4,l" }; + case 0x67: return { "bit 4,a" }; + case 0x68: return { "bit 5,b" }; + case 0x69: return { "bit 5,c" }; + case 0x6a: return { "bit 5,d" }; + case 0x6b: return { "bit 5,e" }; + case 0x6c: return { "bit 5,h" }; + case 0x6d: return { "bit 5,l" }; + case 0x6f: return { "bit 5,a" }; + case 0x70: return { "bit 6,b" }; + case 0x71: return { "bit 6,c" }; + case 0x72: return { "bit 6,d" }; + case 0x73: return { "bit 6,e" }; + case 0x74: return { "bit 6,h" }; + case 0x75: return { "bit 6,l" }; + case 0x77: return { "bit 6,a" }; + case 0x78: return { "bit 7,b" }; + case 0x79: return { "bit 7,c" }; + case 0x7a: return { "bit 7,d" }; + case 0x7b: return { "bit 7,e" }; + case 0x7c: return { "bit 7,h" }; + case 0x7d: return { "bit 7,l" }; + case 0x7f: return { "bit 7,a" }; + case 0x80: return { "res 0,b" }; + case 0x81: return { "res 0,c" }; + case 0x82: return { "res 0,d" }; + case 0x83: return { "res 0,e" }; + case 0x84: return { "res 0,h" }; + case 0x85: return { "res 0,l" }; + case 0x87: return { "res 0,a" }; + case 0x88: return { "res 1,b" }; + case 0x89: return { "res 1,c" }; + case 0x8a: return { "res 1,d" }; + case 0x8b: return { "res 1,e" }; + case 0x8c: return { "res 1,h" }; + case 0x8d: return { "res 1,l" }; + case 0x8f: return { "res 1,a" }; + case 0x90: return { "res 2,b" }; + case 0x91: return { "res 2,c" }; + case 0x92: return { "res 2,d" }; + case 0x93: return { "res 2,e" }; + case 0x94: return { "res 2,h" }; + case 0x95: return { "res 2,l" }; + case 0x97: return { "res 2,a" }; + case 0x98: return { "res 3,b" }; + case 0x99: return { "res 3,c" }; + case 0x9a: return { "res 3,d" }; + case 0x9b: return { "res 3,e" }; + case 0x9c: return { "res 3,h" }; + case 0x9d: return { "res 3,l" }; + case 0x9f: return { "res 3,a" }; + case 0xa0: return { "res 4,b" }; + case 0xa1: return { "res 4,c" }; + case 0xa2: return { "res 4,d" }; + case 0xa3: return { "res 4,e" }; + case 0xa4: return { "res 4,h" }; + case 0xa5: return { "res 4,l" }; + case 0xa7: return { "res 4,a" }; + case 0xa8: return { "res 5,b" }; + case 0xa9: return { "res 5,c" }; + case 0xaa: return { "res 5,d" }; + case 0xab: return { "res 5,e" }; + case 0xac: return { "res 5,h" }; + case 0xad: return { "res 5,l" }; + case 0xaf: return { "res 5,a" }; + case 0xb0: return { "res 6,b" }; + case 0xb1: return { "res 6,c" }; + case 0xb2: return { "res 6,d" }; + case 0xb3: return { "res 6,e" }; + case 0xb4: return { "res 6,h" }; + case 0xb5: return { "res 6,l" }; + case 0xb7: return { "res 6,a" }; + case 0xb8: return { "res 7,b" }; + case 0xb9: return { "res 7,c" }; + case 0xba: return { "res 7,d" }; + case 0xbb: return { "res 7,e" }; + case 0xbc: return { "res 7,h" }; + case 0xbd: return { "res 7,l" }; + case 0xbf: return { "res 7,a" }; + case 0xc0: return { "set 0,b" }; + case 0xc1: return { "set 0,c" }; + case 0xc2: return { "set 0,d" }; + case 0xc3: return { "set 0,e" }; + case 0xc4: return { "set 0,h" }; + case 0xc5: return { "set 0,l" }; + case 0xc7: return { "set 0,a" }; + case 0xc8: return { "set 1,b" }; + case 0xc9: return { "set 1,c" }; + case 0xca: return { "set 1,d" }; + case 0xcb: return { "set 1,e" }; + case 0xcc: return { "set 1,h" }; + case 0xcd: return { "set 1,l" }; + case 0xcf: return { "set 1,a" }; + case 0xd0: return { "set 2,b" }; + case 0xd1: return { "set 2,c" }; + case 0xd2: return { "set 2,d" }; + case 0xd3: return { "set 2,e" }; + case 0xd4: return { "set 2,h" }; + case 0xd5: return { "set 2,l" }; + case 0xd7: return { "set 2,a" }; + case 0xd8: return { "set 3,b" }; + case 0xd9: return { "set 3,c" }; + case 0xda: return { "set 3,d" }; + case 0xdb: return { "set 3,e" }; + case 0xdc: return { "set 3,h" }; + case 0xdd: return { "set 3,l" }; + case 0xdf: return { "set 3,a" }; + case 0xe0: return { "set 4,b" }; + case 0xe1: return { "set 4,c" }; + case 0xe2: return { "set 4,d" }; + case 0xe3: return { "set 4,e" }; + case 0xe4: return { "set 4,h" }; + case 0xe5: return { "set 4,l" }; + case 0xe7: return { "set 4,a" }; + case 0xe8: return { "set 5,b" }; + case 0xe9: return { "set 5,c" }; + case 0xea: return { "set 5,d" }; + case 0xeb: return { "set 5,e" }; + case 0xec: return { "set 5,h" }; + case 0xed: return { "set 5,l" }; + case 0xef: return { "set 5,a" }; + case 0xf0: return { "set 6,b" }; + case 0xf1: return { "set 6,c" }; + case 0xf2: return { "set 6,d" }; + case 0xf3: return { "set 6,e" }; + case 0xf4: return { "set 6,h" }; + case 0xf5: return { "set 6,l" }; + case 0xf7: return { "set 6,a" }; + case 0xf8: return { "set 7,b" }; + case 0xf9: return { "set 7,c" }; + case 0xfa: return { "set 7,d" }; + case 0xfb: return { "set 7,e" }; + case 0xfc: return { "set 7,h" }; + case 0xfd: return { "set 7,l" }; + case 0xff: return { "set 7,a" }; + } + + return { "cb? [", hex<2>(opcode), ",", hex<2>(p0), ",", hex<2>(p1), "]" }; } #endif diff --git a/gameboy/cpu/core/registers.hpp b/gameboy/cpu/core/registers.hpp index db4cb602..fbbcbd80 100755 --- a/gameboy/cpu/core/registers.hpp +++ b/gameboy/cpu/core/registers.hpp @@ -18,6 +18,7 @@ enum { struct Register { virtual operator unsigned() const = 0; virtual unsigned operator=(unsigned x) = 0; + Register& operator=(const Register &x) { operator=((unsigned)x); return *this; } unsigned operator++(int) { unsigned r = *this; operator=(*this + 1); return r; } unsigned operator--(int) { unsigned r = *this; operator=(*this - 1); return r; } diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp index b0646f32..f0c09f6e 100755 --- a/gameboy/cpu/cpu.cpp +++ b/gameboy/cpu/cpu.cpp @@ -4,6 +4,7 @@ namespace GameBoy { #include "core/core.cpp" +#include "mmio/mmio.cpp" #include "timing/timing.cpp" CPU cpu; @@ -23,6 +24,10 @@ void CPU::main() { } void CPU::power() { + for(unsigned n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM + for(unsigned n = 0xe000; n <= 0xfdff; n++) bus.mmio[n] = this; //WRAM (mirror) + for(unsigned n = 0xff80; n <= 0xfffe; n++) bus.mmio[n] = this; //HRAM + reset(); } @@ -36,7 +41,7 @@ void CPU::reset() { r[DE] = 0x0000; r[HL] = 0x0000; - status.lycounter = 0; + status.ime = 0; } CPU::CPU() { diff --git a/gameboy/cpu/cpu.hpp b/gameboy/cpu/cpu.hpp index fa28f0c3..d969145a 100755 --- a/gameboy/cpu/cpu.hpp +++ b/gameboy/cpu/cpu.hpp @@ -1,11 +1,15 @@ -struct CPU : Processor { +struct CPU : Processor, MMIO { #include "core/core.hpp" + #include "mmio/mmio.hpp" #include "timing/timing.hpp" struct Status { - unsigned lycounter; + bool ime; } status; + uint8 wram[8192]; + uint8 hram[128]; + static void Main(); void main(); void power(); diff --git a/gameboy/cpu/mmio/mmio.cpp b/gameboy/cpu/mmio/mmio.cpp new file mode 100755 index 00000000..edb7b8f6 --- /dev/null +++ b/gameboy/cpu/mmio/mmio.cpp @@ -0,0 +1,15 @@ +#ifdef CPU_CPP + +uint8 CPU::mmio_read(uint16 addr) { + if(addr >= 0xc000 && addr <= 0xdfff) return wram[addr & 0x1fff]; + if(addr >= 0xe000 && addr <= 0xfdff) return wram[addr & 0x1fff]; + if(addr >= 0xff80 && addr <= 0xfffe) return hram[addr & 0x7f]; +} + +void CPU::mmio_write(uint16 addr, uint8 data) { + if(addr >= 0xc000 && addr <= 0xdfff) { wram[addr & 0x1fff] = data; return; } + if(addr >= 0xe000 && addr <= 0xfdff) { wram[addr & 0x1fff] = data; return; } + if(addr >= 0xff80 && addr <= 0xfffe) { hram[addr & 0x7f] = data; return; } +} + +#endif diff --git a/gameboy/cpu/mmio/mmio.hpp b/gameboy/cpu/mmio/mmio.hpp new file mode 100755 index 00000000..92b8f614 --- /dev/null +++ b/gameboy/cpu/mmio/mmio.hpp @@ -0,0 +1,2 @@ +uint8 mmio_read(uint16 addr); +void mmio_write(uint16 addr, uint8 data); diff --git a/gameboy/cpu/timing/timing.cpp b/gameboy/cpu/timing/timing.cpp index becf6feb..7f7e8ef9 100755 --- a/gameboy/cpu/timing/timing.cpp +++ b/gameboy/cpu/timing/timing.cpp @@ -16,12 +16,12 @@ void CPU::add_clocks(unsigned clocks) { void CPU::scanline() { clock -= 456; - status.lycounter++; - if(status.lycounter >= 154) frame(); + lcd.status.ly++; + if(lcd.status.ly >= 154) frame(); } void CPU::frame() { - status.lycounter -= 154; + lcd.status.ly -= 154; scheduler.exit(); } diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp index c9c6948b..c5c229b3 100755 --- a/gameboy/gameboy.hpp +++ b/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000.01"; + static const char Version[] = "000.02"; } } @@ -45,7 +45,8 @@ namespace GameBoy { #include #include - #include #include + #include #include + #include }; diff --git a/gameboy/lcd/lcd.cpp b/gameboy/lcd/lcd.cpp new file mode 100755 index 00000000..fef0a795 --- /dev/null +++ b/gameboy/lcd/lcd.cpp @@ -0,0 +1,21 @@ +#include + +#define LCD_CPP +namespace GameBoy { + +#include "mmio/mmio.cpp" +LCD lcd; + +void LCD::power() { + for(unsigned n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = this; //VRAM + for(unsigned n = 0xff40; n <= 0xff4f; n++) bus.mmio[n] = this; //MMIO + for(unsigned n = 0xfe00; n <= 0xfe9f; n++) bus.mmio[n] = this; //OAM + + reset(); +} + +void LCD::reset() { + status.ly = 0; +} + +} diff --git a/gameboy/lcd/lcd.hpp b/gameboy/lcd/lcd.hpp new file mode 100755 index 00000000..07a2ae16 --- /dev/null +++ b/gameboy/lcd/lcd.hpp @@ -0,0 +1,15 @@ +struct LCD : Processor, MMIO { + #include "mmio/mmio.hpp" + + struct Status { + unsigned ly; + } status; + + uint8 vram[8192]; + uint8 oam[160]; + + void power(); + void reset(); +}; + +extern LCD lcd; diff --git a/gameboy/lcd/mmio/mmio.cpp b/gameboy/lcd/mmio/mmio.cpp new file mode 100755 index 00000000..51aeb634 --- /dev/null +++ b/gameboy/lcd/mmio/mmio.cpp @@ -0,0 +1,26 @@ +#ifdef LCD_CPP + +uint8 LCD::mmio_read(uint16 addr) { + if(addr >= 0xa000 && addr <= 0xbfff) return vram[addr & 0x1fff]; + if(addr >= 0xfe00 && addr <= 0xfe9f) return oam[addr & 0xff]; + + //LY + if(addr == 0xff44) { + return status.ly; + } + + return 0x00; +} + +void LCD::mmio_write(uint16 addr, uint8 data) { + if(addr >= 0xa000 && addr <= 0xbfff) { vram[addr & 0x1fff] = data; return; } + if(addr >= 0xfe00 && addr <= 0xfe9f) { oam[addr & 0xff] = data; return; } + + //LY + if(addr == 0xff44) { + status.ly = 0; + return; + } +} + +#endif diff --git a/gameboy/lcd/mmio/mmio.hpp b/gameboy/lcd/mmio/mmio.hpp new file mode 100755 index 00000000..92b8f614 --- /dev/null +++ b/gameboy/lcd/mmio/mmio.hpp @@ -0,0 +1,2 @@ +uint8 mmio_read(uint16 addr); +void mmio_write(uint16 addr, uint8 data); diff --git a/gameboy/memory/memory.cpp b/gameboy/memory/memory.cpp index 9bc3bc98..00998f19 100755 --- a/gameboy/memory/memory.cpp +++ b/gameboy/memory/memory.cpp @@ -3,6 +3,7 @@ #define MEMORY_CPP namespace GameBoy { +Unmapped unmapped; Bus bus; uint8_t& Memory::operator[](unsigned addr) { @@ -41,100 +42,19 @@ Memory::~Memory() { // uint8 Bus::read(uint16 addr) { - if(/* addr >= 0x0000 && */ addr <= 0x7fff) { //Cartridge ROM - return cartrom[addr]; - } - - if(/* addr >= 0x8000 && */ addr <= 0x9fff) { //VRAM - return vram[addr & 0x1fff]; - } - - if(/* addr >= 0xa000 && */ addr <= 0xbfff) { //Cartridge RAM - if(cartram.size == 0) return 0x00; - return cartram[addr & 0x1fff]; - } - - if(/* addr >= 0xc000 && */ addr <= 0xdfff) { //WRAM - return wram[addr & 0x1fff]; - } - - if(/* addr >= 0xe000 && */ addr <= 0xfdff) { //WRAM (mirror) - return wram[addr & 0x1fff]; - } - - if(/* addr >= 0xfe00 && */ addr <= 0xfe9f) { //OAM - return oam[addr & 0xff]; - } - - if(/* addr >= 0xfea0 && */ addr <= 0xfeff) { //unmapped - return 0x00; - } - - if(/* addr >= 0xff00 && */ addr <= 0xff7f) { //MMIO - return 0x00; - } - - if(/* addr >= 0xff80 && */ addr <= 0xfffe) { //HRAM - return hram[addr & 0x7f]; - } - - //addr == 0xffff (interrupt enable register) - return 0x00; + return mmio[addr]->mmio_read(addr); } void Bus::write(uint16 addr, uint8 data) { - if(/* addr >= 0x0000 && */ addr <= 0x7fff) { //Cartridge ROM - return; - } - - if(/* addr >= 0x8000 && */ addr <= 0x9fff) { //VRAM - vram[addr & 0x1fff] = data; - return; - } - - if(/* addr >= 0xa000 && */ addr <= 0xbfff) { //Cartridge RAM - if(cartram.size == 0) return; - cartram[addr & 0x1fff] = data; - return; - } - - if(/* addr >= 0xc000 && */ addr <= 0xdfff) { //WRAM - wram[addr & 0x1fff] = data; - return; - } - - if(/* addr >= 0xe000 && */ addr <= 0xfdff) { //WRAM (mirror) - wram[addr & 0x1fff] = data; - return; - } - - if(/* addr >= 0xfe00 && */ addr <= 0xfe9f) { //OAM - oam[addr & 0xff] = data; - return; - } - - if(/* addr >= 0xfea0 && */ addr <= 0xfeff) { //unmapped - return; - } - - if(/* addr >= 0xff00 && */ addr <= 0xff7f) { //MMIO - return; - } - - if(/* addr >= 0xff80 && */ addr <= 0xfffe) { //HRAM - hram[addr & 0x7f] = data; - return; - } - - //addr == 0xffff (interrupt enable register) - return; + mmio[addr]->mmio_write(addr, data); } -Bus::Bus() { - vram.allocate(8192); - wram.allocate(8192); - oam.allocate ( 160); - hram.allocate( 128); +void Bus::power() { + for(unsigned n = 0; n < 65536; n++) mmio[n] = &unmapped; + reset(); +} + +void Bus::reset() { } } diff --git a/gameboy/memory/memory.hpp b/gameboy/memory/memory.hpp index 4c94bb8d..0b9ca99c 100755 --- a/gameboy/memory/memory.hpp +++ b/gameboy/memory/memory.hpp @@ -10,19 +10,27 @@ struct Memory { ~Memory(); }; -class Bus { -public: +struct MMIO { + virtual uint8 mmio_read(uint16 addr) = 0; + virtual void mmio_write(uint16 addr, uint8 data) = 0; +}; + +struct Unmapped : MMIO { + uint8 mmio_read(uint16) { return 0x00; } + void mmio_write(uint16, uint8) {} +}; + +struct Bus { Memory cartrom; Memory cartram; - Memory vram; - Memory wram; - Memory oam; - Memory hram; + MMIO *mmio[65536]; uint8 read(uint16 addr); void write(uint16 addr, uint8 data); - Bus(); + void power(); + void reset(); }; +extern Unmapped unmapped; extern Bus bus; diff --git a/gameboy/system/system.cpp b/gameboy/system/system.cpp index 2a8b0aa1..1fb68c9a 100755 --- a/gameboy/system/system.cpp +++ b/gameboy/system/system.cpp @@ -10,12 +10,18 @@ void System::init(Interface *interface_) { } void System::power() { + bus.power(); + cartridge.power(); cpu.power(); + lcd.power(); scheduler.init(); } void System::reset() { + bus.reset(); + cartridge.power(); cpu.reset(); + lcd.reset(); scheduler.init(); } From 71780949b07885f165689f923c8a40ad877292a5 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 30 Dec 2010 18:15:10 +1100 Subject: [PATCH 05/12] Update to release v000r03. byuu says: All 512 instructions implemented. To make debugging flags easier and to reduce the code size, I made parent routines for all the bit-logic that sets flags. This bumped up the speed to 3,045fps. So about 51x faster than a real Game Boy. I suspect the frame rate to plummet rapidly as I emulate more stuff and try and get timings more accurate. Wild ballpark, I'd guess 300fps or so. Not based on anything, just a made up number. I have no idea what it will end up being. So I still need: * decode MMIO reads/writes * add CPU interrupt support * add LCD emulation (probably the hardest part) * add PCM emulation (well, this would likely be the hardest; but it's not essential right now) * debug the holy living shit out of the CPU core. Anyone want to help now by looking it over? :D --- gameboy/cpu/core/core.cpp | 840 ++++++++++++++++-------------- gameboy/cpu/core/core.hpp | 91 +++- gameboy/cpu/core/disassembler.cpp | 250 ++++++++- gameboy/cpu/core/table.cpp | 519 ++++++++++++++++++ gameboy/cpu/cpu.cpp | 5 +- gameboy/gameboy.hpp | 2 +- ui/main.cpp | 11 + 7 files changed, 1293 insertions(+), 425 deletions(-) create mode 100755 gameboy/cpu/core/table.cpp diff --git a/gameboy/cpu/core/core.cpp b/gameboy/cpu/core/core.cpp index 296c3391..b1eb86cb 100755 --- a/gameboy/cpu/core/core.cpp +++ b/gameboy/cpu/core/core.cpp @@ -1,18 +1,9 @@ #ifdef CPU_CPP +#include "table.cpp" #include "disassembler.cpp" -unsigned opcode_counter = 0; - -void CPU::op_unknown() { - uint8 opcode = bus.read(--r[PC]); - print( - "CPU: unknown opcode [", hex<2>(opcode), "]\n", - "af:", hex<4>(r[AF]), " bc:", hex<4>(r[BC]), " de:", hex<4>(r[DE]), " hl:", hex<4>(r[HL]), " ", - "sp:", hex<4>(r[SP]), " pc:", hex<4>(r[PC]), "\n", - "ly:", decimal<3, ' '>(lcd.status.ly), " exec:", opcode_counter, "\n" - ); - while(true) scheduler.exit(); +void CPU::op_xx() { } void CPU::op_cb() { @@ -34,10 +25,28 @@ template void CPU::op_ld_r_hl() { r[x] = op_read(r[HL]); } +template void CPU::op_ld_hl_r() { + op_write(r[HL], r[x]); +} + void CPU::op_ld_hl_n() { op_write(r[HL], op_read(r[PC]++)); } +template void CPU::op_ld_a_rr() { + r[A] = op_read(r[x]); +} + +void CPU::op_ld_a_nn() { + uint8 lo = op_read(r[PC]++); + uint8 hi = op_read(r[PC]++); + r[A] = op_read((hi << 8) | (lo << 0)); +} + +template void CPU::op_ld_rr_a() { + op_write(r[x], r[A]); +} + void CPU::op_ld_nn_a() { uint8 lo = op_read(r[PC]++); uint8 hi = op_read(r[PC]++); @@ -56,6 +65,11 @@ void CPU::op_ld_ffc_a() { op_write(0xff00 + r[C], r[A]); } +void CPU::op_ldi_hl_a() { + op_write(r[HL], r[A]); + r[HL]++; +} + void CPU::op_ldi_a_hl() { r[A] = op_read(r[HL]); r[HL]++; @@ -66,6 +80,11 @@ void CPU::op_ldd_hl_a() { r[HL]--; } +void CPU::op_ldd_a_hl() { + r[A] = op_read(r[HL]); + r[HL]--; +} + //16-bit load commands template void CPU::op_ld_rr_nn() { @@ -73,6 +92,17 @@ template void CPU::op_ld_rr_nn() { r[x] |= op_read(r[PC]++) << 8; } +void CPU::op_ld_nn_sp() { + uint16 addr = op_read(r[PC]++) << 0; + addr |= op_read(r[PC]++) << 8; + op_write(addr + 0, r[SP] >> 0); + op_write(addr + 1, r[SP] >> 8); +} + +void CPU::op_ld_sp_hl() { + r[SP] = r[HL]; +} + template void CPU::op_push_rr() { op_write(--r[SP], r[x] >> 8); op_write(--r[SP], r[x] >> 0); @@ -86,58 +116,111 @@ template void CPU::op_pop_rr() { //8-bit arithmetic commands -template void CPU::op_add_a_r() { - uint16 rb = (r[A] + r[x]); - uint16 rn = (r[A] & 0x0f) + (r[x] & 0x0f); - r[A] = rb; - r.f.z = (uint8)rb == 0; +void CPU::opi_add_a(uint8 x) { + uint16 rh = r[A] + x; + uint16 rl = (r[A] & 0x0f) + (x & 0x0f); + r[A] = rh; + r.f.z = (uint8)rh == 0; r.f.n = 0; - r.f.h = rn > 0x0f; - r.f.c = rb > 0xff; + r.f.h = rl > 0x0f; + r.f.c = rh > 0xff; } -template void CPU::op_and_r() { - r[A] &= r[x]; - r.f.z = r[A] == 0; +template void CPU::op_add_a_r() { opi_add_a(r[x]); } +void CPU::op_add_a_n() { opi_add_a(op_read(r[PC]++)); } +void CPU::op_add_a_hl() { opi_add_a(op_read(r[HL])); } + +void CPU::opi_adc_a(uint8 x) { + uint16 rh = r[A] + x + r.f.c; + uint16 rl = (r[A] & 0x0f) + (x & 0x0f) + r.f.c; + r[A] = rh; + r.f.z = (uint8)rh == 0; r.f.n = 0; - r.f.h = 1; - r.f.c = 0; + r.f.h = rl > 0x0f; + r.f.c = rh > 0x0f; } -void CPU::op_and_n() { - r[A] &= op_read(r[PC]++); - r.f.z = r[A] == 0; - r.f.n = 0; - r.f.h = 1; - r.f.c = 0; -} +template void CPU::op_adc_a_r() { opi_adc_a(r[x]); } +void CPU::op_adc_a_n() { opi_adc_a(op_read(r[PC]++)); } +void CPU::op_adc_a_hl() { opi_adc_a(op_read(r[HL])); } -template void CPU::op_xor_r() { - r[A] ^= r[x]; - r.f.z = r[A] == 0; - r.f.n = 0; - r.f.h = 0; - r.f.c = 0; -} - -template void CPU::op_or_r() { - r[A] |= r[x]; - r.f.z = r[A] == 0; - r.f.n = 0; - r.f.h = 0; - r.f.c = 0; -} - -void CPU::op_cp_n() { - uint8 data = op_read(r[PC]++); - uint16 rb = (r[A] - data); - uint16 rn = (r[A] & 0x0f) - (data & 0x0f); - r.f.z = (uint8)rb == 0; +void CPU::opi_sub_a(uint8 x) { + uint16 rh = r[A] - x; + uint16 rl = (r[A] & 0x0f) - (x & 0x0f); + r[A] = rh; + r.f.z = (uint8)rh == 0; r.f.n = 1; - r.f.h = rn > 0x0f; - r.f.c = rb > 0xff; + r.f.h = rl > 0x0f; + r.f.c = rh > 0xff; } +template void CPU::op_sub_a_r() { opi_sub_a(r[x]); } +void CPU::op_sub_a_n() { opi_sub_a(op_read(r[PC]++)); } +void CPU::op_sub_a_hl() { opi_sub_a(op_read(r[HL])); } + +void CPU::opi_sbc_a(uint8 x) { + uint16 rh = r[A] - x - r.f.c; + uint16 rl = (r[A] & 0x0f) - (x & 0x0f) - r.f.c; + r[A] = rh; + r.f.z = (uint8)rh == 0; + r.f.n = 1; + r.f.h = rl > 0x0f; + r.f.c = rh > 0xff; +} + +template void CPU::op_sbc_a_r() { opi_sbc_a(r[x]); } +void CPU::op_sbc_a_n() { opi_sbc_a(op_read(r[PC]++)); } +void CPU::op_sbc_a_hl() { opi_sbc_a(op_read(r[HL])); } + +void CPU::opi_and_a(uint8 x) { + r[A] &= x; + r.f.z = r[A] == 0; + r.f.n = 0; + r.f.h = 1; + r.f.c = 0; +} + +template void CPU::op_and_a_r() { opi_and_a(r[x]); } +void CPU::op_and_a_n() { opi_and_a(op_read(r[PC]++)); } +void CPU::op_and_a_hl() { opi_and_a(op_read(r[HL])); } + +void CPU::opi_xor_a(uint8 x) { + r[A] ^= x; + r.f.z = r[A] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = 0; +} + +template void CPU::op_xor_a_r() { opi_xor_a(r[x]); } +void CPU::op_xor_a_n() { opi_xor_a(op_read(r[PC]++)); } +void CPU::op_xor_a_hl() { opi_xor_a(op_read(r[HL])); } + +void CPU::opi_or_a(uint8 x) { + r[A] |= x; + r.f.z = r[A] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = 0; +} + +template void CPU::op_or_a_r() { opi_or_a(r[x]); } +void CPU::op_or_a_n() { opi_or_a(op_read(r[PC]++)); } +void CPU::op_or_a_hl() { opi_or_a(op_read(r[HL])); } + +void CPU::opi_cp_a(uint8 x) { + uint16 rh = r[A] - x; + uint16 rl = (r[A] & 0x0f) - (x & 0x0f); + r.f.z = (uint8)rh == 0; + r.f.n = 1; + r.f.h = rl > 0x0f; + r.f.c = rh > 0x0f; +} + +template void CPU::op_cp_a_r() { opi_cp_a(r[x]); } +void CPU::op_cp_a_n() { opi_cp_a(op_read(r[PC]++)); } +void CPU::op_cp_a_hl() { opi_cp_a(op_read(r[HL])); } + template void CPU::op_inc_r() { r[x]++; r.f.z = r[x] == 0; @@ -145,6 +228,14 @@ template void CPU::op_inc_r() { r.f.h = (r[x] & 0x0f) == 0x00; } +void CPU::op_inc_hl() { + uint8 n = op_read(r[HL]); + op_write(r[HL], ++n); + r.f.z = n == 0; + r.f.n = 0; + r.f.h = (n & 0x0f) == 0x00; +} + template void CPU::op_dec_r() { r[x]--; r.f.z = r[x] == 0; @@ -152,6 +243,32 @@ template void CPU::op_dec_r() { r.f.h = (r[x] & 0x0f) == 0x0f; } +void CPU::op_dec_hl() { + uint8 n = op_read(r[HL]); + op_write(r[HL], --n); + r.f.z = n == 0; + r.f.n = 1; + r.f.h = (n & 0x0f) == 0x0f; +} + +void CPU::op_daa() { + signed a = r[A]; + if(r.f.n == 0) { + if(r.f.h || (a & 0x0f) > 0x09) a += 0x06; + if(r.f.c || (a & 0xff) > 0x9f) a += 0x60; + } else { + if(r.f.h) { + a -= 0x06; + if(r.f.c == 0) a &= 0xff; + } + if(r.f.c) a -= 0x60; + } + r[A] = a; + r.f.z = r[A] == 0; + r.f.h = 0; + r.f.c = a & 0x100; +} + void CPU::op_cpl() { r[A] ^= 0xff; r.f.n = 1; @@ -177,8 +294,156 @@ template void CPU::op_dec_rr() { r[x]--; } +void CPU::op_add_sp_n() { + signed n = (int8)op_read(r[PC]++); + r.f.z = 0; + r.f.n = 0; + r.f.h = ((r[SP] & 0x0f) + (n & 0x0f)) > 0x0f; + r.f.c = ((r[SP] & 0xff) + (n & 0xff)) > 0xff; + r[SP] += n; +} + +void CPU::op_ld_hl_sp_n() { + signed n = (int8)op_read(r[PC]++); + r.f.z = 0; + r.f.n = 0; + r.f.h = ((r[SP] & 0x0f) + (n & 0x0f)) > 0x0f; + r.f.c = ((r[SP] & 0xff) + (n & 0xff)) > 0xff; + r[HL] = r[SP] + n; +} + //rotate/shift commands +void CPU::op_rlca() { + r[A] = (r[A] << 1) | (r[A] >> 7); + r.f.z = 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = r[A] & 0x01; +} + +void CPU::op_rla() { + bool c = r[A] & 0x80; + r[A] = (r[A] << 1) | (c << 0); + r.f.z = 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + +void CPU::op_rrca() { + r[A] = (r[A] >> 1) | (r[A] << 7); + r.f.z = 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = r[A] & 0x80; +} + +void CPU::op_rra() { + bool c = r[A] & 0x01; + r[A] = (r[A] >> 1) | (c << 7); + r.f.z = 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + +template void CPU::op_rlc_r() { + r[x] = (r[x] << 1) | (r[x] >> 7); + r.f.z = r[x] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = r[x] & 0x01; +} + +void CPU::op_rlc_hl() { + uint8 n = op_read(r[HL]); + n = (n << 1) | (n >> 7); + op_write(r[HL], n); + r.f.z = n == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = n & 0x01; +} + +template void CPU::op_rl_r() { + bool c = r[x] & 0x80; + r[x] = (r[x] << 1) | (r.f.c << 0); + r.f.z = r[x] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + +void CPU::op_rl_hl() { + uint8 n = op_read(r[HL]); + bool c = n & 0x80; + n = (n << 1) | (r.f.c << 0); + op_write(r[HL], n); + r.f.z = n == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + +template void CPU::op_rrc_r() { + r[x] = (r[x] >> 1) | (r[x] << 7); + r.f.z = r[x] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = r[x] & 0x80; +} + +void CPU::op_rrc_hl() { + uint8 n = op_read(r[HL]); + n = (n >> 1) | (n << 7); + op_write(r[HL], n); + r.f.z = n == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = n & 0x80; +} + +template void CPU::op_rr_r() { + bool c = r[x] & 0x01; + r[x] = (r[x] >> 1) | (r.f.c << 7); + r.f.z = r[x] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + +void CPU::op_rr_hl() { + uint8 n = op_read(r[HL]); + bool c = n & 0x01; + n = (n >> 1) | (r.f.c << 7); + op_write(r[HL], n); + r.f.z = n == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + +template void CPU::op_sla_r() { + bool c = r[x] & 0x80; + r[x] <<= 1; + r.f.z = r[x] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + +void CPU::op_sla_hl() { + uint8 n = op_read(r[HL]); + bool c = n & 0x80; + n <<= 1; + op_write(r[HL], n); + r.f.z = n == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + template void CPU::op_swap_r() { r[x] = (r[x] << 4) | (r[x] >> 4); r.f.z = r[x] == 0; @@ -187,6 +452,56 @@ template void CPU::op_swap_r() { r.f.c = 0; } +void CPU::op_swap_hl() { + uint8 n = op_read(r[HL]); + n = (n << 4) | (n >> 4); + op_write(r[HL], n); + r.f.z = n == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = 0; +} + +template void CPU::op_sra_r() { + bool c = r[x] & 0x01; + r[x] = (int8)r[x] >> 1; + r.f.z = r[x] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + +void CPU::op_sra_hl() { + uint8 n = op_read(r[HL]); + bool c = n & 0x01; + n = (int8)n >> 1; + op_write(r[HL], n); + r.f.z = n == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + +template void CPU::op_srl_r() { + bool c = r[x] & 0x01; + r[x] >>= 1; + r.f.z = r[x] == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + +void CPU::op_srl_hl() { + uint8 n = op_read(r[HL]); + bool c = n & 0x01; + n >>= 1; + op_write(r[HL], n); + r.f.z = n == 0; + r.f.n = 0; + r.f.h = 0; + r.f.c = c; +} + //single-bit commands template void CPU::op_bit_n_r() { @@ -195,19 +510,58 @@ template void CPU::op_bit_n_r() { r.f.h = 1; } +template void CPU::op_bit_n_hl() { + uint8 n = op_read(r[HL]); + r.f.z = (n & (1 << b)) == 0; + r.f.n = 0; + r.f.h = 1; +} + template void CPU::op_set_n_r() { r[x] |= 1 << b; } +template void CPU::op_set_n_hl() { + uint8 n = op_read(r[HL]); + n |= 1 << b; + op_write(r[HL], n); +} + template void CPU::op_res_n_r() { r[x] &= ~(1 << b); } +template void CPU::op_res_n_hl() { + uint8 n = op_read(r[HL]); + n &= ~(1 << b); + op_write(r[HL], n); +} + //control commands +void CPU::op_ccf() { + r.f.n = 0; + r.f.h = 0; + r.f.c = !r.f.c; +} + +void CPU::op_scf() { + r.f.n = 0; + r.f.h = 0; + r.f.c = 1; +} + void CPU::op_nop() { } +void CPU::op_halt() { + //TODO +} + +void CPU::op_stop() { + //TODO +} + void CPU::op_di() { status.ime = 0; } @@ -229,6 +583,21 @@ void CPU::op_jp_hl() { r[PC] = r[HL]; } +template void CPU::op_jp_f_nn() { + uint8 lo = op_read(r[PC]++); + uint8 hi = op_read(r[PC]++); + if(r.f[x] == y) { + r[PC] = (hi << 8) | (lo << 0); + op_io(); + } +} + +void CPU::op_jr_n() { + int8 n = op_read(r[PC]++); + r[PC] += n; + op_io(); +} + template void CPU::op_jr_f_n() { int8 n = op_read(r[PC]++); if(r.f[x] == y) { @@ -238,15 +607,25 @@ template void CPU::op_jr_f_n() { } void CPU::op_call_nn() { - uint16 dest = r[PC] + 2; - op_write(--r[SP], dest >> 8); - op_write(--r[SP], dest >> 0); uint8 lo = op_read(r[PC]++); uint8 hi = op_read(r[PC]++); + op_write(--r[SP], r[PC] >> 8); + op_write(--r[SP], r[PC] >> 0); r[PC] = (hi << 8) | (lo << 0); op_io(); } +template void CPU::op_call_f_nn() { + uint8 lo = op_read(r[PC]++); + uint8 hi = op_read(r[PC]++); + if(r.f[x] == y) { + op_write(--r[SP], r[PC] >> 8); + op_write(--r[SP], r[PC] >> 0); + r[PC] = (hi << 8) | (lo << 0); + op_io(); + } +} + void CPU::op_ret() { uint8 lo = op_read(r[SP]++); uint8 hi = op_read(r[SP]++); @@ -254,6 +633,24 @@ void CPU::op_ret() { op_io(); } +template void CPU::op_ret_f() { + op_io(); + if(r.f[x] == y) { + uint8 lo = op_read(r[SP]++); + uint8 hi = op_read(r[SP]++); + r[PC] = (hi << 8) | (lo << 0); + op_io(); + } +} + +void CPU::op_reti() { + uint8 lo = op_read(r[SP]++); + uint8 hi = op_read(r[SP]++); + r[PC] = (hi << 8) | (lo << 0); + op_io(); + status.ime = 1; +} + template void CPU::op_rst_n() { op_write(--r[SP], r[PC] >> 8); op_write(--r[SP], r[PC] >> 0); @@ -261,335 +658,4 @@ template void CPU::op_rst_n() { op_io(); } -//opcode tables - -void CPU::initialize_opcode_table() { - for(unsigned n = 0; n < 256; n++) opcode_table[n] = &CPU::op_unknown; - for(unsigned n = 0; n < 256; n++) opcode_table_cb[n] = &CPU::op_unknown; - - opcode_table[0x00] = &CPU::op_nop; - opcode_table[0x01] = &CPU::op_ld_rr_nn; - opcode_table[0x03] = &CPU::op_inc_rr; - opcode_table[0x05] = &CPU::op_dec_r; - opcode_table[0x06] = &CPU::op_ld_r_n; - opcode_table[0x09] = &CPU::op_add_hl_rr; - opcode_table[0x0b] = &CPU::op_dec_rr; - opcode_table[0x0c] = &CPU::op_inc_r; - opcode_table[0x0d] = &CPU::op_dec_r; - opcode_table[0x0e] = &CPU::op_ld_r_n; - opcode_table[0x11] = &CPU::op_ld_rr_nn; - opcode_table[0x13] = &CPU::op_inc_rr; - opcode_table[0x16] = &CPU::op_ld_r_n; - opcode_table[0x19] = &CPU::op_add_hl_rr; - opcode_table[0x1b] = &CPU::op_dec_rr; - opcode_table[0x1e] = &CPU::op_ld_r_n; - opcode_table[0x20] = &CPU::op_jr_f_n; - opcode_table[0x21] = &CPU::op_ld_rr_nn; - opcode_table[0x23] = &CPU::op_inc_rr; - opcode_table[0x26] = &CPU::op_ld_r_n; - opcode_table[0x29] = &CPU::op_add_hl_rr; - opcode_table[0x2a] = &CPU::op_ldi_a_hl; - opcode_table[0x2b] = &CPU::op_dec_rr; - opcode_table[0x2e] = &CPU::op_ld_r_n; - opcode_table[0x2f] = &CPU::op_cpl; - opcode_table[0x31] = &CPU::op_ld_rr_nn; - opcode_table[0x32] = &CPU::op_ldd_hl_a; - opcode_table[0x33] = &CPU::op_inc_rr; - opcode_table[0x36] = &CPU::op_ld_hl_n; - opcode_table[0x39] = &CPU::op_add_hl_rr; - opcode_table[0x3b] = &CPU::op_dec_rr; - opcode_table[0x3e] = &CPU::op_ld_r_n; - opcode_table[0x40] = &CPU::op_ld_r_r; - opcode_table[0x41] = &CPU::op_ld_r_r; - opcode_table[0x42] = &CPU::op_ld_r_r; - opcode_table[0x43] = &CPU::op_ld_r_r; - opcode_table[0x44] = &CPU::op_ld_r_r; - opcode_table[0x45] = &CPU::op_ld_r_r; - opcode_table[0x46] = &CPU::op_ld_r_hl; - opcode_table[0x47] = &CPU::op_ld_r_r; - opcode_table[0x48] = &CPU::op_ld_r_r; - opcode_table[0x49] = &CPU::op_ld_r_r; - opcode_table[0x4a] = &CPU::op_ld_r_r; - opcode_table[0x4b] = &CPU::op_ld_r_r; - opcode_table[0x4c] = &CPU::op_ld_r_r; - opcode_table[0x4d] = &CPU::op_ld_r_r; - opcode_table[0x4e] = &CPU::op_ld_r_hl; - opcode_table[0x4f] = &CPU::op_ld_r_r; - opcode_table[0x50] = &CPU::op_ld_r_r; - opcode_table[0x51] = &CPU::op_ld_r_r; - opcode_table[0x52] = &CPU::op_ld_r_r; - opcode_table[0x53] = &CPU::op_ld_r_r; - opcode_table[0x54] = &CPU::op_ld_r_r; - opcode_table[0x55] = &CPU::op_ld_r_r; - opcode_table[0x56] = &CPU::op_ld_r_hl; - opcode_table[0x57] = &CPU::op_ld_r_r; - opcode_table[0x58] = &CPU::op_ld_r_r; - opcode_table[0x59] = &CPU::op_ld_r_r; - opcode_table[0x5a] = &CPU::op_ld_r_r; - opcode_table[0x5b] = &CPU::op_ld_r_r; - opcode_table[0x5c] = &CPU::op_ld_r_r; - opcode_table[0x5d] = &CPU::op_ld_r_r; - opcode_table[0x5e] = &CPU::op_ld_r_hl; - opcode_table[0x5f] = &CPU::op_ld_r_r; - opcode_table[0x60] = &CPU::op_ld_r_r; - opcode_table[0x61] = &CPU::op_ld_r_r; - opcode_table[0x62] = &CPU::op_ld_r_r; - opcode_table[0x63] = &CPU::op_ld_r_r; - opcode_table[0x64] = &CPU::op_ld_r_r; - opcode_table[0x65] = &CPU::op_ld_r_r; - opcode_table[0x66] = &CPU::op_ld_r_hl; - opcode_table[0x67] = &CPU::op_ld_r_r; - opcode_table[0x68] = &CPU::op_ld_r_r; - opcode_table[0x69] = &CPU::op_ld_r_r; - opcode_table[0x6a] = &CPU::op_ld_r_r; - opcode_table[0x6b] = &CPU::op_ld_r_r; - opcode_table[0x6c] = &CPU::op_ld_r_r; - opcode_table[0x6d] = &CPU::op_ld_r_r; - opcode_table[0x6e] = &CPU::op_ld_r_hl; - opcode_table[0x6f] = &CPU::op_ld_r_r; - opcode_table[0x78] = &CPU::op_ld_r_r; - opcode_table[0x79] = &CPU::op_ld_r_r; - opcode_table[0x7a] = &CPU::op_ld_r_r; - opcode_table[0x7b] = &CPU::op_ld_r_r; - opcode_table[0x7c] = &CPU::op_ld_r_r; - opcode_table[0x7d] = &CPU::op_ld_r_r; - opcode_table[0x7e] = &CPU::op_ld_r_hl; - opcode_table[0x7f] = &CPU::op_ld_r_r; - opcode_table[0x80] = &CPU::op_add_a_r; - opcode_table[0x81] = &CPU::op_add_a_r; - opcode_table[0x82] = &CPU::op_add_a_r; - opcode_table[0x83] = &CPU::op_add_a_r; - opcode_table[0x84] = &CPU::op_add_a_r; - opcode_table[0x85] = &CPU::op_add_a_r; - opcode_table[0x87] = &CPU::op_add_a_r; - opcode_table[0xa0] = &CPU::op_and_r; - opcode_table[0xa1] = &CPU::op_and_r; - opcode_table[0xa2] = &CPU::op_and_r; - opcode_table[0xa3] = &CPU::op_and_r; - opcode_table[0xa4] = &CPU::op_and_r; - opcode_table[0xa5] = &CPU::op_and_r; - opcode_table[0xa7] = &CPU::op_and_r; - opcode_table[0xa8] = &CPU::op_xor_r; - opcode_table[0xa9] = &CPU::op_xor_r; - opcode_table[0xaa] = &CPU::op_xor_r; - opcode_table[0xab] = &CPU::op_xor_r; - opcode_table[0xac] = &CPU::op_xor_r; - opcode_table[0xad] = &CPU::op_xor_r; - opcode_table[0xaf] = &CPU::op_xor_r; - opcode_table[0xb0] = &CPU::op_or_r; - opcode_table[0xb1] = &CPU::op_or_r; - opcode_table[0xb2] = &CPU::op_or_r; - opcode_table[0xb3] = &CPU::op_or_r; - opcode_table[0xb4] = &CPU::op_or_r; - opcode_table[0xb5] = &CPU::op_or_r; - opcode_table[0xb7] = &CPU::op_or_r; - opcode_table[0xc1] = &CPU::op_pop_rr; - opcode_table[0xc3] = &CPU::op_jp_nn; - opcode_table[0xc5] = &CPU::op_push_rr; - opcode_table[0xc7] = &CPU::op_rst_n<0x00>; - opcode_table[0xc9] = &CPU::op_ret; - opcode_table[0xcb] = &CPU::op_cb; - opcode_table[0xcd] = &CPU::op_call_nn; - opcode_table[0xcf] = &CPU::op_rst_n<0x08>; - opcode_table[0xd1] = &CPU::op_pop_rr; - opcode_table[0xd5] = &CPU::op_push_rr; - opcode_table[0xd7] = &CPU::op_rst_n<0x10>; - opcode_table[0xdf] = &CPU::op_rst_n<0x18>; - opcode_table[0xe0] = &CPU::op_ld_ffn_a; - opcode_table[0xe1] = &CPU::op_pop_rr; - opcode_table[0xe2] = &CPU::op_ld_ffc_a; - opcode_table[0xe5] = &CPU::op_push_rr; - opcode_table[0xe6] = &CPU::op_and_n; - opcode_table[0xe7] = &CPU::op_rst_n<0x20>; - opcode_table[0xe9] = &CPU::op_jp_hl; - opcode_table[0xea] = &CPU::op_ld_nn_a; - opcode_table[0xef] = &CPU::op_rst_n<0x28>; - opcode_table[0xf0] = &CPU::op_ld_a_ffn; - opcode_table[0xf1] = &CPU::op_pop_rr; - opcode_table[0xf3] = &CPU::op_di; - opcode_table[0xf5] = &CPU::op_push_rr; - opcode_table[0xf7] = &CPU::op_rst_n<0x30>; - opcode_table[0xfb] = &CPU::op_ei; - opcode_table[0xfe] = &CPU::op_cp_n; - opcode_table[0xff] = &CPU::op_rst_n<0x38>; - - opcode_table_cb[0x37] = &CPU::op_swap_r; - opcode_table_cb[0x40] = &CPU::op_bit_n_r<0, B>; - opcode_table_cb[0x41] = &CPU::op_bit_n_r<0, C>; - opcode_table_cb[0x42] = &CPU::op_bit_n_r<0, D>; - opcode_table_cb[0x43] = &CPU::op_bit_n_r<0, E>; - opcode_table_cb[0x44] = &CPU::op_bit_n_r<0, H>; - opcode_table_cb[0x45] = &CPU::op_bit_n_r<0, L>; - opcode_table_cb[0x47] = &CPU::op_bit_n_r<0, A>; - opcode_table_cb[0x48] = &CPU::op_bit_n_r<1, B>; - opcode_table_cb[0x49] = &CPU::op_bit_n_r<1, C>; - opcode_table_cb[0x4a] = &CPU::op_bit_n_r<1, D>; - opcode_table_cb[0x4b] = &CPU::op_bit_n_r<1, E>; - opcode_table_cb[0x4c] = &CPU::op_bit_n_r<1, H>; - opcode_table_cb[0x4d] = &CPU::op_bit_n_r<1, L>; - opcode_table_cb[0x4f] = &CPU::op_bit_n_r<1, A>; - opcode_table_cb[0x50] = &CPU::op_bit_n_r<2, B>; - opcode_table_cb[0x51] = &CPU::op_bit_n_r<2, C>; - opcode_table_cb[0x52] = &CPU::op_bit_n_r<2, D>; - opcode_table_cb[0x53] = &CPU::op_bit_n_r<2, E>; - opcode_table_cb[0x54] = &CPU::op_bit_n_r<2, H>; - opcode_table_cb[0x55] = &CPU::op_bit_n_r<2, L>; - opcode_table_cb[0x57] = &CPU::op_bit_n_r<2, A>; - opcode_table_cb[0x58] = &CPU::op_bit_n_r<3, B>; - opcode_table_cb[0x59] = &CPU::op_bit_n_r<3, C>; - opcode_table_cb[0x5a] = &CPU::op_bit_n_r<3, D>; - opcode_table_cb[0x5b] = &CPU::op_bit_n_r<3, E>; - opcode_table_cb[0x5c] = &CPU::op_bit_n_r<3, H>; - opcode_table_cb[0x5d] = &CPU::op_bit_n_r<3, L>; - opcode_table_cb[0x5f] = &CPU::op_bit_n_r<3, A>; - opcode_table_cb[0x60] = &CPU::op_bit_n_r<4, B>; - opcode_table_cb[0x61] = &CPU::op_bit_n_r<4, C>; - opcode_table_cb[0x62] = &CPU::op_bit_n_r<4, D>; - opcode_table_cb[0x63] = &CPU::op_bit_n_r<4, E>; - opcode_table_cb[0x64] = &CPU::op_bit_n_r<4, H>; - opcode_table_cb[0x65] = &CPU::op_bit_n_r<4, L>; - opcode_table_cb[0x67] = &CPU::op_bit_n_r<4, A>; - opcode_table_cb[0x68] = &CPU::op_bit_n_r<5, B>; - opcode_table_cb[0x69] = &CPU::op_bit_n_r<5, C>; - opcode_table_cb[0x6a] = &CPU::op_bit_n_r<5, D>; - opcode_table_cb[0x6b] = &CPU::op_bit_n_r<5, E>; - opcode_table_cb[0x6c] = &CPU::op_bit_n_r<5, H>; - opcode_table_cb[0x6d] = &CPU::op_bit_n_r<5, L>; - opcode_table_cb[0x6f] = &CPU::op_bit_n_r<5, A>; - opcode_table_cb[0x70] = &CPU::op_bit_n_r<6, B>; - opcode_table_cb[0x71] = &CPU::op_bit_n_r<6, C>; - opcode_table_cb[0x72] = &CPU::op_bit_n_r<6, D>; - opcode_table_cb[0x73] = &CPU::op_bit_n_r<6, E>; - opcode_table_cb[0x74] = &CPU::op_bit_n_r<6, H>; - opcode_table_cb[0x75] = &CPU::op_bit_n_r<6, L>; - opcode_table_cb[0x77] = &CPU::op_bit_n_r<6, A>; - opcode_table_cb[0x78] = &CPU::op_bit_n_r<7, B>; - opcode_table_cb[0x79] = &CPU::op_bit_n_r<7, C>; - opcode_table_cb[0x7a] = &CPU::op_bit_n_r<7, D>; - opcode_table_cb[0x7b] = &CPU::op_bit_n_r<7, E>; - opcode_table_cb[0x7c] = &CPU::op_bit_n_r<7, H>; - opcode_table_cb[0x7d] = &CPU::op_bit_n_r<7, L>; - opcode_table_cb[0x7f] = &CPU::op_bit_n_r<7, A>; - opcode_table_cb[0x80] = &CPU::op_res_n_r<0, B>; - opcode_table_cb[0x81] = &CPU::op_res_n_r<0, C>; - opcode_table_cb[0x82] = &CPU::op_res_n_r<0, D>; - opcode_table_cb[0x83] = &CPU::op_res_n_r<0, E>; - opcode_table_cb[0x84] = &CPU::op_res_n_r<0, H>; - opcode_table_cb[0x85] = &CPU::op_res_n_r<0, L>; - opcode_table_cb[0x87] = &CPU::op_res_n_r<0, A>; - opcode_table_cb[0x88] = &CPU::op_res_n_r<1, B>; - opcode_table_cb[0x89] = &CPU::op_res_n_r<1, C>; - opcode_table_cb[0x8a] = &CPU::op_res_n_r<1, D>; - opcode_table_cb[0x8b] = &CPU::op_res_n_r<1, E>; - opcode_table_cb[0x8c] = &CPU::op_res_n_r<1, H>; - opcode_table_cb[0x8d] = &CPU::op_res_n_r<1, L>; - opcode_table_cb[0x8f] = &CPU::op_res_n_r<1, A>; - opcode_table_cb[0x90] = &CPU::op_res_n_r<2, B>; - opcode_table_cb[0x91] = &CPU::op_res_n_r<2, C>; - opcode_table_cb[0x92] = &CPU::op_res_n_r<2, D>; - opcode_table_cb[0x93] = &CPU::op_res_n_r<2, E>; - opcode_table_cb[0x94] = &CPU::op_res_n_r<2, H>; - opcode_table_cb[0x95] = &CPU::op_res_n_r<2, L>; - opcode_table_cb[0x97] = &CPU::op_res_n_r<2, A>; - opcode_table_cb[0x98] = &CPU::op_res_n_r<3, B>; - opcode_table_cb[0x99] = &CPU::op_res_n_r<3, C>; - opcode_table_cb[0x9a] = &CPU::op_res_n_r<3, D>; - opcode_table_cb[0x9b] = &CPU::op_res_n_r<3, E>; - opcode_table_cb[0x9c] = &CPU::op_res_n_r<3, H>; - opcode_table_cb[0x9d] = &CPU::op_res_n_r<3, L>; - opcode_table_cb[0x9f] = &CPU::op_res_n_r<3, A>; - opcode_table_cb[0xa0] = &CPU::op_res_n_r<4, B>; - opcode_table_cb[0xa1] = &CPU::op_res_n_r<4, C>; - opcode_table_cb[0xa2] = &CPU::op_res_n_r<4, D>; - opcode_table_cb[0xa3] = &CPU::op_res_n_r<4, E>; - opcode_table_cb[0xa4] = &CPU::op_res_n_r<4, H>; - opcode_table_cb[0xa5] = &CPU::op_res_n_r<4, L>; - opcode_table_cb[0xa7] = &CPU::op_res_n_r<4, A>; - opcode_table_cb[0xa8] = &CPU::op_res_n_r<5, B>; - opcode_table_cb[0xa9] = &CPU::op_res_n_r<5, C>; - opcode_table_cb[0xaa] = &CPU::op_res_n_r<5, D>; - opcode_table_cb[0xab] = &CPU::op_res_n_r<5, E>; - opcode_table_cb[0xac] = &CPU::op_res_n_r<5, H>; - opcode_table_cb[0xad] = &CPU::op_res_n_r<5, L>; - opcode_table_cb[0xaf] = &CPU::op_res_n_r<5, A>; - opcode_table_cb[0xb0] = &CPU::op_res_n_r<6, B>; - opcode_table_cb[0xb1] = &CPU::op_res_n_r<6, C>; - opcode_table_cb[0xb2] = &CPU::op_res_n_r<6, D>; - opcode_table_cb[0xb3] = &CPU::op_res_n_r<6, E>; - opcode_table_cb[0xb4] = &CPU::op_res_n_r<6, H>; - opcode_table_cb[0xb5] = &CPU::op_res_n_r<6, L>; - opcode_table_cb[0xb7] = &CPU::op_res_n_r<6, A>; - opcode_table_cb[0xb8] = &CPU::op_res_n_r<7, B>; - opcode_table_cb[0xb9] = &CPU::op_res_n_r<7, C>; - opcode_table_cb[0xba] = &CPU::op_res_n_r<7, D>; - opcode_table_cb[0xbb] = &CPU::op_res_n_r<7, E>; - opcode_table_cb[0xbc] = &CPU::op_res_n_r<7, H>; - opcode_table_cb[0xbd] = &CPU::op_res_n_r<7, L>; - opcode_table_cb[0xbf] = &CPU::op_res_n_r<7, A>; - opcode_table_cb[0xc0] = &CPU::op_set_n_r<0, B>; - opcode_table_cb[0xc1] = &CPU::op_set_n_r<0, C>; - opcode_table_cb[0xc2] = &CPU::op_set_n_r<0, D>; - opcode_table_cb[0xc3] = &CPU::op_set_n_r<0, E>; - opcode_table_cb[0xc4] = &CPU::op_set_n_r<0, H>; - opcode_table_cb[0xc5] = &CPU::op_set_n_r<0, L>; - opcode_table_cb[0xc7] = &CPU::op_set_n_r<0, A>; - opcode_table_cb[0xc8] = &CPU::op_set_n_r<1, B>; - opcode_table_cb[0xc9] = &CPU::op_set_n_r<1, C>; - opcode_table_cb[0xca] = &CPU::op_set_n_r<1, D>; - opcode_table_cb[0xcb] = &CPU::op_set_n_r<1, E>; - opcode_table_cb[0xcc] = &CPU::op_set_n_r<1, H>; - opcode_table_cb[0xcd] = &CPU::op_set_n_r<1, L>; - opcode_table_cb[0xcf] = &CPU::op_set_n_r<1, A>; - opcode_table_cb[0xd0] = &CPU::op_set_n_r<2, B>; - opcode_table_cb[0xd1] = &CPU::op_set_n_r<2, C>; - opcode_table_cb[0xd2] = &CPU::op_set_n_r<2, D>; - opcode_table_cb[0xd3] = &CPU::op_set_n_r<2, E>; - opcode_table_cb[0xd4] = &CPU::op_set_n_r<2, H>; - opcode_table_cb[0xd5] = &CPU::op_set_n_r<2, L>; - opcode_table_cb[0xd7] = &CPU::op_set_n_r<2, A>; - opcode_table_cb[0xd8] = &CPU::op_set_n_r<3, B>; - opcode_table_cb[0xd9] = &CPU::op_set_n_r<3, C>; - opcode_table_cb[0xda] = &CPU::op_set_n_r<3, D>; - opcode_table_cb[0xdb] = &CPU::op_set_n_r<3, E>; - opcode_table_cb[0xdc] = &CPU::op_set_n_r<3, H>; - opcode_table_cb[0xdd] = &CPU::op_set_n_r<3, L>; - opcode_table_cb[0xdf] = &CPU::op_set_n_r<3, A>; - opcode_table_cb[0xe0] = &CPU::op_set_n_r<4, B>; - opcode_table_cb[0xe1] = &CPU::op_set_n_r<4, C>; - opcode_table_cb[0xe2] = &CPU::op_set_n_r<4, D>; - opcode_table_cb[0xe3] = &CPU::op_set_n_r<4, E>; - opcode_table_cb[0xe4] = &CPU::op_set_n_r<4, H>; - opcode_table_cb[0xe5] = &CPU::op_set_n_r<4, L>; - opcode_table_cb[0xe7] = &CPU::op_set_n_r<4, A>; - opcode_table_cb[0xe8] = &CPU::op_set_n_r<5, B>; - opcode_table_cb[0xe9] = &CPU::op_set_n_r<5, C>; - opcode_table_cb[0xea] = &CPU::op_set_n_r<5, D>; - opcode_table_cb[0xeb] = &CPU::op_set_n_r<5, E>; - opcode_table_cb[0xec] = &CPU::op_set_n_r<5, H>; - opcode_table_cb[0xed] = &CPU::op_set_n_r<5, L>; - opcode_table_cb[0xef] = &CPU::op_set_n_r<5, A>; - opcode_table_cb[0xf0] = &CPU::op_set_n_r<6, B>; - opcode_table_cb[0xf1] = &CPU::op_set_n_r<6, C>; - opcode_table_cb[0xf2] = &CPU::op_set_n_r<6, D>; - opcode_table_cb[0xf3] = &CPU::op_set_n_r<6, E>; - opcode_table_cb[0xf4] = &CPU::op_set_n_r<6, H>; - opcode_table_cb[0xf5] = &CPU::op_set_n_r<6, L>; - opcode_table_cb[0xf7] = &CPU::op_set_n_r<6, A>; - opcode_table_cb[0xf8] = &CPU::op_set_n_r<7, B>; - opcode_table_cb[0xf9] = &CPU::op_set_n_r<7, C>; - opcode_table_cb[0xfa] = &CPU::op_set_n_r<7, D>; - opcode_table_cb[0xfb] = &CPU::op_set_n_r<7, E>; - opcode_table_cb[0xfc] = &CPU::op_set_n_r<7, H>; - opcode_table_cb[0xfd] = &CPU::op_set_n_r<7, L>; - opcode_table_cb[0xff] = &CPU::op_set_n_r<7, A>; - - unsigned missing = 0; - for(unsigned n = 0; n < 256; n++) { - if(opcode_table[n] == &CPU::op_unknown) missing++; - if(opcode_table_cb[n] == &CPU::op_unknown) missing++; - } - - print("CPU opcodes: ", 512 - missing, " implemented, ", missing, " remaining.\n"); -} - #endif diff --git a/gameboy/cpu/core/core.hpp b/gameboy/cpu/core/core.hpp index 82a0ed03..16444966 100755 --- a/gameboy/cpu/core/core.hpp +++ b/gameboy/cpu/core/core.hpp @@ -3,62 +3,139 @@ void (CPU::*opcode_table[256])(); void (CPU::*opcode_table_cb[256])(); void initialize_opcode_table(); -void op_unknown(); - +void op_xx(); void op_cb(); //8-bit load commands template void op_ld_r_r(); template void op_ld_r_n(); template void op_ld_r_hl(); +template void op_ld_hl_r(); void op_ld_hl_n(); +template void op_ld_a_rr(); +void op_ld_a_nn(); +template void op_ld_rr_a(); void op_ld_nn_a(); void op_ld_a_ffn(); void op_ld_ffn_a(); void op_ld_ffc_a(); +void op_ldi_hl_a(); void op_ldi_a_hl(); void op_ldd_hl_a(); +void op_ldd_a_hl(); //16-bit load commands template void op_ld_rr_nn(); +void op_ld_nn_sp(); +void op_ld_sp_hl(); template void op_push_rr(); template void op_pop_rr(); //8-bit arithmetic commands +void opi_add_a(uint8 x); template void op_add_a_r(); -template void op_and_r(); -void op_and_n(); -template void op_xor_r(); -template void op_or_r(); -void op_cp_n(); +void op_add_a_n(); +void op_add_a_hl(); + +void opi_adc_a(uint8 x); +template void op_adc_a_r(); +void op_adc_a_n(); +void op_adc_a_hl(); + +void opi_sub_a(uint8 x); +template void op_sub_a_r(); +void op_sub_a_n(); +void op_sub_a_hl(); + +void opi_sbc_a(uint8 x); +template void op_sbc_a_r(); +void op_sbc_a_n(); +void op_sbc_a_hl(); + +void opi_and_a(uint8 x); +template void op_and_a_r(); +void op_and_a_n(); +void op_and_a_hl(); + +void opi_xor_a(uint8 x); +template void op_xor_a_r(); +void op_xor_a_n(); +void op_xor_a_hl(); + +void opi_or_a(uint8 x); +template void op_or_a_r(); +void op_or_a_n(); +void op_or_a_hl(); + +void opi_cp_a(uint8 x); +template void op_cp_a_r(); +void op_cp_a_n(); +void op_cp_a_hl(); + template void op_inc_r(); +void op_inc_hl(); template void op_dec_r(); +void op_dec_hl(); +void op_daa(); void op_cpl(); //16-bit arithmetic commands template void op_add_hl_rr(); template void op_inc_rr(); template void op_dec_rr(); +void op_add_sp_n(); +void op_ld_hl_sp_n(); //rotate/shift commands +void op_rlca(); +void op_rla(); +void op_rrca(); +void op_rra(); +template void op_rlc_r(); +void op_rlc_hl(); +template void op_rl_r(); +void op_rl_hl(); +template void op_rrc_r(); +void op_rrc_hl(); +template void op_rr_r(); +void op_rr_hl(); +template void op_sla_r(); +void op_sla_hl(); template void op_swap_r(); +void op_swap_hl(); +template void op_sra_r(); +void op_sra_hl(); +template void op_srl_r(); +void op_srl_hl(); //single-bit commands template void op_bit_n_r(); +template void op_bit_n_hl(); template void op_set_n_r(); +template void op_set_n_hl(); template void op_res_n_r(); +template void op_res_n_hl(); //control commands +void op_ccf(); +void op_scf(); void op_nop(); +void op_halt(); +void op_stop(); void op_di(); void op_ei(); //jump commands void op_jp_nn(); void op_jp_hl(); +template void op_jp_f_nn(); +void op_jr_n(); template void op_jr_f_n(); void op_call_nn(); +template void op_call_f_nn(); void op_ret(); +template void op_ret_f(); +void op_reti(); template void op_rst_n(); //disassembler.cpp diff --git a/gameboy/cpu/core/disassembler.cpp b/gameboy/cpu/core/disassembler.cpp index c8c69fa9..9c67f444 100755 --- a/gameboy/cpu/core/disassembler.cpp +++ b/gameboy/cpu/core/disassembler.cpp @@ -30,36 +30,68 @@ string CPU::disassemble_opcode(uint16 pc) { switch(opcode) { case 0x00: return { "nop" }; case 0x01: return { "ld bc,$", hex<2>(p1), hex<2>(p0) }; + case 0x02: return { "ld (bc),a" }; case 0x03: return { "inc bc" }; + case 0x04: return { "inc b" }; case 0x05: return { "dec b" }; case 0x06: return { "ld b,$", hex<2>(p0) }; + case 0x07: return { "rlc a" }; + case 0x08: return { "ld ($", hex<2>(p1), hex<2>(p0), "),sp" }; case 0x09: return { "add hl,bc" }; + case 0x0a: return { "ld a,(bc)" }; case 0x0b: return { "dec bc" }; case 0x0c: return { "inc c" }; case 0x0d: return { "dec c" }; case 0x0e: return { "ld c,$", hex<2>(p0) }; + case 0x0f: return { "rrc a" }; + case 0x10: return { "stop" }; case 0x11: return { "ld de,$", hex<2>(p1), hex<2>(p0) }; + case 0x12: return { "ld (de),a" }; case 0x13: return { "inc de" }; + case 0x14: return { "inc d" }; + case 0x15: return { "dec d" }; case 0x16: return { "ld d,$", hex<2>(p0) }; + case 0x17: return { "rl a" }; + case 0x18: return { "jr $", hex<4>(r[PC] + 2 + (int8)p0) }; case 0x19: return { "add hl,de" }; + case 0x1a: return { "ld a,(de)" }; case 0x1b: return { "dec de" }; + case 0x1c: return { "inc e" }; + case 0x1d: return { "dec e" }; case 0x1e: return { "ld e,$", hex<2>(p0) }; - case 0x20: return { "jp nz,$", hex<4>(r[PC] + 2 + (int8)p0) }; + case 0x1f: return { "rr a" }; + case 0x20: return { "jr nz,$", hex<4>(r[PC] + 2 + (int8)p0) }; case 0x21: return { "ld hl,$", hex<2>(p1), hex<2>(p0) }; + case 0x22: return { "ldi (hl),a" }; case 0x23: return { "inc hl" }; + case 0x24: return { "inc h" }; + case 0x25: return { "dec h" }; case 0x26: return { "ld h,$", hex<2>(p0) }; + case 0x27: return { "daa" }; + case 0x28: return { "jr z,$", hex<4>(r[PC] + 2 + (int8)p0) }; case 0x29: return { "add hl,hl" }; case 0x2a: return { "ldi a,(hl)" }; case 0x2b: return { "dec hl" }; + case 0x2c: return { "inc l" }; + case 0x2d: return { "dec l" }; case 0x2e: return { "ld l,$", hex<2>(p0) }; case 0x2f: return { "cpl" }; + case 0x30: return { "jr nc,$", hex<4>(r[PC] + 2 + (int8)p0) }; case 0x31: return { "ld sp,$", hex<2>(p1), hex<2>(p0) }; case 0x32: return { "ldd (hl),a" }; case 0x33: return { "inc sp" }; + case 0x34: return { "inc (hl)" }; + case 0x35: return { "dec (hl)" }; case 0x36: return { "ld (hl),$", hex<2>(p0) }; + case 0x37: return { "scf" }; + case 0x38: return { "jr c,$", hex<4>(r[PC] + 2 + (int8)p0) }; case 0x39: return { "add hl,sp" }; + case 0x3a: return { "ldd a,(hl)" }; case 0x3b: return { "dec sp" }; + case 0x3c: return { "inc a" }; + case 0x3d: return { "dec a" }; case 0x3e: return { "ld a,$", hex<2>(p0) }; + case 0x3f: return { "ccf" }; case 0x40: return { "ld b,b" }; case 0x41: return { "ld b,c" }; case 0x42: return { "ld b,d" }; @@ -108,6 +140,14 @@ string CPU::disassemble_opcode(uint16 pc) { case 0x6d: return { "ld l,l" }; case 0x6e: return { "ld l,(hl)" }; case 0x6f: return { "ld l,a" }; + case 0x70: return { "ld (hl),b" }; + case 0x71: return { "ld (hl),c" }; + case 0x72: return { "ld (hl),d" }; + case 0x73: return { "ld (hl),e" }; + case 0x74: return { "ld (hl),h" }; + case 0x75: return { "ld (hl),l" }; + case 0x76: return { "halt" }; + case 0x77: return { "ld (hl),a" }; case 0x78: return { "ld a,b" }; case 0x79: return { "ld a,c" }; case 0x7a: return { "ld a,d" }; @@ -122,60 +162,131 @@ string CPU::disassemble_opcode(uint16 pc) { case 0x83: return { "add a,e" }; case 0x84: return { "add a,h" }; case 0x85: return { "add a,l" }; + case 0x86: return { "add a,(hl)" }; case 0x87: return { "add a,a" }; - case 0xa0: return { "and b" }; - case 0xa1: return { "and c" }; - case 0xa2: return { "and d" }; - case 0xa3: return { "and e" }; - case 0xa4: return { "and h" }; - case 0xa5: return { "and l" }; - case 0xa7: return { "and a" }; - case 0xa8: return { "xor b" }; - case 0xa9: return { "xor c" }; - case 0xaa: return { "xor d" }; - case 0xab: return { "xor e" }; - case 0xac: return { "xor h" }; - case 0xad: return { "xor l" }; - case 0xaf: return { "xor a" }; - case 0xb0: return { "or b" }; - case 0xb1: return { "or c" }; - case 0xb2: return { "or d" }; - case 0xb3: return { "or e" }; - case 0xb4: return { "or h" }; - case 0xb5: return { "or l" }; - case 0xb7: return { "or a" }; + case 0x88: return { "adc a,b" }; + case 0x89: return { "adc a,c" }; + case 0x8a: return { "adc a,d" }; + case 0x8b: return { "adc a,e" }; + case 0x8c: return { "adc a,h" }; + case 0x8d: return { "adc a,l" }; + case 0x8e: return { "adc a,(hl)" }; + case 0x8f: return { "adc a,a" }; + case 0x90: return { "sub a,b" }; + case 0x91: return { "sub a,c" }; + case 0x92: return { "sub a,d" }; + case 0x93: return { "sub a,e" }; + case 0x94: return { "sub a,h" }; + case 0x95: return { "sub a,l" }; + case 0x96: return { "sub a,(hl)" }; + case 0x97: return { "sub a,a" }; + case 0x98: return { "sbc a,b" }; + case 0x99: return { "sbc a,c" }; + case 0x9a: return { "sbc a,d" }; + case 0x9b: return { "sbc a,e" }; + case 0x9c: return { "sbc a,h" }; + case 0x9d: return { "sbc a,l" }; + case 0x9e: return { "sbc a,(hl)" }; + case 0x9f: return { "sbc a,a" }; + case 0xa0: return { "and a,b" }; + case 0xa1: return { "and a,c" }; + case 0xa2: return { "and a,d" }; + case 0xa3: return { "and a,e" }; + case 0xa4: return { "and a,h" }; + case 0xa5: return { "and a,l" }; + case 0xa6: return { "and a,(hl)" }; + case 0xa7: return { "and a,a" }; + case 0xa8: return { "xor a,b" }; + case 0xa9: return { "xor a,c" }; + case 0xaa: return { "xor a,d" }; + case 0xab: return { "xor a,e" }; + case 0xac: return { "xor a,h" }; + case 0xad: return { "xor a,l" }; + case 0xae: return { "xor a,(hl)" }; + case 0xaf: return { "xor a,a" }; + case 0xb0: return { "or a,b" }; + case 0xb1: return { "or a,c" }; + case 0xb2: return { "or a,d" }; + case 0xb3: return { "or a,e" }; + case 0xb4: return { "or a,h" }; + case 0xb5: return { "or a,l" }; + case 0xb6: return { "or a,(hl)" }; + case 0xb7: return { "or a,a" }; + case 0xb8: return { "cp a,b" }; + case 0xb9: return { "cp a,c" }; + case 0xba: return { "cp a,d" }; + case 0xbb: return { "cp a,e" }; + case 0xbc: return { "cp a,h" }; + case 0xbd: return { "cp a,l" }; + case 0xbe: return { "cp a,(hl)" }; + case 0xbf: return { "cp a,a" }; + case 0xc0: return { "ret nz" }; case 0xc1: return { "pop bc" }; + case 0xc2: return { "jp nz,$", hex<2>(p1), hex<2>(p0) }; case 0xc3: return { "jp $", hex<2>(p1), hex<2>(p0) }; + case 0xc4: return { "call nz,$", hex<2>(p1), hex<2>(p0) }; case 0xc5: return { "push bc" }; + case 0xc6: return { "add a,$", hex<2>(p0) }; case 0xc7: return { "rst $0000" }; + case 0xc8: return { "ret z" }; case 0xc9: return { "ret" }; + case 0xca: return { "jp z,$", hex<2>(p1), hex<2>(p0) }; case 0xcb: return disassemble_opcode_cb(pc + 1); + case 0xcc: return { "call z,$", hex<2>(p1), hex<2>(p0) }; case 0xcd: return { "call $", hex<2>(p1), hex<2>(p0) }; + case 0xce: return { "adc a,$", hex<2>(p0) }; case 0xcf: return { "rst $0008" }; + case 0xd0: return { "ret nc" }; case 0xd1: return { "pop de" }; + case 0xd2: return { "jp nc,$", hex<2>(p1), hex<2>(p0) }; + case 0xd3: return { "xx" }; + case 0xd4: return { "call nc,$", hex<2>(p1), hex<2>(p0) }; case 0xd5: return { "push de" }; + case 0xd6: return { "sub a,$", hex<2>(p0) }; case 0xd7: return { "rst $0010" }; + case 0xd8: return { "ret c" }; + case 0xd9: return { "reti" }; + case 0xda: return { "jp c,$", hex<2>(p1), hex<2>(p0) }; + case 0xdb: return { "xx" }; + case 0xdc: return { "call c,$", hex<2>(p1), hex<2>(p0) }; + case 0xdd: return { "xx" }; + case 0xde: return { "sbc a,$", hex<2>(p0) }; case 0xdf: return { "rst $0018" }; case 0xe0: return { "ld ($ff", hex<2>(p0), "),a" }; case 0xe1: return { "pop hl" }; case 0xe2: return { "ld ($ff00+c),a" }; + case 0xe3: return { "xx" }; + case 0xe4: return { "xx" }; case 0xe5: return { "push hl" }; - case 0xe6: return { "and $", hex<2>(p0) }; + case 0xe6: return { "and a,$", hex<2>(p0) }; case 0xe7: return { "rst $0020" }; + case 0xe8: return { "add sp,$", hex<4>((int8)p0) }; case 0xe9: return { "jp hl" }; case 0xea: return { "ld ($", hex<2>(p1), hex<2>(p0), "),a" }; + case 0xeb: return { "xx" }; + case 0xec: return { "xx" }; + case 0xed: return { "xx" }; + case 0xee: return { "xor a,$", hex<2>(p0) }; case 0xef: return { "rst $0028" }; case 0xf0: return { "ld a,($ff", hex<2>(p0), ")" }; case 0xf1: return { "pop af" }; + case 0xf2: return { "xx" }; case 0xf3: return { "di" }; + case 0xf4: return { "xx" }; case 0xf5: return { "push af" }; + case 0xf6: return { "or a,$", hex<2>(p0) }; case 0xf7: return { "rst $0030" }; + case 0xf8: return { "ld hl,sp+$", hex<4>((int8)p0) }; + case 0xf9: return { "ld sp,hl" }; + case 0xfa: return { "ld a,($", hex<2>(p1), hex<2>(p0), ")" }; case 0xfb: return { "ei" }; - case 0xfe: return { "cp $", hex<2>(p0) }; + case 0xfc: return { "xx" }; + case 0xfd: return { "xx" }; + case 0xfe: return { "cp a,$", hex<2>(p0) }; case 0xff: return { "rst $0038" }; } - return { "??? [", hex<2>(opcode), ",", hex<2>(p1), ",", hex<2>(p0), "]" }; + return ""; } string CPU::disassemble_opcode_cb(uint16 pc) { @@ -185,13 +296,77 @@ string CPU::disassemble_opcode_cb(uint16 pc) { uint8 p2 = bus.read(pc + 3); switch(opcode) { + case 0x00: return { "rlc b" }; + case 0x01: return { "rlc c" }; + case 0x02: return { "rlc d" }; + case 0x03: return { "rlc e" }; + case 0x04: return { "rlc h" }; + case 0x05: return { "rlc l" }; + case 0x06: return { "rlc (hl)" }; + case 0x07: return { "rlc a" }; + case 0x08: return { "rrc b" }; + case 0x09: return { "rrc c" }; + case 0x0a: return { "rrc d" }; + case 0x0b: return { "rrc e" }; + case 0x0c: return { "rrc h" }; + case 0x0d: return { "rrc l" }; + case 0x0e: return { "rrc (hl)" }; + case 0x0f: return { "rrc a" }; + case 0x10: return { "rl b" }; + case 0x11: return { "rl c" }; + case 0x12: return { "rl d" }; + case 0x13: return { "rl e" }; + case 0x14: return { "rl h" }; + case 0x15: return { "rl l" }; + case 0x16: return { "rl (hl)" }; + case 0x17: return { "rl a" }; + case 0x18: return { "rr b" }; + case 0x19: return { "rr c" }; + case 0x1a: return { "rr d" }; + case 0x1b: return { "rr e" }; + case 0x1c: return { "rr h" }; + case 0x1d: return { "rr l" }; + case 0x1e: return { "rr (hl)" }; + case 0x1f: return { "rr a" }; + case 0x20: return { "sla b" }; + case 0x21: return { "sla c" }; + case 0x22: return { "sla d" }; + case 0x23: return { "sla e" }; + case 0x24: return { "sla h" }; + case 0x25: return { "sla l" }; + case 0x26: return { "sla (hl)" }; + case 0x27: return { "sla a" }; + case 0x28: return { "sra b" }; + case 0x29: return { "sra c" }; + case 0x2a: return { "sra d" }; + case 0x2b: return { "sra e" }; + case 0x2c: return { "sra h" }; + case 0x2d: return { "sra l" }; + case 0x2e: return { "sra (hl)" }; + case 0x2f: return { "sra a" }; + case 0x30: return { "swap b" }; + case 0x31: return { "swap c" }; + case 0x32: return { "swap d" }; + case 0x33: return { "swap e" }; + case 0x34: return { "swap h" }; + case 0x35: return { "swap l" }; + case 0x36: return { "swap (hl)" }; case 0x37: return { "swap a" }; + case 0x38: return { "srl b" }; + case 0x39: return { "srl c" }; + case 0x3a: return { "srl d" }; + case 0x3b: return { "srl e" }; + case 0x3c: return { "srl h" }; + case 0x3d: return { "srl l" }; + case 0x3e: return { "srl (hl)" }; + case 0x3f: return { "srl a" }; case 0x40: return { "bit 0,b" }; case 0x41: return { "bit 0,c" }; case 0x42: return { "bit 0,d" }; case 0x43: return { "bit 0,e" }; case 0x44: return { "bit 0,h" }; case 0x45: return { "bit 0,l" }; + case 0x46: return { "bit 0,(hl)" }; case 0x47: return { "bit 0,a" }; case 0x48: return { "bit 1,b" }; case 0x49: return { "bit 1,c" }; @@ -199,6 +374,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0x4b: return { "bit 1,e" }; case 0x4c: return { "bit 1,h" }; case 0x4d: return { "bit 1,l" }; + case 0x4e: return { "bit 1,(hl)" }; case 0x4f: return { "bit 1,a" }; case 0x50: return { "bit 2,b" }; case 0x51: return { "bit 2,c" }; @@ -206,6 +382,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0x53: return { "bit 2,e" }; case 0x54: return { "bit 2,h" }; case 0x55: return { "bit 2,l" }; + case 0x56: return { "bit 2,(hl)" }; case 0x57: return { "bit 2,a" }; case 0x58: return { "bit 3,b" }; case 0x59: return { "bit 3,c" }; @@ -213,6 +390,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0x5b: return { "bit 3,e" }; case 0x5c: return { "bit 3,h" }; case 0x5d: return { "bit 3,l" }; + case 0x5e: return { "bit 3,(hl)" }; case 0x5f: return { "bit 3,a" }; case 0x60: return { "bit 4,b" }; case 0x61: return { "bit 4,c" }; @@ -220,6 +398,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0x63: return { "bit 4,e" }; case 0x64: return { "bit 4,h" }; case 0x65: return { "bit 4,l" }; + case 0x66: return { "bit 4,(hl)" }; case 0x67: return { "bit 4,a" }; case 0x68: return { "bit 5,b" }; case 0x69: return { "bit 5,c" }; @@ -227,6 +406,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0x6b: return { "bit 5,e" }; case 0x6c: return { "bit 5,h" }; case 0x6d: return { "bit 5,l" }; + case 0x6e: return { "bit 5,(hl)" }; case 0x6f: return { "bit 5,a" }; case 0x70: return { "bit 6,b" }; case 0x71: return { "bit 6,c" }; @@ -234,6 +414,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0x73: return { "bit 6,e" }; case 0x74: return { "bit 6,h" }; case 0x75: return { "bit 6,l" }; + case 0x76: return { "bit 6,(hl)" }; case 0x77: return { "bit 6,a" }; case 0x78: return { "bit 7,b" }; case 0x79: return { "bit 7,c" }; @@ -241,6 +422,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0x7b: return { "bit 7,e" }; case 0x7c: return { "bit 7,h" }; case 0x7d: return { "bit 7,l" }; + case 0x7e: return { "bit 7,(hl)" }; case 0x7f: return { "bit 7,a" }; case 0x80: return { "res 0,b" }; case 0x81: return { "res 0,c" }; @@ -248,6 +430,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0x83: return { "res 0,e" }; case 0x84: return { "res 0,h" }; case 0x85: return { "res 0,l" }; + case 0x86: return { "res 0,(hl)" }; case 0x87: return { "res 0,a" }; case 0x88: return { "res 1,b" }; case 0x89: return { "res 1,c" }; @@ -255,6 +438,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0x8b: return { "res 1,e" }; case 0x8c: return { "res 1,h" }; case 0x8d: return { "res 1,l" }; + case 0x8e: return { "res 1,(hl)" }; case 0x8f: return { "res 1,a" }; case 0x90: return { "res 2,b" }; case 0x91: return { "res 2,c" }; @@ -262,6 +446,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0x93: return { "res 2,e" }; case 0x94: return { "res 2,h" }; case 0x95: return { "res 2,l" }; + case 0x96: return { "res 2,(hl)" }; case 0x97: return { "res 2,a" }; case 0x98: return { "res 3,b" }; case 0x99: return { "res 3,c" }; @@ -269,6 +454,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0x9b: return { "res 3,e" }; case 0x9c: return { "res 3,h" }; case 0x9d: return { "res 3,l" }; + case 0x9e: return { "res 3,(hl)" }; case 0x9f: return { "res 3,a" }; case 0xa0: return { "res 4,b" }; case 0xa1: return { "res 4,c" }; @@ -276,6 +462,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xa3: return { "res 4,e" }; case 0xa4: return { "res 4,h" }; case 0xa5: return { "res 4,l" }; + case 0xa6: return { "res 4,(hl)" }; case 0xa7: return { "res 4,a" }; case 0xa8: return { "res 5,b" }; case 0xa9: return { "res 5,c" }; @@ -283,6 +470,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xab: return { "res 5,e" }; case 0xac: return { "res 5,h" }; case 0xad: return { "res 5,l" }; + case 0xae: return { "res 5,(hl)" }; case 0xaf: return { "res 5,a" }; case 0xb0: return { "res 6,b" }; case 0xb1: return { "res 6,c" }; @@ -290,6 +478,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xb3: return { "res 6,e" }; case 0xb4: return { "res 6,h" }; case 0xb5: return { "res 6,l" }; + case 0xb6: return { "res 6,(hl)" }; case 0xb7: return { "res 6,a" }; case 0xb8: return { "res 7,b" }; case 0xb9: return { "res 7,c" }; @@ -297,6 +486,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xbb: return { "res 7,e" }; case 0xbc: return { "res 7,h" }; case 0xbd: return { "res 7,l" }; + case 0xbe: return { "res 7,(hl)" }; case 0xbf: return { "res 7,a" }; case 0xc0: return { "set 0,b" }; case 0xc1: return { "set 0,c" }; @@ -304,6 +494,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xc3: return { "set 0,e" }; case 0xc4: return { "set 0,h" }; case 0xc5: return { "set 0,l" }; + case 0xc6: return { "set 0,(hl)" }; case 0xc7: return { "set 0,a" }; case 0xc8: return { "set 1,b" }; case 0xc9: return { "set 1,c" }; @@ -311,6 +502,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xcb: return { "set 1,e" }; case 0xcc: return { "set 1,h" }; case 0xcd: return { "set 1,l" }; + case 0xce: return { "set 1,(hl)" }; case 0xcf: return { "set 1,a" }; case 0xd0: return { "set 2,b" }; case 0xd1: return { "set 2,c" }; @@ -318,6 +510,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xd3: return { "set 2,e" }; case 0xd4: return { "set 2,h" }; case 0xd5: return { "set 2,l" }; + case 0xd6: return { "set 2,(hl)" }; case 0xd7: return { "set 2,a" }; case 0xd8: return { "set 3,b" }; case 0xd9: return { "set 3,c" }; @@ -325,6 +518,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xdb: return { "set 3,e" }; case 0xdc: return { "set 3,h" }; case 0xdd: return { "set 3,l" }; + case 0xde: return { "set 3,(hl)" }; case 0xdf: return { "set 3,a" }; case 0xe0: return { "set 4,b" }; case 0xe1: return { "set 4,c" }; @@ -332,6 +526,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xe3: return { "set 4,e" }; case 0xe4: return { "set 4,h" }; case 0xe5: return { "set 4,l" }; + case 0xe6: return { "set 4,(hl)" }; case 0xe7: return { "set 4,a" }; case 0xe8: return { "set 5,b" }; case 0xe9: return { "set 5,c" }; @@ -339,6 +534,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xeb: return { "set 5,e" }; case 0xec: return { "set 5,h" }; case 0xed: return { "set 5,l" }; + case 0xee: return { "set 5,(hl)" }; case 0xef: return { "set 5,a" }; case 0xf0: return { "set 6,b" }; case 0xf1: return { "set 6,c" }; @@ -346,6 +542,7 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xf3: return { "set 6,e" }; case 0xf4: return { "set 6,h" }; case 0xf5: return { "set 6,l" }; + case 0xf6: return { "set 6,(hl)" }; case 0xf7: return { "set 6,a" }; case 0xf8: return { "set 7,b" }; case 0xf9: return { "set 7,c" }; @@ -353,10 +550,11 @@ string CPU::disassemble_opcode_cb(uint16 pc) { case 0xfb: return { "set 7,e" }; case 0xfc: return { "set 7,h" }; case 0xfd: return { "set 7,l" }; + case 0xfe: return { "set 7,(hl)" }; case 0xff: return { "set 7,a" }; } - return { "cb? [", hex<2>(opcode), ",", hex<2>(p0), ",", hex<2>(p1), "]" }; + return ""; } #endif diff --git a/gameboy/cpu/core/table.cpp b/gameboy/cpu/core/table.cpp new file mode 100755 index 00000000..2c885591 --- /dev/null +++ b/gameboy/cpu/core/table.cpp @@ -0,0 +1,519 @@ +#ifdef CPU_CPP + +void CPU::initialize_opcode_table() { + opcode_table[0x00] = &CPU::op_nop; + opcode_table[0x01] = &CPU::op_ld_rr_nn; + opcode_table[0x02] = &CPU::op_ld_rr_a; + opcode_table[0x03] = &CPU::op_inc_rr; + opcode_table[0x04] = &CPU::op_inc_r; + opcode_table[0x05] = &CPU::op_dec_r; + opcode_table[0x06] = &CPU::op_ld_r_n; + opcode_table[0x07] = &CPU::op_rlca; + opcode_table[0x08] = &CPU::op_ld_nn_sp; + opcode_table[0x09] = &CPU::op_add_hl_rr; + opcode_table[0x0a] = &CPU::op_ld_a_rr; + opcode_table[0x0b] = &CPU::op_dec_rr; + opcode_table[0x0c] = &CPU::op_inc_r; + opcode_table[0x0d] = &CPU::op_dec_r; + opcode_table[0x0e] = &CPU::op_ld_r_n; + opcode_table[0x0f] = &CPU::op_rrca; + opcode_table[0x10] = &CPU::op_stop; + opcode_table[0x11] = &CPU::op_ld_rr_nn; + opcode_table[0x12] = &CPU::op_ld_rr_a; + opcode_table[0x13] = &CPU::op_inc_rr; + opcode_table[0x14] = &CPU::op_inc_r; + opcode_table[0x15] = &CPU::op_dec_r; + opcode_table[0x16] = &CPU::op_ld_r_n; + opcode_table[0x17] = &CPU::op_rla; + opcode_table[0x18] = &CPU::op_jr_n; + opcode_table[0x19] = &CPU::op_add_hl_rr; + opcode_table[0x1a] = &CPU::op_ld_a_rr; + opcode_table[0x1b] = &CPU::op_dec_rr; + opcode_table[0x1c] = &CPU::op_inc_r; + opcode_table[0x1d] = &CPU::op_dec_r; + opcode_table[0x1e] = &CPU::op_ld_r_n; + opcode_table[0x1f] = &CPU::op_rra; + opcode_table[0x20] = &CPU::op_jr_f_n; + opcode_table[0x21] = &CPU::op_ld_rr_nn; + opcode_table[0x22] = &CPU::op_ldi_hl_a; + opcode_table[0x23] = &CPU::op_inc_rr; + opcode_table[0x24] = &CPU::op_inc_r; + opcode_table[0x25] = &CPU::op_dec_r; + opcode_table[0x26] = &CPU::op_ld_r_n; + opcode_table[0x27] = &CPU::op_daa; + opcode_table[0x28] = &CPU::op_jr_f_n; + opcode_table[0x29] = &CPU::op_add_hl_rr; + opcode_table[0x2a] = &CPU::op_ldi_a_hl; + opcode_table[0x2b] = &CPU::op_dec_rr; + opcode_table[0x2c] = &CPU::op_inc_r; + opcode_table[0x2d] = &CPU::op_dec_r; + opcode_table[0x2e] = &CPU::op_ld_r_n; + opcode_table[0x2f] = &CPU::op_cpl; + opcode_table[0x30] = &CPU::op_jr_f_n; + opcode_table[0x31] = &CPU::op_ld_rr_nn; + opcode_table[0x32] = &CPU::op_ldd_hl_a; + opcode_table[0x33] = &CPU::op_inc_rr; + opcode_table[0x34] = &CPU::op_inc_hl; + opcode_table[0x35] = &CPU::op_dec_hl; + opcode_table[0x36] = &CPU::op_ld_hl_n; + opcode_table[0x37] = &CPU::op_scf; + opcode_table[0x38] = &CPU::op_jr_f_n; + opcode_table[0x39] = &CPU::op_add_hl_rr; + opcode_table[0x3a] = &CPU::op_ldd_a_hl; + opcode_table[0x3b] = &CPU::op_dec_rr; + opcode_table[0x3c] = &CPU::op_inc_r; + opcode_table[0x3d] = &CPU::op_dec_r; + opcode_table[0x3e] = &CPU::op_ld_r_n; + opcode_table[0x3f] = &CPU::op_ccf; + opcode_table[0x40] = &CPU::op_ld_r_r; + opcode_table[0x41] = &CPU::op_ld_r_r; + opcode_table[0x42] = &CPU::op_ld_r_r; + opcode_table[0x43] = &CPU::op_ld_r_r; + opcode_table[0x44] = &CPU::op_ld_r_r; + opcode_table[0x45] = &CPU::op_ld_r_r; + opcode_table[0x46] = &CPU::op_ld_r_hl; + opcode_table[0x47] = &CPU::op_ld_r_r; + opcode_table[0x48] = &CPU::op_ld_r_r; + opcode_table[0x49] = &CPU::op_ld_r_r; + opcode_table[0x4a] = &CPU::op_ld_r_r; + opcode_table[0x4b] = &CPU::op_ld_r_r; + opcode_table[0x4c] = &CPU::op_ld_r_r; + opcode_table[0x4d] = &CPU::op_ld_r_r; + opcode_table[0x4e] = &CPU::op_ld_r_hl; + opcode_table[0x4f] = &CPU::op_ld_r_r; + opcode_table[0x50] = &CPU::op_ld_r_r; + opcode_table[0x51] = &CPU::op_ld_r_r; + opcode_table[0x52] = &CPU::op_ld_r_r; + opcode_table[0x53] = &CPU::op_ld_r_r; + opcode_table[0x54] = &CPU::op_ld_r_r; + opcode_table[0x55] = &CPU::op_ld_r_r; + opcode_table[0x56] = &CPU::op_ld_r_hl; + opcode_table[0x57] = &CPU::op_ld_r_r; + opcode_table[0x58] = &CPU::op_ld_r_r; + opcode_table[0x59] = &CPU::op_ld_r_r; + opcode_table[0x5a] = &CPU::op_ld_r_r; + opcode_table[0x5b] = &CPU::op_ld_r_r; + opcode_table[0x5c] = &CPU::op_ld_r_r; + opcode_table[0x5d] = &CPU::op_ld_r_r; + opcode_table[0x5e] = &CPU::op_ld_r_hl; + opcode_table[0x5f] = &CPU::op_ld_r_r; + opcode_table[0x60] = &CPU::op_ld_r_r; + opcode_table[0x61] = &CPU::op_ld_r_r; + opcode_table[0x62] = &CPU::op_ld_r_r; + opcode_table[0x63] = &CPU::op_ld_r_r; + opcode_table[0x64] = &CPU::op_ld_r_r; + opcode_table[0x65] = &CPU::op_ld_r_r; + opcode_table[0x66] = &CPU::op_ld_r_hl; + opcode_table[0x67] = &CPU::op_ld_r_r; + opcode_table[0x68] = &CPU::op_ld_r_r; + opcode_table[0x69] = &CPU::op_ld_r_r; + opcode_table[0x6a] = &CPU::op_ld_r_r; + opcode_table[0x6b] = &CPU::op_ld_r_r; + opcode_table[0x6c] = &CPU::op_ld_r_r; + opcode_table[0x6d] = &CPU::op_ld_r_r; + opcode_table[0x6e] = &CPU::op_ld_r_hl; + opcode_table[0x6f] = &CPU::op_ld_r_r; + opcode_table[0x70] = &CPU::op_ld_hl_r; + opcode_table[0x71] = &CPU::op_ld_hl_r; + opcode_table[0x72] = &CPU::op_ld_hl_r; + opcode_table[0x73] = &CPU::op_ld_hl_r; + opcode_table[0x74] = &CPU::op_ld_hl_r; + opcode_table[0x75] = &CPU::op_ld_hl_r; + opcode_table[0x76] = &CPU::op_halt; + opcode_table[0x77] = &CPU::op_ld_hl_r; + opcode_table[0x78] = &CPU::op_ld_r_r; + opcode_table[0x79] = &CPU::op_ld_r_r; + opcode_table[0x7a] = &CPU::op_ld_r_r; + opcode_table[0x7b] = &CPU::op_ld_r_r; + opcode_table[0x7c] = &CPU::op_ld_r_r; + opcode_table[0x7d] = &CPU::op_ld_r_r; + opcode_table[0x7e] = &CPU::op_ld_r_hl; + opcode_table[0x7f] = &CPU::op_ld_r_r; + opcode_table[0x80] = &CPU::op_add_a_r; + opcode_table[0x81] = &CPU::op_add_a_r; + opcode_table[0x82] = &CPU::op_add_a_r; + opcode_table[0x83] = &CPU::op_add_a_r; + opcode_table[0x84] = &CPU::op_add_a_r; + opcode_table[0x85] = &CPU::op_add_a_r; + opcode_table[0x86] = &CPU::op_add_a_hl; + opcode_table[0x87] = &CPU::op_add_a_r; + opcode_table[0x88] = &CPU::op_adc_a_r; + opcode_table[0x89] = &CPU::op_adc_a_r; + opcode_table[0x8a] = &CPU::op_adc_a_r; + opcode_table[0x8b] = &CPU::op_adc_a_r; + opcode_table[0x8c] = &CPU::op_adc_a_r; + opcode_table[0x8d] = &CPU::op_adc_a_r; + opcode_table[0x8e] = &CPU::op_adc_a_hl; + opcode_table[0x8f] = &CPU::op_adc_a_r; + opcode_table[0x90] = &CPU::op_sub_a_r; + opcode_table[0x91] = &CPU::op_sub_a_r; + opcode_table[0x92] = &CPU::op_sub_a_r; + opcode_table[0x93] = &CPU::op_sub_a_r; + opcode_table[0x94] = &CPU::op_sub_a_r; + opcode_table[0x95] = &CPU::op_sub_a_r; + opcode_table[0x96] = &CPU::op_sub_a_hl; + opcode_table[0x97] = &CPU::op_sub_a_r; + opcode_table[0x98] = &CPU::op_sbc_a_r; + opcode_table[0x99] = &CPU::op_sbc_a_r; + opcode_table[0x9a] = &CPU::op_sbc_a_r; + opcode_table[0x9b] = &CPU::op_sbc_a_r; + opcode_table[0x9c] = &CPU::op_sbc_a_r; + opcode_table[0x9d] = &CPU::op_sbc_a_r; + opcode_table[0x9e] = &CPU::op_sbc_a_hl; + opcode_table[0x9f] = &CPU::op_sbc_a_r; + opcode_table[0xa0] = &CPU::op_and_a_r; + opcode_table[0xa1] = &CPU::op_and_a_r; + opcode_table[0xa2] = &CPU::op_and_a_r; + opcode_table[0xa3] = &CPU::op_and_a_r; + opcode_table[0xa4] = &CPU::op_and_a_r; + opcode_table[0xa5] = &CPU::op_and_a_r; + opcode_table[0xa6] = &CPU::op_and_a_hl; + opcode_table[0xa7] = &CPU::op_and_a_r; + opcode_table[0xa8] = &CPU::op_xor_a_r; + opcode_table[0xa9] = &CPU::op_xor_a_r; + opcode_table[0xaa] = &CPU::op_xor_a_r; + opcode_table[0xab] = &CPU::op_xor_a_r; + opcode_table[0xac] = &CPU::op_xor_a_r; + opcode_table[0xad] = &CPU::op_xor_a_r; + opcode_table[0xae] = &CPU::op_xor_a_hl; + opcode_table[0xaf] = &CPU::op_xor_a_r; + opcode_table[0xb0] = &CPU::op_or_a_r; + opcode_table[0xb1] = &CPU::op_or_a_r; + opcode_table[0xb2] = &CPU::op_or_a_r; + opcode_table[0xb3] = &CPU::op_or_a_r; + opcode_table[0xb4] = &CPU::op_or_a_r; + opcode_table[0xb5] = &CPU::op_or_a_r; + opcode_table[0xb6] = &CPU::op_or_a_hl; + opcode_table[0xb7] = &CPU::op_or_a_r; + opcode_table[0xb8] = &CPU::op_cp_a_r; + opcode_table[0xb9] = &CPU::op_cp_a_r; + opcode_table[0xba] = &CPU::op_cp_a_r; + opcode_table[0xbb] = &CPU::op_cp_a_r; + opcode_table[0xbc] = &CPU::op_cp_a_r; + opcode_table[0xbd] = &CPU::op_cp_a_r; + opcode_table[0xbe] = &CPU::op_cp_a_hl; + opcode_table[0xbf] = &CPU::op_cp_a_r; + opcode_table[0xc0] = &CPU::op_ret_f; + opcode_table[0xc1] = &CPU::op_pop_rr; + opcode_table[0xc2] = &CPU::op_jp_f_nn; + opcode_table[0xc3] = &CPU::op_jp_nn; + opcode_table[0xc4] = &CPU::op_call_f_nn; + opcode_table[0xc5] = &CPU::op_push_rr; + opcode_table[0xc6] = &CPU::op_add_a_n; + opcode_table[0xc7] = &CPU::op_rst_n<0x00>; + opcode_table[0xc8] = &CPU::op_ret_f; + opcode_table[0xc9] = &CPU::op_ret; + opcode_table[0xca] = &CPU::op_jp_f_nn; + opcode_table[0xcb] = &CPU::op_cb; + opcode_table[0xcc] = &CPU::op_call_f_nn; + opcode_table[0xcd] = &CPU::op_call_nn; + opcode_table[0xce] = &CPU::op_adc_a_n; + opcode_table[0xcf] = &CPU::op_rst_n<0x08>; + opcode_table[0xd0] = &CPU::op_ret_f; + opcode_table[0xd1] = &CPU::op_pop_rr; + opcode_table[0xd2] = &CPU::op_jp_f_nn; + opcode_table[0xd3] = &CPU::op_xx; + opcode_table[0xd4] = &CPU::op_call_f_nn; + opcode_table[0xd5] = &CPU::op_push_rr; + opcode_table[0xd6] = &CPU::op_sub_a_n; + opcode_table[0xd7] = &CPU::op_rst_n<0x10>; + opcode_table[0xd8] = &CPU::op_ret_f; + opcode_table[0xd9] = &CPU::op_reti; + opcode_table[0xda] = &CPU::op_jp_f_nn; + opcode_table[0xdb] = &CPU::op_xx; + opcode_table[0xdc] = &CPU::op_call_f_nn; + opcode_table[0xdd] = &CPU::op_xx; + opcode_table[0xde] = &CPU::op_sbc_a_n; + opcode_table[0xdf] = &CPU::op_rst_n<0x18>; + opcode_table[0xe0] = &CPU::op_ld_ffn_a; + opcode_table[0xe1] = &CPU::op_pop_rr; + opcode_table[0xe2] = &CPU::op_ld_ffc_a; + opcode_table[0xe3] = &CPU::op_xx; + opcode_table[0xe4] = &CPU::op_xx; + opcode_table[0xe5] = &CPU::op_push_rr; + opcode_table[0xe6] = &CPU::op_and_a_n; + opcode_table[0xe7] = &CPU::op_rst_n<0x20>; + opcode_table[0xe8] = &CPU::op_add_sp_n; + opcode_table[0xe9] = &CPU::op_jp_hl; + opcode_table[0xea] = &CPU::op_ld_nn_a; + opcode_table[0xeb] = &CPU::op_xx; + opcode_table[0xec] = &CPU::op_xx; + opcode_table[0xed] = &CPU::op_xx; + opcode_table[0xee] = &CPU::op_xor_a_n; + opcode_table[0xef] = &CPU::op_rst_n<0x28>; + opcode_table[0xf0] = &CPU::op_ld_a_ffn; + opcode_table[0xf1] = &CPU::op_pop_rr; + opcode_table[0xf2] = &CPU::op_xx; + opcode_table[0xf3] = &CPU::op_di; + opcode_table[0xf4] = &CPU::op_xx; + opcode_table[0xf5] = &CPU::op_push_rr; + opcode_table[0xf6] = &CPU::op_or_a_n; + opcode_table[0xf7] = &CPU::op_rst_n<0x30>; + opcode_table[0xf8] = &CPU::op_ld_hl_sp_n; + opcode_table[0xf9] = &CPU::op_ld_sp_hl; + opcode_table[0xfa] = &CPU::op_ld_a_nn; + opcode_table[0xfb] = &CPU::op_ei; + opcode_table[0xfc] = &CPU::op_xx; + opcode_table[0xfd] = &CPU::op_xx; + opcode_table[0xfe] = &CPU::op_cp_a_n; + opcode_table[0xff] = &CPU::op_rst_n<0x38>; + + opcode_table_cb[0x00] = &CPU::op_rlc_r; + opcode_table_cb[0x01] = &CPU::op_rlc_r; + opcode_table_cb[0x02] = &CPU::op_rlc_r; + opcode_table_cb[0x03] = &CPU::op_rlc_r; + opcode_table_cb[0x04] = &CPU::op_rlc_r; + opcode_table_cb[0x05] = &CPU::op_rlc_r; + opcode_table_cb[0x06] = &CPU::op_rlc_hl; + opcode_table_cb[0x07] = &CPU::op_rlc_r; + opcode_table_cb[0x08] = &CPU::op_rrc_r; + opcode_table_cb[0x09] = &CPU::op_rrc_r; + opcode_table_cb[0x0a] = &CPU::op_rrc_r; + opcode_table_cb[0x0b] = &CPU::op_rrc_r; + opcode_table_cb[0x0c] = &CPU::op_rrc_r; + opcode_table_cb[0x0d] = &CPU::op_rrc_r; + opcode_table_cb[0x0e] = &CPU::op_rrc_hl; + opcode_table_cb[0x0f] = &CPU::op_rrc_r; + opcode_table_cb[0x10] = &CPU::op_rl_r; + opcode_table_cb[0x11] = &CPU::op_rl_r; + opcode_table_cb[0x12] = &CPU::op_rl_r; + opcode_table_cb[0x13] = &CPU::op_rl_r; + opcode_table_cb[0x14] = &CPU::op_rl_r; + opcode_table_cb[0x15] = &CPU::op_rl_r; + opcode_table_cb[0x16] = &CPU::op_rl_hl; + opcode_table_cb[0x17] = &CPU::op_rl_r; + opcode_table_cb[0x18] = &CPU::op_rr_r; + opcode_table_cb[0x19] = &CPU::op_rr_r; + opcode_table_cb[0x1a] = &CPU::op_rr_r; + opcode_table_cb[0x1b] = &CPU::op_rr_r; + opcode_table_cb[0x1c] = &CPU::op_rr_r; + opcode_table_cb[0x1d] = &CPU::op_rr_r; + opcode_table_cb[0x1e] = &CPU::op_rr_hl; + opcode_table_cb[0x1f] = &CPU::op_rr_r; + opcode_table_cb[0x20] = &CPU::op_sla_r; + opcode_table_cb[0x21] = &CPU::op_sla_r; + opcode_table_cb[0x22] = &CPU::op_sla_r; + opcode_table_cb[0x23] = &CPU::op_sla_r; + opcode_table_cb[0x24] = &CPU::op_sla_r; + opcode_table_cb[0x25] = &CPU::op_sla_r; + opcode_table_cb[0x26] = &CPU::op_sla_hl; + opcode_table_cb[0x27] = &CPU::op_sla_r; + opcode_table_cb[0x28] = &CPU::op_sra_r; + opcode_table_cb[0x29] = &CPU::op_sra_r; + opcode_table_cb[0x2a] = &CPU::op_sra_r; + opcode_table_cb[0x2b] = &CPU::op_sra_r; + opcode_table_cb[0x2c] = &CPU::op_sra_r; + opcode_table_cb[0x2d] = &CPU::op_sra_r; + opcode_table_cb[0x2e] = &CPU::op_sra_hl; + opcode_table_cb[0x2f] = &CPU::op_sra_r; + opcode_table_cb[0x30] = &CPU::op_swap_r; + opcode_table_cb[0x31] = &CPU::op_swap_r; + opcode_table_cb[0x32] = &CPU::op_swap_r; + opcode_table_cb[0x33] = &CPU::op_swap_r; + opcode_table_cb[0x34] = &CPU::op_swap_r; + opcode_table_cb[0x35] = &CPU::op_swap_r; + opcode_table_cb[0x36] = &CPU::op_swap_hl; + opcode_table_cb[0x37] = &CPU::op_swap_r; + opcode_table_cb[0x38] = &CPU::op_srl_r; + opcode_table_cb[0x39] = &CPU::op_srl_r; + opcode_table_cb[0x3a] = &CPU::op_srl_r; + opcode_table_cb[0x3b] = &CPU::op_srl_r; + opcode_table_cb[0x3c] = &CPU::op_srl_r; + opcode_table_cb[0x3d] = &CPU::op_srl_r; + opcode_table_cb[0x3e] = &CPU::op_srl_hl; + opcode_table_cb[0x3f] = &CPU::op_srl_r; + opcode_table_cb[0x40] = &CPU::op_bit_n_r<0, B>; + opcode_table_cb[0x41] = &CPU::op_bit_n_r<0, C>; + opcode_table_cb[0x42] = &CPU::op_bit_n_r<0, D>; + opcode_table_cb[0x43] = &CPU::op_bit_n_r<0, E>; + opcode_table_cb[0x44] = &CPU::op_bit_n_r<0, H>; + opcode_table_cb[0x45] = &CPU::op_bit_n_r<0, L>; + opcode_table_cb[0x46] = &CPU::op_bit_n_hl<0>; + opcode_table_cb[0x47] = &CPU::op_bit_n_r<0, A>; + opcode_table_cb[0x48] = &CPU::op_bit_n_r<1, B>; + opcode_table_cb[0x49] = &CPU::op_bit_n_r<1, C>; + opcode_table_cb[0x4a] = &CPU::op_bit_n_r<1, D>; + opcode_table_cb[0x4b] = &CPU::op_bit_n_r<1, E>; + opcode_table_cb[0x4c] = &CPU::op_bit_n_r<1, H>; + opcode_table_cb[0x4d] = &CPU::op_bit_n_r<1, L>; + opcode_table_cb[0x4e] = &CPU::op_bit_n_hl<1>; + opcode_table_cb[0x4f] = &CPU::op_bit_n_r<1, A>; + opcode_table_cb[0x50] = &CPU::op_bit_n_r<2, B>; + opcode_table_cb[0x51] = &CPU::op_bit_n_r<2, C>; + opcode_table_cb[0x52] = &CPU::op_bit_n_r<2, D>; + opcode_table_cb[0x53] = &CPU::op_bit_n_r<2, E>; + opcode_table_cb[0x54] = &CPU::op_bit_n_r<2, H>; + opcode_table_cb[0x55] = &CPU::op_bit_n_r<2, L>; + opcode_table_cb[0x56] = &CPU::op_bit_n_hl<2>; + opcode_table_cb[0x57] = &CPU::op_bit_n_r<2, A>; + opcode_table_cb[0x58] = &CPU::op_bit_n_r<3, B>; + opcode_table_cb[0x59] = &CPU::op_bit_n_r<3, C>; + opcode_table_cb[0x5a] = &CPU::op_bit_n_r<3, D>; + opcode_table_cb[0x5b] = &CPU::op_bit_n_r<3, E>; + opcode_table_cb[0x5c] = &CPU::op_bit_n_r<3, H>; + opcode_table_cb[0x5d] = &CPU::op_bit_n_r<3, L>; + opcode_table_cb[0x5e] = &CPU::op_bit_n_hl<3>; + opcode_table_cb[0x5f] = &CPU::op_bit_n_r<3, A>; + opcode_table_cb[0x60] = &CPU::op_bit_n_r<4, B>; + opcode_table_cb[0x61] = &CPU::op_bit_n_r<4, C>; + opcode_table_cb[0x62] = &CPU::op_bit_n_r<4, D>; + opcode_table_cb[0x63] = &CPU::op_bit_n_r<4, E>; + opcode_table_cb[0x64] = &CPU::op_bit_n_r<4, H>; + opcode_table_cb[0x65] = &CPU::op_bit_n_r<4, L>; + opcode_table_cb[0x66] = &CPU::op_bit_n_hl<4>; + opcode_table_cb[0x67] = &CPU::op_bit_n_r<4, A>; + opcode_table_cb[0x68] = &CPU::op_bit_n_r<5, B>; + opcode_table_cb[0x69] = &CPU::op_bit_n_r<5, C>; + opcode_table_cb[0x6a] = &CPU::op_bit_n_r<5, D>; + opcode_table_cb[0x6b] = &CPU::op_bit_n_r<5, E>; + opcode_table_cb[0x6c] = &CPU::op_bit_n_r<5, H>; + opcode_table_cb[0x6d] = &CPU::op_bit_n_r<5, L>; + opcode_table_cb[0x6e] = &CPU::op_bit_n_hl<5>; + opcode_table_cb[0x6f] = &CPU::op_bit_n_r<5, A>; + opcode_table_cb[0x70] = &CPU::op_bit_n_r<6, B>; + opcode_table_cb[0x71] = &CPU::op_bit_n_r<6, C>; + opcode_table_cb[0x72] = &CPU::op_bit_n_r<6, D>; + opcode_table_cb[0x73] = &CPU::op_bit_n_r<6, E>; + opcode_table_cb[0x74] = &CPU::op_bit_n_r<6, H>; + opcode_table_cb[0x75] = &CPU::op_bit_n_r<6, L>; + opcode_table_cb[0x76] = &CPU::op_bit_n_hl<6>; + opcode_table_cb[0x77] = &CPU::op_bit_n_r<6, A>; + opcode_table_cb[0x78] = &CPU::op_bit_n_r<7, B>; + opcode_table_cb[0x79] = &CPU::op_bit_n_r<7, C>; + opcode_table_cb[0x7a] = &CPU::op_bit_n_r<7, D>; + opcode_table_cb[0x7b] = &CPU::op_bit_n_r<7, E>; + opcode_table_cb[0x7c] = &CPU::op_bit_n_r<7, H>; + opcode_table_cb[0x7d] = &CPU::op_bit_n_r<7, L>; + opcode_table_cb[0x7e] = &CPU::op_bit_n_hl<7>; + opcode_table_cb[0x7f] = &CPU::op_bit_n_r<7, A>; + opcode_table_cb[0x80] = &CPU::op_res_n_r<0, B>; + opcode_table_cb[0x81] = &CPU::op_res_n_r<0, C>; + opcode_table_cb[0x82] = &CPU::op_res_n_r<0, D>; + opcode_table_cb[0x83] = &CPU::op_res_n_r<0, E>; + opcode_table_cb[0x84] = &CPU::op_res_n_r<0, H>; + opcode_table_cb[0x85] = &CPU::op_res_n_r<0, L>; + opcode_table_cb[0x86] = &CPU::op_res_n_hl<0>; + opcode_table_cb[0x87] = &CPU::op_res_n_r<0, A>; + opcode_table_cb[0x88] = &CPU::op_res_n_r<1, B>; + opcode_table_cb[0x89] = &CPU::op_res_n_r<1, C>; + opcode_table_cb[0x8a] = &CPU::op_res_n_r<1, D>; + opcode_table_cb[0x8b] = &CPU::op_res_n_r<1, E>; + opcode_table_cb[0x8c] = &CPU::op_res_n_r<1, H>; + opcode_table_cb[0x8d] = &CPU::op_res_n_r<1, L>; + opcode_table_cb[0x8e] = &CPU::op_res_n_hl<1>; + opcode_table_cb[0x8f] = &CPU::op_res_n_r<1, A>; + opcode_table_cb[0x90] = &CPU::op_res_n_r<2, B>; + opcode_table_cb[0x91] = &CPU::op_res_n_r<2, C>; + opcode_table_cb[0x92] = &CPU::op_res_n_r<2, D>; + opcode_table_cb[0x93] = &CPU::op_res_n_r<2, E>; + opcode_table_cb[0x94] = &CPU::op_res_n_r<2, H>; + opcode_table_cb[0x95] = &CPU::op_res_n_r<2, L>; + opcode_table_cb[0x96] = &CPU::op_res_n_hl<2>; + opcode_table_cb[0x97] = &CPU::op_res_n_r<2, A>; + opcode_table_cb[0x98] = &CPU::op_res_n_r<3, B>; + opcode_table_cb[0x99] = &CPU::op_res_n_r<3, C>; + opcode_table_cb[0x9a] = &CPU::op_res_n_r<3, D>; + opcode_table_cb[0x9b] = &CPU::op_res_n_r<3, E>; + opcode_table_cb[0x9c] = &CPU::op_res_n_r<3, H>; + opcode_table_cb[0x9d] = &CPU::op_res_n_r<3, L>; + opcode_table_cb[0x9e] = &CPU::op_res_n_hl<3>; + opcode_table_cb[0x9f] = &CPU::op_res_n_r<3, A>; + opcode_table_cb[0xa0] = &CPU::op_res_n_r<4, B>; + opcode_table_cb[0xa1] = &CPU::op_res_n_r<4, C>; + opcode_table_cb[0xa2] = &CPU::op_res_n_r<4, D>; + opcode_table_cb[0xa3] = &CPU::op_res_n_r<4, E>; + opcode_table_cb[0xa4] = &CPU::op_res_n_r<4, H>; + opcode_table_cb[0xa5] = &CPU::op_res_n_r<4, L>; + opcode_table_cb[0xa6] = &CPU::op_res_n_hl<4>; + opcode_table_cb[0xa7] = &CPU::op_res_n_r<4, A>; + opcode_table_cb[0xa8] = &CPU::op_res_n_r<5, B>; + opcode_table_cb[0xa9] = &CPU::op_res_n_r<5, C>; + opcode_table_cb[0xaa] = &CPU::op_res_n_r<5, D>; + opcode_table_cb[0xab] = &CPU::op_res_n_r<5, E>; + opcode_table_cb[0xac] = &CPU::op_res_n_r<5, H>; + opcode_table_cb[0xad] = &CPU::op_res_n_r<5, L>; + opcode_table_cb[0xae] = &CPU::op_res_n_hl<5>; + opcode_table_cb[0xaf] = &CPU::op_res_n_r<5, A>; + opcode_table_cb[0xb0] = &CPU::op_res_n_r<6, B>; + opcode_table_cb[0xb1] = &CPU::op_res_n_r<6, C>; + opcode_table_cb[0xb2] = &CPU::op_res_n_r<6, D>; + opcode_table_cb[0xb3] = &CPU::op_res_n_r<6, E>; + opcode_table_cb[0xb4] = &CPU::op_res_n_r<6, H>; + opcode_table_cb[0xb5] = &CPU::op_res_n_r<6, L>; + opcode_table_cb[0xb6] = &CPU::op_res_n_hl<6>; + opcode_table_cb[0xb7] = &CPU::op_res_n_r<6, A>; + opcode_table_cb[0xb8] = &CPU::op_res_n_r<7, B>; + opcode_table_cb[0xb9] = &CPU::op_res_n_r<7, C>; + opcode_table_cb[0xba] = &CPU::op_res_n_r<7, D>; + opcode_table_cb[0xbb] = &CPU::op_res_n_r<7, E>; + opcode_table_cb[0xbc] = &CPU::op_res_n_r<7, H>; + opcode_table_cb[0xbd] = &CPU::op_res_n_r<7, L>; + opcode_table_cb[0xbe] = &CPU::op_res_n_hl<7>; + opcode_table_cb[0xbf] = &CPU::op_res_n_r<7, A>; + opcode_table_cb[0xc0] = &CPU::op_set_n_r<0, B>; + opcode_table_cb[0xc1] = &CPU::op_set_n_r<0, C>; + opcode_table_cb[0xc2] = &CPU::op_set_n_r<0, D>; + opcode_table_cb[0xc3] = &CPU::op_set_n_r<0, E>; + opcode_table_cb[0xc4] = &CPU::op_set_n_r<0, H>; + opcode_table_cb[0xc5] = &CPU::op_set_n_r<0, L>; + opcode_table_cb[0xc6] = &CPU::op_set_n_hl<0>; + opcode_table_cb[0xc7] = &CPU::op_set_n_r<0, A>; + opcode_table_cb[0xc8] = &CPU::op_set_n_r<1, B>; + opcode_table_cb[0xc9] = &CPU::op_set_n_r<1, C>; + opcode_table_cb[0xca] = &CPU::op_set_n_r<1, D>; + opcode_table_cb[0xcb] = &CPU::op_set_n_r<1, E>; + opcode_table_cb[0xcc] = &CPU::op_set_n_r<1, H>; + opcode_table_cb[0xcd] = &CPU::op_set_n_r<1, L>; + opcode_table_cb[0xce] = &CPU::op_set_n_hl<1>; + opcode_table_cb[0xcf] = &CPU::op_set_n_r<1, A>; + opcode_table_cb[0xd0] = &CPU::op_set_n_r<2, B>; + opcode_table_cb[0xd1] = &CPU::op_set_n_r<2, C>; + opcode_table_cb[0xd2] = &CPU::op_set_n_r<2, D>; + opcode_table_cb[0xd3] = &CPU::op_set_n_r<2, E>; + opcode_table_cb[0xd4] = &CPU::op_set_n_r<2, H>; + opcode_table_cb[0xd5] = &CPU::op_set_n_r<2, L>; + opcode_table_cb[0xd6] = &CPU::op_set_n_hl<2>; + opcode_table_cb[0xd7] = &CPU::op_set_n_r<2, A>; + opcode_table_cb[0xd8] = &CPU::op_set_n_r<3, B>; + opcode_table_cb[0xd9] = &CPU::op_set_n_r<3, C>; + opcode_table_cb[0xda] = &CPU::op_set_n_r<3, D>; + opcode_table_cb[0xdb] = &CPU::op_set_n_r<3, E>; + opcode_table_cb[0xdc] = &CPU::op_set_n_r<3, H>; + opcode_table_cb[0xdd] = &CPU::op_set_n_r<3, L>; + opcode_table_cb[0xde] = &CPU::op_set_n_hl<3>; + opcode_table_cb[0xdf] = &CPU::op_set_n_r<3, A>; + opcode_table_cb[0xe0] = &CPU::op_set_n_r<4, B>; + opcode_table_cb[0xe1] = &CPU::op_set_n_r<4, C>; + opcode_table_cb[0xe2] = &CPU::op_set_n_r<4, D>; + opcode_table_cb[0xe3] = &CPU::op_set_n_r<4, E>; + opcode_table_cb[0xe4] = &CPU::op_set_n_r<4, H>; + opcode_table_cb[0xe5] = &CPU::op_set_n_r<4, L>; + opcode_table_cb[0xe6] = &CPU::op_set_n_hl<4>; + opcode_table_cb[0xe7] = &CPU::op_set_n_r<4, A>; + opcode_table_cb[0xe8] = &CPU::op_set_n_r<5, B>; + opcode_table_cb[0xe9] = &CPU::op_set_n_r<5, C>; + opcode_table_cb[0xea] = &CPU::op_set_n_r<5, D>; + opcode_table_cb[0xeb] = &CPU::op_set_n_r<5, E>; + opcode_table_cb[0xec] = &CPU::op_set_n_r<5, H>; + opcode_table_cb[0xed] = &CPU::op_set_n_r<5, L>; + opcode_table_cb[0xee] = &CPU::op_set_n_hl<5>; + opcode_table_cb[0xef] = &CPU::op_set_n_r<5, A>; + opcode_table_cb[0xf0] = &CPU::op_set_n_r<6, B>; + opcode_table_cb[0xf1] = &CPU::op_set_n_r<6, C>; + opcode_table_cb[0xf2] = &CPU::op_set_n_r<6, D>; + opcode_table_cb[0xf3] = &CPU::op_set_n_r<6, E>; + opcode_table_cb[0xf4] = &CPU::op_set_n_r<6, H>; + opcode_table_cb[0xf5] = &CPU::op_set_n_r<6, L>; + opcode_table_cb[0xf6] = &CPU::op_set_n_hl<6>; + opcode_table_cb[0xf7] = &CPU::op_set_n_r<6, A>; + opcode_table_cb[0xf8] = &CPU::op_set_n_r<7, B>; + opcode_table_cb[0xf9] = &CPU::op_set_n_r<7, C>; + opcode_table_cb[0xfa] = &CPU::op_set_n_r<7, D>; + opcode_table_cb[0xfb] = &CPU::op_set_n_r<7, E>; + opcode_table_cb[0xfc] = &CPU::op_set_n_r<7, H>; + opcode_table_cb[0xfd] = &CPU::op_set_n_r<7, L>; + opcode_table_cb[0xfe] = &CPU::op_set_n_hl<7>; + opcode_table_cb[0xff] = &CPU::op_set_n_r<7, A>; +} + +#endif diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp index f0c09f6e..0589f0a4 100755 --- a/gameboy/cpu/cpu.cpp +++ b/gameboy/cpu/cpu.cpp @@ -14,12 +14,9 @@ void CPU::Main() { void CPU::main() { while(true) { - print(disassemble(r[PC]), "\n"); - + //print(disassemble(r[PC]), "\n"); uint8 opcode = op_read(r[PC]++); (this->*opcode_table[opcode])(); - - opcode_counter++; } } diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp index c5c229b3..e6ba73a5 100755 --- a/gameboy/gameboy.hpp +++ b/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000.02"; + static const char Version[] = "000.03"; } } diff --git a/ui/main.cpp b/ui/main.cpp index 8aace5a4..87deb7f4 100755 --- a/ui/main.cpp +++ b/ui/main.cpp @@ -14,11 +14,22 @@ void Application::main(int argc, char **argv) { GameBoy::system.init(&interface); + unsigned frameCounter = 0; + time_t timeCounter = time(0); + while(quit == false) { OS::run(); if(GameBoy::cartridge.loaded()) { GameBoy::system.run(); + + frameCounter++; + time_t currentTime = time(0); + if(currentTime != timeCounter) { + timeCounter = currentTime; + mainWindow.setStatusText({ "FPS: ", frameCounter }); + frameCounter = 0; + } } } } From 2330ed6e8c1459a7409f47bf5d42c2475618b746 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 30 Dec 2010 18:18:47 +1100 Subject: [PATCH 06/12] Update to release v000r04. byuu says: [screenshot of the Tetris title screen] Less than fourty-eight hours after starting on this. I guess you weren't kidding, Exophase. --- gameboy/cpu/core/core.cpp | 4 +- gameboy/cpu/cpu.cpp | 72 ++++++++++++++++- gameboy/cpu/cpu.hpp | 37 +++++++++ gameboy/cpu/mmio/mmio.cpp | 110 ++++++++++++++++++++++++++ gameboy/cpu/timing/timing.cpp | 62 ++++++++++++--- gameboy/cpu/timing/timing.hpp | 6 +- gameboy/gameboy.hpp | 2 +- gameboy/lcd/lcd.cpp | 108 ++++++++++++++++++++++++- gameboy/lcd/lcd.hpp | 55 ++++++++++++- gameboy/lcd/mmio/mmio.cpp | 145 ++++++++++++++++++++++++++++++++-- gameboy/system/system.hpp | 4 + ui/base.hpp | 7 ++ ui/general/main-window.cpp | 10 ++- ui/interface.cpp | 37 +++++++++ ui/interface.hpp | 2 + ui/main.cpp | 34 +++++--- 16 files changed, 654 insertions(+), 41 deletions(-) diff --git a/gameboy/cpu/core/core.cpp b/gameboy/cpu/core/core.cpp index b1eb86cb..b401ec18 100755 --- a/gameboy/cpu/core/core.cpp +++ b/gameboy/cpu/core/core.cpp @@ -324,7 +324,7 @@ void CPU::op_rlca() { void CPU::op_rla() { bool c = r[A] & 0x80; - r[A] = (r[A] << 1) | (c << 0); + r[A] = (r[A] << 1) | (r.f.c << 0); r.f.z = 0; r.f.n = 0; r.f.h = 0; @@ -341,7 +341,7 @@ void CPU::op_rrca() { void CPU::op_rra() { bool c = r[A] & 0x01; - r[A] = (r[A] >> 1) | (c << 7); + r[A] = (r[A] >> 1) | (r.f.c << 7); r.f.z = 0; r.f.n = 0; r.f.h = 0; diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp index 0589f0a4..5a0bc001 100755 --- a/gameboy/cpu/cpu.cpp +++ b/gameboy/cpu/cpu.cpp @@ -15,15 +15,57 @@ void CPU::Main() { void CPU::main() { while(true) { //print(disassemble(r[PC]), "\n"); + interrupt_test(); uint8 opcode = op_read(r[PC]++); (this->*opcode_table[opcode])(); } } +void CPU::interrupt_test() { + if(status.ime) { + if(status.interrupt_request_vblank && status.interrupt_enable_vblank) { + status.interrupt_request_vblank = 0; + return interrupt_exec(0x0040); + } + + if(status.interrupt_request_stat && status.interrupt_enable_stat) { + status.interrupt_request_stat = 0; + return interrupt_exec(0x0048); + } + + if(status.interrupt_request_timer && status.interrupt_enable_timer) { + status.interrupt_request_timer = 0; + return interrupt_exec(0x0050); + } + + if(status.interrupt_request_serial && status.interrupt_enable_serial) { + status.interrupt_request_serial = 0; + return interrupt_exec(0x0058); + } + + if(status.interrupt_request_joypad && status.interrupt_enable_joypad) { + status.interrupt_request_joypad = 0; + return interrupt_exec(0x0060); + } + } +} + +void CPU::interrupt_exec(uint16 pc) { + status.ime = 0; + op_write(--r[SP], r[PC] >> 8); + op_write(--r[SP], r[PC] >> 0); + r[PC] = pc; + op_io(); +} + void CPU::power() { for(unsigned n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM for(unsigned n = 0xe000; n <= 0xfdff; n++) bus.mmio[n] = this; //WRAM (mirror) - for(unsigned n = 0xff80; n <= 0xfffe; n++) bus.mmio[n] = this; //HRAM + for(unsigned n = 0xff00; n <= 0xff0f; n++) bus.mmio[n] = this; //MMIO + for(unsigned n = 0xff80; n <= 0xffff; n++) bus.mmio[n] = this; //HRAM+IE + + for(unsigned n = 0; n < 8192; n++) wram[n] = 0x00; + for(unsigned n = 0; n < 128; n++) hram[n] = 0x00; reset(); } @@ -39,6 +81,34 @@ void CPU::reset() { r[HL] = 0x0000; status.ime = 0; + status.timer0 = 0; + status.timer1 = 0; + status.timer2 = 0; + status.timer3 = 0; + + status.p15 = 0; + status.p14 = 0; + + status.div = 0; + + status.tima = 0; + + status.tma = 0; + + status.timer_enable = 0; + status.timer_clock = 0; + + status.interrupt_request_joypad = 0; + status.interrupt_request_serial = 0; + status.interrupt_request_timer = 0; + status.interrupt_request_stat = 0; + status.interrupt_request_vblank = 0; + + status.interrupt_enable_joypad = 0; + status.interrupt_enable_serial = 0; + status.interrupt_enable_timer = 0; + status.interrupt_enable_stat = 0; + status.interrupt_enable_vblank = 0; } CPU::CPU() { diff --git a/gameboy/cpu/cpu.hpp b/gameboy/cpu/cpu.hpp index d969145a..dc66e90f 100755 --- a/gameboy/cpu/cpu.hpp +++ b/gameboy/cpu/cpu.hpp @@ -5,6 +5,41 @@ struct CPU : Processor, MMIO { struct Status { bool ime; + unsigned timer0; + unsigned timer1; + unsigned timer2; + unsigned timer3; + + //$ff00 JOYP + bool p15; + bool p14; + + //$ff04 DIV + uint8 div; + + //$ff05 TIMA + uint8 tima; + + //$ff06 TMA + uint8 tma; + + //$ff07 TAC + bool timer_enable; + unsigned timer_clock; + + //$ff0f IF + bool interrupt_request_joypad; + bool interrupt_request_serial; + bool interrupt_request_timer; + bool interrupt_request_stat; + bool interrupt_request_vblank; + + //$ffff IE + bool interrupt_enable_joypad; + bool interrupt_enable_serial; + bool interrupt_enable_timer; + bool interrupt_enable_stat; + bool interrupt_enable_vblank; } status; uint8 wram[8192]; @@ -12,6 +47,8 @@ struct CPU : Processor, MMIO { static void Main(); void main(); + void interrupt_test(); + void interrupt_exec(uint16 pc); void power(); void reset(); CPU(); diff --git a/gameboy/cpu/mmio/mmio.cpp b/gameboy/cpu/mmio/mmio.cpp index edb7b8f6..4d6322b0 100755 --- a/gameboy/cpu/mmio/mmio.cpp +++ b/gameboy/cpu/mmio/mmio.cpp @@ -4,12 +4,122 @@ uint8 CPU::mmio_read(uint16 addr) { if(addr >= 0xc000 && addr <= 0xdfff) return wram[addr & 0x1fff]; if(addr >= 0xe000 && addr <= 0xfdff) return wram[addr & 0x1fff]; if(addr >= 0xff80 && addr <= 0xfffe) return hram[addr & 0x7f]; + + if(addr == 0xff00) { //JOYP + unsigned keys = 0x0f; + + if(status.p15 == 0 && status.p14 == 1) { + keys = !system.interface->input_poll((unsigned)Input::Down) << 3; + keys |= !system.interface->input_poll((unsigned)Input::Up) << 2; + keys |= !system.interface->input_poll((unsigned)Input::Left) << 1; + keys |= !system.interface->input_poll((unsigned)Input::Right) << 0; + } + + if(status.p15 == 1 && status.p14 == 0) { + keys = !system.interface->input_poll((unsigned)Input::Start) << 3; + keys |= !system.interface->input_poll((unsigned)Input::Select) << 2; + keys |= !system.interface->input_poll((unsigned)Input::B) << 1; + keys |= !system.interface->input_poll((unsigned)Input::A) << 0; + } + + return (status.p15 << 5) + | (status.p14 << 4) + | (keys << 0); + } + + if(addr == 0xff04) { //DIV + return status.div; + } + + if(addr == 0xff05) { //TIMA + return status.tima; + } + + if(addr == 0xff06) { //TMA + return status.tma; + } + + if(addr == 0xff07) { //TAC + return (status.timer_enable << 2) + | (status.timer_clock << 0); + } + + if(addr == 0xff0f) { //IF + return (status.interrupt_request_joypad << 4) + | (status.interrupt_request_serial << 3) + | (status.interrupt_request_timer << 2) + | (status.interrupt_request_stat << 1) + | (status.interrupt_request_vblank << 0); + } + + if(addr == 0xffff) { //IE + return (status.interrupt_enable_joypad << 4) + | (status.interrupt_enable_serial << 3) + | (status.interrupt_enable_timer << 2) + | (status.interrupt_enable_stat << 1) + | (status.interrupt_enable_vblank << 0); + } + + return 0x00; } void CPU::mmio_write(uint16 addr, uint8 data) { if(addr >= 0xc000 && addr <= 0xdfff) { wram[addr & 0x1fff] = data; return; } if(addr >= 0xe000 && addr <= 0xfdff) { wram[addr & 0x1fff] = data; return; } if(addr >= 0xff80 && addr <= 0xfffe) { hram[addr & 0x7f] = data; return; } + + if(addr == 0xff00) { //JOYP + status.p15 = data & 0x20; + status.p14 = data & 0x10; + return; + } + + if(addr == 0xff01) { //SB + return; + } + + if(addr == 0xff02) { //SC + return; + } + + if(addr == 0xff04) { //DIV + status.div = 0; + return; + } + + if(addr == 0xff05) { //TIMA + status.tima = data; + return; + } + + if(addr == 0xff06) { //TMA + status.tma = data; + return; + } + + if(addr == 0xff07) { //TAC + status.timer_enable = data & 0x04; + status.timer_clock = data & 0x03; + return; + } + + if(addr == 0xff0f) { //IF + status.interrupt_request_joypad = data & 0x10; + status.interrupt_request_serial = data & 0x08; + status.interrupt_request_timer = data & 0x04; + status.interrupt_request_stat = data & 0x02; + status.interrupt_request_vblank = data & 0x01; + return; + } + + if(addr == 0xffff) { //IE + status.interrupt_enable_joypad = data & 0x10; + status.interrupt_enable_serial = data & 0x08; + status.interrupt_enable_timer = data & 0x04; + status.interrupt_enable_stat = data & 0x02; + status.interrupt_enable_vblank = data & 0x01; + return; + } } #endif diff --git a/gameboy/cpu/timing/timing.cpp b/gameboy/cpu/timing/timing.cpp index 7f7e8ef9..0d96e62e 100755 --- a/gameboy/cpu/timing/timing.cpp +++ b/gameboy/cpu/timing/timing.cpp @@ -3,26 +3,70 @@ // 456 clocks/scanline // 154 scanlines/frame +//4194304 / 4096 = 1024 +//4194304 / 262144 = 16 +//4194304 / 65536 = 64 +//4394304 / 16384 = 256 + #ifdef CPU_CPP #include "opcode.cpp" void CPU::add_clocks(unsigned clocks) { - clock += clocks; + status.timer0 += clocks; + if(status.timer0 >= 16) timer_stage0(); - if(clock >= 456) scanline(); + cpu.clock += clocks; + if(cpu.clock >= 0) co_switch(scheduler.active_thread = lcd.thread); } -void CPU::scanline() { - clock -= 456; +void CPU::timer_stage0() { //262144hz + if(status.timer_clock == 1) { + if(++status.tima == 0) { + status.tima = status.tma; + status.interrupt_request_timer = 1; + } + } - lcd.status.ly++; - if(lcd.status.ly >= 154) frame(); + status.timer0 -= 16; + if(++status.timer1 >= 4) timer_stage1(); } -void CPU::frame() { - lcd.status.ly -= 154; - scheduler.exit(); +void CPU::timer_stage1() { // 65536hz + if(status.timer_clock == 2) { + if(++status.tima == 0) { + status.tima = status.tma; + status.interrupt_request_timer = 1; + } + } + + status.timer1 -= 4; + if(++status.timer2 >= 4) timer_stage2(); +} + +void CPU::timer_stage2() { // 16384hz + if(status.timer_clock == 3) { + if(++status.tima == 0) { + status.tima = status.tma; + status.interrupt_request_timer = 1; + } + } + + status.div++; + + status.timer2 -= 4; + if(++status.timer3 >= 4) timer_stage3(); +} + +void CPU::timer_stage3() { // 4096hz + if(status.timer_clock == 0) { + if(++status.tima == 0) { + status.tima = status.tma; + status.interrupt_request_timer = 1; + } + } + + status.timer3 -= 4; } #endif diff --git a/gameboy/cpu/timing/timing.hpp b/gameboy/cpu/timing/timing.hpp index 89dbf7d8..7362f02e 100755 --- a/gameboy/cpu/timing/timing.hpp +++ b/gameboy/cpu/timing/timing.hpp @@ -1,6 +1,8 @@ void add_clocks(unsigned clocks); -void scanline(); -void frame(); +void timer_stage0(); +void timer_stage1(); +void timer_stage2(); +void timer_stage3(); //opcode.cpp void op_io(); diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp index e6ba73a5..f51da8c0 100755 --- a/gameboy/gameboy.hpp +++ b/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000.03"; + static const char Version[] = "000.04"; } } diff --git a/gameboy/lcd/lcd.cpp b/gameboy/lcd/lcd.cpp index fef0a795..11686b29 100755 --- a/gameboy/lcd/lcd.cpp +++ b/gameboy/lcd/lcd.cpp @@ -6,16 +6,120 @@ namespace GameBoy { #include "mmio/mmio.cpp" LCD lcd; +void LCD::Main() { + lcd.main(); +} + +void LCD::main() { + while(true) { + add_clocks(4); + } +} + +void LCD::add_clocks(unsigned clocks) { + status.lx += clocks; + if(status.lx >= 456) scanline(); + + cpu.clock -= clocks; + if(cpu.clock <= 0) co_switch(scheduler.active_thread = cpu.thread); +} + +void LCD::scanline() { + status.lx -= 456; + status.ly++; + + if(status.ly == 144) cpu.status.interrupt_request_vblank = 1; +//print("Vblank - ", cpu.status.ime, " - ", cpu.status.interrupt_enable_vblank, "\n"); } + if(status.ly == 154) frame(); + + if(status.ly < 144) render(); +} + +void LCD::frame() { + system.interface->video_refresh(screen); + system.interface->input_poll(); + + status.ly = 0; + scheduler.exit(); +} + +void LCD::render() { + uint8_t *output = screen + status.ly * 160; + uint8 y = status.ly + status.scy; + uint16 tmaddr = (status.bg_tilemap_select == 0 ? 0x1800 : 0x1c00); + tmaddr += (y >> 3) * 32; + tmaddr += (status.scx >> 3); + + for(unsigned t = 0; t < 20; t++) { + unsigned tdaddr; + if(status.bg_tiledata_select == 0) { + tdaddr = 0x1000 + (int8)vram[tmaddr + t] * 16; + } else { + tdaddr = 0x0000 + vram[tmaddr + t] * 16; + } + tdaddr += (status.ly & 7) * 2; + + uint8 d0 = vram[tdaddr + 0]; + uint8 d1 = vram[tdaddr + 1]; + + for(unsigned x = 0; x < 8; x++) { + uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6); + d0 <<= 1, d1 <<= 1; + *output++ = (3 - status.bgp[palette]) * 0x55; + } + } +} + void LCD::power() { - for(unsigned n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = this; //VRAM - for(unsigned n = 0xff40; n <= 0xff4f; n++) bus.mmio[n] = this; //MMIO + for(unsigned n = 0x8000; n <= 0x9fff; n++) bus.mmio[n] = this; //VRAM + for(unsigned n = 0xff40; n <= 0xff4b; n++) bus.mmio[n] = this; //MMIO for(unsigned n = 0xfe00; n <= 0xfe9f; n++) bus.mmio[n] = this; //OAM + for(unsigned n = 0; n < 8192; n++) vram[n] = 0x00; + for(unsigned n = 0; n < 160; n++) oam [n] = 0x00; + reset(); } void LCD::reset() { + create(Main, 4 * 1024 * 1024); + for(unsigned n = 0; n < 160 * 144; n++) screen[n] = 0x00; + + status.lx = 0; + + status.display_enable = 0; + status.window_tilemap_select = 0; + status.window_display_enable = 0; + status.bg_tiledata_select = 0; + status.bg_tilemap_select = 0; + status.obj_size = 0; + status.obj_enable = 0; + status.bg_display = 0; + + status.interrupt_lyc = 0; + status.interrupt_oam = 0; + status.interrupt_vblank = 0; + status.interrupt_hblank = 0; + status.coincidence = 0; + status.mode = 0; + + status.scy = 0; + + status.scx = 0; + status.ly = 0; + + status.lyc = 0; + + for(unsigned n = 0; n < 4; n++) { + status.bgp[n] = n; + status.obp0[n] = n; + status.obp1[n] = n; + } + + status.wy = 0; + + status.wx = 0; } } diff --git a/gameboy/lcd/lcd.hpp b/gameboy/lcd/lcd.hpp index 07a2ae16..70056c8c 100755 --- a/gameboy/lcd/lcd.hpp +++ b/gameboy/lcd/lcd.hpp @@ -2,12 +2,65 @@ struct LCD : Processor, MMIO { #include "mmio/mmio.hpp" struct Status { - unsigned ly; + unsigned lx; + + //$ff40 LCDC + bool display_enable; + bool window_tilemap_select; + bool window_display_enable; + bool bg_tiledata_select; + bool bg_tilemap_select; + bool obj_size; + bool obj_enable; + bool bg_display; + + //$ff41 STAT + bool interrupt_lyc; + bool interrupt_oam; + bool interrupt_vblank; + bool interrupt_hblank; + bool coincidence; + unsigned mode; + + //$ff42 SCY + uint8 scy; + + //$ff43 SCX + uint8 scx; + + //$ff44 LY + uint8 ly; + + //$ff45 LYC + uint8 lyc; + + //$ff47 BGP + uint8 bgp[4]; + + //$ff48 OBP0 + uint8 obp0[4]; + + //$ff49 OBP1 + uint8 obp1[4]; + + //$ff4a WY + uint8 wy; + + //$ff4b WX + uint8 wx; } status; + uint8 screen[160 * 144]; uint8 vram[8192]; uint8 oam[160]; + static void Main(); + void main(); + void add_clocks(unsigned clocks); + void scanline(); + void frame(); + void render(); + void power(); void reset(); }; diff --git a/gameboy/lcd/mmio/mmio.cpp b/gameboy/lcd/mmio/mmio.cpp index 51aeb634..bff31ada 100755 --- a/gameboy/lcd/mmio/mmio.cpp +++ b/gameboy/lcd/mmio/mmio.cpp @@ -1,26 +1,159 @@ #ifdef LCD_CPP uint8 LCD::mmio_read(uint16 addr) { - if(addr >= 0xa000 && addr <= 0xbfff) return vram[addr & 0x1fff]; + if(addr >= 0x8000 && addr <= 0x9fff) return vram[addr & 0x1fff]; if(addr >= 0xfe00 && addr <= 0xfe9f) return oam[addr & 0xff]; - //LY - if(addr == 0xff44) { + if(addr == 0xff40) { //LCDC + return (status.display_enable << 7) + | (status.window_tilemap_select << 6) + | (status.window_display_enable << 5) + | (status.bg_tiledata_select << 4) + | (status.bg_tilemap_select << 3) + | (status.obj_size << 2) + | (status.obj_enable << 1) + | (status.bg_display << 0); + } + + if(addr == 0xff41) { //STAT + return (status.interrupt_lyc << 6) + | (status.interrupt_oam << 5) + | (status.interrupt_vblank << 4) + | (status.interrupt_hblank << 3) + | (status.coincidence << 2) + | (status.mode << 0); + } + + if(addr == 0xff42) { //SCY + return status.scy; + } + + if(addr == 0xff43) { //SCX + return status.scx; + } + + if(addr == 0xff44) { //LY return status.ly; } + if(addr == 0xff45) { //LYC + return status.lyc; + } + + if(addr == 0xff47) { //BGP + return (status.bgp[3] << 6) + | (status.bgp[2] << 4) + | (status.bgp[1] << 2) + | (status.bgp[0] << 0); + } + + if(addr == 0xff48) { //OBP0 + return (status.obp0[3] << 6) + | (status.obp0[2] << 4) + | (status.obp0[1] << 2) + | (status.obp0[0] << 0); + } + + if(addr == 0xff49) { //OBP1 + return (status.obp1[3] << 6) + | (status.obp1[2] << 4) + | (status.obp1[1] << 2) + | (status.obp1[0] << 0); + } + + if(addr == 0xff4a) { //WY + return status.wy; + } + + if(addr == 0xff4b) { //WX + return status.wx; + } + return 0x00; } void LCD::mmio_write(uint16 addr, uint8 data) { - if(addr >= 0xa000 && addr <= 0xbfff) { vram[addr & 0x1fff] = data; return; } + if(addr >= 0x8000 && addr <= 0x9fff) { vram[addr & 0x1fff] = data; return; } if(addr >= 0xfe00 && addr <= 0xfe9f) { oam[addr & 0xff] = data; return; } - //LY - if(addr == 0xff44) { + if(addr == 0xff40) { //LCDC + status.display_enable = data & 0x80; + status.window_tilemap_select = data & 0x40; + status.window_display_enable = data & 0x20; + status.bg_tiledata_select = data & 0x10; + status.bg_tilemap_select = data & 0x08; + status.obj_size = data & 0x04; + status.obj_enable = data & 0x02; + status.bg_display = data & 0x01; + return; + } + + if(addr == 0xff41) { //STAT + status.interrupt_lyc = data & 0x40; + status.interrupt_oam = data & 0x20; + status.interrupt_vblank = data & 0x10; + status.interrupt_hblank = data & 0x08; + return; + } + + if(addr == 0xff42) { //SCY + status.scy = data; + return; + } + + if(addr == 0xff43) { //SCX + status.scx = data; + return; + } + + if(addr == 0xff44) { //LY status.ly = 0; return; } + + if(addr == 0xff45) { //LYC + status.lyc = data; + return; + } + + if(addr == 0xff46) { //DMA + //TODO + return; + } + + if(addr == 0xff47) { //BGP + status.bgp[3] = (data >> 6) & 3; + status.bgp[2] = (data >> 4) & 3; + status.bgp[1] = (data >> 2) & 3; + status.bgp[0] = (data >> 0) & 3; + return; + } + + if(addr == 0xff48) { //OBP0 + status.obp0[3] = (data >> 6) & 3; + status.obp0[2] = (data >> 4) & 3; + status.obp0[1] = (data >> 2) & 3; + status.obp0[0] = (data >> 0) & 3; + return; + } + + if(addr == 0xff49) { //OBP1 + status.obp1[3] = (data >> 6) & 3; + status.obp1[2] = (data >> 4) & 3; + status.obp1[1] = (data >> 2) & 3; + status.obp1[0] = (data >> 0) & 3; + return; + } + + if(addr == 0xff4a) { //WY + status.wy = data; + return; + } + + if(addr == 0xff4b) { //WX + status.wx = data; + return; + } } #endif diff --git a/gameboy/system/system.hpp b/gameboy/system/system.hpp index ccc6c889..f9a6011c 100755 --- a/gameboy/system/system.hpp +++ b/gameboy/system/system.hpp @@ -1,5 +1,9 @@ class Interface; +enum class Input : unsigned { + Up, Down, Left, Right, B, A, Select, Start, +}; + class System { public: void init(Interface*); diff --git a/ui/base.hpp b/ui/base.hpp index 323331fb..66eb11ad 100755 --- a/ui/base.hpp +++ b/ui/base.hpp @@ -4,6 +4,9 @@ #include using namespace nall; +#include +using namespace ruby; + #include using namespace phoenix; @@ -18,6 +21,10 @@ using namespace phoenix; struct Application { bool quit; + Font proportionalFont; + Font proportionalFontBold; + Font monospaceFont; + void main(int argc, char **argv); }; diff --git a/ui/general/main-window.cpp b/ui/general/main-window.cpp index fb08eac2..7aa7541e 100755 --- a/ui/general/main-window.cpp +++ b/ui/general/main-window.cpp @@ -1,7 +1,9 @@ MainWindow mainWindow; void MainWindow::create() { - Window::create(128, 128, 160 * 3, 144 * 3, { GameBoy::Info::Name, " v", GameBoy::Info::Version }); + Window::create(128, 128, 160 * 2, 144 * 2, { GameBoy::Info::Name, " v", GameBoy::Info::Version }); + setDefaultFont(application.proportionalFont); + setFont(application.proportionalFontBold); system.create(*this, "System"); systemLoadCartridge.create(system, "Load Cartridge ..."); @@ -12,15 +14,15 @@ void MainWindow::create() { systemReset.setEnabled(false); settings.create(*this, "Settings"); - settings.setEnabled(false); +//settings.setEnabled(false); tools.create(*this, "Tools"); - tools.setEnabled(false); +//tools.setEnabled(false); help.create(*this, "Help"); helpAbout.create(help, "About ..."); - viewport.create(*this, 0, 0, 160 * 3, 144 * 3); + viewport.create(*this, 0, 0, 160 * 2, 144 * 2); setMenuVisible(true); setStatusVisible(true); diff --git a/ui/interface.cpp b/ui/interface.cpp index dd28990d..4cda5ac3 100755 --- a/ui/interface.cpp +++ b/ui/interface.cpp @@ -1,15 +1,52 @@ Interface interface; void Interface::video_refresh(const uint8_t *data) { + uint32_t *buffer; + unsigned pitch; + if(video.lock(buffer, pitch, 160, 144)) { + for(unsigned y = 0; y < 144; y++) { + uint32_t *line = buffer + y * (pitch >> 2); + const uint8_t *source = data + y * 160; + for(unsigned x = 0; x < 160; x++) { + uint32_t color = *source++; + *line++ = (color << 16) | (color << 8) | (color << 0); + } + } + video.unlock(); + video.refresh(); + } + + static unsigned frameCounter = 0; + static time_t timeCounter = time(0); + + frameCounter++; + time_t currentTime = time(0); + if(currentTime != timeCounter) { + timeCounter = currentTime; + mainWindow.setStatusText({ "FPS: ", frameCounter }); + frameCounter = 0; + } } void Interface::audio_sample(signed left, signed right) { } void Interface::input_poll() { + input.poll(inputState); } bool Interface::input_poll(unsigned id) { + switch(id) { + case GameBoy::Input::Up: return inputState[keyboard(0)[Keyboard::Up]]; + case GameBoy::Input::Down: return inputState[keyboard(0)[Keyboard::Down]]; + case GameBoy::Input::Left: return inputState[keyboard(0)[Keyboard::Left]]; + case GameBoy::Input::Right: return inputState[keyboard(0)[Keyboard::Right]]; + case GameBoy::Input::B: return inputState[keyboard(0)[Keyboard::Z]]; + case GameBoy::Input::A: return inputState[keyboard(0)[Keyboard::X]]; + case GameBoy::Input::Select: return inputState[keyboard(0)[Keyboard::Apostrophe]]; + case GameBoy::Input::Start: return inputState[keyboard(0)[Keyboard::Return]]; + } + return false; } diff --git a/ui/interface.hpp b/ui/interface.hpp index e33902d5..d6910bb9 100755 --- a/ui/interface.hpp +++ b/ui/interface.hpp @@ -1,4 +1,6 @@ struct Interface : public GameBoy::Interface { + int16_t inputState[Scancode::Limit]; + void video_refresh(const uint8_t *data); void audio_sample(signed left, signed right); void input_poll(); diff --git a/ui/main.cpp b/ui/main.cpp index 87deb7f4..6f65651e 100755 --- a/ui/main.cpp +++ b/ui/main.cpp @@ -8,34 +8,42 @@ Application application; void Application::main(int argc, char **argv) { quit = false; - mainWindow.create(); + #if defined(PHOENIX_WINDOWS) + proportionalFont.create("Tahoma", 8); + proportionalFontBold.create("Tahoma", 8, Font::Style::Bold); + monospaceFont.create("Courier New", 8); + #else + proportionalFont.create("Sans", 8); + proportionalFontBold.create("Sans", 8, Font::Style::Bold); + monospaceFont.create("Liberation Mono", 8); + #endif + mainWindow.create(); mainWindow.setVisible(); + OS::run(); + + video.driver("OpenGL"); + video.set(Video::Handle, (uintptr_t)mainWindow.viewport.handle()); + video.set(Video::Synchronize, false); + video.set(Video::Filter, (unsigned)0); + video.init(); + + input.driver("SDL"); + input.set(Input::Handle, (uintptr_t)mainWindow.viewport.handle()); + input.init(); GameBoy::system.init(&interface); - unsigned frameCounter = 0; - time_t timeCounter = time(0); - while(quit == false) { OS::run(); if(GameBoy::cartridge.loaded()) { GameBoy::system.run(); - - frameCounter++; - time_t currentTime = time(0); - if(currentTime != timeCounter) { - timeCounter = currentTime; - mainWindow.setStatusText({ "FPS: ", frameCounter }); - frameCounter = 0; - } } } } int main(int argc, char **argv) { application.main(argc, argv); - return 0; } From 42a9f9cfa4b98aead8f862ea5c570918bd4d2e32 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Fri, 31 Dec 2010 16:43:47 +1100 Subject: [PATCH 07/12] Update to release v000r05. byuu says: Fixed all of the previously mentioned problems by myself and Jonas Quinn. Fixed up JOYP and hooked up JOYP interrupts, they work on JOYP writes as well if your selection makes the low four bits change to != 0xF. Added basic sprite emulation, very very very lousy but it works for Tetris. Fixed DAA, fuck that opcode. Fixes blargg's CPU tests 1 and 11 (for some odd reason.) Only test 2 is failing, on the "EI" test. Maybe it relies on STAT interrupts? Did some other stuff. Tetris is now 100% fully playable. But that renderer is an abomination. Soooooo simplistic and missing so many edge cases. But holy shit, a fully playable commercial game in three days. I would have killed to have made that progress when I started on bsnes. --- gameboy/cpu/core/core.cpp | 21 +++++++++++++---- gameboy/cpu/core/core.hpp | 1 + gameboy/cpu/core/disassembler.cpp | 2 +- gameboy/cpu/core/table.cpp | 4 ++-- gameboy/cpu/cpu.cpp | 1 + gameboy/cpu/cpu.hpp | 1 + gameboy/cpu/mmio/mmio.cpp | 38 +++++++++++++++++-------------- gameboy/cpu/mmio/mmio.hpp | 1 + gameboy/gameboy.hpp | 2 +- gameboy/lcd/lcd.cpp | 31 +++++++++++++++++++++++-- gameboy/lcd/lcd.hpp | 4 +--- gameboy/lcd/mmio/mmio.cpp | 34 +++++++++++++-------------- ui/main.cpp | 2 +- 13 files changed, 93 insertions(+), 49 deletions(-) diff --git a/gameboy/cpu/core/core.cpp b/gameboy/cpu/core/core.cpp index b401ec18..317b936b 100755 --- a/gameboy/cpu/core/core.cpp +++ b/gameboy/cpu/core/core.cpp @@ -61,6 +61,10 @@ void CPU::op_ld_ffn_a() { op_write(0xff00 + op_read(r[PC]++), r[A]); } +void CPU::op_ld_a_ffc() { + r[A] = op_read(0xff00 + r[C]); +} + void CPU::op_ld_ffc_a() { op_write(0xff00 + r[C], r[A]); } @@ -101,6 +105,7 @@ void CPU::op_ld_nn_sp() { void CPU::op_ld_sp_hl() { r[SP] = r[HL]; + op_io(); } template void CPU::op_push_rr() { @@ -137,7 +142,7 @@ void CPU::opi_adc_a(uint8 x) { r.f.z = (uint8)rh == 0; r.f.n = 0; r.f.h = rl > 0x0f; - r.f.c = rh > 0x0f; + r.f.c = rh > 0xff; } template void CPU::op_adc_a_r() { opi_adc_a(r[x]); } @@ -214,7 +219,7 @@ void CPU::opi_cp_a(uint8 x) { r.f.z = (uint8)rh == 0; r.f.n = 1; r.f.h = rl > 0x0f; - r.f.c = rh > 0x0f; + r.f.c = rh > 0xff; } template void CPU::op_cp_a_r() { opi_cp_a(r[x]); } @@ -252,10 +257,10 @@ void CPU::op_dec_hl() { } void CPU::op_daa() { - signed a = r[A]; + uint16 a = r[A]; if(r.f.n == 0) { if(r.f.h || (a & 0x0f) > 0x09) a += 0x06; - if(r.f.c || (a & 0xff) > 0x9f) a += 0x60; + if(r.f.c || (a ) > 0x9f) a += 0x60; } else { if(r.f.h) { a -= 0x06; @@ -266,7 +271,7 @@ void CPU::op_daa() { r[A] = a; r.f.z = r[A] == 0; r.f.h = 0; - r.f.c = a & 0x100; + r.f.c |= a & 0x100; } void CPU::op_cpl() { @@ -278,6 +283,7 @@ void CPU::op_cpl() { //16-bit arithmetic commands template void CPU::op_add_hl_rr() { + op_io(); uint32 rb = (r[HL] + r[x]); uint32 rn = (r[HL] & 0xfff) + (r[x] & 0xfff); r[HL] = rb; @@ -287,14 +293,18 @@ template void CPU::op_add_hl_rr() { } template void CPU::op_inc_rr() { + op_io(); r[x]++; } template void CPU::op_dec_rr() { + op_io(); r[x]--; } void CPU::op_add_sp_n() { + op_io(); + op_io(); signed n = (int8)op_read(r[PC]++); r.f.z = 0; r.f.n = 0; @@ -304,6 +314,7 @@ void CPU::op_add_sp_n() { } void CPU::op_ld_hl_sp_n() { + op_io(); signed n = (int8)op_read(r[PC]++); r.f.z = 0; r.f.n = 0; diff --git a/gameboy/cpu/core/core.hpp b/gameboy/cpu/core/core.hpp index 16444966..85ebeb1d 100755 --- a/gameboy/cpu/core/core.hpp +++ b/gameboy/cpu/core/core.hpp @@ -18,6 +18,7 @@ template void op_ld_rr_a(); void op_ld_nn_a(); void op_ld_a_ffn(); void op_ld_ffn_a(); +void op_ld_a_ffc(); void op_ld_ffc_a(); void op_ldi_hl_a(); void op_ldi_a_hl(); diff --git a/gameboy/cpu/core/disassembler.cpp b/gameboy/cpu/core/disassembler.cpp index 9c67f444..dbcee7ee 100755 --- a/gameboy/cpu/core/disassembler.cpp +++ b/gameboy/cpu/core/disassembler.cpp @@ -270,7 +270,7 @@ string CPU::disassemble_opcode(uint16 pc) { case 0xef: return { "rst $0028" }; case 0xf0: return { "ld a,($ff", hex<2>(p0), ")" }; case 0xf1: return { "pop af" }; - case 0xf2: return { "xx" }; + case 0xf2: return { "ld a,($ff00+c)" }; case 0xf3: return { "di" }; case 0xf4: return { "xx" }; case 0xf5: return { "push af" }; diff --git a/gameboy/cpu/core/table.cpp b/gameboy/cpu/core/table.cpp index 2c885591..fa2f1112 100755 --- a/gameboy/cpu/core/table.cpp +++ b/gameboy/cpu/core/table.cpp @@ -127,7 +127,7 @@ void CPU::initialize_opcode_table() { opcode_table[0x7b] = &CPU::op_ld_r_r; opcode_table[0x7c] = &CPU::op_ld_r_r; opcode_table[0x7d] = &CPU::op_ld_r_r; - opcode_table[0x7e] = &CPU::op_ld_r_hl; + opcode_table[0x7e] = &CPU::op_ld_r_hl; opcode_table[0x7f] = &CPU::op_ld_r_r; opcode_table[0x80] = &CPU::op_add_a_r; opcode_table[0x81] = &CPU::op_add_a_r; @@ -243,7 +243,7 @@ void CPU::initialize_opcode_table() { opcode_table[0xef] = &CPU::op_rst_n<0x28>; opcode_table[0xf0] = &CPU::op_ld_a_ffn; opcode_table[0xf1] = &CPU::op_pop_rr; - opcode_table[0xf2] = &CPU::op_xx; + opcode_table[0xf2] = &CPU::op_ld_a_ffc; opcode_table[0xf3] = &CPU::op_di; opcode_table[0xf4] = &CPU::op_xx; opcode_table[0xf5] = &CPU::op_push_rr; diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp index 5a0bc001..70b4130f 100755 --- a/gameboy/cpu/cpu.cpp +++ b/gameboy/cpu/cpu.cpp @@ -88,6 +88,7 @@ void CPU::reset() { status.p15 = 0; status.p14 = 0; + status.joyp = 0; status.div = 0; diff --git a/gameboy/cpu/cpu.hpp b/gameboy/cpu/cpu.hpp index dc66e90f..28f4d211 100755 --- a/gameboy/cpu/cpu.hpp +++ b/gameboy/cpu/cpu.hpp @@ -13,6 +13,7 @@ struct CPU : Processor, MMIO { //$ff00 JOYP bool p15; bool p14; + uint8 joyp; //$ff04 DIV uint8 div; diff --git a/gameboy/cpu/mmio/mmio.cpp b/gameboy/cpu/mmio/mmio.cpp index 4d6322b0..e18b1e5e 100755 --- a/gameboy/cpu/mmio/mmio.cpp +++ b/gameboy/cpu/mmio/mmio.cpp @@ -1,30 +1,33 @@ #ifdef CPU_CPP +void CPU::mmio_joyp_poll() { + unsigned button = 0, dpad = 0; + + button |= system.interface->input_poll((unsigned)Input::Start) << 3; + button |= system.interface->input_poll((unsigned)Input::Select) << 2; + button |= system.interface->input_poll((unsigned)Input::B) << 1; + button |= system.interface->input_poll((unsigned)Input::A) << 0; + + dpad |= system.interface->input_poll((unsigned)Input::Down) << 3; + dpad |= system.interface->input_poll((unsigned)Input::Up) << 2; + dpad |= system.interface->input_poll((unsigned)Input::Left) << 1; + dpad |= system.interface->input_poll((unsigned)Input::Right) << 0; + + status.joyp = 0x0f; + if(status.p15 == 0) status.joyp &= button ^ 0x0f; + if(status.p14 == 0) status.joyp &= dpad ^ 0x0f; + if(status.joyp != 0x0f) status.interrupt_request_joypad = 1; +} + uint8 CPU::mmio_read(uint16 addr) { if(addr >= 0xc000 && addr <= 0xdfff) return wram[addr & 0x1fff]; if(addr >= 0xe000 && addr <= 0xfdff) return wram[addr & 0x1fff]; if(addr >= 0xff80 && addr <= 0xfffe) return hram[addr & 0x7f]; if(addr == 0xff00) { //JOYP - unsigned keys = 0x0f; - - if(status.p15 == 0 && status.p14 == 1) { - keys = !system.interface->input_poll((unsigned)Input::Down) << 3; - keys |= !system.interface->input_poll((unsigned)Input::Up) << 2; - keys |= !system.interface->input_poll((unsigned)Input::Left) << 1; - keys |= !system.interface->input_poll((unsigned)Input::Right) << 0; - } - - if(status.p15 == 1 && status.p14 == 0) { - keys = !system.interface->input_poll((unsigned)Input::Start) << 3; - keys |= !system.interface->input_poll((unsigned)Input::Select) << 2; - keys |= !system.interface->input_poll((unsigned)Input::B) << 1; - keys |= !system.interface->input_poll((unsigned)Input::A) << 0; - } - return (status.p15 << 5) | (status.p14 << 4) - | (keys << 0); + | (status.joyp << 0); } if(addr == 0xff04) { //DIV @@ -71,6 +74,7 @@ void CPU::mmio_write(uint16 addr, uint8 data) { if(addr == 0xff00) { //JOYP status.p15 = data & 0x20; status.p14 = data & 0x10; + mmio_joyp_poll(); return; } diff --git a/gameboy/cpu/mmio/mmio.hpp b/gameboy/cpu/mmio/mmio.hpp index 92b8f614..bc9eb8ba 100755 --- a/gameboy/cpu/mmio/mmio.hpp +++ b/gameboy/cpu/mmio/mmio.hpp @@ -1,2 +1,3 @@ +void mmio_joyp_poll(); uint8 mmio_read(uint16 addr); void mmio_write(uint16 addr, uint8 data); diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp index f51da8c0..4c131b7f 100755 --- a/gameboy/gameboy.hpp +++ b/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000.04"; + static const char Version[] = "000.05"; } } diff --git a/gameboy/lcd/lcd.cpp b/gameboy/lcd/lcd.cpp index 11686b29..be9818bc 100755 --- a/gameboy/lcd/lcd.cpp +++ b/gameboy/lcd/lcd.cpp @@ -38,6 +38,7 @@ void LCD::scanline() { void LCD::frame() { system.interface->video_refresh(screen); system.interface->input_poll(); + cpu.mmio_joyp_poll(); status.ly = 0; scheduler.exit(); @@ -68,6 +69,32 @@ void LCD::render() { *output++ = (3 - status.bgp[palette]) * 0x55; } } + + output = screen + status.ly * 160; + for(unsigned s = 0; s < 40; s++) { + unsigned sy = oam[(s << 2) + 0] - 9; + unsigned sx = oam[(s << 2) + 1] - 8; + unsigned tile = oam[(s << 2) + 2]; + unsigned attribute = oam[(s << 2) + 3]; + + sy -= status.ly; + if(sy >= 8) continue; + if(attribute & 0x40||1) sy ^= 7; + + unsigned addr = tile * 16 + sy * 2; + + uint8 d0 = vram[addr + 0]; + uint8 d1 = vram[addr + 1]; + unsigned xflip = attribute & 0x20 ? -7 : 0; + + for(unsigned x = 0; x < 8; x++) { + uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6); + d0 <<= 1, d1 <<= 1; + if(palette == 0) continue; + palette = status.obp[(bool)(attribute & 0x10)][palette]; + output[sx + (x ^ xflip)] = (3 - palette) * 0x55; + } + } } void LCD::power() { @@ -113,8 +140,8 @@ void LCD::reset() { for(unsigned n = 0; n < 4; n++) { status.bgp[n] = n; - status.obp0[n] = n; - status.obp1[n] = n; + status.obp[0][n] = n; + status.obp[1][n] = n; } status.wy = 0; diff --git a/gameboy/lcd/lcd.hpp b/gameboy/lcd/lcd.hpp index 70056c8c..d819b3b0 100755 --- a/gameboy/lcd/lcd.hpp +++ b/gameboy/lcd/lcd.hpp @@ -38,10 +38,8 @@ struct LCD : Processor, MMIO { uint8 bgp[4]; //$ff48 OBP0 - uint8 obp0[4]; - //$ff49 OBP1 - uint8 obp1[4]; + uint8 obp[2][4]; //$ff4a WY uint8 wy; diff --git a/gameboy/lcd/mmio/mmio.cpp b/gameboy/lcd/mmio/mmio.cpp index bff31ada..230ba463 100755 --- a/gameboy/lcd/mmio/mmio.cpp +++ b/gameboy/lcd/mmio/mmio.cpp @@ -48,17 +48,17 @@ uint8 LCD::mmio_read(uint16 addr) { } if(addr == 0xff48) { //OBP0 - return (status.obp0[3] << 6) - | (status.obp0[2] << 4) - | (status.obp0[1] << 2) - | (status.obp0[0] << 0); + return (status.obp[0][3] << 6) + | (status.obp[0][2] << 4) + | (status.obp[0][1] << 2) + | (status.obp[0][0] << 0); } if(addr == 0xff49) { //OBP1 - return (status.obp1[3] << 6) - | (status.obp1[2] << 4) - | (status.obp1[1] << 2) - | (status.obp1[0] << 0); + return (status.obp[1][3] << 6) + | (status.obp[1][2] << 4) + | (status.obp[1][1] << 2) + | (status.obp[1][0] << 0); } if(addr == 0xff4a) { //WY @@ -117,7 +117,7 @@ void LCD::mmio_write(uint16 addr, uint8 data) { } if(addr == 0xff46) { //DMA - //TODO + for(unsigned n = 0x00; n <= 0x9f; n++) bus.write(0xfe00 + n, bus.read((data << 8) + n)); return; } @@ -130,18 +130,18 @@ void LCD::mmio_write(uint16 addr, uint8 data) { } if(addr == 0xff48) { //OBP0 - status.obp0[3] = (data >> 6) & 3; - status.obp0[2] = (data >> 4) & 3; - status.obp0[1] = (data >> 2) & 3; - status.obp0[0] = (data >> 0) & 3; + status.obp[0][3] = (data >> 6) & 3; + status.obp[0][2] = (data >> 4) & 3; + status.obp[0][1] = (data >> 2) & 3; + status.obp[0][0] = (data >> 0) & 3; return; } if(addr == 0xff49) { //OBP1 - status.obp1[3] = (data >> 6) & 3; - status.obp1[2] = (data >> 4) & 3; - status.obp1[1] = (data >> 2) & 3; - status.obp1[0] = (data >> 0) & 3; + status.obp[1][3] = (data >> 6) & 3; + status.obp[1][2] = (data >> 4) & 3; + status.obp[1][1] = (data >> 2) & 3; + status.obp[1][0] = (data >> 0) & 3; return; } diff --git a/ui/main.cpp b/ui/main.cpp index 6f65651e..f617896d 100755 --- a/ui/main.cpp +++ b/ui/main.cpp @@ -24,7 +24,7 @@ void Application::main(int argc, char **argv) { video.driver("OpenGL"); video.set(Video::Handle, (uintptr_t)mainWindow.viewport.handle()); - video.set(Video::Synchronize, false); + video.set(Video::Synchronize, true); video.set(Video::Filter, (unsigned)0); video.init(); From ebd6a528118eab29ae1e06831389f81560c2ae44 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Sun, 2 Jan 2011 15:46:54 +1100 Subject: [PATCH 08/12] Update to release v000r06. byuu says: Added MBC1 emulation, although battery RAM doesn't save or load to disk yet. Made up a fake MBC0 which is really just saying 'no MBC', for consistent handling of all MBCs. Added bumpers to stop ROM/RAM out of bounds accesses. Added STAT interrupts for LY coincidence, Vblank and Hblank (not for OAM access yet, I don't know the timings.) Fixed timer interrupt [Jonas Quinn] Made all interrupts call a CPU function instead of just setting a flag for better control (to allow below addition.) Added HALT and STOP emulation, the latter permanently locks the Game Boy for now. The former breaks on interrupts. Rewrote all the rendering code to suck 50% less, though it's still absolutely miserable and scanline-based. Added pixel-level horizontal scrolling to BGs. Fixed OBJ rendering error that was making them render upside down (I was flipping to compensate before.) Added OBJ 8x16 mode. Added OBJ priority support. Added window (but it's broken to all hell on Mega Man II.) --- gameboy/cartridge/cartridge.cpp | 55 ++++++++++- gameboy/cartridge/cartridge.hpp | 23 ++++- gameboy/cartridge/mbc0/mbc0.cpp | 17 ++++ gameboy/cartridge/mbc0/mbc0.hpp | 6 ++ gameboy/cartridge/mbc1/mbc1.cpp | 62 ++++++++++++ gameboy/cartridge/mbc1/mbc1.hpp | 14 +++ gameboy/cartridge/mmio/mmio.cpp | 10 -- gameboy/cartridge/mmio/mmio.hpp | 2 - gameboy/cpu/core/core.cpp | 6 +- gameboy/cpu/cpu.cpp | 14 +++ gameboy/cpu/cpu.hpp | 12 +++ gameboy/cpu/mmio/mmio.cpp | 2 +- gameboy/cpu/timing/timing.cpp | 16 +-- gameboy/gameboy.hpp | 2 +- gameboy/lcd/lcd.cpp | 168 +++++++++++++++++++++++--------- gameboy/lcd/lcd.hpp | 12 ++- gameboy/lcd/mmio/mmio.cpp | 13 ++- ui/general/main-window.cpp | 2 +- 18 files changed, 354 insertions(+), 82 deletions(-) create mode 100755 gameboy/cartridge/mbc0/mbc0.cpp create mode 100755 gameboy/cartridge/mbc0/mbc0.hpp create mode 100755 gameboy/cartridge/mbc1/mbc1.cpp create mode 100755 gameboy/cartridge/mbc1/mbc1.hpp delete mode 100755 gameboy/cartridge/mmio/mmio.cpp delete mode 100755 gameboy/cartridge/mmio/mmio.hpp diff --git a/gameboy/cartridge/cartridge.cpp b/gameboy/cartridge/cartridge.cpp index 611b35f5..53d3d66a 100755 --- a/gameboy/cartridge/cartridge.cpp +++ b/gameboy/cartridge/cartridge.cpp @@ -3,7 +3,8 @@ #define CARTRIDGE_CPP namespace GameBoy { -#include "mmio/mmio.cpp" +#include "mbc0/mbc0.cpp" +#include "mbc1/mbc1.cpp" Cartridge cartridge; void Cartridge::load(uint8_t *data, unsigned size) { @@ -18,7 +19,18 @@ void Cartridge::load(uint8_t *data, unsigned size) { info.cgbflag = romdata[0x0143]; info.sgbflag = romdata[0x0146]; - info.type = romdata[0x0147]; + + info.mapper = Mapper::Unknown; + info.ram = false; + info.battery = false; + info.rtc = false; + + switch(romdata[0x0147]) { default: + case 0x00: info.mapper = Mapper::MBC0; break; + case 0x01: info.mapper = Mapper::MBC1; break; + case 0x02: info.mapper = Mapper::MBC1; info.ram = true; break; + case 0x03: info.mapper = Mapper::MBC1; info.ram = true; info.battery = true; break; + } switch(romdata[0x0148]) { default: case 0x00: info.romsize = 2 * 16 * 1024; break; @@ -42,6 +54,8 @@ void Cartridge::load(uint8_t *data, unsigned size) { case 0x03: info.ramsize = 32 * 1024; break; } + ramdata = new uint8_t[ramsize = info.ramsize](); + loaded = true; } @@ -53,13 +67,48 @@ void Cartridge::unload() { loaded = false; } +uint8 Cartridge::rom_read(unsigned addr) { + if(addr >= romsize) addr %= romsize; + return romdata[addr]; +} + +void Cartridge::rom_write(unsigned addr, uint8 data) { + if(addr >= romsize) addr %= romsize; + romdata[addr] = data; +} + +uint8 Cartridge::ram_read(unsigned addr) { + if(ramsize == 0) return 0x00; + if(addr >= ramsize) addr %= ramsize; + return ramdata[addr]; +} + +void Cartridge::ram_write(unsigned addr, uint8 data) { + if(ramsize == 0) return; + if(addr >= ramsize) addr %= ramsize; + ramdata[addr] = data; +} + void Cartridge::power() { - for(unsigned n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = this; + mbc0.power(); + mbc1.power(); + + MMIO *mapper = 0; + switch(info.mapper) { + case Mapper::MBC0: mapper = &mbc0; break; + case Mapper::MBC1: mapper = &mbc1; break; + } + if(mapper) { + for(unsigned n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = mapper; + for(unsigned n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = mapper; + } reset(); } void Cartridge::reset() { + mbc1.reset(); + mbc1.reset(); } Cartridge::Cartridge() { diff --git a/gameboy/cartridge/cartridge.hpp b/gameboy/cartridge/cartridge.hpp index 4868fc56..03e4a1f2 100755 --- a/gameboy/cartridge/cartridge.hpp +++ b/gameboy/cartridge/cartridge.hpp @@ -1,11 +1,23 @@ -struct Cartridge : MMIO, property { - #include "mmio/mmio.hpp" +struct Cartridge : property { + #include "mbc0/mbc0.hpp" + #include "mbc1/mbc1.hpp" + + enum Mapper : unsigned { + MBC0, + MBC1, + Unknown, + }; struct Information { string name; uint8 cgbflag; uint8 sgbflag; - uint8 type; + + Mapper mapper; + bool ram; + bool battery; + bool rtc; + unsigned romsize; unsigned ramsize; } info; @@ -21,6 +33,11 @@ struct Cartridge : MMIO, property { void load(uint8_t *data, unsigned size); void unload(); + uint8 rom_read(unsigned addr); + void rom_write(unsigned addr, uint8 data); + uint8 ram_read(unsigned addr); + void ram_write(unsigned addr, uint8 data); + void power(); void reset(); diff --git a/gameboy/cartridge/mbc0/mbc0.cpp b/gameboy/cartridge/mbc0/mbc0.cpp new file mode 100755 index 00000000..85a8a505 --- /dev/null +++ b/gameboy/cartridge/mbc0/mbc0.cpp @@ -0,0 +1,17 @@ +#ifdef CARTRIDGE_CPP + +uint8 Cartridge::MBC0::mmio_read(uint16 addr) { + if(addr >= 0x0000 && addr <= 0x7fff) return cartridge.rom_read(addr); + return 0x00; +} + +void Cartridge::MBC0::mmio_write(uint16 addr, uint8 data) { +} + +void Cartridge::MBC0::power() { +} + +void Cartridge::MBC0::reset() { +} + +#endif diff --git a/gameboy/cartridge/mbc0/mbc0.hpp b/gameboy/cartridge/mbc0/mbc0.hpp new file mode 100755 index 00000000..fef9f9a9 --- /dev/null +++ b/gameboy/cartridge/mbc0/mbc0.hpp @@ -0,0 +1,6 @@ +struct MBC0 : MMIO { + uint8 mmio_read(uint16 addr); + void mmio_write(uint16 addr, uint8 data); + void power(); + void reset(); +} mbc0; diff --git a/gameboy/cartridge/mbc1/mbc1.cpp b/gameboy/cartridge/mbc1/mbc1.cpp new file mode 100755 index 00000000..fedb6b79 --- /dev/null +++ b/gameboy/cartridge/mbc1/mbc1.cpp @@ -0,0 +1,62 @@ +#ifdef CARTRIDGE_CPP + +uint8 Cartridge::MBC1::mmio_read(uint16 addr) { + if(addr >= 0x0000 && addr <= 0x3fff) { + return cartridge.rom_read(addr); + } + + if(addr >= 0x4000 && addr <= 0x7fff) { + return cartridge.rom_read(rom_bank | (addr & 0x3fff)); + } + + if(addr >= 0xa000 && addr <= 0xbfff) { + if(ram_enable) return cartridge.ram_read(ram_bank | (addr & 0x1fff)); + return 0x00; + } +} + +void Cartridge::MBC1::mmio_write(uint16 addr, uint8 data) { + if(addr >= 0x0000 && addr <= 0x1fff) { + ram_enable = (data & 0x0f) == 0x0a; + } + + if(addr >= 0x2000 && addr <= 0x3fff) { + rom_select = data & 0x1f; + if(rom_select == 0) rom_select = 1; + } + + if(addr >= 0x4000 && addr <= 0x5fff) { + ram_select = data & 0x03; + } + + if(addr >= 0x6000 && addr <= 0x7fff) { + mode_select = data & 0x01; + } + + if(mode_select == 0) { + rom_bank = (ram_select << 19) | (rom_select << 14); + ram_bank = 0x00; + } else { + rom_bank = (rom_select << 14); + ram_bank = (ram_select << 13); + } + + if(addr >= 0xa000 && addr <= 0xbfff) { + if(ram_enable) cartridge.ram_write(ram_bank | (addr & 0x1fff), data); + } +} + +void Cartridge::MBC1::power() { +} + +void Cartridge::MBC1::reset() { + ram_enable = false; + rom_select = 0x01; + ram_select = 0x00; + mode_select = 0; + + rom_bank = 0x4000; + ram_bank = 0x0000; +} + +#endif diff --git a/gameboy/cartridge/mbc1/mbc1.hpp b/gameboy/cartridge/mbc1/mbc1.hpp new file mode 100755 index 00000000..516acde8 --- /dev/null +++ b/gameboy/cartridge/mbc1/mbc1.hpp @@ -0,0 +1,14 @@ +struct MBC1 : MMIO { + bool ram_enable; //0000-1fff + uint8 rom_select; //2000-3fff + uint8 ram_select; //4000-5fff + bool mode_select; //6000-7fff + + unsigned rom_bank; + unsigned ram_bank; + + uint8 mmio_read(uint16 addr); + void mmio_write(uint16 addr, uint8 data); + void power(); + void reset(); +} mbc1; diff --git a/gameboy/cartridge/mmio/mmio.cpp b/gameboy/cartridge/mmio/mmio.cpp deleted file mode 100755 index 749b5895..00000000 --- a/gameboy/cartridge/mmio/mmio.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifdef CARTRIDGE_CPP - -uint8 Cartridge::mmio_read(uint16 addr) { - if(addr >= 0x0000 && addr <= 0x7fff) return romdata[addr]; -} - -void Cartridge::mmio_write(uint16 addr, uint8 data) { -} - -#endif diff --git a/gameboy/cartridge/mmio/mmio.hpp b/gameboy/cartridge/mmio/mmio.hpp deleted file mode 100755 index 92b8f614..00000000 --- a/gameboy/cartridge/mmio/mmio.hpp +++ /dev/null @@ -1,2 +0,0 @@ -uint8 mmio_read(uint16 addr); -void mmio_write(uint16 addr, uint8 data); diff --git a/gameboy/cpu/core/core.cpp b/gameboy/cpu/core/core.cpp index 317b936b..e1e226e2 100755 --- a/gameboy/cpu/core/core.cpp +++ b/gameboy/cpu/core/core.cpp @@ -566,11 +566,13 @@ void CPU::op_nop() { } void CPU::op_halt() { - //TODO + status.halt = true; + while(status.halt == true) op_io(); } void CPU::op_stop() { - //TODO + status.stop = true; + while(status.stop == true) op_io(); } void CPU::op_di() { diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp index 70b4130f..f01523e4 100755 --- a/gameboy/cpu/cpu.cpp +++ b/gameboy/cpu/cpu.cpp @@ -21,6 +21,17 @@ void CPU::main() { } } +void CPU::interrupt_raise(CPU::Interrupt id) { + switch(id) { + case Interrupt::Vblank: status.interrupt_request_vblank = 1; break; + case Interrupt::Stat : status.interrupt_request_stat = 1; break; + case Interrupt::Timer : status.interrupt_request_timer = 1; break; + case Interrupt::Serial: status.interrupt_request_serial = 1; break; + case Interrupt::Joypad: status.interrupt_request_joypad = 1; break; + } + status.halt = false; +} + void CPU::interrupt_test() { if(status.ime) { if(status.interrupt_request_vblank && status.interrupt_enable_vblank) { @@ -80,6 +91,9 @@ void CPU::reset() { r[DE] = 0x0000; r[HL] = 0x0000; + status.halt = false; + status.stop = false; + status.ime = 0; status.timer0 = 0; status.timer1 = 0; diff --git a/gameboy/cpu/cpu.hpp b/gameboy/cpu/cpu.hpp index 28f4d211..7d410b35 100755 --- a/gameboy/cpu/cpu.hpp +++ b/gameboy/cpu/cpu.hpp @@ -3,7 +3,18 @@ struct CPU : Processor, MMIO { #include "mmio/mmio.hpp" #include "timing/timing.hpp" + enum class Interrupt : unsigned { + Vblank, + Stat, + Timer, + Serial, + Joypad, + }; + struct Status { + bool halt; + bool stop; + bool ime; unsigned timer0; unsigned timer1; @@ -48,6 +59,7 @@ struct CPU : Processor, MMIO { static void Main(); void main(); + void interrupt_raise(Interrupt id); void interrupt_test(); void interrupt_exec(uint16 pc); void power(); diff --git a/gameboy/cpu/mmio/mmio.cpp b/gameboy/cpu/mmio/mmio.cpp index e18b1e5e..610474b9 100755 --- a/gameboy/cpu/mmio/mmio.cpp +++ b/gameboy/cpu/mmio/mmio.cpp @@ -16,7 +16,7 @@ void CPU::mmio_joyp_poll() { status.joyp = 0x0f; if(status.p15 == 0) status.joyp &= button ^ 0x0f; if(status.p14 == 0) status.joyp &= dpad ^ 0x0f; - if(status.joyp != 0x0f) status.interrupt_request_joypad = 1; + if(status.joyp != 0x0f) interrupt_raise(Interrupt::Joypad); } uint8 CPU::mmio_read(uint16 addr) { diff --git a/gameboy/cpu/timing/timing.cpp b/gameboy/cpu/timing/timing.cpp index 0d96e62e..b62c5995 100755 --- a/gameboy/cpu/timing/timing.cpp +++ b/gameboy/cpu/timing/timing.cpp @@ -21,10 +21,10 @@ void CPU::add_clocks(unsigned clocks) { } void CPU::timer_stage0() { //262144hz - if(status.timer_clock == 1) { + if(status.timer_enable && status.timer_clock == 1) { if(++status.tima == 0) { status.tima = status.tma; - status.interrupt_request_timer = 1; + interrupt_raise(Interrupt::Timer); } } @@ -33,10 +33,10 @@ void CPU::timer_stage0() { //262144hz } void CPU::timer_stage1() { // 65536hz - if(status.timer_clock == 2) { + if(status.timer_enable && status.timer_clock == 2) { if(++status.tima == 0) { status.tima = status.tma; - status.interrupt_request_timer = 1; + interrupt_raise(Interrupt::Timer); } } @@ -45,10 +45,10 @@ void CPU::timer_stage1() { // 65536hz } void CPU::timer_stage2() { // 16384hz - if(status.timer_clock == 3) { + if(status.timer_enable && status.timer_clock == 3) { if(++status.tima == 0) { status.tima = status.tma; - status.interrupt_request_timer = 1; + interrupt_raise(Interrupt::Timer); } } @@ -59,10 +59,10 @@ void CPU::timer_stage2() { // 16384hz } void CPU::timer_stage3() { // 4096hz - if(status.timer_clock == 0) { + if(status.timer_enable && status.timer_clock == 0) { if(++status.tima == 0) { status.tima = status.tma; - status.interrupt_request_timer = 1; + interrupt_raise(Interrupt::Timer); } } diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp index 4c131b7f..13c62158 100755 --- a/gameboy/gameboy.hpp +++ b/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000.05"; + static const char Version[] = "000.06"; } } diff --git a/gameboy/lcd/lcd.cpp b/gameboy/lcd/lcd.cpp index be9818bc..abcf26ad 100755 --- a/gameboy/lcd/lcd.cpp +++ b/gameboy/lcd/lcd.cpp @@ -13,6 +13,10 @@ void LCD::Main() { void LCD::main() { while(true) { add_clocks(4); + + if(status.lx == 320) { + if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat); + } } } @@ -28,8 +32,14 @@ void LCD::scanline() { status.lx -= 456; status.ly++; - if(status.ly == 144) cpu.status.interrupt_request_vblank = 1; -//print("Vblank - ", cpu.status.ime, " - ", cpu.status.interrupt_enable_vblank, "\n"); } + if(status.interrupt_lyc == true) { + if(status.ly == status.lyc) cpu.interrupt_raise(CPU::Interrupt::Stat); + } + + if(status.ly == 144) { + cpu.interrupt_raise(CPU::Interrupt::Vblank); + if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat); + } if(status.ly == 154) frame(); if(status.ly < 144) render(); @@ -45,54 +55,130 @@ void LCD::frame() { } void LCD::render() { - uint8_t *output = screen + status.ly * 160; - uint8 y = status.ly + status.scy; - uint16 tmaddr = (status.bg_tilemap_select == 0 ? 0x1800 : 0x1c00); - tmaddr += (y >> 3) * 32; - tmaddr += (status.scx >> 3); - - for(unsigned t = 0; t < 20; t++) { - unsigned tdaddr; - if(status.bg_tiledata_select == 0) { - tdaddr = 0x1000 + (int8)vram[tmaddr + t] * 16; - } else { - tdaddr = 0x0000 + vram[tmaddr + t] * 16; - } - tdaddr += (status.ly & 7) * 2; - - uint8 d0 = vram[tdaddr + 0]; - uint8 d1 = vram[tdaddr + 1]; - - for(unsigned x = 0; x < 8; x++) { - uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6); - d0 <<= 1, d1 <<= 1; - *output++ = (3 - status.bgp[palette]) * 0x55; - } + for(unsigned n = 0; n < 160; n++) { + line[n].source = Line::Source::None; + line[n].output = 0; } - output = screen + status.ly * 160; + if(status.display_enable == true) { + if(status.bg_enable == true) render_bg(); + if(status.obj_enable == true) render_obj(); + if(status.window_display_enable == true) render_window(); + } + + uint8_t *output = screen + status.ly * 160; + for(unsigned n = 0; n < 160; n++) { + output[n] = (3 - line[n].output) * 0x55; + } +} + +void LCD::render_bg() { + unsigned iy = (status.ly + status.scy) & 255; + unsigned ix = status.scx; + + unsigned tmaddr = (status.bg_tilemap_select == 0 ? 0x1800 : 0x1c00); + + unsigned tx = (ix - 7) & 7; + uint8 d0 = 0, d1 = 0; + for(signed ox = -7; ox < 160; ox++) { + if(tx == 0) { + unsigned tile = (((iy >> 3) * 32) + (ix >> 3)) & 0x3fff; + unsigned tdaddr; + if(status.bg_tiledata_select == 0) { + tdaddr = 0x1000 + (int8)vram[tmaddr + tile] * 16; + } else { + tdaddr = 0x0000 + vram[tmaddr + tile] * 16; + } + tdaddr += (iy & 7) * 2; + + d0 = vram[tdaddr + 0]; + d1 = vram[tdaddr + 1]; + } + + uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6); + d0 <<= 1, d1 <<= 1; + + if(ox >= 0) { + line[ox].source = Line::Source::BG; + line[ox].output = status.bgp[palette]; + } + + ix = (ix + 1) & 255; + tx = (tx + 1) & 7; + } +} + +void LCD::render_window() { + if(status.wy > status.ly) return; + unsigned iy = (status.ly + status.wy) & 255; + unsigned ix = (status.wx - 7) & 255; + + unsigned tmaddr = (status.window_tilemap_select == 0 ? 0x1800 : 0x1c00); + + unsigned tx = (ix - 7) & 7; + uint8 d0 = 0, d1 = 0; + for(signed ox = -7; ox < 160; ox++) { + if(tx == 0) { + unsigned tile = (((iy >> 3) * 32) + (ix >> 3)) & 0x3fff; + unsigned tdaddr; + if(status.bg_tiledata_select == 0) { + tdaddr = 0x1000 + (int8)vram[tmaddr + tile] * 16; + } else { + tdaddr = 0x0000 + vram[tmaddr + tile] * 16; + } + tdaddr += (iy & 7) * 2; + + d0 = vram[tdaddr + 0]; + d1 = vram[tdaddr + 1]; + } + + uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6); + d0 <<= 1, d1 <<= 1; + + if(ox >= 7) { + line[ox].source = Line::Source::Window; + line[ox].output = status.bgp[palette]; + } + + ix = (ix + 1) & 255; + tx = (tx + 1) & 7; + } +} + +void LCD::render_obj() { + unsigned obj_size = (status.obj_size == 0 ? 8 : 16); + for(unsigned s = 0; s < 40; s++) { - unsigned sy = oam[(s << 2) + 0] - 9; - unsigned sx = oam[(s << 2) + 1] - 8; + unsigned sy = oam[(s << 2) + 0] - 16; + unsigned sx = oam[(s << 2) + 1] - 8; unsigned tile = oam[(s << 2) + 2]; unsigned attribute = oam[(s << 2) + 3]; - sy -= status.ly; - if(sy >= 8) continue; - if(attribute & 0x40||1) sy ^= 7; + sy = status.ly - sy; + if(sy >= obj_size) continue; + if(attribute & 0x40) sy ^= (obj_size - 1); - unsigned addr = tile * 16 + sy * 2; + unsigned tdaddr = tile * 16 + sy * 2; - uint8 d0 = vram[addr + 0]; - uint8 d1 = vram[addr + 1]; - unsigned xflip = attribute & 0x20 ? -7 : 0; + uint8 d0 = vram[tdaddr + 0]; + uint8 d1 = vram[tdaddr + 1]; + unsigned xflip = attribute & 0x20 ? 7 : 0; - for(unsigned x = 0; x < 8; x++) { + for(unsigned tx = 0; tx < 8; tx++) { uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6); d0 <<= 1, d1 <<= 1; if(palette == 0) continue; + palette = status.obp[(bool)(attribute & 0x10)][palette]; - output[sx + (x ^ xflip)] = (3 - palette) * 0x55; + unsigned ox = sx + (tx ^ xflip); + + if(ox <= 159) { + if((attribute & 0x80) == 1) { + if(line[ox].source == Line::Source::BG && line[ox].output > 0) continue; + } + line[ox].source = Line::Source::OBJ; + line[ox].output = palette; + } } } } @@ -121,21 +207,16 @@ void LCD::reset() { status.bg_tilemap_select = 0; status.obj_size = 0; status.obj_enable = 0; - status.bg_display = 0; + status.bg_enable = 0; status.interrupt_lyc = 0; status.interrupt_oam = 0; status.interrupt_vblank = 0; status.interrupt_hblank = 0; - status.coincidence = 0; - status.mode = 0; status.scy = 0; - status.scx = 0; - status.ly = 0; - status.lyc = 0; for(unsigned n = 0; n < 4; n++) { @@ -145,7 +226,6 @@ void LCD::reset() { } status.wy = 0; - status.wx = 0; } diff --git a/gameboy/lcd/lcd.hpp b/gameboy/lcd/lcd.hpp index d819b3b0..cd3dcc12 100755 --- a/gameboy/lcd/lcd.hpp +++ b/gameboy/lcd/lcd.hpp @@ -12,15 +12,13 @@ struct LCD : Processor, MMIO { bool bg_tilemap_select; bool obj_size; bool obj_enable; - bool bg_display; + bool bg_enable; //$ff41 STAT bool interrupt_lyc; bool interrupt_oam; bool interrupt_vblank; bool interrupt_hblank; - bool coincidence; - unsigned mode; //$ff42 SCY uint8 scy; @@ -52,12 +50,20 @@ struct LCD : Processor, MMIO { uint8 vram[8192]; uint8 oam[160]; + struct Line { + enum class Source : unsigned { None, BG, OBJ, Window } source; + uint8 output; + } line[160]; + static void Main(); void main(); void add_clocks(unsigned clocks); void scanline(); void frame(); void render(); + void render_bg(); + void render_window(); + void render_obj(); void power(); void reset(); diff --git a/gameboy/lcd/mmio/mmio.cpp b/gameboy/lcd/mmio/mmio.cpp index 230ba463..aafef096 100755 --- a/gameboy/lcd/mmio/mmio.cpp +++ b/gameboy/lcd/mmio/mmio.cpp @@ -12,16 +12,21 @@ uint8 LCD::mmio_read(uint16 addr) { | (status.bg_tilemap_select << 3) | (status.obj_size << 2) | (status.obj_enable << 1) - | (status.bg_display << 0); + | (status.bg_enable << 0); } if(addr == 0xff41) { //STAT + unsigned mode; + if(status.ly >= 144) mode = 1; //Vblank + else if(status.lx >= 320) mode = 0; //Hblank + else mode = 3; //LCD transfer + return (status.interrupt_lyc << 6) | (status.interrupt_oam << 5) | (status.interrupt_vblank << 4) | (status.interrupt_hblank << 3) - | (status.coincidence << 2) - | (status.mode << 0); + | ((status.ly == status.lyc) << 2) + | (mode << 0); } if(addr == 0xff42) { //SCY @@ -84,7 +89,7 @@ void LCD::mmio_write(uint16 addr, uint8 data) { status.bg_tilemap_select = data & 0x08; status.obj_size = data & 0x04; status.obj_enable = data & 0x02; - status.bg_display = data & 0x01; + status.bg_enable = data & 0x01; return; } diff --git a/ui/general/main-window.cpp b/ui/general/main-window.cpp index 7aa7541e..9aa81741 100755 --- a/ui/general/main-window.cpp +++ b/ui/general/main-window.cpp @@ -33,7 +33,7 @@ void MainWindow::create() { }; systemLoadCartridge.onTick = []() { - string filename = OS::fileOpen(mainWindow, "Game Boy cartridges\t*.gb", "/media/sdb1/root/gameboy_images/"); + string filename = OS::fileOpen(mainWindow, "Game Boy cartridges\t*.gb,*.gbc", "/media/sdb1/root/gameboy_images/"); if(filename != "") utility.loadCartridge(filename); }; From afdb3c4d202c9d07196087955b3129714ed94b10 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Mon, 3 Jan 2011 15:28:36 +1100 Subject: [PATCH 09/12] Update to release v000r07. byuu says: Changelog: - fixed sprite Vflip check - fixed up window rendering (well, mostly, works great in Megaman II but not so great in Makaitoushi SaGa) - added MBC2, MBC5 (already had MBC0, MBC1) - removed reset, hooked up power cycle and Vsync toggle - some other stuff Makaitoushi SaGa locks on the main menu after some graphical glitches on the title screen, damn. Shin Megami Tensei - Devichil Black Book locks up immediately, hitting HALT opcodes all the time, damn again. Megaman II should be fully playable now. Contra 3 is really close, but goes crazy on the turtle boss fight. --- Makefile | 2 +- gameboy/cartridge/cartridge.cpp | 32 ++++++--- gameboy/cartridge/cartridge.hpp | 20 +++--- gameboy/cartridge/mbc0/mbc0.cpp | 3 - gameboy/cartridge/mbc0/mbc0.hpp | 1 - gameboy/cartridge/mbc1/mbc1.cpp | 3 - gameboy/cartridge/mbc1/mbc1.hpp | 1 - gameboy/cartridge/mbc2/mbc2.cpp | 55 +++++++++++++++ gameboy/cartridge/mbc2/mbc2.hpp | 8 +++ gameboy/cartridge/mbc5/mbc5.cpp | 52 +++++++++++++++ gameboy/cartridge/mbc5/mbc5.hpp | 9 +++ gameboy/cpu/cpu.cpp | 14 ++-- gameboy/cpu/cpu.hpp | 1 - gameboy/gameboy.hpp | 14 ++-- gameboy/lcd/lcd.cpp | 115 ++++++++++++-------------------- gameboy/lcd/lcd.hpp | 8 +-- gameboy/memory/memory.cpp | 2 +- gameboy/scheduler/scheduler.cpp | 2 +- gameboy/system/system.cpp | 10 +-- gameboy/system/system.hpp | 3 +- ui/base.hpp | 2 +- ui/general/main-window.cpp | 14 ++-- ui/general/main-window.hpp | 2 +- ui/main.cpp | 8 +++ 24 files changed, 239 insertions(+), 142 deletions(-) create mode 100755 gameboy/cartridge/mbc2/mbc2.cpp create mode 100755 gameboy/cartridge/mbc2/mbc2.hpp create mode 100755 gameboy/cartridge/mbc5/mbc5.cpp create mode 100755 gameboy/cartridge/mbc5/mbc5.hpp diff --git a/Makefile b/Makefile index 4788f548..e1e5d9e0 100755 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ ui := ui # compiler c := $(compiler) -std=gnu99 cpp := $(subst cc,++,$(compiler)) -std=gnu++0x -flags := -O3 -fomit-frame-pointer -I. -I$(gameboy) +flags := -O3 -fomit-frame-pointer -I. link := objects := diff --git a/gameboy/cartridge/cartridge.cpp b/gameboy/cartridge/cartridge.cpp index 53d3d66a..f7e47556 100755 --- a/gameboy/cartridge/cartridge.cpp +++ b/gameboy/cartridge/cartridge.cpp @@ -1,10 +1,12 @@ -#include +#include #define CARTRIDGE_CPP namespace GameBoy { #include "mbc0/mbc0.cpp" #include "mbc1/mbc1.cpp" +#include "mbc2/mbc2.cpp" +#include "mbc5/mbc5.cpp" Cartridge cartridge; void Cartridge::load(uint8_t *data, unsigned size) { @@ -25,11 +27,20 @@ void Cartridge::load(uint8_t *data, unsigned size) { info.battery = false; info.rtc = false; - switch(romdata[0x0147]) { default: + switch(romdata[0x0147]) { case 0x00: info.mapper = Mapper::MBC0; break; case 0x01: info.mapper = Mapper::MBC1; break; case 0x02: info.mapper = Mapper::MBC1; info.ram = true; break; case 0x03: info.mapper = Mapper::MBC1; info.ram = true; info.battery = true; break; + case 0x05: info.mapper = Mapper::MBC2; info.ram = true; break; + case 0x06: info.mapper = Mapper::MBC2; info.ram = true; info.battery = true; break; + case 0x19: info.mapper = Mapper::MBC5; break; + case 0x1a: info.mapper = Mapper::MBC5; info.ram = true; break; + case 0x1b: info.mapper = Mapper::MBC5; info.ram = true; info.battery = true; break; + case 0x1c: info.mapper = Mapper::MBC5; info.rumble = true; break; + case 0x1d: info.mapper = Mapper::MBC5; info.rumble = true; info.ram = true; break; + case 0x1e: info.mapper = Mapper::MBC5; info.rumble = true; info.ram = true; info.battery = true; break; + default: print("Unknown mapper: ", hex<2>(romdata[0x0147]), "\n"); break; } switch(romdata[0x0148]) { default: @@ -46,7 +57,6 @@ void Cartridge::load(uint8_t *data, unsigned size) { case 0x54: info.romsize = 96 * 16 * 1024; break; } - //TODO: MBC2 always stores 0x00 here; yet it has 512x4-bits RAM switch(romdata[0x0149]) { default: case 0x00: info.ramsize = 0 * 1024; break; case 0x01: info.ramsize = 2 * 1024; break; @@ -54,6 +64,8 @@ void Cartridge::load(uint8_t *data, unsigned size) { case 0x03: info.ramsize = 32 * 1024; break; } + if(info.mapper == Mapper::MBC2) info.ramsize = 256; //512 x 4-bit + ramdata = new uint8_t[ramsize = info.ramsize](); loaded = true; @@ -92,23 +104,21 @@ void Cartridge::ram_write(unsigned addr, uint8 data) { void Cartridge::power() { mbc0.power(); mbc1.power(); + mbc2.power(); + mbc5.power(); MMIO *mapper = 0; - switch(info.mapper) { + switch(info.mapper) { default: case Mapper::MBC0: mapper = &mbc0; break; case Mapper::MBC1: mapper = &mbc1; break; + case Mapper::MBC2: mapper = &mbc2; break; + case Mapper::MBC5: mapper = &mbc5; break; } + if(mapper) { for(unsigned n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = mapper; for(unsigned n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = mapper; } - - reset(); -} - -void Cartridge::reset() { - mbc1.reset(); - mbc1.reset(); } Cartridge::Cartridge() { diff --git a/gameboy/cartridge/cartridge.hpp b/gameboy/cartridge/cartridge.hpp index 03e4a1f2..05eb052b 100755 --- a/gameboy/cartridge/cartridge.hpp +++ b/gameboy/cartridge/cartridge.hpp @@ -1,22 +1,27 @@ struct Cartridge : property { #include "mbc0/mbc0.hpp" #include "mbc1/mbc1.hpp" + #include "mbc2/mbc2.hpp" + #include "mbc5/mbc5.hpp" enum Mapper : unsigned { MBC0, MBC1, + MBC2, + MBC5, Unknown, }; struct Information { - string name; - uint8 cgbflag; - uint8 sgbflag; + string name; + uint8 cgbflag; + uint8 sgbflag; - Mapper mapper; - bool ram; - bool battery; - bool rtc; + Mapper mapper; + bool ram; + bool battery; + bool rtc; + bool rumble; unsigned romsize; unsigned ramsize; @@ -39,7 +44,6 @@ struct Cartridge : property { void ram_write(unsigned addr, uint8 data); void power(); - void reset(); Cartridge(); ~Cartridge(); diff --git a/gameboy/cartridge/mbc0/mbc0.cpp b/gameboy/cartridge/mbc0/mbc0.cpp index 85a8a505..fa9d616d 100755 --- a/gameboy/cartridge/mbc0/mbc0.cpp +++ b/gameboy/cartridge/mbc0/mbc0.cpp @@ -11,7 +11,4 @@ void Cartridge::MBC0::mmio_write(uint16 addr, uint8 data) { void Cartridge::MBC0::power() { } -void Cartridge::MBC0::reset() { -} - #endif diff --git a/gameboy/cartridge/mbc0/mbc0.hpp b/gameboy/cartridge/mbc0/mbc0.hpp index fef9f9a9..da7390ea 100755 --- a/gameboy/cartridge/mbc0/mbc0.hpp +++ b/gameboy/cartridge/mbc0/mbc0.hpp @@ -2,5 +2,4 @@ struct MBC0 : MMIO { uint8 mmio_read(uint16 addr); void mmio_write(uint16 addr, uint8 data); void power(); - void reset(); } mbc0; diff --git a/gameboy/cartridge/mbc1/mbc1.cpp b/gameboy/cartridge/mbc1/mbc1.cpp index fedb6b79..e31997dc 100755 --- a/gameboy/cartridge/mbc1/mbc1.cpp +++ b/gameboy/cartridge/mbc1/mbc1.cpp @@ -47,9 +47,6 @@ void Cartridge::MBC1::mmio_write(uint16 addr, uint8 data) { } void Cartridge::MBC1::power() { -} - -void Cartridge::MBC1::reset() { ram_enable = false; rom_select = 0x01; ram_select = 0x00; diff --git a/gameboy/cartridge/mbc1/mbc1.hpp b/gameboy/cartridge/mbc1/mbc1.hpp index 516acde8..ccbb1bf3 100755 --- a/gameboy/cartridge/mbc1/mbc1.hpp +++ b/gameboy/cartridge/mbc1/mbc1.hpp @@ -10,5 +10,4 @@ struct MBC1 : MMIO { uint8 mmio_read(uint16 addr); void mmio_write(uint16 addr, uint8 data); void power(); - void reset(); } mbc1; diff --git a/gameboy/cartridge/mbc2/mbc2.cpp b/gameboy/cartridge/mbc2/mbc2.cpp new file mode 100755 index 00000000..491b21d6 --- /dev/null +++ b/gameboy/cartridge/mbc2/mbc2.cpp @@ -0,0 +1,55 @@ +#ifdef CARTRIDGE_CPP + +uint8 Cartridge::MBC2::mmio_read(uint16 addr) { + if(addr >= 0x0000 && addr <= 0x3fff) { + return cartridge.rom_read(addr); + } + + if(addr >= 0x4000 && addr <= 0x7fff) { + return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff)); + } + + if(addr >= 0xa000 && addr <= 0xa1ff) { + if(ram_enable) { + uint8 data = cartridge.ram_read(addr & 0x1ff); + return (addr & 1) == 0 ? ((data >> 4) & 0x0f) : ((data >> 0) & 0x0f); + } + return 0x00; + } + + return 0x00; +} + +void Cartridge::MBC2::mmio_write(uint16 addr, uint8 data) { + if(addr >= 0x0000 && addr <= 0x1fff) { + if((addr & 0x100) == 0) { + ram_enable = (data & 0x0f) == 0x0a; + } + } + + if(addr >= 0x2000 && addr <= 0x3fff) { + if(addr & 0x100) { + rom_select = data & 0x0f; + if(rom_select == 0) rom_select = 1; + } + } + + if(addr >= 0xa000 && addr <= 0xa1ff) { + if(ram_enable) { + addr &= 511; + if((addr & 1) == 0) { + cartridge.ram_write(addr, (cartridge.ram_read(addr) & 0x0f) | (data << 4)); + } else { + cartridge.ram_write(addr, (cartridge.ram_read(addr) & 0xf0) | (data << 0)); + } + return; + } + } +} + +void Cartridge::MBC2::power() { + ram_enable = false; + rom_select = 0x01; +} + +#endif diff --git a/gameboy/cartridge/mbc2/mbc2.hpp b/gameboy/cartridge/mbc2/mbc2.hpp new file mode 100755 index 00000000..be9b7db6 --- /dev/null +++ b/gameboy/cartridge/mbc2/mbc2.hpp @@ -0,0 +1,8 @@ +struct MBC2 : MMIO { + bool ram_enable; //0000-1fff + uint8 rom_select; //2000-3fff + + uint8 mmio_read(uint16 addr); + void mmio_write(uint16 addr, uint8 data); + void power(); +} mbc2; diff --git a/gameboy/cartridge/mbc5/mbc5.cpp b/gameboy/cartridge/mbc5/mbc5.cpp new file mode 100755 index 00000000..0d209edf --- /dev/null +++ b/gameboy/cartridge/mbc5/mbc5.cpp @@ -0,0 +1,52 @@ +#ifdef CARTRIDGE_CPP + +uint8 Cartridge::MBC5::mmio_read(uint16 addr) { + if(addr >= 0x0000 && addr <= 0x3fff) { + return cartridge.rom_read(addr); + } + + if(addr >= 0x4000 && addr <= 0x7fff) { + return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff)); + } + + if(addr >= 0xa000 && addr <= 0xbfff) { + if(ram_enable) { + return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff)); + } + return 0x00; + } + + return 0x00; +} + +void Cartridge::MBC5::mmio_write(uint16 addr, uint8 data) { + if(addr >= 0x0000 && addr <= 0x1fff) { + ram_enable = (data & 0x0f) == 0x0a; + } + + if(addr >= 0x2000 && addr <= 0x2fff) { + rom_select = (rom_select & 0x0100) | data; + } + + if(addr >= 0x3000 && addr <= 0x3fff) { + rom_select = ((data & 1) << 8) | (rom_select & 0x00ff); + } + + if(addr >= 0x4000 && addr <= 0x5fff) { + ram_select = data & 0x0f; + } + + if(addr >= 0xa000 && addr <= 0xbfff) { + if(ram_enable) { + cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data); + } + } +} + +void Cartridge::MBC5::power() { + ram_enable = false; + rom_select = 0x001; + ram_select = 0x00; +} + +#endif diff --git a/gameboy/cartridge/mbc5/mbc5.hpp b/gameboy/cartridge/mbc5/mbc5.hpp new file mode 100755 index 00000000..7d5b0a30 --- /dev/null +++ b/gameboy/cartridge/mbc5/mbc5.hpp @@ -0,0 +1,9 @@ +struct MBC5 : MMIO { + bool ram_enable; //0000-1fff + uint16 rom_select; //2000-2fff + 3000-3fff + uint8 ram_select; //4000-5fff + + uint8 mmio_read(uint16 addr); + void mmio_write(uint16 addr, uint8 data); + void power(); +} mbc5; diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp index f01523e4..e6739fc8 100755 --- a/gameboy/cpu/cpu.cpp +++ b/gameboy/cpu/cpu.cpp @@ -1,4 +1,4 @@ -#include +#include #define CPU_CPP namespace GameBoy { @@ -67,9 +67,13 @@ void CPU::interrupt_exec(uint16 pc) { op_write(--r[SP], r[PC] >> 0); r[PC] = pc; op_io(); + op_io(); + op_io(); } void CPU::power() { + create(Main, 4 * 1024 * 1024); + for(unsigned n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM for(unsigned n = 0xe000; n <= 0xfdff; n++) bus.mmio[n] = this; //WRAM (mirror) for(unsigned n = 0xff00; n <= 0xff0f; n++) bus.mmio[n] = this; //MMIO @@ -78,15 +82,9 @@ void CPU::power() { for(unsigned n = 0; n < 8192; n++) wram[n] = 0x00; for(unsigned n = 0; n < 128; n++) hram[n] = 0x00; - reset(); -} - -void CPU::reset() { - create(Main, 4 * 1024 * 1024); - r[PC] = 0x0100; r[SP] = 0xfffe; - r[AF] = 0x0000; + r[AF] = 0x0100; r[BC] = 0x0000; r[DE] = 0x0000; r[HL] = 0x0000; diff --git a/gameboy/cpu/cpu.hpp b/gameboy/cpu/cpu.hpp index 7d410b35..dc92a37c 100755 --- a/gameboy/cpu/cpu.hpp +++ b/gameboy/cpu/cpu.hpp @@ -63,7 +63,6 @@ struct CPU : Processor, MMIO { void interrupt_test(); void interrupt_exec(uint16 pc); void power(); - void reset(); CPU(); }; diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp index 13c62158..777a7980 100755 --- a/gameboy/gameboy.hpp +++ b/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000.06"; + static const char Version[] = "000.07"; } } @@ -43,10 +43,10 @@ namespace GameBoy { inline Processor() : thread(0) {} }; - #include - #include - #include - #include - #include - #include + #include + #include + #include + #include + #include + #include }; diff --git a/gameboy/lcd/lcd.cpp b/gameboy/lcd/lcd.cpp index abcf26ad..f29efea3 100755 --- a/gameboy/lcd/lcd.cpp +++ b/gameboy/lcd/lcd.cpp @@ -1,4 +1,4 @@ -#include +#include #define LCD_CPP namespace GameBoy { @@ -55,93 +55,66 @@ void LCD::frame() { } void LCD::render() { - for(unsigned n = 0; n < 160; n++) { - line[n].source = Line::Source::None; - line[n].output = 0; - } + for(unsigned n = 0; n < 160; n++) line[n] = 0x00; if(status.display_enable == true) { if(status.bg_enable == true) render_bg(); - if(status.obj_enable == true) render_obj(); if(status.window_display_enable == true) render_window(); + if(status.obj_enable == true) render_obj(); } uint8_t *output = screen + status.ly * 160; - for(unsigned n = 0; n < 160; n++) { - output[n] = (3 - line[n].output) * 0x55; + for(unsigned n = 0; n < 160; n++) output[n] = (3 - line[n]) * 0x55; +} + +uint16 LCD::read_tile(bool select, unsigned x, unsigned y) { + unsigned tmaddr = 0x1800 + (select << 10), tdaddr; + tmaddr += (((y >> 3) << 5) + (x >> 3)) & 0x03ff; + if(status.bg_tiledata_select == 0) { + tdaddr = 0x1000 + ((int8)vram[tmaddr] << 4); + } else { + tdaddr = 0x0000 + (vram[tmaddr] << 4); } + tdaddr += (y & 7) << 1; + return (vram[tdaddr + 0] << 0) | (vram[tdaddr + 1] << 8); } void LCD::render_bg() { unsigned iy = (status.ly + status.scy) & 255; - unsigned ix = status.scx; + unsigned ix = status.scx, tx = ix & 7; + uint8 mask = 0x80 >> tx; + unsigned data = read_tile(status.bg_tilemap_select, ix, iy), palette; - unsigned tmaddr = (status.bg_tilemap_select == 0 ? 0x1800 : 0x1c00); - - unsigned tx = (ix - 7) & 7; - uint8 d0 = 0, d1 = 0; - for(signed ox = -7; ox < 160; ox++) { - if(tx == 0) { - unsigned tile = (((iy >> 3) * 32) + (ix >> 3)) & 0x3fff; - unsigned tdaddr; - if(status.bg_tiledata_select == 0) { - tdaddr = 0x1000 + (int8)vram[tmaddr + tile] * 16; - } else { - tdaddr = 0x0000 + vram[tmaddr + tile] * 16; - } - tdaddr += (iy & 7) * 2; - - d0 = vram[tdaddr + 0]; - d1 = vram[tdaddr + 1]; - } - - uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6); - d0 <<= 1, d1 <<= 1; - - if(ox >= 0) { - line[ox].source = Line::Source::BG; - line[ox].output = status.bgp[palette]; - } + for(unsigned ox = 0; ox < 160; ox++) { + palette = ((data & (mask << 0)) ? 1 : 0); + palette |= ((data & (mask << 8)) ? 2 : 0); + mask = (mask >> 1) | (mask << 7); + line[ox] = status.bgp[palette]; ix = (ix + 1) & 255; tx = (tx + 1) & 7; + + if(tx == 0) data = read_tile(status.bg_tilemap_select, ix, iy); } } void LCD::render_window() { - if(status.wy > status.ly) return; - unsigned iy = (status.ly + status.wy) & 255; - unsigned ix = (status.wx - 7) & 255; + if(status.ly - status.wy >= 144U) return; + unsigned iy = status.ly - status.wy; + unsigned ix = (status.wx - 7) & 255, tx = ix & 7; + uint8 mask = 0x80 >> tx; + unsigned data = read_tile(status.window_tilemap_select, ix, iy), palette; - unsigned tmaddr = (status.window_tilemap_select == 0 ? 0x1800 : 0x1c00); - - unsigned tx = (ix - 7) & 7; - uint8 d0 = 0, d1 = 0; - for(signed ox = -7; ox < 160; ox++) { - if(tx == 0) { - unsigned tile = (((iy >> 3) * 32) + (ix >> 3)) & 0x3fff; - unsigned tdaddr; - if(status.bg_tiledata_select == 0) { - tdaddr = 0x1000 + (int8)vram[tmaddr + tile] * 16; - } else { - tdaddr = 0x0000 + vram[tmaddr + tile] * 16; - } - tdaddr += (iy & 7) * 2; - - d0 = vram[tdaddr + 0]; - d1 = vram[tdaddr + 1]; - } - - uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6); - d0 <<= 1, d1 <<= 1; - - if(ox >= 7) { - line[ox].source = Line::Source::Window; - line[ox].output = status.bgp[palette]; - } + for(unsigned ox = 0; ox < 160; ox++) { + palette = ((data & (mask << 0)) ? 1 : 0); + palette |= ((data & (mask << 8)) ? 2 : 0); + mask = (mask >> 1) | (mask << 7); + if(ox - (status.wx - 7) < 160U) line[ox] = status.bgp[palette]; ix = (ix + 1) & 255; tx = (tx + 1) & 7; + + if(tx == 0) data = read_tile(status.window_tilemap_select, ix, iy); } } @@ -173,17 +146,18 @@ void LCD::render_obj() { unsigned ox = sx + (tx ^ xflip); if(ox <= 159) { - if((attribute & 0x80) == 1) { - if(line[ox].source == Line::Source::BG && line[ox].output > 0) continue; + if(attribute & 0x80) { + if(line[ox] > 0) continue; } - line[ox].source = Line::Source::OBJ; - line[ox].output = palette; + line[ox] = palette; } } } } void LCD::power() { + create(Main, 4 * 1024 * 1024); + for(unsigned n = 0x8000; n <= 0x9fff; n++) bus.mmio[n] = this; //VRAM for(unsigned n = 0xff40; n <= 0xff4b; n++) bus.mmio[n] = this; //MMIO for(unsigned n = 0xfe00; n <= 0xfe9f; n++) bus.mmio[n] = this; //OAM @@ -191,11 +165,6 @@ void LCD::power() { for(unsigned n = 0; n < 8192; n++) vram[n] = 0x00; for(unsigned n = 0; n < 160; n++) oam [n] = 0x00; - reset(); -} - -void LCD::reset() { - create(Main, 4 * 1024 * 1024); for(unsigned n = 0; n < 160 * 144; n++) screen[n] = 0x00; status.lx = 0; diff --git a/gameboy/lcd/lcd.hpp b/gameboy/lcd/lcd.hpp index cd3dcc12..a3c72525 100755 --- a/gameboy/lcd/lcd.hpp +++ b/gameboy/lcd/lcd.hpp @@ -49,11 +49,7 @@ struct LCD : Processor, MMIO { uint8 screen[160 * 144]; uint8 vram[8192]; uint8 oam[160]; - - struct Line { - enum class Source : unsigned { None, BG, OBJ, Window } source; - uint8 output; - } line[160]; + uint8 line[160]; static void Main(); void main(); @@ -61,12 +57,12 @@ struct LCD : Processor, MMIO { void scanline(); void frame(); void render(); + uint16 read_tile(bool select, unsigned x, unsigned y); void render_bg(); void render_window(); void render_obj(); void power(); - void reset(); }; extern LCD lcd; diff --git a/gameboy/memory/memory.cpp b/gameboy/memory/memory.cpp index 00998f19..62ffe162 100755 --- a/gameboy/memory/memory.cpp +++ b/gameboy/memory/memory.cpp @@ -1,4 +1,4 @@ -#include +#include #define MEMORY_CPP namespace GameBoy { diff --git a/gameboy/scheduler/scheduler.cpp b/gameboy/scheduler/scheduler.cpp index e8a98068..2f32cea5 100755 --- a/gameboy/scheduler/scheduler.cpp +++ b/gameboy/scheduler/scheduler.cpp @@ -1,4 +1,4 @@ -#include +#include #define SCHEDULER_CPP namespace GameBoy { diff --git a/gameboy/system/system.cpp b/gameboy/system/system.cpp index 1fb68c9a..360c4a3b 100755 --- a/gameboy/system/system.cpp +++ b/gameboy/system/system.cpp @@ -1,4 +1,4 @@ -#include +#include #define SYSTEM_CPP namespace GameBoy { @@ -17,14 +17,6 @@ void System::power() { scheduler.init(); } -void System::reset() { - bus.reset(); - cartridge.power(); - cpu.reset(); - lcd.reset(); - scheduler.init(); -} - void System::run() { scheduler.enter(); } diff --git a/gameboy/system/system.hpp b/gameboy/system/system.hpp index f9a6011c..4bbb7040 100755 --- a/gameboy/system/system.hpp +++ b/gameboy/system/system.hpp @@ -8,13 +8,12 @@ class System { public: void init(Interface*); void power(); - void reset(); void run(); //private: Interface *interface; }; -#include +#include extern System system; diff --git a/ui/base.hpp b/ui/base.hpp index 66eb11ad..1024ef22 100755 --- a/ui/base.hpp +++ b/ui/base.hpp @@ -10,7 +10,7 @@ using namespace ruby; #include using namespace phoenix; -#include +#include #include "interface.hpp" diff --git a/ui/general/main-window.cpp b/ui/general/main-window.cpp index 9aa81741..0fc97cf8 100755 --- a/ui/general/main-window.cpp +++ b/ui/general/main-window.cpp @@ -9,12 +9,10 @@ void MainWindow::create() { systemLoadCartridge.create(system, "Load Cartridge ..."); systemSeparator1.create(system); systemPower.create(system, "Power Cycle"); - systemPower.setEnabled(false); - systemReset.create(system, "Reset"); - systemReset.setEnabled(false); settings.create(*this, "Settings"); -//settings.setEnabled(false); + settingsVideoSync.create(settings, "Synchronize Video"); + settingsVideoSync.setChecked(true); tools.create(*this, "Tools"); //tools.setEnabled(false); @@ -37,6 +35,14 @@ void MainWindow::create() { if(filename != "") utility.loadCartridge(filename); }; + systemPower.onTick = []() { + if(GameBoy::cartridge.loaded()) GameBoy::system.power(); + }; + + settingsVideoSync.onTick = []() { + video.set(Video::Synchronize, mainWindow.settingsVideoSync.checked()); + }; + helpAbout.onTick = []() { MessageWindow::information(mainWindow, { "bgameboy\n\n", diff --git a/ui/general/main-window.hpp b/ui/general/main-window.hpp index b9d965ad..f8ca8de5 100755 --- a/ui/general/main-window.hpp +++ b/ui/general/main-window.hpp @@ -3,9 +3,9 @@ struct MainWindow : Window { MenuItem systemLoadCartridge; MenuSeparator systemSeparator1; MenuItem systemPower; - MenuItem systemReset; Menu settings; + MenuCheckItem settingsVideoSync; Menu tools; diff --git a/ui/main.cpp b/ui/main.cpp index f617896d..4fcc6e4c 100755 --- a/ui/main.cpp +++ b/ui/main.cpp @@ -22,13 +22,21 @@ void Application::main(int argc, char **argv) { mainWindow.setVisible(); OS::run(); + #if defined(PHOENIX_WINDOWS) + video.driver("Direct3D"); + #else video.driver("OpenGL"); + #endif video.set(Video::Handle, (uintptr_t)mainWindow.viewport.handle()); video.set(Video::Synchronize, true); video.set(Video::Filter, (unsigned)0); video.init(); + #if defined(PHOENIX_WINDOWS) + input.driver("RawInput"); + #else input.driver("SDL"); + #endif input.set(Input::Handle, (uintptr_t)mainWindow.viewport.handle()); input.init(); From a7ffbd784b9f59d248df8e680ae4e8cc5543308b Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Tue, 4 Jan 2011 21:42:27 +1100 Subject: [PATCH 10/12] Update to release v000r08. byuu says: Changelog: - rewrote sprite rendering, grabs first ten sprites, draws them in revere order of a: X-index, b: OAM appearance order - simplified tile decoding to use less variables - added MBC3 emulation (for now, RTC is always enabled) - STOP can be broken via joypad IF, this may not be correct though (it may trigger even without P10-13 being modified) - cleaned up all MBC emulation to use masks instead of ranges - MBC2 uses 512-byte table now, ignores high 4-bits. Easier this way I guess - tools menu temporarily has a console tracer enable option - some other stuff No real visible improvements :( --- gameboy/cartridge/cartridge.cpp | 12 +++- gameboy/cartridge/cartridge.hpp | 2 + gameboy/cartridge/mbc0/mbc0.cpp | 13 +++- gameboy/cartridge/mbc1/mbc1.cpp | 59 +++++++++------- gameboy/cartridge/mbc1/mbc1.hpp | 3 - gameboy/cartridge/mbc2/mbc2.cpp | 39 ++++------- gameboy/cartridge/mbc3/mbc3.cpp | 120 ++++++++++++++++++++++++++++++++ gameboy/cartridge/mbc3/mbc3.hpp | 24 +++++++ gameboy/cartridge/mbc5/mbc5.cpp | 29 ++++---- gameboy/cpu/cpu.cpp | 7 +- gameboy/cpu/cpu.hpp | 3 + gameboy/cpu/timing/timing.cpp | 6 ++ gameboy/gameboy.hpp | 2 +- gameboy/lcd/lcd.cpp | 56 ++++++++++----- ui/general/main-window.cpp | 6 +- ui/general/main-window.hpp | 1 + 16 files changed, 292 insertions(+), 90 deletions(-) create mode 100755 gameboy/cartridge/mbc3/mbc3.cpp create mode 100755 gameboy/cartridge/mbc3/mbc3.hpp diff --git a/gameboy/cartridge/cartridge.cpp b/gameboy/cartridge/cartridge.cpp index f7e47556..aaf2c30d 100755 --- a/gameboy/cartridge/cartridge.cpp +++ b/gameboy/cartridge/cartridge.cpp @@ -6,6 +6,7 @@ namespace GameBoy { #include "mbc0/mbc0.cpp" #include "mbc1/mbc1.cpp" #include "mbc2/mbc2.cpp" +#include "mbc3/mbc3.cpp" #include "mbc5/mbc5.cpp" Cartridge cartridge; @@ -34,6 +35,13 @@ void Cartridge::load(uint8_t *data, unsigned size) { case 0x03: info.mapper = Mapper::MBC1; info.ram = true; info.battery = true; break; case 0x05: info.mapper = Mapper::MBC2; info.ram = true; break; case 0x06: info.mapper = Mapper::MBC2; info.ram = true; info.battery = true; break; + case 0x08: info.mapper = Mapper::MBC0; info.ram = true; break; + case 0x09: info.mapper = Mapper::MBC0; info.ram = true; info.battery = true; break; + case 0x0f: info.mapper = Mapper::MBC3; info.rtc = true; info.battery = true; break; + case 0x10: info.mapper = Mapper::MBC3; info.rtc = true; info.ram = true; info.battery = true; break; + case 0x11: info.mapper = Mapper::MBC3; break; + case 0x12: info.mapper = Mapper::MBC3; info.ram = true; break; + case 0x13: info.mapper = Mapper::MBC3; info.ram = true; info.battery = true; break; case 0x19: info.mapper = Mapper::MBC5; break; case 0x1a: info.mapper = Mapper::MBC5; info.ram = true; break; case 0x1b: info.mapper = Mapper::MBC5; info.ram = true; info.battery = true; break; @@ -64,7 +72,7 @@ void Cartridge::load(uint8_t *data, unsigned size) { case 0x03: info.ramsize = 32 * 1024; break; } - if(info.mapper == Mapper::MBC2) info.ramsize = 256; //512 x 4-bit + if(info.mapper == Mapper::MBC2) info.ramsize = 512; //512 x 4-bit ramdata = new uint8_t[ramsize = info.ramsize](); @@ -105,6 +113,7 @@ void Cartridge::power() { mbc0.power(); mbc1.power(); mbc2.power(); + mbc3.power(); mbc5.power(); MMIO *mapper = 0; @@ -112,6 +121,7 @@ void Cartridge::power() { case Mapper::MBC0: mapper = &mbc0; break; case Mapper::MBC1: mapper = &mbc1; break; case Mapper::MBC2: mapper = &mbc2; break; + case Mapper::MBC3: mapper = &mbc3; break; case Mapper::MBC5: mapper = &mbc5; break; } diff --git a/gameboy/cartridge/cartridge.hpp b/gameboy/cartridge/cartridge.hpp index 05eb052b..b04d16a0 100755 --- a/gameboy/cartridge/cartridge.hpp +++ b/gameboy/cartridge/cartridge.hpp @@ -2,12 +2,14 @@ struct Cartridge : property { #include "mbc0/mbc0.hpp" #include "mbc1/mbc1.hpp" #include "mbc2/mbc2.hpp" + #include "mbc3/mbc3.hpp" #include "mbc5/mbc5.hpp" enum Mapper : unsigned { MBC0, MBC1, MBC2, + MBC3, MBC5, Unknown, }; diff --git a/gameboy/cartridge/mbc0/mbc0.cpp b/gameboy/cartridge/mbc0/mbc0.cpp index fa9d616d..46461b77 100755 --- a/gameboy/cartridge/mbc0/mbc0.cpp +++ b/gameboy/cartridge/mbc0/mbc0.cpp @@ -1,11 +1,22 @@ #ifdef CARTRIDGE_CPP uint8 Cartridge::MBC0::mmio_read(uint16 addr) { - if(addr >= 0x0000 && addr <= 0x7fff) return cartridge.rom_read(addr); + if((addr & 0x8000) == 0x0000) { //0000-7fff + return cartridge.rom_read(addr); + } + + if((addr & 0xe000) == 0xa000) { //a000-bfff + return cartridge.ram_read(addr & 0x1fff); + } + return 0x00; } void Cartridge::MBC0::mmio_write(uint16 addr, uint8 data) { + if((addr & 0xe000) == 0xa000) { //a000-bfff + cartridge.ram_write(addr & 0x1fff, data); + return; + } } void Cartridge::MBC0::power() { diff --git a/gameboy/cartridge/mbc1/mbc1.cpp b/gameboy/cartridge/mbc1/mbc1.cpp index e31997dc..50622e82 100755 --- a/gameboy/cartridge/mbc1/mbc1.cpp +++ b/gameboy/cartridge/mbc1/mbc1.cpp @@ -1,48 +1,62 @@ #ifdef CARTRIDGE_CPP uint8 Cartridge::MBC1::mmio_read(uint16 addr) { - if(addr >= 0x0000 && addr <= 0x3fff) { + if((addr & 0xc000) == 0x0000) { //0000-3fff return cartridge.rom_read(addr); } - if(addr >= 0x4000 && addr <= 0x7fff) { - return cartridge.rom_read(rom_bank | (addr & 0x3fff)); + if((addr & 0xc000) == 0x4000) { //4000-7fff + if(mode_select == 0) { + return cartridge.rom_read((ram_select << 19) | (rom_select << 14) | (addr & 0x3fff)); + } else { + return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff)); + } } - if(addr >= 0xa000 && addr <= 0xbfff) { - if(ram_enable) return cartridge.ram_read(ram_bank | (addr & 0x1fff)); + if((addr & 0xe000) == 0xa000) { //a000-bfff + if(ram_enable) { + if(mode_select == 0) { + return cartridge.ram_read(addr & 0x1fff); + } else { + return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff)); + } + } return 0x00; } + + return 0x00; } void Cartridge::MBC1::mmio_write(uint16 addr, uint8 data) { - if(addr >= 0x0000 && addr <= 0x1fff) { + if((addr & 0xe000) == 0x0000) { //0000-1fff ram_enable = (data & 0x0f) == 0x0a; + return; } - if(addr >= 0x2000 && addr <= 0x3fff) { - rom_select = data & 0x1f; - if(rom_select == 0) rom_select = 1; + if((addr & 0xe000) == 0x2000) { //2000-3fff + rom_select = (data & 0x1f) + ((data & 0x1f) == 0); + return; } - if(addr >= 0x4000 && addr <= 0x5fff) { + if((addr & 0xe000) == 0x4000) { //4000-5fff ram_select = data & 0x03; + return; } - if(addr >= 0x6000 && addr <= 0x7fff) { + if((addr & 0xe000) == 0x6000) { //6000-7fff mode_select = data & 0x01; + return; } - if(mode_select == 0) { - rom_bank = (ram_select << 19) | (rom_select << 14); - ram_bank = 0x00; - } else { - rom_bank = (rom_select << 14); - ram_bank = (ram_select << 13); - } - - if(addr >= 0xa000 && addr <= 0xbfff) { - if(ram_enable) cartridge.ram_write(ram_bank | (addr & 0x1fff), data); + if((addr & 0xe000) == 0xa000) { //a000-bfff + if(ram_enable) { + if(mode_select == 0) { + cartridge.ram_write(addr & 0x1fff, data); + } else { + cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data); + } + } + return; } } @@ -51,9 +65,6 @@ void Cartridge::MBC1::power() { rom_select = 0x01; ram_select = 0x00; mode_select = 0; - - rom_bank = 0x4000; - ram_bank = 0x0000; } #endif diff --git a/gameboy/cartridge/mbc1/mbc1.hpp b/gameboy/cartridge/mbc1/mbc1.hpp index ccbb1bf3..099cf43d 100755 --- a/gameboy/cartridge/mbc1/mbc1.hpp +++ b/gameboy/cartridge/mbc1/mbc1.hpp @@ -4,9 +4,6 @@ struct MBC1 : MMIO { uint8 ram_select; //4000-5fff bool mode_select; //6000-7fff - unsigned rom_bank; - unsigned ram_bank; - uint8 mmio_read(uint16 addr); void mmio_write(uint16 addr, uint8 data); void power(); diff --git a/gameboy/cartridge/mbc2/mbc2.cpp b/gameboy/cartridge/mbc2/mbc2.cpp index 491b21d6..3b952c78 100755 --- a/gameboy/cartridge/mbc2/mbc2.cpp +++ b/gameboy/cartridge/mbc2/mbc2.cpp @@ -1,19 +1,16 @@ #ifdef CARTRIDGE_CPP uint8 Cartridge::MBC2::mmio_read(uint16 addr) { - if(addr >= 0x0000 && addr <= 0x3fff) { + if((addr & 0xc000) == 0x0000) { //0000-3fff return cartridge.rom_read(addr); } - if(addr >= 0x4000 && addr <= 0x7fff) { + if((addr & 0xc000) == 0x4000) { //4000-7fff return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff)); } - if(addr >= 0xa000 && addr <= 0xa1ff) { - if(ram_enable) { - uint8 data = cartridge.ram_read(addr & 0x1ff); - return (addr & 1) == 0 ? ((data >> 4) & 0x0f) : ((data >> 0) & 0x0f); - } + if((addr & 0xfe00) == 0xa000) { //a000-a1ff + if(ram_enable) return cartridge.ram_read(addr & 0x1ff); return 0x00; } @@ -21,29 +18,19 @@ uint8 Cartridge::MBC2::mmio_read(uint16 addr) { } void Cartridge::MBC2::mmio_write(uint16 addr, uint8 data) { - if(addr >= 0x0000 && addr <= 0x1fff) { - if((addr & 0x100) == 0) { - ram_enable = (data & 0x0f) == 0x0a; - } + if((addr & 0xe100) == 0x0000) { //0000-1fff [d8=0] + ram_enable = (data & 0x0f) == 0x0a; + return; } - if(addr >= 0x2000 && addr <= 0x3fff) { - if(addr & 0x100) { - rom_select = data & 0x0f; - if(rom_select == 0) rom_select = 1; - } + if((addr & 0xe100) == 0x2100) { //2000-3fff [d8=1] + rom_select = (data & 0x0f) + ((data & 0x0f) == 0); + return; } - if(addr >= 0xa000 && addr <= 0xa1ff) { - if(ram_enable) { - addr &= 511; - if((addr & 1) == 0) { - cartridge.ram_write(addr, (cartridge.ram_read(addr) & 0x0f) | (data << 4)); - } else { - cartridge.ram_write(addr, (cartridge.ram_read(addr) & 0xf0) | (data << 0)); - } - return; - } + if((addr & 0xfe00) == 0xa000) { //a000-a1ff + if(ram_enable) cartridge.ram_write(addr & 0x1ff, data & 0x0f); + return; } } diff --git a/gameboy/cartridge/mbc3/mbc3.cpp b/gameboy/cartridge/mbc3/mbc3.cpp new file mode 100755 index 00000000..709970dc --- /dev/null +++ b/gameboy/cartridge/mbc3/mbc3.cpp @@ -0,0 +1,120 @@ +#ifdef CARTRIDGE_CPP + +void Cartridge::MBC3::second() { + if(rtc_halt == false) { + if(++rtc_second >= 60) { + rtc_second = 0; + if(++rtc_minute >= 60) { + rtc_minute = 0; + if(++rtc_hour >= 24) { + rtc_hour = 0; + if(++rtc_day >= 512) { + rtc_day = 0; + rtc_day_carry = true; + } + } + } + } + } +} + +uint8 Cartridge::MBC3::mmio_read(uint16 addr) { + if((addr & 0xc000) == 0x0000) { //0000-3fff + return cartridge.rom_read(addr); + } + + if((addr & 0xc000) == 0x4000) { //4000-7fff + return cartridge.rom_read((rom_select << 13) | (addr & 0x3fff)); + } + + if((addr & 0xe000) == 0xa000) { //a000-bfff + if(ram_enable) { + if(ram_select >= 0x00 && ram_select <= 0x03) { + return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff)); + } + if(ram_select == 0x08) return rtc_latch_second; + if(ram_select == 0x09) return rtc_latch_minute; + if(ram_select == 0x0a) return rtc_latch_hour; + if(ram_select == 0x0b) return rtc_latch_day; + if(ram_select == 0x0c) return (rtc_latch_day_carry << 7) | (rtc_latch_day >> 8); + } + return 0x00; + } + + return 0x00; +} + +void Cartridge::MBC3::mmio_write(uint16 addr, uint8 data) { + if((addr & 0xe000) == 0x0000) { //0000-1fff + ram_enable = (data & 0x0f) == 0x0a; + return; + } + + if((addr & 0xe000) == 0x2000) { //2000-3fff + rom_select = (data & 0x7f) + ((data & 0x7f) == 0); + return; + } + + if((addr & 0xe000) == 0x4000) { //4000-5fff + ram_select = data; + return; + } + + if((addr & 0xe000) == 0x6000) { //6000-7fff + if(rtc_latch == 0 && data == 1) { + rtc_latch_second = rtc_second; + rtc_latch_minute = rtc_minute; + rtc_latch_hour = rtc_hour; + rtc_latch_day = rtc_day; + rtc_latch_day_carry = rtc_day_carry; + } + rtc_latch = data; + return; + } + + if((addr & 0xe000) == 0xa000) { //a000-bfff + if(ram_enable) { + if(ram_select >= 0x00 && ram_select <= 0x03) { + cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data); + } else if(ram_select == 0x08) { + if(data >= 60) data = 0; + rtc_second = data; + } else if(ram_select == 0x09) { + if(data >= 60) data = 0; + rtc_minute = data; + } else if(ram_select == 0x0a) { + if(data >= 24) data = 0; + rtc_hour = data; + } else if(ram_select == 0x0b) { + rtc_day = (rtc_day & 0x0100) | data; + } else if(ram_select == 0x0c) { + rtc_day = ((data & 1) << 8) | (rtc_day & 0xff); + rtc_halt = data & 0x40; + rtc_day_carry = data & 0x80; + } + } + return; + } +} + +void Cartridge::MBC3::power() { + ram_enable = false; + rom_select = 0x01; + ram_select = 0x00; + rtc_latch = 0; + + rtc_halt = true; + rtc_second = 0; + rtc_minute = 0; + rtc_hour = 0; + rtc_day = 0; + rtc_day_carry = false; + + rtc_latch_second = 0; + rtc_latch_minute = 0; + rtc_latch_hour = 0; + rtc_latch_day = 0; + rtc_latch_day_carry = false; +} + +#endif diff --git a/gameboy/cartridge/mbc3/mbc3.hpp b/gameboy/cartridge/mbc3/mbc3.hpp new file mode 100755 index 00000000..d83ba182 --- /dev/null +++ b/gameboy/cartridge/mbc3/mbc3.hpp @@ -0,0 +1,24 @@ +struct MBC3 : MMIO { + bool ram_enable; //0000-1fff + uint8 rom_select; //2000-3fff + uint8 ram_select; //4000-5fff + bool rtc_latch; //6000-7fff + + bool rtc_halt; + unsigned rtc_second; + unsigned rtc_minute; + unsigned rtc_hour; + unsigned rtc_day; + bool rtc_day_carry; + + unsigned rtc_latch_second; + unsigned rtc_latch_minute; + unsigned rtc_latch_hour; + unsigned rtc_latch_day; + unsigned rtc_latch_day_carry; + + void second(); + uint8 mmio_read(uint16 addr); + void mmio_write(uint16 addr, uint8 data); + void power(); +} mbc3; diff --git a/gameboy/cartridge/mbc5/mbc5.cpp b/gameboy/cartridge/mbc5/mbc5.cpp index 0d209edf..263e5125 100755 --- a/gameboy/cartridge/mbc5/mbc5.cpp +++ b/gameboy/cartridge/mbc5/mbc5.cpp @@ -1,18 +1,16 @@ #ifdef CARTRIDGE_CPP uint8 Cartridge::MBC5::mmio_read(uint16 addr) { - if(addr >= 0x0000 && addr <= 0x3fff) { + if((addr & 0xc000) == 0x0000) { //0000-3fff return cartridge.rom_read(addr); } - if(addr >= 0x4000 && addr <= 0x7fff) { + if((addr & 0xc000) == 0x4000) { //4000-7fff return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff)); } - if(addr >= 0xa000 && addr <= 0xbfff) { - if(ram_enable) { - return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff)); - } + if((addr & 0xe000) == 0xa000) { //a000-bfff + if(ram_enable) return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff)); return 0x00; } @@ -20,26 +18,29 @@ uint8 Cartridge::MBC5::mmio_read(uint16 addr) { } void Cartridge::MBC5::mmio_write(uint16 addr, uint8 data) { - if(addr >= 0x0000 && addr <= 0x1fff) { + if((addr & 0xe000) == 0x0000) { //0000-1fff ram_enable = (data & 0x0f) == 0x0a; + return; } - if(addr >= 0x2000 && addr <= 0x2fff) { + if((addr & 0xf000) == 0x2000) { //2000-2fff rom_select = (rom_select & 0x0100) | data; + return; } - if(addr >= 0x3000 && addr <= 0x3fff) { + if((addr & 0xf000) == 0x3000) { //3000-3fff rom_select = ((data & 1) << 8) | (rom_select & 0x00ff); + return; } - if(addr >= 0x4000 && addr <= 0x5fff) { + if((addr & 0xe000) == 0x4000) { //4000-5fff ram_select = data & 0x0f; + return; } - if(addr >= 0xa000 && addr <= 0xbfff) { - if(ram_enable) { - cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data); - } + if((addr & 0xe000) == 0xa000) { //a000-bfff + if(ram_enable) cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data); + return; } } diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp index e6739fc8..6978f965 100755 --- a/gameboy/cpu/cpu.cpp +++ b/gameboy/cpu/cpu.cpp @@ -14,7 +14,7 @@ void CPU::Main() { void CPU::main() { while(true) { - //print(disassemble(r[PC]), "\n"); + if(trace) print(disassemble(r[PC]), "\n"); interrupt_test(); uint8 opcode = op_read(r[PC]++); (this->*opcode_table[opcode])(); @@ -27,7 +27,7 @@ void CPU::interrupt_raise(CPU::Interrupt id) { case Interrupt::Stat : status.interrupt_request_stat = 1; break; case Interrupt::Timer : status.interrupt_request_timer = 1; break; case Interrupt::Serial: status.interrupt_request_serial = 1; break; - case Interrupt::Joypad: status.interrupt_request_joypad = 1; break; + case Interrupt::Joypad: status.interrupt_request_joypad = 1; status.stop = false; break; } status.halt = false; } @@ -89,6 +89,7 @@ void CPU::power() { r[DE] = 0x0000; r[HL] = 0x0000; + status.clock = 0; status.halt = false; status.stop = false; @@ -124,7 +125,7 @@ void CPU::power() { status.interrupt_enable_vblank = 0; } -CPU::CPU() { +CPU::CPU() : trace(false) { initialize_opcode_table(); } diff --git a/gameboy/cpu/cpu.hpp b/gameboy/cpu/cpu.hpp index dc92a37c..23b632da 100755 --- a/gameboy/cpu/cpu.hpp +++ b/gameboy/cpu/cpu.hpp @@ -3,6 +3,8 @@ struct CPU : Processor, MMIO { #include "mmio/mmio.hpp" #include "timing/timing.hpp" + bool trace; + enum class Interrupt : unsigned { Vblank, Stat, @@ -12,6 +14,7 @@ struct CPU : Processor, MMIO { }; struct Status { + unsigned clock; bool halt; bool stop; diff --git a/gameboy/cpu/timing/timing.cpp b/gameboy/cpu/timing/timing.cpp index b62c5995..987dc9ea 100755 --- a/gameboy/cpu/timing/timing.cpp +++ b/gameboy/cpu/timing/timing.cpp @@ -13,6 +13,12 @@ #include "opcode.cpp" void CPU::add_clocks(unsigned clocks) { + status.clock += clocks; + if(status.clock >= 4 * 1024 * 1024) { + status.clock -= 4 * 1024 * 1024; + cartridge.mbc3.second(); + } + status.timer0 += clocks; if(status.timer0 >= 16) timer_stage0(); diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp index 777a7980..1a51634b 100755 --- a/gameboy/gameboy.hpp +++ b/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000.07"; + static const char Version[] = "000.08"; } } diff --git a/gameboy/lcd/lcd.cpp b/gameboy/lcd/lcd.cpp index f29efea3..f555e40e 100755 --- a/gameboy/lcd/lcd.cpp +++ b/gameboy/lcd/lcd.cpp @@ -82,13 +82,11 @@ uint16 LCD::read_tile(bool select, unsigned x, unsigned y) { void LCD::render_bg() { unsigned iy = (status.ly + status.scy) & 255; unsigned ix = status.scx, tx = ix & 7; - uint8 mask = 0x80 >> tx; - unsigned data = read_tile(status.bg_tilemap_select, ix, iy), palette; + unsigned data = read_tile(status.bg_tilemap_select, ix, iy); for(unsigned ox = 0; ox < 160; ox++) { - palette = ((data & (mask << 0)) ? 1 : 0); - palette |= ((data & (mask << 8)) ? 2 : 0); - mask = (mask >> 1) | (mask << 7); + uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0) + | ((data & (0x8000 >> tx)) ? 2 : 0); line[ox] = status.bgp[palette]; ix = (ix + 1) & 255; @@ -102,13 +100,11 @@ void LCD::render_window() { if(status.ly - status.wy >= 144U) return; unsigned iy = status.ly - status.wy; unsigned ix = (status.wx - 7) & 255, tx = ix & 7; - uint8 mask = 0x80 >> tx; - unsigned data = read_tile(status.window_tilemap_select, ix, iy), palette; + unsigned data = read_tile(status.window_tilemap_select, ix, iy); for(unsigned ox = 0; ox < 160; ox++) { - palette = ((data & (mask << 0)) ? 1 : 0); - palette |= ((data & (mask << 8)) ? 2 : 0); - mask = (mask >> 1) | (mask << 7); + uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0) + | ((data & (0x8000 >> tx)) ? 2 : 0); if(ox - (status.wx - 7) < 160U) line[ox] = status.bgp[palette]; ix = (ix + 1) & 255; @@ -121,25 +117,53 @@ void LCD::render_window() { void LCD::render_obj() { unsigned obj_size = (status.obj_size == 0 ? 8 : 16); + unsigned sprite[10], sprites = 0; + + //find first ten sprites on this scanline for(unsigned s = 0; s < 40; s++) { unsigned sy = oam[(s << 2) + 0] - 16; unsigned sx = oam[(s << 2) + 1] - 8; - unsigned tile = oam[(s << 2) + 2]; - unsigned attribute = oam[(s << 2) + 3]; + + sy = status.ly - sy; + if(sy >= obj_size) continue; + + sprite[sprites++] = s; + if(sprites == 10) break; + } + + //sort by X-coordinate, when equal, lower address comes first + for(unsigned x = 0; x < sprites; x++) { + for(unsigned y = x + 1; y < sprites; y++) { + signed sx = oam[(sprite[x] << 2) + 1] - 8; + signed sy = oam[(sprite[y] << 2) + 1] - 8; + if(sy < sx) { + sprite[x] ^= sprite[y]; + sprite[y] ^= sprite[x]; + sprite[x] ^= sprite[y]; + } + } + } + + //render backwards, so that first sprite has highest priority + for(signed s = sprites - 1; s >= 0; s--) { + unsigned n = sprite[s] << 2; + unsigned sy = oam[n + 0] - 16; + unsigned sx = oam[n + 1] - 8; + unsigned tile = oam[n + 2]; + unsigned attribute = oam[n + 3]; sy = status.ly - sy; if(sy >= obj_size) continue; if(attribute & 0x40) sy ^= (obj_size - 1); - unsigned tdaddr = tile * 16 + sy * 2; - + unsigned tdaddr = (tile << 4) + (sy << 1); uint8 d0 = vram[tdaddr + 0]; uint8 d1 = vram[tdaddr + 1]; unsigned xflip = attribute & 0x20 ? 7 : 0; for(unsigned tx = 0; tx < 8; tx++) { - uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6); - d0 <<= 1, d1 <<= 1; + uint8 palette = ((d0 & (0x80 >> tx)) ? 1 : 0) + | ((d1 & (0x80 >> tx)) ? 2 : 0); if(palette == 0) continue; palette = status.obp[(bool)(attribute & 0x10)][palette]; diff --git a/ui/general/main-window.cpp b/ui/general/main-window.cpp index 0fc97cf8..d20d9d11 100755 --- a/ui/general/main-window.cpp +++ b/ui/general/main-window.cpp @@ -15,7 +15,7 @@ void MainWindow::create() { settingsVideoSync.setChecked(true); tools.create(*this, "Tools"); -//tools.setEnabled(false); + toolsTraceCPU.create(tools, "Trace CPU"); help.create(*this, "Help"); helpAbout.create(help, "About ..."); @@ -43,6 +43,10 @@ void MainWindow::create() { video.set(Video::Synchronize, mainWindow.settingsVideoSync.checked()); }; + toolsTraceCPU.onTick = []() { + GameBoy::cpu.trace = mainWindow.toolsTraceCPU.checked(); + }; + helpAbout.onTick = []() { MessageWindow::information(mainWindow, { "bgameboy\n\n", diff --git a/ui/general/main-window.hpp b/ui/general/main-window.hpp index f8ca8de5..57b8084f 100755 --- a/ui/general/main-window.hpp +++ b/ui/general/main-window.hpp @@ -8,6 +8,7 @@ struct MainWindow : Window { MenuCheckItem settingsVideoSync; Menu tools; + MenuCheckItem toolsTraceCPU; Menu help; MenuItem helpAbout; From 2d49a4408d55ab11a62105b2669108f694fe41b7 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 5 Jan 2011 20:59:22 +1100 Subject: [PATCH 11/12] Update to release v000r09. byuu says: Changelog: - added skeleton HuC1 (gets to a menu) - added skeleton HuC3 (gets to a menu, this one is supposedly a lot more complex (IR, RTC, etc.)) - added MMM01 emulation --- gameboy/cartridge/cartridge.cpp | 35 ++++++++++++++--- gameboy/cartridge/cartridge.hpp | 6 +++ gameboy/cartridge/huc1/huc1.cpp | 53 +++++++++++++++++++++++++ gameboy/cartridge/huc1/huc1.hpp | 9 +++++ gameboy/cartridge/huc3/huc3.cpp | 53 +++++++++++++++++++++++++ gameboy/cartridge/huc3/huc3.hpp | 9 +++++ gameboy/cartridge/mmm01/mmm01.cpp | 65 +++++++++++++++++++++++++++++++ gameboy/cartridge/mmm01/mmm01.hpp | 12 ++++++ gameboy/gameboy.hpp | 2 +- 9 files changed, 237 insertions(+), 7 deletions(-) create mode 100755 gameboy/cartridge/huc1/huc1.cpp create mode 100755 gameboy/cartridge/huc1/huc1.hpp create mode 100755 gameboy/cartridge/huc3/huc3.cpp create mode 100755 gameboy/cartridge/huc3/huc3.hpp create mode 100755 gameboy/cartridge/mmm01/mmm01.cpp create mode 100755 gameboy/cartridge/mmm01/mmm01.hpp diff --git a/gameboy/cartridge/cartridge.cpp b/gameboy/cartridge/cartridge.cpp index aaf2c30d..6e79803b 100755 --- a/gameboy/cartridge/cartridge.cpp +++ b/gameboy/cartridge/cartridge.cpp @@ -1,5 +1,7 @@ #include +#include + #define CARTRIDGE_CPP namespace GameBoy { @@ -8,9 +10,15 @@ namespace GameBoy { #include "mbc2/mbc2.cpp" #include "mbc3/mbc3.cpp" #include "mbc5/mbc5.cpp" +#include "mmm01/mmm01.cpp" +#include "huc1/huc1.cpp" +#include "huc3/huc3.cpp" Cartridge cartridge; void Cartridge::load(uint8_t *data, unsigned size) { +//uint32_t crc = crc32_calculate(data, size); +//print("CRC32 = ", hex<4>(crc), "\n"); + romdata = new uint8[romsize = size]; memcpy(romdata, data, size); @@ -37,6 +45,9 @@ void Cartridge::load(uint8_t *data, unsigned size) { case 0x06: info.mapper = Mapper::MBC2; info.ram = true; info.battery = true; break; case 0x08: info.mapper = Mapper::MBC0; info.ram = true; break; case 0x09: info.mapper = Mapper::MBC0; info.ram = true; info.battery = true; break; + case 0x0b: info.mapper = Mapper::MMM01; break; + case 0x0c: info.mapper = Mapper::MMM01; info.ram = true; break; + case 0x0d: info.mapper = Mapper::MMM01; info.ram = true; info.battery = true; break; case 0x0f: info.mapper = Mapper::MBC3; info.rtc = true; info.battery = true; break; case 0x10: info.mapper = Mapper::MBC3; info.rtc = true; info.ram = true; info.battery = true; break; case 0x11: info.mapper = Mapper::MBC3; break; @@ -48,8 +59,12 @@ void Cartridge::load(uint8_t *data, unsigned size) { case 0x1c: info.mapper = Mapper::MBC5; info.rumble = true; break; case 0x1d: info.mapper = Mapper::MBC5; info.rumble = true; info.ram = true; break; case 0x1e: info.mapper = Mapper::MBC5; info.rumble = true; info.ram = true; info.battery = true; break; - default: print("Unknown mapper: ", hex<2>(romdata[0x0147]), "\n"); break; + case 0xfc: break; //Pocket Camera + case 0xfd: break; //Bandai TAMA5 + case 0xfe: info.mapper = Mapper::HuC3; break; + case 0xff: info.mapper = Mapper::HuC1; info.ram = true; info.battery = true; break; } + print("Mapper: ", hex<2>(romdata[0x0147]), "\n"); switch(romdata[0x0148]) { default: case 0x00: info.romsize = 2 * 16 * 1024; break; @@ -88,6 +103,8 @@ void Cartridge::unload() { } uint8 Cartridge::rom_read(unsigned addr) { +//if(addr >= 0x028000) print(hex<6>(addr), " - ", romsize, "\n"); + if(addr >= romsize) addr %= romsize; return romdata[addr]; } @@ -115,14 +132,20 @@ void Cartridge::power() { mbc2.power(); mbc3.power(); mbc5.power(); + mmm01.power(); + huc1.power(); + huc3.power(); MMIO *mapper = 0; switch(info.mapper) { default: - case Mapper::MBC0: mapper = &mbc0; break; - case Mapper::MBC1: mapper = &mbc1; break; - case Mapper::MBC2: mapper = &mbc2; break; - case Mapper::MBC3: mapper = &mbc3; break; - case Mapper::MBC5: mapper = &mbc5; break; + case Mapper::MBC0: mapper = &mbc0; break; + case Mapper::MBC1: mapper = &mbc1; break; + case Mapper::MBC2: mapper = &mbc2; break; + case Mapper::MBC3: mapper = &mbc3; break; + case Mapper::MBC5: mapper = &mbc5; break; + case Mapper::MMM01: mapper = &mmm01; break; + case Mapper::HuC1: mapper = &huc1; break; + case Mapper::HuC3: mapper = &huc3; break; } if(mapper) { diff --git a/gameboy/cartridge/cartridge.hpp b/gameboy/cartridge/cartridge.hpp index b04d16a0..633a947f 100755 --- a/gameboy/cartridge/cartridge.hpp +++ b/gameboy/cartridge/cartridge.hpp @@ -4,6 +4,9 @@ struct Cartridge : property { #include "mbc2/mbc2.hpp" #include "mbc3/mbc3.hpp" #include "mbc5/mbc5.hpp" + #include "mmm01/mmm01.hpp" + #include "huc1/huc1.hpp" + #include "huc3/huc3.hpp" enum Mapper : unsigned { MBC0, @@ -11,6 +14,9 @@ struct Cartridge : property { MBC2, MBC3, MBC5, + MMM01, + HuC1, + HuC3, Unknown, }; diff --git a/gameboy/cartridge/huc1/huc1.cpp b/gameboy/cartridge/huc1/huc1.cpp new file mode 100755 index 00000000..8f64b560 --- /dev/null +++ b/gameboy/cartridge/huc1/huc1.cpp @@ -0,0 +1,53 @@ +#ifdef CARTRIDGE_CPP + +uint8 Cartridge::HuC1::mmio_read(uint16 addr) { + if((addr & 0xc000) == 0x0000) { //0000-3fff + return cartridge.rom_read(addr); + } + + if((addr & 0xc000) == 0x4000) { //4000-7fff + return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff)); + } + + if((addr & 0xe000) == 0xa000) { //a000-bfff + if(ram_enable) return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff)); + return 0x00; + } + + return 0x00; +} + +void Cartridge::HuC1::mmio_write(uint16 addr, uint8 data) { + if((addr & 0xe000) == 0x0000) { //0000-1fff + ram_enable = (data & 0x0f) == 0x0a; + return; + } + + if((addr & 0xe000) == 0x2000) { //2000-3fff + rom_select = data; + return; + } + + if((addr & 0xe000) == 0x4000) { //4000-5fff + ram_select = data; + return; + } + + if((addr & 0xe000) == 0x6000) { //6000-7fff + //unknown purpose + return; + } + + if((addr & 0xe000) == 0xa000) { //a000-bfff + if(ram_enable) cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data); + return; + } +} + +void Cartridge::HuC1::power() { + ram_enable = false; + rom_select = 0x01; + ram_select = 0x00; +} + +#endif diff --git a/gameboy/cartridge/huc1/huc1.hpp b/gameboy/cartridge/huc1/huc1.hpp new file mode 100755 index 00000000..f7bcc37d --- /dev/null +++ b/gameboy/cartridge/huc1/huc1.hpp @@ -0,0 +1,9 @@ +struct HuC1 : MMIO { + bool ram_enable; //0000-1fff + uint8 rom_select; //2000-3fff + uint8 ram_select; //4000-5fff + + uint8 mmio_read(uint16 addr); + void mmio_write(uint16 addr, uint8 data); + void power(); +} huc1; diff --git a/gameboy/cartridge/huc3/huc3.cpp b/gameboy/cartridge/huc3/huc3.cpp new file mode 100755 index 00000000..aef74d69 --- /dev/null +++ b/gameboy/cartridge/huc3/huc3.cpp @@ -0,0 +1,53 @@ +#ifdef CARTRIDGE_CPP + +uint8 Cartridge::HuC3::mmio_read(uint16 addr) { + if((addr & 0xc000) == 0x0000) { //0000-3fff + return cartridge.rom_read(addr); + } + + if((addr & 0xc000) == 0x4000) { //4000-7fff + return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff)); + } + + if((addr & 0xe000) == 0xa000) { //a000-bfff + if(ram_enable) return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff)); + return 0x00; + } + + return 0x00; +} + +void Cartridge::HuC3::mmio_write(uint16 addr, uint8 data) { + if((addr & 0xe000) == 0x0000) { //0000-1fff + ram_enable = (data & 0x0f) == 0x0a; + return; + } + + if((addr & 0xe000) == 0x2000) { //2000-3fff + rom_select = data; + return; + } + + if((addr & 0xe000) == 0x4000) { //4000-5fff + ram_select = data; + return; + } + + if((addr & 0xe000) == 0x6000) { //6000-7fff + //unknown purpose + return; + } + + if((addr & 0xe000) == 0xa000) { //a000-bfff + if(ram_enable) cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data); + return; + } +} + +void Cartridge::HuC3::power() { + ram_enable = false; + rom_select = 0x01; + ram_select = 0x00; +} + +#endif diff --git a/gameboy/cartridge/huc3/huc3.hpp b/gameboy/cartridge/huc3/huc3.hpp new file mode 100755 index 00000000..407d3bd4 --- /dev/null +++ b/gameboy/cartridge/huc3/huc3.hpp @@ -0,0 +1,9 @@ +struct HuC3 : MMIO { + bool ram_enable; //0000-1fff + uint8 rom_select; //2000-3fff + uint8 ram_select; //4000-5fff + + uint8 mmio_read(uint16 addr); + void mmio_write(uint16 addr, uint8 data); + void power(); +} huc3; diff --git a/gameboy/cartridge/mmm01/mmm01.cpp b/gameboy/cartridge/mmm01/mmm01.cpp new file mode 100755 index 00000000..7bef9219 --- /dev/null +++ b/gameboy/cartridge/mmm01/mmm01.cpp @@ -0,0 +1,65 @@ +#ifdef CARTRIDGE_CPP + +uint8 Cartridge::MMM01::mmio_read(uint16 addr) { + if((addr & 0x8000) == 0x0000) { + if(rom_mode == 0) return cartridge.rom_read(addr); + } + + if((addr & 0xc000) == 0x0000) { + return cartridge.rom_read(0x8000 + (rom_base << 14) + (addr & 0x3fff)); + } + + if((addr & 0xc000) == 0x4000) { + return cartridge.rom_read(0x8000 + (rom_base << 14) + (rom_select << 14) + (addr & 0x3fff)); + } + + if((addr & 0xe000) == 0xa000) { + if(ram_enable) return cartridge.ram_read((ram_select << 13) + (addr & 0x1fff)); + return 0x00; + } + + return 0x00; +} + +void Cartridge::MMM01::mmio_write(uint16 addr, uint8 data) { + if((addr & 0xe000) == 0x0000) { //0000-1fff + if(rom_mode == 0) { + rom_mode = 1; + } else { + ram_enable = (data & 0x0f) == 0x0a; + } + } + + if((addr & 0xe000) == 0x2000) { //2000-3fff + if(rom_mode == 0) { + rom_base = data & 0x3f; + } else { + rom_select = data; + } + } + + if((addr & 0xe000) == 0x4000) { //4000-5fff + if(rom_mode == 1) { + ram_select = data; + } + } + + if((addr & 0xe000) == 0x6000) { //6000-7fff + //unknown purpose + } + + if((addr & 0xe000) == 0xa000) { //a000-bfff + if(ram_enable) cartridge.ram_write((ram_select << 13) + (addr & 0x1fff), data); + } +} + +void Cartridge::MMM01::power() { + rom_mode = 0; + rom_base = 0x00; + + ram_enable = false; + rom_select = 0x01; + ram_select = 0x00; +} + +#endif diff --git a/gameboy/cartridge/mmm01/mmm01.hpp b/gameboy/cartridge/mmm01/mmm01.hpp new file mode 100755 index 00000000..3474b062 --- /dev/null +++ b/gameboy/cartridge/mmm01/mmm01.hpp @@ -0,0 +1,12 @@ +struct MMM01 : MMIO { + bool rom_mode; + uint8 rom_base; + + bool ram_enable; + uint8 rom_select; + uint8 ram_select; + + uint8 mmio_read(uint16 addr); + void mmio_write(uint16 addr, uint8 data); + void power(); +} mmm01; diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp index 1a51634b..4327602e 100755 --- a/gameboy/gameboy.hpp +++ b/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000.08"; + static const char Version[] = "000.09"; } } From f0796e546e3c2077e7deb58c32f5d159acdb7eb4 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 6 Jan 2011 21:16:07 +1100 Subject: [PATCH 12/12] Update to release v000r10. byuu says: Changelog: - fixed LYC interrupt at LY=0 (fixes Makai Toushi SaGa) - fixed MBC3 ROM bank mapping (fixes Harvest Moon GBC) - added Super Game Boy MLT_REQ support to JOYP, needed for ICD2-R emulation - temporarily changed System::run() to execute only four cycles before exiting for bsnes, will make two versions later - uses actual boot ROMs, has DMG+SGB1 for now. Need SGB2, don't care about CGB. Defaults to SGB1, no way to select just yet. DMG 4-second wait is annoying. Does not force games to act like SGB on bgameboy itself, because that has no ICD2 and fails the required MLT_REQ check --- Makefile | 4 +++- gameboy/Makefile | 5 +---- gameboy/cartridge/cartridge.cpp | 7 ++++--- gameboy/cartridge/cartridge.hpp | 1 + gameboy/cartridge/mbc3/mbc3.cpp | 2 +- gameboy/cpu/cpu.cpp | 7 ++++--- gameboy/cpu/cpu.hpp | 1 + gameboy/cpu/mmio/mmio.cpp | 2 ++ gameboy/cpu/timing/timing.cpp | 3 +++ gameboy/gameboy.hpp | 4 ++-- gameboy/interface/interface.hpp | 2 ++ gameboy/lcd/lcd.cpp | 7 +++---- gameboy/system/bootrom-dmg.cpp | 23 +++++++++++++++++++++++ gameboy/system/bootrom-sgb.cpp | 23 +++++++++++++++++++++++ gameboy/system/system.cpp | 20 ++++++++++++++++++++ gameboy/system/system.hpp | 13 ++++++++++--- ui/main.cpp | 2 +- 17 files changed, 104 insertions(+), 22 deletions(-) create mode 100755 gameboy/system/bootrom-dmg.cpp create mode 100755 gameboy/system/bootrom-sgb.cpp diff --git a/Makefile b/Makefile index e1e5d9e0..64eeb85a 100755 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ c := $(compiler) -std=gnu99 cpp := $(subst cc,++,$(compiler)) -std=gnu++0x flags := -O3 -fomit-frame-pointer -I. link := -objects := +objects := libco # profile-guided instrumentation # flags += -fprofile-generate @@ -44,6 +44,8 @@ compile = \ all: build; +obj/libco.o: libco/libco.c libco/* + include $(gameboy)/Makefile include $(ui)/Makefile diff --git a/gameboy/Makefile b/gameboy/Makefile index b50d3ad4..149614c4 100755 --- a/gameboy/Makefile +++ b/gameboy/Makefile @@ -1,11 +1,8 @@ -gameboy_objects := libco -gameboy_objects += gameboy-system gameboy-scheduler +gameboy_objects := gameboy-system gameboy-scheduler gameboy_objects += gameboy-memory gameboy-cartridge gameboy_objects += gameboy-cpu gameboy-lcd objects += $(gameboy_objects) -obj/libco.o: libco/libco.c libco/* - obj/gameboy-system.o: $(gameboy)/system/system.cpp $(call rwildcard,$(gameboy)/system/) obj/gameboy-scheduler.o: $(gameboy)/scheduler/scheduler.cpp $(call rwildcard,$(gameboy)/scheduler/) obj/gameboy-cartridge.o: $(gameboy)/cartridge/cartridge.cpp $(call rwildcard,$(gameboy)/cartridge/) diff --git a/gameboy/cartridge/cartridge.cpp b/gameboy/cartridge/cartridge.cpp index 6e79803b..7939d68c 100755 --- a/gameboy/cartridge/cartridge.cpp +++ b/gameboy/cartridge/cartridge.cpp @@ -64,7 +64,7 @@ void Cartridge::load(uint8_t *data, unsigned size) { case 0xfe: info.mapper = Mapper::HuC3; break; case 0xff: info.mapper = Mapper::HuC1; info.ram = true; info.battery = true; break; } - print("Mapper: ", hex<2>(romdata[0x0147]), "\n"); +//print("Mapper: ", hex<2>(romdata[0x0147]), "\n"); switch(romdata[0x0148]) { default: case 0x00: info.romsize = 2 * 16 * 1024; break; @@ -103,8 +103,6 @@ void Cartridge::unload() { } uint8 Cartridge::rom_read(unsigned addr) { -//if(addr >= 0x028000) print(hex<6>(addr), " - ", romsize, "\n"); - if(addr >= romsize) addr %= romsize; return romdata[addr]; } @@ -135,7 +133,10 @@ void Cartridge::power() { mmm01.power(); huc1.power(); huc3.power(); + map(); +} +void Cartridge::map() { MMIO *mapper = 0; switch(info.mapper) { default: case Mapper::MBC0: mapper = &mbc0; break; diff --git a/gameboy/cartridge/cartridge.hpp b/gameboy/cartridge/cartridge.hpp index 633a947f..fc0cb15f 100755 --- a/gameboy/cartridge/cartridge.hpp +++ b/gameboy/cartridge/cartridge.hpp @@ -52,6 +52,7 @@ struct Cartridge : property { void ram_write(unsigned addr, uint8 data); void power(); + void map(); Cartridge(); ~Cartridge(); diff --git a/gameboy/cartridge/mbc3/mbc3.cpp b/gameboy/cartridge/mbc3/mbc3.cpp index 709970dc..de7701ea 100755 --- a/gameboy/cartridge/mbc3/mbc3.cpp +++ b/gameboy/cartridge/mbc3/mbc3.cpp @@ -24,7 +24,7 @@ uint8 Cartridge::MBC3::mmio_read(uint16 addr) { } if((addr & 0xc000) == 0x4000) { //4000-7fff - return cartridge.rom_read((rom_select << 13) | (addr & 0x3fff)); + return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff)); } if((addr & 0xe000) == 0xa000) { //a000-bfff diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp index 6978f965..5e00802f 100755 --- a/gameboy/cpu/cpu.cpp +++ b/gameboy/cpu/cpu.cpp @@ -82,9 +82,9 @@ void CPU::power() { for(unsigned n = 0; n < 8192; n++) wram[n] = 0x00; for(unsigned n = 0; n < 128; n++) hram[n] = 0x00; - r[PC] = 0x0100; - r[SP] = 0xfffe; - r[AF] = 0x0100; + r[PC] = 0x0000; + r[SP] = 0x0000; + r[AF] = 0x0000; r[BC] = 0x0000; r[DE] = 0x0000; r[HL] = 0x0000; @@ -102,6 +102,7 @@ void CPU::power() { status.p15 = 0; status.p14 = 0; status.joyp = 0; + status.mlt_req = 0; status.div = 0; diff --git a/gameboy/cpu/cpu.hpp b/gameboy/cpu/cpu.hpp index 23b632da..50ad7264 100755 --- a/gameboy/cpu/cpu.hpp +++ b/gameboy/cpu/cpu.hpp @@ -28,6 +28,7 @@ struct CPU : Processor, MMIO { bool p15; bool p14; uint8 joyp; + uint8 mlt_req; //$ff04 DIV uint8 div; diff --git a/gameboy/cpu/mmio/mmio.cpp b/gameboy/cpu/mmio/mmio.cpp index 610474b9..1e02bd66 100755 --- a/gameboy/cpu/mmio/mmio.cpp +++ b/gameboy/cpu/mmio/mmio.cpp @@ -14,6 +14,7 @@ void CPU::mmio_joyp_poll() { dpad |= system.interface->input_poll((unsigned)Input::Right) << 0; status.joyp = 0x0f; + if(status.p15 == 1 && status.p14 == 1) status.joyp -= status.mlt_req; if(status.p15 == 0) status.joyp &= button ^ 0x0f; if(status.p14 == 0) status.joyp &= dpad ^ 0x0f; if(status.joyp != 0x0f) interrupt_raise(Interrupt::Joypad); @@ -74,6 +75,7 @@ void CPU::mmio_write(uint16 addr, uint8 data) { if(addr == 0xff00) { //JOYP status.p15 = data & 0x20; status.p14 = data & 0x10; + system.interface->joyp_write(status.p15, status.p14); mmio_joyp_poll(); return; } diff --git a/gameboy/cpu/timing/timing.cpp b/gameboy/cpu/timing/timing.cpp index 987dc9ea..3a400d06 100755 --- a/gameboy/cpu/timing/timing.cpp +++ b/gameboy/cpu/timing/timing.cpp @@ -13,6 +13,9 @@ #include "opcode.cpp" void CPU::add_clocks(unsigned clocks) { + system.clocks_executed += clocks; + scheduler.exit(); + status.clock += clocks; if(status.clock >= 4 * 1024 * 1024) { status.clock -= 4 * 1024 * 1024; diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp index 4327602e..4a5e6773 100755 --- a/gameboy/gameboy.hpp +++ b/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000.09"; + static const char Version[] = "000.10"; } } @@ -43,9 +43,9 @@ namespace GameBoy { inline Processor() : thread(0) {} }; + #include #include #include - #include #include #include #include diff --git a/gameboy/interface/interface.hpp b/gameboy/interface/interface.hpp index e43a4a16..372cbd7f 100755 --- a/gameboy/interface/interface.hpp +++ b/gameboy/interface/interface.hpp @@ -1,5 +1,7 @@ class Interface { public: + virtual void joyp_write(bool p15, bool p14) {} + virtual void video_refresh(const uint8_t *data) {} virtual void audio_sample(signed left, signed right) {} virtual void input_poll() {} diff --git a/gameboy/lcd/lcd.cpp b/gameboy/lcd/lcd.cpp index f555e40e..33143362 100755 --- a/gameboy/lcd/lcd.cpp +++ b/gameboy/lcd/lcd.cpp @@ -30,19 +30,18 @@ void LCD::add_clocks(unsigned clocks) { void LCD::scanline() { status.lx -= 456; - status.ly++; + if(++status.ly == 154) frame(); if(status.interrupt_lyc == true) { if(status.ly == status.lyc) cpu.interrupt_raise(CPU::Interrupt::Stat); } + if(status.ly < 144) render(); + if(status.ly == 144) { cpu.interrupt_raise(CPU::Interrupt::Vblank); if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat); } - if(status.ly == 154) frame(); - - if(status.ly < 144) render(); } void LCD::frame() { diff --git a/gameboy/system/bootrom-dmg.cpp b/gameboy/system/bootrom-dmg.cpp new file mode 100755 index 00000000..5fb38844 --- /dev/null +++ b/gameboy/system/bootrom-dmg.cpp @@ -0,0 +1,23 @@ +#ifdef SYSTEM_CPP + +//MD5SUM = 32fbbd84168d3482956eb3c5051637f5 +const uint8_t System::BootROM::dmg[256] = { + 0x31,0xfe,0xff,0xaf,0x21,0xff,0x9f,0x32,0xcb,0x7c,0x20,0xfb,0x21,0x26,0xff,0x0e, + 0x11,0x3e,0x80,0x32,0xe2,0x0c,0x3e,0xf3,0xe2,0x32,0x3e,0x77,0x77,0x3e,0xfc,0xe0, + 0x47,0x11,0x04,0x01,0x21,0x10,0x80,0x1a,0xcd,0x95,0x00,0xcd,0x96,0x00,0x13,0x7b, + 0xfe,0x34,0x20,0xf3,0x11,0xd8,0x00,0x06,0x08,0x1a,0x13,0x22,0x23,0x05,0x20,0xf9, + 0x3e,0x19,0xea,0x10,0x99,0x21,0x2f,0x99,0x0e,0x0c,0x3d,0x28,0x08,0x32,0x0d,0x20, + 0xf9,0x2e,0x0f,0x18,0xf3,0x67,0x3e,0x64,0x57,0xe0,0x42,0x3e,0x91,0xe0,0x40,0x04, + 0x1e,0x02,0x0e,0x0c,0xf0,0x44,0xfe,0x90,0x20,0xfa,0x0d,0x20,0xf7,0x1d,0x20,0xf2, + 0x0e,0x13,0x24,0x7c,0x1e,0x83,0xfe,0x62,0x28,0x06,0x1e,0xc1,0xfe,0x64,0x20,0x06, + 0x7b,0xe2,0x0c,0x3e,0x87,0xe2,0xf0,0x42,0x90,0xe0,0x42,0x15,0x20,0xd2,0x05,0x20, + 0x4f,0x16,0x20,0x18,0xcb,0x4f,0x06,0x04,0xc5,0xcb,0x11,0x17,0xc1,0xcb,0x11,0x17, + 0x05,0x20,0xf5,0x22,0x23,0x22,0x23,0xc9,0xce,0xed,0x66,0x66,0xcc,0x0d,0x00,0x0b, + 0x03,0x73,0x00,0x83,0x00,0x0c,0x00,0x0d,0x00,0x08,0x11,0x1f,0x88,0x89,0x00,0x0e, + 0xdc,0xcc,0x6e,0xe6,0xdd,0xdd,0xd9,0x99,0xbb,0xbb,0x67,0x63,0x6e,0x0e,0xec,0xcc, + 0xdd,0xdc,0x99,0x9f,0xbb,0xb9,0x33,0x3e,0x3c,0x42,0xb9,0xa5,0xb9,0xa5,0x42,0x3c, + 0x21,0x04,0x01,0x11,0xa8,0x00,0x1a,0x13,0xbe,0x20,0xfe,0x23,0x7d,0xfe,0x34,0x20, + 0xf5,0x06,0x19,0x78,0x86,0x23,0x05,0x20,0xfb,0x86,0x20,0xfe,0x3e,0x01,0xe0,0x50, +}; + +#endif diff --git a/gameboy/system/bootrom-sgb.cpp b/gameboy/system/bootrom-sgb.cpp new file mode 100755 index 00000000..ac58529d --- /dev/null +++ b/gameboy/system/bootrom-sgb.cpp @@ -0,0 +1,23 @@ +#ifdef SYSTEM_CPP + +//MD5SUM = d574d4f9c12f305074798f54c091a8b4 +const uint8_t System::BootROM::sgb[256] = { + 0x31,0xfe,0xff,0x3e,0x30,0xe0,0x00,0xaf,0x21,0xff,0x9f,0x32,0xcb,0x7c,0x20,0xfb, + 0x21,0x26,0xff,0x0e,0x11,0x3e,0x80,0x32,0xe2,0x0c,0x3e,0xf3,0xe2,0x32,0x3e,0x77, + 0x77,0x3e,0xfc,0xe0,0x47,0x21,0x5f,0xc0,0x0e,0x08,0xaf,0x32,0x0d,0x20,0xfc,0x11, + 0x4f,0x01,0x3e,0xfb,0x0e,0x06,0xf5,0x06,0x00,0x1a,0x1b,0x32,0x80,0x47,0x0d,0x20, + 0xf8,0x32,0xf1,0x32,0x0e,0x0e,0xd6,0x02,0xfe,0xef,0x20,0xea,0x11,0x04,0x01,0x21, + 0x10,0x80,0x1a,0xcd,0xd3,0x00,0xcd,0xd4,0x00,0x13,0x7b,0xfe,0x34,0x20,0xf3,0x11, + 0xe6,0x00,0x06,0x08,0x1a,0x13,0x22,0x23,0x05,0x20,0xf9,0x3e,0x19,0xea,0x10,0x99, + 0x21,0x2f,0x99,0x0e,0x0c,0x3d,0x28,0x08,0x32,0x0d,0x20,0xf9,0x2e,0x0f,0x18,0xf3, + 0x3e,0x91,0xe0,0x40,0x21,0x00,0xc0,0x0e,0x00,0x3e,0x00,0xe2,0x3e,0x30,0xe2,0x06, + 0x10,0x1e,0x08,0x2a,0x57,0xcb,0x42,0x3e,0x10,0x20,0x02,0x3e,0x20,0xe2,0x3e,0x30, + 0xe2,0xcb,0x1a,0x1d,0x20,0xef,0x05,0x20,0xe8,0x3e,0x20,0xe2,0x3e,0x30,0xe2,0xcd, + 0xc2,0x00,0x7d,0xfe,0x60,0x20,0xd2,0x0e,0x13,0x3e,0xc1,0xe2,0x0c,0x3e,0x07,0xe2, + 0x18,0x3a,0x16,0x04,0xf0,0x44,0xfe,0x90,0x20,0xfa,0x1e,0x00,0x1d,0x20,0xfd,0x15, + 0x20,0xf2,0xc9,0x4f,0x06,0x04,0xc5,0xcb,0x11,0x17,0xc1,0xcb,0x11,0x17,0x05,0x20, + 0xf5,0x22,0x23,0x22,0x23,0xc9,0x3c,0x42,0xb9,0xa5,0xb9,0xa5,0x42,0x3c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3e,0x01,0xe0,0x50, +}; + +#endif diff --git a/gameboy/system/system.cpp b/gameboy/system/system.cpp index 360c4a3b..4e959d4b 100755 --- a/gameboy/system/system.cpp +++ b/gameboy/system/system.cpp @@ -3,8 +3,23 @@ #define SYSTEM_CPP namespace GameBoy { +#include "bootrom-dmg.cpp" +#include "bootrom-sgb.cpp" System system; +uint8 System::mmio_read(uint16 addr) { + if((addr & 0xff00) == 0x0000) { + return BootROM::sgb[addr]; + } + return 0x00; +} + +void System::mmio_write(uint16 addr, uint8 data) { + if(addr == 0xff50) { + if(data == 0x01) cartridge.map(); + } +} + void System::init(Interface *interface_) { interface = interface_; } @@ -15,6 +30,11 @@ void System::power() { cpu.power(); lcd.power(); scheduler.init(); + + for(unsigned n = 0x0000; n <= 0x00ff; n++) bus.mmio[n] = this; + bus.mmio[0xff50] = this; + + system.clocks_executed = 0; } void System::run() { diff --git a/gameboy/system/system.hpp b/gameboy/system/system.hpp index 4bbb7040..7d54d529 100755 --- a/gameboy/system/system.hpp +++ b/gameboy/system/system.hpp @@ -4,14 +4,21 @@ enum class Input : unsigned { Up, Down, Left, Right, B, A, Select, Start, }; -class System { -public: +struct System : MMIO { + struct BootROM { + static const uint8 dmg[256]; + static const uint8 sgb[256]; + } bootROM; + + uint8 mmio_read(uint16 addr); + void mmio_write(uint16 addr, uint8 data); + void init(Interface*); void power(); void run(); -//private: Interface *interface; + unsigned clocks_executed; }; #include diff --git a/ui/main.cpp b/ui/main.cpp index 4fcc6e4c..5aebd270 100755 --- a/ui/main.cpp +++ b/ui/main.cpp @@ -46,7 +46,7 @@ void Application::main(int argc, char **argv) { OS::run(); if(GameBoy::cartridge.loaded()) { - GameBoy::system.run(); + for(unsigned n = 0; n < 1024 * 1024; n++) GameBoy::system.run(); } } }